diff --git a/packages/gatsby-remark-code-repls/README.md b/packages/gatsby-remark-code-repls/README.md
index 4ff8ffcc67b47..f317848271a6b 100644
--- a/packages/gatsby-remark-code-repls/README.md
+++ b/packages/gatsby-remark-code-repls/README.md
@@ -163,6 +163,13 @@ specified examples directory. (This will avoid broken links at runtime.)
// Note that if a target is specified, "noreferrer" will also be added.
// eg ...
target: '_blank',
+
+ // Include CSS with matching name.
+ // This option only applies to REPLs that support it (eg Codepen).
+ // If set to `true`, when specifying `file1.js` as example file,
+ // it will try to inject the CSS in `file1.css` if the file exists,
+ // otherwise the default behaviour is preserved
+ includeMatchingCSS: false,
},
},
```
diff --git a/packages/gatsby-remark-code-repls/src/__tests__/gatsby-node.js b/packages/gatsby-remark-code-repls/src/__tests__/gatsby-node.js
index be21fb86b260e..6eabf27e2e416 100644
--- a/packages/gatsby-remark-code-repls/src/__tests__/gatsby-node.js
+++ b/packages/gatsby-remark-code-repls/src/__tests__/gatsby-node.js
@@ -30,6 +30,12 @@ const createPagesParams = {
reporter,
}
+const throwFileNotFoundErr = path => {
+ const err = new Error(`no such file or directory '${path}'`)
+ err.code = `ENOENT`
+ throw err
+}
+
describe(`gatsby-remark-code-repls`, () => {
beforeEach(() => {
fs.existsSync.mockReset()
@@ -198,5 +204,105 @@ describe(`gatsby-remark-code-repls`, () => {
expect(html).toBe(``)
})
+
+ it(`should support includeMatchingCSS = "true" when matching file exists`, async () => {
+ readdir.mockResolvedValue([`file.js`, `file.css`])
+ fs.readFileSync.mockReset()
+ fs.readFileSync.mockImplementation((path, options) => {
+ if (path === `file.js`) {
+ return `const foo = "bar";`
+ } else if (path === `file.css`) {
+ return `html { color: red; }`
+ } else {
+ throwFileNotFoundErr(path)
+ }
+ return null
+ })
+
+ await createPages(createPagesParams, {
+ includeMatchingCSS: true,
+ })
+
+ const { css, js } = JSON.parse(
+ createPage.mock.calls[0][0].context.payload
+ )
+
+ expect(js).toBe(`const foo = "bar";`)
+ expect(css).toBe(`html { color: red; }`)
+ })
+
+ it(`should support includeMatchingCSS = "false" when matching file exists`, async () => {
+ readdir.mockResolvedValue([`file.js`, `file.css`])
+ fs.readFileSync.mockReset()
+ fs.readFileSync.mockImplementation((path, options) => {
+ if (path === `file.js`) {
+ return `const foo = "bar";`
+ } else if (path === `file.css`) {
+ return `html { color: red; }`
+ } else {
+ throwFileNotFoundErr(path)
+ }
+ return null
+ })
+
+ await createPages(createPagesParams, {
+ includeMatchingCSS: false,
+ })
+
+ const { css, js } = JSON.parse(
+ createPage.mock.calls[0][0].context.payload
+ )
+
+ expect(js).toBe(`const foo = "bar";`)
+ expect(css).toBe(undefined)
+ })
+
+ it(`should support includeMatchingCSS = "true" when matching file doesn't exist`, async () => {
+ readdir.mockResolvedValue([`file.js`])
+ fs.readFileSync.mockReset()
+ fs.readFileSync.mockImplementation((path, options) => {
+ if (path === `file.js`) {
+ return `const foo = "bar";`
+ } else {
+ throwFileNotFoundErr(path)
+ }
+ return null
+ })
+
+ await createPages(createPagesParams, {
+ includeMatchingCSS: true,
+ })
+
+ const { css, js } = JSON.parse(
+ createPage.mock.calls[0][0].context.payload
+ )
+
+ expect(js).toBe(`const foo = "bar";`)
+ expect(css).toBe(undefined)
+ })
+
+ it(`should support includeMatchingCSS = "false" when matching file doesn't exist`, async () => {
+ readdir.mockResolvedValue([`file.js`])
+ fs.readFileSync.mockReset()
+ fs.readFileSync.mockImplementation((path, options) => {
+ if (path === `file.js`) {
+ return `const foo = "bar";`
+ } else {
+ throwFileNotFoundErr(path)
+ }
+ return null
+ })
+
+ await createPages(createPagesParams, {
+ includeMatchingCSS: false,
+ })
+
+ const { css, js } = JSON.parse(
+ createPage.mock.calls[0][0].context.payload
+ )
+
+ expect(js).toBe(`const foo = "bar";`)
+ expect(css).toBe(undefined)
+ })
})
})
diff --git a/packages/gatsby-remark-code-repls/src/__tests__/index.js b/packages/gatsby-remark-code-repls/src/__tests__/index.js
index 1256a10ee766f..21c254c299e5e 100644
--- a/packages/gatsby-remark-code-repls/src/__tests__/index.js
+++ b/packages/gatsby-remark-code-repls/src/__tests__/index.js
@@ -141,6 +141,21 @@ describe(`gatsby-remark-code-repls`, () => {
}
})
+ it(`supports includeMatchingCSS`, () => {
+ const markdownAST = remark.parse(
+ `[](${protocol}path/to/nested/file.js)`
+ )
+ const runPlugin = () =>
+ plugin(
+ { markdownAST },
+ {
+ directory: `examples`,
+ includeMatchingCSS: true,
+ }
+ )
+ expect(runPlugin).not.toThrow()
+ })
+
if (protocol === PROTOCOL_CODE_SANDBOX) {
it(`supports custom html config option for index html`, () => {
const markdownAST = remark.parse(
diff --git a/packages/gatsby-remark-code-repls/src/constants.js b/packages/gatsby-remark-code-repls/src/constants.js
index 6314b9561d555..a6ef0ea45a5ec 100644
--- a/packages/gatsby-remark-code-repls/src/constants.js
+++ b/packages/gatsby-remark-code-repls/src/constants.js
@@ -9,6 +9,7 @@ module.exports = {
OPTION_DEFAULT_REDIRECT_TEMPLATE_PATH: normalizePath(
join(__dirname, `default-redirect-template.js`)
),
+ OPTION_DEFAULT_INCLUDE_MATCHING_CSS: false,
PROTOCOL_BABEL: `babel://`,
PROTOCOL_CODEPEN: `codepen://`,
PROTOCOL_CODE_SANDBOX: `codesandbox://`,
diff --git a/packages/gatsby-remark-code-repls/src/gatsby-node.js b/packages/gatsby-remark-code-repls/src/gatsby-node.js
index a18e02fb333a7..69559a16302c5 100644
--- a/packages/gatsby-remark-code-repls/src/gatsby-node.js
+++ b/packages/gatsby-remark-code-repls/src/gatsby-node.js
@@ -9,6 +9,7 @@ const {
OPTION_DEFAULT_LINK_TEXT,
OPTION_DEFAULT_HTML,
OPTION_DEFAULT_REDIRECT_TEMPLATE_PATH,
+ OPTION_DEFAULT_INCLUDE_MATCHING_CSS,
} = require(`./constants`)
exports.createPages = async (
@@ -18,6 +19,7 @@ exports.createPages = async (
externals = [],
html = OPTION_DEFAULT_HTML,
redirectTemplate = OPTION_DEFAULT_REDIRECT_TEMPLATE_PATH,
+ includeMatchingCSS = OPTION_DEFAULT_INCLUDE_MATCHING_CSS,
} = {}
) => {
if (!directory.endsWith(`/`)) {
@@ -51,6 +53,18 @@ exports.createPages = async (
.replace(new RegExp(`^${directory}`), `redirect-to-codepen/`)
const code = fs.readFileSync(file, `utf8`)
+ let css
+ if (includeMatchingCSS === true) {
+ try {
+ css = fs.readFileSync(file.replace(extname(file), `.css`), `utf8`)
+ } catch (err) {
+ // If the file doesn't exist, we gracefully ignore the error
+ if (err.code !== `ENOENT`) {
+ throw err
+ }
+ }
+ }
+
// Codepen configuration.
// https://blog.codepen.io/documentation/api/prefill/
const action = `https://codepen.io/pen/define`
@@ -61,6 +75,7 @@ exports.createPages = async (
js_external: externals.join(`;`),
js_pre_processor: `babel`,
layout: `left`,
+ css,
})
createPage({