Refactor website CDN url replacements (#18553)

This commit is contained in:
fisker Cheung authored and GitHub committed 2025-12-31 02:17:06 +08:00
1 parent 65e600a539
commit 1c49bedbee
2 files changed
+166 -81

No files matched your search

+106
View File
@@ -0,0 +1,106 @@
import fs from "node:fs/promises";
import { findPackageJSON } from "node:module";
async function _getPackageJson(packageJsonUrl) {
const packageJson = JSON.parse(await fs.readFile(packageJsonUrl, "utf8"));
const dependencies = new Set();
for (const { dependencyType, optional } of [
{ dependencyType: "dependencies", optional: false },
{ dependencyType: "peerDependencies", optional: true },
]) {
if (!packageJson[dependencyType]) {
continue;
}
for (const name of Object.keys(packageJson[dependencyType])) {
let dependencyPackageJsonUrl;
try {
dependencyPackageJsonUrl = findPackageJSON(name, packageJsonUrl);
} catch (error) {
if (optional && error.code === "ERR_MODULE_NOT_FOUND") {
continue;
}
throw error;
}
dependencies.add({ name, packageJsonUrl: dependencyPackageJsonUrl });
}
}
return {
name: packageJson.name,
version: packageJson.version,
packageJsonUrl,
dependencies: [...dependencies],
};
}
const packageJsonCache = new Map();
function getPackageJson(packageJsonUrl) {
if (!packageJsonCache.has(packageJsonUrl)) {
packageJsonCache.set(packageJsonUrl, _getPackageJson(packageJsonUrl));
}
return packageJsonCache.get(packageJsonUrl);
}
/**
@param {{
name: string,
base: string | URL,
overrideDependencies?: Map<string, string[]>,
ignoreDependencies?: (name: string) => boolean,
}} param0
@returns
*/
async function getPackageDependencies({
name,
base,
overrideDependencies = new Map(),
ignoreDependencies = () => false,
}) {
const packageJsonUrl = findPackageJSON(name, base);
const packageJsonUrls = new Set();
const packages = [{ name, packageJsonUrl }];
while (packages.length > 0) {
const { name, packageJsonUrl } = packages.pop();
if (ignoreDependencies(name) || packageJsonUrls.has(packageJsonUrl)) {
continue;
}
packageJsonUrls.add(packageJsonUrl);
let dependencies;
if (overrideDependencies.has(name)) {
dependencies = overrideDependencies
.get(name)
.map((name) => ({ name, packageJsonUrl: findPackageJSON(name, base) }));
} else {
({ dependencies } = await getPackageJson(packageJsonUrl));
}
packages.push(...dependencies);
}
// Delete self
packageJsonUrls.delete(packageJsonUrl);
const [packageJsonData, ...dependencies] = await Promise.all(
[packageJsonUrl, ...packageJsonUrls].map((packageJsonUrl) =>
getPackageJson(packageJsonUrl),
),
);
return {
...packageJsonData,
dependencies: dependencies.toSorted(({ name: nameA }, { name: nameB }) =>
nameA.localeCompare(nameB),
),
};
}
export { getPackageDependencies };
+60 -81
View File
@@ -1,119 +1,98 @@
import fs from "node:fs/promises";
import module from "node:module";
import assert from "node:assert/strict";
import vitePluginVue from "@vitejs/plugin-vue";
import vitePluginVueJsx from "@vitejs/plugin-vue-jsx";
import { visualizer as rollupPluginVisualizer } from "rollup-plugin-visualizer";
import { defineConfig } from "vite";
import packageJson from "./package.json" with { type: "json" };
import { getPackageDependencies } from "./scripts/collect-dependencies.mjs";
const IS_PRODUCTION = process.env.NODE_ENV === "production";
const IS_CI = Boolean(process.env.CI);
const OUT_DIRECTORY = "./static/playground/";
const DEPENDENCIES_EXCLUDE_FROM_CDN = new Set(["@docusaurus/preset-classic"]);
const cdnDependencies = {
exclude: new Set(["@docusaurus/preset-classic"]),
overrideDependencies: new Map([
// We don't need deps from `vue`
["vue", []],
]),
ignoreDependencies: (name) => name.startsWith("@types/"),
};
// Local develop requires `vue` for HMR
if (!IS_PRODUCTION) {
DEPENDENCIES_EXCLUDE_FROM_CDN.add("vue");
cdnDependencies.exclude.add("vue");
}
export default defineConfig(async () => ({
base: IS_PRODUCTION ? "/playground/" : undefined,
publicDir: IS_PRODUCTION ? undefined : "./static/",
plugins: [
vitePluginVue(),
vitePluginVueJsx(),
IS_CI || !IS_PRODUCTION
? undefined
: rollupPluginVisualizer({
filename: `${OUT_DIRECTORY}bundle-report.html`,
}),
].filter(Boolean),
resolve: {
alias: await buildCdnAlias(),
},
build: {
outDir: OUT_DIRECTORY,
minify: IS_CI,
},
}));
export default defineConfig(async () => {
const alias = await buildCdnAlias();
return {
base: IS_PRODUCTION ? "/playground/" : undefined,
publicDir: IS_PRODUCTION ? undefined : "./static/",
plugins: [
vitePluginVue(),
vitePluginVueJsx(),
IS_CI || !IS_PRODUCTION
? undefined
: rollupPluginVisualizer({
filename: `${OUT_DIRECTORY}bundle-report.html`,
}),
].filter(Boolean),
resolve: {
alias,
},
build: {
outDir: OUT_DIRECTORY,
minify: IS_CI,
},
};
});
async function buildCdnAlias() {
// if (!IS_PRODUCTION) {
// return;
// }
if (!IS_PRODUCTION) {
return;
}
return Object.fromEntries(
await Promise.all(
Object.keys(packageJson.dependencies)
.filter((name) => !DEPENDENCIES_EXCLUDE_FROM_CDN.has(name))
.filter((name) => !cdnDependencies.exclude.has(name))
.map(async (name) => [name, await getPackageCdnUrl(name)]),
),
);
}
async function getPackageCdnUrl(name) {
const version = packageJson.dependencies[name];
async function getPackageCdnUrl(dependencyName) {
const { name, version, dependencies } = await getPackageDependencies({
name: dependencyName,
base: import.meta.url,
overrideDependencies: cdnDependencies.overrideDependencies,
ignoreDependencies: cdnDependencies.ignoreDependencies,
});
assertDependenciesUnique(name, dependencies);
const url = new URL(`https://esm.sh/${name}@${version}`);
let dependencies = await getPackageDependencies(name, import.meta.url);
dependencies = dependencies
// Exclude self
.filter((dependency) => dependency.name !== name)
// Sort alphabetically
.toSorted(({ name: nameA }, { name: nameB }) => nameA.localeCompare(nameB))
// Dedupe
.filter(
({ name, version }, index, dependencies) =>
dependencies.findIndex(
(searching) =>
searching.name === name && searching.version === version,
) === index,
);
const { searchParams } = url;
// searchParams.set("conditions", ["browser"]);
if (dependencies.length > 0) {
url.searchParams.set(
searchParams.set(
"deps",
dependencies.map(({ name, version }) => `${name}@${version}`).join(","),
);
}
return url;
return url.href;
}
async function getPackageJson(name, base) {
const packageJsonUrl = module.findPackageJSON(name, base);
function assertDependenciesUnique(name, dependencies) {
const versions = new Map();
return {
url: packageJsonUrl,
packageJson: JSON.parse(await fs.readFile(packageJsonUrl, "utf8")),
};
}
async function getPackageDependenciesWithoutCache(name, base) {
const { url, packageJson } = await getPackageJson(name, base);
const dependencies = [{ name, version: packageJson.version }];
for (const name of Object.keys({
...packageJson.dependencies,
...packageJson.peerDependencies,
})) {
dependencies.push(...(await getPackageDependencies(name, url)));
for (const dependency of dependencies) {
assert.ok(
!versions.has(dependency.name),
`Multiple version of '${dependency.name}' in dependency tree of '${name}', '${versions.get(dependency.name)?.version}' and '${dependency.version}'.`,
);
versions.set(dependency.name, dependency);
}
return dependencies;
}
const cache = new Map();
function getPackageDependencies(name, base) {
// We don't need deps from `vue`
if (name === "vue") {
return [];
}
const cacheKey = JSON.stringify({ name, base });
if (!cache.has(cacheKey)) {
cache.set(cacheKey, getPackageDependenciesWithoutCache(name, base));
}
return cache.get(cacheKey);
}