diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 83fa63bc4..2413a2df2 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -173,7 +173,6 @@ module.exports = { { files: [ "**/*.cjs", - "src/*.js", "src/common/**/*.js", "src/config/**/*.js", "src/document/**/*.js", diff --git a/jest.config.mjs b/jest.config.mjs index 1c39b4df1..f2095aa20 100644 --- a/jest.config.mjs +++ b/jest.config.mjs @@ -2,7 +2,6 @@ import path from "node:path"; import createEsmUtils from "esm-utils"; import installPrettier from "./tests/config/install-prettier.js"; -// @ts-expect-error const { dirname: PROJECT_ROOT } = createEsmUtils(import.meta); const isProduction = process.env.NODE_ENV === "production"; const ENABLE_CODE_COVERAGE = Boolean(process.env.ENABLE_CODE_COVERAGE); diff --git a/scripts/build/bundler.mjs b/scripts/build/bundler.mjs index f0433b8ca..bdb0abc39 100644 --- a/scripts/build/bundler.mjs +++ b/scripts/build/bundler.mjs @@ -10,6 +10,7 @@ import esbuildPluginEvaluate from "./esbuild-plugins/evaluate.mjs"; import esbuildPluginReplaceModule from "./esbuild-plugins/replace-module.mjs"; import esbuildPluginLicense from "./esbuild-plugins/license.mjs"; import esbuildPluginUmd from "./esbuild-plugins/umd.mjs"; +import esbuildPluginInteropDefault from "./esbuild-plugins/interop-default.mjs"; import esbuildPluginVisualizer from "./esbuild-plugins/visualizer.mjs"; import bundles from "./config.mjs"; @@ -23,6 +24,9 @@ function* getEsbuildOptions(bundle, buildOptions) { const replaceStrings = { // `tslib` exports global variables "createExporter(root": "createExporter({}", + + // Use `require` directly + "const require = createRequire(import.meta.url);": "", }; const define = { @@ -46,7 +50,7 @@ function* getEsbuildOptions(bundle, buildOptions) { define.__dirname = JSON.stringify("/prettier-security-dirname-placeholder"); } - const replaceModule = {}; + const replaceModule = { module: EMPTY_MODULE_REPLACEMENT }; // Replace other bundled files if (bundle.target === "node") { // TODO[@fisker]: Fix this later, currently esbuild resolve it as ESM @@ -63,7 +67,11 @@ function* getEsbuildOptions(bundle, buildOptions) { // Dynamic require bundled files for (const item of bundles) { if (item.input !== bundle.input) { - replaceModule[path.join(PROJECT_ROOT, item.input)] = `./${item.output}`; + replaceModule[path.join(PROJECT_ROOT, item.input)] = { + path: `./${item.output}`, + isEsm: item.isEsm, + external: true, + }; } } } else { @@ -138,7 +146,10 @@ function* getEsbuildOptions(bundle, buildOptions) { ...esbuildOptions, outfile: bundle.output, plugins: [ - esbuildPluginUmd({ name: bundle.name }), + esbuildPluginUmd({ + name: bundle.name, + interopDefault: Boolean(bundle.isEsm), + }), ...esbuildOptions.plugins, ], format: "umd", @@ -159,6 +170,10 @@ function* getEsbuildOptions(bundle, buildOptions) { .map((item) => `./${item.output}`) ); + if (bundle.isEsm) { + esbuildOptions.plugins.push(esbuildPluginInteropDefault()); + } + yield { ...esbuildOptions, outfile: bundle.output, diff --git a/scripts/build/config.mjs b/scripts/build/config.mjs index 28c6c1631..eae484fec 100644 --- a/scripts/build/config.mjs +++ b/scripts/build/config.mjs @@ -16,6 +16,7 @@ const { require } = createEsmUtils(import.meta); * @property {Object.} replace - map of strings to replace when processing the bundle * @property {string[]} babelPlugins - babel plugins * @property {boolean?} minify - minify + * @property {boolean?} isEsm - Entry is ES Module * @typedef {Object} CommonJSConfig * @property {string[]} ignore - paths of CJS modules to ignore @@ -172,6 +173,7 @@ const coreBundles = [ // from @iarna/toml/parse-string "eval(\"require('util').inspect\")": "require('util').inspect", }, + isEsm: true, }, { input: "src/document/index.js", @@ -192,6 +194,7 @@ const coreBundles = [ [createRequire(require.resolve("vnopts")).resolve("chalk")]: require.resolve("./shims/chalk.cjs"), }, + isEsm: true, }, { input: "bin/prettier.js", diff --git a/scripts/build/esbuild-plugins/interop-default.mjs b/scripts/build/esbuild-plugins/interop-default.mjs new file mode 100644 index 000000000..972f2e999 --- /dev/null +++ b/scripts/build/esbuild-plugins/interop-default.mjs @@ -0,0 +1,31 @@ +import fs from "node:fs"; + +export default function esbuildPluginInteropDefault() { + return { + name: "interop-default", + setup(build) { + const { onEnd, initialOptions: esbuildOptions } = build; + + onEnd(() => { + const file = esbuildOptions.outfile; + + if (!fs.existsSync(file)) { + throw new Error(`${file} not exists`); + } + + const text = fs.readFileSync(file, "utf8").trim(); + const lines = text.split("\n"); + let lastLine = lines[lines.length - 1]; + + if (!lastLine.startsWith("module.exports = __toCommonJS(")) { + throw new Error("Unexpected output."); + } + + lastLine = lastLine.replace(/;$/, ".default;"); + lines[lines.length - 1] = lastLine; + + fs.writeFileSync(file, lines.join("\n")); + }); + }, + }; +} diff --git a/scripts/build/esbuild-plugins/replace-module.mjs b/scripts/build/esbuild-plugins/replace-module.mjs index 7f166f6d8..2d6284b4f 100644 --- a/scripts/build/esbuild-plugins/replace-module.mjs +++ b/scripts/build/esbuild-plugins/replace-module.mjs @@ -20,6 +20,7 @@ export default function esbuildPluginReplaceModule(replacements = {}) { contents, loader = "js", external: isExternal, + isEsm, } = options; // Make this work with `@esbuild-plugins/node-modules-polyfill` plugin @@ -39,13 +40,13 @@ export default function esbuildPluginReplaceModule(replacements = {}) { // Prevent `esbuild` to resolve contents = ` const entry = ${JSON.stringify(`${file}`)}; - module.exports = require(entry); + ${isEsm ? "export default" : "module.exports ="} require(entry); `; } else { if (file) { - contents = ` - module.exports = require(${JSON.stringify(`${file}`)}); - `; + contents = isEsm + ? `export { default } from ${JSON.stringify(`${file}`)};` + : `module.exports = require(${JSON.stringify(`${file}`)})`; } } diff --git a/scripts/build/esbuild-plugins/umd.mjs b/scripts/build/esbuild-plugins/umd.mjs index 69e6f2da8..3102bdbac 100644 --- a/scripts/build/esbuild-plugins/umd.mjs +++ b/scripts/build/esbuild-plugins/umd.mjs @@ -2,7 +2,7 @@ import fs from "node:fs"; import camelCase from "camelcase"; import { outdent } from "outdent"; -function getUmdWrapper(name, build) { +function getUmdWrapper({ name, interopDefault = false }, build) { const path = name.split("."); const { minify } = build.initialOptions; const temporaryName = minify ? "m" : camelCase(name); @@ -12,7 +12,7 @@ function getUmdWrapper(name, build) { for (let index = 0; index < path.length; index++) { const object = ["root", ...path.slice(0, index + 1)].join("."); if (index === path.length - 1) { - globalObjectText.push(`${object} = factory();`); + globalObjectText.push(`${object}`); } else { globalObjectText.push(`${object} = ${object} || {};`); } @@ -23,10 +23,17 @@ function getUmdWrapper(name, build) { let wrapper = outdent` (function (factory) { + function interopModuleDefault(factory) { + var module = factory(); + return module${interopDefault ? ".default" : ""}; + } + if (typeof exports === "object" && typeof module === "object") { - module.exports = factory(); + module.exports = interopModuleDefault(factory); } else if (typeof define === "function" && define.amd) { - define(factory); + define(function () { + return interopModuleDefault(factory) + }); } else { var root = typeof globalThis !== "undefined" @@ -36,9 +43,10 @@ function getUmdWrapper(name, build) { : typeof self !== "undefined" ? self : this || {}; - ${globalObjectText.trimStart()} + ${globalObjectText.trimStart()} = interopModuleDefault(factory); } })(function() { + "use strict"; ${placeholder} return ${temporaryName}; }); @@ -52,33 +60,33 @@ function getUmdWrapper(name, build) { const [intro, outro] = wrapper.split(placeholder); + const expectedOutput = `var ${temporaryName}${minify ? "=" : " = "}`; + return { name: temporaryName, intro, outro, - expectedOutput: `"use strict";${minify ? "" : "\n"}var ${temporaryName}${ - minify ? "=" : " = " - }`, + expectedOutput, }; } -export default function esbuildPluginUmd({ name }) { +export default function esbuildPluginUmd(options) { return { name: "umd", setup(build) { - const options = build.initialOptions; - const { globalName, format, outfile } = options; + const esbuildOptions = build.initialOptions; + const { globalName, format, outfile } = esbuildOptions; if (globalName) { - throw new Error("'globalName' in options cannot be set."); + throw new Error("'globalName' in esbuildOptions cannot be set."); } if (format !== "umd") { - throw new Error("'format' options must be 'umd'."); + throw new Error("'format' esbuildOptions must be 'umd'."); } if (!outfile) { - throw new Error("'outfile' options is required."); + throw new Error("'outfile' esbuildOptions is required."); } const { @@ -86,15 +94,20 @@ export default function esbuildPluginUmd({ name }) { intro, outro, expectedOutput, - } = getUmdWrapper(name, build); - options.globalName = temporaryName; - options.format = "iife"; + } = getUmdWrapper(options, build); + esbuildOptions.globalName = temporaryName; + esbuildOptions.format = "iife"; build.onEnd(() => { if (!fs.existsSync(outfile)) { throw new Error(`${outfile} not exists`); } - const text = fs.readFileSync(outfile, "utf8"); + + // We already insert `"use strict";` in the wrapper + let text = fs.readFileSync(outfile, "utf8"); + if (text.startsWith('"use strict";')) { + text = text.slice('"use strict";'.length).trimStart(); + } const actualOutput = text.slice(0, expectedOutput.length); if (actualOutput !== expectedOutput) { console.log(); diff --git a/src/common/load-plugins.js b/src/common/load-plugins.js index 2ccdb26c9..23f86bfb2 100644 --- a/src/common/load-plugins.js +++ b/src/common/load-plugins.js @@ -5,7 +5,6 @@ const path = require("path"); const uniqBy = require("lodash/uniqBy.js"); const partition = require("lodash/partition.js"); const fastGlob = require("fast-glob"); -const internalPlugins = require("../languages.js"); const { default: mem, memClear } = require("../../vendors/mem.js"); const thirdParty = require("./third-party.js"); const resolve = require("./resolve.js"); @@ -98,7 +97,7 @@ function load(plugins, pluginSearchDirs) { ...externalPluginInstances, ]; - return [...internalPlugins, ...externalPlugins]; + return externalPlugins; } function findPluginsInNodeModules(nodeModulesDir) { diff --git a/src/package.json b/src/common/package.json similarity index 100% rename from src/package.json rename to src/common/package.json diff --git a/src/config/package.json b/src/config/package.json new file mode 100644 index 000000000..54d221724 --- /dev/null +++ b/src/config/package.json @@ -0,0 +1,5 @@ +{ + "//TODO": "Remove this file when we close #10157", + "type": "commonjs", + "description": "A package.json file to use `.js` files in this directory as CommonJS module." +} diff --git a/src/document/package.json b/src/document/package.json new file mode 100644 index 000000000..54d221724 --- /dev/null +++ b/src/document/package.json @@ -0,0 +1,5 @@ +{ + "//TODO": "Remove this file when we close #10157", + "type": "commonjs", + "description": "A package.json file to use `.js` files in this directory as CommonJS module." +} diff --git a/src/index.js b/src/index.js index 0312d18e7..c10edd32f 100644 --- a/src/index.js +++ b/src/index.js @@ -1,15 +1,17 @@ -"use strict"; +import { createRequire } from "module"; +import core from "./main/core.js"; +import { getSupportInfo } from "./main/support.js"; +import getFileInfo from "./common/get-file-info.js"; +import sharedUtil from "./common/util-shared.js"; +import plugins from "./common/load-plugins.js"; +import config from "./config/resolve-config.js"; +import doc from "./document/index.js"; +import languages from "./languages.js"; + +const require = createRequire(import.meta.url); const { version } = require("../package.json"); -const core = require("./main/core.js"); -const { getSupportInfo } = require("./main/support.js"); -const getFileInfo = require("./common/get-file-info.js"); -const sharedUtil = require("./common/util-shared.js"); -const plugins = require("./common/load-plugins.js"); -const config = require("./config/resolve-config.js"); -const doc = require("./document/index.js"); - function _withPlugins( fn, optsArgIdx = 1 // Usually `opts` is the 2nd argument @@ -18,7 +20,10 @@ function _withPlugins( const opts = args[optsArgIdx] || {}; args[optsArgIdx] = { ...opts, - plugins: plugins.loadPlugins(opts.plugins, opts.pluginSearchDirs), + plugins: [ + ...languages, + ...plugins.loadPlugins(opts.plugins, opts.pluginSearchDirs), + ], }; return fn(...args); }; @@ -35,7 +40,7 @@ function withPlugins(fn, optsArgIdx) { const formatWithCursor = withPlugins(core.formatWithCursor); -module.exports = { +const prettier = { formatWithCursor, format(text, opts) { @@ -87,3 +92,5 @@ module.exports = { printDocToString: withPlugins(core.printDocToString), }, }; + +export default prettier; diff --git a/src/language-css/package.json b/src/language-css/package.json new file mode 100644 index 000000000..54d221724 --- /dev/null +++ b/src/language-css/package.json @@ -0,0 +1,5 @@ +{ + "//TODO": "Remove this file when we close #10157", + "type": "commonjs", + "description": "A package.json file to use `.js` files in this directory as CommonJS module." +} diff --git a/src/language-graphql/package.json b/src/language-graphql/package.json new file mode 100644 index 000000000..54d221724 --- /dev/null +++ b/src/language-graphql/package.json @@ -0,0 +1,5 @@ +{ + "//TODO": "Remove this file when we close #10157", + "type": "commonjs", + "description": "A package.json file to use `.js` files in this directory as CommonJS module." +} diff --git a/src/language-handlebars/package.json b/src/language-handlebars/package.json new file mode 100644 index 000000000..54d221724 --- /dev/null +++ b/src/language-handlebars/package.json @@ -0,0 +1,5 @@ +{ + "//TODO": "Remove this file when we close #10157", + "type": "commonjs", + "description": "A package.json file to use `.js` files in this directory as CommonJS module." +} diff --git a/src/language-html/package.json b/src/language-html/package.json new file mode 100644 index 000000000..54d221724 --- /dev/null +++ b/src/language-html/package.json @@ -0,0 +1,5 @@ +{ + "//TODO": "Remove this file when we close #10157", + "type": "commonjs", + "description": "A package.json file to use `.js` files in this directory as CommonJS module." +} diff --git a/src/language-js/package.json b/src/language-js/package.json new file mode 100644 index 000000000..54d221724 --- /dev/null +++ b/src/language-js/package.json @@ -0,0 +1,5 @@ +{ + "//TODO": "Remove this file when we close #10157", + "type": "commonjs", + "description": "A package.json file to use `.js` files in this directory as CommonJS module." +} diff --git a/src/language-markdown/package.json b/src/language-markdown/package.json new file mode 100644 index 000000000..54d221724 --- /dev/null +++ b/src/language-markdown/package.json @@ -0,0 +1,5 @@ +{ + "//TODO": "Remove this file when we close #10157", + "type": "commonjs", + "description": "A package.json file to use `.js` files in this directory as CommonJS module." +} diff --git a/src/language-yaml/package.json b/src/language-yaml/package.json new file mode 100644 index 000000000..54d221724 --- /dev/null +++ b/src/language-yaml/package.json @@ -0,0 +1,5 @@ +{ + "//TODO": "Remove this file when we close #10157", + "type": "commonjs", + "description": "A package.json file to use `.js` files in this directory as CommonJS module." +} diff --git a/src/languages.js b/src/languages.js index 46c216cea..289756b09 100644 --- a/src/languages.js +++ b/src/languages.js @@ -1,7 +1,9 @@ -"use strict"; +import { createRequire } from "module"; + +const require = createRequire(import.meta.url); // We need to list the parsers and getters so we can load them only when necessary. -module.exports = [ +const languages = [ // JS require("./language-js/index.js"), // CSS @@ -17,3 +19,5 @@ module.exports = [ // YAML require("./language-yaml/index.js"), ]; + +export default languages; diff --git a/src/main/package.json b/src/main/package.json new file mode 100644 index 000000000..54d221724 --- /dev/null +++ b/src/main/package.json @@ -0,0 +1,5 @@ +{ + "//TODO": "Remove this file when we close #10157", + "type": "commonjs", + "description": "A package.json file to use `.js` files in this directory as CommonJS module." +} diff --git a/src/standalone.js b/src/standalone.js index 21b190849..9f38ffe45 100644 --- a/src/standalone.js +++ b/src/standalone.js @@ -1,13 +1,13 @@ -"use strict"; +import { createRequire } from "module"; +import core from "./main/core.js"; +import { getSupportInfo } from "./main/support.js"; +import sharedUtil from "./common/util-shared.js"; +import languages from "./languages.js"; +import doc from "./document/index.js"; +const require = createRequire(import.meta.url); const { version } = require("../package.json"); -const core = require("./main/core.js"); -const { getSupportInfo } = require("./main/support.js"); -const sharedUtil = require("./common/util-shared.js"); -const languages = require("./languages.js"); -const doc = require("./document/index.js"); - function withPlugins( fn, optsArgIdx = 1 // Usually `opts` is the 2nd argument @@ -30,7 +30,7 @@ function withPlugins( const formatWithCursor = withPlugins(core.formatWithCursor); -module.exports = { +const prettierStandalone = { formatWithCursor, format(text, opts) { @@ -58,3 +58,5 @@ module.exports = { printDocToString: withPlugins(core.printDocToString), }, }; + +export default prettierStandalone; diff --git a/src/utils/package.json b/src/utils/package.json new file mode 100644 index 000000000..54d221724 --- /dev/null +++ b/src/utils/package.json @@ -0,0 +1,5 @@ +{ + "//TODO": "Remove this file when we close #10157", + "type": "commonjs", + "description": "A package.json file to use `.js` files in this directory as CommonJS module." +} diff --git a/tsconfig.json b/tsconfig.json index d23191f9b..1b86f9297 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,8 +3,9 @@ "allowJs": true, "checkJs": true, "noEmit": true, - "target": "es2015", - "module": "commonjs", + "target": "esnext", + "module": "esnext", + "moduleResolution": "node", "resolveJsonModule": true, "esModuleInterop": true, // TBD it is desired to enabled strict type checking at some point: