mirror of https://github.com/vuejs/vitepress
chore: migrate build to tsdown (#5396)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>pull/4957/merge
parent
5a368d3198
commit
357b072a47
@ -0,0 +1,18 @@
|
|||||||
|
diff --git a/dist/rolldown.mjs b/dist/rolldown.mjs
|
||||||
|
index 7e04d96c703656b81220193b1f01426778b6a42f..26e96378fc0ad985237302e673ed0ab07674bf41 100644
|
||||||
|
--- a/dist/rolldown.mjs
|
||||||
|
+++ b/dist/rolldown.mjs
|
||||||
|
@@ -196,10 +196,11 @@ function resolveCache(options) {
|
||||||
|
async function transpileScript(code, filename = "__sfc.ts") {
|
||||||
|
const result = await transform(filename, code, {
|
||||||
|
lang: "ts",
|
||||||
|
- sourcemap: false
|
||||||
|
+ sourcemap: false,
|
||||||
|
+ typescript: { onlyRemoveTypeImports: true }
|
||||||
|
});
|
||||||
|
if (result.errors.length) throw new AggregateError(result.errors, `[vue-sfc-transformer] failed to transpile script in ${filename}`);
|
||||||
|
- return result.code ?? code;
|
||||||
|
+ return (result.code ?? code).replace(/\n?export \{\};?[\s\n]*$/, "");
|
||||||
|
}
|
||||||
|
function vueSfcPlugin(pluginOptions) {
|
||||||
|
const cwd = pluginOptions.cwd ?? process.cwd();
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,115 +0,0 @@
|
|||||||
import { rm } from 'node:fs/promises'
|
|
||||||
import { builtinModules } from 'node:module'
|
|
||||||
import { fileURLToPath } from 'node:url'
|
|
||||||
|
|
||||||
import alias from '@rollup/plugin-alias'
|
|
||||||
import commonjs from '@rollup/plugin-commonjs'
|
|
||||||
import json from '@rollup/plugin-json'
|
|
||||||
import { nodeResolve } from '@rollup/plugin-node-resolve'
|
|
||||||
import replace from '@rollup/plugin-replace'
|
|
||||||
import { type RollupOptions, defineConfig } from 'rollup'
|
|
||||||
import dts from 'rollup-plugin-dts'
|
|
||||||
import esbuild from 'rollup-plugin-esbuild'
|
|
||||||
|
|
||||||
import pkg from './package.json' with { type: 'json' }
|
|
||||||
|
|
||||||
const DEV = !!process.env.DEV
|
|
||||||
const PROD = !DEV
|
|
||||||
|
|
||||||
const external = [
|
|
||||||
...Object.keys(pkg.dependencies),
|
|
||||||
...Object.keys(pkg.peerDependencies),
|
|
||||||
...builtinModules.flatMap((m) =>
|
|
||||||
m.includes('punycode') ? [] : [m, `node:${m}`]
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
const plugins = [
|
|
||||||
alias({ entries: { 'readable-stream': 'stream' } }),
|
|
||||||
replace({
|
|
||||||
// polyfill broken browser check from bundled deps
|
|
||||||
'navigator.userAgentData': 'undefined',
|
|
||||||
'navigator.userAgent': 'undefined',
|
|
||||||
preventAssignment: true
|
|
||||||
}),
|
|
||||||
commonjs(),
|
|
||||||
nodeResolve({ preferBuiltins: false }),
|
|
||||||
esbuild({ target: 'node22' }),
|
|
||||||
json()
|
|
||||||
]
|
|
||||||
|
|
||||||
const esmBuild: RollupOptions = {
|
|
||||||
input: ['src/node/index.ts', 'src/node/cli.ts'],
|
|
||||||
output: {
|
|
||||||
format: 'esm',
|
|
||||||
entryFileNames: `[name].js`,
|
|
||||||
chunkFileNames: 'chunk-[hash].js',
|
|
||||||
dir: 'dist/node',
|
|
||||||
sourcemap: DEV
|
|
||||||
},
|
|
||||||
external,
|
|
||||||
plugins,
|
|
||||||
onwarn(warning, warn) {
|
|
||||||
if (warning.code !== 'EVAL') warn(warning)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// keep .d.ts files under the repo root (e.g. types/*) external so module
|
|
||||||
// augmentations in the bundle still target the same files users reference.
|
|
||||||
// compared on normalized resolved paths so this works regardless of the
|
|
||||||
// checkout location, path separators, or drive-letter casing.
|
|
||||||
const normalizePath = (id: string): string => {
|
|
||||||
const normalized = id.replaceAll('\\', '/')
|
|
||||||
return process.platform === 'win32' ? normalized.toLowerCase() : normalized
|
|
||||||
}
|
|
||||||
|
|
||||||
const root = normalizePath(fileURLToPath(new URL('.', import.meta.url)))
|
|
||||||
|
|
||||||
const typesExternal = (id: string): boolean => {
|
|
||||||
if (external.includes(id) || /^markdown-it(?:\/|$)/.test(id)) return true
|
|
||||||
const normalized = normalizePath(id)
|
|
||||||
return (
|
|
||||||
normalized.endsWith('.d.ts') &&
|
|
||||||
normalized.startsWith(root) &&
|
|
||||||
!normalized.startsWith(`${root}dist/`) &&
|
|
||||||
!normalized.startsWith(`${root}node_modules/`)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const dtsNode = dts({
|
|
||||||
respectExternal: true,
|
|
||||||
tsconfig: 'src/node/tsconfig.json',
|
|
||||||
compilerOptions: { preserveSymlinks: false }
|
|
||||||
})
|
|
||||||
|
|
||||||
const nodeTypes: RollupOptions = {
|
|
||||||
input: 'src/node/index.ts',
|
|
||||||
output: {
|
|
||||||
format: 'esm',
|
|
||||||
file: 'dist/node/index.d.ts'
|
|
||||||
},
|
|
||||||
external: typesExternal,
|
|
||||||
plugins: [dtsNode]
|
|
||||||
}
|
|
||||||
|
|
||||||
const clientTypes: RollupOptions = {
|
|
||||||
input: 'dist/client-types/index.d.ts',
|
|
||||||
output: {
|
|
||||||
format: 'esm',
|
|
||||||
file: 'dist/client/index.d.ts'
|
|
||||||
},
|
|
||||||
external: typesExternal,
|
|
||||||
plugins: [
|
|
||||||
dts({ respectExternal: true }),
|
|
||||||
{
|
|
||||||
name: 'cleanup',
|
|
||||||
async closeBundle() {
|
|
||||||
if (PROD) {
|
|
||||||
await rm('dist/client-types', { recursive: true })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineConfig([esmBuild, nodeTypes, clientTypes])
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
import { cp } from 'node:fs/promises'
|
|
||||||
|
|
||||||
import { globSync } from 'tinyglobby'
|
|
||||||
|
|
||||||
function toDest(file: string) {
|
|
||||||
return file.replace(/^src\//, 'dist/')
|
|
||||||
}
|
|
||||||
|
|
||||||
globSync(['src/client/**']).forEach((file) => {
|
|
||||||
if (/(\.ts|tsconfig\.json)$/.test(file)) return
|
|
||||||
cp(file, toDest(file))
|
|
||||||
})
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
import { cp } from 'node:fs/promises'
|
|
||||||
|
|
||||||
import { globSync } from 'tinyglobby'
|
|
||||||
|
|
||||||
globSync(['src/shared/**/*.ts']).forEach(async (file) => {
|
|
||||||
await Promise.all([
|
|
||||||
cp(file, file.replace(/^src\/shared\//, 'src/node/')),
|
|
||||||
cp(file, file.replace(/^src\/shared\//, 'src/client/'))
|
|
||||||
])
|
|
||||||
})
|
|
||||||
@ -1,38 +0,0 @@
|
|||||||
import { cp, rm } from 'node:fs/promises'
|
|
||||||
|
|
||||||
import { watch } from 'chokidar'
|
|
||||||
import { normalizePath } from 'vite'
|
|
||||||
|
|
||||||
function toClientAndNode(method: 'copy' | 'remove', file: string) {
|
|
||||||
file = normalizePath(file)
|
|
||||||
if (method === 'copy') {
|
|
||||||
cp(file, file.replace(/^src\/shared\//, 'src/node/'))
|
|
||||||
cp(file, file.replace(/^src\/shared\//, 'src/client/'))
|
|
||||||
} else if (method === 'remove') {
|
|
||||||
rm(file.replace(/^src\/shared\//, 'src/node/'), { force: true })
|
|
||||||
rm(file.replace(/^src\/shared\//, 'src/client/'), { force: true })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toDist(file: string) {
|
|
||||||
return normalizePath(file).replace(/^src\//, 'dist/')
|
|
||||||
}
|
|
||||||
|
|
||||||
// copy shared files to the client and node directory whenever they change.
|
|
||||||
watch('src/shared', {
|
|
||||||
ignored: (path, stats) => !!stats?.isFile() && !path.endsWith('.ts')
|
|
||||||
})
|
|
||||||
.on('change', (file) => toClientAndNode('copy', file))
|
|
||||||
.on('add', (file) => toClientAndNode('copy', file))
|
|
||||||
.on('unlink', (file) => toClientAndNode('remove', file))
|
|
||||||
|
|
||||||
// copy non ts files, such as an html or css, to the dist directory whenever
|
|
||||||
// they change.
|
|
||||||
watch('src/client', {
|
|
||||||
ignored: (path, stats) =>
|
|
||||||
!!stats?.isFile() &&
|
|
||||||
(path.endsWith('.ts') || path.endsWith('tsconfig.json'))
|
|
||||||
})
|
|
||||||
.on('change', (file) => cp(file, toDist(file)))
|
|
||||||
.on('add', (file) => cp(file, toDist(file)))
|
|
||||||
.on('unlink', (file) => rm(toDist(file), { force: true }))
|
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
// Cross-environment globals that environment-neutral code may use, declared
|
||||||
|
// merge-compatibly with lib.dom and @types/node (interface merging plus an
|
||||||
|
// identically named var). Loaded only by projects without a DOM lib; keep
|
||||||
|
// members to what shared code actually touches.
|
||||||
|
|
||||||
|
interface Console {
|
||||||
|
debug(...data: unknown[]): void
|
||||||
|
warn(...data: unknown[]): void
|
||||||
|
}
|
||||||
|
declare var console: Console
|
||||||
|
|
||||||
|
interface Document {}
|
||||||
|
declare var document: Document
|
||||||
@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "../../tsconfig.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"outDir": "../../dist/client",
|
|
||||||
"declaration": true,
|
|
||||||
"declarationDir": "../../dist/client-types",
|
|
||||||
"types": ["../../client.d.ts", "@types/node"],
|
|
||||||
"paths": {
|
|
||||||
"vitepress": ["./index.ts"],
|
|
||||||
"vitepress/theme": ["../../theme.d.ts"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"include": ["."]
|
|
||||||
}
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "../../tsconfig.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"outDir": "../../dist/node",
|
|
||||||
"types": ["node"],
|
|
||||||
"sourceMap": true
|
|
||||||
},
|
|
||||||
"include": ["."]
|
|
||||||
}
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "../../tsconfig.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"lib": ["esnext", "dom", "dom.iterable"]
|
|
||||||
},
|
|
||||||
"include": ["."]
|
|
||||||
}
|
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "preserve",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"lib": ["ES2023", "DOM", "DOM.Iterable"],
|
||||||
|
|
||||||
|
"noEmit": true,
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"erasableSyntaxOnly": true,
|
||||||
|
|
||||||
|
"strict": true,
|
||||||
|
"noImplicitOverride": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
|
||||||
|
"skipLibCheck": true
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"**/node_modules/**",
|
||||||
|
"**/dist/**",
|
||||||
|
"template",
|
||||||
|
"bin",
|
||||||
|
"docs/snippets"
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["ES2023", "DOM", "DOM.Iterable"],
|
||||||
|
"types": ["./client.d.ts"],
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"paths": {
|
||||||
|
"vitepress": ["./src/client/index.ts"],
|
||||||
|
// default-theme.d.ts instead of theme.d.ts so type checks and the .vue
|
||||||
|
// declaration emit don't depend on a built dist.
|
||||||
|
"vitepress/theme": ["./types/default-theme.d.ts"],
|
||||||
|
// dev-server virtual modules, resolved onto their ambient shims
|
||||||
|
"@siteData": ["./src/client/shims.d.ts"],
|
||||||
|
"@theme/index": ["./src/client/shims.d.ts"],
|
||||||
|
"@localSearchIndex": ["./src/client/shims.d.ts"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["src/client"]
|
||||||
|
}
|
||||||
@ -1,22 +1,3 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"extends": "./tsconfig.base.json"
|
||||||
"module": "esnext",
|
|
||||||
"target": "esnext",
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"strict": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"noUnusedLocals": true,
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"verbatimModuleSyntax": true,
|
|
||||||
"jsx": "preserve",
|
|
||||||
"lib": ["esnext", "dom", "dom.iterable"]
|
|
||||||
},
|
|
||||||
"exclude": [
|
|
||||||
"**/node_modules/**",
|
|
||||||
"**/dist/**",
|
|
||||||
"template",
|
|
||||||
"bin",
|
|
||||||
"docs/snippets"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["ES2023"],
|
||||||
|
"types": ["node"]
|
||||||
|
},
|
||||||
|
"include": ["src/node", "scripts", "tsdown.config.ts", "shared-globals.d.ts"]
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["ES2023"],
|
||||||
|
"types": [],
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"exactOptionalPropertyTypes": true
|
||||||
|
},
|
||||||
|
"include": ["src/shared", "shared-globals.d.ts"]
|
||||||
|
}
|
||||||
@ -0,0 +1,241 @@
|
|||||||
|
import { readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
||||||
|
import path from 'node:path'
|
||||||
|
|
||||||
|
import { defineConfig, type Rolldown, type UserConfig } from 'tsdown'
|
||||||
|
import { vueSfcPlugin } from 'vue-sfc-transformer/rolldown'
|
||||||
|
|
||||||
|
const ROOT = import.meta.dirname
|
||||||
|
|
||||||
|
// wipe all of dist (not just the outDirs) once per invocation; the configs
|
||||||
|
// set `clean: false` so watch-mode rebuilds never clear it mid-session
|
||||||
|
rmSync(path.join(ROOT, 'dist'), {
|
||||||
|
recursive: true,
|
||||||
|
force: true,
|
||||||
|
maxRetries: 10
|
||||||
|
})
|
||||||
|
|
||||||
|
const normalizePath = (p: string): string => {
|
||||||
|
const normalized = p.replaceAll('\\', '/')
|
||||||
|
return process.platform === 'win32' ? normalized.toLowerCase() : normalized
|
||||||
|
}
|
||||||
|
|
||||||
|
const TYPES_DIR = normalizePath(path.join(ROOT, 'types')) + '/'
|
||||||
|
|
||||||
|
function isRootTypes(id: string, importer: string | undefined): boolean {
|
||||||
|
if (!importer || !/^\.\.?\//.test(id)) return false
|
||||||
|
const resolved = normalizePath(path.resolve(path.dirname(importer), id))
|
||||||
|
return resolved.startsWith(TYPES_DIR)
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep .d.ts files under types/* external so module augmentations in the
|
||||||
|
// output still target the same files users reference, spelled with the .js
|
||||||
|
// extension so they resolve under node16 too
|
||||||
|
function rootTypesSpecifiers(): Rolldown.Plugin {
|
||||||
|
return {
|
||||||
|
name: 'vitepress:root-types-specifiers',
|
||||||
|
resolveId: {
|
||||||
|
order: 'pre',
|
||||||
|
handler(id, importer) {
|
||||||
|
if (
|
||||||
|
importer &&
|
||||||
|
/\.d\.[cm]?ts$/.test(importer) &&
|
||||||
|
isRootTypes(id, importer)
|
||||||
|
) {
|
||||||
|
return { id: id.replace(/\.js$/, '') + '.js', external: 'relative' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// src/shared is compiled twice, once per environment, via gitignored copies.
|
||||||
|
// Copies are written only when their content differs — they are watched
|
||||||
|
// modules, so an unconditional write on every buildStart would retrigger the
|
||||||
|
// watcher in an endless loop.
|
||||||
|
function syncSharedFiles(
|
||||||
|
dest: 'client' | 'node',
|
||||||
|
track?: (file: string) => void
|
||||||
|
): void {
|
||||||
|
const src = path.join(ROOT, 'src/shared')
|
||||||
|
for (const entry of readdirSync(src, {
|
||||||
|
recursive: true,
|
||||||
|
encoding: 'utf8'
|
||||||
|
})) {
|
||||||
|
if (!entry.endsWith('.ts')) continue
|
||||||
|
const file = path.join(src, entry)
|
||||||
|
track?.(file)
|
||||||
|
const content = readFileSync(file)
|
||||||
|
const copy = path.join(ROOT, 'src', dest, entry)
|
||||||
|
let stale = true
|
||||||
|
try {
|
||||||
|
stale = !content.equals(readFileSync(copy))
|
||||||
|
} catch {}
|
||||||
|
if (stale) writeFileSync(copy, content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// seed the copies now, before the entry globs below are resolved
|
||||||
|
syncSharedFiles('client')
|
||||||
|
syncSharedFiles('node')
|
||||||
|
|
||||||
|
// keeps the copies fresh across watch-mode rebuilds
|
||||||
|
function syncShared(dest: 'client' | 'node'): Rolldown.Plugin {
|
||||||
|
return {
|
||||||
|
name: 'vitepress:sync-shared',
|
||||||
|
buildStart() {
|
||||||
|
syncSharedFiles(dest, (file) => this.addWatchFile(file))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ships styles and fonts as-is, keeps css imports as relative specifiers in
|
||||||
|
// the output for the consumer's bundler, and drops them from declaration
|
||||||
|
// files where they would dangle.
|
||||||
|
function clientAssets(): Rolldown.Plugin {
|
||||||
|
const src = path.join(ROOT, 'src/client')
|
||||||
|
return {
|
||||||
|
name: 'vitepress:client-assets',
|
||||||
|
resolveId(id) {
|
||||||
|
if (id.endsWith('.css') && id[0] === '.') {
|
||||||
|
return { id, external: 'relative' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
buildStart() {
|
||||||
|
for (const entry of readdirSync(src, {
|
||||||
|
recursive: true,
|
||||||
|
encoding: 'utf8'
|
||||||
|
})) {
|
||||||
|
if (!/\.(css|woff2)$/.test(entry)) continue
|
||||||
|
const file = path.join(src, entry)
|
||||||
|
this.addWatchFile(file)
|
||||||
|
this.emitFile({
|
||||||
|
type: 'asset',
|
||||||
|
fileName: normalizePath(entry),
|
||||||
|
source: readFileSync(file)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Declarations must resolve under node16, so relative specifiers need the .js
|
||||||
|
// extension: vue-tsc keeps the SFC's extensionless ones in .d.vue.ts files,
|
||||||
|
// and rolldown-plugin-dts drops the extension from chunk imports on Windows.
|
||||||
|
// Dangling side-effect css imports are removed along the way.
|
||||||
|
function fixDeclarationSpecifiers(): Rolldown.Plugin {
|
||||||
|
const stripCssImports = (code: string) =>
|
||||||
|
code.replace(/^\s*import\s+["'][^"']+\.css["'];?\s*\r?\n/gm, '')
|
||||||
|
const addJsExtensions = (code: string) =>
|
||||||
|
code.replace(
|
||||||
|
/(\bfrom\s*|\bimport\s*\(\s*|\bimport\s+)(['"])(\.\.?\/[^'"]*?)\2/g,
|
||||||
|
(match, keyword, quote, spec) =>
|
||||||
|
/\.[^/.]+$/.test(spec) ? match : `${keyword}${quote}${spec}.js${quote}`
|
||||||
|
)
|
||||||
|
const fix = (code: string) => addJsExtensions(stripCssImports(code))
|
||||||
|
return {
|
||||||
|
name: 'vitepress:fix-declaration-specifiers',
|
||||||
|
generateBundle(_options, bundle) {
|
||||||
|
for (const file of Object.values(bundle)) {
|
||||||
|
if (!/\.d\.(?:vue\.)?ts$/.test(file.fileName)) continue
|
||||||
|
if (file.type === 'chunk') file.code = fix(file.code)
|
||||||
|
else if (typeof file.source === 'string') file.source = fix(file.source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rebuilds re-emit every output; dropping byte-identical files before they
|
||||||
|
// are written keeps their mtimes stable, so watchers of dist (the docs dev
|
||||||
|
// server's vite) only react to files that actually changed.
|
||||||
|
function skipUnchanged(): Rolldown.Plugin {
|
||||||
|
return {
|
||||||
|
name: 'vitepress:skip-unchanged',
|
||||||
|
generateBundle: {
|
||||||
|
order: 'post',
|
||||||
|
handler(options, bundle) {
|
||||||
|
for (const [key, file] of Object.entries(bundle)) {
|
||||||
|
const next =
|
||||||
|
file.type === 'chunk'
|
||||||
|
? Buffer.from(file.code)
|
||||||
|
: Buffer.from(file.source)
|
||||||
|
try {
|
||||||
|
const existing = readFileSync(
|
||||||
|
path.resolve(ROOT, options.dir ?? '.', file.fileName)
|
||||||
|
)
|
||||||
|
if (existing.equals(next)) delete bundle[key]
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function withStableOutputs(config: UserConfig): UserConfig {
|
||||||
|
return {
|
||||||
|
...config,
|
||||||
|
clean: false,
|
||||||
|
plugins: [
|
||||||
|
...(config.plugins as Rolldown.Plugin[]),
|
||||||
|
fixDeclarationSpecifiers(),
|
||||||
|
skipUnchanged()
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const client: UserConfig = {
|
||||||
|
entry: ['src/client/**/*.ts', '!src/client/**/*.d.ts'],
|
||||||
|
outDir: 'dist/client',
|
||||||
|
platform: 'neutral',
|
||||||
|
unbundle: true,
|
||||||
|
fixedExtension: false,
|
||||||
|
dts: { vue: true },
|
||||||
|
tsconfig: 'tsconfig.client.json',
|
||||||
|
deps: {
|
||||||
|
// self-imports and dev-server virtual modules, resolved at site build time
|
||||||
|
neverBundle: [
|
||||||
|
/^vitepress(?:\/|$)/,
|
||||||
|
'@siteData',
|
||||||
|
'@theme/index',
|
||||||
|
'@localSearchIndex'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
syncShared('client'),
|
||||||
|
rootTypesSpecifiers(),
|
||||||
|
clientAssets(),
|
||||||
|
vueSfcPlugin({
|
||||||
|
srcDir: 'src/client',
|
||||||
|
cwd: ROOT,
|
||||||
|
tsconfig: './tsconfig.client.json'
|
||||||
|
})
|
||||||
|
],
|
||||||
|
checks: { pluginTimings: false }
|
||||||
|
}
|
||||||
|
|
||||||
|
const node: UserConfig = {
|
||||||
|
entry: ['src/node/index.ts', 'src/node/cli.ts'],
|
||||||
|
outDir: 'dist/node',
|
||||||
|
platform: 'node',
|
||||||
|
target: 'node22',
|
||||||
|
fixedExtension: false,
|
||||||
|
dts: true,
|
||||||
|
tsconfig: 'tsconfig.node.json',
|
||||||
|
// polyfill broken browser check from bundled deps
|
||||||
|
define: {
|
||||||
|
'navigator.userAgentData': 'undefined',
|
||||||
|
'navigator.userAgent': 'undefined'
|
||||||
|
},
|
||||||
|
deps: {
|
||||||
|
// devDependencies are bundled by design
|
||||||
|
onlyBundle: false,
|
||||||
|
// markdown-it types are provided by @types/markdown-it (a runtime dep)
|
||||||
|
dts: { neverBundle: /^markdown-it(?:\/|$)/ }
|
||||||
|
},
|
||||||
|
// code-level compression only — no name mangling or whitespace removal
|
||||||
|
minify: { compress: true, mangle: false, codegen: false },
|
||||||
|
outputOptions: { chunkFileNames: 'chunk-[hash].js' },
|
||||||
|
checks: { eval: false, pluginTimings: false },
|
||||||
|
plugins: [syncShared('node'), rootTypesSpecifiers()]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineConfig([client, node].map(withStableOutputs))
|
||||||
Loading…
Reference in new issue