From 32114b760c0e7e2762f77eb4181ab5f82fe6851c Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Fri, 9 Jan 2026 04:46:45 +0800 Subject: [PATCH] Website: collect dependencies with `package-dependencies-tree` (#18591) --- website/package.json | 1 + website/scripts/collect-dependencies.mjs | 182 +++++++++++------------ website/vite.config.mjs | 105 +++++-------- website/yarn.lock | 8 + 4 files changed, 139 insertions(+), 157 deletions(-) diff --git a/website/package.json b/website/package.json index 6dc5c9882..e04c8c2ef 100644 --- a/website/package.json +++ b/website/package.json @@ -50,6 +50,7 @@ "@vitejs/plugin-vue": "6.0.3", "@vitejs/plugin-vue-jsx": "5.1.3", "concurrently": "9.2.1", + "package-dependencies-tree": "0.0.1", "rollup-plugin-visualizer": "6.0.5", "vite": "7.3.0", "yaml": "2.8.2" diff --git a/website/scripts/collect-dependencies.mjs b/website/scripts/collect-dependencies.mjs index cca915c9b..d7cf6cd0d 100644 --- a/website/scripts/collect-dependencies.mjs +++ b/website/scripts/collect-dependencies.mjs @@ -1,106 +1,102 @@ -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); -} +import assert from "node:assert/strict"; +import getDependencies from "package-dependencies-tree"; /** -@param {{ - name: string, - base: string | URL, - overrideDependencies?: Map, - ignoreDependencies?: (name: string) => boolean, -}} param0 -@returns +@import {PackageJson} from "package-dependencies-tree" */ -async function getPackageDependencies({ - name, - base, + +/** +@param {PackageJson} packageJson +@param {{ + overrideDependencies: Map + ignoreDependencies = (name) => boolean, + seen: Set +}} seen +@returns {Generator} +*/ +function* collectDependencies(packageJson, options) { + if (!options.seen) { + options = { ...options, seen: new Set() }; + } + + const { seen, overrideDependencies, ignoreDependencies } = options; + + if (overrideDependencies.has(packageJson.name)) { + yield* overrideDependencies.get(packageJson.name); + return; + } + + for (const { type, optional } of [ + { type: "dependencies", optional: false }, + { type: "peerDependencies", optional: true }, + ]) { + for (const dependency of packageJson[type].values()) { + if (ignoreDependencies(dependency.name)) { + continue; + } + + const { resolved } = dependency; + if (!optional) { + assert.ok( + resolved, + `Unable to resolve ${dependency.type} '${dependency.name}@${dependency.version}' from '${dependency.base}'.`, + ); + } + + if (resolved && !seen.has(resolved)) { + seen.add(resolved); + yield resolved; + yield* collectDependencies(resolved, options); + } + } + } +} + +function assertDependenciesUnique(packageJson, dependencies) { + const versions = new Map(); + + for (const dependency of dependencies) { + assert.ok( + !versions.has(dependency.name), + `Multiple version of '${dependency.name}' in dependency tree of '${packageJson.name}', '${versions.get(dependency.name)?.version}' and '${dependency.version}'.`, + ); + versions.set(dependency.name, dependency); + } +} + +function getPackageDependencies({ + file, + exclude, overrideDependencies = new Map(), ignoreDependencies = () => false, }) { - const packageJsonUrl = findPackageJSON(name, base); - const packageJsonUrls = new Set(); + return Array.from( + getDependencies(file).dependencies.entries(), + ([name, dependency]) => { + if (exclude.has(name)) { + return; + } - const packages = [{ name, packageJsonUrl }]; - while (packages.length > 0) { - const { name, packageJsonUrl } = packages.pop(); - if (ignoreDependencies(name) || packageJsonUrls.has(packageJsonUrl)) { - continue; - } + const packageJson = dependency.resolved; - packageJsonUrls.add(packageJsonUrl); + const dependencies = [ + ...collectDependencies(packageJson, { + overrideDependencies, + ignoreDependencies, + }), + ].toSorted(({ name: nameA }, { name: nameB }) => + nameA.localeCompare(nameB), + ); - let dependencies; + assertDependenciesUnique(packageJson, 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), - ), - }; + return { + name, + packageJson, + dependencies, + }; + }, + ).filter(Boolean); } export { getPackageDependencies }; diff --git a/website/vite.config.mjs b/website/vite.config.mjs index 48b3c1812..df9826770 100644 --- a/website/vite.config.mjs +++ b/website/vite.config.mjs @@ -1,15 +1,14 @@ -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 cdnDependencies = { + file: new URL("./package.json", import.meta.url), exclude: new Set(["@docusaurus/preset-classic"]), overrideDependencies: new Map([ // We don't need deps from `vue` @@ -23,76 +22,54 @@ if (!IS_PRODUCTION) { cdnDependencies.exclude.add("vue"); } -export default defineConfig(async () => { - const alias = await buildCdnAlias(); +const alias = 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, - }, - }; +export default defineConfig({ + 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() { +function buildCdnAlias() { if (!IS_PRODUCTION) { return; } return Object.fromEntries( - await Promise.all( - Object.keys(packageJson.dependencies) - .filter((name) => !cdnDependencies.exclude.has(name)) - .map(async (name) => [name, await getPackageCdnUrl(name)]), + getPackageDependencies(cdnDependencies).map( + ({ name, packageJson, dependencies }) => { + const url = new URL( + `https://esm.sh/${packageJson.name}@${packageJson.version}`, + ); + const { searchParams } = url; + // searchParams.set("conditions", ["browser"]); + + if (dependencies.length > 0) { + searchParams.set( + "deps", + dependencies + .map(({ name, version }) => `${name}@${version}`) + .join(","), + ); + } + + return [name, url.href]; + }, ), ); } - -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}`); - const { searchParams } = url; - // searchParams.set("conditions", ["browser"]); - - if (dependencies.length > 0) { - searchParams.set( - "deps", - dependencies.map(({ name, version }) => `${name}@${version}`).join(","), - ); - } - - return url.href; -} - -function assertDependenciesUnique(name, dependencies) { - const versions = new Map(); - - 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); - } -} diff --git a/website/yarn.lock b/website/yarn.lock index 882bf0f3c..c21bc7be7 100644 --- a/website/yarn.lock +++ b/website/yarn.lock @@ -11350,6 +11350,13 @@ __metadata: languageName: node linkType: hard +"package-dependencies-tree@npm:0.0.1": + version: 0.0.1 + resolution: "package-dependencies-tree@npm:0.0.1" + checksum: 10/018fa5be276997bc618eccb8aac827135fb7eb788dc8f1bfbc1da55ee59b3bcd8037ae39dc023de655d30b125c1358e4523afa07cc4dca6aec3cd9a8c89e2530 + languageName: node + linkType: hard + "package-json@npm:^8.1.0": version: 8.1.1 resolution: "package-json@npm:8.1.1" @@ -13292,6 +13299,7 @@ __metadata: github-buttons: "npm:2.29.1" graphql: "npm:16.12.0" lz-string: "npm:1.5.0" + package-dependencies-tree: "npm:0.0.1" prism-react-renderer: "npm:2.4.1" react: "npm:19.2.1" react-dom: "npm:19.2.1"