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

feat(gatsby-remark-code-repls): include matching css option (CodePen) #12110

7 changes: 7 additions & 0 deletions packages/gatsby-remark-code-repls/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <a href="..." target="_blank" rel="noreferrer">...</a>
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,
},
},
```
106 changes: 106 additions & 0 deletions packages/gatsby-remark-code-repls/src/__tests__/gatsby-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -198,5 +204,105 @@ describe(`gatsby-remark-code-repls`, () => {

expect(html).toBe(`<span id="foo"></span>`)
})

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)
})
})
})
15 changes: 15 additions & 0 deletions packages/gatsby-remark-code-repls/src/__tests__/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
1 change: 1 addition & 0 deletions packages/gatsby-remark-code-repls/src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -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://`,
Expand Down
15 changes: 15 additions & 0 deletions packages/gatsby-remark-code-repls/src/gatsby-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -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(`/`)) {
Expand Down Expand Up @@ -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`
Expand All @@ -61,6 +75,7 @@ exports.createPages = async (
js_external: externals.join(`;`),
js_pre_processor: `babel`,
layout: `left`,
css,
})

createPage({
Expand Down