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:
17 files changed
+412
-199
No files matched your search
Vendored
+7
-1
@@ -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
@@ -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...");
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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 };
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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"),
|
||||||
"",
|
"",
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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] }];
|
|
||||||
@@ -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
@@ -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,
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
Reference in new issue
Block a user