diff --git a/package.json b/package.json index 612a782..1d67617 100755 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "license": "MIT", "repository": "https://github.com/FormidableLabs/prism-react-renderer", "scripts": { + "postinstall": "pnpm run --filter generate-prism-languages generate", "build": "pnpm run --filter prism-react-renderer build", "build:watch": "pnpm run --filter prism-react-renderer build:watch" }, diff --git a/packages/generate-prism-languages/index.ts b/packages/generate-prism-languages/index.ts new file mode 100644 index 0000000..b59fb1d --- /dev/null +++ b/packages/generate-prism-languages/index.ts @@ -0,0 +1,104 @@ +import flowRight from "lodash.flowright" +import pc from "picocolors" +import { readFile, writeFile, access } from "node:fs/promises" +import { constants } from "node:fs" +import { join, dirname } from "node:path" +import { languages as prismLanguages } from "prismjs/components" +import uglify from "uglify-js" + +export const languagesToBundle = [ + "jsx", + "tsx", + "swift", + "kotlin", + "objectivec", + "rust", + "graphql", + "yaml", + "go", + "cpp", + "markdown", +] + +/** + * We need to disable typechecking on this generated file as it's just concatenating JS code + * that starts off assuming Prism lives in global scope. We also need to provide Prism as that + * gets passed into an iffe preventing us from needing to use global scope. + */ +const header = `// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck\nimport Prism from "prismjs"\n` +const prismPath = dirname(require.resolve("prismjs")) + +const readLanguageFile = async (language: string): Promise => { + const pathToLanguage = join(prismPath, `components/prism-${language}.js`) + await access(pathToLanguage, constants.R_OK) + const buffer = await readFile(pathToLanguage, { encoding: "utf-8" }) + return buffer.toString() +} + +const strArrayFromUnknown = (input: unknown) => (array: string[]) => { + if (typeof input === "string") array.push(input) + else if (Array.isArray(input)) array = array.concat(input) + return array +} + +const main = async () => { + let output = "" + const bundledLanguages = new Set() + const orderBundled = new Set() + const outputPath = join( + __dirname, + "../prism-react-renderer/src/prism-langs.ts" + ) + + const addLanguageToOutput = async (language?: string) => { + if (bundledLanguages.has(language)) { + return + } + if (language == null || prismLanguages[language] == null) { + return + } + bundledLanguages.add(language) + + /** + * We need to ensure any language dependencies are bundled first + */ + const prismLang = prismLanguages[language] + const deps = flowRight( + strArrayFromUnknown(prismLang.require), + strArrayFromUnknown(prismLang.optional) + )([]) + const peerDeps = strArrayFromUnknown(prismLang.peerDependencies)([]) + + for await (const language of deps) { + await addLanguageToOutput(language) + } + + output += await readLanguageFile(language) + orderBundled.add(language) + + for await (const language of peerDeps) { + await addLanguageToOutput(language) + } + } + + for await (const language of languagesToBundle) { + await addLanguageToOutput(language) + } + + console.info( + pc.bold(pc.bgYellow(pc.black("Formidable Prism React Renderer"))), + "\n" + ) + console.info( + pc.bgBlue(`Generated TypeScript output at:`), + pc.cyan(outputPath) + ) + console.info( + pc.bgGreen(`Included language definitions in the following order:`), + Array.from(orderBundled.values()).join(", ") + ) + + await writeFile(outputPath, header + uglify.minify(output).code) +} + +main() diff --git a/packages/generate-prism-languages/package.json b/packages/generate-prism-languages/package.json new file mode 100644 index 0000000..d506aed --- /dev/null +++ b/packages/generate-prism-languages/package.json @@ -0,0 +1,23 @@ +{ + "name": "generate-prism-languages", + "private": true, + "scripts": { + "generate": "ts-node ./index.ts" + }, + "peerDependencies": { + "react": ">=16.0.0" + }, + "devDependencies": { + "@types/lodash.flowright": "^3.5.7", + "@types/node": "^18.15.11", + "@types/prismjs": "^1.26.0", + "@types/uglify-js": "^3.17.1", + "picocolors": "^1.0.0", + "prismjs": "*", + "ts-node": "^10.9.1", + "tslib": "^2.5.0", + "typescript": "*", + "uglify-js": "^3.17.4", + "lodash.flowright": "^3.5.0" + } +} diff --git a/packages/generate-prism-languages/tsconfig.json b/packages/generate-prism-languages/tsconfig.json new file mode 100644 index 0000000..9e93e21 --- /dev/null +++ b/packages/generate-prism-languages/tsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "esModuleInterop": true, + } +} \ No newline at end of file diff --git a/packages/prism-react-renderer/package.json b/packages/prism-react-renderer/package.json index f253cc1..885d04f 100755 --- a/packages/prism-react-renderer/package.json +++ b/packages/prism-react-renderer/package.json @@ -15,15 +15,12 @@ "themes" ], "scripts": { - "postinstall": "pnpm run build:languages", "prebuild": "patch-package", "build": "tsup", "build:watch": "tsup --watch", "test": "jest", "lint": "eslint 'src/**/*.{js,jsx,ts,tsx}'", - "format": "prettier --write 'src/**/*.js'", - "prepublishOnly": "run-p flow build", - "build:languages": "ts-node ./scripts/generate-base-languages.ts" + "format": "prettier --write 'src/**/*.js'" }, "peerDependencies": { "react": ">=16.0.0" diff --git a/packages/prism-react-renderer/scripts/generate-base-languages.ts b/packages/prism-react-renderer/scripts/generate-base-languages.ts deleted file mode 100644 index ee77c4d..0000000 --- a/packages/prism-react-renderer/scripts/generate-base-languages.ts +++ /dev/null @@ -1,100 +0,0 @@ -const { readFileSync, writeFileSync } = require("fs") -const { dirname, join } = require("path") -const { languages } = require("prismjs/components") -const prismPath = dirname(require.resolve("prismjs")) - -const baseLanguages = { - markup: true, - bash: true, - c: true, - clike: true, - javascript: true, - jsx: true, - graphql: true, - markdown: true, - css: true, - typescript: true, - tsx: true, - swift: true, - objectivec: true, - rust: true, -} as const - -let output = ` -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-nocheck\n -import Prism from "prismjs"\n -` - -type LanguageEntry = { - title: string - require: string | string[] - optional: string | string[] - alias: string - owner: string - peerDependencies?: never[] -} - -const addLanguageToOutput = (language: string) => { - const pathToLanguage = "components/prism-" + language - const fullPath = join(prismPath, pathToLanguage + ".js") - const contents = readFileSync(fullPath, "utf8") - output += contents -} - -const visitedLanguages: Record = {} - -const visitLanguage = (language: string, langEntry: LanguageEntry) => { - // Mark language as visited or return if it was - if (visitedLanguages[language]) { - return - } else { - visitedLanguages[language] = true - } - - // Required + optional dependencies come before the actual language - const dependencies = ([] as string[]) - .concat(langEntry.require) - .concat(langEntry.optional) - .filter(f => f) - - if (dependencies.length > 0) { - dependencies.forEach(x => { - if (baseLanguages[x as keyof typeof baseLanguages]) { - if (languages[x]) { - visitLanguage(x, languages[x]) - } else { - console.warn("[prismjs/components]: Language", x, "does not exist!") - } - } - }) - } - - // Add current language to output - addLanguageToOutput(language) - - // Peer dependencies come after the actual language - const peerDependencies = ([] as string[]) - .concat(langEntry.peerDependencies || "") - .filter(f => f) - - if (Array.isArray(peerDependencies)) { - peerDependencies.forEach(x => { - if (languages[x]) { - visitLanguage(x, languages[x]) - } else { - console.warn("[prismjs/components]: Language", x, "does not exist!") - } - }) - } -} - -Object.keys(baseLanguages).forEach(language => { - visitLanguage(language, languages[language]) -}) - -try { - writeFileSync("./src/prism-langs.ts", output) -} catch (err) { - console.error(err) -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8c25a35..a53dc49 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -91,6 +91,33 @@ importers: typescript: 4.9.5 vite: 4.2.1 + packages/generate-prism-languages: + specifiers: + '@types/lodash.flowright': ^3.5.7 + '@types/node': ^18.15.11 + '@types/prismjs': ^1.26.0 + '@types/uglify-js': ^3.17.1 + lodash.flowright: ^3.5.0 + picocolors: ^1.0.0 + prismjs: '*' + ts-node: ^10.9.1 + tslib: ^2.5.0 + typescript: '*' + uglify-js: ^3.17.4 + dependencies: + lodash.flowright: 3.5.0 + devDependencies: + '@types/lodash.flowright': 3.5.7 + '@types/node': 18.15.11 + '@types/prismjs': 1.26.0 + '@types/uglify-js': 3.17.1 + picocolors: 1.0.0 + prismjs: 1.26.0 + ts-node: 10.9.1_bhanhq442dy43ncydsavgi4jfi + tslib: 2.5.0 + typescript: 5.0.4 + uglify-js: 3.17.4 + packages/prism-react-renderer: specifiers: '@babel/core': '*' @@ -2363,6 +2390,16 @@ packages: resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} dev: true + /@types/lodash.flowright/3.5.7: + resolution: {integrity: sha512-stlmNXfnJr1yI7+xwMkU1IQxWS59JMw83MejH13y0STHv0iVBzOrw6KcxSAZQjSkAtoRGk3T0aWAt67vrF7V0Q==} + dependencies: + '@types/lodash': 4.14.194 + dev: true + + /@types/lodash/4.14.194: + resolution: {integrity: sha512-r22s9tAS7imvBt2lyHC9B8AGwWnXaYb1tY09oyLkXDs4vArpYJzw09nj8MLx5VfciBPGIb+ZwG0ssYnEPJxn/g==} + dev: true + /@types/node/18.15.11: resolution: {integrity: sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==} dev: true @@ -2419,6 +2456,12 @@ packages: resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==} dev: true + /@types/uglify-js/3.17.1: + resolution: {integrity: sha512-GkewRA4i5oXacU/n4MA9+bLgt5/L3F1mKrYvFGm7r2ouLXhRKjuWwo9XHNnbx6WF3vlGW21S3fCvgqxvxXXc5g==} + dependencies: + source-map: 0.6.1 + dev: true + /@types/yargs-parser/21.0.0: resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==} dev: true @@ -5540,6 +5583,10 @@ packages: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} dev: true + /lodash.flowright/3.5.0: + resolution: {integrity: sha512-YxTYuodkvyINbDInmFcGGvkQwoAuoGUYosqstRTr5eq63GQt7WQ2xFU0wG1UfdbKYPwevd3zWDd6ybEE2g6qvA==} + dev: false + /lodash.merge/4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true @@ -7164,6 +7211,12 @@ packages: hasBin: true dev: true + /uglify-js/3.17.4: + resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==} + engines: {node: '>=0.8.0'} + hasBin: true + dev: true + /unbox-primitive/1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} dependencies: