Add playground for current version (#18830)

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: fisker <lionkay@gmail.com>
This commit is contained in:
authored and GitHub committed 2026-02-05 22:29:59 +08:00
1 parent efcc72d91b
commit d9bd8abfeb
17 files changed
+412 -199

No files matched your search

+7 -1
View File
@@ -1,6 +1,8 @@
const fs = require("node:fs"); const fs = require("node:fs");
const path = require("node:path"); const path = require("node:path");
const root = path.join(__dirname, "../../");
function setupVscode() { function setupVscode() {
const settingsFile = path.join(__dirname, "../../.vscode/settings.json"); const settingsFile = path.join(__dirname, "../../.vscode/settings.json");
@@ -27,7 +29,11 @@ module.exports = {
name: "plugin-prepare", name: "plugin-prepare",
factory: () => ({ factory: () => ({
hooks: { hooks: {
async afterAllInstalled() { async afterAllInstalled({ cwd }) {
if (cwd !== root) {
return;
}
setupVscode(); setupVscode();
try { try {
await buildBabelCodeFrameForTest(); await buildBabelCodeFrameForTest();
+62 -36
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env node #!/usr/bin/env node
import fs from "node:fs/promises"; import assert from "node:assert/strict";
import path from "node:path"; import path from "node:path";
import url from "node:url"; import url from "node:url";
import esbuild from "esbuild"; import esbuild from "esbuild";
@@ -13,58 +13,61 @@ import {
PROJECT_ROOT, PROJECT_ROOT,
WEBSITE_DIR, WEBSITE_DIR,
writeFile, writeFile,
writeJson,
} from "./utilities/index.js"; } from "./utilities/index.js";
const runYarn = (command, args, options) => const runYarn = (command, args, options) =>
spawn("yarn", [command, ...args], { stdio: "inherit", ...options }); spawn("yarn", [command, ...args], { stdio: "inherit", ...options });
const IS_PULL_REQUEST = process.env.PULL_REQUEST === "true"; const IS_PULL_REQUEST = process.env.PULL_REQUEST === "true";
const PACKAGES_DIRECTORY = IS_PULL_REQUEST const NODE_MODULES_DIR = url.fileURLToPath(
? DIST_DIR new URL("../node_modules", import.meta.url),
: url.fileURLToPath(new URL("../node_modules", import.meta.url)); );
const PLAYGROUND_LIB_DIRECTORY = path.join(WEBSITE_DIR, "static/lib");
async function writeScript(file, code) { async function writeScript(file, code) {
const { code: minified } = await esbuild.transform(code, { const { code: minified } = await esbuild.transform(code, {
loader: "js", loader: "js",
minify: true, minify: true,
}); });
await writeFile(path.join(PLAYGROUND_LIB_DIRECTORY, file), minified.trim()); await writeFile(file, minified.trim());
} }
async function buildPrettier() { /** @param {"stable" | "next"} version */
// --- Build prettier for PR --- async function buildPlaygroundFiles(version) {
const packageJsonFile = path.join(PROJECT_ROOT, "package.json"); assert.ok(version === "stable" || version === "next");
const packageJsonContent = await fs.readFile(packageJsonFile);
const packageJson = JSON.parse(packageJsonContent);
await writeJson(packageJsonFile, {
...packageJson,
version: `999.999.999-pr.${process.env.REVIEW_ID}`,
});
try { let packagesDirectory;
await runYarn("build", ["--clean", "--playground"], { const versionData = { name: version };
cwd: PROJECT_ROOT, const libDirectory = path.join(WEBSITE_DIR, `static/lib/${version}/`);
});
} finally { if (version === "stable") {
// restore packagesDirectory = NODE_MODULES_DIR;
await writeFile(packageJsonFile, packageJsonContent); const { version: prettierVersion } = await import(
url.pathToFileURL(path.join(packagesDirectory, "prettier/standalone.mjs"))
);
versionData.version = prettierVersion;
}
if (version === "next") {
packagesDirectory = DIST_DIR;
versionData.gitTree = await getGitTreeInformation();
if (IS_PULL_REQUEST) {
versionData.pr = process.env.REVIEW_ID;
}
} }
}
async function buildPlaygroundFiles() {
const pluginFiles = []; const pluginFiles = [];
// Builtin plugins // Builtin plugins
for (const fileName of await fastGlob(["plugins/*.mjs"], { for (const fileName of await fastGlob(["plugins/*.mjs"], {
cwd: path.join(PACKAGES_DIRECTORY, "prettier"), cwd: path.join(packagesDirectory, "prettier"),
ignore: ["plugins/flow*"],
})) { })) {
pluginFiles.push(`prettier/${fileName}`); pluginFiles.push(`prettier/${fileName}`);
} }
// TODO: Support stable version // TODO: Support stable version
// External plugins // External plugins
if (IS_PULL_REQUEST) { if (version === "next") {
for (const pluginName of ["plugin-hermes"]) { for (const pluginName of ["plugin-hermes"]) {
pluginFiles.push(`${pluginName}/index.mjs`); pluginFiles.push(`${pluginName}/index.mjs`);
} }
@@ -74,6 +77,7 @@ async function buildPlaygroundFiles() {
} }
const packageManifest = { const packageManifest = {
version: versionData,
prettier: { prettier: {
file: "prettier/standalone.mjs", file: "prettier/standalone.mjs",
}, },
@@ -82,7 +86,7 @@ async function buildPlaygroundFiles() {
const plugin = { file }; const plugin = { file };
const pluginModule = await import( const pluginModule = await import(
url.pathToFileURL(path.join(PACKAGES_DIRECTORY, file)) url.pathToFileURL(path.join(packagesDirectory, file))
); );
for (const property of ["languages", "options", "defaultOptions"]) { for (const property of ["languages", "options", "defaultOptions"]) {
@@ -107,24 +111,46 @@ async function buildPlaygroundFiles() {
await Promise.all([ await Promise.all([
...[packageManifest.prettier, ...packageManifest.plugins].map(({ file }) => ...[packageManifest.prettier, ...packageManifest.plugins].map(({ file }) =>
copyFile( copyFile(
path.join(PACKAGES_DIRECTORY, file), path.join(packagesDirectory, file),
path.join(PLAYGROUND_LIB_DIRECTORY, file), path.join(libDirectory, file),
), ),
), ),
writeScript( writeScript(
"package-manifest.mjs", path.join(libDirectory, "package-manifest.mjs"),
`export default ${serialize(packageManifest, { space: 2 })};`, `export default ${serialize(packageManifest, { space: 2 })};`,
), ),
]); ]);
} }
if (IS_PULL_REQUEST) { async function getGitTreeInformation() {
console.log("Building prettier..."); const { stdout: branch } = await spawn("git", [
await buildPrettier(); "rev-parse",
"--abbrev-ref",
"HEAD",
]);
const { stdout: diff } = await spawn("git", ["diff", "--name-only"]);
if (diff) {
return { branch };
}
const { stdout: commit } = await spawn("git", [
"rev-parse",
"--short",
"HEAD",
]);
return { branch, commit };
} }
console.log("Preparing files for playground..."); console.log("Building prettier...");
await buildPlaygroundFiles(); await runYarn("build", ["--clean", "--playground"], { cwd: PROJECT_ROOT });
console.log("Preparing files for playground (stable)...");
await buildPlaygroundFiles("stable");
console.log("Preparing files for playground (next)...");
await buildPlaygroundFiles("next");
// --- Site --- // --- Site ---
console.log("Installing website dependencies..."); console.log("Installing website dependencies...");
+2
View File
@@ -77,6 +77,8 @@ const config = {
path: "../docs", path: "../docs",
sidebarCollapsed: false, sidebarCollapsed: false,
breadcrumbs: false, breadcrumbs: false,
lastVersion:
process.env.PULL_REQUEST === "true" ? "current" : undefined,
versions: { versions: {
current: { current: {
label: "next", label: "next",
+3 -3
View File
@@ -162,7 +162,7 @@ function setup(props) {
const getMarkdown = ({ formatted, reformatted, full, doc }) => { const getMarkdown = ({ formatted, reformatted, full, doc }) => {
const { content, options } = state; const { content, options } = state;
const { availableOptions, version } = props; const { availableOptions } = props;
const orderedOptions = orderOptions(availableOptions, [ const orderedOptions = orderOptions(availableOptions, [
...ENABLED_OPTIONS, ...ENABLED_OPTIONS,
"rangeStart", "rangeStart",
@@ -176,7 +176,7 @@ function setup(props) {
output: formatted, output: formatted,
output2: reformatted, output2: reformatted,
doc, doc,
version, version: props.version,
url: window.location.href, url: window.location.href,
options, options,
cliOptions, cliOptions,
@@ -547,7 +547,7 @@ const Playground = {
name: "Playground", name: "Playground",
props: { props: {
availableOptions: { type: Array, required: true }, availableOptions: { type: Array, required: true },
version: { type: String, required: true }, version: { type: Object, required: true },
}, },
setup, setup,
}; };
+3
View File
@@ -1,6 +1,7 @@
import { onMounted, reactive, toRaw, watch } from "vue"; import { onMounted, reactive, toRaw, watch } from "vue";
import { settings } from "./composables/playground-settings.js"; import { settings } from "./composables/playground-settings.js";
import { worker } from "./composables/prettier-worker.js"; import { worker } from "./composables/prettier-worker.js";
import { version } from "./composables/use-version.js";
function setup(props, { slots }) { function setup(props, { slots }) {
const state = reactive({ formatted: "", debug: {} }); const state = reactive({ formatted: "", debug: {} });
@@ -14,6 +15,7 @@ function setup(props, { slots }) {
props.code, props.code,
toRaw(props.options), toRaw(props.options),
toRaw(settings), toRaw(settings),
version.value,
); );
Object.assign(state, result); Object.assign(state, result);
@@ -24,6 +26,7 @@ function setup(props, { slots }) {
onMounted(componentDidMount); onMounted(componentDidMount);
watch(() => props.code, format); watch(() => props.code, format);
watch(() => props.options, format, { deep: true }); watch(() => props.options, format, { deep: true });
watch(() => version.value, format);
// Should not trigger format when these changes // Should not trigger format when these changes
const ignoredKeys = new Set(["showSidebar", "showInput"]); const ignoredKeys = new Set(["showSidebar", "showInput"]);
@@ -2,14 +2,20 @@
@import {PlaygroundSettings} from "./playground-settings.js" @import {PlaygroundSettings} from "./playground-settings.js"
@import {FormatMessage, MetaMessage} from "../../static/worker.mjs" @import {FormatMessage, MetaMessage} from "../../static/worker.mjs"
*/ */
class WorkerApi { class WorkerApi {
#worker = new Worker("/worker.mjs", { type: "module" }); #workers = {};
#counter = 0; #counter = 0;
#handlers = {}; #handlers = {};
constructor() { #initWorker(version) {
const worker = this.#worker; if (this.#workers[version]) {
return this.#workers[version];
}
const worker = new Worker(`/worker.mjs?version=${version}`, {
type: "module",
});
this.#workers[version] = worker;
const handlers = this.#handlers; const handlers = this.#handlers;
worker.addEventListener("message", (event) => { worker.addEventListener("message", (event) => {
@@ -28,20 +34,25 @@ class WorkerApi {
resolve(message); resolve(message);
} }
}); });
return worker;
} }
#postMessage(message) { #postMessage(message) {
const worker = this.#initWorker(message.version);
const uid = ++this.#counter; const uid = ++this.#counter;
const worker = this.#worker;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.#handlers[uid] = [resolve, reject]; this.#handlers[uid] = [resolve, reject];
worker.postMessage({ uid, message }); worker.postMessage({ uid, message });
}); });
} }
getMetadata() { /**
* @param {MetaMessage["version"]} version
*/
getMetadata(version) {
/** @type {MetaMessage} */ /** @type {MetaMessage} */
const message = { type: "meta" }; const message = { type: "meta", version };
return this.#postMessage(message); return this.#postMessage(message);
} }
@@ -49,10 +60,11 @@ class WorkerApi {
* @param {FormatMessage["code"]} code * @param {FormatMessage["code"]} code
* @param {FormatMessage["options"]} options * @param {FormatMessage["options"]} options
* @param {FormatMessage["settings"]} settings * @param {FormatMessage["settings"]} settings
* @param {FormatMessage["version"]} version
*/ */
format(code, options, settings) { format(code, options, settings, version) {
/** @type {FormatMessage} */ /** @type {FormatMessage} */
const message = { type: "format", code, options, settings }; const message = { type: "format", version, code, options, settings };
return this.#postMessage(message); return this.#postMessage(message);
} }
} }
@@ -0,0 +1,50 @@
import { ref } from "vue";
const ALLOWED_VERSIONS = new Set(["next", "stable"]);
const IS_PULL_REQUEST = import.meta.env.VITE_IS_PR;
const DEFAULT_VERSION = IS_PULL_REQUEST ? "next" : "stable";
const DOCS_VERSION_KEY = "docs-preferred-version-default";
function getVersion() {
const versionInUrl = new URLSearchParams(window.location.search).get(
"version",
);
if (ALLOWED_VERSIONS.has(versionInUrl)) {
return versionInUrl;
}
let docsVersion = localStorage.getItem(DOCS_VERSION_KEY);
if (docsVersion === "current") {
docsVersion = "next";
}
if (ALLOWED_VERSIONS.has(docsVersion)) {
return docsVersion;
}
return DEFAULT_VERSION;
}
function setVersion(value) {
value = ALLOWED_VERSIONS.has(value) ? value : DEFAULT_VERSION;
// Save to localStorage, so the docs can respond to version change
localStorage.setItem(DOCS_VERSION_KEY, value === "next" ? "current" : value);
const url = new URL(window.location);
if (!IS_PULL_REQUEST && value === DEFAULT_VERSION) {
url.searchParams.delete("version");
} else {
url.searchParams.set("version", value);
}
window.history.replaceState({}, "", url);
version.value = value;
}
const version = ref(undefined);
setVersion(getVersion());
export { setVersion, version };
+42 -17
View File
@@ -5,42 +5,67 @@ import VersionLink from "./version-link.vue";
const logo = "/icon.png"; const logo = "/icon.png";
defineProps({ const props = defineProps({
version: { type: String, required: true }, version: { type: Object, required: true },
}); });
</script> </script>
<template> <template>
<header> <header class="header">
<a href="/" class="logo-wrapper"> <a href="/" class="logo-wrapper">
<img class="logo" :src="logo" alt="" /> <img class="logo" :src="logo" alt="" />
<h1>Prettier <VersionLink :version></VersionLink></h1> <h1>Prettier</h1>
</a> </a>
<span class="links"> <VersionLink :version="version" />
<GithubButton
href="https://github.com/prettier/prettier" <div class="links">
dataShowCount="true" <div class="github-button">
class="github-button" <GithubButton
aria-label="Star prettier on GitHub" href="https://github.com/prettier/prettier"
>GitHub</GithubButton dataShowCount="true"
> aria-label="Star prettier on GitHub"
>GitHub</GithubButton
>
</div>
<ThemeToggle></ThemeToggle> <ThemeToggle></ThemeToggle>
</span> </div>
</header> </header>
</template> </template>
<style> <style>
.header {
display: flex;
align-items: center;
gap: 10px;
}
.logo-wrapper {
display: flex;
align-items: center;
}
.logo-wrapper h1 {
font-size: 20px;
display: block;
font-family: inherit;
font-weight: 400;
line-height: 18px;
position: relative;
margin: 0;
}
.logo {
height: 34px;
margin-right: 10px;
}
.links { .links {
display: flex; display: flex;
gap: 8px; gap: 8px;
align-items: flex-end; align-items: flex-end;
} }
.links > * {
display: flex;
}
.github-button span { .github-button span {
display: flex; display: flex;
} }
+16 -8
View File
@@ -1,26 +1,34 @@
import "./install-service-worker.js"; import "./install-service-worker.js";
import { createApp, onMounted, reactive } from "vue"; import { createApp, onMounted, reactive, toRaw, watch } from "vue";
import { settings } from "./composables/playground-settings.js";
import { worker } from "./composables/prettier-worker.js"; import { worker } from "./composables/prettier-worker.js";
import { setVersion, version } from "./composables/use-version.js";
import Header from "./header.vue"; import Header from "./header.vue";
import Playground from "./Playground.jsx"; import Playground from "./Playground.jsx";
import { fixPrettierVersion } from "./utilities.js"; import { formatVersion } from "./utilities.js";
const App = { const App = {
name: "App", name: "App",
setup() { setup() {
const state = reactive({ loaded: false }); const state = reactive({
loaded: false,
});
const componentDidMount = async () => { const updateMetadata = async () => {
const { supportInfo, version } = await worker.getMetadata(); const { supportInfo, version: versionData } = await worker.getMetadata(
version.value,
);
Object.assign(state, { Object.assign(state, {
loaded: true, loaded: true,
availableOptions: supportInfo.options.map(augmentOption), availableOptions: supportInfo.options.map(augmentOption),
version: fixPrettierVersion(version), version: formatVersion(versionData),
}); });
}; };
watch(() => version.value, updateMetadata);
const render = () => { const render = () => {
const { loaded, availableOptions, version } = state; const { loaded, availableOptions, version } = state;
@@ -30,13 +38,13 @@ const App = {
return ( return (
<> <>
<Header version={version}></Header> <Header version={version} />
<Playground availableOptions={availableOptions} version={version} /> <Playground availableOptions={availableOptions} version={version} />
</> </>
); );
}; };
onMounted(componentDidMount); onMounted(() => updateMetadata());
return render; return render;
}, },
}; };
+1 -1
View File
@@ -14,7 +14,7 @@ function formatMarkdown({
const isIdempotent = !output2 || output === output2; const isIdempotent = !output2 || output === output2;
return [ return [
`**Prettier ${version}**`, `**Prettier ${version.text}**`,
`[Playground link](${url})`, `[Playground link](${url})`,
optionsString === "" ? null : codeBlock(optionsString, "sh"), optionsString === "" ? null : codeBlock(optionsString, "sh"),
"", "",
+4 -21
View File
@@ -56,6 +56,10 @@ header {
header { header {
padding: 10px; padding: 10px;
} }
.github-button {
display: none;
}
} }
header a, header a,
@@ -67,27 +71,6 @@ header a:hover {
text-decoration: none; text-decoration: none;
} }
.logo-wrapper {
display: flex;
align-items: center;
}
.logo {
height: 34px;
margin-right: 10px;
}
header h1 {
font-size: 20px;
display: block;
font-family: inherit;
font-weight: 400;
line-height: 18px;
position: relative;
z-index: 9999;
margin: 0;
}
.theme-icon { .theme-icon {
padding-block: 2px; padding-block: 2px;
} }
+25 -5
View File
@@ -1,9 +1,29 @@
export function fixPrettierVersion(version) { export function formatVersion(version) {
const match = version.match(/^\d+\.\d+\.\d+-pr.(\d+)$/); if (version.name === "stable") {
if (match) { return {
return `pr-${match[1]}`; text: `v${version.version}`,
title: `Prettier v${version.version}`,
link: `https://github.com/prettier/prettier/releases/tag/${version.version}`,
};
} }
return version;
if (version.name === "next" && version.pr) {
return {
text: `PR #${version.pr}`,
title: `Prettier #${version.pr}`,
link: `https://github.com/prettier/prettier/pull/${version.pr}`,
};
}
const commit = version.gitTree?.commit;
return {
text: commit ?? "uncommitted changes",
title: `Prettier (${commit ?? "uncommitted"})`,
link: commit
? `https://github.com/prettier/prettier/commit/${commit}`
: "https://github.com/prettier/prettier/",
};
} }
export function getDefaults(availableOptions, optionNames) { export function getDefaults(availableOptions, optionNames) {
+38 -32
View File
@@ -1,51 +1,57 @@
<script setup> <script setup>
import { computed, watch, onMounted } from "vue"; import { watch, onMounted } from "vue";
import {
version as playgroundVersion,
setVersion as setPlaygroundVersion,
} from "./composables/use-version.js";
const props = defineProps({ const props = defineProps({
version: { type: String, required: true }, version: { type: Object, required: true },
});
const versionData = computed(() => {
const match = props.version.match(/^pr-(\d+)$/);
let href;
if (match) {
href = `pull/${match[1]}`;
} else if (/\.0$/.test(props.version)) {
href = `releases/tag/${props.version}`;
} else {
href = `blob/main/CHANGELOG.md#${props.version.replaceAll(".", "")}`;
}
return {
href,
formattedVersion: match ? `PR #${match[1]}` : `v${props.version}`,
};
}); });
const updateTitle = () => { const updateTitle = () => {
document.title = `Prettier ${versionData.value.formattedVersion}`; document.title = props.version.title;
}; };
watch(() => props.version, updateTitle); watch(() => props.version.title, updateTitle);
onMounted(updateTitle); onMounted(updateTitle);
</script> </script>
<template> <template>
<a <div class="version-wrapper">
:href="`https://github.com/prettier/prettier/${versionData.href}`" <select
target="_blank" class="channel-select"
rel="noreferrer noopener" :value="playgroundVersion"
class="version" @change="
> (event) => {
{{ versionData.formattedVersion }} setPlaygroundVersion(event.target.value);
</a> }
"
>
<option value="stable">stable</option>
<option value="next">next</option>
</select>
<a
class="version-link"
:href="version.link"
target="_blank"
rel="noopener noreferrer"
>
{{ version.text }}
</a>
</div>
</template> </template>
<style> <style>
.version { .version-wrapper {
font-size: 0.5em; display: flex;
line-height: 0; align-items: center;
gap: 0.5em;
flex: 1;
}
.version-link {
opacity: 0.5; opacity: 0.5;
} }
</style> </style>
+52 -47
View File
@@ -1,20 +1,8 @@
import prettierPackageManifest from "./lib/package-manifest.mjs";
import * as prettier from "./lib/prettier/standalone.mjs";
const builderNames = new Set(Object.keys(prettier.doc.builders));
const parserName = "doc-explorer"; const parserName = "doc-explorer";
const languageName = "Doc explorer"; const languageName = "Doc explorer";
const astFormat = parserName; const astFormat = parserName;
const expressionParserName = "__js_expression"; const expressionParserName = "__js_expression";
async function getJsExpressionParser() {
const plugin = prettierPackageManifest.plugins.find((plugin) =>
plugin.parsers?.includes(expressionParserName),
);
const pluginModule = await import(`./lib/${plugin.file}`);
return pluginModule.parsers[expressionParserName];
}
// `Symbol.for('description')` // `Symbol.for('description')`
function isSymbol(node) { function isSymbol(node) {
@@ -32,7 +20,7 @@ function isSymbol(node) {
); );
} }
function validateNode(node) { function validateNode(node, builderNames) {
if (!node?.type) { if (!node?.type) {
return; return;
} }
@@ -54,7 +42,7 @@ function validateNode(node) {
continue; continue;
} }
validateNode(value); validateNode(value, builderNames);
} }
return; return;
} }
@@ -67,44 +55,61 @@ function validateNode(node) {
const children = Array.isArray(value) ? value : [value]; const children = Array.isArray(value) ? value : [value];
for (const child of children) { for (const child of children) {
validateNode(child); validateNode(child, builderNames);
} }
} }
} }
let expressionParser; export function createDocExplorerPlugin(
async function validateDoc(text) { prettier,
expressionParser ??= await getJsExpressionParser(); prettierPackageManifest,
version,
) {
const builderNames = new Set(Object.keys(prettier.doc.builders));
const ast = expressionParser.parse(text); async function getJsExpressionParser() {
if (ast.type !== "JsExpressionRoot") { const plugin = prettierPackageManifest.plugins.find((plugin) =>
throw new Error("Invalid doc"); plugin.parsers?.includes(expressionParserName),
);
const pluginModule = await import(`./lib/${version}/${plugin.file}`);
return pluginModule.parsers[expressionParserName];
} }
validateNode(ast.node); let expressionParser;
async function validateDoc(text) {
expressionParser ??= await getJsExpressionParser();
const ast = expressionParser.parse(text);
if (ast.type !== "JsExpressionRoot") {
throw new Error("Invalid doc");
}
validateNode(ast.node, builderNames);
}
async function parse(text) {
await validateDoc(text);
const value = new Function(
`{ ${Object.keys(prettier.doc.builders)} }`,
`const result = (${text || "''"}\n); return result;`,
)(prettier.doc.builders);
return { value };
}
return {
parsers: {
[parserName]: {
parse,
astFormat,
},
},
printers: {
[astFormat]: {
print: ({ node }) => node.value,
},
},
languages: [{ name: languageName, parsers: [parserName] }],
};
} }
async function parse(text) {
await validateDoc(text);
const value = new Function(
`{ ${Object.keys(prettier.doc.builders)} }`,
`const result = (${text || "''"}\n); return result;`,
)(prettier.doc.builders);
return { value };
}
export const parsers = {
[parserName]: {
parse,
astFormat,
},
};
export const printers = {
[astFormat]: {
print: ({ node }) => node.value,
},
};
export const languages = [{ name: languageName, parsers: [parserName] }];
+15 -7
View File
@@ -1,17 +1,25 @@
import "https://cdnjs.cloudflare.com/ajax/libs/sw-toolbox/3.6.0/sw-toolbox.js"; import "https://cdnjs.cloudflare.com/ajax/libs/sw-toolbox/3.6.0/sw-toolbox.js";
import prettierPackageManifest from "./lib/package-manifest.mjs"; import prettierPackageManifestNext from "./lib/next/package-manifest.mjs";
import prettierPackageManifestStable from "./lib/stable/package-manifest.mjs";
const { toolbox } = self; const { toolbox } = self;
const packageFiles = [
prettierPackageManifest.prettier, function getPackageFiles(manifest) {
...prettierPackageManifest.plugins, return [manifest.prettier, ...manifest.plugins].map(
].map(({ file }) => `./lib/${file}`); ({ file }) => `./lib/${manifest.version.name}/${file}`,
);
}
const stableFiles = getPackageFiles(prettierPackageManifestStable);
const nextFiles = getPackageFiles(prettierPackageManifestNext);
toolbox.precache([ toolbox.precache([
// Scripts // Scripts
"lib/package-manifest.mjs", "lib/stable/package-manifest.mjs",
...packageFiles, ...stableFiles,
"lib/next/package-manifest.mjs",
...nextFiles,
"https://cdnjs.cloudflare.com/ajax/libs/sw-toolbox/3.6.0/sw-toolbox.js", "https://cdnjs.cloudflare.com/ajax/libs/sw-toolbox/3.6.0/sw-toolbox.js",
// Images // Images
+66 -12
View File
@@ -1,22 +1,74 @@
import prettierPackageManifest from "./lib/package-manifest.mjs"; import { createDocExplorerPlugin } from "./prettier-plugin-doc-explorer.mjs";
import * as prettier from "./lib/prettier/standalone.mjs";
import * as prettierPluginDocExplorer from "./prettier-plugin-doc-explorer.mjs";
/** /**
@import {PlaygroundSettings} from "../playground/composables/playground-settings.js" @import {PlaygroundSettings} from "../playground/composables/playground-settings.js"
@typedef {{ type: "meta" }} MetaMessage @typedef {{
type: "meta",
version: "stable" | "next",
}} MetaMessage
@typedef {{ @typedef {{
type: "format", type: "format",
code: string, code: string,
options: any, options: any,
settings: PlaygroundSettings settings: PlaygroundSettings,
version: "stable" | "next",
}} FormatMessage }} FormatMessage
*/ */
const workerUrl = new URL(import.meta.url);
const version =
workerUrl.searchParams.get("version") === "next" ? "next" : "stable";
let prettierCache;
let prettierPackageManifestCache;
let prettierPluginDocExplorerCache;
let pluginsCache;
async function loadPrettier() {
if (!prettierCache) {
prettierCache = import(`./lib/${version}/prettier/standalone.mjs`);
}
return await prettierCache;
}
async function loadPrettierPackageManifest() {
if (!prettierPackageManifestCache) {
prettierPackageManifestCache = import(
`./lib/${version}/package-manifest.mjs`
).then((m) => m.default);
}
return await prettierPackageManifestCache;
}
async function loadPrettierPluginDocExplorer() {
if (!prettierPluginDocExplorerCache) {
const prettier = await loadPrettier();
const prettierPackageManifest = await loadPrettierPackageManifest();
prettierPluginDocExplorerCache = createDocExplorerPlugin(
prettier,
prettierPackageManifest,
version,
);
}
return prettierPluginDocExplorerCache;
}
async function loadPlugins() {
if (!pluginsCache) {
const prettierPackageManifest = await loadPrettierPackageManifest();
const prettierPluginDocExplorer = await loadPrettierPluginDocExplorer();
pluginsCache = [
...prettierPackageManifest.plugins.map((plugin) => createPlugin(plugin)),
prettierPluginDocExplorer,
];
}
return pluginsCache;
}
const pluginLoadPromises = new Map(); const pluginLoadPromises = new Map();
async function importPlugin(plugin) { async function importPlugin(plugin) {
if (!pluginLoadPromises.has(plugin)) { if (!pluginLoadPromises.has(plugin)) {
pluginLoadPromises.set(plugin, import(`./lib/${plugin.file}`)); pluginLoadPromises.set(plugin, import(`./lib/${version}/${plugin.file}`));
} }
try { try {
@@ -52,11 +104,6 @@ function createPlugin(pluginManifest) {
return plugin; return plugin;
} }
const plugins = [
...prettierPackageManifest.plugins.map((plugin) => createPlugin(plugin)),
prettierPluginDocExplorer,
];
self.onmessage = async function (event) { self.onmessage = async function (event) {
self.postMessage({ self.postMessage({
uid: event.data.uid, uid: event.data.uid,
@@ -113,13 +160,17 @@ function handleMessage(message) {
} }
async function handleMetaMessage() { async function handleMetaMessage() {
const prettier = await loadPrettier();
const prettierPackageManifest = await loadPrettierPackageManifest();
const plugins = await loadPlugins();
const supportInfo = await prettier.getSupportInfo({ plugins }); const supportInfo = await prettier.getSupportInfo({ plugins });
return { return {
type: "meta", type: "meta",
// eslint-disable-next-line unicorn/prefer-structured-clone // eslint-disable-next-line unicorn/prefer-structured-clone
supportInfo: JSON.parse(JSON.stringify(supportInfo)), supportInfo: JSON.parse(JSON.stringify(supportInfo)),
version: prettier.version, version: prettierPackageManifest.version,
}; };
} }
@@ -127,6 +178,8 @@ async function handleMetaMessage() {
@param {FormatMessage} message @param {FormatMessage} message
*/ */
async function handleFormatMessage(message) { async function handleFormatMessage(message) {
const prettier = await loadPrettier();
const plugins = await loadPlugins();
let { options, code, settings } = message; let { options, code, settings } = message;
options = { ...options, plugins }; options = { ...options, plugins };
@@ -231,6 +284,7 @@ async function handleFormatMessage(message) {
@param {PlaygroundSettings} settings @param {PlaygroundSettings} settings
*/ */
async function formatCode(text, options, settings = {}) { async function formatCode(text, options, settings = {}) {
const prettier = await loadPrettier();
if (options.parser === "doc-explorer") { if (options.parser === "doc-explorer") {
options = { options = {
...options, ...options,
+5
View File
@@ -43,6 +43,11 @@ export default defineConfig({
outDir: OUT_DIRECTORY, outDir: OUT_DIRECTORY,
minify: IS_CI, minify: IS_CI,
}, },
define: {
"import.meta.env.VITE_IS_PR": JSON.stringify(
Boolean(process.env.PULL_REQUEST),
),
},
}); });
function buildCdnAlias() { function buildCdnAlias() {