Run browser tests on UMD version (#18526)

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
fisker Cheungandautofix-ci[bot] authored and GitHub committed 2025-12-25 00:08:21 +08:00
1 parent 06a73b700b
commit b7055dbfb4
8 files changed
+169 -57

No files matched your search

+10 -3
View File
@@ -145,7 +145,7 @@ jobs:
working-directory: dist/prettier
run: node bin/prettier.cjs --debug-benchmark package.json 2>&1 >/dev/null | grep "Latency avg" || exit 1
browser-test:
browser:
timeout-minutes: 15
strategy:
fail-fast: false
@@ -191,8 +191,15 @@ jobs:
name: dist
path: dist
- name: Run Tests
run: yarn test:production-browser
- name: Run Tests (ESM)
env:
TEST_BUNDLE_FORMAT: esm
run: yarn test:production:browser
- name: Run Tests (UMD)
env:
TEST_BUNDLE_FORMAT: umd
run: yarn test:production:browser
cli:
name: CLI
+5 -3
View File
@@ -191,9 +191,11 @@
"test:production-node14": "cross-env NODE_ENV=production yarn test:node14",
"test:production-standalone": "cross-env TEST_STANDALONE=1 yarn test:production",
"test:production-standalone-node14": "cross-env TEST_STANDALONE=1 yarn test:production-node14",
"test:production-browser": "cross-env NODE_ENV=production TEST_RUNTIME=browser yarn test",
"test:production-browser:chrome": "cross-env TEST_RUNTIME_BROWSER=chrome yarn test:production-browser",
"test:production-browser:firefox": "cross-env TEST_RUNTIME_BROWSER=firefox yarn test:production-browser",
"test:production:browser": "cross-env TEST_RUNTIME=browser yarn test:production",
"test:production:browser:chrome": "cross-env TEST_RUNTIME_BROWSER=chrome yarn test:production:browser",
"test:production:browser:firefox": "cross-env TEST_RUNTIME_BROWSER=firefox yarn test:production:browser",
"test:production:browser:chrome:umd": "cross-env TEST_BUNDLE_FORMAT=umd yarn test:production:browser:chrome",
"test:production:browser:firefox:umd": "cross-env TEST_BUNDLE_FORMAT=umd yarn test:production:browser:firefox",
"test:production-lint": "eslint dist --config=./scripts/production-eslint-config.js --quiet --format friendly",
"perf": "yarn && yarn build && cross-env NODE_ENV=production node ./dist/prettier/bin/prettier.cjs",
"perf:inspect": "yarn && yarn build && cross-env NODE_ENV=production node --inspect-brk ./dist/prettier/bin/prettier.cjs",
+1 -2
View File
@@ -6,7 +6,6 @@ function withPlugins(
optionsArgumentIndex = 1, // Usually `options` is the 2nd argument
) {
// Returns Promises to consistent with functions in `index.js`
// eslint-disable-next-line require-await
return async (...args) => {
const options = args[optionsArgumentIndex] ?? {};
const plugins = options.plugins ?? [];
@@ -16,7 +15,7 @@ function withPlugins(
plugins: Array.isArray(plugins) ? plugins : Object.values(plugins),
};
return fn(...args);
return await fn(...args);
};
}
+7 -4
View File
@@ -3,7 +3,10 @@ import { startServer } from "./server.js";
import { requestFromNode } from "./utilities.js";
async function getBrowserPrettier() {
/** @type {"chrome" | "firefox"} */
const browserName = process.env.TEST_RUNTIME_BROWSER ?? "chrome";
/** @type {"esm" | "umd"} */
const prettierBundleFormat = process.env.TEST_BUNDLE_FORMAT ?? "esm";
const browser = await launchBrowser({ browser: browserName });
process.once("exit", async () => {
@@ -16,14 +19,14 @@ async function getBrowserPrettier() {
});
const page = await browser.newPage();
await page.goto(server.url + "/esm.html");
await page.goto(server.url);
const proxyFunction = (accessPath, optionsIndex = 1) =>
requestFromNode(
(...arguments_) =>
page.evaluate(
([arguments_, accessPath]) => {
const prettier = globalThis.__prettier;
([arguments_, accessPath, prettierBundleFormat]) => {
const prettier = globalThis[`__prettier_${prettierBundleFormat}`];
let function_ = prettier;
for (const property of accessPath.split(".")) {
function_ = function_[property];
@@ -31,7 +34,7 @@ async function getBrowserPrettier() {
return function_(...arguments_);
},
[arguments_, accessPath],
[arguments_, accessPath, prettierBundleFormat],
),
{
browser: browserName,
@@ -6,7 +6,10 @@
<title>Document</title>
</head>
<body>
<div>Use <code>__prettier</code> to debug in console.</div>
<div>
Use <code>__prettier_esm</code> or <code>__prettier_umd</code> to debug in
console.
</div>
<script type="module" src="main.js"></script>
</body>
</html>
+131 -41
View File
@@ -1,4 +1,4 @@
import { responseInBrowser } from "./utilities.js";
import { ensurePromise, responseInBrowser } from "./utilities.js";
const browser = navigator.userAgent.includes("Firefox/") ? "firefox" : "chrome";
@@ -22,50 +22,140 @@ const esmFiles = {
"plugin-oxc/index.browser.mjs",
],
};
const umdFiles = {
prettier: "prettier/standalone.js",
plugins: [
"prettier/plugins/angular.js",
"prettier/plugins/graphql.js",
"prettier/plugins/typescript.js",
"prettier/plugins/estree.js",
"prettier/plugins/markdown.js",
"prettier/plugins/acorn.js",
"prettier/plugins/glimmer.js",
"prettier/plugins/postcss.js",
"prettier/plugins/babel.js",
"prettier/plugins/html.js",
"prettier/plugins/yaml.js",
"prettier/plugins/flow.js",
"prettier/plugins/meriyah.js",
// These two plugins don't have umd version
"plugin-hermes/index.mjs",
"plugin-oxc/index.browser.mjs",
],
};
let prettier;
let builtinPlugins;
async function loadScript(url) {
const { document } = globalThis;
const { head } = document;
const script = Object.assign(document.createElement("script"), { src: url });
function proxyFunction(accessPath, optionsIndex = 1) {
let function_;
return responseInBrowser(
async (...arguments_) => {
if (!prettier) {
[prettier, ...builtinPlugins] = await Promise.all(
[esmFiles.prettier, ...esmFiles.plugins].map(
(file) => import(`/${file}`),
),
);
}
function on(event, callback) {
script.addEventListener(event, callback, { once: true, passive: true });
}
if (!function_) {
function_ = prettier;
for (const property of accessPath.split(".")) {
function_ = function_[property];
}
}
try {
await new Promise((resolve, reject) => {
on("load", resolve);
const options = arguments_[optionsIndex] ?? {};
on("error", () => {
reject(new Error(`Failed to load script '${url}'.`));
});
arguments_[optionsIndex] = {
...options,
plugins: [...builtinPlugins, ...(options.plugins || [])],
};
return function_(...arguments_);
},
{
browser,
accessPath,
optionsIndex,
},
);
head.appendChild(script);
});
} finally {
head.removeChild(script);
}
}
globalThis.__prettier = {
formatWithCursor: proxyFunction("formatWithCursor"),
getSupportInfo: proxyFunction("getSupportInfo", /* optionsIndex */ 0),
__debug: {
parse: proxyFunction("__debug.parse"),
},
};
async function loadEsmPrettier() {
const [prettier, ...builtinPlugins] = await Promise.all(
[esmFiles.prettier, ...esmFiles.plugins].map((file) => import(`/${file}`)),
);
return { prettier, builtinPlugins };
}
async function loadUmdPrettier() {
const { umdPluginFiles, esmPluginFiles } = Object.groupBy(
umdFiles.plugins,
(file) => `${file.endsWith(".mjs") ? "esm" : "umd"}PluginFiles`,
);
const [, esmPlugins] = await Promise.all([
Promise.all(
[umdFiles.prettier, ...umdPluginFiles].map((file) =>
loadScript(`/${file}`),
),
),
Promise.all(esmPluginFiles.map((file) => import(`/${file}`))),
]);
const { prettier } = globalThis;
const builtinPlugins = [
...Object.values(globalThis.prettierPlugins),
...esmPlugins,
];
delete globalThis.prettier;
delete globalThis.prettierPlugins;
return { prettier, builtinPlugins };
}
/**
@param {"esm" | "umd"} prettierBundleFormat
*/
function createPrettier(prettierBundleFormat) {
let cache;
function loadPrettier() {
if (!cache) {
cache =
prettierBundleFormat === "esm" ? loadEsmPrettier() : loadUmdPrettier();
}
return cache;
}
const functions = new Map();
function proxyFunction(accessPath, optionsIndex = 1) {
let function_;
return responseInBrowser(
async (...arguments_) => {
const { prettier, builtinPlugins } = await loadPrettier();
if (!functions.has(accessPath)) {
function_ = prettier;
for (const property of accessPath.split(".")) {
function_ = function_[property];
}
functions.set(accessPath, function_);
}
const options = arguments_[optionsIndex] ?? {};
arguments_[optionsIndex] = {
...options,
plugins: [...builtinPlugins, ...(options.plugins || [])],
};
return ensurePromise(function_(...arguments_));
},
{
browser,
accessPath,
optionsIndex,
},
);
}
return {
formatWithCursor: proxyFunction("formatWithCursor"),
getSupportInfo: proxyFunction("getSupportInfo", /* optionsIndex */ 0),
__debug: {
parse: proxyFunction("__debug.parse"),
},
};
}
globalThis.__prettier_esm = createPrettier("esm");
globalThis.__prettier_umd = createPrettier("umd");
+2 -2
View File
@@ -9,7 +9,7 @@ async function getContent(url) {
assert.ok(url.startsWith("/"));
url = url.slice(1);
if (url === "esm.html" || url === "main.js" || url === "utilities.js") {
if (url === "index.html" || url === "main.js" || url === "utilities.js") {
return await fs.readFile(new URL(url, import.meta.url), "utf8");
}
@@ -29,7 +29,7 @@ async function startServer({ silent = false, port: preferredPort } = {}) {
}
if (url === "/") {
url = "/esm.html";
url = "/index.html";
}
// Only allow `.mjs`, `.js` and `.html`
+9 -1
View File
@@ -157,4 +157,12 @@ function serializeValue(value) {
return value;
}
export { requestFromNode, responseInBrowser };
const ensurePromise = (value) => {
if (!(value instanceof Promise)) {
throw new TypeError("Expected value to be a 'Promise'.");
}
return value;
};
export { ensurePromise, requestFromNode, responseInBrowser };