diff --git a/package.json b/package.json index 392bd90c4..a44093549 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,6 @@ "cspell": "5.19.0", "enquirer": "2.3.6", "esbuild": "0.14.25", - "esbuild-plugin-text-replace": "1.2.0", "esbuild-visualizer": "0.3.1", "eslint": "8.11.0", "eslint-config-prettier": "8.5.0", diff --git a/scripts/build/bundler.mjs b/scripts/build/bundler.mjs index c041dc55b..548191a88 100644 --- a/scripts/build/bundler.mjs +++ b/scripts/build/bundler.mjs @@ -4,11 +4,11 @@ import createEsmUtils from "esm-utils"; import * as babel from "@babel/core"; import esbuild from "esbuild"; import { NodeModulesPolyfillPlugin as esbuildPluginNodeModulePolyfills } from "@esbuild-plugins/node-modules-polyfill"; -import esbuildPluginTextReplace from "esbuild-plugin-text-replace"; import browserslistToEsbuild from "browserslist-to-esbuild"; import { PROJECT_ROOT, DIST_DIR } from "../utils/index.mjs"; import esbuildPluginEvaluate from "./esbuild-plugins/evaluate.mjs"; import esbuildPluginReplaceModule from "./esbuild-plugins/replace-module.mjs"; +import esbuildPluginReplaceText from "./esbuild-plugins/replace-text.mjs"; import esbuildPluginLicense from "./esbuild-plugins/license.mjs"; import esbuildPluginUmd from "./esbuild-plugins/umd.mjs"; import esbuildPluginVisualizer from "./esbuild-plugins/visualizer.mjs"; @@ -76,10 +76,14 @@ const bundledFiles = [ })); function* getEsbuildOptions(bundle, buildOptions) { - const replaceStrings = { + const replaceText = [ // `tslib` exports global variables - "createExporter(root": "createExporter({}", - }; + { + file: require.resolve("tslib"), + find: "factory(createExporter(root", + replacement: "factory(createExporter({}", + }, + ]; const define = { "process.env.PRETTIER_TARGET": JSON.stringify(bundle.target), @@ -89,7 +93,11 @@ function* getEsbuildOptions(bundle, buildOptions) { if (bundle.target === "universal") { // We can't reference `process` in UMD bundles and this is // an undocumented "feature" - replaceStrings["process.env.PRETTIER_DEBUG"] = "globalThis.PRETTIER_DEBUG"; + replaceText.push({ + file: "*", + find: "process.env.PRETTIER_DEBUG", + replacement: "globalThis.PRETTIER_DEBUG", + }); define.process = JSON.stringify({ env: {}, argv: [] }); @@ -148,10 +156,9 @@ function* getEsbuildOptions(bundle, buildOptions) { esbuildPluginEvaluate(), esbuildPluginReplaceModule({ ...replaceModule, ...bundle.replaceModule }), bundle.target === "universal" && esbuildPluginNodeModulePolyfills(), - esbuildPluginTextReplace({ - include: /\.[cm]?js$/, - // TODO[@fisker]: Use RegExp when possible - pattern: Object.entries({ ...replaceStrings, ...bundle.replace }), + esbuildPluginReplaceText({ + filter: /\.[cm]?js$/, + replacements: [...replaceText, ...(bundle.replaceText ?? [])], }), buildOptions.onLicenseFound && esbuildPluginLicense({ diff --git a/scripts/build/config.mjs b/scripts/build/config.mjs index 238569533..3b3d4e164 100644 --- a/scripts/build/config.mjs +++ b/scripts/build/config.mjs @@ -13,7 +13,7 @@ const { require } = createEsmUtils(import.meta); * @property {'core' | 'plugin'} type - it's a plugin bundle or core part of prettier * @property {string[]} external - array of paths that should not be included in the final bundle * @property {Object.} replaceModule - module replacement path or code - * @property {Object.} replace - map of strings to replace when processing the bundle + * @property {{file: string, find: string, replacement: string}[]} replaceText - Text replacements * @property {string[]} babelPlugins - babel plugins * @property {boolean?} minify - minify * @property {string[]?} esbuildTarget - ESBuild target @@ -30,68 +30,104 @@ const parsers = [ }, { input: "src/language-js/parse/flow.js", - replace: { + replaceText: [ // `flow-parser` use this for `globalThis`, can't work in strictMode - "(function(){return this}())": "(globalThis)", - }, + { + file: require.resolve("flow-parser"), + find: "(function(){return this}())", + replacement: "(globalThis)", + }, + ], }, { input: "src/language-js/parse/typescript.js", - replace: { - // `typescript/lib/typescript.js` expose extra global objects - // `TypeScript`, `toolsVersion`, `globalThis` - 'typeof process === "undefined" || process.browser': "false", - 'typeof globalThis === "object"': "true", + replaceText: [ // `@typescript-eslint/typescript-estree` v4 - 'require("globby")': "{}", - "extra.projects = prepareAndTransformProjects(": - "extra.projects = [] || prepareAndTransformProjects(", - "process.versions.node": JSON.stringify("999.999.999"), - "process.cwd()": JSON.stringify("/prettier-security-dirname-placeholder"), - // `rollup-plugin-polyfill-node` don't have polyfill for these modules - 'require("perf_hooks")': "{}", - 'require("inspector")': "{}", - "_fs.realpathSync.native": "_fs.realpathSync && _fs.realpathSync.native", - // Remove useless `ts.sys` - "ts.sys = ": "ts.sys = undefined && ", + { + file: "*", + find: 'require("globby")', + replacement: "{}", + }, + { + file: "*", + find: "extra.projects = prepareAndTransformProjects(", + replacement: "extra.projects = [] || prepareAndTransformProjects(", + }, + { + file: "*", + find: "process.versions.node", + replacement: JSON.stringify("999.999.999"), + }, + { + file: "*", + find: "process.cwd()", + replacement: JSON.stringify("/prettier-security-dirname-placeholder"), + }, + { + file: "*", + find: 'require("perf_hooks")', + replacement: "{}", + }, + { + file: "*", + find: 'require("inspector")', + replacement: "{}", + }, + { + file: "*", + find: "typescriptVersionIsAtLeast[version] = semverCheck(version);", + replacement: "typescriptVersionIsAtLeast[version] = true;", + }, - // Remove useless language service - "ts.realizeDiagnostics = ": "ts.realizeDiagnostics = undefined && ", - "ts.TypeScriptServicesFactory = ": - "ts.TypeScriptServicesFactory = undefined && ", - "var ShimBase = ": "var ShimBase = undefined && ", - "var TypeScriptServicesFactory = ": - "var TypeScriptServicesFactory = undefined && ", - "var LanguageServiceShimObject = ": - "var LanguageServiceShimObject = undefined && ", - "var CoreServicesShimHostAdapter = ": - "var CoreServicesShimHostAdapter = undefined && ", - "var LanguageServiceShimHostAdapter = ": - "var LanguageServiceShimHostAdapter = undefined && ", - "var ScriptSnapshotShimAdapter = ": - "var ScriptSnapshotShimAdapter = undefined && ", - "var ClassifierShimObject = ": "var ClassifierShimObject = undefined && ", - "var CoreServicesShimObject = ": - "var CoreServicesShimObject = undefined && ", - "function simpleForwardCall(": "0 && function simpleForwardCall(", - "function forwardJSONCall(": "0 && function forwardJSONCall(", - "function forwardCall(": "0 && function forwardCall(", - "function realizeDiagnostics(": "0 && function realizeDiagnostics(", - "function realizeDiagnostic(": "0 && function realizeDiagnostic(", - "function convertClassifications(": - "0 && function convertClassifications(", + ...Object.entries({ + // `typescript/lib/typescript.js` expose extra global objects + // `TypeScript`, `toolsVersion`, `globalThis` + 'typeof process === "undefined" || process.browser': "false", + 'typeof globalThis === "object"': "true", - // Dynamic `require()`s - "ts.sys && ts.sys.require": "false", - "require(etwModulePath)": "undefined", - 'require("source-map-support").install()': "", - "require(modulePath)": "undefined", - // `node-semver` can't work with `@rollup/plugin-commonjs>=19.0.0` - // https://github.com/rollup/plugins/issues/879 - // https://github.com/npm/node-semver/issues/381 - "typescriptVersionIsAtLeast[version] = semverCheck(version);": - "typescriptVersionIsAtLeast[version] = true;", - }, + "_fs.realpathSync.native": + "_fs.realpathSync && _fs.realpathSync.native", + // Remove useless `ts.sys` + "ts.sys = ": "ts.sys = undefined && ", + + // Remove useless language service + "ts.realizeDiagnostics = ": "ts.realizeDiagnostics = undefined && ", + "ts.TypeScriptServicesFactory = ": + "ts.TypeScriptServicesFactory = undefined && ", + "var ShimBase = ": "var ShimBase = undefined && ", + "var TypeScriptServicesFactory = ": + "var TypeScriptServicesFactory = undefined && ", + "var LanguageServiceShimObject = ": + "var LanguageServiceShimObject = undefined && ", + "var CoreServicesShimHostAdapter = ": + "var CoreServicesShimHostAdapter = undefined && ", + "var LanguageServiceShimHostAdapter = ": + "var LanguageServiceShimHostAdapter = undefined && ", + "var ScriptSnapshotShimAdapter = ": + "var ScriptSnapshotShimAdapter = undefined && ", + "var ClassifierShimObject = ": + "var ClassifierShimObject = undefined && ", + "var CoreServicesShimObject = ": + "var CoreServicesShimObject = undefined && ", + "function simpleForwardCall(": "0 && function simpleForwardCall(", + "function forwardJSONCall(": "0 && function forwardJSONCall(", + "function forwardCall(": "0 && function forwardCall(", + "function realizeDiagnostics(": "0 && function realizeDiagnostics(", + "function realizeDiagnostic(": "0 && function realizeDiagnostic(", + "function convertClassifications(": + "0 && function convertClassifications(", + + // Dynamic `require()`s + "ts.sys && ts.sys.require": "false", + "require(etwModulePath)": "undefined", + 'require("source-map-support").install()': "", + "require(modulePath)": "undefined", + }).map(([find, replacement]) => ({ + file: require.resolve("typescript"), + find, + replacement, + })), + ], replaceModule: { [require.resolve("debug")]: require.resolve("./shims/debug.cjs"), }, @@ -101,27 +137,43 @@ const parsers = [ name: "prettierPlugins.espree", // TODO: Rename this file to `parser-acorn-and-espree.js` or find a better way output: "parser-espree.js", - replace: { - "const Syntax = ": "const Syntax = undefined && ", - "var visitorKeys = ": "var visitorKeys = undefined && ", - }, + replaceText: [ + { + file: require.resolve("espree"), + find: "const Syntax = (function() {", + replacement: "const Syntax = undefined && (function() {", + }, + { + file: require.resolve("espree"), + find: "var visitorKeys = require('eslint-visitor-keys');", + replacement: "var visitorKeys;", + }, + ], }, { input: "src/language-js/parse/meriyah.js", }, { input: "src/language-js/parse/angular.js", - replace: { - "@angular/compiler/src": "@angular/compiler/esm2015/src", - }, + replaceText: [ + { + file: "*", + find: 'require("@angular/compiler/src/', + replacement: 'require("@angular/compiler/esm2015/src/', + }, + ], }, { input: "src/language-css/parser-postcss.js", - replace: { - // `postcss-values-parser` uses constructor.name, it will be changed by rollup or terser - // https://github.com/shellscape/postcss-values-parser/blob/c00f858ab8c86ce9f06fdb702e8f26376f467248/lib/parser.js#L499 - "node.constructor.name === 'Word'": "node.type === 'word'", - }, + replaceText: [ + { + // `postcss-values-parser` uses constructor.name, it will be changed by bundler + // https://github.com/shellscape/postcss-values-parser/blob/c00f858ab8c86ce9f06fdb702e8f26376f467248/lib/parser.js#L499 + file: require.resolve("postcss-values-parser/lib/parser.js"), + find: "node.constructor.name === 'Word'", + replacement: "node.type === 'word'", + }, + ], }, { input: "src/language-graphql/parser-graphql.js", @@ -170,10 +222,13 @@ const parsers = [ const coreBundles = [ { input: "src/index.js", - replace: { - // from @iarna/toml/parse-string - "eval(\"require('util').inspect\")": "require('util').inspect", - }, + replaceText: [ + { + file: require.resolve("@iarna/toml/lib/toml-parser.js"), + find: "const utilInspect = eval(\"require('util').inspect\")", + replacement: "const utilInspect = require('util').inspect", + }, + ], }, { input: "src/document/index.js", @@ -208,10 +263,14 @@ const coreBundles = [ }, { input: "src/common/third-party.js", - replace: { + replaceText: [ // cosmiconfig@6 -> import-fresh can't find parentModule, since module is bundled - "parentModule(__filename)": "__filename", - }, + { + file: require.resolve("import-fresh"), + find: "parentModule(__filename)", + replacement: "__filename", + }, + ], }, ].map((bundle) => ({ type: "core", diff --git a/scripts/build/esbuild-plugins/replace-text.mjs b/scripts/build/esbuild-plugins/replace-text.mjs new file mode 100644 index 000000000..a6dcf7289 --- /dev/null +++ b/scripts/build/esbuild-plugins/replace-text.mjs @@ -0,0 +1,34 @@ +import fs from "node:fs/promises"; + +function replaceText(text, options) { + return text.replaceAll(options.find, options.replacement); +} + +export default function esbuildPluginReplaceText({ + filter = /./, + replacements, +}) { + for (const replacement of replacements) { + if (!replacement.file) { + throw new Error("'file' option is required."); + } + } + + return { + name: "replace-text", + setup(build) { + build.onLoad({ filter }, async ({ path: file }) => { + let text = await fs.readFile(file, "utf8"); + + for (const replacement of replacements) { + if (replacement.file !== "*" && replacement.file !== file) { + continue; + } + text = replaceText(text, replacement); + } + + return { contents: text }; + }); + }, + }; +} diff --git a/yarn.lock b/yarn.lock index 3a601c424..36b3003fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2364,7 +2364,7 @@ core-js-compat@^3.20.2, core-js-compat@^3.21.0: browserslist "^4.19.1" semver "7.0.0" -core-js@3.21.1, core-js@^3.16.2, core-js@^3.4.1: +core-js@3.21.1, core-js@^3.16.2: version "3.21.1" resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.21.1.tgz#f2e0ddc1fc43da6f904706e8e955bc19d06a0d94" integrity sha512-FRq5b/VMrWlrmCzwRrpDYNxyHP9BcAZC+xHJaqTgIE5091ZV1NTmyh0sGOg5XqpnHvR0svdy0sv1gWA1zmhxig== @@ -2834,13 +2834,6 @@ esbuild-openbsd-64@0.14.25: resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.25.tgz#31ebf616aadf6e60674469f2b92cec92280d9930" integrity sha512-QgFJ37A15D7NIXBTYEqz29+uw3nNBOIyog+3kFidANn6kjw0GHZ0lEYQn+cwjyzu94WobR+fes7cTl/ZYlHb1A== -esbuild-plugin-text-replace@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/esbuild-plugin-text-replace/-/esbuild-plugin-text-replace-1.2.0.tgz#43d87d88c5c1314151d8046d490aa21605fe1feb" - integrity sha512-04kdAMjxqGG+TEV/F+uGQVMFZlh65Eb11xo8b532io8FjOg5x2faoCXGicJo97BovXYARCPUhiZEECKZrFQq7Q== - dependencies: - ts-replace-all "^1.0.0" - esbuild-sunos-64@0.14.25: version "0.14.25" resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.25.tgz#815e4f936d74970292a63ccfd5791fe5e3569f5f" @@ -6245,13 +6238,6 @@ trough@^1.0.0: resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406" integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA== -ts-replace-all@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/ts-replace-all/-/ts-replace-all-1.0.0.tgz#ef28283ccdb4da7d69fb03cf543e09e51711659b" - integrity sha512-6uBtdkw3jHXkPtx/e9xB/5vcngMm17CyJYsS2YZeQ+9FdRnt6Ev5g931Sg2p+dxbtMGoCm13m3ax/obicTZIkQ== - dependencies: - core-js "^3.4.1" - tsconfig-paths@^3.12.0: version "3.12.0" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz#19769aca6ee8f6a1a341e38c8fa45dd9fb18899b"