Rewrite build script (#18427)

This commit is contained in:
fisker Cheung authored and GitHub committed 2025-12-05 04:53:15 +08:00
1 parent e8e21135ab
commit f6bd3cbd8a
24 files changed
+990 -975

No files matched your search

+1 -1
View File
@@ -345,6 +345,6 @@
"website/static/playground.js",
"website/src/css/custom.css",
"website/blog/authors.yml",
"scripts/build/typescript-unused-specifiers.js"
"scripts/build/hacks/typescript-unused-specifiers.js"
]
}
+2 -2
View File
@@ -18,8 +18,8 @@ const config = {
],
project: ["src/**", "scripts/**"],
ignore: [
"scripts/build/config.js",
"scripts/build/build-javascript-module.js",
"scripts/build/packages/*",
"scripts/build/builders/javascript-module.js",
"scripts/tools/**",
"src/experimental-cli/**",
"src/universal/*.browser.js",
-185
View File
@@ -1,185 +0,0 @@
import path from "node:path";
import {
PRODUCTION_MINIMAL_NODE_JS_VERSION,
PROJECT_ROOT,
readJson,
writeJson,
} from "../utilities/index.js";
const keysToKeep = [
"name",
"version",
"description",
"bin",
"repository",
"funding",
"homepage",
"author",
"license",
"main",
"browser",
"unpkg",
"exports",
"engines",
"files",
"preferUnplugged",
"sideEffects",
];
const publishConfig = {
access: "public",
registry: "https://registry.npmjs.org/",
};
async function buildPrettierPackageJson({ packageConfig, file }) {
const { distDirectory, files } = packageConfig;
const packageJson = await readJson(path.join(PROJECT_ROOT, file.input));
const bin = files.find(
(file) =>
path.join(PROJECT_ROOT, packageJson.bin) ===
path.join(PROJECT_ROOT, file.input),
).output.file;
const overrides = {
bin: `./${bin}`,
main: "./index.cjs",
engines: {
...packageJson.engines,
// https://github.com/prettier/prettier/pull/13118#discussion_r922708068
// Don't delete, event it's the same in package.json
node: `>=${PRODUCTION_MINIMAL_NODE_JS_VERSION}`,
},
type: "commonjs",
exports: {
".": {
types: "./index.d.ts",
require: "./index.cjs",
browser: {
import: "./standalone.mjs",
default: "./standalone.js",
},
default: "./index.mjs",
},
"./*": "./*",
...Object.fromEntries(
files
.filter((file) => file.output.format === "umd")
.map((file) => {
const basename = path.basename(file.output.file, ".js");
return [
file.isPlugin ? `./plugins/${basename}` : `./${basename}`,
{
types: `./${file.output.file.replace(/\.js$/u, ".d.ts")}`,
// `module-sync` condition can prevent CJS plugins from working: https://github.com/prettier/prettier/issues/17139
// Perform a test before re-adding it.
require: `./${file.output.file}`,
default: `./${file.output.file.replace(/\.js$/u, ".mjs")}`,
},
];
}),
),
// Legacy entries
// TODO: Remove bellow in v4
"./esm/standalone.mjs": "./standalone.mjs",
...Object.fromEntries(
files
.filter(
(file) =>
file.isPlugin &&
file.output.format === "umd" &&
file.output.file !== "plugins/estree.js",
)
.flatMap((file) => {
let basename = path.basename(file.output.file, ".js");
if (basename === "acorn") {
basename = "espree";
}
return [
[`./parser-${basename}`, `./${file.output.file}`],
[`./parser-${basename}.js`, `./${file.output.file}`],
[
`./esm/parser-${basename}.mjs`,
`./${file.output.file.replace(/\.js$/u, ".mjs")}`,
],
];
}),
),
},
files: files.map(({ output: { file } }) => file).sort(),
scripts: {
prepublishOnly:
"node -e \"assert.equal(require('.').version, require('..').version)\"",
},
};
await writeJson(
path.join(distDirectory, file.output.file),
Object.assign(pick(packageJson, keysToKeep), overrides),
);
}
async function buildPluginOxcPackageJson({ packageConfig, file }) {
const { distDirectory, files } = packageConfig;
const packageJson = await readJson(path.join(PROJECT_ROOT, file.input));
const projectPackageJson = await readJson(
path.join(PROJECT_ROOT, "package.json"),
);
const overrides = {
engines: {
...packageJson.engines,
// https://github.com/prettier/prettier/pull/13118#discussion_r922708068
// Don't delete, event it's the same in package.json
node: `>=${PRODUCTION_MINIMAL_NODE_JS_VERSION}`,
},
// Use `commonjs` since we may provide browser build in future.
type: "commonjs",
files: files.map(({ output: { file } }) => file).sort(),
dependencies: {
"oxc-parser": projectPackageJson.dependencies["oxc-parser"],
},
publishConfig,
};
await writeJson(
path.join(distDirectory, file.output.file),
Object.assign(pick(packageJson, keysToKeep), overrides),
);
}
async function buildPluginHermesPackageJson({ packageConfig, file }) {
const { distDirectory, files } = packageConfig;
const packageJson = await readJson(path.join(PROJECT_ROOT, file.input));
const overrides = {
engines: {
...packageJson.engines,
// https://github.com/prettier/prettier/pull/13118#discussion_r922708068
// Don't delete, event it's the same in package.json
node: `>=${PRODUCTION_MINIMAL_NODE_JS_VERSION}`,
},
// Use `commonjs` since we may provide browser build in future.
type: "commonjs",
files: files.map(({ output: { file } }) => file).sort(),
publishConfig,
};
await writeJson(
path.join(distDirectory, file.output.file),
Object.assign(pick(packageJson, keysToKeep), overrides),
);
}
function pick(object, keys) {
keys = new Set(keys);
return Object.fromEntries(
Object.entries(object).filter(([key]) => keys.has(key)),
);
}
export {
buildPluginHermesPackageJson,
buildPluginOxcPackageJson,
buildPrettierPackageJson,
};
+25 -31
View File
@@ -3,16 +3,13 @@
import fs from "node:fs/promises";
import path from "node:path";
import readline from "node:readline";
import createEsmUtils from "esm-utils";
import styleText from "node-style-text";
import prettyBytes from "pretty-bytes";
import prettyMilliseconds from "pretty-ms";
import { DIST_DIR } from "../utilities/index.js";
import packageConfigs from "./config.js";
import { DIST_DIR, PROJECT_ROOT } from "../utilities/index.js";
import packageConfigs from "./packages/index.js";
import parseArguments from "./parse-arguments.js";
const { require } = createEsmUtils(import.meta);
const statusConfig = [
{ color: "bgGreen", text: "DONE" },
{ color: "bgRed", text: "FAIL" },
@@ -47,21 +44,14 @@ const clear = () => {
async function buildFile({ packageConfig, file, cliOptions, results }) {
const { distDirectory } = packageConfig;
let displayName = file.output.file;
if (
(file.platform === "universal" && file.output.format !== "esm") ||
(file.output.file.startsWith("index.") && file.output.format !== "esm") ||
file.output.file.endsWith(".browser.mjs") ||
file.kind === "types"
) {
displayName = ` ${displayName}`;
}
const displayName = ` ${file.output}`;
const fileAbsolutePath = path.join(distDirectory, file.output);
process.stdout.write(fitTerminal(displayName));
if (
(cliOptions.files && !cliOptions.files.has(file.output.file)) ||
(cliOptions.playground && !file.playground)
(cliOptions.playground && !file.playground) ||
(cliOptions.files && !cliOptions.files.has(fileAbsolutePath))
) {
console.log(status.SKIPPED);
return;
@@ -76,14 +66,12 @@ async function buildFile({ packageConfig, file, cliOptions, results }) {
throw error;
}
result ??= {};
if (result.skipped) {
if (result?.skipped) {
console.log(status.SKIPPED);
return;
}
const outputFile = cliOptions.saveAs ?? file.output.file;
const outputFile = cliOptions.saveAs ?? file.output;
const sizeMessages = [];
if (cliOptions.printSize) {
@@ -92,9 +80,12 @@ async function buildFile({ packageConfig, file, cliOptions, results }) {
}
if (cliOptions.compareSize) {
// TODO: Use `import.meta.resolve` when Node.js support
const stablePrettierDirectory = path.dirname(require.resolve("prettier"));
const stablePrettierDirectory = path.join(
PROJECT_ROOT,
`node_modules/${packageConfig.packageName}/`,
);
const stableVersionFile = path.join(stablePrettierDirectory, outputFile);
let stableSize;
try {
({ size: stableSize } = await fs.stat(stableVersionFile));
@@ -172,19 +163,22 @@ async function run() {
}
console.log(
styleText.inverse`[${index + 1}/${packagesToBuild.length}] Building package '${packageConfig.packageName}'`,
styleText.blue`[${index + 1}/${packagesToBuild.length}] Building package '${packageConfig.packageName}'`,
);
const startTime = performance.now();
const results = [];
for (const file of packageConfig.files) {
const result = await buildFile({
packageConfig,
file,
cliOptions,
results,
});
results.push(result);
for (const module of packageConfig.modules) {
console.log(styleText.gray(module.name));
for (const file of module.files) {
const result = await buildFile({
packageConfig,
file,
cliOptions,
results,
});
results.push(result);
}
}
console.log(
`Build package '${packageConfig.packageName}' success in ${prettyMilliseconds(performance.now() - startTime)}`,
+12
View File
@@ -0,0 +1,12 @@
import path from "node:path";
import { copyFile } from "../../utilities/index.js";
const copyFileBuilder = ({ packageConfig, file }) =>
copyFile(
path.isAbsolute(file.input)
? file.input
: path.join(packageConfig.sourceDirectory, file.input),
path.join(packageConfig.distDirectory, file.output),
);
export { copyFileBuilder };
@@ -2,7 +2,7 @@ import fs from "node:fs/promises";
import path from "node:path";
import { outdent } from "outdent";
import rollupPluginLicense from "rollup-plugin-license";
import { PROJECT_ROOT } from "../utilities/index.js";
import { PROJECT_ROOT } from "../../utilities/index.js";
const separator = `\n${"-".repeat(40)}\n\n`;
@@ -20,7 +20,7 @@ function getDependencies(results) {
modules: Object.fromEntries(
results
.flatMap((result) =>
Object.keys(result.esbuildResult?.metafile.inputs ?? {}),
Object.keys(result?.esbuildResult?.metafile.inputs ?? {}),
)
.map((file) => [file, { renderedLength: 1 }]),
),
@@ -42,7 +42,7 @@ function getDependencies(results) {
return dependencies;
}
function getLicenseText(packageConfig, dependencies) {
function getLicenseText(packageConfig, dependencies, packageDisplayName) {
dependencies = dependencies.filter(
(dependency, index) =>
// Exclude self
@@ -72,7 +72,7 @@ function getLicenseText(packageConfig, dependencies) {
const head = outdent`
# Licenses of bundled dependencies
The published ${packageConfig.packageDisplayName ?? packageConfig.packageName} artifact additionally contains code with the following licenses:
The published ${packageDisplayName ?? packageConfig.packageName} artifact additionally contains code with the following licenses:
${new Intl.ListFormat("en-US", { type: "conjunction" }).format(licenses)}.
`;
@@ -120,35 +120,40 @@ function getLicenseText(packageConfig, dependencies) {
return [head, content].join("\n\n");
}
async function buildDependenciesLicense({
packageConfig,
file,
results,
cliOptions,
}) {
const { distDirectory, files } = packageConfig;
function createDependenciesLicenseBuilder({ packageDisplayName }) {
return async function buildDependenciesLicense({
packageConfig,
file,
results,
cliOptions,
}) {
const files = packageConfig.modules.flatMap((module) => module.files);
const fileName = file.output;
const fileName = file.output.file;
if (files.at(-1) !== file) {
throw new Error(`${fileName} should be last file to build.`);
}
if (files.at(-1) !== file) {
throw new Error(`${fileName} should be last file to build.`);
}
const shouldBuildLicense = !cliOptions.files;
const shouldBuildLicense = !cliOptions.files;
if (!shouldBuildLicense) {
return { skipped: true };
}
if (!shouldBuildLicense) {
return { skipped: true };
}
const dependencies = getDependencies(results);
const dependencies = getDependencies(results);
if (dependencies.length === 0) {
throw new Error("Fail to collect dependencies.");
}
if (dependencies.length === 0) {
throw new Error("Fail to collect dependencies.");
}
const text = getLicenseText(
packageConfig,
dependencies,
packageDisplayName,
);
const text = getLicenseText(packageConfig, dependencies);
await fs.writeFile(path.join(distDirectory, fileName), text);
await fs.writeFile(path.join(packageConfig.distDirectory, fileName), text);
};
}
export default buildDependenciesLicense;
export { createDependenciesLicenseBuilder };
@@ -3,30 +3,23 @@ import url from "node:url";
import browserslistToEsbuild from "browserslist-to-esbuild";
import esbuild from "esbuild";
import createEsmUtils from "esm-utils";
import {
PRODUCTION_MINIMAL_NODE_JS_VERSION,
PROJECT_ROOT,
} from "../utilities/index.js";
import esbuildPluginAddDefaultExport from "./esbuild-plugins/add-default-export.js";
import esbuildPluginEvaluate from "./esbuild-plugins/evaluate.js";
import esbuildPluginPrimitiveDefine from "./esbuild-plugins/primitive-define.js";
import esbuildPluginReplaceModule from "./esbuild-plugins/replace-module.js";
import esbuildPluginShimCommonjsObjects from "./esbuild-plugins/shim-commonjs-objects.js";
import esbuildPluginStripNodeProtocol from "./esbuild-plugins/strip-node-protocol.js";
import esbuildPluginThrowWarnings from "./esbuild-plugins/throw-warnings.js";
import esbuildPluginUmd from "./esbuild-plugins/umd.js";
import esbuildPluginVisualizer from "./esbuild-plugins/visualizer.js";
import transform from "./transform/index.js";
import { getPackageFile } from "./utilities.js";
import projectPackageJson from "../../../package.json" with { type: "json" };
import { PRODUCTION_MINIMAL_NODE_JS_VERSION } from "../../utilities/index.js";
import esbuildPluginAddDefaultExport from "../esbuild-plugins/add-default-export.js";
import esbuildPluginEvaluate from "../esbuild-plugins/evaluate.js";
import esbuildPluginPrimitiveDefine from "../esbuild-plugins/primitive-define.js";
import esbuildPluginReplaceModule from "../esbuild-plugins/replace-module.js";
import esbuildPluginShimCommonjsObjects from "../esbuild-plugins/shim-commonjs-objects.js";
import esbuildPluginStripNodeProtocol from "../esbuild-plugins/strip-node-protocol.js";
import esbuildPluginThrowWarnings from "../esbuild-plugins/throw-warnings.js";
import esbuildPluginUmd from "../esbuild-plugins/umd.js";
import esbuildPluginVisualizer from "../esbuild-plugins/visualizer.js";
import transform from "../transform/index.js";
import { getPackageFile } from "../utilities.js";
const {
readJsonSync,
require,
resolve: importMetaResolve,
} = createEsmUtils(import.meta);
const packageJson = readJsonSync("../../package.json");
const { require, resolve: importMetaResolve } = createEsmUtils(import.meta);
const universalTarget = browserslistToEsbuild(packageJson.browserslist);
const universalTarget = browserslistToEsbuild(projectPackageJson.browserslist);
const getRelativePath = (from, to) => {
const relativePath = path.posix.relative(path.dirname(`/${from}`), `/${to}`);
if (!relativePath.startsWith(".")) {
@@ -36,8 +29,8 @@ const getRelativePath = (from, to) => {
return relativePath;
};
function getEsbuildOptions({ packageConfig, file, cliOptions }) {
const { distDirectory, files } = packageConfig;
function getEsbuildOptions({ packageConfig, file, cliOptions, buildOptions }) {
const { sourceDirectory, distDirectory } = packageConfig;
// Save dependencies to file
file.dependencies = [];
@@ -116,11 +109,11 @@ function getEsbuildOptions({ packageConfig, file, cliOptions }) {
];
const define = {
"process.env.PRETTIER_TARGET": file.platform,
"process.env.PRETTIER_TARGET": buildOptions.platform,
"process.env.NODE_ENV": "production",
};
if (file.platform === "universal") {
if (buildOptions.platform === "universal") {
// We can't reference `process` in UMD bundles and this is
// an undocumented "feature"
replaceModule.push({
@@ -144,48 +137,48 @@ function getEsbuildOptions({ packageConfig, file, cliOptions }) {
define.__dirname = "/prettier-security-dirname-placeholder";
}
if (file.platform === "node") {
// External other bundled files
replaceModule.push(
...files
.filter(
(bundle) =>
bundle.input === "package.json" ||
(file.input !== bundle.input && bundle.output.format === "esm"),
)
.map((bundle) => {
let output = bundle.output.file;
if (
file.output.file === "index.cjs" &&
bundle.output.file === "doc.mjs"
) {
output = "doc.js";
}
// External other bundled files
if (buildOptions.platform === "node") {
const files = packageConfig.modules
.flatMap((module) => module.files)
.filter(
(bundle) =>
bundle.input === "package.json" ||
(file.input &&
file.input !== bundle.input &&
bundle.output.endsWith(".mjs")),
);
const replacements = files.map((bundle) => {
let { output } = bundle;
if (file.output === "index.cjs" && output === "doc.mjs") {
output = "doc.js";
}
return {
module: path.join(PROJECT_ROOT, bundle.input),
external: getRelativePath(file.output.file, output),
};
}),
);
return {
module: path.join(sourceDirectory, bundle.input),
external: getRelativePath(file.output, output),
};
});
replaceModule.push(...replacements);
}
// Current version of `yaml` is not tree-shakable,
// but when we update it, we may reduce size,
// since the UMD version don't need expose `__parsePrettierYamlConfig`
if (file.output.format === "umd" && file.output.file === "plugins/yaml.js") {
if (buildOptions.format === "umd" && file.output === "plugins/yaml.js") {
replaceModule.push({
module: path.join(PROJECT_ROOT, file.input),
module: path.join(sourceDirectory, file.input),
text: 'export * from "../language-yaml/index.js";',
});
}
const { buildOptions } = file;
const shouldMinify =
cliOptions.minify ?? buildOptions.minify ?? file.platform === "universal";
cliOptions.minify ??
buildOptions.minify ??
buildOptions.platform === "universal";
const esbuildOptions = {
entryPoints: [path.join(PROJECT_ROOT, file.input)],
entryPoints: [path.join(sourceDirectory, file.input)],
bundle: true,
metafile: true,
plugins: [
@@ -198,8 +191,8 @@ function getEsbuildOptions({ packageConfig, file, cliOptions }) {
cliOptions.reports &&
esbuildPluginVisualizer({ formats: cliOptions.reports }),
esbuildPluginThrowWarnings({
allowDynamicRequire: file.platform === "node",
allowDynamicImport: file.platform === "node",
allowDynamicRequire: buildOptions.platform === "node",
allowDynamicImport: buildOptions.platform === "node",
allowedWarnings: buildOptions.allowedWarnings,
}),
buildOptions.addDefaultExport && esbuildPluginAddDefaultExport(),
@@ -213,10 +206,11 @@ function getEsbuildOptions({ packageConfig, file, cliOptions }) {
...(buildOptions.target ?? [`node${PRODUCTION_MINIMAL_NODE_JS_VERSION}`]),
],
logLevel: "error",
format: file.output.format,
outfile: path.join(distDirectory, cliOptions.saveAs ?? file.output.file),
format: buildOptions.format,
outfile: path.join(distDirectory, cliOptions.saveAs ?? file.output),
// https://esbuild.github.io/api/#main-fields
mainFields: file.platform === "node" ? ["module", "main"] : undefined,
mainFields:
buildOptions.platform === "node" ? ["module", "main"] : undefined,
supported: {
...buildOptions.supported,
// https://github.com/evanw/esbuild/issues/3471
@@ -231,24 +225,23 @@ function getEsbuildOptions({ packageConfig, file, cliOptions }) {
packages: "bundle",
};
if (file.platform === "universal") {
if (buildOptions.platform === "universal") {
if (!buildOptions.target) {
esbuildOptions.target.push(...universalTarget);
}
if (file.output.format === "umd") {
if (buildOptions.format === "umd") {
esbuildOptions.plugins.push(
esbuildPluginUmd({
name: file.output.umdVariableName,
name: buildOptions.umdVariableName,
}),
);
}
} else {
esbuildOptions.platform = "node";
esbuildOptions.external.push(...files.map((file) => file.output.file));
// https://github.com/evanw/esbuild/issues/1921
if (file.output.format === "esm") {
if (buildOptions.format === "esm") {
esbuildOptions.plugins.push(esbuildPluginShimCommonjsObjects());
}
}
@@ -256,9 +249,20 @@ function getEsbuildOptions({ packageConfig, file, cliOptions }) {
return esbuildOptions;
}
async function runEsbuild(options) {
const esbuildOptions = getEsbuildOptions(options);
return { esbuildResult: await esbuild.build(esbuildOptions) };
function createJavascriptModuleBuilder(buildOptions) {
return async function runEsbuild({ packageConfig, file, cliOptions }) {
if (typeof buildOptions === "function") {
buildOptions = await buildOptions();
}
const esbuildOptions = getEsbuildOptions({
packageConfig,
file,
cliOptions,
buildOptions,
});
return { esbuildResult: await esbuild.build(esbuildOptions) };
};
}
export default runEsbuild;
export { createJavascriptModuleBuilder };
+86
View File
@@ -0,0 +1,86 @@
import assert from "node:assert/strict";
import path from "node:path";
import projectPackageJson from "../../../package.json" with { type: "json" };
import {
PRODUCTION_MINIMAL_NODE_JS_VERSION,
readJson,
writeJson,
} from "../../utilities/index.js";
const keysToKeep = [
"name",
"version",
"description",
"bin",
"repository",
"funding",
"homepage",
"author",
"license",
"main",
"browser",
"unpkg",
"exports",
"engines",
"files",
"preferUnplugged",
"sideEffects",
];
const publishConfig = {
access: "public",
registry: "https://registry.npmjs.org/",
};
function createPackageJsonBuilder({ process }) {
return async function buildPackageJson({ packageConfig, file }) {
const { distDirectory, sourceDirectory } = packageConfig;
const original = await readJson(path.join(sourceDirectory, file.input));
const packageFiles = packageConfig.modules.flatMap(({ files }) => files);
let packageJson = {
...pick(original, keysToKeep),
engines: {
...original.engines,
// https://github.com/prettier/prettier/pull/13118#discussion_r922708068
// Don't delete, event it's the same in package.json
node: `>=${PRODUCTION_MINIMAL_NODE_JS_VERSION}`,
},
type: "commonjs",
files: packageFiles.map((file) => file.output).sort(),
publishConfig,
};
// Rewrite `bin`
if (original.bin) {
const originalBinFile = path.join(sourceDirectory, original.bin);
const bin = packageFiles.find(
(file) =>
file.input &&
path.join(sourceDirectory, file.input) === originalBinFile,
).output;
assert.ok(typeof bin === "string");
packageJson.bin = `./${bin}`;
}
if (process) {
packageJson = await process(packageJson, {
packageConfig,
originalPackageJson: original,
projectPackageJson,
});
}
await writeJson(path.join(distDirectory, file.output), packageJson);
};
}
function pick(object, keys) {
keys = new Set(keys);
return Object.fromEntries(
Object.entries(object).filter(([key]) => keys.has(key)),
);
}
export { createPackageJsonBuilder };
@@ -3,7 +3,7 @@ import path from "node:path";
import url from "node:url";
import { isValidIdentifier } from "@babel/types";
import { outdent } from "outdent";
import { PROJECT_ROOT, writeFile } from "../utilities/index.js";
import { writeFile } from "../../utilities/index.js";
async function typesFileBuilder({ packageConfig, file }) {
/**
@@ -20,10 +20,8 @@ async function typesFileBuilder({ packageConfig, file }) {
for (const { from, to } of replacements) {
text = text.replaceAll(` from "${from}";`, ` from "${to}";`);
}
await writeFile(
path.join(packageConfig.distDirectory, file.output.file),
text,
);
await writeFile(path.join(packageConfig.distDirectory, file.output), text);
}
function toPropertyKey(name) {
@@ -32,7 +30,7 @@ function toPropertyKey(name) {
async function buildPluginTypes({ packageConfig, file: { input, output } }) {
const pluginModule = await import(
url.pathToFileURL(path.join(PROJECT_ROOT, input))
url.pathToFileURL(path.join(packageConfig.sourceDirectory, input))
);
const plugin = pluginModule.default ?? pluginModule;
const parserNames = Object.keys(plugin.parsers ?? {});
@@ -55,16 +53,11 @@ async function buildPluginTypes({ packageConfig, file: { input, output } }) {
};
`;
await writeFile(
path.join(packageConfig.distDirectory, output.file),
`${code}\n`,
);
await writeFile(path.join(packageConfig.distDirectory, output), `${code}\n`);
}
function buildTypes(options) {
return options.file.isPlugin
? buildPluginTypes(options)
: typesFileBuilder(options);
function createTypesBuilder({ isPlugin }) {
return isPlugin ? buildPluginTypes : typesFileBuilder;
}
export default buildTypes;
export { createTypesBuilder };
@@ -3,13 +3,12 @@ import { existsSync, promises as fs } from "node:fs";
import url from "node:url";
import spawn from "nano-spawn";
import { outdent } from "outdent";
import packageJson from "../../package.json" with { type: "json" };
import packageJson from "../../../package.json" with { type: "json" };
const TEMPORARY_DIRECTORY = new URL("../../../.tmp/", import.meta.url);
const DIRECTORY_NAME = "prettier-oxc-wasm-parser";
const PACKAGE_NAME = "@oxc-parser/binding-wasm32-wasi";
const TEMPORARY_DIRECTORY = new URL("../../.tmp/", import.meta.url);
const runYarn = (command, options) =>
spawn("yarn", command.split(" "), options);
@@ -60,6 +59,11 @@ async function inlineWasmBinary(directory) {
${text}
`;
text = text.replaceAll(
/new URL\(".*?", import\.meta\.url\)/gu,
"{url:undefined}",
);
text = text.replace(
"await fetch(__wasmUrl).then((res) => res.arrayBuffer())",
`__base64ToArrayBuffer(${JSON.stringify(wasm)})`,
@@ -2,7 +2,7 @@
import * as path from "node:path";
import { outdent } from "outdent";
import { PROJECT_ROOT, writeFile } from "../utilities/index.js";
import { PROJECT_ROOT, writeFile } from "../../utilities/index.js";
function esmifyTypescriptEslint(text) {
/*
@@ -2,7 +2,7 @@ import path from "node:path";
import escapeStringRegexp from "escape-string-regexp";
import MagicString from "magic-string";
import { outdent } from "outdent";
import { PROJECT_ROOT, writeFile } from "../utilities/index.js";
import { PROJECT_ROOT, writeFile } from "../../utilities/index.js";
import UNUSED_SPECIFIERS from "./typescript-unused-specifiers.js";
function* getModules(text) {
@@ -276,7 +276,6 @@ export default new Set([
"getCommentRange",
"getCommonSourceDirectory",
"getCommonSourceDirectoryOfConfig",
"getCompilerOptionsDiffValue",
"getConditions",
"getConfigFileParsingDiagnostics",
"getConstantValue",
+140
View File
@@ -0,0 +1,140 @@
import fs from "node:fs";
import path from "node:path";
import { PROJECT_ROOT } from "../../utilities/index.js";
import { copyFileBuilder } from "../builders/copy-file.js";
import { createDependenciesLicenseBuilder } from "../builders/dependencies-license.js";
import { createJavascriptModuleBuilder } from "../builders/javascript-module.js";
import { createPackageJsonBuilder } from "../builders/package-json.js";
import { createTypesBuilder } from "../builders/types.js";
const extensions = {
esm: ".mjs",
umd: ".js",
cjs: ".cjs",
};
function createPackageJsonConfig({ "package.json": process }) {
return {
input: "package.json",
output: "package.json",
build: createPackageJsonBuilder({ process }),
};
}
function createReadmeConfig() {
return {
input: "README.md",
output: "README.md",
build: copyFileBuilder,
};
}
function createLicenseConfig() {
return {
input: path.join(PROJECT_ROOT, "LICENSE"),
output: "LICENSE",
build: copyFileBuilder,
};
}
function createThirdPartyNoticeConfig({ packageDisplayName }) {
return {
output: "THIRD-PARTY-NOTICES.md",
build: createDependenciesLicenseBuilder({ packageDisplayName }),
};
}
function createPackageMetaFilesConfig(options = {}) {
return {
name: "Meta files",
files: [
createPackageJsonConfig,
createLicenseConfig,
createReadmeConfig,
createThirdPartyNoticeConfig,
].map((create) => create(options)),
};
}
function createTypesConfig({ input, outputBaseName, isPlugin }) {
const inputBasename = input.slice(0, -path.extname(input).length);
if (!isPlugin) {
input = `${inputBasename}.d.ts`;
if (!fs.existsSync(path.join(PROJECT_ROOT, input))) {
return [];
}
}
return [
{
input,
output: `${outputBaseName ?? inputBasename}.d.ts`,
build: createTypesBuilder({ isPlugin }),
},
];
}
function createNodejsFileConfig(file) {
if (typeof file === "string") {
file = { input: file };
}
let { input, output, outputBaseName, ...buildOptions } = file;
const format = input.endsWith(".cjs") ? "cjs" : "esm";
outputBaseName ??= path.basename(input, path.extname(input));
return [
{
input,
output: `${outputBaseName}${extensions[format]}`,
build: createJavascriptModuleBuilder({
format,
...buildOptions,
platform: "node",
}),
},
...createTypesConfig({ input, outputBaseName }),
];
}
function createUniversalFileConfig(file) {
if (typeof file === "string") {
file = { input: file };
}
let { input, outputBaseName, umdVariableName, ...buildOptions } = file;
outputBaseName ??= path.basename(input, path.extname(input));
umdVariableName ??= outputBaseName;
return [
...[
{
format: "esm",
file: `${outputBaseName}${extensions.esm}`,
},
{
format: "umd",
file: `${outputBaseName}${extensions.umd}`,
umdVariableName,
},
].map((output) => ({
input,
output: output.file,
build: createJavascriptModuleBuilder({
platform: "universal",
addDefaultExport: output.format === "esm",
format: output.format,
umdVariableName: output.umdVariableName,
...buildOptions,
}),
})),
...createTypesConfig({ input, outputBaseName }),
];
}
export {
createNodejsFileConfig,
createPackageMetaFilesConfig,
createTypesConfig,
createUniversalFileConfig,
};
+9
View File
@@ -0,0 +1,9 @@
import pluginHermesBuildConfig from "./plugin-hermes.js";
import pluginOxcBuildConfig from "./plugin-oxc.js";
import prettierBuildConfig from "./prettier.js";
export default [
prettierBuildConfig,
pluginHermesBuildConfig,
pluginOxcBuildConfig,
];
+61
View File
@@ -0,0 +1,61 @@
import path from "node:path";
import { DIST_DIR, PACKAGES_DIRECTORY } from "../../utilities/index.js";
import { createJavascriptModuleBuilder } from "../builders/javascript-module.js";
import { getPackageFile } from "../utilities.js";
import {
createPackageMetaFilesConfig,
createTypesConfig,
} from "./config-helpers.js";
const packageConfig = {
packageName: "@prettier/plugin-hermes",
sourceDirectory: path.join(PACKAGES_DIRECTORY, "plugin-hermes"),
distDirectory: path.join(DIST_DIR, "plugin-hermes"),
modules: [],
};
const mainModule = {
name: "Entries",
files: [
{
input: "index.js",
output: "index.mjs",
build: createJavascriptModuleBuilder({
platform: "universal",
addDefaultExport: true,
replaceModule: [
{
module: getPackageFile("hermes-parser/dist/HermesParser.js"),
process(text) {
text =
'const Buffer = globalThis.Buffer ?? require("buffer/").Buffer;' +
text;
return text;
},
},
{
module: getPackageFile("hermes-parser/dist/HermesParserWASM.js"),
process(text) {
text = text.replaceAll("process.argv", "[]");
text = text.replaceAll('require("fs")', "undefined");
text = text.replaceAll('require("path")', "undefined");
text =
'const Buffer = globalThis.Buffer ?? require("buffer/").Buffer;' +
text;
return text;
},
},
],
format: "esm",
}),
playground: true,
},
...createTypesConfig({ input: "index.js", isPlugin: true }),
],
};
packageConfig.modules.push(mainModule, createPackageMetaFilesConfig());
export default packageConfig;
+72
View File
@@ -0,0 +1,72 @@
import path from "node:path";
import { DIST_DIR, PACKAGES_DIRECTORY } from "../../utilities/index.js";
import { createJavascriptModuleBuilder } from "../builders/javascript-module.js";
import buildOxcWasmParser from "../hacks/build-oxc-wasm-parser.js";
import { getPackageFile } from "../utilities.js";
import {
createPackageMetaFilesConfig,
createTypesConfig,
} from "./config-helpers.js";
const packageConfig = {
packageName: "@prettier/plugin-oxc",
sourceDirectory: path.join(PACKAGES_DIRECTORY, "plugin-oxc"),
distDirectory: path.join(DIST_DIR, "plugin-oxc"),
modules: [],
};
const mainModule = {
name: "Entries",
files: [
{
input: "index.js",
output: "index.mjs",
prepare: buildOxcWasmParser,
build: createJavascriptModuleBuilder({
input: "index.js",
output: "index.mjs",
format: "esm",
platform: "node",
external: ["oxc-parser"],
addDefaultExport: true,
}),
},
{
input: "index.js",
output: "index.browser.mjs",
build: createJavascriptModuleBuilder(async () => {
const { entry } = await buildOxcWasmParser();
return {
format: "esm",
platform: "universal",
addDefaultExport: true,
replaceModule: [
{
module: getPackageFile("oxc-parser/src-js/wasm.js"),
path: entry,
},
],
allowedWarnings: ["indirect-require", "package.json"],
};
}),
playground: true,
},
...createTypesConfig({ input: "index.js", isPlugin: true }),
],
};
packageConfig.modules.push(
mainModule,
createPackageMetaFilesConfig({
"package.json"(packageJson, { projectPackageJson }) {
return {
...packageJson,
dependencies: {
"oxc-parser": projectPackageJson.dependencies["oxc-parser"],
},
};
},
}),
);
export default packageConfig;
@@ -0,0 +1,72 @@
import path from "node:path";
function generatePackageJson(packageJson, { packageConfig }) {
const files = packageConfig.modules.flatMap((module) => module.files);
return {
...packageJson,
main: "./index.cjs",
exports: {
".": {
types: "./index.d.ts",
require: "./index.cjs",
browser: {
import: "./standalone.mjs",
default: "./standalone.js",
},
default: "./index.mjs",
},
"./*": "./*",
...Object.fromEntries(
files
.filter((file) => file.output.endsWith(".js"))
.map((file) => {
const basename = path.basename(file.output, ".js");
const isPlugin = file.output.startsWith("plugins/");
return [
isPlugin ? `./plugins/${basename}` : `./${basename}`,
{
types: `./${file.output.replace(/\.js$/u, ".d.ts")}`,
// `module-sync` condition can prevent CJS plugins from working: https://github.com/prettier/prettier/issues/17139
// Perform a test before re-adding it.
require: `./${file.output}`,
default: `./${file.output.replace(/\.js$/u, ".mjs")}`,
},
];
}),
),
// Legacy entries
// TODO: Remove bellow in v4
"./esm/standalone.mjs": "./standalone.mjs",
...Object.fromEntries(
files
.filter(
(file) =>
file.output.startsWith("plugins/") &&
file.output.endsWith(".js") &&
file.output !== "plugins/estree.js",
)
.flatMap((file) => {
let basename = path.basename(file.output, ".js");
if (basename === "acorn") {
basename = "espree";
}
return [
[`./parser-${basename}`, `./${file.output}`],
[`./parser-${basename}.js`, `./${file.output}`],
[
`./esm/parser-${basename}.mjs`,
`./${file.output.replace(/\.js$/u, ".mjs")}`,
],
];
}),
),
},
scripts: {
prepublishOnly:
"node -e \"assert.equal(require('.').version, require('..').version)\"",
},
};
}
export { generatePackageJson };
@@ -1,21 +1,19 @@
import fs from "node:fs";
import path from "node:path";
import url from "node:url";
import createEsmUtils from "esm-utils";
import { outdent } from "outdent";
import { copyFile, DIST_DIR, PROJECT_ROOT } from "../utilities/index.js";
import buildDependenciesLicense from "./build-dependencies-license.js";
import buildJavascriptModule from "./build-javascript-module.js";
import buildOxcWasmParser from "./build-oxc-wasm-parser.js";
import { DIST_DIR, PROJECT_ROOT } from "../../utilities/index.js";
import { createJavascriptModuleBuilder } from "../builders/javascript-module.js";
import esmifyTypescriptEslint from "../hacks/esmify-typescript-eslint.js";
import modifyTypescriptModule from "../hacks/modify-typescript-module.js";
import { getPackageFile } from "../utilities.js";
import {
buildPluginHermesPackageJson,
buildPluginOxcPackageJson,
buildPrettierPackageJson,
} from "./build-package-json.js";
import buildTypes from "./build-types.js";
import esmifyTypescriptEslint from "./esmify-typescript-eslint.js";
import modifyTypescriptModule from "./modify-typescript-module.js";
import { getPackageFile } from "./utilities.js";
createNodejsFileConfig,
createPackageMetaFilesConfig,
createTypesConfig,
createUniversalFileConfig,
} from "./config-helpers.js";
import { generatePackageJson } from "./prettier-package-json.js";
const {
require,
@@ -24,56 +22,6 @@ const {
} = createEsmUtils(import.meta);
const resolveEsmModulePath = (specifier) =>
url.fileURLToPath(importMetaResolve(specifier));
const copyFileBuilder = ({ packageConfig, file }) =>
copyFile(
path.join(PROJECT_ROOT, file.input),
path.join(packageConfig.distDirectory, file.output.file),
);
const oxcWasmParser = await buildOxcWasmParser();
function getTypesFileConfig({ input: jsFileInput, outputBaseName, isPlugin }) {
let input = jsFileInput;
if (!isPlugin) {
input = jsFileInput.replace(/\.[cm]?js$/u, ".d.ts");
if (!fs.existsSync(path.join(PROJECT_ROOT, input))) {
return;
}
}
return {
input,
output: {
file: `${outputBaseName}.d.ts`,
},
kind: "types",
isPlugin,
build: buildTypes,
};
}
/**
* @typedef {Object} BuildOptions
* @property {object[]?} replaceModule - Module replacements
* @property {string[]?} target - ESBuild targets
* @property {string[]?} external - array of paths that should not be included in the final bundle
* @property {boolean?} minify - disable code minification
*
* @typedef {Object} Output
* @property {"esm" | "umd" | "cjs" | "text" | "json"} format - File format
* @property {string} file - path of the output file in the `dist/` folder
* @property {string?} umdVariableName - name for the UMD file (for plugins, it'll be `prettierPlugins.${name}`)
*
* @typedef {Object} File
* @property {string} input - input of the file
* @property {Output} output - output of the file
* @property {"javascript" | "types" | "meta"} kind - file kind
* @property {function} build - file generate function
* @property {"node" | "universal"} platform - ESBuild platform
* @property {BuildOptions} buildOptions - ESBuild options
* @property {boolean?} isPlugin - file is a plugin
* @property {boolean?} addDefaultExport - add default export to bundle
*/
const extensions = {
esm: ".mjs",
@@ -81,6 +29,233 @@ const extensions = {
cjs: ".cjs",
};
const packageConfig = {
packageName: "prettier",
sourceDirectory: PROJECT_ROOT,
distDirectory: path.join(DIST_DIR, "prettier"),
modules: [],
};
const mainModule = {
name: "Main",
files: [
createNodejsFileConfig({
input: "src/index.js",
replaceModule: [
// `editorconfig` use a older version of `semver` and only uses `semver.gte`
{
module: require.resolve("editorconfig"),
find: 'var semver = __importStar(require("semver"));',
replacement: outdent`
var semver = {
gte: require(${JSON.stringify(
require.resolve("semver/functions/gte"),
)})
};
`,
},
{
module: require.resolve("n-readlines"),
find: "const readBuffer = new Buffer(this.options.readChunk);",
replacement:
"const readBuffer = Buffer.alloc(this.options.readChunk);",
},
// `parse-json` use another copy of `@babel/code-frame`
{
module: require.resolve("@babel/code-frame", {
paths: [require.resolve("parse-json")],
}),
path: require.resolve("@babel/code-frame"),
},
{
module: getPackageFile("json5/dist/index.mjs"),
find: "export default lib;",
replacement: "export default { parse };",
},
],
addDefaultExport: true,
reuseDocModule: true,
format: "esm",
}),
createNodejsFileConfig("src/index.cjs"),
createUniversalFileConfig({
input: "src/standalone.js",
umdVariableName: "prettier",
replaceModule: [
{
module: require.resolve("@babel/code-frame"),
process(text) {
text = text.replaceAll(
"var picocolors = require('picocolors');",
"",
);
text = text.replaceAll("var jsTokens = require('js-tokens');", "");
text = text.replaceAll(
"var helperValidatorIdentifier = require('@babel/helper-validator-identifier');",
"",
);
text = text.replaceAll(
/(?<=\n)let tokenize;\n\{\n.*?\n\}(?=\n)/gsu,
"",
);
text = text.replaceAll(
/(?<=\n)function highlight\(text\) \{\n.*?\n\}(?=\n)/gsu,
"function highlight(text) {return text}",
);
text = text.replaceAll(
/(?<=\n)function getDefs\(enabled\) \{\n.*?\n\}(?=\n)/gsu,
outdent`
function getDefs() {
return new Proxy({}, {get: () => (text) => text})
}
`,
);
text = text.replaceAll(
"const defsOn = buildDefs(picocolors.createColors(true));",
"",
);
text = text.replaceAll(
"const defsOff = buildDefs(picocolors.createColors(false));",
"",
);
text = text.replaceAll(
"const shouldHighlight = opts.forceColor || isColorSupported() && opts.highlightCode;",
"const shouldHighlight = false;",
);
text = text.replaceAll("exports.default = index;", "");
text = text.replaceAll("exports.highlight = highlight;", "");
return text;
},
},
// Smaller size
{
module: getPackageFile("picocolors/picocolors.browser.js"),
path: path.join(dirname, "../shims/colors.js"),
},
],
}),
].flat(),
};
for (const file of mainModule.files) {
if (file.output === "standalone.mjs") {
file.playground = true;
}
}
const cliModule = {
name: "CLI",
files: [
{
input: "bin/prettier.cjs",
outputBaseName: "bin/prettier",
target: ["node0.10"],
replaceModule: [
{
module: path.join(PROJECT_ROOT, "bin/prettier.cjs"),
process(text) {
text = text.replace(
"../src/cli/index.js",
"../internal/legacy-cli.mjs",
);
text = text.replace(
"../src/experimental-cli/index.js",
"../internal/experimental-cli.mjs",
);
return text;
},
},
],
},
{
input: "src/cli/index.js",
outputBaseName: "internal/legacy-cli",
external: ["tinybench"],
// TODO: Remove this when we drop support for Node.js v16
replaceModule: [
{
module: resolveEsmModulePath("@cacheable/memory"),
process: (text) =>
outdent`
const structuredClone =
globalThis.structuredClone ??
((value) => JSON.parse(JSON.stringify(value)));
${text}
`,
},
],
},
...[
{
input: "src/experimental-cli/index.js",
outputBaseName: "internal/experimental-cli",
replaceModule: [
{
module: getPackageFile("@prettier/cli/dist/prettier_serial.js"),
external: "./experimental-cli-worker.mjs",
},
{
module: getPackageFile("@prettier/cli/dist/prettier_parallel.js"),
find: 'new URL("./prettier_serial.js", import.meta.url)',
replacement:
'new URL("./experimental-cli-worker.mjs", import.meta.url)',
},
{
module: getPackageFile("json5/dist/index.mjs"),
find: "export default lib;",
replacement: "export default { parse };",
},
{
module: getPackageFile("js-yaml/dist/js-yaml.mjs"),
find: "var dump = dumper.dump;",
replacement: "var dump;",
},
],
},
{
input: "src/experimental-cli/worker.js",
outputBaseName: "internal/experimental-cli-worker",
},
].map(({ input, outputBaseName, replaceModule = [] }) => ({
input,
outputBaseName,
replaceModule: [
...replaceModule,
{
module: getPackageFile("@prettier/cli/dist/constants.js"),
path: path.join(
PROJECT_ROOT,
"src/experimental-cli/constants.evaluate.js",
),
},
...["package.json", "index.mjs"].map((file) => ({
module: getPackageFile(`prettier/${file}`),
external: `../${file}`,
})),
],
})),
].flatMap((file) => createNodejsFileConfig(file)),
};
const docModule = {
name: "Doc",
files: [
createUniversalFileConfig({
input: "src/document/public.js",
outputBaseName: "doc",
umdVariableName: "doc",
minify: false,
}),
].flat(),
};
const pluginFiles = [
"src/plugins/estree.js",
{
@@ -272,7 +447,7 @@ const pluginFiles = [
// Only needed if `range`/`loc` in parse options is `false`
{
module: getPackageFile("debug/src/browser.js"),
path: path.join(dirname, "./shims/debug.js"),
path: path.join(dirname, "../shims/debug.js"),
},
{
module: require.resolve("ts-api-utils"),
@@ -614,521 +789,67 @@ const pluginFiles = [
},
"src/plugins/html.js",
"src/plugins/yaml.js",
].map((file) => {
];
function createPluginModule(file) {
if (typeof file === "string") {
file = { input: file };
}
let { input, umdPropertyName, outputBaseName, ...buildOptions } = file;
const { input, ...buildOptions } = file;
const { pluginName } = input.match(
/\/plugins\/(?<pluginName>.*?)\.js$/u,
).groups;
outputBaseName ??= input.match(/\/plugins\/(?<outputBaseName>.*?)\.js$/u)
.groups.outputBaseName;
const umdVariableName = `prettierPlugins.${pluginName}`;
const umdVariableName = `prettierPlugins.${
umdPropertyName ?? outputBaseName
}`;
const files = [
{
format: "esm",
file: `${pluginName}${extensions.esm}`,
},
{
format: "umd",
file: `${pluginName}${extensions.umd}`,
umdVariableName,
},
].map((output) => ({
input,
output: `plugins/${output.file}`,
build: createJavascriptModuleBuilder({
platform: "universal",
addDefaultExport: output.format === "esm",
...buildOptions,
format: output.format,
umdVariableName: output.umdVariableName,
}),
playground: output.format === "esm",
}));
return {
input,
outputBaseName: `plugins/${outputBaseName}`,
umdVariableName,
buildOptions,
isPlugin: true,
};
});
const nonPluginUniversalFiles = [
{
input: "src/document/public.js",
outputBaseName: "doc",
umdVariableName: "doc",
minify: false,
},
{
input: "src/standalone.js",
umdVariableName: "prettier",
replaceModule: [
{
module: require.resolve("@babel/code-frame"),
process(text) {
text = text.replaceAll("var picocolors = require('picocolors');", "");
text = text.replaceAll("var jsTokens = require('js-tokens');", "");
text = text.replaceAll(
"var helperValidatorIdentifier = require('@babel/helper-validator-identifier');",
"",
);
text = text.replaceAll(
/(?<=\n)let tokenize;\n\{\n.*?\n\}(?=\n)/gsu,
"",
);
text = text.replaceAll(
/(?<=\n)function highlight\(text\) \{\n.*?\n\}(?=\n)/gsu,
"function highlight(text) {return text}",
);
text = text.replaceAll(
/(?<=\n)function getDefs\(enabled\) \{\n.*?\n\}(?=\n)/gsu,
outdent`
function getDefs() {
return new Proxy({}, {get: () => (text) => text})
}
`,
);
text = text.replaceAll(
"const defsOn = buildDefs(picocolors.createColors(true));",
"",
);
text = text.replaceAll(
"const defsOff = buildDefs(picocolors.createColors(false));",
"",
);
text = text.replaceAll(
"const shouldHighlight = opts.forceColor || isColorSupported() && opts.highlightCode;",
"const shouldHighlight = false;",
);
text = text.replaceAll("exports.default = index;", "");
text = text.replaceAll("exports.highlight = highlight;", "");
return text;
},
},
// Smaller size
{
module: getPackageFile("picocolors/picocolors.browser.js"),
path: path.join(dirname, "./shims/colors.js"),
},
],
},
].map((file) => {
const {
input,
outputBaseName = path.basename(input, ".js"),
umdVariableName,
...buildOptions
} = file;
return {
input,
outputBaseName,
umdVariableName,
buildOptions,
};
});
const universalFiles = [...nonPluginUniversalFiles, ...pluginFiles].flatMap(
(file) => {
let { input, outputBaseName, umdVariableName, buildOptions, isPlugin } =
file;
outputBaseName ??= path.basename(input);
return [
...[
{
format: "esm",
file: `${outputBaseName}${extensions.esm}`,
},
{
format: "umd",
file: `${outputBaseName}${extensions.umd}`,
umdVariableName,
},
].map((output) => ({
name: `Plugin[${pluginName}]`,
files: [
...files,
...createTypesConfig({
input,
output,
platform: "universal",
buildOptions: {
addDefaultExport: output.format === "esm",
...buildOptions,
},
isPlugin,
build: buildJavascriptModule,
kind: "javascript",
playground: output.format === "esm" && outputBaseName !== "doc",
})),
getTypesFileConfig({ input, outputBaseName, isPlugin }),
];
},
outputBaseName: `plugins/${pluginName}`,
isPlugin: true,
}),
],
};
}
const pluginModules = pluginFiles.map((file) => createPluginModule(file));
packageConfig.modules.push(
mainModule,
cliModule,
docModule,
...pluginModules,
createPackageMetaFilesConfig({
"package.json": generatePackageJson,
packageDisplayName: "Prettier",
}),
);
const nodejsFiles = [
{
input: "src/index.js",
replaceModule: [
// `editorconfig` use a older version of `semver` and only uses `semver.gte`
{
module: require.resolve("editorconfig"),
find: 'var semver = __importStar(require("semver"));',
replacement: `
var semver = {
gte: require(${JSON.stringify(
require.resolve("semver/functions/gte"),
)})
};
`,
},
{
module: require.resolve("n-readlines"),
find: "const readBuffer = new Buffer(this.options.readChunk);",
replacement: "const readBuffer = Buffer.alloc(this.options.readChunk);",
},
// `parse-json` use another copy of `@babel/code-frame`
{
module: require.resolve("@babel/code-frame", {
paths: [require.resolve("parse-json")],
}),
path: require.resolve("@babel/code-frame"),
},
{
module: getPackageFile("json5/dist/index.mjs"),
find: "export default lib;",
replacement: "export default { parse };",
},
],
addDefaultExport: true,
reuseDocModule: true,
},
{
input: "src/index.cjs",
},
{
input: "bin/prettier.cjs",
outputBaseName: "bin/prettier",
target: ["node0.10"],
replaceModule: [
{
module: path.join(PROJECT_ROOT, "bin/prettier.cjs"),
process(text) {
text = text.replace(
"../src/cli/index.js",
"../internal/legacy-cli.mjs",
);
text = text.replace(
"../src/experimental-cli/index.js",
"../internal/experimental-cli.mjs",
);
return text;
},
},
],
},
{
input: "src/cli/index.js",
outputBaseName: "internal/legacy-cli",
external: ["tinybench"],
// TODO: Remove this when we drop support for Node.js v16
replaceModule: [
{
module: resolveEsmModulePath("@cacheable/memory"),
process: (text) =>
outdent`
const structuredClone =
globalThis.structuredClone ??
((value) => JSON.parse(JSON.stringify(value)));
${text}
`,
},
],
},
...[
{
input: "src/experimental-cli/index.js",
outputBaseName: "internal/experimental-cli",
replaceModule: [
{
module: getPackageFile("@prettier/cli/dist/prettier_serial.js"),
external: "./experimental-cli-worker.mjs",
},
{
module: getPackageFile("@prettier/cli/dist/prettier_parallel.js"),
find: 'new URL("./prettier_serial.js", import.meta.url)',
replacement:
'new URL("./experimental-cli-worker.mjs", import.meta.url)',
},
{
module: getPackageFile("json5/dist/index.mjs"),
find: "export default lib;",
replacement: "export default { parse };",
},
{
module: getPackageFile("js-yaml/dist/js-yaml.mjs"),
find: "var dump = dumper.dump;",
replacement: "var dump;",
},
],
},
{
input: "src/experimental-cli/worker.js",
outputBaseName: "internal/experimental-cli-worker",
},
].map(({ input, outputBaseName, replaceModule = [] }) => ({
input,
outputBaseName,
replaceModule: [
...replaceModule,
{
module: getPackageFile("@prettier/cli/dist/constants.js"),
path: path.join(
PROJECT_ROOT,
"src/experimental-cli/constants.evaluate.js",
),
},
...["package.json", "index.mjs"].map((file) => ({
module: getPackageFile(`prettier/${file}`),
external: `../${file}`,
})),
],
})),
].flatMap((file) => {
let { input, output, outputBaseName, ...buildOptions } = file;
const format = input.endsWith(".cjs") ? "cjs" : "esm";
outputBaseName ??= path.basename(input, path.extname(input));
return [
{
input,
output: {
format,
file: `${outputBaseName}${extensions[format]}`,
},
platform: "node",
buildOptions,
build: buildJavascriptModule,
kind: "javascript",
},
getTypesFileConfig({ input, outputBaseName }),
];
});
const metaFiles = [
{
input: "package.json",
output: {
format: "json",
},
build: buildPrettierPackageJson,
},
{
input: "README.md",
build: copyFileBuilder,
},
{
input: "LICENSE",
build: copyFileBuilder,
},
{
output: {
file: "THIRD-PARTY-NOTICES.md",
},
build: buildDependenciesLicense,
},
].map((file) => ({
...file,
output: { file: file.input, ...file.output },
kind: "meta",
}));
/** @type {Files[]} */
const files = [...nodejsFiles, ...universalFiles, ...metaFiles].filter(Boolean);
export default [
{
packageName: "prettier",
// Used in THIRD-PARTY-NOTICES.md
packageDisplayName: "Prettier",
distDirectory: path.join(DIST_DIR, "prettier"),
files,
},
{
packageName: "@prettier/plugin-oxc",
distDirectory: path.join(DIST_DIR, "plugin-oxc"),
files: [
...[
{
input: "packages/plugin-oxc/index.js",
addDefaultExport: true,
},
].flatMap((file) => {
let { input, output, outputBaseName, ...buildOptions } = file;
const format = input.endsWith(".cjs") ? "cjs" : "esm";
outputBaseName ??= path.basename(input, path.extname(input));
return [
{
input,
output: {
format,
file: `${outputBaseName}${extensions[format]}`,
},
platform: "node",
buildOptions: {
...buildOptions,
external: ["oxc-parser"],
},
build: buildJavascriptModule,
kind: "javascript",
},
{
input,
output: {
format,
file: `${outputBaseName}.browser${extensions[format]}`,
},
platform: "universal",
buildOptions: {
...buildOptions,
replaceModule: [
...(buildOptions.replaceModule ?? []),
{
module: getPackageFile("oxc-parser/src-js/wasm.js"),
path: oxcWasmParser.entry,
},
],
allowedWarnings: ["indirect-require", "package.json"],
},
build: buildJavascriptModule,
kind: "javascript",
playground: true,
},
getTypesFileConfig({ input, outputBaseName, isPlugin: true }),
];
}),
...[
{
input: "packages/plugin-oxc/package.json",
output: {
file: "package.json",
format: "json",
},
build: buildPluginOxcPackageJson,
},
{
input: "packages/plugin-oxc/README.md",
output: {
file: "README.md",
},
build: copyFileBuilder,
},
{
input: "LICENSE",
output: {
file: "LICENSE",
},
build: copyFileBuilder,
},
{
output: {
file: "THIRD-PARTY-NOTICES.md",
},
build: buildDependenciesLicense,
},
].map((file) => ({
...file,
output: { file: file.input, ...file.output },
kind: "meta",
})),
],
},
{
packageName: "@prettier/plugin-hermes",
distDirectory: path.join(DIST_DIR, "plugin-hermes"),
files: [
...[
{
input: "packages/plugin-hermes/index.js",
addDefaultExport: true,
replaceModule: [
{
module: require.resolve("hermes-parser/dist/HermesParser.js"),
process(text) {
text =
'const Buffer = globalThis.Buffer ?? require("buffer/").Buffer;' +
text;
return text;
},
},
{
module: require.resolve("hermes-parser/dist/HermesParserWASM.js"),
process(text) {
text = text.replaceAll("process.argv", "[]");
text = text.replaceAll('require("fs")', "undefined");
text = text.replaceAll('require("path")', "undefined");
text =
'const Buffer = globalThis.Buffer ?? require("buffer/").Buffer;' +
text;
return text;
},
},
],
},
].flatMap((file) => {
let { input, output, outputBaseName, ...buildOptions } = file;
outputBaseName ??= path.basename(input, path.extname(input));
const format = input.endsWith(".cjs") ? "cjs" : "esm";
return [
{
input,
output: {
format,
file: `${outputBaseName}${extensions[format]}`,
},
platform: "universal",
buildOptions: {
addDefaultExport: true,
...buildOptions,
},
isPlugin: true,
build: buildJavascriptModule,
playground: true,
kind: "javascript",
},
getTypesFileConfig({ input, outputBaseName, isPlugin: true }),
];
}),
...[
{
input: "packages/plugin-hermes/package.json",
output: {
file: "package.json",
format: "json",
},
build: buildPluginHermesPackageJson,
},
{
input: "packages/plugin-hermes/README.md",
output: {
file: "README.md",
},
build: copyFileBuilder,
},
{
input: "LICENSE",
output: {
file: "LICENSE",
},
build: copyFileBuilder,
},
{
output: {
file: "THIRD-PARTY-NOTICES.md",
},
build: buildDependenciesLicense,
},
].map((file) => ({
...file,
output: { file: file.input, ...file.output },
kind: "meta",
})),
],
},
];
export default packageConfig;
+3 -1
View File
@@ -25,7 +25,9 @@ function parseArguments() {
}
const result = {
files: Array.isArray(values.file) ? new Set(values.file) : undefined,
files: Array.isArray(values.file)
? new Set(values.file.map((file) => path.join(DIST_DIR, file)))
: undefined,
playground: values.playground,
printSize: values["print-size"],
compareSize: values["compare-size"],
+23 -14
View File
@@ -2,27 +2,36 @@ import assert from "node:assert";
import fs from "node:fs/promises";
import path from "node:path";
import eslintPluginCompat from "eslint-plugin-compat";
import buildConfig from "./build/config.js";
import packageBuildConfigs from "./build/packages/index.js";
import { DIST_DIR } from "./utilities/index.js";
const { browserslist: targets } = JSON.parse(
await fs.readFile(new URL("../package.json", import.meta.url)),
);
function getProductionFiles(platform) {
return buildConfig
.flatMap((project) =>
project.files
.filter(
(file) => file.kind === "javascript" && file.platform === platform,
)
.map((file) => path.join(project.distDirectory, file.output.file)),
)
.map((file) => path.relative(DIST_DIR, file).replaceAll("\\", "/"));
}
const jsFiles = packageBuildConfigs
.flatMap((packageConfig) =>
packageConfig.modules.flatMap((module) =>
module.files
.filter((file) => /\.[cm]?js$/u.test(file.output))
.map((file) => path.join(packageConfig.distDirectory, file.output)),
),
)
.map((file) => path.relative(DIST_DIR, file).replaceAll("\\", "/"));
const browserFiles = getProductionFiles("universal");
const nodejsFiles = getProductionFiles("node");
const nodejsFiles = jsFiles.filter(
(file) =>
file.endsWith(".cjs") ||
[
"prettier/index.mjs",
"prettier/internal/legacy-cli.mjs",
"prettier/internal/experimental-cli.mjs",
"prettier/internal/experimental-cli-worker.mjs",
"plugin-oxc/index.mjs",
].includes(file),
);
const browserFiles = jsFiles.filter((file) => !nodejsFiles.includes(file));
assert.ok(browserFiles.length > 0);
assert.ok(nodejsFiles.length > 0);
@@ -4,8 +4,8 @@ import * as importMetaResolve from "import-meta-resolve";
import { outdent } from "outdent";
import * as prettier from "prettier";
import * as typescript from "typescript";
import { modifyTypescriptModule } from "./build/modify-typescript-module.js";
import UNUSED_SPECIFIERS from "./build/typescript-unused-specifiers.js";
import { modifyTypescriptModule } from "./build/hacks/modify-typescript-module.js";
import UNUSED_SPECIFIERS from "./build/hacks/typescript-unused-specifiers.js";
async function getRemovedSpecifiers(code, exports) {
let errors = [];
@@ -47,7 +47,7 @@ async function main() {
.sort();
await fs.writeFile(
new URL("./build/typescript-unused-specifiers.js", import.meta.url),
new URL("./build/hacks/typescript-unused-specifiers.js", import.meta.url),
await prettier.format(
outdent`
export default new Set(${JSON.stringify(specifiers, undefined, 2)});
+64 -51
View File
@@ -2,7 +2,7 @@ import { createRequire } from "node:module";
import path from "node:path";
import url from "node:url";
import webpack from "webpack";
import packageConfigs from "../../build/config.js";
import packageBuildConfigs from "../../build/packages/index.js";
function runWebpack(config) {
return new Promise((resolve, reject) => {
@@ -29,60 +29,73 @@ const TEMPORARY_DIRECTORY = url.fileURLToPath(
new URL("./.tmp", import.meta.url),
);
for (const packageConfig of packageConfigs) {
const { packageName, distDirectory, files } = packageConfig;
// Node.js only files
const exclude = new Map([
[
"prettier",
[
"index.mjs",
"internal/legacy-cli.mjs",
"internal/experimental-cli.mjs",
"internal/experimental-cli-worker.mjs",
],
],
["@prettier/plugin-oxc", ["index.mjs"]],
]);
if (packageName === "@prettier/plugin-oxc") {
continue;
}
for (const packageConfig of packageBuildConfigs) {
const { packageName, distDirectory, modules } = packageConfig;
const excludedFiles = new Set(exclude.get(packageName));
/* `require` in `parser-typescript.js`, #12338 */
for (const file of files) {
if (file.platform !== "universal") {
continue;
}
console.log(`${file.output.file}: `);
const stats = await runWebpack({
mode: "production",
entry: path.join(distDirectory, file.output.file),
output: {
path: TEMPORARY_DIRECTORY,
filename: `${file.output.file}.[contenthash:7].${
file.output.format === "esm" ? "mjs" : "cjs"
}`,
},
performance: { hints: false },
optimization: { minimize: false },
});
const result = stats.toJson();
const { warnings, assets } = result;
if (warnings.length > 0) {
console.log(warnings);
throw new Error("Unexpected webpack warning.");
}
if (assets.length > 1) {
console.log(assets);
throw new Error("Unexpected assets.");
}
const outputFileName = assets[0].name;
const outputFile = path.join(TEMPORARY_DIRECTORY, outputFileName);
const require = createRequire(import.meta.url);
try {
if (file.output.format !== "esm") {
require(outputFile);
} else {
await import(url.pathToFileURL(outputFile));
for (const module of modules) {
for (const file of module.files) {
if (!/\.m?js$/u.test(file.output) || excludedFiles.has(file.output)) {
continue;
}
} catch (error) {
console.log(`'${outputFileName}' is not functional.`);
throw error;
}
console.log(`${file.output}: `);
console.log(" Passed.");
const stats = await runWebpack({
mode: "production",
entry: path.join(distDirectory, file.output),
output: {
path: TEMPORARY_DIRECTORY,
filename: `${file.output}.[contenthash:7].${
file.output.endsWith(".mjs") ? "mjs" : "cjs"
}`,
},
performance: { hints: false },
optimization: { minimize: false },
});
const result = stats.toJson();
const { warnings, assets } = result;
if (warnings.length > 0) {
console.log(warnings);
throw new Error("Unexpected webpack warning.");
}
if (assets.length > 1) {
console.log(assets);
throw new Error("Unexpected assets.");
}
const outputFileName = assets[0].name;
const outputFile = path.join(TEMPORARY_DIRECTORY, outputFileName);
const require = createRequire(import.meta.url);
try {
if (file.output.endsWith(".mjs")) {
await import(url.pathToFileURL(outputFile));
} else {
require(outputFile);
}
} catch (error) {
console.log(`'${outputFileName}' is not functional.`);
throw error;
}
console.log(" Passed.");
}
}
}
+4
View File
@@ -36,15 +36,19 @@ const PROJECT_ROOT = url.fileURLToPath(new URL("../../", import.meta.url));
const DIST_DIR = path.join(PROJECT_ROOT, "dist");
const WEBSITE_DIR = path.join(PROJECT_ROOT, "website");
const SOURCE_DIR = path.join(PROJECT_ROOT, "src");
const TEMPORARY_DIRECTORY = path.join(PROJECT_ROOT, ".tmp");
const PACKAGES_DIRECTORY = path.join(PROJECT_ROOT, "packages");
const PRODUCTION_MINIMAL_NODE_JS_VERSION = "14";
export {
copyFile,
DIST_DIR,
PACKAGES_DIRECTORY,
PRODUCTION_MINIMAL_NODE_JS_VERSION,
PROJECT_ROOT,
readJson,
SOURCE_DIR,
TEMPORARY_DIRECTORY,
WEBSITE_DIR,
writeFile,
writeJson,