diff --git a/packages/gatsby-remark-prismjs/scripts/get-prism-language-dependencies.js b/packages/gatsby-remark-prismjs/scripts/get-prism-language-dependencies.js new file mode 100644 index 0000000000000..1f4897fbe38dd --- /dev/null +++ b/packages/gatsby-remark-prismjs/scripts/get-prism-language-dependencies.js @@ -0,0 +1,63 @@ +const https = require(`https`) +const execSync = require(`child_process`).execSync +const fs = require(`fs`) +const path = require(`path`) + +const fileSavePath = path.resolve(__dirname, `../src/prism-language-dependencies.js`) + +function getVersion() { + const prismInfo = JSON.parse(execSync(`npm ls prismjs --json`)) + return prismInfo.dependencies.prismjs.version +} + +function processData(data, url) { + // `components.js`: + // var components = { + // "core": { ... }, + // "languages": { ... }, + // ... + // } + eval(data) + if (typeof components === `undefined`) { + throw new Error(`The content structure of \`components.js\` seems changed.`) + } + + // eslint-disable-next-line no-undef + const languages = components.languages + const content = `// From ${JSON.stringify(url)} +module.exports = ${JSON.stringify(languages, null, 2)} +` + + fs.writeFileSync(fileSavePath, content, `utf8`) +} + +function requestData() { + const version = getVersion() + const url = `https://raw.githubusercontent.com/PrismJS/prism/v${version}/components.js` + + https + .get(url, res => { + if (res.statusCode !== 200) { + throw new Error(`Request Failed.\nRequest URL: ${url}\nStatus Code: ${res.statusCode}`) + } + + res.setEncoding(`utf8`) + let rawData = `` + res.on(`data`, chunk => { + rawData += chunk + }) + + res.on(`end`, () => { + try { + processData(rawData, url) + } catch (e) { + console.error(e.message) + } + }) + }) + .on(`error`, e => { + console.error(e.message) + }) +} + +requestData() diff --git a/packages/gatsby-remark-prismjs/src/__tests__/__snapshots__/highlight-code.js.snap b/packages/gatsby-remark-prismjs/src/__tests__/__snapshots__/highlight-code.js.snap index f52008094748b..967a967dde641 100644 --- a/packages/gatsby-remark-prismjs/src/__tests__/__snapshots__/highlight-code.js.snap +++ b/packages/gatsby-remark-prismjs/src/__tests__/__snapshots__/highlight-code.js.snap @@ -1,6 +1,15 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`highlight code and lines with PrismJS highlights code 1`] = ` +exports[`highlight code and lines with PrismJS highlights code for language cpp 1`] = ` +" +int sum(a, b) { + return a + b; +} + +" +`; + +exports[`highlight code and lines with PrismJS highlights code for language jsx 1`] = ` " import React from \\"react\\" diff --git a/packages/gatsby-remark-prismjs/src/__tests__/highlight-code.js b/packages/gatsby-remark-prismjs/src/__tests__/highlight-code.js index 9e67f3389e4ec..69c59f8c7d6ee 100644 --- a/packages/gatsby-remark-prismjs/src/__tests__/highlight-code.js +++ b/packages/gatsby-remark-prismjs/src/__tests__/highlight-code.js @@ -1,5 +1,4 @@ const parseLineNumberRange = require(`../parse-line-number-range`) -const highlightCode = require(`../highlight-code`) describe(`highlight code and lines with PrismJS`, () => { it(`parses numeric ranges from the languages variable`, () => { @@ -46,10 +45,30 @@ describe(`highlight code and lines with PrismJS`, () => { expect(parseLineNumberRange(`jsx`).splitLanguage).toEqual(`jsx`) }) - it(`highlights code`, () => { - const language = `jsx` - const lineNumbersHighlight = [12, 13, 15] - const code = ` + describe(`highlights code for language`, () => { + afterEach(() => { + jest.resetModules() + }) + + it(`cpp`, () => { + const highlightCode = require(`../highlight-code`) + const language = `cpp` + const lineNumbersHighlight = [1, 2] + const code = ` +int sum(a, b) { + return a + b; +} +` + expect( + highlightCode(language, code, lineNumbersHighlight) + ).toMatchSnapshot() + }) + + it(`jsx`, () => { + const highlightCode = require(`../highlight-code`) + const language = `jsx` + const lineNumbersHighlight = [12, 13, 15] + const code = ` import React from "react" class Counter extends React.Component { @@ -76,8 +95,9 @@ class Counter extends React.Component { export default Counter ` - expect( - highlightCode(language, code, lineNumbersHighlight) - ).toMatchSnapshot() + expect( + highlightCode(language, code, lineNumbersHighlight) + ).toMatchSnapshot() + }) }) }) diff --git a/packages/gatsby-remark-prismjs/src/__tests__/load-prism-language.js b/packages/gatsby-remark-prismjs/src/__tests__/load-prism-language.js new file mode 100644 index 0000000000000..b8f7b30496201 --- /dev/null +++ b/packages/gatsby-remark-prismjs/src/__tests__/load-prism-language.js @@ -0,0 +1,44 @@ +const loadPrismLanguage = require(`../load-prism-language`) + +describe(`load prism language`, () => { + afterEach(() => { + jest.resetModules() + }) + + it(`throw if language not support`, () => { + expect(() => loadPrismLanguage(`imnotalanguage`)).toThrow( + `Prism doesn't support language 'imnotalanguage'.` + ) + }) + + it(`load supported language`, () => { + const language = `c` + const Prism = require(`prismjs`) + + const languagesBeforeLoaded = Object.keys(Prism.languages) + expect(Prism.languages).not.toHaveProperty(language) + + loadPrismLanguage(language) + + const languagesAfterLoaded = Object.keys(Prism.languages) + expect(Prism.languages).toHaveProperty(language) + expect(languagesAfterLoaded.length).toBe(languagesBeforeLoaded.length + 1) + }) + + it(`also load the required language`, () => { + const language = `cpp` + const requiredLanguage = `c` + const Prism = require(`prismjs`) + + const languagesBeforeLoaded = Object.keys(Prism.languages) + expect(Prism.languages).not.toHaveProperty(language) + expect(Prism.languages).not.toHaveProperty(requiredLanguage) + + loadPrismLanguage(language) + + const languagesAfterLoaded = Object.keys(Prism.languages) + expect(Prism.languages).toHaveProperty(language) + expect(Prism.languages).toHaveProperty(requiredLanguage) + expect(languagesAfterLoaded.length).toBe(languagesBeforeLoaded.length + 2) + }) +}) diff --git a/packages/gatsby-remark-prismjs/src/__tests__/parse-line-number-range.js b/packages/gatsby-remark-prismjs/src/__tests__/parse-line-number-range.js index 25492d06d437e..de39d52c8b554 100644 --- a/packages/gatsby-remark-prismjs/src/__tests__/parse-line-number-range.js +++ b/packages/gatsby-remark-prismjs/src/__tests__/parse-line-number-range.js @@ -1,6 +1,6 @@ const parseLineNumberRange = require(`../parse-line-number-range`) -describe(`parses numeric ranges from the languges markdown code directive`, () => { +describe(`parses numeric ranges from the languages markdown code directive`, () => { it(`parses numeric ranges from the languages variable`, () => { expect(parseLineNumberRange(`jsx{1,5,7-8}`).highlightLines).toEqual([ 1, diff --git a/packages/gatsby-remark-prismjs/src/highlight-code.js b/packages/gatsby-remark-prismjs/src/highlight-code.js index 61ed747fc940c..f2ec3f7e955c4 100644 --- a/packages/gatsby-remark-prismjs/src/highlight-code.js +++ b/packages/gatsby-remark-prismjs/src/highlight-code.js @@ -1,11 +1,13 @@ const Prism = require(`prismjs`) const _ = require(`lodash`) +const loadPrismLanguage = require(`./load-prism-language`) + module.exports = (language, code, lineNumbersHighlight = []) => { // (Try to) load languages on demand. if (!Prism.languages[language]) { try { - require(`prismjs/components/prism-${language}.js`) + loadPrismLanguage(language) } catch (e) { // Language wasn't loaded so let's bail. return code diff --git a/packages/gatsby-remark-prismjs/src/load-prism-language.js b/packages/gatsby-remark-prismjs/src/load-prism-language.js new file mode 100644 index 0000000000000..af5b326443d13 --- /dev/null +++ b/packages/gatsby-remark-prismjs/src/load-prism-language.js @@ -0,0 +1,31 @@ +const Prism = require(`prismjs`) + +const languageDependencies = require(`./prism-language-dependencies`) + +module.exports = function loadPrismLanguage(language) { + if (Prism.languages[language]) { + // Don't load already loaded language + return + } + + const languageData = languageDependencies[language] + if (!languageData) { + throw new Error(`Prism doesn't support language '${language}'.`) + } + + if (languageData.option === `default`) { + // Default language has already been loaded by Prism + return + } + + if (languageData.require) { + // Load the required language first + if (Array.isArray(languageData.require)) { + languageData.require.forEach(loadPrismLanguage) + } else { + loadPrismLanguage(languageData.require) + } + } + + require(`prismjs/components/prism-${language}.js`) +} diff --git a/packages/gatsby-remark-prismjs/src/prism-language-dependencies.js b/packages/gatsby-remark-prismjs/src/prism-language-dependencies.js new file mode 100644 index 0000000000000..70b803a09e564 --- /dev/null +++ b/packages/gatsby-remark-prismjs/src/prism-language-dependencies.js @@ -0,0 +1,543 @@ +// From "https://raw.githubusercontent.com/PrismJS/prism/v1.6.0/components.js" +module.exports = { + "meta": { + "path": "components/prism-{id}", + "noCSS": true, + "examplesPath": "examples/prism-{id}", + "addCheckAll": true + }, + "markup": { + "title": "Markup", + "aliasTitles": { + "html": "HTML", + "xml": "XML", + "svg": "SVG", + "mathml": "MathML" + }, + "option": "default" + }, + "css": { + "title": "CSS", + "option": "default" + }, + "clike": { + "title": "C-like", + "option": "default" + }, + "javascript": { + "title": "JavaScript", + "option": "default", + "require": "clike" + }, + "abap": { + "title": "ABAP", + "owner": "dellagustin" + }, + "actionscript": { + "title": "ActionScript", + "require": "javascript", + "owner": "Golmote" + }, + "ada": { + "title": "Ada", + "owner": "Lucretia" + }, + "apacheconf": { + "title": "Apache Configuration", + "owner": "GuiTeK" + }, + "apl": { + "title": "APL", + "owner": "ngn" + }, + "applescript": { + "title": "AppleScript", + "owner": "Golmote" + }, + "asciidoc": { + "title": "AsciiDoc", + "owner": "Golmote" + }, + "aspnet": { + "title": "ASP.NET (C#)", + "require": "markup", + "owner": "nauzilus" + }, + "autoit": { + "title": "AutoIt", + "owner": "Golmote" + }, + "autohotkey": { + "title": "AutoHotkey", + "owner": "aviaryan" + }, + "bash": { + "title": "Bash", + "owner": "zeitgeist87" + }, + "basic": { + "title": "BASIC", + "owner": "Golmote" + }, + "batch": { + "title": "Batch", + "owner": "Golmote" + }, + "bison": { + "title": "Bison", + "require": "c", + "owner": "Golmote" + }, + "brainfuck": { + "title": "Brainfuck", + "owner": "Golmote" + }, + "bro": { + "title": "Bro", + "owner": "wayward710" + }, + "c": { + "title": "C", + "require": "clike", + "owner": "zeitgeist87" + }, + "csharp": { + "title": "C#", + "require": "clike", + "owner": "mvalipour" + }, + "cpp": { + "title": "C++", + "require": "c", + "owner": "zeitgeist87" + }, + "coffeescript": { + "title": "CoffeeScript", + "require": "javascript", + "owner": "R-osey" + }, + "crystal": { + "title": "Crystal", + "require": "ruby", + "owner": "MakeNowJust" + }, + "css-extras": { + "title": "CSS Extras", + "require": "css", + "owner": "milesj" + }, + "d": { + "title": "D", + "require": "clike", + "owner": "Golmote" + }, + "dart": { + "title": "Dart", + "require": "clike", + "owner": "Golmote" + }, + "diff": { + "title": "Diff", + "owner": "uranusjr" + }, + "docker": { + "title": "Docker", + "owner": "JustinBeckwith" + }, + "eiffel": { + "title": "Eiffel", + "owner": "Conaclos" + }, + "elixir": { + "title": "Elixir", + "owner": "Golmote" + }, + "erlang": { + "title": "Erlang", + "owner": "Golmote" + }, + "fsharp": { + "title": "F#", + "require": "clike", + "owner": "simonreynolds7" + }, + "fortran": { + "title": "Fortran", + "owner": "Golmote" + }, + "gherkin": { + "title": "Gherkin", + "owner": "hason" + }, + "git": { + "title": "Git", + "owner": "lgiraudel" + }, + "glsl": { + "title": "GLSL", + "require": "clike", + "owner": "Golmote" + }, + "go": { + "title": "Go", + "require": "clike", + "owner": "arnehormann" + }, + "graphql": { + "title": "GraphQL", + "owner": "Golmote" + }, + "groovy": { + "title": "Groovy", + "require": "clike", + "owner": "robfletcher" + }, + "haml": { + "title": "Haml", + "require": "ruby", + "owner": "Golmote" + }, + "handlebars": { + "title": "Handlebars", + "require": "markup", + "owner": "Golmote" + }, + "haskell": { + "title": "Haskell", + "owner": "bholst" + }, + "haxe": { + "title": "Haxe", + "require": "clike", + "owner": "Golmote" + }, + "http": { + "title": "HTTP", + "owner": "danielgtaylor" + }, + "icon": { + "title": "Icon", + "owner": "Golmote" + }, + "inform7": { + "title": "Inform 7", + "owner": "Golmote" + }, + "ini": { + "title": "Ini", + "owner": "aviaryan" + }, + "j": { + "title": "J", + "owner": "Golmote" + }, + "jade": { + "title": "Jade", + "require": "javascript", + "owner": "Golmote" + }, + "java": { + "title": "Java", + "require": "clike", + "owner": "sherblot" + }, + "jolie": { + "title": "Jolie", + "require": "clike", + "owner": "thesave" + }, + "json": { + "title": "JSON", + "owner": "CupOfTea696" + }, + "julia": { + "title": "Julia", + "owner": "cdagnino" + }, + "keyman": { + "title": "Keyman", + "owner": "mcdurdin" + }, + "kotlin": { + "title": "Kotlin", + "require": "clike", + "owner": "Golmote" + }, + "latex": { + "title": "LaTeX", + "owner": "japborst" + }, + "less": { + "title": "Less", + "require": "css", + "owner": "Golmote" + }, + "livescript": { + "title": "LiveScript", + "owner": "Golmote" + }, + "lolcode": { + "title": "LOLCODE", + "owner": "Golmote" + }, + "lua": { + "title": "Lua", + "owner": "Golmote" + }, + "makefile": { + "title": "Makefile", + "owner": "Golmote" + }, + "markdown": { + "title": "Markdown", + "require": "markup", + "owner": "Golmote" + }, + "matlab": { + "title": "MATLAB", + "owner": "Golmote" + }, + "mel": { + "title": "MEL", + "owner": "Golmote" + }, + "mizar": { + "title": "Mizar", + "owner": "Golmote" + }, + "monkey": { + "title": "Monkey", + "owner": "Golmote" + }, + "nasm": { + "title": "NASM", + "owner": "rbmj" + }, + "nginx": { + "title": "nginx", + "owner": "westonganger", + "require": "clike" + }, + "nim": { + "title": "Nim", + "owner": "Golmote" + }, + "nix": { + "title": "Nix", + "owner": "Golmote" + }, + "nsis": { + "title": "NSIS", + "owner": "idleberg" + }, + "objectivec": { + "title": "Objective-C", + "require": "c", + "owner": "uranusjr" + }, + "ocaml": { + "title": "OCaml", + "owner": "Golmote" + }, + "oz": { + "title": "Oz", + "owner": "Golmote" + }, + "parigp": { + "title": "PARI/GP", + "owner": "Golmote" + }, + "parser": { + "title": "Parser", + "require": "markup", + "owner": "Golmote" + }, + "pascal": { + "title": "Pascal", + "owner": "Golmote" + }, + "perl": { + "title": "Perl", + "owner": "Golmote" + }, + "php": { + "title": "PHP", + "require": "clike", + "owner": "milesj" + }, + "php-extras": { + "title": "PHP Extras", + "require": "php", + "owner": "milesj" + }, + "powershell": { + "title": "PowerShell", + "owner": "nauzilus" + }, + "processing": { + "title": "Processing", + "require": "clike", + "owner": "Golmote" + }, + "prolog": { + "title": "Prolog", + "owner": "Golmote" + }, + "properties": { + "title": ".properties", + "owner": "Golmote" + }, + "protobuf": { + "title": "Protocol Buffers", + "require": "clike", + "owner": "just-boris" + }, + "puppet": { + "title": "Puppet", + "owner": "Golmote" + }, + "pure": { + "title": "Pure", + "owner": "Golmote" + }, + "python": { + "title": "Python", + "owner": "multipetros" + }, + "q": { + "title": "Q", + "owner": "Golmote" + }, + "qore": { + "title": "Qore", + "require": "clike", + "owner": "temnroegg" + }, + "r": { + "title": "R", + "owner": "Golmote" + }, + "jsx": { + "title": "React JSX", + "require": [ + "markup", + "javascript" + ], + "owner": "vkbansal" + }, + "reason": { + "title": "Reason", + "require": "clike", + "owner": "Golmote" + }, + "rest": { + "title": "reST (reStructuredText)", + "owner": "Golmote" + }, + "rip": { + "title": "Rip", + "owner": "ravinggenius" + }, + "roboconf": { + "title": "Roboconf", + "owner": "Golmote" + }, + "ruby": { + "title": "Ruby", + "require": "clike", + "owner": "samflores" + }, + "rust": { + "title": "Rust", + "owner": "Golmote" + }, + "sas": { + "title": "SAS", + "owner": "Golmote" + }, + "sass": { + "title": "Sass (Sass)", + "require": "css", + "owner": "Golmote" + }, + "scss": { + "title": "Sass (Scss)", + "require": "css", + "owner": "MoOx" + }, + "scala": { + "title": "Scala", + "require": "java", + "owner": "jozic" + }, + "scheme": { + "title": "Scheme", + "owner": "bacchus123" + }, + "smalltalk": { + "title": "Smalltalk", + "owner": "Golmote" + }, + "smarty": { + "title": "Smarty", + "require": "markup", + "owner": "Golmote" + }, + "sql": { + "title": "SQL", + "owner": "multipetros" + }, + "stylus": { + "title": "Stylus", + "owner": "vkbansal" + }, + "swift": { + "title": "Swift", + "require": "clike", + "owner": "chrischares" + }, + "tcl": { + "title": "Tcl", + "owner": "PeterChaplin" + }, + "textile": { + "title": "Textile", + "require": "markup", + "owner": "Golmote" + }, + "twig": { + "title": "Twig", + "require": "markup", + "owner": "brandonkelly" + }, + "typescript": { + "title": "TypeScript", + "require": "javascript", + "owner": "vkbansal" + }, + "verilog": { + "title": "Verilog", + "owner": "a-rey" + }, + "vhdl": { + "title": "VHDL", + "owner": "a-rey" + }, + "vim": { + "title": "vim", + "owner": "westonganger" + }, + "wiki": { + "title": "Wiki markup", + "require": "markup", + "owner": "Golmote" + }, + "xojo": { + "title": "Xojo (REALbasic)", + "owner": "Golmote" + }, + "yaml": { + "title": "YAML", + "owner": "hason" + } +}