From 8500557c0262f1202bce132dca0ecfa17c102fdf Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Thu, 6 May 2021 16:20:44 +0200 Subject: [PATCH 01/13] save MDXContent to different file --- packages/gatsby-plugin-mdx/constants.js | 2 ++ packages/gatsby-plugin-mdx/utils/gen-mdx.js | 30 +++++++++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/packages/gatsby-plugin-mdx/constants.js b/packages/gatsby-plugin-mdx/constants.js index bb3f006a6aff8..9a1971f6502a6 100644 --- a/packages/gatsby-plugin-mdx/constants.js +++ b/packages/gatsby-plugin-mdx/constants.js @@ -1,7 +1,9 @@ const MDX_WRAPPERS_LOCATION = `mdx-wrappers-dir` const MDX_SCOPES_LOCATION = `mdx-scopes-dir` +const MDX_LOADER_PASSTHROUGH_LOCATION = `mdx-loader-passthrough-dir` module.exports = { MDX_WRAPPERS_LOCATION, MDX_SCOPES_LOCATION, + MDX_LOADER_PASSTHROUGH_LOCATION, } diff --git a/packages/gatsby-plugin-mdx/utils/gen-mdx.js b/packages/gatsby-plugin-mdx/utils/gen-mdx.js index a991745f032a1..3dcd3a3f24054 100644 --- a/packages/gatsby-plugin-mdx/utils/gen-mdx.js +++ b/packages/gatsby-plugin-mdx/utils/gen-mdx.js @@ -2,6 +2,8 @@ const babel = require(`@babel/core`) const grayMatter = require(`gray-matter`) const mdx = require(`@mdx-js/mdx`) const objRestSpread = require(`@babel/plugin-proposal-object-rest-spread`) +const path = require(`path`) +const fs = require(`fs-extra`) const debug = require(`debug`)(`gatsby-plugin-mdx:gen-mdx`) @@ -10,6 +12,7 @@ const htmlAttrToJSXAttr = require(`./babel-plugin-html-attr-to-jsx-attr`) const removeExportKeywords = require(`./babel-plugin-remove-export-keywords`) const BabelPluginPluckImports = require(`./babel-plugin-pluck-imports`) const { parseImportBindings } = require(`./import-parser`) +const { MDX_LOADER_PASSTHROUGH_LOCATION } = require(`../constants`) /* * function mutateNode({ @@ -89,7 +92,9 @@ async function genMDX( // pull classic style frontmatter off the raw MDX body debug(`processing classic frontmatter`) const { data, content: frontMatterCodeResult } = grayMatter(node.rawBody) - const content = `${frontMatterCodeResult} + const content = isLoader + ? frontMatterCodeResult + : `${frontMatterCodeResult} export const _frontmatter = ${JSON.stringify(data)}` @@ -136,11 +141,13 @@ export const _frontmatter = ${JSON.stringify(data)}` ), }) - results.rawMDXOutput = `/* @jsx mdx */ + const rawMDXOutput = `/* @jsx mdx */ import { mdx } from '@mdx-js/react'; ${code}` if (!isLoader) { + results.rawMDXOutput = rawMDXOutput + debug(`compiling scope`) const instance = new BabelPluginPluckImports() const result = babel.transform(code, { @@ -183,6 +190,25 @@ ${code}` /export\s*{\s*MDXContent\s+as\s+default\s*};?/, `return MDXContent;` ) + } else { + // code path for webpack loader + // actual react component is saved to different file so that _frontmatter export doesn't + // disable react-refresh (multiple exports are not handled) + const filePath = path.join( + cache.directory, + MDX_LOADER_PASSTHROUGH_LOCATION, + `${helpers.createContentDigest(node.fileAbsolutePath)}.js` + ) + + await fs.outputFile(filePath, rawMDXOutput) + + results.rawMDXOutput = ` + import MDXContent from "${filePath}"; + + export default MDXContent; + + export const _frontmatter = ${JSON.stringify(data)}; + ` } /* results.html = renderToStaticMarkup( * React.createElement(MDXRenderer, null, results.body) From 1cc119e54b3a6f7eec275d153f9a84f65c556636 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Fri, 7 May 2021 11:29:49 +0200 Subject: [PATCH 02/13] tmp: skip mdx loader unit tests --- packages/gatsby-plugin-mdx/loaders/mdx-loader.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/gatsby-plugin-mdx/loaders/mdx-loader.test.js b/packages/gatsby-plugin-mdx/loaders/mdx-loader.test.js index 9fcfda0f782d3..50c5ad44f438f 100644 --- a/packages/gatsby-plugin-mdx/loaders/mdx-loader.test.js +++ b/packages/gatsby-plugin-mdx/loaders/mdx-loader.test.js @@ -53,7 +53,8 @@ const fixtures = new BaseN([true, false], 3) content, ]) -describe(`mdx-loader`, () => { +// temporarily skip those until final solution is done +describe.skip(`mdx-loader`, () => { expect.addSnapshotSerializer({ print(val /* , serialize */) { return prettier.format(val, { parser: `babel` }) From 6cdafeeb27acc4b8d1f3356068bcbf640b575739 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Fri, 7 May 2021 12:40:47 +0200 Subject: [PATCH 03/13] test(e2e-mdx): upgrade cypress, setup running dev --- e2e-tests/mdx/cypress-dev.json | 3 +++ e2e-tests/mdx/package.json | 19 ++++++++++++------- 2 files changed, 15 insertions(+), 7 deletions(-) create mode 100644 e2e-tests/mdx/cypress-dev.json diff --git a/e2e-tests/mdx/cypress-dev.json b/e2e-tests/mdx/cypress-dev.json new file mode 100644 index 0000000000000..b471a6a521eda --- /dev/null +++ b/e2e-tests/mdx/cypress-dev.json @@ -0,0 +1,3 @@ +{ + "baseUrl": "http://localhost:8000" +} diff --git a/e2e-tests/mdx/package.json b/e2e-tests/mdx/package.json index fde0c84ad8827..0d16dbd45ca46 100644 --- a/e2e-tests/mdx/package.json +++ b/e2e-tests/mdx/package.json @@ -5,7 +5,7 @@ "dependencies": { "@mdx-js/mdx": "^1.6.6", "@mdx-js/react": "^1.6.6", - "cypress": "^3.1.0", + "cypress": "^7.2.0", "fs-extra": "^8.1.0", "gatsby": "^3.0.0", "gatsby-plugin-mdx": "^2.0.0", @@ -19,14 +19,19 @@ ], "license": "MIT", "scripts": { - "build": "gatsby build", - "develop": "gatsby develop", + "build": "cross-env CYPRESS_SUPPORT=y gatsby build", + "develop": "cross-env CYPRESS_SUPPORT=y gatsby develop", "format": "prettier --write '**/*.js'", - "test": "cross-env CYPRESS_SUPPORT=y npm run build && npm run start-server-and-test", - "start-server-and-test": "start-server-and-test serve http://localhost:9000 cy:run", + "test:build": "cross-env CYPRESS_SUPPORT=y npm run build && npm run start-server-and-test:build", + "test:develop": "npm run start-server-and-test:develop || (npm run reset && exit 1)", + "test": "npm run test:build && npm run test:develop", + "start-server-and-test:develop": "start-server-and-test develop http://localhost:8000 cy:run:develop", + "start-server-and-test:build": "start-server-and-test serve http://localhost:9000 cy:run:build", "serve": "gatsby serve", - "cy:open": "cypress open", - "cy:run": "node ../../scripts/cypress-run-with-conditional-record-flag.js --browser chrome" + "cy:open:develop": "cypress open --config-file cypress-dev.json", + "cy:open:build": "cypress open", + "cy:run:build": "node ../../scripts/cypress-run-with-conditional-record-flag.js --browser chrome --group production", + "cy:run:develop": "node ../../scripts/cypress-run-with-conditional-record-flag.js --browser chrome --config-file cypress-dev.json --group development" }, "devDependencies": { "cross-env": "^5.2.0", From c8f0f1aa8045492bbc84f1cd348fbc9a5fffe2f1 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Fri, 7 May 2021 12:53:00 +0200 Subject: [PATCH 04/13] test(e2e-mdx): add hmr test case --- e2e-tests/mdx/cypress-dev.json | 5 +- e2e-tests/mdx/cypress.json | 5 +- e2e-tests/mdx/cypress/integration/hmr.js | 20 +++++ e2e-tests/mdx/package.json | 4 +- e2e-tests/mdx/scripts/history.js | 25 ++++++ e2e-tests/mdx/scripts/reset.js | 21 +++++ e2e-tests/mdx/scripts/update.js | 103 +++++++++++++++++++++++ e2e-tests/mdx/src/pages/hmr.mdx | 1 + 8 files changed, 181 insertions(+), 3 deletions(-) create mode 100644 e2e-tests/mdx/cypress/integration/hmr.js create mode 100644 e2e-tests/mdx/scripts/history.js create mode 100644 e2e-tests/mdx/scripts/reset.js create mode 100644 e2e-tests/mdx/scripts/update.js create mode 100644 e2e-tests/mdx/src/pages/hmr.mdx diff --git a/e2e-tests/mdx/cypress-dev.json b/e2e-tests/mdx/cypress-dev.json index b471a6a521eda..89024688f2bea 100644 --- a/e2e-tests/mdx/cypress-dev.json +++ b/e2e-tests/mdx/cypress-dev.json @@ -1,3 +1,6 @@ { - "baseUrl": "http://localhost:8000" + "baseUrl": "http://localhost:8000", + "env": { + "GATSBY_COMMAND": "develop" + } } diff --git a/e2e-tests/mdx/cypress.json b/e2e-tests/mdx/cypress.json index db2ba6ad5f8d9..42fda02830191 100644 --- a/e2e-tests/mdx/cypress.json +++ b/e2e-tests/mdx/cypress.json @@ -1,3 +1,6 @@ { - "baseUrl": "http://localhost:9000" + "baseUrl": "http://localhost:9000", + "env": { + "GATSBY_COMMAND": "build" + } } diff --git a/e2e-tests/mdx/cypress/integration/hmr.js b/e2e-tests/mdx/cypress/integration/hmr.js new file mode 100644 index 0000000000000..fed4f6ebf697e --- /dev/null +++ b/e2e-tests/mdx/cypress/integration/hmr.js @@ -0,0 +1,20 @@ +if (Cypress.env("GATSBY_COMMAND") === `develop`) { + before(() => { + cy.exec(`npm run reset`) + }) + + after(() => { + cy.exec(`npm run reset`) + }) + + it(`Can hot-reload`, () => { + cy.visit(`/hmr`).waitForRouteChange() + cy.get(`h2`).invoke(`text`).should(`eq`, `Lorem`) + + cy.exec( + `npm run update -- --file src/pages/hmr.mdx --exact --replacements "Lorem:Ipsum"` + ) + + cy.get(`h2`).invoke(`text`).should(`eq`, `Ipsum`) + }) +} diff --git a/e2e-tests/mdx/package.json b/e2e-tests/mdx/package.json index 0d16dbd45ca46..5e103c197b723 100644 --- a/e2e-tests/mdx/package.json +++ b/e2e-tests/mdx/package.json @@ -31,7 +31,9 @@ "cy:open:develop": "cypress open --config-file cypress-dev.json", "cy:open:build": "cypress open", "cy:run:build": "node ../../scripts/cypress-run-with-conditional-record-flag.js --browser chrome --group production", - "cy:run:develop": "node ../../scripts/cypress-run-with-conditional-record-flag.js --browser chrome --config-file cypress-dev.json --group development" + "cy:run:develop": "node ../../scripts/cypress-run-with-conditional-record-flag.js --browser chrome --config-file cypress-dev.json --group development", + "reset": "node scripts/reset.js", + "update": "node scripts/update.js" }, "devDependencies": { "cross-env": "^5.2.0", diff --git a/e2e-tests/mdx/scripts/history.js b/e2e-tests/mdx/scripts/history.js new file mode 100644 index 0000000000000..2b6996deffe35 --- /dev/null +++ b/e2e-tests/mdx/scripts/history.js @@ -0,0 +1,25 @@ +const fs = require(`fs-extra`) + +const HISTORY_FILE = `__history__.json` + +exports.__HISTORY_FILE__ = HISTORY_FILE + +exports.getHistory = async (file = HISTORY_FILE) => { + try { + const contents = await fs + .readFile(file, `utf8`) + .then(contents => JSON.parse(contents)) + + return new Map(contents) + } catch (e) { + return new Map() + } +} + +exports.writeHistory = async (contents, file = HISTORY_FILE) => { + try { + await fs.writeFile(file, JSON.stringify([...contents]), `utf8`) + } catch (e) { + console.error(e) + } +} diff --git a/e2e-tests/mdx/scripts/reset.js b/e2e-tests/mdx/scripts/reset.js new file mode 100644 index 0000000000000..9aabb5ed7bb82 --- /dev/null +++ b/e2e-tests/mdx/scripts/reset.js @@ -0,0 +1,21 @@ +const fs = require(`fs-extra`) +const path = require(`path`) + +const { __HISTORY_FILE__, getHistory } = require(`./history`) + +async function reset() { + const history = await getHistory() + + await Promise.all( + Array.from(history).map(([filePath, value]) => { + if (typeof value === `string`) { + return fs.writeFile(path.resolve(filePath), value, `utf8`) + } + return fs.remove(path.resolve(filePath)) + }) + ) + + await fs.remove(__HISTORY_FILE__) +} + +reset() diff --git a/e2e-tests/mdx/scripts/update.js b/e2e-tests/mdx/scripts/update.js new file mode 100644 index 0000000000000..094fc0c111ee0 --- /dev/null +++ b/e2e-tests/mdx/scripts/update.js @@ -0,0 +1,103 @@ +const fs = require(`fs-extra`) +const path = require(`path`) +const yargs = require(`yargs`) + +const { getHistory, writeHistory } = require(`./history`) + +const args = yargs + .option(`file`, { + demand: true, + type: `string`, + }) + .option(`replacements`, { + default: [], + type: `array`, + }) + .option(`exact`, { + default: false, + type: `boolean`, + }) + .option(`delete`, { + default: false, + type: `boolean`, + }) + .option(`fileContent`, { + default: JSON.stringify( + ` + import * as React from 'react'; + + import Layout from '../components/layout'; + + export default function SomeComponent() { + return ( + +

Hello %REPLACEMENT%

+
+ ) + } + ` + ).trim(), + type: `string`, + }) + .option(`fileSource`, { + type: `string`, + }) + .option(`restore`, { + default: false, + type: `boolean`, + }).argv + +async function update() { + const history = await getHistory() + + const { file: fileArg, replacements, restore } = args + const filePath = path.resolve(fileArg) + if (restore) { + const original = history.get(filePath) + if (original) { + await fs.writeFile(filePath, original, `utf-8`) + } else if (original === false) { + await fs.remove(filePath) + } else { + console.log(`Didn't make changes to "${fileArg}". Nothing to restore.`) + } + history.delete(filePath) + return + } + let exists = true + if (!fs.existsSync(filePath)) { + exists = false + let fileContent + if (args.fileSource) { + fileContent = await fs.readFile(args.fileSource, `utf8`) + } else if (args.fileContent) { + fileContent = JSON.parse(args.fileContent).replace(/\+n/g, `\n`) + } + await fs.writeFile(filePath, fileContent, `utf8`) + } + const file = await fs.readFile(filePath, `utf8`) + + if (!history.has(filePath)) { + history.set(filePath, exists ? file : false) + } + + if (args.delete) { + if (exists) { + await fs.remove(filePath) + } + } else { + const contents = replacements.reduce((replaced, pair) => { + const [key, value] = pair.split(`:`) + return replaced.replace( + args.exact ? key : new RegExp(`%${key}%`, `g`), + value + ) + }, file) + + await fs.writeFile(filePath, contents, `utf8`) + } + + await writeHistory(history) +} + +update() diff --git a/e2e-tests/mdx/src/pages/hmr.mdx b/e2e-tests/mdx/src/pages/hmr.mdx new file mode 100644 index 0000000000000..2c249bfdc6e76 --- /dev/null +++ b/e2e-tests/mdx/src/pages/hmr.mdx @@ -0,0 +1 @@ +## Lorem From 397bf4fb243a3f2e86be582d417593e32760f760 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Fri, 7 May 2021 16:08:31 +0200 Subject: [PATCH 05/13] only apply hmr workaround to develop stage --- .../gatsby/create-webpack-config.js | 1 + packages/gatsby-plugin-mdx/loaders/mdx-loader.js | 2 ++ packages/gatsby-plugin-mdx/utils/gen-mdx.js | 14 +++++++++----- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/gatsby-plugin-mdx/gatsby/create-webpack-config.js b/packages/gatsby-plugin-mdx/gatsby/create-webpack-config.js index fa441f8d38168..4bcc8658946dd 100644 --- a/packages/gatsby-plugin-mdx/gatsby/create-webpack-config.js +++ b/packages/gatsby-plugin-mdx/gatsby/create-webpack-config.js @@ -76,6 +76,7 @@ module.exports = ( options: { cache: cache, actions: actions, + stage, ...other, pluginOptions: options, }, diff --git a/packages/gatsby-plugin-mdx/loaders/mdx-loader.js b/packages/gatsby-plugin-mdx/loaders/mdx-loader.js index 9d9b9fb85b201..c7f02367a9198 100644 --- a/packages/gatsby-plugin-mdx/loaders/mdx-loader.js +++ b/packages/gatsby-plugin-mdx/loaders/mdx-loader.js @@ -96,6 +96,7 @@ const hasDefaultExport = (str, options) => { module.exports = async function mdxLoader(content) { const callback = this.async() const { + stage, getNode: rawGetNode, getNodes, getNodesByType, @@ -214,6 +215,7 @@ ${contentWithoutFrontmatter}` reporter, cache, pathPrefix, + stage, }) try { diff --git a/packages/gatsby-plugin-mdx/utils/gen-mdx.js b/packages/gatsby-plugin-mdx/utils/gen-mdx.js index 3dcd3a3f24054..4ffff554d7d31 100644 --- a/packages/gatsby-plugin-mdx/utils/gen-mdx.js +++ b/packages/gatsby-plugin-mdx/utils/gen-mdx.js @@ -54,6 +54,7 @@ async function genMDX( reporter, cache, pathPrefix, + stage, ...helpers }, { forceDisableCache = false } = {} @@ -92,7 +93,10 @@ async function genMDX( // pull classic style frontmatter off the raw MDX body debug(`processing classic frontmatter`) const { data, content: frontMatterCodeResult } = grayMatter(node.rawBody) - const content = isLoader + + const isDevelopLoader = isLoader && stage === `develop` + + const content = isDevelopLoader ? frontMatterCodeResult : `${frontMatterCodeResult} @@ -145,9 +149,9 @@ export const _frontmatter = ${JSON.stringify(data)}` import { mdx } from '@mdx-js/react'; ${code}` - if (!isLoader) { - results.rawMDXOutput = rawMDXOutput + results.rawMDXOutput = rawMDXOutput + if (!isLoader) { debug(`compiling scope`) const instance = new BabelPluginPluckImports() const result = babel.transform(code, { @@ -190,8 +194,8 @@ ${code}` /export\s*{\s*MDXContent\s+as\s+default\s*};?/, `return MDXContent;` ) - } else { - // code path for webpack loader + } else if (isDevelopLoader) { + // code path for webpack loader in `develop` stage // actual react component is saved to different file so that _frontmatter export doesn't // disable react-refresh (multiple exports are not handled) const filePath = path.join( From fa5423d5d43f160088b345261a7de67dafcef5fa Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 10 May 2021 11:35:38 +0200 Subject: [PATCH 06/13] don't save mdx component to fs, use webpack tricks with query params --- .../gatsby/create-webpack-config.js | 2 +- .../gatsby-plugin-mdx/loaders/mdx-loader.js | 22 ++++++++-- packages/gatsby-plugin-mdx/utils/gen-mdx.js | 27 ++---------- packages/gatsby/src/utils/webpack.config.js | 43 ++++++++++++++++++- 4 files changed, 64 insertions(+), 30 deletions(-) diff --git a/packages/gatsby-plugin-mdx/gatsby/create-webpack-config.js b/packages/gatsby-plugin-mdx/gatsby/create-webpack-config.js index 4bcc8658946dd..e08f154c58e70 100644 --- a/packages/gatsby-plugin-mdx/gatsby/create-webpack-config.js +++ b/packages/gatsby-plugin-mdx/gatsby/create-webpack-config.js @@ -76,7 +76,7 @@ module.exports = ( options: { cache: cache, actions: actions, - stage, + isolateMDXComponent: stage === `develop`, ...other, pluginOptions: options, }, diff --git a/packages/gatsby-plugin-mdx/loaders/mdx-loader.js b/packages/gatsby-plugin-mdx/loaders/mdx-loader.js index c7f02367a9198..d1aa22568fdbe 100644 --- a/packages/gatsby-plugin-mdx/loaders/mdx-loader.js +++ b/packages/gatsby-plugin-mdx/loaders/mdx-loader.js @@ -1,6 +1,7 @@ const _ = require(`lodash`) const { getOptions } = require(`loader-utils`) const grayMatter = require(`gray-matter`) +const path = require(`path`) const unified = require(`unified`) const babel = require(`@babel/core`) const { createRequireFromPath, slash } = require(`gatsby-core-utils`) @@ -94,9 +95,8 @@ const hasDefaultExport = (str, options) => { } module.exports = async function mdxLoader(content) { - const callback = this.async() const { - stage, + isolateMDXComponent, getNode: rawGetNode, getNodes, getNodesByType, @@ -107,6 +107,22 @@ module.exports = async function mdxLoader(content) { ...helpers } = getOptions(this) + const resourceQuery = this.resourceQuery || `` + if (isolateMDXComponent && !resourceQuery.includes(`type=component`)) { + const { data } = grayMatter(content) + + return `import MDXContent from "/${path.relative( + this.rootContext, + this.resourcePath + )}?type=component"; + +export default MDXContent; + +export const _frontmatter = ${JSON.stringify(data)};` + } + + const callback = this.async() + const options = withDefaultOptions(pluginOptions) let fileNode = getNodes().find( @@ -215,7 +231,7 @@ ${contentWithoutFrontmatter}` reporter, cache, pathPrefix, - stage, + isolateMDXComponent, }) try { diff --git a/packages/gatsby-plugin-mdx/utils/gen-mdx.js b/packages/gatsby-plugin-mdx/utils/gen-mdx.js index 4ffff554d7d31..608f5e5ff97e1 100644 --- a/packages/gatsby-plugin-mdx/utils/gen-mdx.js +++ b/packages/gatsby-plugin-mdx/utils/gen-mdx.js @@ -54,14 +54,14 @@ async function genMDX( reporter, cache, pathPrefix, - stage, + isolateMDXComponent, ...helpers }, { forceDisableCache = false } = {} ) { const pathPrefixCacheStr = pathPrefix || `` const payloadCacheKey = node => - `gatsby-plugin-mdx-entire-payload-${node.internal.contentDigest}-${pathPrefixCacheStr}` + `gatsby-plugin-mdx-entire-payload-${node.internal.contentDigest}-${pathPrefixCacheStr}-${isolateMDXComponent}` if (!forceDisableCache) { const cachedPayload = await cache.get(payloadCacheKey(node)) @@ -94,9 +94,7 @@ async function genMDX( debug(`processing classic frontmatter`) const { data, content: frontMatterCodeResult } = grayMatter(node.rawBody) - const isDevelopLoader = isLoader && stage === `develop` - - const content = isDevelopLoader + const content = isolateMDXComponent ? frontMatterCodeResult : `${frontMatterCodeResult} @@ -194,25 +192,6 @@ ${code}` /export\s*{\s*MDXContent\s+as\s+default\s*};?/, `return MDXContent;` ) - } else if (isDevelopLoader) { - // code path for webpack loader in `develop` stage - // actual react component is saved to different file so that _frontmatter export doesn't - // disable react-refresh (multiple exports are not handled) - const filePath = path.join( - cache.directory, - MDX_LOADER_PASSTHROUGH_LOCATION, - `${helpers.createContentDigest(node.fileAbsolutePath)}.js` - ) - - await fs.outputFile(filePath, rawMDXOutput) - - results.rawMDXOutput = ` - import MDXContent from "${filePath}"; - - export default MDXContent; - - export const _frontmatter = ${JSON.stringify(data)}; - ` } /* results.html = renderToStaticMarkup( * React.createElement(MDXRenderer, null, results.body) diff --git a/packages/gatsby/src/utils/webpack.config.js b/packages/gatsby/src/utils/webpack.config.js index 538442bd46b0d..63848bfcc4d8d 100644 --- a/packages/gatsby/src/utils/webpack.config.js +++ b/packages/gatsby/src/utils/webpack.config.js @@ -38,6 +38,7 @@ module.exports = async ( port, { parentSpan } = {} ) => { + let fastRefreshPlugin const modulesThatUseGatsby = await getGatsbyDependents() const directoryPath = withBasePath(directory) @@ -219,7 +220,7 @@ module.exports = async ( case `develop`: { configPlugins = configPlugins .concat([ - plugins.fastRefresh({ modulesThatUseGatsby }), + (fastRefreshPlugin = plugins.fastRefresh({ modulesThatUseGatsby })), new ForceCssHMRForEdgeCases(), plugins.hotModuleReplacement(), plugins.noEmitOnErrors(), @@ -276,7 +277,7 @@ module.exports = async ( function getDevtool() { switch (stage) { case `develop`: - return `eval-cheap-module-source-map` + return `cheap-module-source-map` // use a normal `source-map` for the html phases since // it gives better line and column numbers case `develop-html`: @@ -810,5 +811,43 @@ module.exports = async ( parentSpan, }) + if (fastRefreshPlugin) { + // Fast refresh plugin has `include` option that determines + // wether HMR code gets injected. We need to make sure all custom loaders + // (like .ts or .mdx) that use our babel-loader will be taken into account + // when deciding which modules get fast-refresh HMR addition. + const fastRefreshIncludes = [] + const babelLoaderLoc = require.resolve(`./babel-loader`) + for (const rule of getConfig().module.rules) { + if (!rule.use) { + continue + } + + const hasBabelLoader = (Array.isArray(rule.use) + ? rule.use + : [rule.use] + ).some(loaderConfig => loaderConfig.loader === babelLoaderLoc) + + if (hasBabelLoader) { + fastRefreshIncludes.push(rule.test) + } + } + + // start with default include of fast refresh plugin + const includeRegex = /\.([jt]sx?|flow)$/i + includeRegex.test = modulePath => { + // drop query param from request (i.e. ?type=component for mdx-loader) + // so loader rule test work well + const queryParamStartIndex = modulePath.indexOf(`?`) + if (queryParamStartIndex !== -1) { + modulePath = modulePath.substr(0, queryParamStartIndex) + } + + return fastRefreshIncludes.some(re => re.test(modulePath)) + } + + fastRefreshPlugin.options.include = includeRegex + } + return getConfig() } From 224bccd6bc2812b3dd0fd5664a59bee34faaae97 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 10 May 2021 11:53:05 +0200 Subject: [PATCH 07/13] wait for hmr in mdx/develop --- e2e-tests/mdx/cypress/integration/hmr.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/e2e-tests/mdx/cypress/integration/hmr.js b/e2e-tests/mdx/cypress/integration/hmr.js index fed4f6ebf697e..722424a2707ae 100644 --- a/e2e-tests/mdx/cypress/integration/hmr.js +++ b/e2e-tests/mdx/cypress/integration/hmr.js @@ -8,13 +8,20 @@ if (Cypress.env("GATSBY_COMMAND") === `develop`) { }) it(`Can hot-reload`, () => { - cy.visit(`/hmr`).waitForRouteChange() + cy.visit(`/hmr`, { + onBeforeLoad: win => { + cy.spy(win.console, "log").as(`hmrConsoleLog`) + }, + }).waitForRouteChange() cy.get(`h2`).invoke(`text`).should(`eq`, `Lorem`) cy.exec( `npm run update -- --file src/pages/hmr.mdx --exact --replacements "Lorem:Ipsum"` ) + cy.get(`@hmrConsoleLog`).should(`be.calledWithMatch`, `App is up to date`) + cy.wait(1000) + cy.get(`h2`).invoke(`text`).should(`eq`, `Ipsum`) }) } From 4dd5088f20d6d415acf255484eb01b72e2edf6a2 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 10 May 2021 11:55:48 +0200 Subject: [PATCH 08/13] drop passthrough fs location --- packages/gatsby-plugin-mdx/constants.js | 2 -- packages/gatsby-plugin-mdx/utils/gen-mdx.js | 1 - 2 files changed, 3 deletions(-) diff --git a/packages/gatsby-plugin-mdx/constants.js b/packages/gatsby-plugin-mdx/constants.js index 9a1971f6502a6..bb3f006a6aff8 100644 --- a/packages/gatsby-plugin-mdx/constants.js +++ b/packages/gatsby-plugin-mdx/constants.js @@ -1,9 +1,7 @@ const MDX_WRAPPERS_LOCATION = `mdx-wrappers-dir` const MDX_SCOPES_LOCATION = `mdx-scopes-dir` -const MDX_LOADER_PASSTHROUGH_LOCATION = `mdx-loader-passthrough-dir` module.exports = { MDX_WRAPPERS_LOCATION, MDX_SCOPES_LOCATION, - MDX_LOADER_PASSTHROUGH_LOCATION, } diff --git a/packages/gatsby-plugin-mdx/utils/gen-mdx.js b/packages/gatsby-plugin-mdx/utils/gen-mdx.js index 608f5e5ff97e1..5efcc6595b95e 100644 --- a/packages/gatsby-plugin-mdx/utils/gen-mdx.js +++ b/packages/gatsby-plugin-mdx/utils/gen-mdx.js @@ -12,7 +12,6 @@ const htmlAttrToJSXAttr = require(`./babel-plugin-html-attr-to-jsx-attr`) const removeExportKeywords = require(`./babel-plugin-remove-export-keywords`) const BabelPluginPluckImports = require(`./babel-plugin-pluck-imports`) const { parseImportBindings } = require(`./import-parser`) -const { MDX_LOADER_PASSTHROUGH_LOCATION } = require(`../constants`) /* * function mutateNode({ From 26fcde4922717018f06f049d5ccba55c1d1f63f1 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 10 May 2021 11:57:17 +0200 Subject: [PATCH 09/13] revert unneeded change --- packages/gatsby-plugin-mdx/utils/gen-mdx.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/gatsby-plugin-mdx/utils/gen-mdx.js b/packages/gatsby-plugin-mdx/utils/gen-mdx.js index 5efcc6595b95e..04e26e109fe3d 100644 --- a/packages/gatsby-plugin-mdx/utils/gen-mdx.js +++ b/packages/gatsby-plugin-mdx/utils/gen-mdx.js @@ -142,12 +142,10 @@ export const _frontmatter = ${JSON.stringify(data)}` ), }) - const rawMDXOutput = `/* @jsx mdx */ + results.rawMDXOutput = `/* @jsx mdx */ import { mdx } from '@mdx-js/react'; ${code}` - results.rawMDXOutput = rawMDXOutput - if (!isLoader) { debug(`compiling scope`) const instance = new BabelPluginPluckImports() From 4a78743922eaf928a2fd9d0ce71f79c34cb33a53 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 10 May 2021 11:58:34 +0200 Subject: [PATCH 10/13] more reverts --- packages/gatsby-plugin-mdx/utils/gen-mdx.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/gatsby-plugin-mdx/utils/gen-mdx.js b/packages/gatsby-plugin-mdx/utils/gen-mdx.js index 04e26e109fe3d..38ed9459eadc0 100644 --- a/packages/gatsby-plugin-mdx/utils/gen-mdx.js +++ b/packages/gatsby-plugin-mdx/utils/gen-mdx.js @@ -2,8 +2,6 @@ const babel = require(`@babel/core`) const grayMatter = require(`gray-matter`) const mdx = require(`@mdx-js/mdx`) const objRestSpread = require(`@babel/plugin-proposal-object-rest-spread`) -const path = require(`path`) -const fs = require(`fs-extra`) const debug = require(`debug`)(`gatsby-plugin-mdx:gen-mdx`) From fd1b5b771c69d32ef52dfa9a3b7ef0ca910227bf Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 10 May 2021 12:44:12 +0200 Subject: [PATCH 11/13] revert devtool debugging change --- packages/gatsby/src/utils/webpack.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gatsby/src/utils/webpack.config.js b/packages/gatsby/src/utils/webpack.config.js index 63848bfcc4d8d..034c2aeea5bd3 100644 --- a/packages/gatsby/src/utils/webpack.config.js +++ b/packages/gatsby/src/utils/webpack.config.js @@ -277,7 +277,7 @@ module.exports = async ( function getDevtool() { switch (stage) { case `develop`: - return `cheap-module-source-map` + return `eval-cheap-module-source-map` // use a normal `source-map` for the html phases since // it gives better line and column numbers case `develop-html`: From 3b5d084455d4adcac4480179f50e644c6210a680 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 10 May 2021 16:22:27 +0200 Subject: [PATCH 12/13] adjust unit tests --- .../__snapshots__/mdx-loader.test.js.snap | 722 ++++++++++++++++-- .../gatsby-plugin-mdx/loaders/mdx-loader.js | 15 +- .../loaders/mdx-loader.test.js | 118 ++- 3 files changed, 732 insertions(+), 123 deletions(-) diff --git a/packages/gatsby-plugin-mdx/loaders/__snapshots__/mdx-loader.test.js.snap b/packages/gatsby-plugin-mdx/loaders/__snapshots__/mdx-loader.test.js.snap index 772d412259508..a3640f04edc8d 100644 --- a/packages/gatsby-plugin-mdx/loaders/__snapshots__/mdx-loader.test.js.snap +++ b/packages/gatsby-plugin-mdx/loaders/__snapshots__/mdx-loader.test.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`mdx-loader snapshot [lessBabel=true] with body 1`] = ` +exports[`mdx-loader snapshot with body 1`] = ` import * as React from "react"; /* @jsx mdx */ import { mdx } from "@mdx-js/react"; @@ -29,7 +29,7 @@ MDXContent.isMDXComponent = true; `; -exports[`mdx-loader snapshot [lessBabel=true] with frontmatter 1`] = ` +exports[`mdx-loader snapshot with frontmatter 1`] = ` import * as React from "react"; /* @jsx mdx */ import { mdx } from "@mdx-js/react"; @@ -62,7 +62,77 @@ MDXContent.isMDXComponent = true; `; -exports[`mdx-loader snapshot [lessBabel=true] with frontmatter-layout 1`] = ` +exports[`mdx-loader snapshot with frontmatter-isDevelopStage 1`] = ` +import MDXContent from "/frontmatter-isDevelopStage?type=component"; +export default MDXContent; +export * from "/frontmatter-isDevelopStage?type=component"; + +export const _frontmatter = { one: "two", three: 4, array: [1, 2, 3] }; + +`; + +exports[`mdx-loader snapshot with frontmatter-isDevelopStage 2`] = ` +import * as React from "react"; +/* @jsx mdx */ +import { mdx } from "@mdx-js/react"; +/* @jsx mdx */ + +const layoutProps = {}; +const MDXLayout = "wrapper"; +export default function MDXContent({ components, ...props }) { + return ( + +

{\`Some title\`}

+

{\`a bit of a paragraph\`}

+

{\`some content\`}

+
+ ); +} +MDXContent.isMDXComponent = true; + +`; + +exports[`mdx-loader snapshot with frontmatter-isDevelopStage-lessBabel 1`] = ` +import MDXContent from "/frontmatter-isDevelopStage-lessBabel?type=component"; +export default MDXContent; +export * from "/frontmatter-isDevelopStage-lessBabel?type=component"; + +export const _frontmatter = { one: "two", three: 4, array: [1, 2, 3] }; + +`; + +exports[`mdx-loader snapshot with frontmatter-isDevelopStage-lessBabel 2`] = ` +import * as React from "react"; +/* @jsx mdx */ +import { mdx } from "@mdx-js/react"; +/* @jsx mdx */ + +const layoutProps = {}; +const MDXLayout = "wrapper"; +export default function MDXContent({ components, ...props }) { + return ( + +

{\`Some title\`}

+

{\`a bit of a paragraph\`}

+

{\`some content\`}

+
+ ); +} +MDXContent.isMDXComponent = true; + +`; + +exports[`mdx-loader snapshot with frontmatter-layout 1`] = ` import * as React from "react"; /* @jsx mdx */ import { mdx } from "@mdx-js/react"; @@ -97,22 +167,92 @@ MDXContent.isMDXComponent = true; `; -exports[`mdx-loader snapshot [lessBabel=true] with frontmatter-layout-namedExports 1`] = ` +exports[`mdx-loader snapshot with frontmatter-layout-isDevelopStage 1`] = ` +import MDXContent from "/frontmatter-layout-isDevelopStage?type=component"; +export default MDXContent; +export * from "/frontmatter-layout-isDevelopStage?type=component"; + +export const _frontmatter = { one: "two", three: 4, array: [1, 2, 3] }; + +`; + +exports[`mdx-loader snapshot with frontmatter-layout-isDevelopStage 2`] = ` +import * as React from "react"; +/* @jsx mdx */ +import { mdx } from "@mdx-js/react"; +/* @jsx mdx */ + +const layoutProps = {}; + +const MDXLayout = ({ children, ...props }) =>
{children}
; + +export default function MDXContent({ components, ...props }) { + return ( + +

{\`Some title\`}

+

{\`a bit of a paragraph\`}

+

{\`some content\`}

+
+ ); +} +MDXContent.isMDXComponent = true; + +`; + +exports[`mdx-loader snapshot with frontmatter-layout-isDevelopStage-lessBabel 1`] = ` +import MDXContent from "/frontmatter-layout-isDevelopStage-lessBabel?type=component"; +export default MDXContent; +export * from "/frontmatter-layout-isDevelopStage-lessBabel?type=component"; + +export const _frontmatter = { one: "two", three: 4, array: [1, 2, 3] }; + +`; + +exports[`mdx-loader snapshot with frontmatter-layout-isDevelopStage-lessBabel 2`] = ` +import * as React from "react"; +/* @jsx mdx */ +import { mdx } from "@mdx-js/react"; +/* @jsx mdx */ + +const layoutProps = {}; + +const MDXLayout = ({ children, ...props }) =>
{children}
; + +export default function MDXContent({ components, ...props }) { + return ( + +

{\`Some title\`}

+

{\`a bit of a paragraph\`}

+

{\`some content\`}

+
+ ); +} +MDXContent.isMDXComponent = true; + +`; + +exports[`mdx-loader snapshot with frontmatter-layout-lessBabel 1`] = ` import * as React from "react"; /* @jsx mdx */ import { mdx } from "@mdx-js/react"; /* @jsx mdx */ -export const meta = { - author: "chris", -}; export const _frontmatter = { one: "two", three: 4, array: [1, 2, 3], }; const layoutProps = { - meta, _frontmatter, }; @@ -136,7 +276,7 @@ MDXContent.isMDXComponent = true; `; -exports[`mdx-loader snapshot [lessBabel=true] with frontmatter-namedExports 1`] = ` +exports[`mdx-loader snapshot with frontmatter-layout-namedExports 1`] = ` import * as React from "react"; /* @jsx mdx */ import { mdx } from "@mdx-js/react"; @@ -154,7 +294,9 @@ const layoutProps = { meta, _frontmatter, }; -const MDXLayout = "wrapper"; + +const MDXLayout = ({ children, ...props }) =>
{children}
; + export default function MDXContent({ components, ...props }) { return (
{children}
; @@ -204,7 +357,16 @@ MDXContent.isMDXComponent = true; `; -exports[`mdx-loader snapshot [lessBabel=true] with layout-namedExports 1`] = ` +exports[`mdx-loader snapshot with frontmatter-layout-namedExports-isDevelopStage-lessBabel 1`] = ` +import MDXContent from "/frontmatter-layout-namedExports-isDevelopStage-lessBabel?type=component"; +export default MDXContent; +export * from "/frontmatter-layout-namedExports-isDevelopStage-lessBabel?type=component"; + +export const _frontmatter = { one: "two", three: 4, array: [1, 2, 3] }; + +`; + +exports[`mdx-loader snapshot with frontmatter-layout-namedExports-isDevelopStage-lessBabel 2`] = ` import * as React from "react"; /* @jsx mdx */ import { mdx } from "@mdx-js/react"; @@ -213,10 +375,8 @@ import { mdx } from "@mdx-js/react"; export const meta = { author: "chris", }; -export const _frontmatter = {}; const layoutProps = { meta, - _frontmatter, }; const MDXLayout = ({ children, ...props }) =>
{children}
; @@ -239,7 +399,7 @@ MDXContent.isMDXComponent = true; `; -exports[`mdx-loader snapshot [lessBabel=true] with namedExports 1`] = ` +exports[`mdx-loader snapshot with frontmatter-layout-namedExports-lessBabel 1`] = ` import * as React from "react"; /* @jsx mdx */ import { mdx } from "@mdx-js/react"; @@ -248,12 +408,18 @@ import { mdx } from "@mdx-js/react"; export const meta = { author: "chris", }; -export const _frontmatter = {}; +export const _frontmatter = { + one: "two", + three: 4, + array: [1, 2, 3], +}; const layoutProps = { meta, _frontmatter, }; -const MDXLayout = "wrapper"; + +const MDXLayout = ({ children, ...props }) =>
{children}
; + export default function MDXContent({ components, ...props }) { return (
{children}
; - +const MDXLayout = "wrapper"; export default function MDXContent({ components, ...props }) { return (
{children}
; - +const MDXLayout = "wrapper"; export default function MDXContent({ components, ...props }) { return ( +

{\`Some title\`}

+

{\`a bit of a paragraph\`}

+

{\`some content\`}

+
+ ); +} +MDXContent.isMDXComponent = true; + +`; + +exports[`mdx-loader snapshot with isDevelopStage-lessBabel 1`] = ` +import MDXContent from "/isDevelopStage-lessBabel?type=component"; +export default MDXContent; +export * from "/isDevelopStage-lessBabel?type=component"; + export const _frontmatter = {}; -const layoutProps = { - _frontmatter, -}; -const MDXLayout = ({ children, ...props }) =>
{children}
; +`; + +exports[`mdx-loader snapshot with isDevelopStage-lessBabel 2`] = ` +import * as React from "react"; +/* @jsx mdx */ +import { mdx } from "@mdx-js/react"; +/* @jsx mdx */ +const layoutProps = {}; +const MDXLayout = "wrapper"; export default function MDXContent({ components, ...props }) { return (
{children}
; + +export default function MDXContent({ components, ...props }) { + return ( + +

{\`Some title\`}

+

{\`a bit of a paragraph\`}

+

{\`some content\`}

+
+ ); +} +MDXContent.isMDXComponent = true; + +`; + +exports[`mdx-loader snapshot with layout-isDevelopStage-lessBabel 1`] = ` +import MDXContent from "/layout-isDevelopStage-lessBabel?type=component"; +export default MDXContent; +export * from "/layout-isDevelopStage-lessBabel?type=component"; + +export const _frontmatter = {}; + +`; + +exports[`mdx-loader snapshot with layout-isDevelopStage-lessBabel 2`] = ` +import * as React from "react"; +/* @jsx mdx */ +import { mdx } from "@mdx-js/react"; +/* @jsx mdx */ + +const layoutProps = {}; + +const MDXLayout = ({ children, ...props }) =>
{children}
; + +export default function MDXContent({ components, ...props }) { + return ( + +

{\`Some title\`}

+

{\`a bit of a paragraph\`}

+

{\`some content\`}

+
+ ); +} +MDXContent.isMDXComponent = true; + +`; + +exports[`mdx-loader snapshot with layout-lessBabel 1`] = ` +import * as React from "react"; +/* @jsx mdx */ +import { mdx } from "@mdx-js/react"; +/* @jsx mdx */ + +export const _frontmatter = {}; +const layoutProps = { + _frontmatter, +}; + +const MDXLayout = ({ children, ...props }) =>
{children}
; + +export default function MDXContent({ components, ...props }) { + return ( + +

{\`Some title\`}

+

{\`a bit of a paragraph\`}

+

{\`some content\`}

+
+ ); +} +MDXContent.isMDXComponent = true; + +`; + +exports[`mdx-loader snapshot with layout-namedExports 1`] = ` +import * as React from "react"; +/* @jsx mdx */ +import { mdx } from "@mdx-js/react"; +/* @jsx mdx */ + +export const meta = { + author: "chris", +}; +export const _frontmatter = {}; +const layoutProps = { + meta, + _frontmatter, +}; + +const MDXLayout = ({ children, ...props }) =>
{children}
; + +export default function MDXContent({ components, ...props }) { + return ( + +

{\`Some title\`}

+

{\`a bit of a paragraph\`}

+

{\`some content\`}

+
+ ); +} +MDXContent.isMDXComponent = true; + +`; + +exports[`mdx-loader snapshot with layout-namedExports-isDevelopStage 1`] = ` +import MDXContent from "/layout-namedExports-isDevelopStage?type=component"; +export default MDXContent; +export * from "/layout-namedExports-isDevelopStage?type=component"; + +export const _frontmatter = {}; + +`; + +exports[`mdx-loader snapshot with layout-namedExports-isDevelopStage 2`] = ` +import * as React from "react"; +/* @jsx mdx */ +import { mdx } from "@mdx-js/react"; +/* @jsx mdx */ + +export const meta = { + author: "chris", +}; +const layoutProps = { + meta, +}; + +const MDXLayout = ({ children, ...props }) =>
{children}
; + +export default function MDXContent({ components, ...props }) { + return ( + +

{\`Some title\`}

+

{\`a bit of a paragraph\`}

+

{\`some content\`}

+
+ ); +} +MDXContent.isMDXComponent = true; + +`; + +exports[`mdx-loader snapshot with layout-namedExports-isDevelopStage-lessBabel 1`] = ` +import MDXContent from "/layout-namedExports-isDevelopStage-lessBabel?type=component"; +export default MDXContent; +export * from "/layout-namedExports-isDevelopStage-lessBabel?type=component"; + +export const _frontmatter = {}; + +`; + +exports[`mdx-loader snapshot with layout-namedExports-isDevelopStage-lessBabel 2`] = ` +import * as React from "react"; +/* @jsx mdx */ +import { mdx } from "@mdx-js/react"; +/* @jsx mdx */ + +export const meta = { + author: "chris", +}; +const layoutProps = { + meta, +}; + +const MDXLayout = ({ children, ...props }) =>
{children}
; + +export default function MDXContent({ components, ...props }) { + return ( + +

{\`Some title\`}

+

{\`a bit of a paragraph\`}

+

{\`some content\`}

+
+ ); +} +MDXContent.isMDXComponent = true; + +`; + +exports[`mdx-loader snapshot with layout-namedExports-lessBabel 1`] = ` +import * as React from "react"; +/* @jsx mdx */ +import { mdx } from "@mdx-js/react"; +/* @jsx mdx */ + +export const meta = { + author: "chris", +}; +export const _frontmatter = {}; +const layoutProps = { + meta, + _frontmatter, +}; + +const MDXLayout = ({ children, ...props }) =>
{children}
; + +export default function MDXContent({ components, ...props }) { + return ( + +

{\`Some title\`}

+

{\`a bit of a paragraph\`}

+

{\`some content\`}

+
+ ); +} +MDXContent.isMDXComponent = true; + +`; + +exports[`mdx-loader snapshot with lessBabel 1`] = ` +import * as React from "react"; +/* @jsx mdx */ +import { mdx } from "@mdx-js/react"; +/* @jsx mdx */ + +export const _frontmatter = {}; +const layoutProps = { + _frontmatter, +}; +const MDXLayout = "wrapper"; +export default function MDXContent({ components, ...props }) { + return ( + +

{\`Some title\`}

+

{\`a bit of a paragraph\`}

+

{\`some content\`}

+
+ ); +} +MDXContent.isMDXComponent = true; + +`; + +exports[`mdx-loader snapshot with namedExports 1`] = ` +import * as React from "react"; +/* @jsx mdx */ +import { mdx } from "@mdx-js/react"; +/* @jsx mdx */ + +export const meta = { + author: "chris", +}; +export const _frontmatter = {}; +const layoutProps = { + meta, + _frontmatter, +}; +const MDXLayout = "wrapper"; +export default function MDXContent({ components, ...props }) { + return ( + +

{\`Some title\`}

+

{\`a bit of a paragraph\`}

+

{\`some content\`}

+
+ ); +} +MDXContent.isMDXComponent = true; + +`; + +exports[`mdx-loader snapshot with namedExports-isDevelopStage 1`] = ` +import MDXContent from "/namedExports-isDevelopStage?type=component"; +export default MDXContent; +export * from "/namedExports-isDevelopStage?type=component"; + +export const _frontmatter = {}; + +`; + +exports[`mdx-loader snapshot with namedExports-isDevelopStage 2`] = ` +import * as React from "react"; +/* @jsx mdx */ +import { mdx } from "@mdx-js/react"; +/* @jsx mdx */ + +export const meta = { + author: "chris", +}; +const layoutProps = { + meta, +}; +const MDXLayout = "wrapper"; +export default function MDXContent({ components, ...props }) { + return ( + +

{\`Some title\`}

+

{\`a bit of a paragraph\`}

+

{\`some content\`}

+
+ ); +} +MDXContent.isMDXComponent = true; + +`; + +exports[`mdx-loader snapshot with namedExports-isDevelopStage-lessBabel 1`] = ` +import MDXContent from "/namedExports-isDevelopStage-lessBabel?type=component"; +export default MDXContent; +export * from "/namedExports-isDevelopStage-lessBabel?type=component"; + +export const _frontmatter = {}; + +`; + +exports[`mdx-loader snapshot with namedExports-isDevelopStage-lessBabel 2`] = ` +import * as React from "react"; +/* @jsx mdx */ +import { mdx } from "@mdx-js/react"; +/* @jsx mdx */ + +export const meta = { + author: "chris", +}; +const layoutProps = { + meta, +}; +const MDXLayout = "wrapper"; +export default function MDXContent({ components, ...props }) { + return ( + +

{\`Some title\`}

+

{\`a bit of a paragraph\`}

+

{\`some content\`}

+
+ ); +} +MDXContent.isMDXComponent = true; + +`; + +exports[`mdx-loader snapshot with namedExports-lessBabel 1`] = ` import * as React from "react"; /* @jsx mdx */ import { mdx } from "@mdx-js/react"; diff --git a/packages/gatsby-plugin-mdx/loaders/mdx-loader.js b/packages/gatsby-plugin-mdx/loaders/mdx-loader.js index d1aa22568fdbe..fdd574914b074 100644 --- a/packages/gatsby-plugin-mdx/loaders/mdx-loader.js +++ b/packages/gatsby-plugin-mdx/loaders/mdx-loader.js @@ -95,6 +95,8 @@ const hasDefaultExport = (str, options) => { } module.exports = async function mdxLoader(content) { + const callback = this.async() + const { isolateMDXComponent, getNode: rawGetNode, @@ -111,18 +113,21 @@ module.exports = async function mdxLoader(content) { if (isolateMDXComponent && !resourceQuery.includes(`type=component`)) { const { data } = grayMatter(content) - return `import MDXContent from "/${path.relative( + const requestPath = `/${path.relative( this.rootContext, this.resourcePath - )}?type=component"; - + )}?type=component` + + return callback( + null, + `import MDXContent from "${requestPath}"; export default MDXContent; +export * from "${requestPath}" export const _frontmatter = ${JSON.stringify(data)};` + ) } - const callback = this.async() - const options = withDefaultOptions(pluginOptions) let fileNode = getNodes().find( diff --git a/packages/gatsby-plugin-mdx/loaders/mdx-loader.test.js b/packages/gatsby-plugin-mdx/loaders/mdx-loader.test.js index 50c5ad44f438f..f6f72ee98ec48 100644 --- a/packages/gatsby-plugin-mdx/loaders/mdx-loader.test.js +++ b/packages/gatsby-plugin-mdx/loaders/mdx-loader.test.js @@ -34,16 +34,18 @@ some content`, input.namedExports ? code.namedExports : ``, code.body, ].join(`\n\n`), + isDevelopStage: input.isDevelopStage, + lessBabel: input.lessBabel, } } // generate a table of all possible combinations of genMDXfile input -const fixtures = new BaseN([true, false], 3) +const fixtures = new BaseN([true, false], 5) .toArray() - .map(([frontmatter, layout, namedExports]) => - genMDXFile({ frontmatter, layout, namedExports }) + .map(([frontmatter, layout, namedExports, isDevelopStage, lessBabel]) => + genMDXFile({ frontmatter, layout, namedExports, isDevelopStage, lessBabel }) ) - .map(({ name, content }) => [ + .map(({ name, content, isDevelopStage, lessBabel }) => [ name, { internal: { type: `File` }, @@ -51,10 +53,11 @@ const fixtures = new BaseN([true, false], 3) absolutePath: `/fake/${name}`, }, content, + isDevelopStage, + lessBabel, ]) -// temporarily skip those until final solution is done -describe.skip(`mdx-loader`, () => { +describe(`mdx-loader`, () => { expect.addSnapshotSerializer({ print(val /* , serialize */) { return prettier.format(val, { parser: `babel` }) @@ -65,71 +68,56 @@ describe.skip(`mdx-loader`, () => { }) test.each(fixtures)( `snapshot with %s`, - async (filename, fakeGatsbyNode, content) => { - const loader = mdxLoader.bind({ - async() { - return (err, result) => { - expect(err).toBeNull() - expect(result).toMatchSnapshot() - } - }, - query: { - getNodes(_type) { - return fixtures.map(([, node]) => node) - }, - getNodesByType(_type) { - return fixtures.map(([, node]) => node) - }, - pluginOptions: { - lessBabel: false, // default + async (filename, fakeGatsbyNode, content, isDevelopStage, lessBabel) => { + let err + let result + + const createLoader = (opts = {}) => + mdxLoader.bind({ + async() { + return (_err, _result) => { + err = _err + result = _result + } }, - cache: { - get() { - return false + query: { + getNodes(_type) { + return fixtures.map(([, node]) => node) }, - set() { - return + getNodesByType(_type) { + return fixtures.map(([, node]) => node) }, - }, - }, - resourcePath: fakeGatsbyNode.absolutePath, - }) - await loader(content) - } - ) - - test.each(fixtures)( - `snapshot [lessBabel=true] with %s`, - async (filename, fakeGatsbyNode, content) => { - const loader = mdxLoader.bind({ - async() { - return (err, result) => { - expect(err).toBeNull() - expect(result).toMatchSnapshot() - } - }, - query: { - getNodes(_type) { - return fixtures.map(([, node]) => node) - }, - getNodesByType(_type) { - return fixtures.map(([, node]) => node) - }, - pluginOptions: { - lessBabel: true, - }, - cache: { - get() { - return false + pluginOptions: { + lessBabel, }, - set() { - return + cache: { + get() { + return false + }, + set() { + return + }, }, + isolateMDXComponent: isDevelopStage, }, - }, - resourcePath: fakeGatsbyNode.absolutePath, - }) - await loader(content) + resourcePath: fakeGatsbyNode.absolutePath, + resourceQuery: fakeGatsbyNode.absolutePath, + rootContext: `/fake/`, + ...opts, + }) + + await createLoader()(content) + expect(err).toBeNull() + expect(result).toMatchSnapshot() + err = result = undefined + + if (isDevelopStage) { + await createLoader({ + resourceQuery: `${fakeGatsbyNode.absolutePath}?type=component`, + })(content) + expect(err).toBeNull() + expect(result).toMatchSnapshot() + } } ) }) From 63d754d13036cb19375a96514d99f369b98e838f Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Tue, 11 May 2021 16:48:40 +0200 Subject: [PATCH 13/13] add more e2e test - editing prop in markdown, editing component imported by mdx --- e2e-tests/mdx/cypress/integration/hmr.js | 46 ++++++++++++++++++- .../mdx/src/components/hmr-component-edit.js | 7 +++ e2e-tests/mdx/src/components/hmr-prop-edit.js | 7 +++ e2e-tests/mdx/src/pages/hmr.mdx | 7 +++ 4 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 e2e-tests/mdx/src/components/hmr-component-edit.js create mode 100644 e2e-tests/mdx/src/components/hmr-prop-edit.js diff --git a/e2e-tests/mdx/cypress/integration/hmr.js b/e2e-tests/mdx/cypress/integration/hmr.js index 722424a2707ae..353184fc9ce44 100644 --- a/e2e-tests/mdx/cypress/integration/hmr.js +++ b/e2e-tests/mdx/cypress/integration/hmr.js @@ -7,7 +7,7 @@ if (Cypress.env("GATSBY_COMMAND") === `develop`) { cy.exec(`npm run reset`) }) - it(`Can hot-reload`, () => { + it(`Can hot-reload markdown content`, () => { cy.visit(`/hmr`, { onBeforeLoad: win => { cy.spy(win.console, "log").as(`hmrConsoleLog`) @@ -24,4 +24,48 @@ if (Cypress.env("GATSBY_COMMAND") === `develop`) { cy.get(`h2`).invoke(`text`).should(`eq`, `Ipsum`) }) + + it(`Can hot-reload react content (i.e. change prop in mdx content)`, () => { + cy.visit(`/hmr`, { + onBeforeLoad: win => { + cy.spy(win.console, "log").as(`hmrConsoleLog`) + }, + }).waitForRouteChange() + cy.get(`[data-testid="test-prop-edit"]`) + .invoke(`text`) + .should(`eq`, `prop-before`) + + cy.exec( + `npm run update -- --file src/pages/hmr.mdx --exact --replacements "prop-before:prop-after"` + ) + + cy.get(`@hmrConsoleLog`).should(`be.calledWithMatch`, `App is up to date`) + cy.wait(1000) + + cy.get(`[data-testid="test-prop-edit"]`) + .invoke(`text`) + .should(`eq`, `prop-after`) + }) + + it(`Can hot-reload imported js components`, () => { + cy.visit(`/hmr`, { + onBeforeLoad: win => { + cy.spy(win.console, "log").as(`hmrConsoleLog`) + }, + }).waitForRouteChange() + cy.get(`[data-testid="test-imported-edit"]`) + .invoke(`text`) + .should(`eq`, `component-before`) + + cy.exec( + `npm run update -- --file src/components/hmr-component-edit.js --exact --replacements "component-before:component-after"` + ) + + cy.get(`@hmrConsoleLog`).should(`be.calledWithMatch`, `App is up to date`) + cy.wait(1000) + + cy.get(`[data-testid="test-imported-edit"]`) + .invoke(`text`) + .should(`eq`, `component-after`) + }) } diff --git a/e2e-tests/mdx/src/components/hmr-component-edit.js b/e2e-tests/mdx/src/components/hmr-component-edit.js new file mode 100644 index 0000000000000..c7a70d646a637 --- /dev/null +++ b/e2e-tests/mdx/src/components/hmr-component-edit.js @@ -0,0 +1,7 @@ +import React from "react" + +const HMRImportEditComponent = () => ( +
component-before
+) + +export default HMRImportEditComponent diff --git a/e2e-tests/mdx/src/components/hmr-prop-edit.js b/e2e-tests/mdx/src/components/hmr-prop-edit.js new file mode 100644 index 0000000000000..b67dd3de30a35 --- /dev/null +++ b/e2e-tests/mdx/src/components/hmr-prop-edit.js @@ -0,0 +1,7 @@ +import React from "react" + +const HMRPropEditComponent = ({ test }) => ( +
{test}
+) + +export default HMRPropEditComponent diff --git a/e2e-tests/mdx/src/pages/hmr.mdx b/e2e-tests/mdx/src/pages/hmr.mdx index 2c249bfdc6e76..294f97e970378 100644 --- a/e2e-tests/mdx/src/pages/hmr.mdx +++ b/e2e-tests/mdx/src/pages/hmr.mdx @@ -1 +1,8 @@ +import HMRImportEditComponent from "../components/hmr-component-edit" +import HMRPropEditComponent from "../components/hmr-prop-edit" + ## Lorem + + + +