From cb7fa00a6f43776e03137038a04732c10522bb5d Mon Sep 17 00:00:00 2001 From: Alessio Gravili Date: Wed, 14 Aug 2024 16:40:31 -0400 Subject: [PATCH] fix: use tsx instead of swc as default bin script transpiler, as swc errors when it encounters 'next/cache' (#7681) Fixes https://github.com/payloadcms/payload/issues/7677 - Payload bin scripts were not properly working on windows - Use tsx by default instead of swc, as swc does not handle next/cache imports without the .js at the end - Support other node runtimes through --disable-transpile flag --- docs/admin/components.mdx | 2 +- docs/local-api/outside-nextjs.mdx | 21 ++++++++-- packages/graphql/bin.js | 55 ++++++++++++++++++++----- packages/graphql/package.json | 2 +- packages/payload/bin.js | 62 +++++++++++++++++------------ packages/payload/package.json | 2 +- packages/payload/src/bin/index.ts | 7 ++-- packages/payload/src/config/find.ts | 5 +-- pnpm-lock.yaml | 13 +++--- 9 files changed, 111 insertions(+), 58 deletions(-) diff --git a/docs/admin/components.mdx b/docs/admin/components.mdx index ccab84115..b87003be2 100644 --- a/docs/admin/components.mdx +++ b/docs/admin/components.mdx @@ -145,7 +145,7 @@ Instead, we utilize component paths to reference React Components. This method e When constructing the `ClientConfig`, Payload uses the component paths as keys to fetch the corresponding React Component imports from the Import Map. It then substitutes the `PayloadComponent` with a `MappedComponent`. A `MappedComponent` includes the React Component and additional metadata, such as whether it's a server or a client component and which props it should receive. These components are then rendered through the `` component within the Payload Admin Panel. -Import maps are regenerated whenever you modify any element related to component paths. This regeneration occurs at startup and whenever Hot Module Replacement (HMR) runs. If the import maps fail to regenerate during HMR, you can restart your application and execute the `payload generate:importmap` command to manually create a new import map. +Import maps are regenerated whenever you modify any element related to component paths. This regeneration occurs at startup and whenever Hot Module Replacement (HMR) runs. If the import maps fail to regenerate during HMR, you can restart your application and execute the `payload generate:importmap` command to manually create a new import map. If you encounter any errors running this command, see the [Troubleshooting](/docs/beta/local-api/outside-nextjs#troubleshooting) section. ### Component paths in external packages diff --git a/docs/local-api/outside-nextjs.mdx b/docs/local-api/outside-nextjs.mdx index 11073a5d9..de24b5279 100644 --- a/docs/local-api/outside-nextjs.mdx +++ b/docs/local-api/outside-nextjs.mdx @@ -61,14 +61,27 @@ payload run src/seed.ts The `payload run` command does two things for you: 1. It loads the environment variables the same way Next.js loads them, eliminating the need for additional dependencies like `dotenv`. The usage of `dotenv` is not recommended, as Next.js loads environment variables differently. By using `payload run`, you ensure consistent environment variable handling across your Payload and Next.js setup. -2. It initializes swc, allowing direct execution of TypeScript files without requiring tools like tsx or ts-node. +2. It initializes tsx, allowing direct execution of TypeScript files manually installing tools like tsx or ts-node. ### Troubleshooting -If you encounter import-related errors, try running the script in TSX mode: +If you encounter import-related errors, you have 2 options: +#### Option 1: enable swc mode by appending `--use-swc` to the `payload` command: + +Example: ```sh -payload run src/seed.ts --use-tsx +payload run src/seed.ts --use-swc ``` -Note: Install tsx in your project first. Be aware that TSX mode is slower than the default swc mode, so only use it if necessary. +Note: Install @swc-node/register in your project first. While swc mode is faster than the default tsx mode, it might break for some imports. + +#### Option 2: use an alternative runtime like bun + +While we do not guarantee support for alternative runtimes, you are free to use them and disable payloads own transpilation by appending the `--disable-transpilation` flag to the `payload` command: + +```sh +bunx --bun payload run src/seed.ts --disable-transpile +``` + +You will need to have bun installed on your system for this to work. diff --git a/packages/graphql/bin.js b/packages/graphql/bin.js index e9bec7ed3..177f6550c 100755 --- a/packages/graphql/bin.js +++ b/packages/graphql/bin.js @@ -1,21 +1,54 @@ -#!/usr/bin/env node +#!/usr/bin/env node --no-deprecation -import { register } from 'node:module' import path from 'node:path' import { fileURLToPath, pathToFileURL } from 'node:url' -// Allow disabling SWC for debugging -if (process.env.DISABLE_SWC !== 'true') { +const useSwc = process.argv.includes('--use-swc') +const disableTranspile = process.argv.includes('--disable-transpile') + +if (disableTranspile) { + // Remove --disable-transpile from arguments + process.argv = process.argv.filter((arg) => arg !== '--disable-transpile') + + const start = async () => { + const { bin } = await import('./dist/bin/index.js') + await bin() + } + + void start() +} else { const filename = fileURLToPath(import.meta.url) const dirname = path.dirname(filename) const url = pathToFileURL(dirname).toString() + '/' - register('@swc-node/register/esm', url) -} + if (!useSwc) { + const start = async () => { + // Use tsx + let tsImport = (await import('tsx/esm/api')).tsImport -const start = async () => { - const { bin } = await import('./dist/bin/index.js') - await bin() -} + const { bin } = await tsImport('./dist/bin/index.js', url) + await bin() + } -void start() + void start() + } else if (useSwc) { + const { register } = await import('node:module') + // Remove --use-swc from arguments + process.argv = process.argv.filter((arg) => arg !== '--use-swc') + + try { + register('@swc-node/register/esm', url) + } catch (_) { + console.error( + '@swc-node/register is not installed. Please install @swc-node/register in your project, if you want to use swc in payload run.', + ) + } + + const start = async () => { + const { bin } = await import('./dist/bin/index.js') + await bin() + } + + void start() + } +} diff --git a/packages/graphql/package.json b/packages/graphql/package.json index f1d09a695..702031545 100644 --- a/packages/graphql/package.json +++ b/packages/graphql/package.json @@ -43,7 +43,7 @@ "dependencies": { "graphql-scalars": "1.22.2", "pluralize": "8.0.0", - "@swc-node/register": "1.10.9", + "tsx": "4.17.0", "ts-essentials": "7.0.3" }, "devDependencies": { diff --git a/packages/payload/bin.js b/packages/payload/bin.js index 9ac3b30a4..177f6550c 100755 --- a/packages/payload/bin.js +++ b/packages/payload/bin.js @@ -1,18 +1,14 @@ #!/usr/bin/env node --no-deprecation -import { register } from 'node:module' import path from 'node:path' import { fileURLToPath, pathToFileURL } from 'node:url' -const useTsx = process.argv.includes('--use-tsx') +const useSwc = process.argv.includes('--use-swc') +const disableTranspile = process.argv.includes('--disable-transpile') -// Allow disabling SWC/TSX for debugging -if (process.env.DISABLE_SWC !== 'true' && !useTsx) { - const filename = fileURLToPath(import.meta.url) - const dirname = path.dirname(filename) - const url = pathToFileURL(dirname).toString() + '/' - - register('@swc-node/register/esm', url) +if (disableTranspile) { + // Remove --disable-transpile from arguments + process.argv = process.argv.filter((arg) => arg !== '--disable-transpile') const start = async () => { const { bin } = await import('./dist/bin/index.js') @@ -20,25 +16,39 @@ if (process.env.DISABLE_SWC !== 'true' && !useTsx) { } void start() -} else if (useTsx) { - // Remove --use-tsx from arguments - process.argv = process.argv.filter((arg) => arg !== '--use-tsx') +} else { + const filename = fileURLToPath(import.meta.url) + const dirname = path.dirname(filename) + const url = pathToFileURL(dirname).toString() + '/' - const start = async () => { - // Use tsx - let tsImport - try { - tsImport = (await import('tsx/esm/api')).tsImport - } catch (_) { - console.error( - 'tsx is not installed. Please install tsx in your project, if you want to use tsx in payload run.', - ) - return + if (!useSwc) { + const start = async () => { + // Use tsx + let tsImport = (await import('tsx/esm/api')).tsImport + + const { bin } = await tsImport('./dist/bin/index.js', url) + await bin() } - const { bin } = await tsImport('./dist/bin/index.js', import.meta.url) - await bin() - } + void start() + } else if (useSwc) { + const { register } = await import('node:module') + // Remove --use-swc from arguments + process.argv = process.argv.filter((arg) => arg !== '--use-swc') - void start() + try { + register('@swc-node/register/esm', url) + } catch (_) { + console.error( + '@swc-node/register is not installed. Please install @swc-node/register in your project, if you want to use swc in payload run.', + ) + } + + const start = async () => { + const { bin } = await import('./dist/bin/index.js') + await bin() + } + + void start() + } } diff --git a/packages/payload/package.json b/packages/payload/package.json index 978c5e79d..8cde6d501 100644 --- a/packages/payload/package.json +++ b/packages/payload/package.json @@ -86,7 +86,7 @@ "dependencies": { "@next/env": "^15.0.0-canary.104", "@payloadcms/translations": "workspace:*", - "@swc-node/register": "1.10.9", + "tsx": "4.17.0", "ajv": "8.14.0", "bson-objectid": "2.0.4", "ci-info": "^4.0.0", diff --git a/packages/payload/src/bin/index.ts b/packages/payload/src/bin/index.ts index 933c2d146..227d9bb56 100755 --- a/packages/payload/src/bin/index.ts +++ b/packages/payload/src/bin/index.ts @@ -1,4 +1,5 @@ import minimist from 'minimist' +import { pathToFileURL } from 'node:url' import path from 'path' import type { BinScript } from '../config/types.js' @@ -29,7 +30,7 @@ export const bin = async () => { process.argv = [process.argv[0], process.argv[1], ...args._.slice(2)] try { - await import(absoluteScriptPath) + await import(pathToFileURL(absoluteScriptPath).toString()) } catch (error) { console.error(`Error running script: ${absoluteScriptPath}`) console.error(error) @@ -42,7 +43,7 @@ export const bin = async () => { } const configPath = findConfig() - const configPromise = await import(configPath) + const configPromise = await import(pathToFileURL(configPath).toString()) let config = await configPromise if (config.default) config = await config.default @@ -52,7 +53,7 @@ export const bin = async () => { if (userBinScript) { try { - const script: BinScript = await import(userBinScript.scriptPath) + const script: BinScript = await import(pathToFileURL(userBinScript.scriptPath).toString()) await script(config) } catch (err) { console.log(`Could not find associated bin script for the ${userBinScript.key} command`) diff --git a/packages/payload/src/config/find.ts b/packages/payload/src/config/find.ts index e4745ff83..1671da8e6 100644 --- a/packages/payload/src/config/find.ts +++ b/packages/payload/src/config/find.ts @@ -22,10 +22,7 @@ const getTSConfigPaths = (): { const rootConfigDir = path.resolve(tsConfigDir, tsConfig.compilerOptions.baseUrl || '') const srcPath = tsConfig.compilerOptions?.rootDir || path.resolve(process.cwd(), 'src') const outPath = tsConfig.compilerOptions?.outDir || path.resolve(process.cwd(), 'dist') - let configPath = path.resolve( - rootConfigDir, - tsConfig.compilerOptions?.paths?.['@payload-config']?.[0], - ) + let configPath = tsConfig.compilerOptions?.paths?.['@payload-config']?.[0] if (configPath) { configPath = path.resolve(rootConfigDir, configPath) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aa62450b8..25a0d7f02 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -549,9 +549,6 @@ importers: packages/graphql: dependencies: - '@swc-node/register': - specifier: 1.10.9 - version: 1.10.9(@swc/core@1.7.10(@swc/helpers@0.5.12))(@swc/types@0.1.12)(typescript@5.5.4) graphql: specifier: ^16.8.1 version: 16.9.0 @@ -564,6 +561,9 @@ importers: ts-essentials: specifier: 7.0.3 version: 7.0.3(typescript@5.5.4) + tsx: + specifier: 4.17.0 + version: 4.17.0 devDependencies: '@payloadcms/eslint-config': specifier: workspace:* @@ -727,9 +727,6 @@ importers: '@payloadcms/translations': specifier: workspace:* version: link:../translations - '@swc-node/register': - specifier: 1.10.9 - version: 1.10.9(@swc/core@1.7.10(@swc/helpers@0.5.12))(@swc/types@0.1.12)(typescript@5.5.4) ajv: specifier: 8.14.0 version: 8.14.0 @@ -796,6 +793,9 @@ importers: ts-essentials: specifier: 7.0.3 version: 7.0.3(typescript@5.5.4) + tsx: + specifier: 4.17.0 + version: 4.17.0 uuid: specifier: 10.0.0 version: 10.0.0 @@ -7096,7 +7096,6 @@ packages: libsql@0.3.19: resolution: {integrity: sha512-Aj5cQ5uk/6fHdmeW0TiXK42FqUlwx7ytmMLPSaUQPin5HKKKuUPD62MAbN4OEweGBBI7q1BekoEN4gPUEL6MZA==} - cpu: [x64, arm64, wasm32] os: [darwin, linux, win32] lie@3.1.1: