Skip to content

Commit

Permalink
fix(gatsby-plugin-mdx): enable hmr when importing mdx (#31288)
Browse files Browse the repository at this point in the history
* save MDXContent to different file

* tmp: skip mdx loader unit tests

* test(e2e-mdx): upgrade cypress, setup running dev

* test(e2e-mdx): add hmr test case

* only apply hmr workaround to develop stage

* don't save mdx component to fs, use webpack tricks with query params

* wait for hmr in mdx/develop

* drop passthrough fs location

* revert unneeded change

* more reverts

* revert devtool debugging change

* adjust unit tests

* add more e2e test - editing prop in markdown, editing component imported by mdx
  • Loading branch information
pieh committed May 11, 2021
1 parent 6ad990c commit c8db78f
Show file tree
Hide file tree
Showing 16 changed files with 1,057 additions and 127 deletions.
6 changes: 6 additions & 0 deletions e2e-tests/mdx/cypress-dev.json
@@ -0,0 +1,6 @@
{
"baseUrl": "http://localhost:8000",
"env": {
"GATSBY_COMMAND": "develop"
}
}
5 changes: 4 additions & 1 deletion e2e-tests/mdx/cypress.json
@@ -1,3 +1,6 @@
{
"baseUrl": "http://localhost:9000"
"baseUrl": "http://localhost:9000",
"env": {
"GATSBY_COMMAND": "build"
}
}
71 changes: 71 additions & 0 deletions e2e-tests/mdx/cypress/integration/hmr.js
@@ -0,0 +1,71 @@
if (Cypress.env("GATSBY_COMMAND") === `develop`) {
before(() => {
cy.exec(`npm run reset`)
})

after(() => {
cy.exec(`npm run reset`)
})

it(`Can hot-reload markdown content`, () => {
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`)
})

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`)
})
}
21 changes: 14 additions & 7 deletions e2e-tests/mdx/package.json
Expand Up @@ -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",
Expand All @@ -19,14 +19,21 @@
],
"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",
"reset": "node scripts/reset.js",
"update": "node scripts/update.js"
},
"devDependencies": {
"cross-env": "^5.2.0",
Expand Down
25 changes: 25 additions & 0 deletions 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)
}
}
21 changes: 21 additions & 0 deletions 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()
103 changes: 103 additions & 0 deletions 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 (
<Layout>
<h1 data-testid="message">Hello %REPLACEMENT%</h1>
</Layout>
)
}
`
).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()
7 changes: 7 additions & 0 deletions e2e-tests/mdx/src/components/hmr-component-edit.js
@@ -0,0 +1,7 @@
import React from "react"

const HMRImportEditComponent = () => (
<div data-testid="test-imported-edit">component-before</div>
)

export default HMRImportEditComponent
7 changes: 7 additions & 0 deletions e2e-tests/mdx/src/components/hmr-prop-edit.js
@@ -0,0 +1,7 @@
import React from "react"

const HMRPropEditComponent = ({ test }) => (
<div data-testid="test-prop-edit">{test}</div>
)

export default HMRPropEditComponent
8 changes: 8 additions & 0 deletions e2e-tests/mdx/src/pages/hmr.mdx
@@ -0,0 +1,8 @@
import HMRImportEditComponent from "../components/hmr-component-edit"
import HMRPropEditComponent from "../components/hmr-prop-edit"

## Lorem

<HMRImportEditComponent />

<HMRPropEditComponent test="prop-before" />
1 change: 1 addition & 0 deletions packages/gatsby-plugin-mdx/gatsby/create-webpack-config.js
Expand Up @@ -76,6 +76,7 @@ module.exports = (
options: {
cache: cache,
actions: actions,
isolateMDXComponent: stage === `develop`,
...other,
pluginOptions: options,
},
Expand Down

0 comments on commit c8db78f

Please sign in to comment.