From 69cc5c954b1de273ef603562c6a1a470128c504a Mon Sep 17 00:00:00 2001 From: Ty Hopp Date: Wed, 16 Nov 2022 12:08:55 +0800 Subject: [PATCH 01/35] feat(gatsby): ESM in gatsby-config.mjs proof of concept --- .../babel-preset-gatsby-package/lib/index.js | 1 + .../babel-preset-gatsby-package/package.json | 2 +- packages/gatsby/package.json | 2 +- .../gatsby/src/bootstrap/get-config-file.ts | 21 +++++++++++++++++++ 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/packages/babel-preset-gatsby-package/lib/index.js b/packages/babel-preset-gatsby-package/lib/index.js index 3bc01d2d5a52c..f6a388e673c82 100644 --- a/packages/babel-preset-gatsby-package/lib/index.js +++ b/packages/babel-preset-gatsby-package/lib/index.js @@ -66,6 +66,7 @@ function preset(context, options = {}) { shippedProposals: true, modules: esm ? false : `commonjs`, bugfixes: esm, + exclude: [`proposal-dynamic-import`], }, browser ? browserConfig : nodeConfig ), diff --git a/packages/babel-preset-gatsby-package/package.json b/packages/babel-preset-gatsby-package/package.json index 8a09b5fdfeead..d39c88af1380f 100644 --- a/packages/babel-preset-gatsby-package/package.json +++ b/packages/babel-preset-gatsby-package/package.json @@ -44,6 +44,6 @@ "scripts": { "build": "", "prepare": "cross-env NODE_ENV=production npm run build", - "watch": "babel -w src --out-dir dist/ --ignore \"**/__tests__\" --extensions \".ts,.js\"" + "watch": "babel -w lib --out-dir dist/ --ignore \"**/__tests__\" --extensions \".ts,.js\"" } } diff --git a/packages/gatsby/package.json b/packages/gatsby/package.json index aab6464e48560..eb15b4c9e1470 100644 --- a/packages/gatsby/package.json +++ b/packages/gatsby/package.json @@ -195,7 +195,7 @@ "@types/string-similarity": "^4.0.0", "@types/tmp": "^0.2.0", "@types/webpack-virtual-modules": "^0.1.1", - "babel-preset-gatsby-package": "^3.1.0-next.0", + "babel-preset-gatsby-package": "file:../babel-reset-gatsby-package", "copyfiles": "^2.3.0", "cross-env": "^7.0.3", "documentation": "^13.1.0", diff --git a/packages/gatsby/src/bootstrap/get-config-file.ts b/packages/gatsby/src/bootstrap/get-config-file.ts index f89c59a3738aa..7cd0f610f3b45 100644 --- a/packages/gatsby/src/bootstrap/get-config-file.ts +++ b/packages/gatsby/src/bootstrap/get-config-file.ts @@ -5,6 +5,7 @@ import path from "path" import { sync as existsSync } from "fs-exists-cached" import { COMPILED_CACHE_DIR } from "../utils/parcel/compile-gatsby-files" import { isNearMatch } from "../utils/is-near-match" +import { pathToFileURL } from "url" export async function getConfigFile( siteDirectory: string, @@ -97,6 +98,26 @@ export async function getConfigFile( }) } + // TODO: Maybe move this to a better spot in this function, let's test it here for now + if (nearMatch && nearMatch.endsWith(`.mjs`)) { + try { + configFilePath = path.join(siteDirectory, nearMatch) + const url = pathToFileURL(configFilePath) + const importedModule = await import(url.href) + + if (!importedModule.default) { + // TODO: Structured error + console.error(`No default export found in gatsby-config.mjs`) + } + + configModule = importedModule.default + return { configModule, configFilePath } + } catch (cause) { + // TODO: Structured error + throw new Error(`Failed to load .mjs config file`, { cause }) + } + } + // gatsby-config is misnamed if (nearMatch) { const isTSX = nearMatch.endsWith(`.tsx`) From d8e731706361e6a1381d82df38b92b73aa1e8238 Mon Sep 17 00:00:00 2001 From: Ty Hopp Date: Wed, 16 Nov 2022 12:58:58 +0800 Subject: [PATCH 02/35] Explore gatsby-node.mjs support --- .../src/bootstrap/load-plugins/index.ts | 9 +- .../src/bootstrap/load-plugins/validate.ts | 34 ++-- .../src/bootstrap/resolve-module-exports.ts | 21 ++- packages/gatsby/src/utils/api-runner-node.js | 146 +++++++++--------- packages/gatsby/src/utils/jobs/manager.ts | 43 +++--- .../src/utils/parcel/compile-gatsby-files.ts | 6 +- .../gatsby/src/utils/require-gatsby-plugin.ts | 40 ++++- 7 files changed, 181 insertions(+), 118 deletions(-) diff --git a/packages/gatsby/src/bootstrap/load-plugins/index.ts b/packages/gatsby/src/bootstrap/load-plugins/index.ts index 7d743760e5cc4..837a8ceba300b 100644 --- a/packages/gatsby/src/bootstrap/load-plugins/index.ts +++ b/packages/gatsby/src/bootstrap/load-plugins/index.ts @@ -40,11 +40,10 @@ export async function loadPlugins( // Work out which plugins use which APIs, including those which are not // valid Gatsby APIs, aka 'badExports' - const x = collatePluginAPIs({ currentAPIs, flattenedPlugins: pluginArray }) - - // From this point on, these are fully-resolved plugins. - let flattenedPlugins = x.flattenedPlugins - const badExports = x.badExports + let { flattenedPlugins, badExports } = await collatePluginAPIs({ + currentAPIs, + flattenedPlugins: pluginArray, + }) // Show errors for any non-Gatsby APIs exported from plugins await handleBadExports({ currentAPIs, badExports }) diff --git a/packages/gatsby/src/bootstrap/load-plugins/validate.ts b/packages/gatsby/src/bootstrap/load-plugins/validate.ts index 7dffe0706b5cf..0d7f44ce82df9 100644 --- a/packages/gatsby/src/bootstrap/load-plugins/validate.ts +++ b/packages/gatsby/src/bootstrap/load-plugins/validate.ts @@ -403,13 +403,16 @@ export async function validateConfigPluginsOptions( /** * Identify which APIs each plugin exports */ -export function collatePluginAPIs({ +export async function collatePluginAPIs({ currentAPIs, flattenedPlugins, }: { currentAPIs: ICurrentAPIs flattenedPlugins: Array> -}): { flattenedPlugins: Array; badExports: IEntryMap } { +}): Promise<{ + flattenedPlugins: Array + badExports: IEntryMap +}> { // Get a list of bad exports const badExports: IEntryMap = { node: [], @@ -417,7 +420,7 @@ export function collatePluginAPIs({ ssr: [], } - flattenedPlugins.forEach(plugin => { + for (const plugin of flattenedPlugins) { plugin.nodeAPIs = [] plugin.browserAPIs = [] plugin.ssrAPIs = [] @@ -425,16 +428,23 @@ export function collatePluginAPIs({ // Discover which APIs this plugin implements and store an array against // the plugin node itself *and* in an API to plugins map for faster lookups // later. - const pluginNodeExports = resolveModuleExports( - plugin.resolvedCompiledGatsbyNode ?? `${plugin.resolve}/gatsby-node`, - { - mode: `require`, - } - ) - const pluginBrowserExports = resolveModuleExports( + let pluginNodeExports + + try { + pluginNodeExports = await resolveModuleExports( + plugin.resolvedCompiledGatsbyNode ?? `${plugin.resolve}/gatsby-node`, + { + mode: `require`, + } + ) + } catch (cause) { + throw new Error(`ABC Failed to import gatsby-node`, { cause }) + } + + const pluginBrowserExports = await resolveModuleExports( `${plugin.resolve}/gatsby-browser` ) - const pluginSSRExports = resolveModuleExports( + const pluginSSRExports = await resolveModuleExports( `${plugin.resolve}/gatsby-ssr` ) @@ -461,7 +471,7 @@ export function collatePluginAPIs({ getBadExports(plugin, pluginSSRExports, currentAPIs.ssr) ) // Collate any bad exports } - }) + } return { flattenedPlugins: flattenedPlugins as Array, diff --git a/packages/gatsby/src/bootstrap/resolve-module-exports.ts b/packages/gatsby/src/bootstrap/resolve-module-exports.ts index c81d0763a5b1f..8a87a10a7cdb4 100644 --- a/packages/gatsby/src/bootstrap/resolve-module-exports.ts +++ b/packages/gatsby/src/bootstrap/resolve-module-exports.ts @@ -6,6 +6,7 @@ import report from "gatsby-cli/lib/reporter" import { babelParseToAst } from "../utils/babel-parse-to-ast" import { testRequireError } from "../utils/test-require-error" import { resolveModule } from "../utils/module-resolver" +import { pathToFileURL } from "url" const staticallyAnalyzeExports = ( modulePath: string, @@ -186,12 +187,15 @@ https://gatsby.dev/no-mixed-modules * @param mode * @param resolver */ -export const resolveModuleExports = ( +export async function resolveModuleExports( modulePath: string, { mode = `analysis`, resolver = resolveModule } = {} -): Array => { +): Promise> { + let failedWithoutRequireError = false + if (mode === `require`) { let absPath: string | undefined + try { absPath = require.resolve(modulePath) return Object.keys(require(modulePath)).filter( @@ -202,11 +206,24 @@ export const resolveModuleExports = ( // if module exists, but requiring it cause errors, // show the error to the user and terminate build report.panic(`Error in "${absPath}":`, e) + } else { + failedWithoutRequireError = true } } } else { return staticallyAnalyzeExports(modulePath, resolver) } + // TODO: Maybe move this to a better spot, let's test it here for now + if (failedWithoutRequireError) { + // Let's see if it's a gatsby-node.mjs file + const url = pathToFileURL(`${modulePath}.mjs`) + const importedModule = await import(url.href) + const importedModuleExports = Object.keys(importedModule).filter( + exportName => exportName !== `__esModule` + ) + return importedModuleExports + } + return [] } diff --git a/packages/gatsby/src/utils/api-runner-node.js b/packages/gatsby/src/utils/api-runner-node.js index f78512253e541..fe386d74a1d85 100644 --- a/packages/gatsby/src/utils/api-runner-node.js +++ b/packages/gatsby/src/utils/api-runner-node.js @@ -293,7 +293,7 @@ const getUninitializedCache = plugin => { const availableActionsCache = new Map() let publicPath const runAPI = async (plugin, api, args, activity) => { - const gatsbyNode = requireGatsbyPlugin(plugin, `gatsby-node`) + const gatsbyNode = await requireGatsbyPlugin(plugin, `gatsby-node`) if (gatsbyNode[api]) { const parentSpan = args && args.parentSpan @@ -616,83 +616,87 @@ function apiRunnerNode(api, args = {}, { pluginSource, activity } = {}) { return null } - const gatsbyNode = requireGatsbyPlugin(plugin, `gatsby-node`) - const pluginName = - plugin.name === `default-site-plugin` ? `gatsby-node.js` : plugin.name - - // TODO: rethink createNode API to handle this better - if ( - api === `onCreateNode` && - gatsbyNode?.shouldOnCreateNode && // Don't bail if this api is not exported - !gatsbyNode.shouldOnCreateNode( - { node: args.node }, - plugin.pluginOptions - ) - ) { - // Do not try to schedule an async event for this node for this plugin - return null - } - - return new Promise(resolve => { - resolve( - runAPI(plugin, api, { ...args, parentSpan: apiSpan }, activity) - ) - }).catch(err => { - decorateEvent(`BUILD_PANIC`, { - pluginName: `${plugin.name}@${plugin.version}`, - }) + // TODO: Probably refactor + return requireGatsbyPlugin(plugin, `gatsby-node`).then(gatsbyNode => { + const pluginName = + plugin.name === `default-site-plugin` + ? `gatsby-node.js` + : plugin.name + + // TODO: rethink createNode API to handle this better + if ( + api === `onCreateNode` && + gatsbyNode?.shouldOnCreateNode && // Don't bail if this api is not exported + !gatsbyNode.shouldOnCreateNode( + { node: args.node }, + plugin.pluginOptions + ) + ) { + // Do not try to schedule an async event for this node for this plugin + return null + } - const localReporter = getLocalReporter({ activity, reporter }) - - const file = stackTrace - .parse(err) - .find(file => /gatsby-node/.test(file.fileName)) - - let codeFrame = `` - const structuredError = errorParser({ err }) - - if (file) { - const { fileName, lineNumber: line, columnNumber: column } = file - const trimmedFileName = fileName.match(/^(async )?(.*)/)[2] - - try { - const code = fs.readFileSync(trimmedFileName, { - encoding: `utf-8`, - }) - codeFrame = codeFrameColumns( - code, - { - start: { - line, - column, + return new Promise(resolve => { + resolve( + runAPI(plugin, api, { ...args, parentSpan: apiSpan }, activity) + ) + }).catch(err => { + decorateEvent(`BUILD_PANIC`, { + pluginName: `${plugin.name}@${plugin.version}`, + }) + + const localReporter = getLocalReporter({ activity, reporter }) + + const file = stackTrace + .parse(err) + .find(file => /gatsby-node/.test(file.fileName)) + + let codeFrame = `` + const structuredError = errorParser({ err }) + + if (file) { + const { fileName, lineNumber: line, columnNumber: column } = file + const trimmedFileName = fileName.match(/^(async )?(.*)/)[2] + + try { + const code = fs.readFileSync(trimmedFileName, { + encoding: `utf-8`, + }) + codeFrame = codeFrameColumns( + code, + { + start: { + line, + column, + }, }, - }, - { - highlightCode: true, - } - ) - } catch (_e) { - // sometimes stack trace point to not existing file - // particularly when file is transpiled and path actually changes - // (like pointing to not existing `src` dir or original typescript file) + { + highlightCode: true, + } + ) + } catch (_e) { + // sometimes stack trace point to not existing file + // particularly when file is transpiled and path actually changes + // (like pointing to not existing `src` dir or original typescript file) + } + + structuredError.location = { + start: { line: line, column: column }, + } + structuredError.filePath = fileName } - structuredError.location = { - start: { line: line, column: column }, + structuredError.context = { + ...structuredError.context, + pluginName, + api, + codeFrame, } - structuredError.filePath = fileName - } - - structuredError.context = { - ...structuredError.context, - pluginName, - api, - codeFrame, - } - localReporter.panicOnBuild(structuredError) + localReporter.panicOnBuild(structuredError) - return null + return null + }) }) }, apiRunPromiseOptions diff --git a/packages/gatsby/src/utils/jobs/manager.ts b/packages/gatsby/src/utils/jobs/manager.ts index b820cd4d1d79c..73ddb639bcc64 100644 --- a/packages/gatsby/src/utils/jobs/manager.ts +++ b/packages/gatsby/src/utils/jobs/manager.ts @@ -157,30 +157,31 @@ function runJob( ): Promise> { const { plugin } = job try { - const worker = requireGatsbyPlugin(plugin, `gatsby-worker`) - if (!worker[job.name]) { - throw new Error(`No worker function found for ${job.name}`) - } - - if (!forceLocal && !job.plugin.isLocal && hasExternalJobsEnabled()) { - if (process.send) { - if (!isListeningForMessages) { - isListeningForMessages = true - listenForJobMessages() - } + return requireGatsbyPlugin(plugin, `gatsby-worker`).then(worker => { + if (!worker[job.name]) { + throw new Error(`No worker function found for ${job.name}`) + } - return runExternalWorker(job) - } else { - // only show the offloading warning once - if (!hasShownIPCDisabledWarning) { - hasShownIPCDisabledWarning = true - reporter.warn( - `Offloading of a job failed as IPC could not be detected. Running job locally.` - ) + if (!forceLocal && !job.plugin.isLocal && hasExternalJobsEnabled()) { + if (process.send) { + if (!isListeningForMessages) { + isListeningForMessages = true + listenForJobMessages() + } + + return runExternalWorker(job) + } else { + // only show the offloading warning once + if (!hasShownIPCDisabledWarning) { + hasShownIPCDisabledWarning = true + reporter.warn( + `Offloading of a job failed as IPC could not be detected. Running job locally.` + ) + } } } - } - return runLocalWorker(worker[job.name], job) + return runLocalWorker(worker[job.name], job) + }) } catch (err) { throw new Error( `We couldn't find a gatsby-worker.js(${plugin.resolve}/gatsby-worker.js) file for ${plugin.name}@${plugin.version}` diff --git a/packages/gatsby/src/utils/parcel/compile-gatsby-files.ts b/packages/gatsby/src/utils/parcel/compile-gatsby-files.ts index 613b27a445065..a2ad269033d59 100644 --- a/packages/gatsby/src/utils/parcel/compile-gatsby-files.ts +++ b/packages/gatsby/src/utils/parcel/compile-gatsby-files.ts @@ -74,7 +74,11 @@ export async function compileGatsbyFiles( const { name } = path.parse(file) // Of course, allow valid gatsby-node files - if (file === `gatsby-node.js` || file === `gatsby-node.ts`) { + if ( + file === `gatsby-node.js` || + file === `gatsby-node.mjs` || + file === `gatsby-node.ts` + ) { break } diff --git a/packages/gatsby/src/utils/require-gatsby-plugin.ts b/packages/gatsby/src/utils/require-gatsby-plugin.ts index 8167bf22cae64..5ffa581c77ff7 100644 --- a/packages/gatsby/src/utils/require-gatsby-plugin.ts +++ b/packages/gatsby/src/utils/require-gatsby-plugin.ts @@ -1,3 +1,5 @@ +import { pathToFileURL } from "url" + const pluginModuleCache = new Map() export function setGatsbyPluginCache( @@ -9,22 +11,48 @@ export function setGatsbyPluginCache( pluginModuleCache.set(key, moduleObject) } -export function requireGatsbyPlugin( +export async function requireGatsbyPlugin( plugin: { name: string resolve: string resolvedCompiledGatsbyNode?: string }, module: string -): any { +): Promise { const key = `${plugin.name}/${module}` let pluginModule = pluginModuleCache.get(key) if (!pluginModule) { - pluginModule = require(module === `gatsby-node` && - plugin.resolvedCompiledGatsbyNode - ? plugin.resolvedCompiledGatsbyNode - : `${plugin.resolve}/${module}`) + let requirePluginModulePath: string + + if (module === `gatsby-node` && plugin.resolvedCompiledGatsbyNode) { + requirePluginModulePath = plugin.resolvedCompiledGatsbyNode + } else { + requirePluginModulePath = `${plugin.resolve}/${module}` + } + + try { + pluginModule = require(requirePluginModulePath) + } catch (failedToRequireError) { + const url = pathToFileURL(`${requirePluginModulePath}.mjs`) + + // TODO: Refactor probably + try { + pluginModule = await import(url?.href) + } catch (failedToImportError) { + // TODO: Better error handling + throw new Error(`Failed to import plugin ${requirePluginModulePath}`, { + cause: failedToImportError, + }) + } + + if (!pluginModule) { + throw new Error(`Failed to require plugin ${requirePluginModulePath}`, { + cause: failedToRequireError, + }) + } + } + pluginModuleCache.set(key, pluginModule) } return pluginModule From f773168b27321723251f55faed70eee747f8fb92 Mon Sep 17 00:00:00 2001 From: Ty Hopp Date: Thu, 17 Nov 2022 10:42:21 +0800 Subject: [PATCH 03/35] Make gatsby-node.mjs engines bundling work --- .../schema/graphql-engine/print-plugins.ts | 41 +++++++++++-------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/packages/gatsby/src/schema/graphql-engine/print-plugins.ts b/packages/gatsby/src/schema/graphql-engine/print-plugins.ts index b35dd623f1931..21be7226943f2 100644 --- a/packages/gatsby/src/schema/graphql-engine/print-plugins.ts +++ b/packages/gatsby/src/schema/graphql-engine/print-plugins.ts @@ -26,13 +26,11 @@ export async function printQueryEnginePlugins(): Promise { } catch (e) { // no-op } - return await fs.writeFile( - schemaCustomizationPluginsPath, - renderQueryEnginePlugins() - ) + const queryEnginePlugins = await renderQueryEnginePlugins() + return await fs.writeFile(schemaCustomizationPluginsPath, queryEnginePlugins) } -function renderQueryEnginePlugins(): string { +async function renderQueryEnginePlugins(): Promise { const { flattenedPlugins } = store.getState() const usedPlugins = flattenedPlugins.filter( p => @@ -41,7 +39,8 @@ function renderQueryEnginePlugins(): string { p.nodeAPIs.some(api => schemaCustomizationAPIs.has(api))) ) const usedSubPlugins = findSubPlugins(usedPlugins, flattenedPlugins) - return render(usedPlugins, usedSubPlugins) + const result = await render(usedPlugins, usedSubPlugins) + return result } function relativePluginPath(resolve: string): string { @@ -50,10 +49,10 @@ function relativePluginPath(resolve: string): string { ) } -function render( +async function render( usedPlugins: IGatsbyState["flattenedPlugins"], usedSubPlugins: IGatsbyState["flattenedPlugins"] -): string { +): Promise { const uniqGatsbyNode = uniq(usedPlugins) const uniqSubPlugins = uniq(usedSubPlugins) @@ -67,7 +66,7 @@ function render( } }) - const pluginsWithWorkers = filterPluginsWithWorkers(uniqGatsbyNode) + const pluginsWithWorkers = await filterPluginsWithWorkers(uniqGatsbyNode) const subPluginModuleToImportNameMapping = new Map() const imports: Array = [ @@ -144,16 +143,26 @@ export const flattenedPlugins = return output } -function filterPluginsWithWorkers( +async function filterPluginsWithWorkers( plugins: IGatsbyState["flattenedPlugins"] -): IGatsbyState["flattenedPlugins"] { - return plugins.filter(plugin => { +): Promise { + const filteredPlugins: Array = [] + + for (const plugin of plugins) { try { - return Boolean(requireGatsbyPlugin(plugin, `gatsby-worker`)) - } catch (err) { - return false + const pluginWithWorker = await requireGatsbyPlugin( + plugin, + `gatsby-worker` + ) + if (pluginWithWorker) { + filteredPlugins.push(plugin) + } + } catch (_) { + // Do nothing } - }) + } + + return filteredPlugins } type ArrayElement> = ArrayType extends Array< From a98992dc0b2209bd5d54eb47577525e23d219b69 Mon Sep 17 00:00:00 2001 From: Ty Hopp Date: Thu, 17 Nov 2022 13:26:54 +0800 Subject: [PATCH 04/35] Adjust resolve-module-exports to match require behavior --- packages/gatsby/package.json | 2 +- .../src/bootstrap/load-plugins/validate.ts | 19 ++++++------------- .../src/bootstrap/resolve-module-exports.ts | 18 +++++++++++------- 3 files changed, 18 insertions(+), 21 deletions(-) diff --git a/packages/gatsby/package.json b/packages/gatsby/package.json index eb15b4c9e1470..aab6464e48560 100644 --- a/packages/gatsby/package.json +++ b/packages/gatsby/package.json @@ -195,7 +195,7 @@ "@types/string-similarity": "^4.0.0", "@types/tmp": "^0.2.0", "@types/webpack-virtual-modules": "^0.1.1", - "babel-preset-gatsby-package": "file:../babel-reset-gatsby-package", + "babel-preset-gatsby-package": "^3.1.0-next.0", "copyfiles": "^2.3.0", "cross-env": "^7.0.3", "documentation": "^13.1.0", diff --git a/packages/gatsby/src/bootstrap/load-plugins/validate.ts b/packages/gatsby/src/bootstrap/load-plugins/validate.ts index 0d7f44ce82df9..d4e2b45b1bcc8 100644 --- a/packages/gatsby/src/bootstrap/load-plugins/validate.ts +++ b/packages/gatsby/src/bootstrap/load-plugins/validate.ts @@ -428,19 +428,12 @@ export async function collatePluginAPIs({ // Discover which APIs this plugin implements and store an array against // the plugin node itself *and* in an API to plugins map for faster lookups // later. - let pluginNodeExports - - try { - pluginNodeExports = await resolveModuleExports( - plugin.resolvedCompiledGatsbyNode ?? `${plugin.resolve}/gatsby-node`, - { - mode: `require`, - } - ) - } catch (cause) { - throw new Error(`ABC Failed to import gatsby-node`, { cause }) - } - + const pluginNodeExports = await resolveModuleExports( + plugin.resolvedCompiledGatsbyNode ?? `${plugin.resolve}/gatsby-node`, + { + mode: `require`, + } + ) const pluginBrowserExports = await resolveModuleExports( `${plugin.resolve}/gatsby-browser` ) diff --git a/packages/gatsby/src/bootstrap/resolve-module-exports.ts b/packages/gatsby/src/bootstrap/resolve-module-exports.ts index 8a87a10a7cdb4..cf3e040b66949 100644 --- a/packages/gatsby/src/bootstrap/resolve-module-exports.ts +++ b/packages/gatsby/src/bootstrap/resolve-module-exports.ts @@ -216,13 +216,17 @@ export async function resolveModuleExports( // TODO: Maybe move this to a better spot, let's test it here for now if (failedWithoutRequireError) { - // Let's see if it's a gatsby-node.mjs file - const url = pathToFileURL(`${modulePath}.mjs`) - const importedModule = await import(url.href) - const importedModuleExports = Object.keys(importedModule).filter( - exportName => exportName !== `__esModule` - ) - return importedModuleExports + try { + // Let's see if it's a gatsby-node.mjs file + const url = pathToFileURL(`${modulePath}.mjs`) + const importedModule = await import(url.href) + const importedModuleExports = Object.keys(importedModule).filter( + exportName => exportName !== `__esModule` + ) + return importedModuleExports + } catch (error) { + // TODO: Do something like testRequireError above to only panic on errors that are not import related + } } return [] From 4c32983fee8594ca44acd0ba0a0e5e78a45bde7a Mon Sep 17 00:00:00 2001 From: Ty Hopp Date: Fri, 18 Nov 2022 12:23:38 +0800 Subject: [PATCH 05/35] Make gatsby-config.js|.mjs|.ts work --- .../gatsby/src/bootstrap/get-config-file.ts | 77 +++++++++++-------- .../src/bootstrap/resolve-config-file-path.ts | 31 ++++++++ .../src/utils/parcel/compile-gatsby-files.ts | 48 +++++++++++- 3 files changed, 118 insertions(+), 38 deletions(-) create mode 100644 packages/gatsby/src/bootstrap/resolve-config-file-path.ts diff --git a/packages/gatsby/src/bootstrap/get-config-file.ts b/packages/gatsby/src/bootstrap/get-config-file.ts index 7cd0f610f3b45..11abd8f2773e9 100644 --- a/packages/gatsby/src/bootstrap/get-config-file.ts +++ b/packages/gatsby/src/bootstrap/get-config-file.ts @@ -1,11 +1,12 @@ import fs from "fs-extra" -import { testRequireError } from "../utils/test-require-error" +// import { testRequireError } from "../utils/test-require-error" import report from "gatsby-cli/lib/reporter" import path from "path" import { sync as existsSync } from "fs-exists-cached" import { COMPILED_CACHE_DIR } from "../utils/parcel/compile-gatsby-files" import { isNearMatch } from "../utils/is-near-match" import { pathToFileURL } from "url" +import { resolveConfigFilePath } from "./resolve-config-file-path" export async function getConfigFile( siteDirectory: string, @@ -19,51 +20,59 @@ export async function getConfigFile( let configFilePath = `` let configModule: any - // Attempt to find compiled gatsby-config.js in .cache/compiled/gatsby-config.js + // Attempt to find compiled gatsby-config in .cache/compiled/gatsby-config try { configPath = path.join(`${siteDirectory}/${COMPILED_CACHE_DIR}`, configName) - configFilePath = require.resolve(configPath) - configModule = require(configFilePath) + configFilePath = resolveConfigFilePath(configPath) + configModule = await import(configFilePath) + if (!configModule.default) { + // TODO: Structured error + throw new Error(`No default export found in gatsby-config`) + } } catch (outerError) { // Not all plugins will have a compiled file, so the err.message can look like this: // "Cannot find module '/node_modules/gatsby-source-filesystem/.cache/compiled/gatsby-config'" // But the compiled file can also have an error like this: // "Cannot find module 'foobar'" // So this is trying to differentiate between an error we're fine ignoring and an error that we should throw - const isModuleNotFoundError = outerError.code === `MODULE_NOT_FOUND` - const isThisFileRequireError = - outerError?.requireStack?.[0]?.includes(`get-config-file`) ?? true - - // User's module require error inside gatsby-config.js - if (!(isModuleNotFoundError && isThisFileRequireError)) { - report.panic({ - id: `11902`, - error: outerError, - context: { - configName, - message: outerError.message, - }, - }) - } - - // Attempt to find uncompiled gatsby-config.js in root dir + // const isModuleNotFoundError = outerError.code === `MODULE_NOT_FOUND` + // const isThisFileRequireError = + // outerError?.requireStack?.[0]?.includes(`get-config-file`) ?? true + + // TODO: Adjust test for import, not require + // if (!(isModuleNotFoundError && isThisFileRequireError)) { + // report.panic({ + // id: `11902`, + // error: outerError, + // context: { + // configName, + // message: outerError.message, + // }, + // }) + // } + + // Attempt to find uncompiled gatsby-config in root dir configPath = path.join(siteDirectory, configName) try { - configFilePath = require.resolve(configPath) - configModule = require(configFilePath) - } catch (innerError) { - // Some other error that is not a require error - if (!testRequireError(configPath, innerError)) { - report.panic({ - id: `10123`, - error: innerError, - context: { - configName, - message: innerError.message, - }, - }) + configFilePath = resolveConfigFilePath(configPath) + configModule = await import(configFilePath) + if (!configModule.default) { + // TODO: Structured error + throw new Error(`No default export found in gatsby-config`) } + } catch (innerError) { + // TODO: Adjust test for import, not require + // if (!testRequireError(configPath, innerError)) { + // report.panic({ + // id: `10123`, + // error: innerError, + // context: { + // configName, + // message: innerError.message, + // }, + // }) + // } const files = await fs.readdir(siteDirectory) diff --git a/packages/gatsby/src/bootstrap/resolve-config-file-path.ts b/packages/gatsby/src/bootstrap/resolve-config-file-path.ts new file mode 100644 index 0000000000000..1f962865eb407 --- /dev/null +++ b/packages/gatsby/src/bootstrap/resolve-config-file-path.ts @@ -0,0 +1,31 @@ +import report from "gatsby-cli/lib/reporter" +import { sync as existsSync } from "fs-exists-cached" + +/** + * Figure out if the file path is .js or .mjs and return it if it exists. + */ +export function resolveConfigFilePath(filePath: string): string { + const filePathWithJSExtension = `${filePath}.js` + const filePathWithMJSExtension = `${filePath}.mjs` + + if ( + existsSync(filePathWithJSExtension) && + existsSync(filePathWithMJSExtension) + ) { + // TODO: Show project relative path in warning + report.warn( + `The file ${filePath} has both .js and .mjs variants, please use one or the other. Using .js by default.` + ) + return filePathWithJSExtension + } + + if (existsSync(filePathWithJSExtension)) { + return filePathWithJSExtension + } + + if (existsSync(filePathWithMJSExtension)) { + return filePathWithMJSExtension + } + + return `` +} diff --git a/packages/gatsby/src/utils/parcel/compile-gatsby-files.ts b/packages/gatsby/src/utils/parcel/compile-gatsby-files.ts index a2ad269033d59..6a8430d00fb70 100644 --- a/packages/gatsby/src/utils/parcel/compile-gatsby-files.ts +++ b/packages/gatsby/src/utils/parcel/compile-gatsby-files.ts @@ -3,13 +3,21 @@ import { LMDBCache, Cache } from "@parcel/cache" import path from "path" import type { Diagnostic } from "@parcel/diagnostic" import reporter from "gatsby-cli/lib/reporter" -import { ensureDir, emptyDir, existsSync, remove, readdir } from "fs-extra" +import { + ensureDir, + emptyDir, + existsSync, + remove, + readdir, + rename, +} from "fs-extra" import telemetry from "gatsby-telemetry" import { isNearMatch } from "../is-near-match" +import { resolveConfigFilePath } from "../../bootstrap/resolve-config-file-path" export const COMPILED_CACHE_DIR = `.cache/compiled` export const PARCEL_CACHE_DIR = `.cache/.parcel-cache` -export const gatsbyFileRegex = `gatsby-+(node|config).ts` +export const gatsbyFileRegex = `gatsby-+(node|config)(.js|.ts)` // Just for fun see if we can use esm in gatsby-config.js now const RETRY_COUNT = 5 function getCacheDir(siteRoot: string): string { @@ -37,9 +45,14 @@ export function constructParcel(siteRoot: string, cache?: Cache): Parcel { defaultConfig: require.resolve(`gatsby-parcel-config`), mode: `production`, cache, + defaultTargetOptions: { + // TODO: Investigate what else this does for possible side effects + shouldScopeHoist: true, + }, targets: { root: { - outputFormat: `commonjs`, + outputFormat: `esmodule`, + isLibrary: true, includeNodeModules: false, sourceMap: process.env.NODE_ENV === `development`, engines: { @@ -133,8 +146,34 @@ export async function compileGatsbyFiles( for (const bundle of bundles) { // validate that output exists and is valid try { + const { dir, name, ext } = path.parse(bundle.filePath) + + // TODO: Figure out if we can configure Parcel to make the compiled file .mjs instead of .js, rename it manually for now + // Otherwise Node later compains about importing a .js esm module, it wants .mjs or "type": "module" in package.json + if (ext === `.js`) { + const newFilePath = bundle.filePath.replace(ext, `.mjs`) + await rename(bundle.filePath, newFilePath) + + // The below didn't work, Parcel doesn't like an object it didn't create and throws: + // TypeError: Cannot read private member #bundle from an object whose class did not declare it + // Keep for posterity for now, remove later + + // Annoyingly bundle properties are read only + // bundle = new Proxy(bundle, { + // get(target, prop): any { + // if (prop === `filePath`) { + // return newFilePath + // } + // return target[prop] + // }, + // }) + } + delete require.cache[bundle.filePath] - require(bundle.filePath) + + const filePath = resolveConfigFilePath(path.join(dir, name)) + + await import(filePath) } catch (e) { if (retry >= RETRY_COUNT) { reporter.panic({ @@ -142,6 +181,7 @@ export async function compileGatsbyFiles( context: { siteRoot, retries: RETRY_COUNT, + // TODO: All other accessors of bundle.filePath need to be adjusted to .mjs since bundle is read only, skip for now compiledFileLocation: bundle.filePath, sourceFileLocation: bundle.getMainEntry()?.filePath, }, From e0165a89c9b83f81111fe119837f4ba1b89af6a0 Mon Sep 17 00:00:00 2001 From: Ty Hopp Date: Mon, 21 Nov 2022 12:11:08 +0800 Subject: [PATCH 06/35] Revert changes to compile-gatsby-files --- .../src/utils/parcel/compile-gatsby-files.ts | 48 ++----------------- 1 file changed, 4 insertions(+), 44 deletions(-) diff --git a/packages/gatsby/src/utils/parcel/compile-gatsby-files.ts b/packages/gatsby/src/utils/parcel/compile-gatsby-files.ts index 6a8430d00fb70..a2ad269033d59 100644 --- a/packages/gatsby/src/utils/parcel/compile-gatsby-files.ts +++ b/packages/gatsby/src/utils/parcel/compile-gatsby-files.ts @@ -3,21 +3,13 @@ import { LMDBCache, Cache } from "@parcel/cache" import path from "path" import type { Diagnostic } from "@parcel/diagnostic" import reporter from "gatsby-cli/lib/reporter" -import { - ensureDir, - emptyDir, - existsSync, - remove, - readdir, - rename, -} from "fs-extra" +import { ensureDir, emptyDir, existsSync, remove, readdir } from "fs-extra" import telemetry from "gatsby-telemetry" import { isNearMatch } from "../is-near-match" -import { resolveConfigFilePath } from "../../bootstrap/resolve-config-file-path" export const COMPILED_CACHE_DIR = `.cache/compiled` export const PARCEL_CACHE_DIR = `.cache/.parcel-cache` -export const gatsbyFileRegex = `gatsby-+(node|config)(.js|.ts)` // Just for fun see if we can use esm in gatsby-config.js now +export const gatsbyFileRegex = `gatsby-+(node|config).ts` const RETRY_COUNT = 5 function getCacheDir(siteRoot: string): string { @@ -45,14 +37,9 @@ export function constructParcel(siteRoot: string, cache?: Cache): Parcel { defaultConfig: require.resolve(`gatsby-parcel-config`), mode: `production`, cache, - defaultTargetOptions: { - // TODO: Investigate what else this does for possible side effects - shouldScopeHoist: true, - }, targets: { root: { - outputFormat: `esmodule`, - isLibrary: true, + outputFormat: `commonjs`, includeNodeModules: false, sourceMap: process.env.NODE_ENV === `development`, engines: { @@ -146,34 +133,8 @@ export async function compileGatsbyFiles( for (const bundle of bundles) { // validate that output exists and is valid try { - const { dir, name, ext } = path.parse(bundle.filePath) - - // TODO: Figure out if we can configure Parcel to make the compiled file .mjs instead of .js, rename it manually for now - // Otherwise Node later compains about importing a .js esm module, it wants .mjs or "type": "module" in package.json - if (ext === `.js`) { - const newFilePath = bundle.filePath.replace(ext, `.mjs`) - await rename(bundle.filePath, newFilePath) - - // The below didn't work, Parcel doesn't like an object it didn't create and throws: - // TypeError: Cannot read private member #bundle from an object whose class did not declare it - // Keep for posterity for now, remove later - - // Annoyingly bundle properties are read only - // bundle = new Proxy(bundle, { - // get(target, prop): any { - // if (prop === `filePath`) { - // return newFilePath - // } - // return target[prop] - // }, - // }) - } - delete require.cache[bundle.filePath] - - const filePath = resolveConfigFilePath(path.join(dir, name)) - - await import(filePath) + require(bundle.filePath) } catch (e) { if (retry >= RETRY_COUNT) { reporter.panic({ @@ -181,7 +142,6 @@ export async function compileGatsbyFiles( context: { siteRoot, retries: RETRY_COUNT, - // TODO: All other accessors of bundle.filePath need to be adjusted to .mjs since bundle is read only, skip for now compiledFileLocation: bundle.filePath, sourceFileLocation: bundle.getMainEntry()?.filePath, }, From 7cae3ed253095fa5efdac22ffdc61a084bba2792 Mon Sep 17 00:00:00 2001 From: Ty Hopp Date: Mon, 21 Nov 2022 12:11:19 +0800 Subject: [PATCH 07/35] Use single codepath in get-config-file --- .../gatsby/src/bootstrap/get-config-file.ts | 22 +------------------ .../src/bootstrap/resolve-module-exports.ts | 2 +- .../gatsby/src/utils/require-gatsby-plugin.ts | 2 +- 3 files changed, 3 insertions(+), 23 deletions(-) diff --git a/packages/gatsby/src/bootstrap/get-config-file.ts b/packages/gatsby/src/bootstrap/get-config-file.ts index 11abd8f2773e9..b289c0fba6d3b 100644 --- a/packages/gatsby/src/bootstrap/get-config-file.ts +++ b/packages/gatsby/src/bootstrap/get-config-file.ts @@ -5,7 +5,6 @@ import path from "path" import { sync as existsSync } from "fs-exists-cached" import { COMPILED_CACHE_DIR } from "../utils/parcel/compile-gatsby-files" import { isNearMatch } from "../utils/is-near-match" -import { pathToFileURL } from "url" import { resolveConfigFilePath } from "./resolve-config-file-path" export async function getConfigFile( @@ -29,6 +28,7 @@ export async function getConfigFile( // TODO: Structured error throw new Error(`No default export found in gatsby-config`) } + configModule = configModule.default } catch (outerError) { // Not all plugins will have a compiled file, so the err.message can look like this: // "Cannot find module '/node_modules/gatsby-source-filesystem/.cache/compiled/gatsby-config'" @@ -107,26 +107,6 @@ export async function getConfigFile( }) } - // TODO: Maybe move this to a better spot in this function, let's test it here for now - if (nearMatch && nearMatch.endsWith(`.mjs`)) { - try { - configFilePath = path.join(siteDirectory, nearMatch) - const url = pathToFileURL(configFilePath) - const importedModule = await import(url.href) - - if (!importedModule.default) { - // TODO: Structured error - console.error(`No default export found in gatsby-config.mjs`) - } - - configModule = importedModule.default - return { configModule, configFilePath } - } catch (cause) { - // TODO: Structured error - throw new Error(`Failed to load .mjs config file`, { cause }) - } - } - // gatsby-config is misnamed if (nearMatch) { const isTSX = nearMatch.endsWith(`.tsx`) diff --git a/packages/gatsby/src/bootstrap/resolve-module-exports.ts b/packages/gatsby/src/bootstrap/resolve-module-exports.ts index cf3e040b66949..9878dd86fcda5 100644 --- a/packages/gatsby/src/bootstrap/resolve-module-exports.ts +++ b/packages/gatsby/src/bootstrap/resolve-module-exports.ts @@ -214,7 +214,7 @@ export async function resolveModuleExports( return staticallyAnalyzeExports(modulePath, resolver) } - // TODO: Maybe move this to a better spot, let's test it here for now + // TODO: Use a single codepath with await import() if (failedWithoutRequireError) { try { // Let's see if it's a gatsby-node.mjs file diff --git a/packages/gatsby/src/utils/require-gatsby-plugin.ts b/packages/gatsby/src/utils/require-gatsby-plugin.ts index 5ffa581c77ff7..2c665520018dd 100644 --- a/packages/gatsby/src/utils/require-gatsby-plugin.ts +++ b/packages/gatsby/src/utils/require-gatsby-plugin.ts @@ -31,12 +31,12 @@ export async function requireGatsbyPlugin( requirePluginModulePath = `${plugin.resolve}/${module}` } + // TODO: Use a single codepath with await import() try { pluginModule = require(requirePluginModulePath) } catch (failedToRequireError) { const url = pathToFileURL(`${requirePluginModulePath}.mjs`) - // TODO: Refactor probably try { pluginModule = await import(url?.href) } catch (failedToImportError) { From 24998e028a6c528d12c853eb6887d50440418464 Mon Sep 17 00:00:00 2001 From: Ty Hopp Date: Mon, 21 Nov 2022 17:06:11 +0800 Subject: [PATCH 08/35] Add basic integration test --- .../esm-in-gatsby-files/.gitignore | 8 +++ .../esm-in-gatsby-files/__tests__/config.js | 59 +++++++++++++++++++ .../esm-in-gatsby-files/cjs-default.js | 5 ++ .../esm-in-gatsby-files/cjs-named.js | 7 +++ .../esm-in-gatsby-files/esm-default.mjs | 5 ++ .../esm-in-gatsby-files/esm-named.mjs | 5 ++ .../esm-in-gatsby-files/jest-transformer.js | 5 ++ .../esm-in-gatsby-files/jest.config.js | 14 +++++ .../esm-in-gatsby-files/package.json | 22 +++++++ .../esm-in-gatsby-files/src/pages/index.js | 7 +++ .../esm-in-gatsby-files/tsconfig.json | 19 ++++++ 11 files changed, 156 insertions(+) create mode 100644 integration-tests/esm-in-gatsby-files/.gitignore create mode 100644 integration-tests/esm-in-gatsby-files/__tests__/config.js create mode 100644 integration-tests/esm-in-gatsby-files/cjs-default.js create mode 100644 integration-tests/esm-in-gatsby-files/cjs-named.js create mode 100644 integration-tests/esm-in-gatsby-files/esm-default.mjs create mode 100644 integration-tests/esm-in-gatsby-files/esm-named.mjs create mode 100644 integration-tests/esm-in-gatsby-files/jest-transformer.js create mode 100644 integration-tests/esm-in-gatsby-files/jest.config.js create mode 100644 integration-tests/esm-in-gatsby-files/package.json create mode 100644 integration-tests/esm-in-gatsby-files/src/pages/index.js create mode 100644 integration-tests/esm-in-gatsby-files/tsconfig.json diff --git a/integration-tests/esm-in-gatsby-files/.gitignore b/integration-tests/esm-in-gatsby-files/.gitignore new file mode 100644 index 0000000000000..369ea6cd8eb68 --- /dev/null +++ b/integration-tests/esm-in-gatsby-files/.gitignore @@ -0,0 +1,8 @@ +__tests__/__debug__ +node_modules +yarn.lock + +# These are removed and created during the test lifecycle +gatsby-config.js +gatsby-config.mjs +gatsby-config.ts \ No newline at end of file diff --git a/integration-tests/esm-in-gatsby-files/__tests__/config.js b/integration-tests/esm-in-gatsby-files/__tests__/config.js new file mode 100644 index 0000000000000..a4e3e8ed6d140 --- /dev/null +++ b/integration-tests/esm-in-gatsby-files/__tests__/config.js @@ -0,0 +1,59 @@ +import path from "path" +import fs from "fs-extra" +import execa from "execa" + +jest.setTimeout(100000) + +const fixtureRoot = path.resolve(__dirname, `fixtures`) +const siteRoot = path.resolve(__dirname, `..`) + +const fixturePath = { + cjs: path.join(fixtureRoot, `gatsby-config.js`), + esm: path.join(fixtureRoot, `gatsby-config.mjs`), +} + +const configPath = { + cjs: path.join(siteRoot, `gatsby-config.js`), + esm: path.join(siteRoot, `gatsby-config.mjs`), +} + +const gatsbyBin = path.join(`node_modules`, `gatsby`, `cli.js`) + +async function build() { + const { stdout } = await execa(process.execPath, [gatsbyBin, `build`], { + env: { + ...process.env, + NODE_ENV: `production`, + }, + }) + + return stdout +} + +describe(`gatsby-config.js`, () => { + afterEach(() => { + fs.rmSync(configPath.cjs) + }) + + it(`works with required CJS modules`, async () => { + await fs.copyFile(fixturePath.cjs, configPath.cjs) + const stdout = await build() + expect(stdout).toContain(`Done building`) + expect(stdout).toContain(`hello-default-cjs`) + expect(stdout).toContain(`hello-named-cjs`) + }) +}) + +describe(`gatsby-config.mjs`, () => { + afterEach(() => { + fs.rmSync(configPath.esm) + }) + + it(`works with required ESM modules`, async () => { + await fs.copyFile(fixturePath.esm, configPath.esm) + const stdout = await build() + expect(stdout).toContain(`Done building`) + expect(stdout).toContain(`hello-default-esm`) + expect(stdout).toContain(`hello-named-esm`) + }) +}) diff --git a/integration-tests/esm-in-gatsby-files/cjs-default.js b/integration-tests/esm-in-gatsby-files/cjs-default.js new file mode 100644 index 0000000000000..d164627b7ca9a --- /dev/null +++ b/integration-tests/esm-in-gatsby-files/cjs-default.js @@ -0,0 +1,5 @@ +function helloDefaultCJS() { + console.info(`hello-default-cjs`) +} + +module.exports = helloDefaultCJS diff --git a/integration-tests/esm-in-gatsby-files/cjs-named.js b/integration-tests/esm-in-gatsby-files/cjs-named.js new file mode 100644 index 0000000000000..49a68202a716b --- /dev/null +++ b/integration-tests/esm-in-gatsby-files/cjs-named.js @@ -0,0 +1,7 @@ +function helloNamedCJS() { + console.info(`hello-named-cjs`) +} + +module.exports = { + helloNamedCJS, +} diff --git a/integration-tests/esm-in-gatsby-files/esm-default.mjs b/integration-tests/esm-in-gatsby-files/esm-default.mjs new file mode 100644 index 0000000000000..fdf53bdd32539 --- /dev/null +++ b/integration-tests/esm-in-gatsby-files/esm-default.mjs @@ -0,0 +1,5 @@ +function helloDefaultESM() { + console.info(`hello-default-esm`) +} + +export default helloDefaultESM diff --git a/integration-tests/esm-in-gatsby-files/esm-named.mjs b/integration-tests/esm-in-gatsby-files/esm-named.mjs new file mode 100644 index 0000000000000..f3d30a9a52174 --- /dev/null +++ b/integration-tests/esm-in-gatsby-files/esm-named.mjs @@ -0,0 +1,5 @@ +function helloNamedESM() { + console.info(`hello-named-esm`) +} + +export { helloNamedESM } diff --git a/integration-tests/esm-in-gatsby-files/jest-transformer.js b/integration-tests/esm-in-gatsby-files/jest-transformer.js new file mode 100644 index 0000000000000..02167a152534c --- /dev/null +++ b/integration-tests/esm-in-gatsby-files/jest-transformer.js @@ -0,0 +1,5 @@ +const babelJest = require(`babel-jest`) + +module.exports = babelJest.default.createTransformer({ + presets: [`babel-preset-gatsby-package`], +}) diff --git a/integration-tests/esm-in-gatsby-files/jest.config.js b/integration-tests/esm-in-gatsby-files/jest.config.js new file mode 100644 index 0000000000000..2b2a1351623ec --- /dev/null +++ b/integration-tests/esm-in-gatsby-files/jest.config.js @@ -0,0 +1,14 @@ +module.exports = { + testPathIgnorePatterns: [ + `/node_modules/`, + `.cache`, + `public`, + `__tests__/fixtures`, + `gatsby-config.js`, + `gatsby-config.mjs`, + `gatsby-config.ts`, + ], + transform: { + "^.+\\.[jt]sx?$": `./jest-transformer.js`, + }, +} diff --git a/integration-tests/esm-in-gatsby-files/package.json b/integration-tests/esm-in-gatsby-files/package.json new file mode 100644 index 0000000000000..c976d6e4339ff --- /dev/null +++ b/integration-tests/esm-in-gatsby-files/package.json @@ -0,0 +1,22 @@ +{ + "private": true, + "name": "esm-in-gatsby-files-integration-test", + "version": "1.0.0", + "scripts": { + "test": "jest --runInBand" + }, + "dependencies": { + "gatsby": "next", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/node": "^18.11.9", + "@types/react": "^18.0.25", + "@types/react-dom": "^18.0.8", + "execa": "^4.0.1", + "fs-extra": "^10.1.0", + "jest": "^27.2.1", + "typescript": "^4.8.4" + } +} diff --git a/integration-tests/esm-in-gatsby-files/src/pages/index.js b/integration-tests/esm-in-gatsby-files/src/pages/index.js new file mode 100644 index 0000000000000..625d90f6a4511 --- /dev/null +++ b/integration-tests/esm-in-gatsby-files/src/pages/index.js @@ -0,0 +1,7 @@ +import * as React from "react" + +function IndexPage() { + return

Index page

+} + +export default IndexPage diff --git a/integration-tests/esm-in-gatsby-files/tsconfig.json b/integration-tests/esm-in-gatsby-files/tsconfig.json new file mode 100644 index 0000000000000..15128c8efd05b --- /dev/null +++ b/integration-tests/esm-in-gatsby-files/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "esnext", + "lib": ["dom", "esnext"], + "jsx": "react", + "module": "esnext", + "moduleResolution": "node", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true + }, + "include": [ + "./src/**/*", + "./gatsby-node.ts", + "./gatsby-config.ts", + "./plugins/**/*" + ] +} From dcf2d633bf1cb2c3c171c032dec0075cb5e9c4e2 Mon Sep 17 00:00:00 2001 From: Ty Hopp Date: Tue, 22 Nov 2022 10:29:09 +0800 Subject: [PATCH 09/35] Commit test fixtures --- integration-tests/esm-in-gatsby-files/.gitignore | 6 +++--- .../__tests__/fixtures/gatsby-config.js | 13 +++++++++++++ .../__tests__/fixtures/gatsby-config.mjs | 13 +++++++++++++ 3 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 integration-tests/esm-in-gatsby-files/__tests__/fixtures/gatsby-config.js create mode 100644 integration-tests/esm-in-gatsby-files/__tests__/fixtures/gatsby-config.mjs diff --git a/integration-tests/esm-in-gatsby-files/.gitignore b/integration-tests/esm-in-gatsby-files/.gitignore index 369ea6cd8eb68..61c008bc45207 100644 --- a/integration-tests/esm-in-gatsby-files/.gitignore +++ b/integration-tests/esm-in-gatsby-files/.gitignore @@ -3,6 +3,6 @@ node_modules yarn.lock # These are removed and created during the test lifecycle -gatsby-config.js -gatsby-config.mjs -gatsby-config.ts \ No newline at end of file +./gatsby-config.js +./gatsby-config.mjs +./gatsby-config.ts \ No newline at end of file diff --git a/integration-tests/esm-in-gatsby-files/__tests__/fixtures/gatsby-config.js b/integration-tests/esm-in-gatsby-files/__tests__/fixtures/gatsby-config.js new file mode 100644 index 0000000000000..6a02dcb45acc1 --- /dev/null +++ b/integration-tests/esm-in-gatsby-files/__tests__/fixtures/gatsby-config.js @@ -0,0 +1,13 @@ +// This fixture is moved during the test lifecycle + +const helloDefaultCJS = require(`./cjs-default.js`) +const { helloNamedCJS } = require(`./cjs-named.js`) + +helloDefaultCJS() +helloNamedCJS() + +const config = { + plugins: [], +} + +module.exports = config \ No newline at end of file diff --git a/integration-tests/esm-in-gatsby-files/__tests__/fixtures/gatsby-config.mjs b/integration-tests/esm-in-gatsby-files/__tests__/fixtures/gatsby-config.mjs new file mode 100644 index 0000000000000..d80c0d9301157 --- /dev/null +++ b/integration-tests/esm-in-gatsby-files/__tests__/fixtures/gatsby-config.mjs @@ -0,0 +1,13 @@ +// This fixture is moved during the test lifecycle + +import helloDefaultESM from "./esm-default.mjs" +import { helloNamedESM } from "./esm-named.mjs" + +helloDefaultESM() +helloNamedESM() + +const config = { + plugins: [], +} + +export default config \ No newline at end of file From 7b2fd8176352199a144a606e7124e120ad4dff7f Mon Sep 17 00:00:00 2001 From: Ty Hopp Date: Tue, 22 Nov 2022 12:09:39 +0800 Subject: [PATCH 10/35] Integration test for gatsby-config.mjs in plugins --- .../esm-in-gatsby-files/__tests__/config.js | 29 +++++++++++++++++-- .../__tests__/fixtures/gatsby-config.mjs | 10 ++++++- .../plugins/a-local-plugin/gatsby-config.mjs | 9 ++++++ .../plugins/a-local-plugin/gatsby-node.js | 3 ++ .../fixtures/plugins/a-local-plugin/index.js | 1 + .../plugins/a-local-plugin/package.json | 12 ++++++++ .../esm-in-gatsby-files/jest.config.js | 2 +- 7 files changed, 62 insertions(+), 4 deletions(-) create mode 100644 integration-tests/esm-in-gatsby-files/__tests__/fixtures/plugins/a-local-plugin/gatsby-config.mjs create mode 100644 integration-tests/esm-in-gatsby-files/__tests__/fixtures/plugins/a-local-plugin/gatsby-node.js create mode 100644 integration-tests/esm-in-gatsby-files/__tests__/fixtures/plugins/a-local-plugin/index.js create mode 100644 integration-tests/esm-in-gatsby-files/__tests__/fixtures/plugins/a-local-plugin/package.json diff --git a/integration-tests/esm-in-gatsby-files/__tests__/config.js b/integration-tests/esm-in-gatsby-files/__tests__/config.js index a4e3e8ed6d140..4b87243fd8050 100644 --- a/integration-tests/esm-in-gatsby-files/__tests__/config.js +++ b/integration-tests/esm-in-gatsby-files/__tests__/config.js @@ -17,6 +17,9 @@ const configPath = { esm: path.join(siteRoot, `gatsby-config.mjs`), } +const localPluginFixtureDir = path.join(fixtureRoot, `plugins`) +const localPluginTargetDir = path.join(siteRoot, `plugins`) + const gatsbyBin = path.join(`node_modules`, `gatsby`, `cli.js`) async function build() { @@ -30,6 +33,8 @@ async function build() { return stdout } +// Tests include multiple assertions since running multiple builds is time consuming + describe(`gatsby-config.js`, () => { afterEach(() => { fs.rmSync(configPath.cjs) @@ -37,23 +42,43 @@ describe(`gatsby-config.js`, () => { it(`works with required CJS modules`, async () => { await fs.copyFile(fixturePath.cjs, configPath.cjs) + const stdout = await build() + + // Build succeeded expect(stdout).toContain(`Done building`) + + // Requires work expect(stdout).toContain(`hello-default-cjs`) expect(stdout).toContain(`hello-named-cjs`) }) }) describe(`gatsby-config.mjs`, () => { - afterEach(() => { - fs.rmSync(configPath.esm) + afterEach(async () => { + await fs.rm(configPath.esm) + await fs.rm(localPluginTargetDir, { recursive: true }) }) it(`works with required ESM modules`, async () => { await fs.copyFile(fixturePath.esm, configPath.esm) + + await fs.ensureDir(localPluginTargetDir) + await fs.copy(localPluginFixtureDir, localPluginTargetDir) + const stdout = await build() + + // Build succeeded expect(stdout).toContain(`Done building`) + + // Imports work expect(stdout).toContain(`hello-default-esm`) expect(stdout).toContain(`hello-named-esm`) + + // Local plugin works + expect(stdout).toContain(`a-local-plugin-gatsby-config-mjs`) + + // Local plugin passed modules via options work + expect(stdout).toContain(`a-local-plugin-using-passed-esm-module`) }) }) diff --git a/integration-tests/esm-in-gatsby-files/__tests__/fixtures/gatsby-config.mjs b/integration-tests/esm-in-gatsby-files/__tests__/fixtures/gatsby-config.mjs index d80c0d9301157..1fefdb3b4552f 100644 --- a/integration-tests/esm-in-gatsby-files/__tests__/fixtures/gatsby-config.mjs +++ b/integration-tests/esm-in-gatsby-files/__tests__/fixtures/gatsby-config.mjs @@ -1,5 +1,6 @@ // This fixture is moved during the test lifecycle +import slugify from "@sindresorhus/slugify"; import helloDefaultESM from "./esm-default.mjs" import { helloNamedESM } from "./esm-named.mjs" @@ -7,7 +8,14 @@ helloDefaultESM() helloNamedESM() const config = { - plugins: [], + plugins: [ + { + resolve: `a-local-plugin`, + options: { + slugify, + }, + }, + ], } export default config \ No newline at end of file diff --git a/integration-tests/esm-in-gatsby-files/__tests__/fixtures/plugins/a-local-plugin/gatsby-config.mjs b/integration-tests/esm-in-gatsby-files/__tests__/fixtures/plugins/a-local-plugin/gatsby-config.mjs new file mode 100644 index 0000000000000..8a55b80d425c1 --- /dev/null +++ b/integration-tests/esm-in-gatsby-files/__tests__/fixtures/plugins/a-local-plugin/gatsby-config.mjs @@ -0,0 +1,9 @@ +// This fixture is moved during the test lifecycle + +console.info(`a-local-plugin-gatsby-config-mjs`) + +const config = { + plugins: [], +} + +export default config \ No newline at end of file diff --git a/integration-tests/esm-in-gatsby-files/__tests__/fixtures/plugins/a-local-plugin/gatsby-node.js b/integration-tests/esm-in-gatsby-files/__tests__/fixtures/plugins/a-local-plugin/gatsby-node.js new file mode 100644 index 0000000000000..ec7c0730fe53c --- /dev/null +++ b/integration-tests/esm-in-gatsby-files/__tests__/fixtures/plugins/a-local-plugin/gatsby-node.js @@ -0,0 +1,3 @@ +exports.onPreBuild = (_, { slugify }) => { + console.info(slugify(`a local plugin using passed esm module`)); +}; diff --git a/integration-tests/esm-in-gatsby-files/__tests__/fixtures/plugins/a-local-plugin/index.js b/integration-tests/esm-in-gatsby-files/__tests__/fixtures/plugins/a-local-plugin/index.js new file mode 100644 index 0000000000000..625c0891b2c30 --- /dev/null +++ b/integration-tests/esm-in-gatsby-files/__tests__/fixtures/plugins/a-local-plugin/index.js @@ -0,0 +1 @@ +// noop \ No newline at end of file diff --git a/integration-tests/esm-in-gatsby-files/__tests__/fixtures/plugins/a-local-plugin/package.json b/integration-tests/esm-in-gatsby-files/__tests__/fixtures/plugins/a-local-plugin/package.json new file mode 100644 index 0000000000000..2f211daf3667e --- /dev/null +++ b/integration-tests/esm-in-gatsby-files/__tests__/fixtures/plugins/a-local-plugin/package.json @@ -0,0 +1,12 @@ +{ + "name": "a-local-plugin", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/integration-tests/esm-in-gatsby-files/jest.config.js b/integration-tests/esm-in-gatsby-files/jest.config.js index 2b2a1351623ec..613531f9107df 100644 --- a/integration-tests/esm-in-gatsby-files/jest.config.js +++ b/integration-tests/esm-in-gatsby-files/jest.config.js @@ -3,7 +3,7 @@ module.exports = { `/node_modules/`, `.cache`, `public`, - `__tests__/fixtures`, + `/__tests__/fixtures/`, `gatsby-config.js`, `gatsby-config.mjs`, `gatsby-config.ts`, From 105408534853d0e129be85a2dec37cfef04fe919 Mon Sep 17 00:00:00 2001 From: Ty Hopp Date: Tue, 22 Nov 2022 12:38:57 +0800 Subject: [PATCH 11/35] Tell babel to only keep dynamic imports in files that need it --- packages/babel-preset-gatsby-package/lib/index.js | 1 - packages/babel-preset-gatsby-package/package.json | 2 +- packages/gatsby/babel.config.js | 9 ++++++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/babel-preset-gatsby-package/lib/index.js b/packages/babel-preset-gatsby-package/lib/index.js index f6a388e673c82..3bc01d2d5a52c 100644 --- a/packages/babel-preset-gatsby-package/lib/index.js +++ b/packages/babel-preset-gatsby-package/lib/index.js @@ -66,7 +66,6 @@ function preset(context, options = {}) { shippedProposals: true, modules: esm ? false : `commonjs`, bugfixes: esm, - exclude: [`proposal-dynamic-import`], }, browser ? browserConfig : nodeConfig ), diff --git a/packages/babel-preset-gatsby-package/package.json b/packages/babel-preset-gatsby-package/package.json index 85401e256e5fe..647b4d279db9a 100644 --- a/packages/babel-preset-gatsby-package/package.json +++ b/packages/babel-preset-gatsby-package/package.json @@ -44,6 +44,6 @@ "scripts": { "build": "", "prepare": "cross-env NODE_ENV=production npm run build", - "watch": "babel -w lib --out-dir dist/ --ignore \"**/__tests__\" --extensions \".ts,.js\"" + "watch": "babel -w src --out-dir dist/ --ignore \"**/__tests__\" --extensions \".ts,.js\"" } } diff --git a/packages/gatsby/babel.config.js b/packages/gatsby/babel.config.js index 563246ebc554e..fdd6e7fa00e7f 100644 --- a/packages/gatsby/babel.config.js +++ b/packages/gatsby/babel.config.js @@ -4,6 +4,13 @@ module.exports = { sourceMaps: true, presets: [["babel-preset-gatsby-package", { - keepDynamicImports: [`./src/utils/feedback.ts`] + keepDynamicImports: [ + `./src/utils/feedback.ts`, + + // These files use dynamic imports to load gatsby-config and gatsby-node so esm works + `./src/bootstrap/get-config-file.ts`, + `./src/bootstrap/resolve-module-exports.ts`, + `./src/utils/require-gatsby-plugin.ts` + ] }]], } From e0cd66a6db7b0a4088808d470b14443b4352f1a3 Mon Sep 17 00:00:00 2001 From: Ty Hopp Date: Tue, 22 Nov 2022 13:12:11 +0800 Subject: [PATCH 12/35] Integration test for gatsby-node.mjs --- .../__tests__/fixtures/gatsby-node.js | 11 +++ .../__tests__/fixtures/gatsby-node.mjs | 11 +++ .../{gatsby-node.js => gatsby-node.mjs} | 2 +- .../{config.js => gatsby-config.test.js} | 6 +- .../__tests__/gatsby-node.test.js | 77 +++++++++++++++++++ 5 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 integration-tests/esm-in-gatsby-files/__tests__/fixtures/gatsby-node.js create mode 100644 integration-tests/esm-in-gatsby-files/__tests__/fixtures/gatsby-node.mjs rename integration-tests/esm-in-gatsby-files/__tests__/fixtures/plugins/a-local-plugin/{gatsby-node.js => gatsby-node.mjs} (59%) rename integration-tests/esm-in-gatsby-files/__tests__/{config.js => gatsby-config.test.js} (90%) create mode 100644 integration-tests/esm-in-gatsby-files/__tests__/gatsby-node.test.js diff --git a/integration-tests/esm-in-gatsby-files/__tests__/fixtures/gatsby-node.js b/integration-tests/esm-in-gatsby-files/__tests__/fixtures/gatsby-node.js new file mode 100644 index 0000000000000..61fe76542d8eb --- /dev/null +++ b/integration-tests/esm-in-gatsby-files/__tests__/fixtures/gatsby-node.js @@ -0,0 +1,11 @@ +// This fixture is moved during the test lifecycle + +const helloDefaultCJS = require(`./cjs-default.js`) +const { helloNamedCJS } = require(`./cjs-named.js`) + +helloDefaultCJS() +helloNamedCJS() + +exports.onPreBuild = () => { + console.info(`gatsby-node-cjs-on-pre-build`); +}; diff --git a/integration-tests/esm-in-gatsby-files/__tests__/fixtures/gatsby-node.mjs b/integration-tests/esm-in-gatsby-files/__tests__/fixtures/gatsby-node.mjs new file mode 100644 index 0000000000000..24a62a96a7c09 --- /dev/null +++ b/integration-tests/esm-in-gatsby-files/__tests__/fixtures/gatsby-node.mjs @@ -0,0 +1,11 @@ +// This fixture is moved during the test lifecycle + +import helloDefaultESM from "./esm-default.mjs" +import { helloNamedESM } from "./esm-named.mjs" + +helloDefaultESM() +helloNamedESM() + +export const onPreBuild = () => { + console.info(`gatsby-node-esm-on-pre-build`); +}; diff --git a/integration-tests/esm-in-gatsby-files/__tests__/fixtures/plugins/a-local-plugin/gatsby-node.js b/integration-tests/esm-in-gatsby-files/__tests__/fixtures/plugins/a-local-plugin/gatsby-node.mjs similarity index 59% rename from integration-tests/esm-in-gatsby-files/__tests__/fixtures/plugins/a-local-plugin/gatsby-node.js rename to integration-tests/esm-in-gatsby-files/__tests__/fixtures/plugins/a-local-plugin/gatsby-node.mjs index ec7c0730fe53c..5872e2c0501df 100644 --- a/integration-tests/esm-in-gatsby-files/__tests__/fixtures/plugins/a-local-plugin/gatsby-node.js +++ b/integration-tests/esm-in-gatsby-files/__tests__/fixtures/plugins/a-local-plugin/gatsby-node.mjs @@ -1,3 +1,3 @@ -exports.onPreBuild = (_, { slugify }) => { +export const onPreBuild = (_, { slugify }) => { console.info(slugify(`a local plugin using passed esm module`)); }; diff --git a/integration-tests/esm-in-gatsby-files/__tests__/config.js b/integration-tests/esm-in-gatsby-files/__tests__/gatsby-config.test.js similarity index 90% rename from integration-tests/esm-in-gatsby-files/__tests__/config.js rename to integration-tests/esm-in-gatsby-files/__tests__/gatsby-config.test.js index 4b87243fd8050..c835de09a8d09 100644 --- a/integration-tests/esm-in-gatsby-files/__tests__/config.js +++ b/integration-tests/esm-in-gatsby-files/__tests__/gatsby-config.test.js @@ -60,7 +60,7 @@ describe(`gatsby-config.mjs`, () => { await fs.rm(localPluginTargetDir, { recursive: true }) }) - it(`works with required ESM modules`, async () => { + it(`works with imported ESM modules`, async () => { await fs.copyFile(fixturePath.esm, configPath.esm) await fs.ensureDir(localPluginTargetDir) @@ -75,10 +75,10 @@ describe(`gatsby-config.mjs`, () => { expect(stdout).toContain(`hello-default-esm`) expect(stdout).toContain(`hello-named-esm`) - // Local plugin works + // Local plugin gatsby-config.mjs works expect(stdout).toContain(`a-local-plugin-gatsby-config-mjs`) - // Local plugin passed modules via options work + // Local plugin with an esm module passed via options works, this implicitly tests gatsby-node.mjs too expect(stdout).toContain(`a-local-plugin-using-passed-esm-module`) }) }) diff --git a/integration-tests/esm-in-gatsby-files/__tests__/gatsby-node.test.js b/integration-tests/esm-in-gatsby-files/__tests__/gatsby-node.test.js new file mode 100644 index 0000000000000..d13a1bf5e927b --- /dev/null +++ b/integration-tests/esm-in-gatsby-files/__tests__/gatsby-node.test.js @@ -0,0 +1,77 @@ +import path from "path" +import fs from "fs-extra" +import execa from "execa" + +jest.setTimeout(100000) + +const fixtureRoot = path.resolve(__dirname, `fixtures`) +const siteRoot = path.resolve(__dirname, `..`) + +const fixturePath = { + cjs: path.join(fixtureRoot, `gatsby-node.js`), + esm: path.join(fixtureRoot, `gatsby-node.mjs`), +} + +const gatsbyNodePath = { + cjs: path.join(siteRoot, `gatsby-node.js`), + esm: path.join(siteRoot, `gatsby-node.mjs`), +} + +const gatsbyBin = path.join(`node_modules`, `gatsby`, `cli.js`) + +async function build() { + const { stdout } = await execa(process.execPath, [gatsbyBin, `build`], { + env: { + ...process.env, + NODE_ENV: `production`, + }, + }) + + return stdout +} + +// Tests include multiple assertions since running multiple builds is time consuming + +describe(`gatsby-node.js`, () => { + afterEach(() => { + fs.rmSync(gatsbyNodePath.cjs) + }) + + it(`works with required CJS modules`, async () => { + await fs.copyFile(fixturePath.cjs, gatsbyNodePath.cjs) + + const stdout = await build() + + // Build succeeded + expect(stdout).toContain(`Done building`) + + // Requires work + expect(stdout).toContain(`hello-default-cjs`) + expect(stdout).toContain(`hello-named-cjs`) + + // Node API works + expect(stdout).toContain(`gatsby-node-cjs-on-pre-build`) + }) +}) + +describe(`gatsby-node.mjs`, () => { + afterEach(async () => { + await fs.rm(gatsbyNodePath.esm) + }) + + it(`works with imported ESM modules`, async () => { + await fs.copyFile(fixturePath.esm, gatsbyNodePath.esm) + + const stdout = await build() + + // Build succeeded + expect(stdout).toContain(`Done building`) + + // Imports work + expect(stdout).toContain(`hello-default-esm`) + expect(stdout).toContain(`hello-named-esm`) + + // Node API works + expect(stdout).toContain(`gatsby-node-esm-on-pre-build`) + }) +}) From ea03d6fc769ea934b55aab013b1ed6d5e7065f2e Mon Sep 17 00:00:00 2001 From: Ty Hopp Date: Tue, 22 Nov 2022 15:59:37 +0800 Subject: [PATCH 13/35] Integration test for gatsby-node.mjs bundled in engines --- .../fixtures/gatsby-node-engine-bundled.mjs | 12 +++ .../__tests__/fixtures/pages/ssr.js | 22 +++++ .../__tests__/gatsby-node.test.js | 98 ++++++++++++++++--- .../esm-in-gatsby-files/package.json | 2 + 4 files changed, 122 insertions(+), 12 deletions(-) create mode 100644 integration-tests/esm-in-gatsby-files/__tests__/fixtures/gatsby-node-engine-bundled.mjs create mode 100644 integration-tests/esm-in-gatsby-files/__tests__/fixtures/pages/ssr.js diff --git a/integration-tests/esm-in-gatsby-files/__tests__/fixtures/gatsby-node-engine-bundled.mjs b/integration-tests/esm-in-gatsby-files/__tests__/fixtures/gatsby-node-engine-bundled.mjs new file mode 100644 index 0000000000000..97a673e52030a --- /dev/null +++ b/integration-tests/esm-in-gatsby-files/__tests__/fixtures/gatsby-node-engine-bundled.mjs @@ -0,0 +1,12 @@ +const createResolvers = ({ createResolvers }) => { + createResolvers({ + Query: { + fieldAddedByESMPlugin: { + type: `String`, + resolve: () => `gatsby-node-engine-bundled-mjs` + } + } + }) +} + +export { createResolvers } \ No newline at end of file diff --git a/integration-tests/esm-in-gatsby-files/__tests__/fixtures/pages/ssr.js b/integration-tests/esm-in-gatsby-files/__tests__/fixtures/pages/ssr.js new file mode 100644 index 0000000000000..65a8c7bb24f10 --- /dev/null +++ b/integration-tests/esm-in-gatsby-files/__tests__/fixtures/pages/ssr.js @@ -0,0 +1,22 @@ +import React from "react" +import { graphql } from "gatsby" + +function SSRPage({ data, serverData }) { + return
{JSON.stringify({ data, serverData }, null, 2)}
+} + +export const query = graphql` + { + fieldAddedByESMPlugin + } +` + +export const getServerData = () => { + return { + props: { + ssr: true, + }, + } +} + +export default SSRPage diff --git a/integration-tests/esm-in-gatsby-files/__tests__/gatsby-node.test.js b/integration-tests/esm-in-gatsby-files/__tests__/gatsby-node.test.js index d13a1bf5e927b..0c2aba7ac1d24 100644 --- a/integration-tests/esm-in-gatsby-files/__tests__/gatsby-node.test.js +++ b/integration-tests/esm-in-gatsby-files/__tests__/gatsby-node.test.js @@ -1,6 +1,8 @@ -import path from "path" -import fs from "fs-extra" -import execa from "execa" +const path = require(`path`) +const fs = require(`fs-extra`) +const execa = require(`execa`) +const { spawn } = require(`child_process`) +const fetch = require(`node-fetch`) jest.setTimeout(100000) @@ -8,13 +10,19 @@ const fixtureRoot = path.resolve(__dirname, `fixtures`) const siteRoot = path.resolve(__dirname, `..`) const fixturePath = { - cjs: path.join(fixtureRoot, `gatsby-node.js`), - esm: path.join(fixtureRoot, `gatsby-node.mjs`), + gatsbyNodeCjs: path.join(fixtureRoot, `gatsby-node.js`), + gatsbyNodeEsm: path.join(fixtureRoot, `gatsby-node.mjs`), + gatsbyNodeEsmBundled: path.join( + fixtureRoot, + `gatsby-node-engine-bundled.mjs` + ), + ssrPage: path.join(fixtureRoot, `pages`, `ssr.js`), } -const gatsbyNodePath = { - cjs: path.join(siteRoot, `gatsby-node.js`), - esm: path.join(siteRoot, `gatsby-node.mjs`), +const targetPath = { + gatsbyNodeCjs: path.join(siteRoot, `gatsby-node.js`), + gatsbyNodeEsm: path.join(siteRoot, `gatsby-node.mjs`), + ssrPage: path.join(siteRoot, `src`, `pages`, `ssr.js`), } const gatsbyBin = path.join(`node_modules`, `gatsby`, `cli.js`) @@ -30,15 +38,57 @@ async function build() { return stdout } +async function serve() { + const serveProcess = spawn(process.execPath, [gatsbyBin, `serve`], { + env: { + ...process.env, + NODE_ENV: `production`, + }, + }) + + await waitForServeReady(serveProcess) + + return serveProcess +} + +function wait(ms) { + return new Promise(resolve => setTimeout(resolve, ms)) +} + +async function waitForServeReady(serveProcess, retries = 0) { + if (retries > 10) { + console.error( + `Server for esm-in-gatsby-files > gatsby-node.test.js failed to get ready after 10 tries` + ) + serveProcess.kill() + } + + await wait(500) + + let ready = false + + try { + const { ok } = await fetch(`http://localhost:9000/`) + ready = ok + } catch (_) { + // Do nothing + } + + if (!ready) { + retries++ + return waitForServeReady(serveProcess, retries) + } +} + // Tests include multiple assertions since running multiple builds is time consuming describe(`gatsby-node.js`, () => { afterEach(() => { - fs.rmSync(gatsbyNodePath.cjs) + fs.rmSync(targetPath.gatsbyNodeCjs) }) it(`works with required CJS modules`, async () => { - await fs.copyFile(fixturePath.cjs, gatsbyNodePath.cjs) + await fs.copyFile(fixturePath.gatsbyNodeCjs, targetPath.gatsbyNodeCjs) const stdout = await build() @@ -56,11 +106,14 @@ describe(`gatsby-node.js`, () => { describe(`gatsby-node.mjs`, () => { afterEach(async () => { - await fs.rm(gatsbyNodePath.esm) + await fs.rm(targetPath.gatsbyNodeEsm) + if (fs.existsSync(targetPath.ssrPage)) { + await fs.rm(targetPath.ssrPage) + } }) it(`works with imported ESM modules`, async () => { - await fs.copyFile(fixturePath.esm, gatsbyNodePath.esm) + await fs.copyFile(fixturePath.gatsbyNodeEsm, targetPath.gatsbyNodeEsm) const stdout = await build() @@ -74,4 +127,25 @@ describe(`gatsby-node.mjs`, () => { // Node API works expect(stdout).toContain(`gatsby-node-esm-on-pre-build`) }) + + it(`works when bundled in engines`, async () => { + await fs.copyFile( + fixturePath.gatsbyNodeEsmBundled, + targetPath.gatsbyNodeEsm + ) + // Need to copy this because other runs fail on unfound query node type if the page is left in src + await fs.copyFile(fixturePath.ssrPage, targetPath.ssrPage) + + const buildStdout = await build() + const serveProcess = await serve() + const response = await fetch(`http://localhost:9000/ssr/`) + const html = await response.text() + serveProcess.kill() + + // Build succeeded + expect(buildStdout).toContain(`Done building`) + + // Engine bundling works + expect(html).toContain(`gatsby-node-engine-bundled-mjs`) + }) }) diff --git a/integration-tests/esm-in-gatsby-files/package.json b/integration-tests/esm-in-gatsby-files/package.json index c976d6e4339ff..0d007e0e3a939 100644 --- a/integration-tests/esm-in-gatsby-files/package.json +++ b/integration-tests/esm-in-gatsby-files/package.json @@ -3,6 +3,7 @@ "name": "esm-in-gatsby-files-integration-test", "version": "1.0.0", "scripts": { + "clean": "gatsby clean", "test": "jest --runInBand" }, "dependencies": { @@ -17,6 +18,7 @@ "execa": "^4.0.1", "fs-extra": "^10.1.0", "jest": "^27.2.1", + "node-fetch": "^2.6.0", "typescript": "^4.8.4" } } From fafbe19e2f39d38117adfb6b7fcd743b11c336ad Mon Sep 17 00:00:00 2001 From: Jude Agboola Date: Wed, 23 Nov 2022 00:16:19 +0200 Subject: [PATCH 14/35] Handle import errors --- .../gatsby/src/bootstrap/get-config-file.ts | 52 +++++++++---------- packages/gatsby/src/utils/is-import-error.ts | 17 ++++++ 2 files changed, 43 insertions(+), 26 deletions(-) create mode 100644 packages/gatsby/src/utils/is-import-error.ts diff --git a/packages/gatsby/src/bootstrap/get-config-file.ts b/packages/gatsby/src/bootstrap/get-config-file.ts index b289c0fba6d3b..fe2fff1f9b123 100644 --- a/packages/gatsby/src/bootstrap/get-config-file.ts +++ b/packages/gatsby/src/bootstrap/get-config-file.ts @@ -1,5 +1,5 @@ import fs from "fs-extra" -// import { testRequireError } from "../utils/test-require-error" +import { isImportError } from "../utils/is-import-error" import report from "gatsby-cli/lib/reporter" import path from "path" import { sync as existsSync } from "fs-exists-cached" @@ -35,21 +35,20 @@ export async function getConfigFile( // But the compiled file can also have an error like this: // "Cannot find module 'foobar'" // So this is trying to differentiate between an error we're fine ignoring and an error that we should throw - // const isModuleNotFoundError = outerError.code === `MODULE_NOT_FOUND` - // const isThisFileRequireError = - // outerError?.requireStack?.[0]?.includes(`get-config-file`) ?? true + const isModuleNotFoundError = outerError.code === `ERR_MODULE_NOT_FOUND` + const isThisFileRequireError = + outerError?.requireStack?.[0]?.includes(`get-config-file`) ?? true - // TODO: Adjust test for import, not require - // if (!(isModuleNotFoundError && isThisFileRequireError)) { - // report.panic({ - // id: `11902`, - // error: outerError, - // context: { - // configName, - // message: outerError.message, - // }, - // }) - // } + if (!(isModuleNotFoundError && isThisFileRequireError)) { + report.panic({ + id: `11902`, + error: outerError, + context: { + configName, + message: outerError.message, + }, + }) + } // Attempt to find uncompiled gatsby-config in root dir configPath = path.join(siteDirectory, configName) @@ -62,17 +61,18 @@ export async function getConfigFile( throw new Error(`No default export found in gatsby-config`) } } catch (innerError) { - // TODO: Adjust test for import, not require - // if (!testRequireError(configPath, innerError)) { - // report.panic({ - // id: `10123`, - // error: innerError, - // context: { - // configName, - // message: innerError.message, - // }, - // }) - // } + // Panic if error happened because user's gatsby-config has some errors + // if not, we'll contine trying to find a config + if (!isImportError(innerError)) { + report.panic({ + id: `10123`, + error: innerError, + context: { + configName, + message: innerError.message, + }, + }) + } const files = await fs.readdir(siteDirectory) diff --git a/packages/gatsby/src/utils/is-import-error.ts b/packages/gatsby/src/utils/is-import-error.ts new file mode 100644 index 0000000000000..e1534f5573933 --- /dev/null +++ b/packages/gatsby/src/utils/is-import-error.ts @@ -0,0 +1,17 @@ +// This module is also copied into the .cache directory some modules copied there +// from cache-dir can also use this module. +export const isImportError = (err: any): boolean => { + // PnP will return the following code when a require is allowed per the + // dependency tree rules but the requested file doesn't exist + // TODO: confirm if this is the same error code we get in PnP when a module isn't found + + if ( + err.code === `QUALIFIED_PATH_RESOLUTION_FAILED` || + err.pnpCode === `QUALIFIED_PATH_RESOLUTION_FAILED` + ) { + return true + } + return err + .toString() + .includes(`Error [ERR_MODULE_NOT_FOUND]: Cannot find package`) +} From 7a75ac777cc75c69f958002319fbb857129febc4 Mon Sep 17 00:00:00 2001 From: Ty Hopp Date: Wed, 23 Nov 2022 10:25:24 +0800 Subject: [PATCH 15/35] Adjust get-config-file test to make room for esm cases --- .../compiled}/compiled/gatsby-config.js | 0 .../get-config/{ => cjs}/gatsby-config.js | 0 .../near-match}/gatsby-confi.js | 0 .../{src-dir => cjs/src}/src/gatsby-config.js | 0 .../{ts-dir => cjs/ts}/gatsby-config.ts | 0 .../{tsx-dir => cjs/tsx}/gatsby-confi.tsx | 0 .../user-require}/compiled/gatsby-config.js | 0 .../bootstrap/__tests__/get-config-file.ts | 40 +++++++++++-------- 8 files changed, 23 insertions(+), 17 deletions(-) rename packages/gatsby/src/bootstrap/__mocks__/get-config/{compiled-dir => cjs/compiled}/compiled/gatsby-config.js (100%) rename packages/gatsby/src/bootstrap/__mocks__/get-config/{ => cjs}/gatsby-config.js (100%) rename packages/gatsby/src/bootstrap/__mocks__/get-config/{near-match-dir => cjs/near-match}/gatsby-confi.js (100%) rename packages/gatsby/src/bootstrap/__mocks__/get-config/{src-dir => cjs/src}/src/gatsby-config.js (100%) rename packages/gatsby/src/bootstrap/__mocks__/get-config/{ts-dir => cjs/ts}/gatsby-config.ts (100%) rename packages/gatsby/src/bootstrap/__mocks__/get-config/{tsx-dir => cjs/tsx}/gatsby-confi.tsx (100%) rename packages/gatsby/src/bootstrap/__mocks__/get-config/{user-require-dir => cjs/user-require}/compiled/gatsby-config.js (100%) diff --git a/packages/gatsby/src/bootstrap/__mocks__/get-config/compiled-dir/compiled/gatsby-config.js b/packages/gatsby/src/bootstrap/__mocks__/get-config/cjs/compiled/compiled/gatsby-config.js similarity index 100% rename from packages/gatsby/src/bootstrap/__mocks__/get-config/compiled-dir/compiled/gatsby-config.js rename to packages/gatsby/src/bootstrap/__mocks__/get-config/cjs/compiled/compiled/gatsby-config.js diff --git a/packages/gatsby/src/bootstrap/__mocks__/get-config/gatsby-config.js b/packages/gatsby/src/bootstrap/__mocks__/get-config/cjs/gatsby-config.js similarity index 100% rename from packages/gatsby/src/bootstrap/__mocks__/get-config/gatsby-config.js rename to packages/gatsby/src/bootstrap/__mocks__/get-config/cjs/gatsby-config.js diff --git a/packages/gatsby/src/bootstrap/__mocks__/get-config/near-match-dir/gatsby-confi.js b/packages/gatsby/src/bootstrap/__mocks__/get-config/cjs/near-match/gatsby-confi.js similarity index 100% rename from packages/gatsby/src/bootstrap/__mocks__/get-config/near-match-dir/gatsby-confi.js rename to packages/gatsby/src/bootstrap/__mocks__/get-config/cjs/near-match/gatsby-confi.js diff --git a/packages/gatsby/src/bootstrap/__mocks__/get-config/src-dir/src/gatsby-config.js b/packages/gatsby/src/bootstrap/__mocks__/get-config/cjs/src/src/gatsby-config.js similarity index 100% rename from packages/gatsby/src/bootstrap/__mocks__/get-config/src-dir/src/gatsby-config.js rename to packages/gatsby/src/bootstrap/__mocks__/get-config/cjs/src/src/gatsby-config.js diff --git a/packages/gatsby/src/bootstrap/__mocks__/get-config/ts-dir/gatsby-config.ts b/packages/gatsby/src/bootstrap/__mocks__/get-config/cjs/ts/gatsby-config.ts similarity index 100% rename from packages/gatsby/src/bootstrap/__mocks__/get-config/ts-dir/gatsby-config.ts rename to packages/gatsby/src/bootstrap/__mocks__/get-config/cjs/ts/gatsby-config.ts diff --git a/packages/gatsby/src/bootstrap/__mocks__/get-config/tsx-dir/gatsby-confi.tsx b/packages/gatsby/src/bootstrap/__mocks__/get-config/cjs/tsx/gatsby-confi.tsx similarity index 100% rename from packages/gatsby/src/bootstrap/__mocks__/get-config/tsx-dir/gatsby-confi.tsx rename to packages/gatsby/src/bootstrap/__mocks__/get-config/cjs/tsx/gatsby-confi.tsx diff --git a/packages/gatsby/src/bootstrap/__mocks__/get-config/user-require-dir/compiled/gatsby-config.js b/packages/gatsby/src/bootstrap/__mocks__/get-config/cjs/user-require/compiled/gatsby-config.js similarity index 100% rename from packages/gatsby/src/bootstrap/__mocks__/get-config/user-require-dir/compiled/gatsby-config.js rename to packages/gatsby/src/bootstrap/__mocks__/get-config/cjs/user-require/compiled/gatsby-config.js diff --git a/packages/gatsby/src/bootstrap/__tests__/get-config-file.ts b/packages/gatsby/src/bootstrap/__tests__/get-config-file.ts index 59d9b1e6d3ac1..f048b23f3a5d4 100644 --- a/packages/gatsby/src/bootstrap/__tests__/get-config-file.ts +++ b/packages/gatsby/src/bootstrap/__tests__/get-config-file.ts @@ -42,13 +42,19 @@ const reporterPanicMock = reporter.panic as jest.MockedFunction< > // Separate config directories so cases can be tested separately -const dir = path.resolve(__dirname, `../__mocks__/get-config`) -const compiledDir = `${dir}/compiled-dir` -const userRequireDir = `${dir}/user-require-dir` -const tsDir = `${dir}/ts-dir` -const tsxDir = `${dir}/tsx-dir` -const nearMatchDir = `${dir}/near-match-dir` -const srcDir = `${dir}/src-dir` +const baseDir = path.resolve(__dirname, `..`, `__mocks__`, `get-config`) +const cjsDir = path.join(baseDir, `cjs`) + +const configDir = { + cjs: { + compiled: path.join(cjsDir, `compiled`), + userRequire: path.join(cjsDir, `user-require`), + ts: path.join(cjsDir, `ts`), + tsx: path.join(cjsDir, `tsx`), + nearMatch: path.join(cjsDir, `near-match`), + src: path.join(cjsDir, `src`), + }, +} describe(`getConfigFile`, () => { beforeEach(() => { @@ -57,26 +63,26 @@ describe(`getConfigFile`, () => { it(`should get an uncompiled gatsby-config.js`, async () => { const { configModule, configFilePath } = await getConfigFile( - dir, + cjsDir, `gatsby-config` ) - expect(configFilePath).toBe(path.join(dir, `gatsby-config.js`)) + expect(configFilePath).toBe(path.join(cjsDir, `gatsby-config.js`)) expect(configModule.siteMetadata.title).toBe(`uncompiled`) }) it(`should get a compiled gatsby-config.js`, async () => { const { configModule, configFilePath } = await getConfigFile( - compiledDir, + configDir.cjs.compiled, `gatsby-config` ) expect(configFilePath).toBe( - path.join(compiledDir, `compiled`, `gatsby-config.js`) + path.join(configDir.cjs.compiled, `compiled`, `gatsby-config.js`) ) expect(configModule.siteMetadata.title).toBe(`compiled`) }) it(`should handle user require errors found in compiled gatsby-config.js`, async () => { - await getConfigFile(userRequireDir, `gatsby-config`) + await getConfigFile(configDir.cjs.userRequire, `gatsby-config`) expect(reporterPanicMock).toBeCalledWith({ id: `11902`, @@ -91,7 +97,7 @@ describe(`getConfigFile`, () => { it(`should handle non-require errors`, async () => { testRequireErrorMock.mockImplementationOnce(() => false) - await getConfigFile(nearMatchDir, `gatsby-config`) + await getConfigFile(configDir.cjs.nearMatch, `gatsby-config`) expect(reporterPanicMock).toBeCalledWith({ id: `10123`, @@ -110,7 +116,7 @@ describe(`getConfigFile`, () => { .mockImplementationOnce(() => `force-inner-error`) testRequireErrorMock.mockImplementationOnce(() => true) - await getConfigFile(tsDir, `gatsby-config`) + await getConfigFile(configDir.cjs.ts, `gatsby-config`) expect(reporterPanicMock).toBeCalledWith({ id: `10127`, @@ -124,7 +130,7 @@ describe(`getConfigFile`, () => { it(`should handle near matches`, async () => { testRequireErrorMock.mockImplementationOnce(() => true) - await getConfigFile(nearMatchDir, `gatsby-config`) + await getConfigFile(configDir.cjs.nearMatch, `gatsby-config`) expect(reporterPanicMock).toBeCalledWith({ id: `10124`, @@ -140,7 +146,7 @@ describe(`getConfigFile`, () => { it(`should handle .tsx extension`, async () => { testRequireErrorMock.mockImplementationOnce(() => true) - await getConfigFile(tsxDir, `gatsby-config`) + await getConfigFile(configDir.cjs.tsx, `gatsby-config`) expect(reporterPanicMock).toBeCalledWith({ id: `10124`, @@ -156,7 +162,7 @@ describe(`getConfigFile`, () => { it(`should handle gatsby config incorrectly located in src dir`, async () => { testRequireErrorMock.mockImplementationOnce(() => true) - await getConfigFile(srcDir, `gatsby-config`) + await getConfigFile(configDir.cjs.src, `gatsby-config`) expect(reporterPanicMock).toBeCalledWith({ id: `10125`, From 43f867695817eea35be29ad5c9b2e591c179e62e Mon Sep 17 00:00:00 2001 From: Ty Hopp Date: Wed, 23 Nov 2022 14:20:29 +0800 Subject: [PATCH 16/35] Flatten get-config-file, make unit and integration tests pass --- .../compiled/gatsby-config.js | 0 .../cjs/user-require/gatsby-config.js | 9 + .../get-config/esm/gatsby-config.mjs | 9 + .../esm/near-match/gatsby-confi.mjs | 9 + .../get-config/esm/src/src/gatsby-config.mjs | 9 + .../esm/user-import/gatsby-config.mjs | 11 + .../get-config/{cjs => }/ts/gatsby-config.ts | 0 .../get-config/{cjs => }/tsx/gatsby-confi.tsx | 0 .../bootstrap/__tests__/get-config-file.ts | 122 +++++--- .../gatsby/src/bootstrap/get-config-file.ts | 269 +++++++++++------- packages/gatsby/src/utils/is-import-error.ts | 17 -- .../gatsby/src/utils/test-import-error.ts | 19 ++ 12 files changed, 313 insertions(+), 161 deletions(-) rename packages/gatsby/src/bootstrap/__mocks__/get-config/cjs/{user-require => user-require-compiled}/compiled/gatsby-config.js (100%) create mode 100644 packages/gatsby/src/bootstrap/__mocks__/get-config/cjs/user-require/gatsby-config.js create mode 100644 packages/gatsby/src/bootstrap/__mocks__/get-config/esm/gatsby-config.mjs create mode 100644 packages/gatsby/src/bootstrap/__mocks__/get-config/esm/near-match/gatsby-confi.mjs create mode 100644 packages/gatsby/src/bootstrap/__mocks__/get-config/esm/src/src/gatsby-config.mjs create mode 100644 packages/gatsby/src/bootstrap/__mocks__/get-config/esm/user-import/gatsby-config.mjs rename packages/gatsby/src/bootstrap/__mocks__/get-config/{cjs => }/ts/gatsby-config.ts (100%) rename packages/gatsby/src/bootstrap/__mocks__/get-config/{cjs => }/tsx/gatsby-confi.tsx (100%) delete mode 100644 packages/gatsby/src/utils/is-import-error.ts create mode 100644 packages/gatsby/src/utils/test-import-error.ts diff --git a/packages/gatsby/src/bootstrap/__mocks__/get-config/cjs/user-require/compiled/gatsby-config.js b/packages/gatsby/src/bootstrap/__mocks__/get-config/cjs/user-require-compiled/compiled/gatsby-config.js similarity index 100% rename from packages/gatsby/src/bootstrap/__mocks__/get-config/cjs/user-require/compiled/gatsby-config.js rename to packages/gatsby/src/bootstrap/__mocks__/get-config/cjs/user-require-compiled/compiled/gatsby-config.js diff --git a/packages/gatsby/src/bootstrap/__mocks__/get-config/cjs/user-require/gatsby-config.js b/packages/gatsby/src/bootstrap/__mocks__/get-config/cjs/user-require/gatsby-config.js new file mode 100644 index 0000000000000..ea1e68c533fb5 --- /dev/null +++ b/packages/gatsby/src/bootstrap/__mocks__/get-config/cjs/user-require/gatsby-config.js @@ -0,0 +1,9 @@ +const something = require(`some-place-that-does-not-exist`) + +module.exports = { + siteMetadata: { + title: `user-require-error`, + siteUrl: `https://www.yourdomain.tld`, + }, + plugins: [], +} diff --git a/packages/gatsby/src/bootstrap/__mocks__/get-config/esm/gatsby-config.mjs b/packages/gatsby/src/bootstrap/__mocks__/get-config/esm/gatsby-config.mjs new file mode 100644 index 0000000000000..0465574572a2c --- /dev/null +++ b/packages/gatsby/src/bootstrap/__mocks__/get-config/esm/gatsby-config.mjs @@ -0,0 +1,9 @@ +const config = { + siteMetadata: { + title: `uncompiled`, + siteUrl: `https://www.yourdomain.tld`, + }, + plugins: [], +} + +export default config diff --git a/packages/gatsby/src/bootstrap/__mocks__/get-config/esm/near-match/gatsby-confi.mjs b/packages/gatsby/src/bootstrap/__mocks__/get-config/esm/near-match/gatsby-confi.mjs new file mode 100644 index 0000000000000..52998f353f01f --- /dev/null +++ b/packages/gatsby/src/bootstrap/__mocks__/get-config/esm/near-match/gatsby-confi.mjs @@ -0,0 +1,9 @@ +const config = { + siteMetadata: { + title: `near-match`, + siteUrl: `https://www.yourdomain.tld`, + }, + plugins: [], +} + +export default config diff --git a/packages/gatsby/src/bootstrap/__mocks__/get-config/esm/src/src/gatsby-config.mjs b/packages/gatsby/src/bootstrap/__mocks__/get-config/esm/src/src/gatsby-config.mjs new file mode 100644 index 0000000000000..47b20dd9dfc28 --- /dev/null +++ b/packages/gatsby/src/bootstrap/__mocks__/get-config/esm/src/src/gatsby-config.mjs @@ -0,0 +1,9 @@ +const config = { + siteMetadata: { + title: `in-src`, + siteUrl: `https://www.yourdomain.tld`, + }, + plugins: [], +} + +export default config diff --git a/packages/gatsby/src/bootstrap/__mocks__/get-config/esm/user-import/gatsby-config.mjs b/packages/gatsby/src/bootstrap/__mocks__/get-config/esm/user-import/gatsby-config.mjs new file mode 100644 index 0000000000000..aa5a484f9418c --- /dev/null +++ b/packages/gatsby/src/bootstrap/__mocks__/get-config/esm/user-import/gatsby-config.mjs @@ -0,0 +1,11 @@ +import something from "some-place-that-does-not-exist" + +const config = { + siteMetadata: { + title: `user-import-error`, + siteUrl: `https://www.yourdomain.tld`, + }, + plugins: [], +} + +export default config diff --git a/packages/gatsby/src/bootstrap/__mocks__/get-config/cjs/ts/gatsby-config.ts b/packages/gatsby/src/bootstrap/__mocks__/get-config/ts/gatsby-config.ts similarity index 100% rename from packages/gatsby/src/bootstrap/__mocks__/get-config/cjs/ts/gatsby-config.ts rename to packages/gatsby/src/bootstrap/__mocks__/get-config/ts/gatsby-config.ts diff --git a/packages/gatsby/src/bootstrap/__mocks__/get-config/cjs/tsx/gatsby-confi.tsx b/packages/gatsby/src/bootstrap/__mocks__/get-config/tsx/gatsby-confi.tsx similarity index 100% rename from packages/gatsby/src/bootstrap/__mocks__/get-config/cjs/tsx/gatsby-confi.tsx rename to packages/gatsby/src/bootstrap/__mocks__/get-config/tsx/gatsby-confi.tsx diff --git a/packages/gatsby/src/bootstrap/__tests__/get-config-file.ts b/packages/gatsby/src/bootstrap/__tests__/get-config-file.ts index f048b23f3a5d4..98bdd18ea8d5a 100644 --- a/packages/gatsby/src/bootstrap/__tests__/get-config-file.ts +++ b/packages/gatsby/src/bootstrap/__tests__/get-config-file.ts @@ -1,6 +1,5 @@ import path from "path" import { getConfigFile } from "../get-config-file" -import { testRequireError } from "../../utils/test-require-error" import reporter from "gatsby-cli/lib/reporter" jest.mock(`path`, () => { @@ -33,10 +32,6 @@ jest.mock(`gatsby-cli/lib/reporter`, () => { const pathJoinMock = path.join as jest.MockedFunction -const testRequireErrorMock = testRequireError as jest.MockedFunction< - typeof testRequireError -> - const reporterPanicMock = reporter.panic as jest.MockedFunction< typeof reporter.panic > @@ -44,19 +39,26 @@ const reporterPanicMock = reporter.panic as jest.MockedFunction< // Separate config directories so cases can be tested separately const baseDir = path.resolve(__dirname, `..`, `__mocks__`, `get-config`) const cjsDir = path.join(baseDir, `cjs`) +const esmDir = path.join(baseDir, `esm`) const configDir = { cjs: { compiled: path.join(cjsDir, `compiled`), + userRequireCompiled: path.join(cjsDir, `user-require-compiled`), userRequire: path.join(cjsDir, `user-require`), - ts: path.join(cjsDir, `ts`), - tsx: path.join(cjsDir, `tsx`), nearMatch: path.join(cjsDir, `near-match`), src: path.join(cjsDir, `src`), }, + esm: { + userImport: path.join(esmDir, `user-import`), + nearMatch: path.join(esmDir, `near-match`), + src: path.join(esmDir, `src`), + }, + ts: path.join(baseDir, `ts`), + tsx: path.join(baseDir, `tsx`), } -describe(`getConfigFile`, () => { +describe(`getConfigFile with cjs files`, () => { beforeEach(() => { reporterPanicMock.mockClear() }) @@ -82,7 +84,7 @@ describe(`getConfigFile`, () => { }) it(`should handle user require errors found in compiled gatsby-config.js`, async () => { - await getConfigFile(configDir.cjs.userRequire, `gatsby-config`) + await getConfigFile(configDir.cjs.userRequireCompiled, `gatsby-config`) expect(reporterPanicMock).toBeCalledWith({ id: `11902`, @@ -94,10 +96,8 @@ describe(`getConfigFile`, () => { }) }) - it(`should handle non-require errors`, async () => { - testRequireErrorMock.mockImplementationOnce(() => false) - - await getConfigFile(configDir.cjs.nearMatch, `gatsby-config`) + it(`should handle user require errors found in uncompiled gatsby-config.js`, async () => { + await getConfigFile(configDir.cjs.userRequire, `gatsby-config`) expect(reporterPanicMock).toBeCalledWith({ id: `10123`, @@ -109,28 +109,63 @@ describe(`getConfigFile`, () => { }) }) - it(`should handle case where gatsby-config.ts exists but no compiled gatsby-config.js exists`, async () => { - // Force outer and inner errors so we can hit the code path that checks if gatsby-config.ts exists - pathJoinMock - .mockImplementationOnce(() => `force-outer-error`) - .mockImplementationOnce(() => `force-inner-error`) - testRequireErrorMock.mockImplementationOnce(() => true) + it(`should handle near matches`, async () => { + await getConfigFile(configDir.cjs.nearMatch, `gatsby-config`) + + expect(reporterPanicMock).toBeCalledWith({ + id: `10124`, + error: expect.toBeObject(), + context: { + configName: `gatsby-config`, + isTSX: false, + nearMatch: `gatsby-confi.js`, + }, + }) + }) - await getConfigFile(configDir.cjs.ts, `gatsby-config`) + it(`should handle gatsby config incorrectly located in src dir`, async () => { + await getConfigFile(configDir.cjs.src, `gatsby-config`) expect(reporterPanicMock).toBeCalledWith({ - id: `10127`, + id: `10125`, + context: { + configName: `gatsby-config`, + }, + }) + }) +}) + +// TODO: We likely need to adjust the Jest config for this test suite, because integration tests pass yet +// these fail with things like "Cannot use import statement outside a module"" +describe.skip(`getConfigFile with esm files`, () => { + beforeEach(() => { + reporterPanicMock.mockClear() + }) + + it(`should get an uncompiled gatsby-config.mjs`, async () => { + const { configModule, configFilePath } = await getConfigFile( + esmDir, + `gatsby-config` + ) + expect(configFilePath).toBe(path.join(esmDir, `gatsby-config.mjs`)) + expect(configModule.siteMetadata.title).toBe(`uncompiled`) + }) + + it(`should handle user require errors found in uncompiled gatsby-config.mjs`, async () => { + await getConfigFile(configDir.esm.userImport, `gatsby-config`) + + expect(reporterPanicMock).toBeCalledWith({ + id: `10123`, error: expect.toBeObject(), context: { configName: `gatsby-config`, + message: expect.toBeString(), }, }) }) it(`should handle near matches`, async () => { - testRequireErrorMock.mockImplementationOnce(() => true) - - await getConfigFile(configDir.cjs.nearMatch, `gatsby-config`) + await getConfigFile(configDir.esm.nearMatch, `gatsby-config`) expect(reporterPanicMock).toBeCalledWith({ id: `10124`, @@ -138,36 +173,51 @@ describe(`getConfigFile`, () => { context: { configName: `gatsby-config`, isTSX: false, - nearMatch: `gatsby-confi.js`, + nearMatch: `gatsby-confi.mjs`, }, }) }) - it(`should handle .tsx extension`, async () => { - testRequireErrorMock.mockImplementationOnce(() => true) + it(`should handle gatsby config incorrectly located in src dir`, async () => { + await getConfigFile(configDir.esm.src, `gatsby-config`) - await getConfigFile(configDir.cjs.tsx, `gatsby-config`) + expect(reporterPanicMock).toBeCalledWith({ + id: `10125`, + context: { + configName: `gatsby-config`, + }, + }) + }) +}) + +describe(`getConfigFile with ts/tsx files`, () => { + it(`should handle case where gatsby-config.ts exists but no compiled gatsby-config.js exists`, async () => { + // Force outer and inner errors so we can hit the code path that checks if gatsby-config.ts exists + pathJoinMock + .mockImplementationOnce(() => `force-outer-error`) + .mockImplementationOnce(() => `force-inner-error`) + + await getConfigFile(configDir.ts, `gatsby-config`) expect(reporterPanicMock).toBeCalledWith({ - id: `10124`, + id: `10127`, error: expect.toBeObject(), context: { configName: `gatsby-config`, - isTSX: true, - nearMatch: `gatsby-confi.tsx`, }, }) }) - it(`should handle gatsby config incorrectly located in src dir`, async () => { - testRequireErrorMock.mockImplementationOnce(() => true) - - await getConfigFile(configDir.cjs.src, `gatsby-config`) + it(`should handle .tsx extension`, async () => { + await getConfigFile(configDir.tsx, `gatsby-config`) expect(reporterPanicMock).toBeCalledWith({ - id: `10125`, + id: `10124`, + error: expect.toBeObject(), context: { configName: `gatsby-config`, + isTSX: true, + nearMatch: `gatsby-confi.tsx`, }, }) }) diff --git a/packages/gatsby/src/bootstrap/get-config-file.ts b/packages/gatsby/src/bootstrap/get-config-file.ts index fe2fff1f9b123..26e44a0fcabce 100644 --- a/packages/gatsby/src/bootstrap/get-config-file.ts +++ b/packages/gatsby/src/bootstrap/get-config-file.ts @@ -1,5 +1,5 @@ import fs from "fs-extra" -import { isImportError } from "../utils/is-import-error" +import { testImportError } from "../utils/test-import-error" import report from "gatsby-cli/lib/reporter" import path from "path" import { sync as existsSync } from "fs-exists-cached" @@ -15,123 +15,176 @@ export async function getConfigFile( configModule: any configFilePath: string }> { - let configPath = `` - let configFilePath = `` - let configModule: any + const compiledResult = await attemptImportCompiled(siteDirectory, configName) + + if (compiledResult?.configModule && compiledResult?.configFilePath) { + return compiledResult + } + + const uncompiledResult = await attemptImportUncompiled( + siteDirectory, + configName, + distance + ) + + return uncompiledResult || {} +} + +async function attemptImport(configPath: string): Promise<{ + configModule: unknown + configFilePath: string +}> { + const configFilePath = resolveConfigFilePath(configPath) + + // The file does not exist, no sense trying to import it + if (!configFilePath) { + return { configFilePath: ``, configModule: undefined } + } + + const importedModule = await import(configFilePath) + const configModule = importedModule.default + + return { configFilePath, configModule } +} + +async function attemptImportCompiled( + siteDirectory: string, + configName: string +): Promise<{ + configModule: unknown + configFilePath: string +}> { + let compiledResult - // Attempt to find compiled gatsby-config in .cache/compiled/gatsby-config try { - configPath = path.join(`${siteDirectory}/${COMPILED_CACHE_DIR}`, configName) - configFilePath = resolveConfigFilePath(configPath) - configModule = await import(configFilePath) - if (!configModule.default) { - // TODO: Structured error - throw new Error(`No default export found in gatsby-config`) - } - configModule = configModule.default - } catch (outerError) { - // Not all plugins will have a compiled file, so the err.message can look like this: - // "Cannot find module '/node_modules/gatsby-source-filesystem/.cache/compiled/gatsby-config'" - // But the compiled file can also have an error like this: - // "Cannot find module 'foobar'" - // So this is trying to differentiate between an error we're fine ignoring and an error that we should throw - const isModuleNotFoundError = outerError.code === `ERR_MODULE_NOT_FOUND` - const isThisFileRequireError = - outerError?.requireStack?.[0]?.includes(`get-config-file`) ?? true - - if (!(isModuleNotFoundError && isThisFileRequireError)) { + const compiledConfigPath = path.join( + `${siteDirectory}/${COMPILED_CACHE_DIR}`, + configName + ) + compiledResult = await attemptImport(compiledConfigPath) + } catch (error) { + report.panic({ + id: `11902`, + error: error, + context: { + configName, + message: error.message, + }, + }) + } + + return compiledResult +} + +async function attemptImportUncompiled( + siteDirectory: string, + configName: string, + distance: number +): Promise<{ + configModule: unknown + configFilePath: string +}> { + let uncompiledResult + + const uncompiledConfigPath = path.join(siteDirectory, configName) + + try { + uncompiledResult = await attemptImport(uncompiledConfigPath) + } catch (error) { + if (!testImportError(uncompiledConfigPath, error)) { report.panic({ - id: `11902`, - error: outerError, + id: `10123`, + error, context: { configName, - message: outerError.message, + message: error.message, }, }) } + } + + if (uncompiledResult?.configModule && uncompiledResult?.configFilePath) { + return uncompiledResult + } + + const error = new Error(`Cannot find package '${uncompiledConfigPath}'`) + + const { tsConfig, nearMatch } = await checkTsAndNearMatch( + siteDirectory, + configName, + distance + ) + + // gatsby-config.ts exists but compiled gatsby-config.js does not + if (tsConfig) { + report.panic({ + id: `10127`, + error, + context: { + configName, + }, + }) + } + + // gatsby-config is misnamed + if (nearMatch) { + const isTSX = nearMatch.endsWith(`.tsx`) + report.panic({ + id: `10124`, + error, + context: { + configName, + nearMatch, + isTSX, + }, + }) + } + + // gatsby-config.js is incorrectly located in src/gatsby-config.js + if (existsSync(path.join(siteDirectory, `src`, configName + `.js`))) { + report.panic({ + id: `10125`, + context: { + configName, + }, + }) + } + + return uncompiledResult +} + +async function checkTsAndNearMatch( + siteDirectory: string, + configName: string, + distance: number +): Promise<{ + tsConfig: boolean + nearMatch: string +}> { + const files = await fs.readdir(siteDirectory) + + let tsConfig = false + let nearMatch = `` + + for (const file of files) { + if (tsConfig || nearMatch) { + break + } + + const { name, ext } = path.parse(file) - // Attempt to find uncompiled gatsby-config in root dir - configPath = path.join(siteDirectory, configName) - - try { - configFilePath = resolveConfigFilePath(configPath) - configModule = await import(configFilePath) - if (!configModule.default) { - // TODO: Structured error - throw new Error(`No default export found in gatsby-config`) - } - } catch (innerError) { - // Panic if error happened because user's gatsby-config has some errors - // if not, we'll contine trying to find a config - if (!isImportError(innerError)) { - report.panic({ - id: `10123`, - error: innerError, - context: { - configName, - message: innerError.message, - }, - }) - } - - const files = await fs.readdir(siteDirectory) - - let tsConfig = false - let nearMatch = `` - - for (const file of files) { - if (tsConfig || nearMatch) { - break - } - - const { name, ext } = path.parse(file) - - if (name === configName && ext === `.ts`) { - tsConfig = true - break - } - - if (isNearMatch(name, configName, distance)) { - nearMatch = file - } - } - - // gatsby-config.ts exists but compiled gatsby-config.js does not - if (tsConfig) { - report.panic({ - id: `10127`, - error: innerError, - context: { - configName, - }, - }) - } - - // gatsby-config is misnamed - if (nearMatch) { - const isTSX = nearMatch.endsWith(`.tsx`) - report.panic({ - id: `10124`, - error: innerError, - context: { - configName, - nearMatch, - isTSX, - }, - }) - } - - // gatsby-config.js is incorrectly located in src/gatsby-config.js - if (existsSync(path.join(siteDirectory, `src`, configName + `.js`))) { - report.panic({ - id: `10125`, - context: { - configName, - }, - }) - } + if (name === configName && ext === `.ts`) { + tsConfig = true + break + } + + if (isNearMatch(name, configName, distance)) { + nearMatch = file } } - return { configModule, configFilePath } + return { + tsConfig, + nearMatch, + } } diff --git a/packages/gatsby/src/utils/is-import-error.ts b/packages/gatsby/src/utils/is-import-error.ts deleted file mode 100644 index e1534f5573933..0000000000000 --- a/packages/gatsby/src/utils/is-import-error.ts +++ /dev/null @@ -1,17 +0,0 @@ -// This module is also copied into the .cache directory some modules copied there -// from cache-dir can also use this module. -export const isImportError = (err: any): boolean => { - // PnP will return the following code when a require is allowed per the - // dependency tree rules but the requested file doesn't exist - // TODO: confirm if this is the same error code we get in PnP when a module isn't found - - if ( - err.code === `QUALIFIED_PATH_RESOLUTION_FAILED` || - err.pnpCode === `QUALIFIED_PATH_RESOLUTION_FAILED` - ) { - return true - } - return err - .toString() - .includes(`Error [ERR_MODULE_NOT_FOUND]: Cannot find package`) -} diff --git a/packages/gatsby/src/utils/test-import-error.ts b/packages/gatsby/src/utils/test-import-error.ts new file mode 100644 index 0000000000000..7a015ddca177d --- /dev/null +++ b/packages/gatsby/src/utils/test-import-error.ts @@ -0,0 +1,19 @@ +export const testImportError = (moduleName: string, err: any): boolean => { + // PnP will return the following code when an import is allowed per the + // dependency tree rules but the requested file doesn't exist + if ( + err.code === `QUALIFIED_PATH_RESOLUTION_FAILED` || + err.pnpCode === `QUALIFIED_PATH_RESOLUTION_FAILED` + ) { + return true + } + const regex = new RegExp( + `ModuleNotFoundError:\\s(\\S+\\s)?[Cc]annot find module\\s.${moduleName.replace( + /[-/\\^$*+?.()|[\]{}]/g, + `\\$&` + )}` + ) + + const [firstLine] = err.toString().split(`\n`) + return regex.test(firstLine.replace(/\\\\/g, `\\`)) +} From f32ce8bdac7171f14988c8ba8682a4c5d9cbc775 Mon Sep 17 00:00:00 2001 From: Ty Hopp Date: Wed, 23 Nov 2022 16:57:28 +0800 Subject: [PATCH 17/35] Add test for resolve-config-file-path util --- .../both/gatsby-config.js | 3 ++ .../both/gatsby-config.mjs | 5 ++ .../cjs/gatsby-config.js | 3 ++ .../esm/gatsby-config.mjs | 5 ++ .../bootstrap/__tests__/get-config-file.ts | 4 +- .../__tests__/resolve-config-file-path.ts | 51 +++++++++++++++++++ 6 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 packages/gatsby/src/bootstrap/__mocks__/resolve-config-file-path/both/gatsby-config.js create mode 100644 packages/gatsby/src/bootstrap/__mocks__/resolve-config-file-path/both/gatsby-config.mjs create mode 100644 packages/gatsby/src/bootstrap/__mocks__/resolve-config-file-path/cjs/gatsby-config.js create mode 100644 packages/gatsby/src/bootstrap/__mocks__/resolve-config-file-path/esm/gatsby-config.mjs create mode 100644 packages/gatsby/src/bootstrap/__tests__/resolve-config-file-path.ts diff --git a/packages/gatsby/src/bootstrap/__mocks__/resolve-config-file-path/both/gatsby-config.js b/packages/gatsby/src/bootstrap/__mocks__/resolve-config-file-path/both/gatsby-config.js new file mode 100644 index 0000000000000..5ae66ab282a51 --- /dev/null +++ b/packages/gatsby/src/bootstrap/__mocks__/resolve-config-file-path/both/gatsby-config.js @@ -0,0 +1,3 @@ +module.exports = { + plugins: [], +} diff --git a/packages/gatsby/src/bootstrap/__mocks__/resolve-config-file-path/both/gatsby-config.mjs b/packages/gatsby/src/bootstrap/__mocks__/resolve-config-file-path/both/gatsby-config.mjs new file mode 100644 index 0000000000000..7963522537abd --- /dev/null +++ b/packages/gatsby/src/bootstrap/__mocks__/resolve-config-file-path/both/gatsby-config.mjs @@ -0,0 +1,5 @@ +const config = { + plugins: [], +} + +export default config diff --git a/packages/gatsby/src/bootstrap/__mocks__/resolve-config-file-path/cjs/gatsby-config.js b/packages/gatsby/src/bootstrap/__mocks__/resolve-config-file-path/cjs/gatsby-config.js new file mode 100644 index 0000000000000..5ae66ab282a51 --- /dev/null +++ b/packages/gatsby/src/bootstrap/__mocks__/resolve-config-file-path/cjs/gatsby-config.js @@ -0,0 +1,3 @@ +module.exports = { + plugins: [], +} diff --git a/packages/gatsby/src/bootstrap/__mocks__/resolve-config-file-path/esm/gatsby-config.mjs b/packages/gatsby/src/bootstrap/__mocks__/resolve-config-file-path/esm/gatsby-config.mjs new file mode 100644 index 0000000000000..7963522537abd --- /dev/null +++ b/packages/gatsby/src/bootstrap/__mocks__/resolve-config-file-path/esm/gatsby-config.mjs @@ -0,0 +1,5 @@ +const config = { + plugins: [], +} + +export default config diff --git a/packages/gatsby/src/bootstrap/__tests__/get-config-file.ts b/packages/gatsby/src/bootstrap/__tests__/get-config-file.ts index 98bdd18ea8d5a..ca041c3bdfc3a 100644 --- a/packages/gatsby/src/bootstrap/__tests__/get-config-file.ts +++ b/packages/gatsby/src/bootstrap/__tests__/get-config-file.ts @@ -10,9 +10,9 @@ jest.mock(`path`, () => { } }) -jest.mock(`../../utils/test-require-error`, () => { +jest.mock(`../../utils/test-import-error`, () => { return { - testRequireError: jest.fn(), + testImportError: jest.fn(), } }) diff --git a/packages/gatsby/src/bootstrap/__tests__/resolve-config-file-path.ts b/packages/gatsby/src/bootstrap/__tests__/resolve-config-file-path.ts new file mode 100644 index 0000000000000..a9d8dd81acb39 --- /dev/null +++ b/packages/gatsby/src/bootstrap/__tests__/resolve-config-file-path.ts @@ -0,0 +1,51 @@ +import path from "path" +import reporter from "gatsby-cli/lib/reporter" +import { resolveConfigFilePath } from "../resolve-config-file-path" + +const mockDir = path.resolve( + __dirname, + `..`, + `__mocks__`, + `resolve-config-file-path` +) + +jest.mock(`gatsby-cli/lib/reporter`, () => { + return { + warn: jest.fn(), + } +}) + +const reporterWarnMock = reporter.warn as jest.MockedFunction< + typeof reporter.warn +> + +beforeEach(() => { + reporterWarnMock.mockClear() +}) + +it(`resolves gatsby-config.js if it exists`, () => { + const configFilePath = path.join(mockDir, `cjs`, `gatsby-config`) + const resolvedConfigFilePath = resolveConfigFilePath(configFilePath) + expect(resolvedConfigFilePath).toBe(`${configFilePath}.js`) +}) + +it(`resolves gatsby-config.mjs if it exists`, () => { + const configFilePath = path.join(mockDir, `esm`, `gatsby-config`) + const resolvedConfigFilePath = resolveConfigFilePath(configFilePath) + expect(resolvedConfigFilePath).toBe(`${configFilePath}.mjs`) +}) + +it(`warns if both variants exist and defaults to the gatsby-config.js variant`, () => { + const configFilePath = path.join(mockDir, `both`, `gatsby-config`) + const resolvedConfigFilePath = resolveConfigFilePath(configFilePath) + expect(reporterWarnMock).toBeCalledWith( + `The file ${configFilePath} has both .js and .mjs variants, please use one or the other. Using .js by default.` + ) + expect(resolvedConfigFilePath).toBe(`${configFilePath}.js`) +}) + +it(`returns an empty string if no file exists`, () => { + const configFilePath = path.join(mockDir) + const resolvedConfigFilePath = resolveConfigFilePath(configFilePath) + expect(resolvedConfigFilePath).toBe(``) +}) From e6893bc95aaa5004faf5fb87dcc87e7ab4ec323c Mon Sep 17 00:00:00 2001 From: Ty Hopp Date: Wed, 23 Nov 2022 17:08:02 +0800 Subject: [PATCH 18/35] Use relative path in warning for resolve-config-file-path --- .../bootstrap/__tests__/resolve-config-file-path.ts | 11 ++++++----- packages/gatsby/src/bootstrap/get-config-file.ts | 11 +++++++---- .../gatsby/src/bootstrap/resolve-config-file-path.ts | 12 +++++++++--- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/packages/gatsby/src/bootstrap/__tests__/resolve-config-file-path.ts b/packages/gatsby/src/bootstrap/__tests__/resolve-config-file-path.ts index a9d8dd81acb39..e91d4b52b6962 100644 --- a/packages/gatsby/src/bootstrap/__tests__/resolve-config-file-path.ts +++ b/packages/gatsby/src/bootstrap/__tests__/resolve-config-file-path.ts @@ -25,27 +25,28 @@ beforeEach(() => { it(`resolves gatsby-config.js if it exists`, () => { const configFilePath = path.join(mockDir, `cjs`, `gatsby-config`) - const resolvedConfigFilePath = resolveConfigFilePath(configFilePath) + const resolvedConfigFilePath = resolveConfigFilePath(mockDir, configFilePath) expect(resolvedConfigFilePath).toBe(`${configFilePath}.js`) }) it(`resolves gatsby-config.mjs if it exists`, () => { const configFilePath = path.join(mockDir, `esm`, `gatsby-config`) - const resolvedConfigFilePath = resolveConfigFilePath(configFilePath) + const resolvedConfigFilePath = resolveConfigFilePath(mockDir, configFilePath) expect(resolvedConfigFilePath).toBe(`${configFilePath}.mjs`) }) it(`warns if both variants exist and defaults to the gatsby-config.js variant`, () => { const configFilePath = path.join(mockDir, `both`, `gatsby-config`) - const resolvedConfigFilePath = resolveConfigFilePath(configFilePath) + const relativeFilePath = path.relative(mockDir, configFilePath) + const resolvedConfigFilePath = resolveConfigFilePath(mockDir, configFilePath) expect(reporterWarnMock).toBeCalledWith( - `The file ${configFilePath} has both .js and .mjs variants, please use one or the other. Using .js by default.` + `The file '${relativeFilePath}' has both .js and .mjs variants, please use one or the other. Using .js by default.` ) expect(resolvedConfigFilePath).toBe(`${configFilePath}.js`) }) it(`returns an empty string if no file exists`, () => { const configFilePath = path.join(mockDir) - const resolvedConfigFilePath = resolveConfigFilePath(configFilePath) + const resolvedConfigFilePath = resolveConfigFilePath(mockDir, configFilePath) expect(resolvedConfigFilePath).toBe(``) }) diff --git a/packages/gatsby/src/bootstrap/get-config-file.ts b/packages/gatsby/src/bootstrap/get-config-file.ts index 26e44a0fcabce..6ebc909ea8578 100644 --- a/packages/gatsby/src/bootstrap/get-config-file.ts +++ b/packages/gatsby/src/bootstrap/get-config-file.ts @@ -30,11 +30,14 @@ export async function getConfigFile( return uncompiledResult || {} } -async function attemptImport(configPath: string): Promise<{ +async function attemptImport( + siteDirectory: string, + configPath: string +): Promise<{ configModule: unknown configFilePath: string }> { - const configFilePath = resolveConfigFilePath(configPath) + const configFilePath = resolveConfigFilePath(siteDirectory, configPath) // The file does not exist, no sense trying to import it if (!configFilePath) { @@ -61,7 +64,7 @@ async function attemptImportCompiled( `${siteDirectory}/${COMPILED_CACHE_DIR}`, configName ) - compiledResult = await attemptImport(compiledConfigPath) + compiledResult = await attemptImport(siteDirectory, compiledConfigPath) } catch (error) { report.panic({ id: `11902`, @@ -89,7 +92,7 @@ async function attemptImportUncompiled( const uncompiledConfigPath = path.join(siteDirectory, configName) try { - uncompiledResult = await attemptImport(uncompiledConfigPath) + uncompiledResult = await attemptImport(siteDirectory, uncompiledConfigPath) } catch (error) { if (!testImportError(uncompiledConfigPath, error)) { report.panic({ diff --git a/packages/gatsby/src/bootstrap/resolve-config-file-path.ts b/packages/gatsby/src/bootstrap/resolve-config-file-path.ts index 1f962865eb407..a3c7e7e646f1a 100644 --- a/packages/gatsby/src/bootstrap/resolve-config-file-path.ts +++ b/packages/gatsby/src/bootstrap/resolve-config-file-path.ts @@ -1,10 +1,14 @@ +import path from "path" import report from "gatsby-cli/lib/reporter" import { sync as existsSync } from "fs-exists-cached" /** * Figure out if the file path is .js or .mjs and return it if it exists. */ -export function resolveConfigFilePath(filePath: string): string { +export function resolveConfigFilePath( + siteDirectory: string, + filePath: string +): string { const filePathWithJSExtension = `${filePath}.js` const filePathWithMJSExtension = `${filePath}.mjs` @@ -12,9 +16,11 @@ export function resolveConfigFilePath(filePath: string): string { existsSync(filePathWithJSExtension) && existsSync(filePathWithMJSExtension) ) { - // TODO: Show project relative path in warning report.warn( - `The file ${filePath} has both .js and .mjs variants, please use one or the other. Using .js by default.` + `The file '${path.relative( + siteDirectory, + filePath + )}' has both .js and .mjs variants, please use one or the other. Using .js by default.` ) return filePathWithJSExtension } From ed0a480262288d79ec145b20707bb7b4a6810ff1 Mon Sep 17 00:00:00 2001 From: Jude Agboola Date: Tue, 29 Nov 2022 08:18:06 +0100 Subject: [PATCH 19/35] test(gatsby): Add esm-in-gatsby-files integration test to circleci config (#37097) --- .circleci/config.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index a7dfda5324fd1..13fa2674d6003 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -306,6 +306,13 @@ jobs: test_path: integration-tests/head-function-export test_command: yarn test + integration_tests_esm_in_gatsby_files: + executor: node + steps: + - e2e-test: + test_path: integration-tests/esm-in-gatsby-files + test_command: yarn test + e2e_tests_path-prefix: <<: *e2e-executor environment: @@ -592,6 +599,8 @@ workflows: <<: *e2e-test-workflow - integration_tests_head_function_export: <<: *e2e-test-workflow + - integration_tests_esm_in_gatsby_files: + <<: *e2e-test-workflow - integration_tests_gatsby_cli: requires: - bootstrap From fa72d4ae3231928908969246e365ceb38abc9cb6 Mon Sep 17 00:00:00 2001 From: Jude Agboola Date: Tue, 29 Nov 2022 08:19:45 +0100 Subject: [PATCH 20/35] test(gatsby): test that ESM only rehype/remark plugins work in `gatsby-plugin-mdx` (#37094) --- .../using-esm-only-remark-rehype-plugins.js | 15 +++++++++++++ .../{gatsby-config.js => gatsby-config.mjs} | 22 +++++++++++++++++-- e2e-tests/mdx/package.json | 2 ++ .../using-esm-only-rehype-remark-plugins.mdx | 8 +++++++ 4 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 e2e-tests/mdx/cypress/integration/using-esm-only-remark-rehype-plugins.js rename e2e-tests/mdx/{gatsby-config.js => gatsby-config.mjs} (66%) create mode 100644 e2e-tests/mdx/src/pages/using-esm-only-rehype-remark-plugins.mdx diff --git a/e2e-tests/mdx/cypress/integration/using-esm-only-remark-rehype-plugins.js b/e2e-tests/mdx/cypress/integration/using-esm-only-remark-rehype-plugins.js new file mode 100644 index 0000000000000..708b3ea538f20 --- /dev/null +++ b/e2e-tests/mdx/cypress/integration/using-esm-only-remark-rehype-plugins.js @@ -0,0 +1,15 @@ +describe(`ESM only Rehype & Remark plugins`, () => { + describe("Remark Plugin", () => { + it(`transforms to github-like checkbox list`, () => { + cy.visit(`/using-esm-only-rehype-remark-plugins/`).waitForRouteChange() + cy.get(`.task-list-item`).should("have.length", 2) + }) + }) + + describe("Rehype Plugin", () => { + it(`use heading text as id `, () => { + cy.visit(`/using-esm-only-rehype-remark-plugins/`).waitForRouteChange() + cy.get(`#heading-two`).invoke(`text`).should(`eq`, `Heading two`) + }) + }) +}) diff --git a/e2e-tests/mdx/gatsby-config.js b/e2e-tests/mdx/gatsby-config.mjs similarity index 66% rename from e2e-tests/mdx/gatsby-config.js rename to e2e-tests/mdx/gatsby-config.mjs index 20690d9594df7..a51e422cabfd1 100644 --- a/e2e-tests/mdx/gatsby-config.js +++ b/e2e-tests/mdx/gatsby-config.mjs @@ -1,4 +1,11 @@ -module.exports = { +import remarkGfm from "remark-gfm" +import rehypeSlug from "rehype-slug" +import { dirname } from "path" +import { fileURLToPath } from "url" + +const __dirname = dirname(fileURLToPath(import.meta.url)) + +const config = { siteMetadata: { title: `Gatsby MDX e2e`, }, @@ -28,7 +35,16 @@ module.exports = { `gatsby-remark-autolink-headers`, ], mdxOptions: { - remarkPlugins: [remarkRequireFilePathPlugin], + remarkPlugins: [ + remarkRequireFilePathPlugin, + // This is an esm only packages, It should work out of the box + remarkGfm, + ], + + rehypePlugins: [ + // This is an esm only packages, It should work out of the box + rehypeSlug, + ], }, }, }, @@ -47,3 +63,5 @@ function remarkRequireFilePathPlugin() { } } } + +export default config diff --git a/e2e-tests/mdx/package.json b/e2e-tests/mdx/package.json index 8ac2e303ab078..5ce04b582b85b 100644 --- a/e2e-tests/mdx/package.json +++ b/e2e-tests/mdx/package.json @@ -16,6 +16,8 @@ "gatsby-source-filesystem": "next", "react": "^18.2.0", "react-dom": "^18.2.0", + "rehype-slug": "^5.1.0", + "remark-gfm": "^3.0.1", "theme-ui": "^0.3.1" }, "keywords": [ diff --git a/e2e-tests/mdx/src/pages/using-esm-only-rehype-remark-plugins.mdx b/e2e-tests/mdx/src/pages/using-esm-only-rehype-remark-plugins.mdx new file mode 100644 index 0000000000000..607c0285b54fb --- /dev/null +++ b/e2e-tests/mdx/src/pages/using-esm-only-rehype-remark-plugins.mdx @@ -0,0 +1,8 @@ +--- +title: Using esm only rehype & rehype plugins +--- + +## Heading two + +- [ ] A +- [x] B From d9fd79fa6ccfe2d3b1e70c9581ae32fc8f901c09 Mon Sep 17 00:00:00 2001 From: Jude Agboola Date: Tue, 29 Nov 2022 08:21:04 +0100 Subject: [PATCH 21/35] test(gatsby): Make `resolveModuleExports` tests pass (#37105) --- .../__tests__/resolve-module-exports.ts | 100 ++++++++++-------- 1 file changed, 54 insertions(+), 46 deletions(-) diff --git a/packages/gatsby/src/bootstrap/__tests__/resolve-module-exports.ts b/packages/gatsby/src/bootstrap/__tests__/resolve-module-exports.ts index 9a18d031a4dfb..76ec60128a52c 100644 --- a/packages/gatsby/src/bootstrap/__tests__/resolve-module-exports.ts +++ b/packages/gatsby/src/bootstrap/__tests__/resolve-module-exports.ts @@ -138,18 +138,18 @@ describe(`Resolve module exports`, () => { reporter.panic.mockClear() }) - it(`Returns empty array for file paths that don't exist`, () => { - const result = resolveModuleExports(`/file/path/does/not/exist`) + it(`Returns empty array for file paths that don't exist`, async () => { + const result = await resolveModuleExports(`/file/path/does/not/exist`) expect(result).toEqual([]) }) - it(`Returns empty array for directory paths that don't exist`, () => { - const result = resolveModuleExports(`/directory/path/does/not/exist/`) + it(`Returns empty array for directory paths that don't exist`, async () => { + const result = await resolveModuleExports(`/directory/path/does/not/exist/`) expect(result).toEqual([]) }) - it(`Show meaningful error message for invalid JavaScript`, () => { - resolveModuleExports(`/bad/file`, { resolver }) + it(`Show meaningful error message for invalid JavaScript`, async () => { + await resolveModuleExports(`/bad/file`, { resolver }) expect( // @ts-ignore reporter.panic.mock.calls.map(c => @@ -160,119 +160,127 @@ describe(`Resolve module exports`, () => { ).toMatchSnapshot() }) - it(`Resolves an export`, () => { - const result = resolveModuleExports(`/simple/export`, { resolver }) + it(`Resolves an export`, async () => { + const result = await resolveModuleExports(`/simple/export`, { resolver }) expect(result).toEqual([`foo`]) }) - it(`Resolves multiple exports`, () => { - const result = resolveModuleExports(`/multiple/export`, { resolver }) + it(`Resolves multiple exports`, async () => { + const result = await resolveModuleExports(`/multiple/export`, { resolver }) expect(result).toEqual([`bar`, `baz`, `foo`]) }) - it(`Resolves an export from an ES6 file`, () => { - const result = resolveModuleExports(`/import/with/export`, { resolver }) + it(`Resolves an export from an ES6 file`, async () => { + const result = await resolveModuleExports(`/import/with/export`, { + resolver, + }) expect(result).toEqual([`baz`]) }) - it(`Resolves an exported const`, () => { - const result = resolveModuleExports(`/export/const`, { resolver }) + it(`Resolves an exported const`, async () => { + const result = await resolveModuleExports(`/export/const`, { resolver }) expect(result).toEqual([`fooConst`]) }) - it(`Resolves module.exports`, () => { - const result = resolveModuleExports(`/module/exports`, { resolver }) + it(`Resolves module.exports`, async () => { + const result = await resolveModuleExports(`/module/exports`, { resolver }) expect(result).toEqual([`barExports`]) }) - it(`Resolves exports from a larger file`, () => { - const result = resolveModuleExports(`/realistic/export`, { resolver }) + it(`Resolves exports from a larger file`, async () => { + const result = await resolveModuleExports(`/realistic/export`, { resolver }) expect(result).toEqual([`replaceHistory`, `replaceComponentRenderer`]) }) - it(`Ignores exports.__esModule`, () => { - const result = resolveModuleExports(`/esmodule/export`, { resolver }) + it(`Ignores exports.__esModule`, async () => { + const result = await resolveModuleExports(`/esmodule/export`, { resolver }) expect(result).toEqual([`foo`]) }) - it(`Resolves a named export`, () => { - const result = resolveModuleExports(`/export/named`, { resolver }) + it(`Resolves a named export`, async () => { + const result = await resolveModuleExports(`/export/named`, { resolver }) expect(result).toEqual([`foo`]) }) - it(`Resolves a named export from`, () => { - const result = resolveModuleExports(`/export/named/from`, { resolver }) + it(`Resolves a named export from`, async () => { + const result = await resolveModuleExports(`/export/named/from`, { + resolver, + }) expect(result).toEqual([`Component`]) }) - it(`Resolves a named export as`, () => { - const result = resolveModuleExports(`/export/named/as`, { resolver }) + it(`Resolves a named export as`, async () => { + const result = await resolveModuleExports(`/export/named/as`, { resolver }) expect(result).toEqual([`bar`]) }) - it(`Resolves multiple named exports`, () => { - const result = resolveModuleExports(`/export/named/multiple`, { resolver }) + it(`Resolves multiple named exports`, async () => { + const result = await resolveModuleExports(`/export/named/multiple`, { + resolver, + }) expect(result).toEqual([`foo`, `bar`, `baz`]) }) - it(`Resolves default export`, () => { - const result = resolveModuleExports(`/export/default`, { resolver }) + it(`Resolves default export`, async () => { + const result = await resolveModuleExports(`/export/default`, { resolver }) expect(result).toEqual([`export default`]) }) - it(`Resolves default export with name`, () => { - const result = resolveModuleExports(`/export/default/name`, { resolver }) + it(`Resolves default export with name`, async () => { + const result = await resolveModuleExports(`/export/default/name`, { + resolver, + }) expect(result).toEqual([`export default foo`]) }) - it(`Resolves default function`, () => { - const result = resolveModuleExports(`/export/default/function`, { + it(`Resolves default function`, async () => { + const result = await resolveModuleExports(`/export/default/function`, { resolver, }) expect(result).toEqual([`export default`]) }) - it(`Resolves default function with name`, () => { - const result = resolveModuleExports(`/export/default/function/name`, { + it(`Resolves default function with name`, async () => { + const result = await resolveModuleExports(`/export/default/function/name`, { resolver, }) expect(result).toEqual([`export default foo`]) }) - it(`Resolves function declaration`, () => { - const result = resolveModuleExports(`/export/function`, { resolver }) + it(`Resolves function declaration`, async () => { + const result = await resolveModuleExports(`/export/function`, { resolver }) expect(result).toEqual([`foo`]) }) - it(`Resolves exports when using require mode - simple case`, () => { + it(`Resolves exports when using require mode - simple case`, async () => { jest.mock(`require/exports`) - const result = resolveModuleExports(`require/exports`, { + const result = await resolveModuleExports(`require/exports`, { mode: `require`, }) expect(result).toEqual([`foo`, `bar`]) }) - it(`Resolves exports when using require mode - unusual case`, () => { + it(`Resolves exports when using require mode - unusual case`, async () => { jest.mock(`require/unusual-exports`) - const result = resolveModuleExports(`require/unusual-exports`, { + const result = await resolveModuleExports(`require/unusual-exports`, { mode: `require`, }) expect(result).toEqual([`foo`]) }) - it(`Resolves exports when using require mode - returns empty array when module doesn't exist`, () => { - const result = resolveModuleExports(`require/not-existing-module`, { + it(`Resolves exports when using require mode - returns empty array when module doesn't exist`, async () => { + const result = await resolveModuleExports(`require/not-existing-module`, { mode: `require`, }) expect(result).toEqual([]) }) - it(`Resolves exports when using require mode - panic on errors`, () => { + it(`Resolves exports when using require mode - panic on errors`, async () => { jest.mock(`require/module-error`) - resolveModuleExports(`require/module-error`, { + await resolveModuleExports(`require/module-error`, { mode: `require`, }) From e843c6f1fc89bb3bc8b1628c53e82f1c30f09711 Mon Sep 17 00:00:00 2001 From: Jude Agboola Date: Tue, 29 Nov 2022 08:22:29 +0100 Subject: [PATCH 22/35] test(gatsby): Adjust jest config, make get-config test pass in esm cases (#37098) Co-authored-by: Lennart --- jest.config.js | 2 +- .../gatsby/src/bootstrap/__tests__/get-config-file.ts | 4 +--- packages/gatsby/src/bootstrap/get-config-file.ts | 11 +++++++++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/jest.config.js b/jest.config.js index 24daa567e0961..284863c8697c5 100644 --- a/jest.config.js +++ b/jest.config.js @@ -124,7 +124,7 @@ module.exports = { ], transformIgnorePatterns: [`/node_modules/(?!${esModules})`], transform: { - "^.+\\.[jt]sx?$": `/jest-transformer.js`, + "^.+\\.jsx|js|ts|mjs?$": `/jest-transformer.js`, }, moduleNameMapper: { "^highlight.js$": `/node_modules/highlight.js/lib/index.js`, diff --git a/packages/gatsby/src/bootstrap/__tests__/get-config-file.ts b/packages/gatsby/src/bootstrap/__tests__/get-config-file.ts index ca041c3bdfc3a..6783e847e84d1 100644 --- a/packages/gatsby/src/bootstrap/__tests__/get-config-file.ts +++ b/packages/gatsby/src/bootstrap/__tests__/get-config-file.ts @@ -135,9 +135,7 @@ describe(`getConfigFile with cjs files`, () => { }) }) -// TODO: We likely need to adjust the Jest config for this test suite, because integration tests pass yet -// these fail with things like "Cannot use import statement outside a module"" -describe.skip(`getConfigFile with esm files`, () => { +describe(`getConfigFile with esm files`, () => { beforeEach(() => { reporterPanicMock.mockClear() }) diff --git a/packages/gatsby/src/bootstrap/get-config-file.ts b/packages/gatsby/src/bootstrap/get-config-file.ts index 6ebc909ea8578..fe74f166086fd 100644 --- a/packages/gatsby/src/bootstrap/get-config-file.ts +++ b/packages/gatsby/src/bootstrap/get-config-file.ts @@ -143,8 +143,15 @@ async function attemptImportUncompiled( }) } - // gatsby-config.js is incorrectly located in src/gatsby-config.js - if (existsSync(path.join(siteDirectory, `src`, configName + `.js`))) { + // gatsby-config.js/gatsby-config.mjs is incorrectly located in src/gatsby-config.js + const srcDirectoryHasJsConfig = existsSync( + path.join(siteDirectory, `src`, configName + `.js`) + ) + const srcDirectoryHasMJsConfig = existsSync( + path.join(siteDirectory, `src`, configName + `.mjs`) + ) + + if (srcDirectoryHasJsConfig || srcDirectoryHasMJsConfig) { report.panic({ id: `10125`, context: { From b8d30bed4a075f0310501a834b67cc901d777e13 Mon Sep 17 00:00:00 2001 From: Lennart Date: Tue, 29 Nov 2022 08:27:18 +0100 Subject: [PATCH 23/35] Update jest.config.js --- jest.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jest.config.js b/jest.config.js index 284863c8697c5..95507e9d206aa 100644 --- a/jest.config.js +++ b/jest.config.js @@ -124,7 +124,7 @@ module.exports = { ], transformIgnorePatterns: [`/node_modules/(?!${esModules})`], transform: { - "^.+\\.jsx|js|ts|mjs?$": `/jest-transformer.js`, + "^.+\\.jsx|js|ts|tsx|mjs?$": `/jest-transformer.js`, }, moduleNameMapper: { "^highlight.js$": `/node_modules/highlight.js/lib/index.js`, From 3f133134a67a8889014402ed392a815bbf4963dd Mon Sep 17 00:00:00 2001 From: Jude Agboola Date: Tue, 29 Nov 2022 09:40:24 +0100 Subject: [PATCH 24/35] update jest config transform regex --- jest.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jest.config.js b/jest.config.js index 95507e9d206aa..5ca27e4a58357 100644 --- a/jest.config.js +++ b/jest.config.js @@ -124,7 +124,7 @@ module.exports = { ], transformIgnorePatterns: [`/node_modules/(?!${esModules})`], transform: { - "^.+\\.jsx|js|ts|tsx|mjs?$": `/jest-transformer.js`, + "^.+\\.(jsx|js|mjs|ts|tsx)$": `/jest-transformer.js`, }, moduleNameMapper: { "^highlight.js$": `/node_modules/highlight.js/lib/index.js`, From 23e5c2539b7309e124265fa90b144d649fe9963e Mon Sep 17 00:00:00 2001 From: pieh Date: Tue, 29 Nov 2022 11:38:19 +0100 Subject: [PATCH 25/35] fix job/manager tests --- .../src/utils/jobs/__tests__/manager.js | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/packages/gatsby/src/utils/jobs/__tests__/manager.js b/packages/gatsby/src/utils/jobs/__tests__/manager.js index 31670a2eab328..7c50e99077948 100644 --- a/packages/gatsby/src/utils/jobs/__tests__/manager.js +++ b/packages/gatsby/src/utils/jobs/__tests__/manager.js @@ -8,6 +8,7 @@ const fs = require(`fs-extra`) const pDefer = require(`p-defer`) const { uuid } = require(`gatsby-core-utils`) const timers = require(`timers`) +const { MESSAGE_TYPES } = require(`../types`) let WorkerError let jobManager = null @@ -395,6 +396,11 @@ describe(`Jobs manager`, () => { let originalProcessOn let originalSend + /** + * enqueueJob will run some async code before it sends IPC JOB_CREATED message + * This promise allow to await until that moment, to make assertions or execute more code + */ + let waitForJobCreatedIPCSend beforeEach(() => { process.env.ENABLE_GATSBY_EXTERNAL_JOBS = `true` listeners = [] @@ -404,7 +410,14 @@ describe(`Jobs manager`, () => { listeners.push(cb) } - process.send = jest.fn() + waitForJobCreatedIPCSend = new Promise(resolve => { + process.send = jest.fn(msg => { + if (msg?.type === MESSAGE_TYPES.JOB_CREATED) { + resolve(msg.payload) + } + }) + }) + jest.useFakeTimers() }) @@ -425,6 +438,8 @@ describe(`Jobs manager`, () => { enqueueJob(jobArgs) + await waitForJobCreatedIPCSend + jest.runAllTimers() expect(process.send).toHaveBeenCalled() @@ -442,6 +457,9 @@ describe(`Jobs manager`, () => { const jobArgs = createInternalMockJob() const promise = enqueueJob(jobArgs) + + await waitForJobCreatedIPCSend + jest.runAllTimers() listeners[0]({ @@ -468,6 +486,8 @@ describe(`Jobs manager`, () => { const promise = enqueueJob(jobArgs) + await waitForJobCreatedIPCSend + jest.runAllTimers() listeners[0]({ @@ -492,6 +512,8 @@ describe(`Jobs manager`, () => { const jobArgs = createInternalMockJob() const promise = enqueueJob(jobArgs) + await waitForJobCreatedIPCSend + listeners[0]({ type: `JOB_NOT_WHITELISTED`, payload: { From 09981434b101aab1b319aac4e52b4f2599686d0e Mon Sep 17 00:00:00 2001 From: Jude Agboola Date: Tue, 29 Nov 2022 12:52:32 +0100 Subject: [PATCH 26/35] add babel-preset-gatsby-package to itest devDeps --- integration-tests/esm-in-gatsby-files/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/integration-tests/esm-in-gatsby-files/package.json b/integration-tests/esm-in-gatsby-files/package.json index 0d007e0e3a939..d9acff788bb11 100644 --- a/integration-tests/esm-in-gatsby-files/package.json +++ b/integration-tests/esm-in-gatsby-files/package.json @@ -15,6 +15,7 @@ "@types/node": "^18.11.9", "@types/react": "^18.0.25", "@types/react-dom": "^18.0.8", + "babel-preset-gatsby-package": "^2.4.0", "execa": "^4.0.1", "fs-extra": "^10.1.0", "jest": "^27.2.1", From ed00873b370ceaac4a6a449ad5b752ece7a77f6b Mon Sep 17 00:00:00 2001 From: Jude Agboola Date: Tue, 29 Nov 2022 14:00:00 +0100 Subject: [PATCH 27/35] fix collate plugin tests --- .../src/bootstrap/load-plugins/__tests__/validate.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/gatsby/src/bootstrap/load-plugins/__tests__/validate.ts b/packages/gatsby/src/bootstrap/load-plugins/__tests__/validate.ts index 2fd2bed7826b8..d27e9601bcf02 100644 --- a/packages/gatsby/src/bootstrap/load-plugins/__tests__/validate.ts +++ b/packages/gatsby/src/bootstrap/load-plugins/__tests__/validate.ts @@ -79,7 +79,10 @@ describe(`collatePluginAPIs`, () => { }, ] - const result = collatePluginAPIs({ currentAPIs: apis, flattenedPlugins }) + const result = await collatePluginAPIs({ + currentAPIs: apis, + flattenedPlugins, + }) expect(result).toMatchSnapshot() }) @@ -106,7 +109,10 @@ describe(`collatePluginAPIs`, () => { }, ] - const result = collatePluginAPIs({ currentAPIs: apis, flattenedPlugins }) + const result = await collatePluginAPIs({ + currentAPIs: apis, + flattenedPlugins, + }) expect(result).toMatchSnapshot() }) }) From 97f971096fa099e42d459e61895f1d4e9ff82ed1 Mon Sep 17 00:00:00 2001 From: Jude Agboola Date: Wed, 30 Nov 2022 15:37:22 +0100 Subject: [PATCH 28/35] feat(gatsby): Telemetry tracking for ESM (#37122) --- packages/gatsby/src/services/initialize.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/gatsby/src/services/initialize.ts b/packages/gatsby/src/services/initialize.ts index ae1af55b5b21c..f360a472ddbf7 100644 --- a/packages/gatsby/src/services/initialize.ts +++ b/packages/gatsby/src/services/initialize.ts @@ -636,6 +636,16 @@ export async function initialize({ const workerPool = WorkerPool.create() + const siteDirectoryFiles = await fs.readdir(siteDirectory) + + const gatsbyFilesIsInESM = siteDirectoryFiles.some(file => + file.match(/gatsby-(node|config)\.mjs/) + ) + + if (gatsbyFilesIsInESM) { + telemetry.trackFeatureIsUsed(`ESMInGatsbyFiles`) + } + if (state.config.graphqlTypegen) { telemetry.trackFeatureIsUsed(`GraphQLTypegen`) // This is only run during `gatsby develop` From c9990116d45d2e2989c2c10f1dcad367de7f26f1 Mon Sep 17 00:00:00 2001 From: Jude Agboola Date: Wed, 30 Nov 2022 16:44:09 +0100 Subject: [PATCH 29/35] allow empty gatsby-config.mjs files --- packages/gatsby/src/bootstrap/get-config-file.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gatsby/src/bootstrap/get-config-file.ts b/packages/gatsby/src/bootstrap/get-config-file.ts index fe74f166086fd..81eba31675a00 100644 --- a/packages/gatsby/src/bootstrap/get-config-file.ts +++ b/packages/gatsby/src/bootstrap/get-config-file.ts @@ -106,7 +106,7 @@ async function attemptImportUncompiled( } } - if (uncompiledResult?.configModule && uncompiledResult?.configFilePath) { + if (uncompiledResult?.configFilePath) { return uncompiledResult } From b4e07e80a898ebe11ebad5b9a80914b9dd234a8f Mon Sep 17 00:00:00 2001 From: Ty Hopp Date: Tue, 6 Dec 2022 13:04:59 +0800 Subject: [PATCH 30/35] refactor(gatsby): gatsby-node.mjs cleanup (#37128) * Adjust resolve-module-exports to use single await import * Fix load-plugins tests * Import plugins instead of require them * Remove todo, api-runner-node refactor should be revisited * Make jobs manager tests pass again * Do not gitignore jobs manager fixture in node_modules * Prefer default to handle case of await import cjs module * Resolve js file path without fs module * Do not remove default property * Fix case where compiled file paths include file extensions already --- .../esm-in-gatsby-files/package.json | 2 + packages/gatsby/babel.config.js | 2 +- .../__mocks__/{require => import}/exports.js | 0 .../{require => import}/module-error.js | 0 .../{require => import}/unusual-exports.js | 0 .../both/gatsby-config.js | 0 .../both/gatsby-config.mjs | 0 .../cjs/gatsby-config.js | 0 .../esm/gatsby-config.mjs | 0 .../__tests__/resolve-config-file-path.ts | 52 ------------ .../__tests__/resolve-js-file-path.ts | 82 +++++++++++++++++++ .../__tests__/resolve-module-exports.ts | 49 +++++++---- .../gatsby/src/bootstrap/get-config-file.ts | 7 +- .../load-plugins/__tests__/load-plugins.ts | 11 +++ .../src/bootstrap/load-plugins/index.ts | 1 + .../src/bootstrap/load-plugins/validate.ts | 13 ++- .../src/bootstrap/resolve-config-file-path.ts | 37 --------- .../src/bootstrap/resolve-js-file-path.ts | 54 ++++++++++++ .../src/bootstrap/resolve-module-exports.ts | 74 +++++++++-------- .../gatsby/src/schema/graphql-engine/entry.ts | 2 +- .../schema/graphql-engine/print-plugins.ts | 7 +- packages/gatsby/src/utils/api-runner-node.js | 7 +- .../gatsby/src/utils/import-gatsby-plugin.ts | 50 +++++++++++ .../utils/jobs/__tests__/fixtures/.gitignore | 1 + .../gatsby-plugin-local/gatsby-worker.js | 4 + .../gatsby-plugin-test/gatsby-worker.js | 4 + .../src/utils/jobs/__tests__/manager.js | 49 ++++++----- packages/gatsby/src/utils/jobs/manager.ts | 4 +- packages/gatsby/src/utils/module-resolver.ts | 2 +- .../gatsby/src/utils/require-gatsby-plugin.ts | 59 ------------- 30 files changed, 325 insertions(+), 248 deletions(-) rename packages/gatsby/src/bootstrap/__mocks__/{require => import}/exports.js (100%) rename packages/gatsby/src/bootstrap/__mocks__/{require => import}/module-error.js (100%) rename packages/gatsby/src/bootstrap/__mocks__/{require => import}/unusual-exports.js (100%) rename packages/gatsby/src/bootstrap/__mocks__/{resolve-config-file-path => resolve-js-file-path}/both/gatsby-config.js (100%) rename packages/gatsby/src/bootstrap/__mocks__/{resolve-config-file-path => resolve-js-file-path}/both/gatsby-config.mjs (100%) rename packages/gatsby/src/bootstrap/__mocks__/{resolve-config-file-path => resolve-js-file-path}/cjs/gatsby-config.js (100%) rename packages/gatsby/src/bootstrap/__mocks__/{resolve-config-file-path => resolve-js-file-path}/esm/gatsby-config.mjs (100%) delete mode 100644 packages/gatsby/src/bootstrap/__tests__/resolve-config-file-path.ts create mode 100644 packages/gatsby/src/bootstrap/__tests__/resolve-js-file-path.ts delete mode 100644 packages/gatsby/src/bootstrap/resolve-config-file-path.ts create mode 100644 packages/gatsby/src/bootstrap/resolve-js-file-path.ts create mode 100644 packages/gatsby/src/utils/import-gatsby-plugin.ts create mode 100644 packages/gatsby/src/utils/jobs/__tests__/fixtures/.gitignore create mode 100644 packages/gatsby/src/utils/jobs/__tests__/fixtures/gatsby-plugin-local/gatsby-worker.js create mode 100644 packages/gatsby/src/utils/jobs/__tests__/fixtures/node_modules/gatsby-plugin-test/gatsby-worker.js delete mode 100644 packages/gatsby/src/utils/require-gatsby-plugin.ts diff --git a/integration-tests/esm-in-gatsby-files/package.json b/integration-tests/esm-in-gatsby-files/package.json index d9acff788bb11..3bb2320f48f58 100644 --- a/integration-tests/esm-in-gatsby-files/package.json +++ b/integration-tests/esm-in-gatsby-files/package.json @@ -4,6 +4,8 @@ "version": "1.0.0", "scripts": { "clean": "gatsby clean", + "build": "gatsby build", + "serve": "gatsby serve", "test": "jest --runInBand" }, "dependencies": { diff --git a/packages/gatsby/babel.config.js b/packages/gatsby/babel.config.js index fdd6e7fa00e7f..3d14ea9341573 100644 --- a/packages/gatsby/babel.config.js +++ b/packages/gatsby/babel.config.js @@ -10,7 +10,7 @@ module.exports = { // These files use dynamic imports to load gatsby-config and gatsby-node so esm works `./src/bootstrap/get-config-file.ts`, `./src/bootstrap/resolve-module-exports.ts`, - `./src/utils/require-gatsby-plugin.ts` + `./src/utils/import-gatsby-plugin.ts` ] }]], } diff --git a/packages/gatsby/src/bootstrap/__mocks__/require/exports.js b/packages/gatsby/src/bootstrap/__mocks__/import/exports.js similarity index 100% rename from packages/gatsby/src/bootstrap/__mocks__/require/exports.js rename to packages/gatsby/src/bootstrap/__mocks__/import/exports.js diff --git a/packages/gatsby/src/bootstrap/__mocks__/require/module-error.js b/packages/gatsby/src/bootstrap/__mocks__/import/module-error.js similarity index 100% rename from packages/gatsby/src/bootstrap/__mocks__/require/module-error.js rename to packages/gatsby/src/bootstrap/__mocks__/import/module-error.js diff --git a/packages/gatsby/src/bootstrap/__mocks__/require/unusual-exports.js b/packages/gatsby/src/bootstrap/__mocks__/import/unusual-exports.js similarity index 100% rename from packages/gatsby/src/bootstrap/__mocks__/require/unusual-exports.js rename to packages/gatsby/src/bootstrap/__mocks__/import/unusual-exports.js diff --git a/packages/gatsby/src/bootstrap/__mocks__/resolve-config-file-path/both/gatsby-config.js b/packages/gatsby/src/bootstrap/__mocks__/resolve-js-file-path/both/gatsby-config.js similarity index 100% rename from packages/gatsby/src/bootstrap/__mocks__/resolve-config-file-path/both/gatsby-config.js rename to packages/gatsby/src/bootstrap/__mocks__/resolve-js-file-path/both/gatsby-config.js diff --git a/packages/gatsby/src/bootstrap/__mocks__/resolve-config-file-path/both/gatsby-config.mjs b/packages/gatsby/src/bootstrap/__mocks__/resolve-js-file-path/both/gatsby-config.mjs similarity index 100% rename from packages/gatsby/src/bootstrap/__mocks__/resolve-config-file-path/both/gatsby-config.mjs rename to packages/gatsby/src/bootstrap/__mocks__/resolve-js-file-path/both/gatsby-config.mjs diff --git a/packages/gatsby/src/bootstrap/__mocks__/resolve-config-file-path/cjs/gatsby-config.js b/packages/gatsby/src/bootstrap/__mocks__/resolve-js-file-path/cjs/gatsby-config.js similarity index 100% rename from packages/gatsby/src/bootstrap/__mocks__/resolve-config-file-path/cjs/gatsby-config.js rename to packages/gatsby/src/bootstrap/__mocks__/resolve-js-file-path/cjs/gatsby-config.js diff --git a/packages/gatsby/src/bootstrap/__mocks__/resolve-config-file-path/esm/gatsby-config.mjs b/packages/gatsby/src/bootstrap/__mocks__/resolve-js-file-path/esm/gatsby-config.mjs similarity index 100% rename from packages/gatsby/src/bootstrap/__mocks__/resolve-config-file-path/esm/gatsby-config.mjs rename to packages/gatsby/src/bootstrap/__mocks__/resolve-js-file-path/esm/gatsby-config.mjs diff --git a/packages/gatsby/src/bootstrap/__tests__/resolve-config-file-path.ts b/packages/gatsby/src/bootstrap/__tests__/resolve-config-file-path.ts deleted file mode 100644 index e91d4b52b6962..0000000000000 --- a/packages/gatsby/src/bootstrap/__tests__/resolve-config-file-path.ts +++ /dev/null @@ -1,52 +0,0 @@ -import path from "path" -import reporter from "gatsby-cli/lib/reporter" -import { resolveConfigFilePath } from "../resolve-config-file-path" - -const mockDir = path.resolve( - __dirname, - `..`, - `__mocks__`, - `resolve-config-file-path` -) - -jest.mock(`gatsby-cli/lib/reporter`, () => { - return { - warn: jest.fn(), - } -}) - -const reporterWarnMock = reporter.warn as jest.MockedFunction< - typeof reporter.warn -> - -beforeEach(() => { - reporterWarnMock.mockClear() -}) - -it(`resolves gatsby-config.js if it exists`, () => { - const configFilePath = path.join(mockDir, `cjs`, `gatsby-config`) - const resolvedConfigFilePath = resolveConfigFilePath(mockDir, configFilePath) - expect(resolvedConfigFilePath).toBe(`${configFilePath}.js`) -}) - -it(`resolves gatsby-config.mjs if it exists`, () => { - const configFilePath = path.join(mockDir, `esm`, `gatsby-config`) - const resolvedConfigFilePath = resolveConfigFilePath(mockDir, configFilePath) - expect(resolvedConfigFilePath).toBe(`${configFilePath}.mjs`) -}) - -it(`warns if both variants exist and defaults to the gatsby-config.js variant`, () => { - const configFilePath = path.join(mockDir, `both`, `gatsby-config`) - const relativeFilePath = path.relative(mockDir, configFilePath) - const resolvedConfigFilePath = resolveConfigFilePath(mockDir, configFilePath) - expect(reporterWarnMock).toBeCalledWith( - `The file '${relativeFilePath}' has both .js and .mjs variants, please use one or the other. Using .js by default.` - ) - expect(resolvedConfigFilePath).toBe(`${configFilePath}.js`) -}) - -it(`returns an empty string if no file exists`, () => { - const configFilePath = path.join(mockDir) - const resolvedConfigFilePath = resolveConfigFilePath(mockDir, configFilePath) - expect(resolvedConfigFilePath).toBe(``) -}) diff --git a/packages/gatsby/src/bootstrap/__tests__/resolve-js-file-path.ts b/packages/gatsby/src/bootstrap/__tests__/resolve-js-file-path.ts new file mode 100644 index 0000000000000..d22a6c4d75b79 --- /dev/null +++ b/packages/gatsby/src/bootstrap/__tests__/resolve-js-file-path.ts @@ -0,0 +1,82 @@ +import path from "path" +import reporter from "gatsby-cli/lib/reporter" +import { resolveJSFilepath } from "../resolve-js-file-path" + +const mockDir = path.resolve( + __dirname, + `..`, + `__mocks__`, + `resolve-js-file-path` +) + +jest.mock(`gatsby-cli/lib/reporter`, () => { + return { + warn: jest.fn(), + } +}) + +const reporterWarnMock = reporter.warn as jest.MockedFunction< + typeof reporter.warn +> + +beforeEach(() => { + reporterWarnMock.mockClear() +}) + +it(`resolves gatsby-config.js if it exists`, async () => { + const configFilePath = path.join(mockDir, `cjs`, `gatsby-config`) + const resolvedConfigFilePath = await resolveJSFilepath( + mockDir, + configFilePath + ) + expect(resolvedConfigFilePath).toBe(`${configFilePath}.js`) +}) + +it(`resolves gatsby-config.js the same way if a file path with extension is provided`, async () => { + const configFilePath = path.join(mockDir, `cjs`, `gatsby-config.js`) + const resolvedConfigFilePath = await resolveJSFilepath( + mockDir, + configFilePath + ) + expect(resolvedConfigFilePath).toBe(configFilePath) +}) + +it(`resolves gatsby-config.mjs if it exists`, async () => { + const configFilePath = path.join(mockDir, `esm`, `gatsby-config`) + const resolvedConfigFilePath = await resolveJSFilepath( + mockDir, + configFilePath + ) + expect(resolvedConfigFilePath).toBe(`${configFilePath}.mjs`) +}) + +it(`resolves gatsby-config.mjs the same way if a file path with extension is provided`, async () => { + const configFilePath = path.join(mockDir, `esm`, `gatsby-config.mjs`) + const resolvedConfigFilePath = await resolveJSFilepath( + mockDir, + configFilePath + ) + expect(resolvedConfigFilePath).toBe(configFilePath) +}) + +it(`warns if both variants exist and defaults to the gatsby-config.js variant`, async () => { + const configFilePath = path.join(mockDir, `both`, `gatsby-config`) + const relativeFilePath = path.relative(mockDir, configFilePath) + const resolvedConfigFilePath = await resolveJSFilepath( + mockDir, + configFilePath + ) + expect(reporterWarnMock).toBeCalledWith( + `The file '${relativeFilePath}' has both .js and .mjs variants, please use one or the other. Using .js by default.` + ) + expect(resolvedConfigFilePath).toBe(`${configFilePath}.js`) +}) + +it(`returns an empty string if no file exists`, async () => { + const configFilePath = path.join(mockDir) + const resolvedConfigFilePath = await resolveJSFilepath( + mockDir, + configFilePath + ) + expect(resolvedConfigFilePath).toBe(``) +}) diff --git a/packages/gatsby/src/bootstrap/__tests__/resolve-module-exports.ts b/packages/gatsby/src/bootstrap/__tests__/resolve-module-exports.ts index 76ec60128a52c..ba91e4dcbb819 100644 --- a/packages/gatsby/src/bootstrap/__tests__/resolve-module-exports.ts +++ b/packages/gatsby/src/bootstrap/__tests__/resolve-module-exports.ts @@ -12,8 +12,10 @@ jest.mock(`gatsby-cli/lib/reporter`, () => { }) import * as fs from "fs-extra" +import path from "path" import reporter from "gatsby-cli/lib/reporter" import { resolveModuleExports } from "../resolve-module-exports" + let resolver describe(`Resolve module exports`, () => { @@ -127,6 +129,8 @@ describe(`Resolve module exports`, () => { "/export/function": `export function foo() {}`, } + const mockDir = path.resolve(__dirname, `..`, `__mocks__`) + beforeEach(() => { resolver = jest.fn(arg => arg) // @ts-ignore @@ -252,36 +256,45 @@ describe(`Resolve module exports`, () => { expect(result).toEqual([`foo`]) }) - it(`Resolves exports when using require mode - simple case`, async () => { - jest.mock(`require/exports`) + it(`Resolves exports when using import mode - simple case`, async () => { + jest.mock(`import/exports`) - const result = await resolveModuleExports(`require/exports`, { - mode: `require`, - }) + const result = await resolveModuleExports( + path.join(mockDir, `import`, `exports`), + { + mode: `import`, + } + ) expect(result).toEqual([`foo`, `bar`]) }) - it(`Resolves exports when using require mode - unusual case`, async () => { - jest.mock(`require/unusual-exports`) + it(`Resolves exports when using import mode - unusual case`, async () => { + jest.mock(`import/unusual-exports`) - const result = await resolveModuleExports(`require/unusual-exports`, { - mode: `require`, - }) + const result = await resolveModuleExports( + path.join(mockDir, `import`, `unusual-exports`), + { + mode: `import`, + } + ) expect(result).toEqual([`foo`]) }) - it(`Resolves exports when using require mode - returns empty array when module doesn't exist`, async () => { - const result = await resolveModuleExports(`require/not-existing-module`, { - mode: `require`, - }) + it(`Resolves exports when using import mode - returns empty array when module doesn't exist`, async () => { + const result = await resolveModuleExports( + path.join(mockDir, `import`, `not-existing-module`), + { + mode: `import`, + } + ) expect(result).toEqual([]) }) - it(`Resolves exports when using require mode - panic on errors`, async () => { - jest.mock(`require/module-error`) + it(`Resolves exports when using import mode - panic on errors`, async () => { + jest.mock(`import/module-error`) - await resolveModuleExports(`require/module-error`, { - mode: `require`, + await resolveModuleExports(path.join(mockDir, `import`, `module-error`), { + mode: `import`, }) expect(reporter.panic).toBeCalled() diff --git a/packages/gatsby/src/bootstrap/get-config-file.ts b/packages/gatsby/src/bootstrap/get-config-file.ts index 81eba31675a00..4a326ff4b4a71 100644 --- a/packages/gatsby/src/bootstrap/get-config-file.ts +++ b/packages/gatsby/src/bootstrap/get-config-file.ts @@ -5,7 +5,8 @@ import path from "path" import { sync as existsSync } from "fs-exists-cached" import { COMPILED_CACHE_DIR } from "../utils/parcel/compile-gatsby-files" import { isNearMatch } from "../utils/is-near-match" -import { resolveConfigFilePath } from "./resolve-config-file-path" +import { resolveJSFilepath } from "./resolve-js-file-path" +import { preferDefault } from "./prefer-default" export async function getConfigFile( siteDirectory: string, @@ -37,7 +38,7 @@ async function attemptImport( configModule: unknown configFilePath: string }> { - const configFilePath = resolveConfigFilePath(siteDirectory, configPath) + const configFilePath = await resolveJSFilepath(siteDirectory, configPath) // The file does not exist, no sense trying to import it if (!configFilePath) { @@ -45,7 +46,7 @@ async function attemptImport( } const importedModule = await import(configFilePath) - const configModule = importedModule.default + const configModule = preferDefault(importedModule) return { configFilePath, configModule } } diff --git a/packages/gatsby/src/bootstrap/load-plugins/__tests__/load-plugins.ts b/packages/gatsby/src/bootstrap/load-plugins/__tests__/load-plugins.ts index 70ab856efcaaf..5c314388c73fe 100644 --- a/packages/gatsby/src/bootstrap/load-plugins/__tests__/load-plugins.ts +++ b/packages/gatsby/src/bootstrap/load-plugins/__tests__/load-plugins.ts @@ -25,6 +25,17 @@ jest.mock(`gatsby-cli/lib/reporter`, () => { } }) +// Previously babel transpiled src ts plugin files (e.g. gatsby-node files) on the fly, +// making them require-able/test-able without running compileGatsbyFiles prior (as would happen in a real scenario). +// After switching to import to support esm, point file path resolution to the real compiled JS files in dist instead. +jest.mock(`../../resolve-js-file-path`, () => { + return { + resolveJSFilepath: jest.fn( + (_, filePath) => `${filePath.replace(`src`, `dist`)}.js` + ), + } +}) + jest.mock(`resolve-from`) const mockProcessExit = jest.spyOn(process, `exit`).mockImplementation(() => {}) diff --git a/packages/gatsby/src/bootstrap/load-plugins/index.ts b/packages/gatsby/src/bootstrap/load-plugins/index.ts index 837a8ceba300b..36696adf4f747 100644 --- a/packages/gatsby/src/bootstrap/load-plugins/index.ts +++ b/packages/gatsby/src/bootstrap/load-plugins/index.ts @@ -43,6 +43,7 @@ export async function loadPlugins( let { flattenedPlugins, badExports } = await collatePluginAPIs({ currentAPIs, flattenedPlugins: pluginArray, + rootDir, }) // Show errors for any non-Gatsby APIs exported from plugins diff --git a/packages/gatsby/src/bootstrap/load-plugins/validate.ts b/packages/gatsby/src/bootstrap/load-plugins/validate.ts index d4e2b45b1bcc8..c884102c5dafb 100644 --- a/packages/gatsby/src/bootstrap/load-plugins/validate.ts +++ b/packages/gatsby/src/bootstrap/load-plugins/validate.ts @@ -406,9 +406,11 @@ export async function validateConfigPluginsOptions( export async function collatePluginAPIs({ currentAPIs, flattenedPlugins, + rootDir, }: { currentAPIs: ICurrentAPIs flattenedPlugins: Array> + rootDir: string }): Promise<{ flattenedPlugins: Array badExports: IEntryMap @@ -431,14 +433,19 @@ export async function collatePluginAPIs({ const pluginNodeExports = await resolveModuleExports( plugin.resolvedCompiledGatsbyNode ?? `${plugin.resolve}/gatsby-node`, { - mode: `require`, + mode: `import`, + rootDir, } ) const pluginBrowserExports = await resolveModuleExports( - `${plugin.resolve}/gatsby-browser` + `${plugin.resolve}/gatsby-browser`, + { + rootDir, + } ) const pluginSSRExports = await resolveModuleExports( - `${plugin.resolve}/gatsby-ssr` + `${plugin.resolve}/gatsby-ssr`, + { rootDir } ) if (pluginNodeExports.length > 0) { diff --git a/packages/gatsby/src/bootstrap/resolve-config-file-path.ts b/packages/gatsby/src/bootstrap/resolve-config-file-path.ts deleted file mode 100644 index a3c7e7e646f1a..0000000000000 --- a/packages/gatsby/src/bootstrap/resolve-config-file-path.ts +++ /dev/null @@ -1,37 +0,0 @@ -import path from "path" -import report from "gatsby-cli/lib/reporter" -import { sync as existsSync } from "fs-exists-cached" - -/** - * Figure out if the file path is .js or .mjs and return it if it exists. - */ -export function resolveConfigFilePath( - siteDirectory: string, - filePath: string -): string { - const filePathWithJSExtension = `${filePath}.js` - const filePathWithMJSExtension = `${filePath}.mjs` - - if ( - existsSync(filePathWithJSExtension) && - existsSync(filePathWithMJSExtension) - ) { - report.warn( - `The file '${path.relative( - siteDirectory, - filePath - )}' has both .js and .mjs variants, please use one or the other. Using .js by default.` - ) - return filePathWithJSExtension - } - - if (existsSync(filePathWithJSExtension)) { - return filePathWithJSExtension - } - - if (existsSync(filePathWithMJSExtension)) { - return filePathWithMJSExtension - } - - return `` -} diff --git a/packages/gatsby/src/bootstrap/resolve-js-file-path.ts b/packages/gatsby/src/bootstrap/resolve-js-file-path.ts new file mode 100644 index 0000000000000..98e34cbef2b9b --- /dev/null +++ b/packages/gatsby/src/bootstrap/resolve-js-file-path.ts @@ -0,0 +1,54 @@ +import path from "path" +import report from "gatsby-cli/lib/reporter" + +/** + * Figure out if the file path is .js or .mjs without relying on the fs module, and return the file path if it exists. + */ +export async function resolveJSFilepath( + siteDirectory: string, + filePath: string +): Promise { + const filePathWithJSExtension = filePath.endsWith(`.js`) + ? filePath + : `${filePath}.js` + const filePathWithMJSExtension = filePath.endsWith(`.mjs`) + ? filePath + : `${filePath}.mjs` + + // Check if both variants exist + try { + if ( + require.resolve(filePathWithJSExtension) && + require.resolve(filePathWithMJSExtension) + ) { + report.warn( + `The file '${path.relative( + siteDirectory, + filePath + )}' has both .js and .mjs variants, please use one or the other. Using .js by default.` + ) + return filePathWithJSExtension + } + } catch (_) { + // Do nothing + } + + // Check if .js variant exists + try { + if (require.resolve(filePathWithJSExtension)) { + return filePathWithJSExtension + } + } catch (_) { + // Do nothing + } + + try { + if (require.resolve(filePathWithMJSExtension)) { + return filePathWithMJSExtension + } + } catch (_) { + // Do nothing + } + + return `` +} diff --git a/packages/gatsby/src/bootstrap/resolve-module-exports.ts b/packages/gatsby/src/bootstrap/resolve-module-exports.ts index 9878dd86fcda5..e6e7ce01fb705 100644 --- a/packages/gatsby/src/bootstrap/resolve-module-exports.ts +++ b/packages/gatsby/src/bootstrap/resolve-module-exports.ts @@ -4,9 +4,10 @@ import traverse from "@babel/traverse" import { codeFrameColumns, SourceLocation } from "@babel/code-frame" import report from "gatsby-cli/lib/reporter" import { babelParseToAst } from "../utils/babel-parse-to-ast" -import { testRequireError } from "../utils/test-require-error" -import { resolveModule } from "../utils/module-resolver" -import { pathToFileURL } from "url" +import { testImportError } from "../utils/test-import-error" +import { resolveModule, ModuleResolver } from "../utils/module-resolver" +import { resolveJSFilepath } from "./resolve-js-file-path" +import { preferDefault } from "./prefer-default" const staticallyAnalyzeExports = ( modulePath: string, @@ -177,57 +178,58 @@ https://gatsby.dev/no-mixed-modules return exportNames } +interface IResolveModuleExportsOptions { + mode?: `analysis` | `import` + resolver?: ModuleResolver + rootDir?: string +} + /** - * Given a `require.resolve()` compatible path pointing to a JS module, - * return an array listing the names of the module's exports. + * Given a path to a module, return an array of the module's exports. * - * Returns [] for invalid paths and modules without exports. + * It can run in two modes: + * 1. `analysis` mode gets exports via static analysis by traversing the file's AST with babel + * 2. `import` mode gets exports by directly importing the module and accessing its properties * - * @param modulePath - * @param mode - * @param resolver + * At the time of writing, analysis mode is used for files that can be jsx (e.g. gatsby-browser, gatsby-ssr) + * and import mode is used for files that can be js or mjs. + * + * Returns [] for invalid paths and modules without exports. */ export async function resolveModuleExports( modulePath: string, - { mode = `analysis`, resolver = resolveModule } = {} + { + mode = `analysis`, + resolver = resolveModule, + rootDir = process.cwd(), + }: IResolveModuleExportsOptions = {} ): Promise> { - let failedWithoutRequireError = false + if (mode === `import`) { + try { + const moduleFilePath = await resolveJSFilepath(rootDir, modulePath) + + if (!moduleFilePath) { + return [] + } - if (mode === `require`) { - let absPath: string | undefined + const rawImportedModule = await import(moduleFilePath) - try { - absPath = require.resolve(modulePath) - return Object.keys(require(modulePath)).filter( + // If the module is cjs, the properties we care about are nested under a top-level `default` property + const importedModule = preferDefault(rawImportedModule) + + return Object.keys(importedModule).filter( exportName => exportName !== `__esModule` ) - } catch (e) { - if (!testRequireError(modulePath, e)) { + } catch (error) { + if (!testImportError(modulePath, error)) { // if module exists, but requiring it cause errors, // show the error to the user and terminate build - report.panic(`Error in "${absPath}":`, e) - } else { - failedWithoutRequireError = true + report.panic(`Error in "${modulePath}":`, error) } } } else { return staticallyAnalyzeExports(modulePath, resolver) } - // TODO: Use a single codepath with await import() - if (failedWithoutRequireError) { - try { - // Let's see if it's a gatsby-node.mjs file - const url = pathToFileURL(`${modulePath}.mjs`) - const importedModule = await import(url.href) - const importedModuleExports = Object.keys(importedModule).filter( - exportName => exportName !== `__esModule` - ) - return importedModuleExports - } catch (error) { - // TODO: Do something like testRequireError above to only panic on errors that are not import related - } - } - return [] } diff --git a/packages/gatsby/src/schema/graphql-engine/entry.ts b/packages/gatsby/src/schema/graphql-engine/entry.ts index e9a13f9b63a22..c7383fbf0d0b6 100644 --- a/packages/gatsby/src/schema/graphql-engine/entry.ts +++ b/packages/gatsby/src/schema/graphql-engine/entry.ts @@ -11,7 +11,7 @@ import { actions } from "../../redux/actions" import reporter from "gatsby-cli/lib/reporter" import { GraphQLRunner, IQueryOptions } from "../../query/graphql-runner" import { waitJobsByRequest } from "../../utils/wait-until-jobs-complete" -import { setGatsbyPluginCache } from "../../utils/require-gatsby-plugin" +import { setGatsbyPluginCache } from "../../utils/import-gatsby-plugin" import apiRunnerNode from "../../utils/api-runner-node" import type { IGatsbyPage, IGatsbyState } from "../../redux/types" import { findPageByPath } from "../../utils/find-page-by-path" diff --git a/packages/gatsby/src/schema/graphql-engine/print-plugins.ts b/packages/gatsby/src/schema/graphql-engine/print-plugins.ts index 21be7226943f2..2019e93184f6f 100644 --- a/packages/gatsby/src/schema/graphql-engine/print-plugins.ts +++ b/packages/gatsby/src/schema/graphql-engine/print-plugins.ts @@ -5,7 +5,7 @@ import * as _ from "lodash" import { slash } from "gatsby-core-utils" import { store } from "../../redux" import { IGatsbyState } from "../../redux/types" -import { requireGatsbyPlugin } from "../../utils/require-gatsby-plugin" +import { importGatsbyPlugin } from "../../utils/import-gatsby-plugin" export const schemaCustomizationAPIs = new Set([ `setFieldsOnGraphQLNodeType`, @@ -150,10 +150,7 @@ async function filterPluginsWithWorkers( for (const plugin of plugins) { try { - const pluginWithWorker = await requireGatsbyPlugin( - plugin, - `gatsby-worker` - ) + const pluginWithWorker = await importGatsbyPlugin(plugin, `gatsby-worker`) if (pluginWithWorker) { filteredPlugins.push(plugin) } diff --git a/packages/gatsby/src/utils/api-runner-node.js b/packages/gatsby/src/utils/api-runner-node.js index fe386d74a1d85..b55713dfa5f90 100644 --- a/packages/gatsby/src/utils/api-runner-node.js +++ b/packages/gatsby/src/utils/api-runner-node.js @@ -28,7 +28,7 @@ const { emitter, store } = require(`../redux`) const { getNodes, getNode, getNodesByType } = require(`../datastore`) const { getNodeAndSavePathDependency, loadNodeContent } = require(`./nodes`) const { getPublicPath } = require(`./get-public-path`) -const { requireGatsbyPlugin } = require(`./require-gatsby-plugin`) +const { importGatsbyPlugin } = require(`./import-gatsby-plugin`) const { getNonGatsbyCodeFrameFormatted } = require(`./stack-trace-utils`) const { trackBuildError, decorateEvent } = require(`gatsby-telemetry`) import errorParser from "./api-runner-error-parser" @@ -293,7 +293,7 @@ const getUninitializedCache = plugin => { const availableActionsCache = new Map() let publicPath const runAPI = async (plugin, api, args, activity) => { - const gatsbyNode = await requireGatsbyPlugin(plugin, `gatsby-node`) + const gatsbyNode = await importGatsbyPlugin(plugin, `gatsby-node`) if (gatsbyNode[api]) { const parentSpan = args && args.parentSpan @@ -616,8 +616,7 @@ function apiRunnerNode(api, args = {}, { pluginSource, activity } = {}) { return null } - // TODO: Probably refactor - return requireGatsbyPlugin(plugin, `gatsby-node`).then(gatsbyNode => { + return importGatsbyPlugin(plugin, `gatsby-node`).then(gatsbyNode => { const pluginName = plugin.name === `default-site-plugin` ? `gatsby-node.js` diff --git a/packages/gatsby/src/utils/import-gatsby-plugin.ts b/packages/gatsby/src/utils/import-gatsby-plugin.ts new file mode 100644 index 0000000000000..536defb07fae4 --- /dev/null +++ b/packages/gatsby/src/utils/import-gatsby-plugin.ts @@ -0,0 +1,50 @@ +import { resolveJSFilepath } from "../bootstrap/resolve-js-file-path" +import { preferDefault } from "../bootstrap/prefer-default" + +const pluginModuleCache = new Map() + +export function setGatsbyPluginCache( + plugin: { name: string; resolve: string }, + module: string, + moduleObject: any +): void { + const key = `${plugin.name}/${module}` + pluginModuleCache.set(key, moduleObject) +} + +export async function importGatsbyPlugin( + plugin: { + name: string + resolve: string + resolvedCompiledGatsbyNode?: string + }, + module: string +): Promise { + const key = `${plugin.name}/${module}` + + let pluginModule = pluginModuleCache.get(key) + + if (!pluginModule) { + let importPluginModulePath: string + + if (module === `gatsby-node` && plugin.resolvedCompiledGatsbyNode) { + importPluginModulePath = plugin.resolvedCompiledGatsbyNode + } else { + importPluginModulePath = `${plugin.resolve}/${module}` + } + + const pluginFilePath = await resolveJSFilepath( + process.cwd(), + importPluginModulePath + ) + + const rawPluginModule = await import(pluginFilePath) + + // If the module is cjs, the properties we care about are nested under a top-level `default` property + pluginModule = preferDefault(rawPluginModule) + + pluginModuleCache.set(key, pluginModule) + } + + return pluginModule +} diff --git a/packages/gatsby/src/utils/jobs/__tests__/fixtures/.gitignore b/packages/gatsby/src/utils/jobs/__tests__/fixtures/.gitignore new file mode 100644 index 0000000000000..736e8ae58ad87 --- /dev/null +++ b/packages/gatsby/src/utils/jobs/__tests__/fixtures/.gitignore @@ -0,0 +1 @@ +!node_modules \ No newline at end of file diff --git a/packages/gatsby/src/utils/jobs/__tests__/fixtures/gatsby-plugin-local/gatsby-worker.js b/packages/gatsby/src/utils/jobs/__tests__/fixtures/gatsby-plugin-local/gatsby-worker.js new file mode 100644 index 0000000000000..1869565e10eca --- /dev/null +++ b/packages/gatsby/src/utils/jobs/__tests__/fixtures/gatsby-plugin-local/gatsby-worker.js @@ -0,0 +1,4 @@ +module.exports = { + TEST_JOB: jest.fn(), + NEXT_JOB: jest.fn() +} \ No newline at end of file diff --git a/packages/gatsby/src/utils/jobs/__tests__/fixtures/node_modules/gatsby-plugin-test/gatsby-worker.js b/packages/gatsby/src/utils/jobs/__tests__/fixtures/node_modules/gatsby-plugin-test/gatsby-worker.js new file mode 100644 index 0000000000000..1869565e10eca --- /dev/null +++ b/packages/gatsby/src/utils/jobs/__tests__/fixtures/node_modules/gatsby-plugin-test/gatsby-worker.js @@ -0,0 +1,4 @@ +module.exports = { + TEST_JOB: jest.fn(), + NEXT_JOB: jest.fn() +} \ No newline at end of file diff --git a/packages/gatsby/src/utils/jobs/__tests__/manager.js b/packages/gatsby/src/utils/jobs/__tests__/manager.js index 7c50e99077948..93421ce55151a 100644 --- a/packages/gatsby/src/utils/jobs/__tests__/manager.js +++ b/packages/gatsby/src/utils/jobs/__tests__/manager.js @@ -1,7 +1,7 @@ const path = require(`path`) const _ = require(`lodash`) const { slash } = require(`gatsby-core-utils`) -const worker = require(`/node_modules/gatsby-plugin-test/gatsby-worker`) +const worker = require(`./fixtures/node_modules/gatsby-plugin-test/gatsby-worker`) const reporter = require(`gatsby-cli/lib/reporter`) const hasha = require(`hasha`) const fs = require(`fs-extra`) @@ -26,26 +26,6 @@ jest.mock(`gatsby-cli/lib/reporter`, () => { } }) -jest.mock( - `/node_modules/gatsby-plugin-test/gatsby-worker`, - () => { - return { - TEST_JOB: jest.fn(), - } - }, - { virtual: true } -) - -jest.mock( - `/gatsby-plugin-local/gatsby-worker`, - () => { - return { - TEST_JOB: jest.fn(), - } - }, - { virtual: true } -) - jest.mock(`gatsby-core-utils`, () => { const realCoreUtils = jest.requireActual(`gatsby-core-utils`) @@ -65,7 +45,12 @@ fs.ensureDir = jest.fn().mockResolvedValue(true) const plugin = { name: `gatsby-plugin-test`, version: `1.0.0`, - resolve: `/node_modules/gatsby-plugin-test`, + resolve: path.resolve( + __dirname, + `fixtures`, + `node_modules`, + `gatsby-plugin-test` + ), } const createMockJob = (overrides = {}) => { @@ -95,6 +80,7 @@ describe(`Jobs manager`, () => { beforeEach(() => { worker.TEST_JOB.mockReset() + worker.NEXT_JOB.mockReset() endActivity.mockClear() pDefer.mockClear() uuid.v4.mockClear() @@ -144,7 +130,12 @@ describe(`Jobs manager`, () => { plugin: { name: `gatsby-plugin-test`, version: `1.0.0`, - resolve: `/node_modules/gatsby-plugin-test`, + resolve: path.resolve( + __dirname, + `fixtures`, + `node_modules`, + `gatsby-plugin-test` + ), isLocal: false, }, }) @@ -181,7 +172,7 @@ describe(`Jobs manager`, () => { it(`should schedule a job`, async () => { const { enqueueJob } = jobManager worker.TEST_JOB.mockReturnValue({ output: `myresult` }) - worker.NEXT_JOB = jest.fn().mockReturnValue({ output: `another result` }) + worker.NEXT_JOB.mockReturnValue({ output: `another result` }) const mockedJob = createInternalMockJob() const job1 = enqueueJob(mockedJob) const job2 = enqueueJob( @@ -534,12 +525,18 @@ describe(`Jobs manager`, () => { it(`should run the worker locally when it's a local plugin`, async () => { jest.useRealTimers() - const worker = require(`/gatsby-plugin-local/gatsby-worker`) + const localPluginPath = path.resolve( + __dirname, + `fixtures`, + `gatsby-plugin-local` + ) + const localPluginWorkerPath = path.join(localPluginPath, `gatsby-worker`) + const worker = require(localPluginWorkerPath) const { enqueueJob, createInternalJob } = jobManager const jobArgs = createInternalJob(createMockJob(), { name: `gatsby-plugin-local`, version: `1.0.0`, - resolve: `/gatsby-plugin-local`, + resolve: localPluginPath, }) await expect(enqueueJob(jobArgs)).resolves.toBeUndefined() diff --git a/packages/gatsby/src/utils/jobs/manager.ts b/packages/gatsby/src/utils/jobs/manager.ts index 73ddb639bcc64..20ecb52773d89 100644 --- a/packages/gatsby/src/utils/jobs/manager.ts +++ b/packages/gatsby/src/utils/jobs/manager.ts @@ -16,7 +16,7 @@ import { IJobNotWhitelisted, WorkerError, } from "./types" -import { requireGatsbyPlugin } from "../require-gatsby-plugin" +import { importGatsbyPlugin } from "../import-gatsby-plugin" type IncomingMessages = IJobCompletedMessage | IJobFailed | IJobNotWhitelisted @@ -157,7 +157,7 @@ function runJob( ): Promise> { const { plugin } = job try { - return requireGatsbyPlugin(plugin, `gatsby-worker`).then(worker => { + return importGatsbyPlugin(plugin, `gatsby-worker`).then(worker => { if (!worker[job.name]) { throw new Error(`No worker function found for ${job.name}`) } diff --git a/packages/gatsby/src/utils/module-resolver.ts b/packages/gatsby/src/utils/module-resolver.ts index 84aae171261c8..1736f2ae5df4d 100644 --- a/packages/gatsby/src/utils/module-resolver.ts +++ b/packages/gatsby/src/utils/module-resolver.ts @@ -1,7 +1,7 @@ import * as fs from "fs" import enhancedResolve, { CachedInputFileSystem } from "enhanced-resolve" -type ModuleResolver = (modulePath: string) => string | false +export type ModuleResolver = (modulePath: string) => string | false type ResolveType = (context?: any, path?: any, request?: any) => string | false export const resolveModule: ModuleResolver = modulePath => { diff --git a/packages/gatsby/src/utils/require-gatsby-plugin.ts b/packages/gatsby/src/utils/require-gatsby-plugin.ts deleted file mode 100644 index 2c665520018dd..0000000000000 --- a/packages/gatsby/src/utils/require-gatsby-plugin.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { pathToFileURL } from "url" - -const pluginModuleCache = new Map() - -export function setGatsbyPluginCache( - plugin: { name: string; resolve: string }, - module: string, - moduleObject: any -): void { - const key = `${plugin.name}/${module}` - pluginModuleCache.set(key, moduleObject) -} - -export async function requireGatsbyPlugin( - plugin: { - name: string - resolve: string - resolvedCompiledGatsbyNode?: string - }, - module: string -): Promise { - const key = `${plugin.name}/${module}` - - let pluginModule = pluginModuleCache.get(key) - if (!pluginModule) { - let requirePluginModulePath: string - - if (module === `gatsby-node` && plugin.resolvedCompiledGatsbyNode) { - requirePluginModulePath = plugin.resolvedCompiledGatsbyNode - } else { - requirePluginModulePath = `${plugin.resolve}/${module}` - } - - // TODO: Use a single codepath with await import() - try { - pluginModule = require(requirePluginModulePath) - } catch (failedToRequireError) { - const url = pathToFileURL(`${requirePluginModulePath}.mjs`) - - try { - pluginModule = await import(url?.href) - } catch (failedToImportError) { - // TODO: Better error handling - throw new Error(`Failed to import plugin ${requirePluginModulePath}`, { - cause: failedToImportError, - }) - } - - if (!pluginModule) { - throw new Error(`Failed to require plugin ${requirePluginModulePath}`, { - cause: failedToRequireError, - }) - } - } - - pluginModuleCache.set(key, pluginModule) - } - return pluginModule -} From 420c8369b573ac77eb679c5e6e93dcd1c56c9ee7 Mon Sep 17 00:00:00 2001 From: tyhopp Date: Tue, 6 Dec 2022 16:20:01 +0800 Subject: [PATCH 31/35] Fix api-runner-node fixtures --- .../src/utils/__tests__/api-runner-node.js | 164 ++---------------- .../test-plugin-correct/gatsby-node.js | 15 ++ .../test-plugin-error-args/gatsby-node.js | 5 + .../gatsby-node.js | 3 + .../gatsby-node.js | 2 + .../gatsby-node.js | 14 ++ .../test-plugin-progress-throw/gatsby-node.js | 10 ++ .../test-plugin-progress/gatsby-node.js | 9 + .../test-plugin-spinner-throw/gatsby-node.js | 8 + .../test-plugin-spinner/gatsby-node.js | 5 + 10 files changed, 83 insertions(+), 152 deletions(-) create mode 100644 packages/gatsby/src/utils/__tests__/fixtures/api-runner-node/test-plugin-correct/gatsby-node.js create mode 100644 packages/gatsby/src/utils/__tests__/fixtures/api-runner-node/test-plugin-error-args/gatsby-node.js create mode 100644 packages/gatsby/src/utils/__tests__/fixtures/api-runner-node/test-plugin-on-preinit-fails/gatsby-node.js create mode 100644 packages/gatsby/src/utils/__tests__/fixtures/api-runner-node/test-plugin-on-preinit-works/gatsby-node.js create mode 100644 packages/gatsby/src/utils/__tests__/fixtures/api-runner-node/test-plugin-plugin-prefixes/gatsby-node.js create mode 100644 packages/gatsby/src/utils/__tests__/fixtures/api-runner-node/test-plugin-progress-throw/gatsby-node.js create mode 100644 packages/gatsby/src/utils/__tests__/fixtures/api-runner-node/test-plugin-progress/gatsby-node.js create mode 100644 packages/gatsby/src/utils/__tests__/fixtures/api-runner-node/test-plugin-spinner-throw/gatsby-node.js create mode 100644 packages/gatsby/src/utils/__tests__/fixtures/api-runner-node/test-plugin-spinner/gatsby-node.js diff --git a/packages/gatsby/src/utils/__tests__/api-runner-node.js b/packages/gatsby/src/utils/__tests__/api-runner-node.js index 7a2d4644f8345..69d3e505c4482 100644 --- a/packages/gatsby/src/utils/__tests__/api-runner-node.js +++ b/packages/gatsby/src/utils/__tests__/api-runner-node.js @@ -1,4 +1,5 @@ const apiRunnerNode = require(`../api-runner-node`) +const path = require(`path`) jest.mock(`../../redux`, () => { return { @@ -40,6 +41,8 @@ const { store, emitter } = require(`../../redux`) const reporter = require(`gatsby-cli/lib/reporter`) const { getCache } = require(`../get-cache`) +const fixtureDir = path.resolve(__dirname, `fixtures`, `api-runner-node`) + beforeEach(() => { store.getState.mockClear() emitter.on.mockClear() @@ -54,93 +57,6 @@ beforeEach(() => { describe(`api-runner-node`, () => { it(`Ends activities if plugin didn't end them`, async () => { - jest.doMock( - `test-plugin-correct/gatsby-node`, - () => { - return { - testAPIHook: ({ reporter }) => { - const spinnerActivity = reporter.activityTimer( - `control spinner activity` - ) - spinnerActivity.start() - // calling activity.end() to make sure api runner doesn't call it more than needed - spinnerActivity.end() - - const progressActivity = reporter.createProgress( - `control progress activity` - ) - progressActivity.start() - // calling activity.done() to make sure api runner doesn't call it more than needed - progressActivity.done() - }, - } - }, - { virtual: true } - ) - jest.doMock( - `test-plugin-spinner/gatsby-node`, - () => { - return { - testAPIHook: ({ reporter }) => { - const activity = reporter.activityTimer(`spinner activity`) - activity.start() - // not calling activity.end() - api runner should do end it - }, - } - }, - { virtual: true } - ) - jest.doMock( - `test-plugin-progress/gatsby-node`, - () => { - return { - testAPIHook: ({ reporter }) => { - const activity = reporter.createProgress( - `progress activity`, - 100, - 0 - ) - activity.start() - // not calling activity.end() or done() - api runner should do end it - }, - } - }, - { virtual: true } - ) - jest.doMock( - `test-plugin-spinner-throw/gatsby-node`, - () => { - return { - testAPIHook: ({ reporter }) => { - const activity = reporter.activityTimer( - `spinner activity with throwing` - ) - activity.start() - throw new Error(`error`) - // not calling activity.end() - api runner should do end it - }, - } - }, - { virtual: true } - ) - jest.doMock( - `test-plugin-progress-throw/gatsby-node`, - () => { - return { - testAPIHook: ({ reporter }) => { - const activity = reporter.createProgress( - `progress activity with throwing`, - 100, - 0 - ) - activity.start() - throw new Error(`error`) - // not calling activity.end() or done() - api runner should do end it - }, - } - }, - { virtual: true } - ) store.getState.mockImplementation(() => { return { program: {}, @@ -148,27 +64,27 @@ describe(`api-runner-node`, () => { flattenedPlugins: [ { name: `test-plugin-correct`, - resolve: `test-plugin-correct`, + resolve: path.join(fixtureDir, `test-plugin-correct`), nodeAPIs: [`testAPIHook`], }, { name: `test-plugin-spinner`, - resolve: `test-plugin-spinner`, + resolve: path.join(fixtureDir, `test-plugin-spinner`), nodeAPIs: [`testAPIHook`], }, { name: `test-plugin-progress`, - resolve: `test-plugin-progress`, + resolve: path.join(fixtureDir, `test-plugin-progress`), nodeAPIs: [`testAPIHook`], }, { name: `test-plugin-spinner-throw`, - resolve: `test-plugin-spinner-throw`, + resolve: path.join(fixtureDir, `test-plugin-spinner-throw`), nodeAPIs: [`testAPIHook`], }, { name: `test-plugin-progress-throw`, - resolve: `test-plugin-progress-throw`, + resolve: path.join(fixtureDir, `test-plugin-progress-throw`), nodeAPIs: [`testAPIHook`], }, ], @@ -183,27 +99,6 @@ describe(`api-runner-node`, () => { }) it(`Doesn't initialize cache in onPreInit API`, async () => { - jest.doMock( - `test-plugin-on-preinit-works/gatsby-node`, - () => { - return { - onPreInit: () => {}, - otherTestApi: () => {}, - } - }, - { virtual: true } - ) - jest.doMock( - `test-plugin-on-preinit-fails/gatsby-node`, - () => { - return { - onPreInit: async ({ cache }) => { - await cache.get(`foo`) - }, - } - }, - { virtual: true } - ) store.getState.mockImplementation(() => { return { program: {}, @@ -211,12 +106,12 @@ describe(`api-runner-node`, () => { flattenedPlugins: [ { name: `test-plugin-on-preinit-works`, - resolve: `test-plugin-on-preinit-works`, + resolve: path.join(fixtureDir, `test-plugin-on-preinit-works`), nodeAPIs: [`onPreInit`, `otherTestApi`], }, { name: `test-plugin-on-preinit-fails`, - resolve: `test-plugin-on-preinit-fails`, + resolve: path.join(fixtureDir, `test-plugin-on-preinit-fails`), nodeAPIs: [`onPreInit`], }, ], @@ -239,19 +134,6 @@ describe(`api-runner-node`, () => { }) it(`Correctly handle error args`, async () => { - jest.doMock( - `test-plugin-error-args/gatsby-node`, - () => { - return { - onPreInit: ({ reporter }) => { - reporter.panicOnBuild(`Konohagakure`) - reporter.panicOnBuild(new Error(`Rasengan`)) - reporter.panicOnBuild(`Jiraiya`, new Error(`Tsunade`)) - }, - } - }, - { virtual: true } - ) store.getState.mockImplementation(() => { return { program: {}, @@ -259,7 +141,7 @@ describe(`api-runner-node`, () => { flattenedPlugins: [ { name: `test-plugin-error-args`, - resolve: `test-plugin-error-args`, + resolve: path.join(fixtureDir, `test-plugin-error-args`), nodeAPIs: [`onPreInit`], }, ], @@ -289,28 +171,6 @@ describe(`api-runner-node`, () => { }) it(`Correctly uses setErrorMap with pluginName prefixes`, async () => { - jest.doMock( - `test-plugin-plugin-prefixes/gatsby-node`, - () => { - return { - onPreInit: ({ reporter }) => { - reporter.setErrorMap({ - 1337: { - text: context => `Error text is ${context.someProp}`, - level: `ERROR`, - docsUrl: `https://www.gatsbyjs.com/docs/gatsby-cli/#new`, - }, - }) - - reporter.panicOnBuild({ - id: `1337`, - context: { someProp: `Naruto` }, - }) - }, - } - }, - { virtual: true } - ) store.getState.mockImplementation(() => { return { program: {}, @@ -318,7 +178,7 @@ describe(`api-runner-node`, () => { flattenedPlugins: [ { name: `test-plugin-plugin-prefixes`, - resolve: `test-plugin-plugin-prefixes`, + resolve: path.join(fixtureDir, `test-plugin-plugin-prefixes`), nodeAPIs: [`onPreInit`], }, ], diff --git a/packages/gatsby/src/utils/__tests__/fixtures/api-runner-node/test-plugin-correct/gatsby-node.js b/packages/gatsby/src/utils/__tests__/fixtures/api-runner-node/test-plugin-correct/gatsby-node.js new file mode 100644 index 0000000000000..e3f482f7b892b --- /dev/null +++ b/packages/gatsby/src/utils/__tests__/fixtures/api-runner-node/test-plugin-correct/gatsby-node.js @@ -0,0 +1,15 @@ +exports.testAPIHook = ({ reporter }) => { + const spinnerActivity = reporter.activityTimer( + `control spinner activity` + ) + spinnerActivity.start() + // calling activity.end() to make sure api runner doesn't call it more than needed + spinnerActivity.end() + + const progressActivity = reporter.createProgress( + `control progress activity` + ) + progressActivity.start() + // calling activity.done() to make sure api runner doesn't call it more than needed + progressActivity.done() +} \ No newline at end of file diff --git a/packages/gatsby/src/utils/__tests__/fixtures/api-runner-node/test-plugin-error-args/gatsby-node.js b/packages/gatsby/src/utils/__tests__/fixtures/api-runner-node/test-plugin-error-args/gatsby-node.js new file mode 100644 index 0000000000000..d88555bcff28a --- /dev/null +++ b/packages/gatsby/src/utils/__tests__/fixtures/api-runner-node/test-plugin-error-args/gatsby-node.js @@ -0,0 +1,5 @@ +exports.onPreInit = ({ reporter }) => { + reporter.panicOnBuild(`Konohagakure`) + reporter.panicOnBuild(new Error(`Rasengan`)) + reporter.panicOnBuild(`Jiraiya`, new Error(`Tsunade`)) +} \ No newline at end of file diff --git a/packages/gatsby/src/utils/__tests__/fixtures/api-runner-node/test-plugin-on-preinit-fails/gatsby-node.js b/packages/gatsby/src/utils/__tests__/fixtures/api-runner-node/test-plugin-on-preinit-fails/gatsby-node.js new file mode 100644 index 0000000000000..fb355bf1625e5 --- /dev/null +++ b/packages/gatsby/src/utils/__tests__/fixtures/api-runner-node/test-plugin-on-preinit-fails/gatsby-node.js @@ -0,0 +1,3 @@ +exports.onPreInit = async ({ cache }) => { + await cache.get(`foo`) +} \ No newline at end of file diff --git a/packages/gatsby/src/utils/__tests__/fixtures/api-runner-node/test-plugin-on-preinit-works/gatsby-node.js b/packages/gatsby/src/utils/__tests__/fixtures/api-runner-node/test-plugin-on-preinit-works/gatsby-node.js new file mode 100644 index 0000000000000..a142133faa81e --- /dev/null +++ b/packages/gatsby/src/utils/__tests__/fixtures/api-runner-node/test-plugin-on-preinit-works/gatsby-node.js @@ -0,0 +1,2 @@ +exports.onPreInit = () => {} +exports.otherTestApi = () => {} \ No newline at end of file diff --git a/packages/gatsby/src/utils/__tests__/fixtures/api-runner-node/test-plugin-plugin-prefixes/gatsby-node.js b/packages/gatsby/src/utils/__tests__/fixtures/api-runner-node/test-plugin-plugin-prefixes/gatsby-node.js new file mode 100644 index 0000000000000..48f6bb0163aca --- /dev/null +++ b/packages/gatsby/src/utils/__tests__/fixtures/api-runner-node/test-plugin-plugin-prefixes/gatsby-node.js @@ -0,0 +1,14 @@ +exports.onPreInit = ({ reporter }) => { + reporter.setErrorMap({ + 1337: { + text: context => `Error text is ${context.someProp}`, + level: `ERROR`, + docsUrl: `https://www.gatsbyjs.com/docs/gatsby-cli/#new`, + }, + }) + + reporter.panicOnBuild({ + id: `1337`, + context: { someProp: `Naruto` }, + }) +} \ No newline at end of file diff --git a/packages/gatsby/src/utils/__tests__/fixtures/api-runner-node/test-plugin-progress-throw/gatsby-node.js b/packages/gatsby/src/utils/__tests__/fixtures/api-runner-node/test-plugin-progress-throw/gatsby-node.js new file mode 100644 index 0000000000000..76ec49e8519a5 --- /dev/null +++ b/packages/gatsby/src/utils/__tests__/fixtures/api-runner-node/test-plugin-progress-throw/gatsby-node.js @@ -0,0 +1,10 @@ +exports.testAPIHook = ({ reporter }) => { + const activity = reporter.createProgress( + `progress activity with throwing`, + 100, + 0 + ) + activity.start() + throw new Error(`error`) + // not calling activity.end() or done() - api runner should do end it +} \ No newline at end of file diff --git a/packages/gatsby/src/utils/__tests__/fixtures/api-runner-node/test-plugin-progress/gatsby-node.js b/packages/gatsby/src/utils/__tests__/fixtures/api-runner-node/test-plugin-progress/gatsby-node.js new file mode 100644 index 0000000000000..5b41db466a434 --- /dev/null +++ b/packages/gatsby/src/utils/__tests__/fixtures/api-runner-node/test-plugin-progress/gatsby-node.js @@ -0,0 +1,9 @@ +exports.testAPIHook = ({ reporter }) => { + const activity = reporter.createProgress( + `progress activity`, + 100, + 0 + ) + activity.start() + // not calling activity.end() or done() - api runner should do end it +} diff --git a/packages/gatsby/src/utils/__tests__/fixtures/api-runner-node/test-plugin-spinner-throw/gatsby-node.js b/packages/gatsby/src/utils/__tests__/fixtures/api-runner-node/test-plugin-spinner-throw/gatsby-node.js new file mode 100644 index 0000000000000..50839f361ba68 --- /dev/null +++ b/packages/gatsby/src/utils/__tests__/fixtures/api-runner-node/test-plugin-spinner-throw/gatsby-node.js @@ -0,0 +1,8 @@ +exports.testAPIHook = ({ reporter }) => { + const activity = reporter.activityTimer( + `spinner activity with throwing` + ) + activity.start() + throw new Error(`error`) + // not calling activity.end() - api runner should do end it +} \ No newline at end of file diff --git a/packages/gatsby/src/utils/__tests__/fixtures/api-runner-node/test-plugin-spinner/gatsby-node.js b/packages/gatsby/src/utils/__tests__/fixtures/api-runner-node/test-plugin-spinner/gatsby-node.js new file mode 100644 index 0000000000000..1206b97a5bede --- /dev/null +++ b/packages/gatsby/src/utils/__tests__/fixtures/api-runner-node/test-plugin-spinner/gatsby-node.js @@ -0,0 +1,5 @@ +exports.testAPIHook = ({ reporter }) => { + const activity = reporter.activityTimer(`spinner activity`) + activity.start() + // not calling activity.end() - api runner should do end it +} \ No newline at end of file From b9dbdac8aeabc129f6006b7890025adb383c5304 Mon Sep 17 00:00:00 2001 From: tyhopp Date: Tue, 6 Dec 2022 16:35:02 +0800 Subject: [PATCH 32/35] Fix jobs manager fixtures --- .../src/utils/jobs/__tests__/manager.js | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/packages/gatsby/src/utils/jobs/__tests__/manager.js b/packages/gatsby/src/utils/jobs/__tests__/manager.js index 93421ce55151a..b4d9ce34e7537 100644 --- a/packages/gatsby/src/utils/jobs/__tests__/manager.js +++ b/packages/gatsby/src/utils/jobs/__tests__/manager.js @@ -42,15 +42,14 @@ jest.mock(`hasha`, () => jest.requireActual(`hasha`)) fs.ensureDir = jest.fn().mockResolvedValue(true) +const nodeModulesPluginPath = slash( + path.resolve(__dirname, `fixtures`, `node_modules`, `gatsby-plugin-test`) +) + const plugin = { name: `gatsby-plugin-test`, version: `1.0.0`, - resolve: path.resolve( - __dirname, - `fixtures`, - `node_modules`, - `gatsby-plugin-test` - ), + resolve: nodeModulesPluginPath, } const createMockJob = (overrides = {}) => { @@ -130,12 +129,7 @@ describe(`Jobs manager`, () => { plugin: { name: `gatsby-plugin-test`, version: `1.0.0`, - resolve: path.resolve( - __dirname, - `fixtures`, - `node_modules`, - `gatsby-plugin-test` - ), + resolve: nodeModulesPluginPath, isLocal: false, }, }) @@ -525,10 +519,8 @@ describe(`Jobs manager`, () => { it(`should run the worker locally when it's a local plugin`, async () => { jest.useRealTimers() - const localPluginPath = path.resolve( - __dirname, - `fixtures`, - `gatsby-plugin-local` + const localPluginPath = slash( + path.resolve(__dirname, `fixtures`, `gatsby-plugin-local`) ) const localPluginWorkerPath = path.join(localPluginPath, `gatsby-worker`) const worker = require(localPluginWorkerPath) From d86c031b99e00d63b6e5b876ead46efcc71078b6 Mon Sep 17 00:00:00 2001 From: Ty Hopp Date: Thu, 8 Dec 2022 10:59:25 +0800 Subject: [PATCH 33/35] Remove testRequireError, refactor tests --- .../__tests__/fixtures/bad-module-import.js | 1 + .../__tests__/fixtures/bad-module-require.js | 1 - ...-require-error.ts => test-import-error.ts} | 69 ++++++++++--------- .../gatsby/src/utils/test-import-error.ts | 3 +- .../gatsby/src/utils/test-require-error.ts | 21 ------ 5 files changed, 41 insertions(+), 54 deletions(-) create mode 100644 packages/gatsby/src/utils/__tests__/fixtures/bad-module-import.js delete mode 100644 packages/gatsby/src/utils/__tests__/fixtures/bad-module-require.js rename packages/gatsby/src/utils/__tests__/{test-require-error.ts => test-import-error.ts} (55%) delete mode 100644 packages/gatsby/src/utils/test-require-error.ts diff --git a/packages/gatsby/src/utils/__tests__/fixtures/bad-module-import.js b/packages/gatsby/src/utils/__tests__/fixtures/bad-module-import.js new file mode 100644 index 0000000000000..06c1f288f970c --- /dev/null +++ b/packages/gatsby/src/utils/__tests__/fixtures/bad-module-import.js @@ -0,0 +1 @@ +await import(`cheese`) diff --git a/packages/gatsby/src/utils/__tests__/fixtures/bad-module-require.js b/packages/gatsby/src/utils/__tests__/fixtures/bad-module-require.js deleted file mode 100644 index 42322ab5537fc..0000000000000 --- a/packages/gatsby/src/utils/__tests__/fixtures/bad-module-require.js +++ /dev/null @@ -1 +0,0 @@ -require(`cheese`) diff --git a/packages/gatsby/src/utils/__tests__/test-require-error.ts b/packages/gatsby/src/utils/__tests__/test-import-error.ts similarity index 55% rename from packages/gatsby/src/utils/__tests__/test-require-error.ts rename to packages/gatsby/src/utils/__tests__/test-import-error.ts index 5414588916c73..77d47bd0d9a0d 100644 --- a/packages/gatsby/src/utils/__tests__/test-require-error.ts +++ b/packages/gatsby/src/utils/__tests__/test-import-error.ts @@ -1,79 +1,86 @@ -import { testRequireError } from "../test-require-error" +import path from "path" +import { testImportError } from "../test-import-error" -describe(`test-require-error`, () => { - it(`detects require errors`, () => { +describe(`test-import-error`, () => { + it(`detects import errors`, async () => { try { - require(`./fixtures/module-does-not-exist`) + // @ts-expect-error Module doesn't really exist + await import(`./fixtures/module-does-not-exist.js`) } catch (err) { - expect(testRequireError(`./fixtures/module-does-not-exist`, err)).toEqual( - true - ) + expect( + testImportError(`./fixtures/module-does-not-exist.js`, err) + ).toEqual(true) } }) - it(`detects require errors when using windows path`, () => { + + it(`detects import errors when using windows path`, async () => { try { - require(`.\\fixtures\\module-does-not-exist`) + // @ts-expect-error Module doesn't really exist + await import(`.\\fixtures\\module-does-not-exist.js`) } catch (err) { expect( - testRequireError(`.\\fixtures\\module-does-not-exist`, err) + testImportError(`.\\fixtures\\module-does-not-exist.js`, err) ).toEqual(true) } }) - it(`handles windows paths with double slashes`, () => { + + it(`handles windows paths with double slashes`, async () => { expect( - testRequireError( - `C:\\fixtures\\nothing`, - `Error: Cannot find module 'C:\\\\fixtures\\\\nothing'` + testImportError( + `C:\\fixtures\\nothing.js`, + `Error: Cannot find module 'C:\\\\fixtures\\\\nothing.js'` ) ).toEqual(true) }) - it(`Only returns true on not found errors for actual module not "not found" errors of requires inside the module`, () => { + + it(`Only returns true on not found errors for actual module not "not found" errors of imports inside the module`, async () => { try { - require(`./fixtures/bad-module-require`) + await import(path.resolve(`./fixtures/bad-module-import.js`)) } catch (err) { - expect(testRequireError(`./fixtures/bad-module-require`, err)).toEqual( + expect(testImportError(`./fixtures/bad-module-import.js`, err)).toEqual( false ) } }) - it(`ignores other errors`, () => { + + it(`ignores other errors`, async () => { try { - require(`./fixtures/bad-module-syntax`) + await import(path.resolve(`./fixtures/bad-module-syntax.js`)) } catch (err) { - expect(testRequireError(`./fixtures/bad-module-syntax`, err)).toEqual( + expect(testImportError(`./fixtures/bad-module-syntax.js`, err)).toEqual( false ) } }) describe(`handles error message thrown by Bazel`, () => { - it(`detects require errors`, () => { + it(`detects import errors`, () => { const bazelModuleNotFoundError = - new Error(`Error: //:build_bin cannot find module './fixtures/module-does-not-exist' required by '/private/var/tmp/_bazel_misiek/eba1803983a26276494495d851e478a5/execroot/com_github_bweston92_debug_gatsby_bazel_rules_nodejs/bazel-out/darwin-fastbuild/bin/build.runfiles/com_github_bweston92_debug_gatsby_bazel_rules_nodejs/node_modules/gatsby/dist/bootstrap/get-config-file.js' + new Error(`Error: //:build_bin cannot find module './fixtures/module-does-not-exist.js' required by '/private/var/tmp/_bazel_misiek/eba1803983a26276494495d851e478a5/execroot/com_github_bweston92_debug_gatsby_bazel_rules_nodejs/bazel-out/darwin-fastbuild/bin/build.runfiles/com_github_bweston92_debug_gatsby_bazel_rules_nodejs/node_modules/gatsby/dist/bootstrap/get-config-file.js' looked in: - built-in, relative, absolute, nested node_modules - Error: Cannot find module './fixtures/module-does-not-exist' - runfiles - Error: Cannot find module '/private/var/tmp/_bazel_misiek/eba1803983a26276494495d851e478a5/execroot/com_github_bweston92_debug_gatsby_bazel_rules_nodejs/bazel-out/darwin-fastbuild/bin/build.runfiles/fixtures/module-does-not-exist' - node_modules attribute (com_github_bweston92_debug_gatsby_bazel_rules_nodejs/node_modules) - Error: Cannot find module '/private/var/tmp/_bazel_misiek/eba1803983a26276494495d851e478a5/execroot/com_github_bweston92_debug_gatsby_bazel_rules_nodejs/bazel-out/darwin-fastbuild/bin/build.runfiles/com_github_bweston92_debug_gatsby_bazel_rules_nodejs/node_modules/fixtures/module-does-not-exist'`) + built-in, relative, absolute, nested node_modules - Error: Cannot find module './fixtures/module-does-not-exist.js' + runfiles - Error: Cannot find module '/private/var/tmp/_bazel_misiek/eba1803983a26276494495d851e478a5/execroot/com_github_bweston92_debug_gatsby_bazel_rules_nodejs/bazel-out/darwin-fastbuild/bin/build.runfiles/fixtures/module-does-not-exist.js' + node_modules attribute (com_github_bweston92_debug_gatsby_bazel_rules_nodejs/node_modules) - Error: Cannot find module '/private/var/tmp/_bazel_misiek/eba1803983a26276494495d851e478a5/execroot/com_github_bweston92_debug_gatsby_bazel_rules_nodejs/bazel-out/darwin-fastbuild/bin/build.runfiles/com_github_bweston92_debug_gatsby_bazel_rules_nodejs/node_modules/fixtures/module-does-not-exist.js'`) expect( - testRequireError( - `./fixtures/module-does-not-exist`, + testImportError( + `./fixtures/module-does-not-exist.js`, bazelModuleNotFoundError ) ).toEqual(true) }) - it(`detects require errors`, () => { + it(`detects import errors`, () => { const bazelModuleNotFoundError = - new Error(`Error: //:build_bin cannot find module 'cheese' required by '/private/var/tmp/_bazel_misiek/eba1803983a26276494495d851e478a5/execroot/com_github_bweston92_debug_gatsby_bazel_rules_nodejs/bazel-out/darwin-fastbuild/bin/build.runfiles/com_github_bweston92_debug_gatsby_bazel_rules_nodejs/node_modules/gatsby/dist/bootstrap/fixtures/bad-module-require.js' + new Error(`Error: //:build_bin cannot find module 'cheese' required by '/private/var/tmp/_bazel_misiek/eba1803983a26276494495d851e478a5/execroot/com_github_bweston92_debug_gatsby_bazel_rules_nodejs/bazel-out/darwin-fastbuild/bin/build.runfiles/com_github_bweston92_debug_gatsby_bazel_rules_nodejs/node_modules/gatsby/dist/bootstrap/fixtures/bad-module-import.js' looked in: built-in, relative, absolute, nested node_modules - Error: Cannot find module 'cheese' runfiles - Error: Cannot find module '/private/var/tmp/_bazel_misiek/eba1803983a26276494495d851e478a5/execroot/com_github_bweston92_debug_gatsby_bazel_rules_nodejs/bazel-out/darwin-fastbuild/bin/build.runfiles/cheese' node_modules attribute (com_github_bweston92_debug_gatsby_bazel_rules_nodejs/node_modules) - Error: Cannot find module '/private/var/tmp/_bazel_misiek/eba1803983a26276494495d851e478a5/execroot/com_github_bweston92_debug_gatsby_bazel_rules_nodejs/bazel-out/darwin-fastbuild/bin/build.runfiles/com_github_bweston92_debug_gatsby_bazel_rules_nodejs/node_modules/cheese'`) expect( - testRequireError( - `./fixtures/bad-module-require`, + testImportError( + `./fixtures/bad-module-import.js`, bazelModuleNotFoundError ) ).toEqual(false) diff --git a/packages/gatsby/src/utils/test-import-error.ts b/packages/gatsby/src/utils/test-import-error.ts index 7a015ddca177d..f1ef512d6d98f 100644 --- a/packages/gatsby/src/utils/test-import-error.ts +++ b/packages/gatsby/src/utils/test-import-error.ts @@ -8,7 +8,8 @@ export const testImportError = (moduleName: string, err: any): boolean => { return true } const regex = new RegExp( - `ModuleNotFoundError:\\s(\\S+\\s)?[Cc]annot find module\\s.${moduleName.replace( + // stderr will show ModuleNotFoundError, but Error is correct since we toString below + `Error:\\s(\\S+\\s)?[Cc]annot find module\\s.${moduleName.replace( /[-/\\^$*+?.()|[\]{}]/g, `\\$&` )}` diff --git a/packages/gatsby/src/utils/test-require-error.ts b/packages/gatsby/src/utils/test-require-error.ts deleted file mode 100644 index a5bc52b3d214d..0000000000000 --- a/packages/gatsby/src/utils/test-require-error.ts +++ /dev/null @@ -1,21 +0,0 @@ -// This module is also copied into the .cache directory some modules copied there -// from cache-dir can also use this module. -export const testRequireError = (moduleName: string, err: any): boolean => { - // PnP will return the following code when a require is allowed per the - // dependency tree rules but the requested file doesn't exist - if ( - err.code === `QUALIFIED_PATH_RESOLUTION_FAILED` || - err.pnpCode === `QUALIFIED_PATH_RESOLUTION_FAILED` - ) { - return true - } - const regex = new RegExp( - `Error:\\s(\\S+\\s)?[Cc]annot find module\\s.${moduleName.replace( - /[-/\\^$*+?.()|[\]{}]/g, - `\\$&` - )}` - ) - - const [firstLine] = err.toString().split(`\n`) - return regex.test(firstLine.replace(/\\\\/g, `\\`)) -} From ac899b30d0d6fe8ce7d8d4a7387f67c422d195ad Mon Sep 17 00:00:00 2001 From: Ty Hopp Date: Thu, 8 Dec 2022 11:16:48 +0800 Subject: [PATCH 34/35] DRY get-config-file error case in src dir --- .../__tests__/resolve-js-file-path.ts | 61 +++++++++++-------- .../gatsby/src/bootstrap/get-config-file.ts | 21 ++++--- .../load-plugins/__tests__/load-plugins.ts | 2 +- .../src/bootstrap/resolve-js-file-path.ts | 25 +++++--- .../src/bootstrap/resolve-module-exports.ts | 5 +- .../gatsby/src/utils/import-gatsby-plugin.ts | 8 +-- 6 files changed, 72 insertions(+), 50 deletions(-) diff --git a/packages/gatsby/src/bootstrap/__tests__/resolve-js-file-path.ts b/packages/gatsby/src/bootstrap/__tests__/resolve-js-file-path.ts index d22a6c4d75b79..d494a7347279e 100644 --- a/packages/gatsby/src/bootstrap/__tests__/resolve-js-file-path.ts +++ b/packages/gatsby/src/bootstrap/__tests__/resolve-js-file-path.ts @@ -25,58 +25,69 @@ beforeEach(() => { it(`resolves gatsby-config.js if it exists`, async () => { const configFilePath = path.join(mockDir, `cjs`, `gatsby-config`) - const resolvedConfigFilePath = await resolveJSFilepath( - mockDir, - configFilePath - ) + const resolvedConfigFilePath = await resolveJSFilepath({ + rootDir: mockDir, + filePath: configFilePath, + }) expect(resolvedConfigFilePath).toBe(`${configFilePath}.js`) }) it(`resolves gatsby-config.js the same way if a file path with extension is provided`, async () => { const configFilePath = path.join(mockDir, `cjs`, `gatsby-config.js`) - const resolvedConfigFilePath = await resolveJSFilepath( - mockDir, - configFilePath - ) + const resolvedConfigFilePath = await resolveJSFilepath({ + rootDir: mockDir, + filePath: configFilePath, + }) expect(resolvedConfigFilePath).toBe(configFilePath) }) it(`resolves gatsby-config.mjs if it exists`, async () => { const configFilePath = path.join(mockDir, `esm`, `gatsby-config`) - const resolvedConfigFilePath = await resolveJSFilepath( - mockDir, - configFilePath - ) + const resolvedConfigFilePath = await resolveJSFilepath({ + rootDir: mockDir, + filePath: configFilePath, + }) expect(resolvedConfigFilePath).toBe(`${configFilePath}.mjs`) }) it(`resolves gatsby-config.mjs the same way if a file path with extension is provided`, async () => { const configFilePath = path.join(mockDir, `esm`, `gatsby-config.mjs`) - const resolvedConfigFilePath = await resolveJSFilepath( - mockDir, - configFilePath - ) + const resolvedConfigFilePath = await resolveJSFilepath({ + rootDir: mockDir, + filePath: configFilePath, + }) expect(resolvedConfigFilePath).toBe(configFilePath) }) -it(`warns if both variants exist and defaults to the gatsby-config.js variant`, async () => { +it(`warns by default if both variants exist and defaults to the gatsby-config.js variant`, async () => { const configFilePath = path.join(mockDir, `both`, `gatsby-config`) const relativeFilePath = path.relative(mockDir, configFilePath) - const resolvedConfigFilePath = await resolveJSFilepath( - mockDir, - configFilePath - ) + const resolvedConfigFilePath = await resolveJSFilepath({ + rootDir: mockDir, + filePath: configFilePath, + }) expect(reporterWarnMock).toBeCalledWith( `The file '${relativeFilePath}' has both .js and .mjs variants, please use one or the other. Using .js by default.` ) expect(resolvedConfigFilePath).toBe(`${configFilePath}.js`) }) +it(`does NOT warn if both variants exist and warnings are disabled`, async () => { + const configFilePath = path.join(mockDir, `both`, `gatsby-config`) + const resolvedConfigFilePath = await resolveJSFilepath({ + rootDir: mockDir, + filePath: configFilePath, + warn: false, + }) + expect(reporterWarnMock).not.toBeCalled() + expect(resolvedConfigFilePath).toBe(`${configFilePath}.js`) +}) + it(`returns an empty string if no file exists`, async () => { const configFilePath = path.join(mockDir) - const resolvedConfigFilePath = await resolveJSFilepath( - mockDir, - configFilePath - ) + const resolvedConfigFilePath = await resolveJSFilepath({ + rootDir: mockDir, + filePath: configFilePath, + }) expect(resolvedConfigFilePath).toBe(``) }) diff --git a/packages/gatsby/src/bootstrap/get-config-file.ts b/packages/gatsby/src/bootstrap/get-config-file.ts index 4a326ff4b4a71..c2dd3651608e4 100644 --- a/packages/gatsby/src/bootstrap/get-config-file.ts +++ b/packages/gatsby/src/bootstrap/get-config-file.ts @@ -2,7 +2,6 @@ import fs from "fs-extra" import { testImportError } from "../utils/test-import-error" import report from "gatsby-cli/lib/reporter" import path from "path" -import { sync as existsSync } from "fs-exists-cached" import { COMPILED_CACHE_DIR } from "../utils/parcel/compile-gatsby-files" import { isNearMatch } from "../utils/is-near-match" import { resolveJSFilepath } from "./resolve-js-file-path" @@ -38,7 +37,10 @@ async function attemptImport( configModule: unknown configFilePath: string }> { - const configFilePath = await resolveJSFilepath(siteDirectory, configPath) + const configFilePath = await resolveJSFilepath({ + rootDir: siteDirectory, + filePath: configPath, + }) // The file does not exist, no sense trying to import it if (!configFilePath) { @@ -144,15 +146,14 @@ async function attemptImportUncompiled( }) } - // gatsby-config.js/gatsby-config.mjs is incorrectly located in src/gatsby-config.js - const srcDirectoryHasJsConfig = existsSync( - path.join(siteDirectory, `src`, configName + `.js`) - ) - const srcDirectoryHasMJsConfig = existsSync( - path.join(siteDirectory, `src`, configName + `.mjs`) - ) + // gatsby-config is incorrectly located in src directory + const isInSrcDir = await resolveJSFilepath({ + rootDir: siteDirectory, + filePath: path.join(siteDirectory, `src`, configName), + warn: false, + }) - if (srcDirectoryHasJsConfig || srcDirectoryHasMJsConfig) { + if (isInSrcDir) { report.panic({ id: `10125`, context: { diff --git a/packages/gatsby/src/bootstrap/load-plugins/__tests__/load-plugins.ts b/packages/gatsby/src/bootstrap/load-plugins/__tests__/load-plugins.ts index 5c314388c73fe..817fbc2c262e7 100644 --- a/packages/gatsby/src/bootstrap/load-plugins/__tests__/load-plugins.ts +++ b/packages/gatsby/src/bootstrap/load-plugins/__tests__/load-plugins.ts @@ -31,7 +31,7 @@ jest.mock(`gatsby-cli/lib/reporter`, () => { jest.mock(`../../resolve-js-file-path`, () => { return { resolveJSFilepath: jest.fn( - (_, filePath) => `${filePath.replace(`src`, `dist`)}.js` + ({ filePath }) => `${filePath.replace(`src`, `dist`)}.js` ), } }) diff --git a/packages/gatsby/src/bootstrap/resolve-js-file-path.ts b/packages/gatsby/src/bootstrap/resolve-js-file-path.ts index 98e34cbef2b9b..b4ad51bb88b40 100644 --- a/packages/gatsby/src/bootstrap/resolve-js-file-path.ts +++ b/packages/gatsby/src/bootstrap/resolve-js-file-path.ts @@ -4,10 +4,15 @@ import report from "gatsby-cli/lib/reporter" /** * Figure out if the file path is .js or .mjs without relying on the fs module, and return the file path if it exists. */ -export async function resolveJSFilepath( - siteDirectory: string, +export async function resolveJSFilepath({ + rootDir, + filePath, + warn = true, +}: { + rootDir: string filePath: string -): Promise { + warn?: boolean +}): Promise { const filePathWithJSExtension = filePath.endsWith(`.js`) ? filePath : `${filePath}.js` @@ -21,12 +26,14 @@ export async function resolveJSFilepath( require.resolve(filePathWithJSExtension) && require.resolve(filePathWithMJSExtension) ) { - report.warn( - `The file '${path.relative( - siteDirectory, - filePath - )}' has both .js and .mjs variants, please use one or the other. Using .js by default.` - ) + if (warn) { + report.warn( + `The file '${path.relative( + rootDir, + filePath + )}' has both .js and .mjs variants, please use one or the other. Using .js by default.` + ) + } return filePathWithJSExtension } } catch (_) { diff --git a/packages/gatsby/src/bootstrap/resolve-module-exports.ts b/packages/gatsby/src/bootstrap/resolve-module-exports.ts index e6e7ce01fb705..d530a096abfe0 100644 --- a/packages/gatsby/src/bootstrap/resolve-module-exports.ts +++ b/packages/gatsby/src/bootstrap/resolve-module-exports.ts @@ -206,7 +206,10 @@ export async function resolveModuleExports( ): Promise> { if (mode === `import`) { try { - const moduleFilePath = await resolveJSFilepath(rootDir, modulePath) + const moduleFilePath = await resolveJSFilepath({ + rootDir, + filePath: modulePath, + }) if (!moduleFilePath) { return [] diff --git a/packages/gatsby/src/utils/import-gatsby-plugin.ts b/packages/gatsby/src/utils/import-gatsby-plugin.ts index 536defb07fae4..f902c262dfa97 100644 --- a/packages/gatsby/src/utils/import-gatsby-plugin.ts +++ b/packages/gatsby/src/utils/import-gatsby-plugin.ts @@ -33,10 +33,10 @@ export async function importGatsbyPlugin( importPluginModulePath = `${plugin.resolve}/${module}` } - const pluginFilePath = await resolveJSFilepath( - process.cwd(), - importPluginModulePath - ) + const pluginFilePath = await resolveJSFilepath({ + rootDir: process.cwd(), + filePath: importPluginModulePath, + }) const rawPluginModule = await import(pluginFilePath) From f9f6c04529498716806beff5a028eb5a597108a7 Mon Sep 17 00:00:00 2001 From: Ty Hopp Date: Thu, 8 Dec 2022 12:03:10 +0800 Subject: [PATCH 35/35] Do not copy unused test-require-error file during init --- packages/gatsby/cache-dir/develop-static-entry.js | 4 ---- packages/gatsby/cache-dir/ssr-develop-static-entry.js | 4 ---- packages/gatsby/cache-dir/static-entry.js | 4 ---- packages/gatsby/src/services/initialize.ts | 5 +++-- 4 files changed, 3 insertions(+), 14 deletions(-) diff --git a/packages/gatsby/cache-dir/develop-static-entry.js b/packages/gatsby/cache-dir/develop-static-entry.js index 78c04d8e11b4d..77a1cd9357a1f 100644 --- a/packages/gatsby/cache-dir/develop-static-entry.js +++ b/packages/gatsby/cache-dir/develop-static-entry.js @@ -5,10 +5,6 @@ import { merge } from "lodash" import { apiRunner } from "./api-runner-ssr" import asyncRequires from "$virtual/async-requires" -// import testRequireError from "./test-require-error" -// For some extremely mysterious reason, webpack adds the above module *after* -// this module so that when this code runs, testRequireError is undefined. -// So in the meantime, we'll just inline it. const testRequireError = (moduleName, err) => { const regex = new RegExp(`Error: Cannot find module\\s.${moduleName}`) const firstLine = err.toString().split(`\n`)[0] diff --git a/packages/gatsby/cache-dir/ssr-develop-static-entry.js b/packages/gatsby/cache-dir/ssr-develop-static-entry.js index fc3988fde5bee..3b472acd3c700 100644 --- a/packages/gatsby/cache-dir/ssr-develop-static-entry.js +++ b/packages/gatsby/cache-dir/ssr-develop-static-entry.js @@ -16,10 +16,6 @@ import { getStaticQueryResults } from "./loader" // prefer default export if available const preferDefault = m => (m && m.default) || m -// import testRequireError from "./test-require-error" -// For some extremely mysterious reason, webpack adds the above module *after* -// this module so that when this code runs, testRequireError is undefined. -// So in the meantime, we'll just inline it. const testRequireError = (moduleName, err) => { const regex = new RegExp(`Error: Cannot find module\\s.${moduleName}`) const firstLine = err.toString().split(`\n`)[0] diff --git a/packages/gatsby/cache-dir/static-entry.js b/packages/gatsby/cache-dir/static-entry.js index 475170c202652..f6674f978bdcb 100644 --- a/packages/gatsby/cache-dir/static-entry.js +++ b/packages/gatsby/cache-dir/static-entry.js @@ -28,10 +28,6 @@ const { ServerSliceRenderer } = require(`./slice/server-slice-renderer`) // we want to force posix-style joins, so Windows doesn't produce backslashes for urls const { join } = path.posix -// const testRequireError = require("./test-require-error") -// For some extremely mysterious reason, webpack adds the above module *after* -// this module so that when this code runs, testRequireError is undefined. -// So in the meantime, we'll just inline it. const testRequireError = (moduleName, err) => { const regex = new RegExp(`Error: Cannot find module\\s.${moduleName}`) const firstLine = err.toString().split(`\n`)[0] diff --git a/packages/gatsby/src/services/initialize.ts b/packages/gatsby/src/services/initialize.ts index f360a472ddbf7..2667dbad71cce 100644 --- a/packages/gatsby/src/services/initialize.ts +++ b/packages/gatsby/src/services/initialize.ts @@ -493,15 +493,16 @@ export async function initialize({ activity = reporter.activityTimer(`copy gatsby files`, { parentSpan, }) + activity.start() + const srcDir = `${__dirname}/../../cache-dir` const siteDir = cacheDirectory - const tryRequire = `${__dirname}/../utils/test-require-error.js` + try { await fs.copy(srcDir, siteDir, { overwrite: true, }) - await fs.copy(tryRequire, `${siteDir}/test-require-error.js`) await fs.ensureDir(`${cacheDirectory}/${lmdbCacheDirectoryName}`) // Ensure .cache/fragments exists and is empty. We want fragments to be