Skip to content

Commit 1b8d93e

Browse files
deblasiswardpeet
authored andcommitted
feat(gatsby-remark-code-repls): include matching css option (CodePen) (#12110)
<!-- Have any questions? Check out the contributing docs at https://gatsby.app/contribute, or ask in this Pull Request and a Gatsby maintainer will be happy to help :) --> ## Description Currently it's possible to upload multiple files only by using CodeSandbox. This PR adds an option that allows adding CSS files to CodePen examples by setting the `css` property in the `payload` object. Ref: https://blog.codepen.io/documentation/api/prefill/ Matching CSS means that if the user is linking to `example.js` and the option is set to true, if `example.css` is present in the same directory, its content is sent to CodePen. - The default behaviour is left untouched - If the option is set to `true` and the matched file doesn't exist, nothing changes - tests included ## Related Issues <!-- Link to the issue that is fixed by this PR (if there is one) e.g. Fixes #1234, Addresses #1234, Related to #1234, etc. -->
1 parent 900454e commit 1b8d93e

File tree

5 files changed

+144
-0
lines changed

5 files changed

+144
-0
lines changed

packages/gatsby-remark-code-repls/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,13 @@ specified examples directory. (This will avoid broken links at runtime.)
163163
// Note that if a target is specified, "noreferrer" will also be added.
164164
// eg <a href="..." target="_blank" rel="noreferrer">...</a>
165165
target: '_blank',
166+
167+
// Include CSS with matching name.
168+
// This option only applies to REPLs that support it (eg Codepen).
169+
// If set to `true`, when specifying `file1.js` as example file,
170+
// it will try to inject the CSS in `file1.css` if the file exists,
171+
// otherwise the default behaviour is preserved
172+
includeMatchingCSS: false,
166173
},
167174
},
168175
```

packages/gatsby-remark-code-repls/src/__tests__/gatsby-node.js

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ const createPagesParams = {
3030
reporter,
3131
}
3232

33+
const throwFileNotFoundErr = path => {
34+
const err = new Error(`no such file or directory '${path}'`)
35+
err.code = `ENOENT`
36+
throw err
37+
}
38+
3339
describe(`gatsby-remark-code-repls`, () => {
3440
beforeEach(() => {
3541
fs.existsSync.mockReset()
@@ -198,5 +204,105 @@ describe(`gatsby-remark-code-repls`, () => {
198204

199205
expect(html).toBe(`<span id="foo"></span>`)
200206
})
207+
208+
it(`should support includeMatchingCSS = "true" when matching file exists`, async () => {
209+
readdir.mockResolvedValue([`file.js`, `file.css`])
210+
fs.readFileSync.mockReset()
211+
fs.readFileSync.mockImplementation((path, options) => {
212+
if (path === `file.js`) {
213+
return `const foo = "bar";`
214+
} else if (path === `file.css`) {
215+
return `html { color: red; }`
216+
} else {
217+
throwFileNotFoundErr(path)
218+
}
219+
return null
220+
})
221+
222+
await createPages(createPagesParams, {
223+
includeMatchingCSS: true,
224+
})
225+
226+
const { css, js } = JSON.parse(
227+
createPage.mock.calls[0][0].context.payload
228+
)
229+
230+
expect(js).toBe(`const foo = "bar";`)
231+
expect(css).toBe(`html { color: red; }`)
232+
})
233+
234+
it(`should support includeMatchingCSS = "false" when matching file exists`, async () => {
235+
readdir.mockResolvedValue([`file.js`, `file.css`])
236+
fs.readFileSync.mockReset()
237+
fs.readFileSync.mockImplementation((path, options) => {
238+
if (path === `file.js`) {
239+
return `const foo = "bar";`
240+
} else if (path === `file.css`) {
241+
return `html { color: red; }`
242+
} else {
243+
throwFileNotFoundErr(path)
244+
}
245+
return null
246+
})
247+
248+
await createPages(createPagesParams, {
249+
includeMatchingCSS: false,
250+
})
251+
252+
const { css, js } = JSON.parse(
253+
createPage.mock.calls[0][0].context.payload
254+
)
255+
256+
expect(js).toBe(`const foo = "bar";`)
257+
expect(css).toBe(undefined)
258+
})
259+
260+
it(`should support includeMatchingCSS = "true" when matching file doesn't exist`, async () => {
261+
readdir.mockResolvedValue([`file.js`])
262+
fs.readFileSync.mockReset()
263+
fs.readFileSync.mockImplementation((path, options) => {
264+
if (path === `file.js`) {
265+
return `const foo = "bar";`
266+
} else {
267+
throwFileNotFoundErr(path)
268+
}
269+
return null
270+
})
271+
272+
await createPages(createPagesParams, {
273+
includeMatchingCSS: true,
274+
})
275+
276+
const { css, js } = JSON.parse(
277+
createPage.mock.calls[0][0].context.payload
278+
)
279+
280+
expect(js).toBe(`const foo = "bar";`)
281+
expect(css).toBe(undefined)
282+
})
283+
284+
it(`should support includeMatchingCSS = "false" when matching file doesn't exist`, async () => {
285+
readdir.mockResolvedValue([`file.js`])
286+
fs.readFileSync.mockReset()
287+
fs.readFileSync.mockImplementation((path, options) => {
288+
if (path === `file.js`) {
289+
return `const foo = "bar";`
290+
} else {
291+
throwFileNotFoundErr(path)
292+
}
293+
return null
294+
})
295+
296+
await createPages(createPagesParams, {
297+
includeMatchingCSS: false,
298+
})
299+
300+
const { css, js } = JSON.parse(
301+
createPage.mock.calls[0][0].context.payload
302+
)
303+
304+
expect(js).toBe(`const foo = "bar";`)
305+
expect(css).toBe(undefined)
306+
})
201307
})
202308
})

packages/gatsby-remark-code-repls/src/__tests__/index.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,21 @@ describe(`gatsby-remark-code-repls`, () => {
141141
}
142142
})
143143

144+
it(`supports includeMatchingCSS`, () => {
145+
const markdownAST = remark.parse(
146+
`[](${protocol}path/to/nested/file.js)`
147+
)
148+
const runPlugin = () =>
149+
plugin(
150+
{ markdownAST },
151+
{
152+
directory: `examples`,
153+
includeMatchingCSS: true,
154+
}
155+
)
156+
expect(runPlugin).not.toThrow()
157+
})
158+
144159
if (protocol === PROTOCOL_CODE_SANDBOX) {
145160
it(`supports custom html config option for index html`, () => {
146161
const markdownAST = remark.parse(

packages/gatsby-remark-code-repls/src/constants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ module.exports = {
99
OPTION_DEFAULT_REDIRECT_TEMPLATE_PATH: normalizePath(
1010
join(__dirname, `default-redirect-template.js`)
1111
),
12+
OPTION_DEFAULT_INCLUDE_MATCHING_CSS: false,
1213
PROTOCOL_BABEL: `babel://`,
1314
PROTOCOL_CODEPEN: `codepen://`,
1415
PROTOCOL_CODE_SANDBOX: `codesandbox://`,

packages/gatsby-remark-code-repls/src/gatsby-node.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const {
99
OPTION_DEFAULT_LINK_TEXT,
1010
OPTION_DEFAULT_HTML,
1111
OPTION_DEFAULT_REDIRECT_TEMPLATE_PATH,
12+
OPTION_DEFAULT_INCLUDE_MATCHING_CSS,
1213
} = require(`./constants`)
1314

1415
exports.createPages = async (
@@ -18,6 +19,7 @@ exports.createPages = async (
1819
externals = [],
1920
html = OPTION_DEFAULT_HTML,
2021
redirectTemplate = OPTION_DEFAULT_REDIRECT_TEMPLATE_PATH,
22+
includeMatchingCSS = OPTION_DEFAULT_INCLUDE_MATCHING_CSS,
2123
} = {}
2224
) => {
2325
if (!directory.endsWith(`/`)) {
@@ -51,6 +53,18 @@ exports.createPages = async (
5153
.replace(new RegExp(`^${directory}`), `redirect-to-codepen/`)
5254
const code = fs.readFileSync(file, `utf8`)
5355

56+
let css
57+
if (includeMatchingCSS === true) {
58+
try {
59+
css = fs.readFileSync(file.replace(extname(file), `.css`), `utf8`)
60+
} catch (err) {
61+
// If the file doesn't exist, we gracefully ignore the error
62+
if (err.code !== `ENOENT`) {
63+
throw err
64+
}
65+
}
66+
}
67+
5468
// Codepen configuration.
5569
// https://blog.codepen.io/documentation/api/prefill/
5670
const action = `https://codepen.io/pen/define`
@@ -61,6 +75,7 @@ exports.createPages = async (
6175
js_external: externals.join(`;`),
6276
js_pre_processor: `babel`,
6377
layout: `left`,
78+
css,
6479
})
6580

6681
createPage({

0 commit comments

Comments
 (0)