Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(gatsby-plugin-mdx): enable hmr when importing mdx #31288

Merged
merged 13 commits into from
May 11, 2021
Prev Previous commit
Next Next commit
don't save mdx component to fs, use webpack tricks with query params
  • Loading branch information
pieh committed May 10, 2021
commit fa5423d5d43f160088b345261a7de67dafcef5fa
2 changes: 1 addition & 1 deletion packages/gatsby-plugin-mdx/gatsby/create-webpack-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ module.exports = (
options: {
cache: cache,
actions: actions,
stage,
isolateMDXComponent: stage === `develop`,
...other,
pluginOptions: options,
},
Expand Down
22 changes: 19 additions & 3 deletions packages/gatsby-plugin-mdx/loaders/mdx-loader.js
Original file line number Diff line number Diff line change
@@ -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`)
Expand Down Expand Up @@ -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,
Expand All @@ -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(
Expand Down Expand Up @@ -215,7 +231,7 @@ ${contentWithoutFrontmatter}`
reporter,
cache,
pathPrefix,
stage,
isolateMDXComponent,
})

try {
Expand Down
27 changes: 3 additions & 24 deletions packages/gatsby-plugin-mdx/utils/gen-mdx.js
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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}

Expand Down Expand Up @@ -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)
Expand Down
43 changes: 41 additions & 2 deletions packages/gatsby/src/utils/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ module.exports = async (
port,
{ parentSpan } = {}
) => {
let fastRefreshPlugin
const modulesThatUseGatsby = await getGatsbyDependents()
const directoryPath = withBasePath(directory)

Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -276,7 +277,7 @@ module.exports = async (
function getDevtool() {
switch (stage) {
case `develop`:
return `eval-cheap-module-source-map`
return `cheap-module-source-map`
pieh marked this conversation as resolved.
Show resolved Hide resolved
// use a normal `source-map` for the html phases since
// it gives better line and column numbers
case `develop-html`:
Expand Down Expand Up @@ -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()
}