| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271 |
- // @ts-check
- import assert from 'node:assert/strict'
- import { parseSync } from 'oxc-parser'
- import { existsSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'
- import { dts } from 'rolldown-plugin-dts'
- import { createRequire } from 'node:module'
- import { fileURLToPath } from 'node:url'
- import path from 'node:path'
- if (!existsSync('temp/packages')) {
- console.warn(
- 'no temp dts files found. run `tsc -p tsconfig.build.json --noCheck` first.',
- )
- process.exit(1)
- }
- const require = createRequire(import.meta.url)
- const __dirname = fileURLToPath(new URL('.', import.meta.url))
- const packagesDir = path.resolve(__dirname, 'packages')
- const packages = readdirSync('temp/packages')
- const targets = process.env.TARGETS ? process.env.TARGETS.split(',') : null
- const targetPackages = targets
- ? packages.filter(pkg => targets.includes(pkg))
- : packages
- function resolveExternal(/**@type {string}*/ packageName) {
- const pkg = require(`${packagesDir}/${packageName}/package.json`)
- return [
- ...Object.keys(pkg.dependencies || {}),
- ...Object.keys(pkg.devDependencies || {}),
- ...Object.keys(pkg.peerDependencies || {}),
- ]
- }
- export default targetPackages.map(
- /** @returns {import('rolldown').BuildOptions} */
- pkg => {
- return {
- input: `./temp/packages/${pkg}/src/index${pkg === 'vue' ? '-with-vapor' : ''}.d.ts`,
- output: {
- file: `packages/${pkg}/dist/${pkg}.d.ts`,
- format: 'es',
- },
- experimental: {
- nativeMagicString: true,
- },
- external: resolveExternal(pkg),
- plugins: [dts(), patchTypes(pkg), ...(pkg === 'vue' ? [copyMts()] : [])],
- onwarn(warning, warn) {
- // during dts rolldown, everything is externalized by default
- if (
- warning.code === 'UNRESOLVED_IMPORT' &&
- !warning.exporter?.startsWith('.')
- ) {
- return
- }
- warn(warning)
- },
- }
- },
- )
- /**
- * Patch the dts generated by rolldown-plugin-dts
- * 1. Convert all types to inline exports
- * and remove them from the big export {} declaration
- * otherwise it gets weird in vitepress `defineComponent` call with
- * "the inferred type cannot be named without a reference"
- * 2. Append custom augmentations (jsx, macros)
- *
- * @param {string} pkg
- * @returns {import('rolldown').Plugin}
- */
- function patchTypes(pkg) {
- return {
- name: 'patch-types',
- renderChunk(code, chunk, outputOptions, meta) {
- const s = meta.magicString
- const { program: ast, errors } = parseSync('x.d.ts', code, {
- sourceType: 'module',
- })
- if (errors.length) {
- throw new Error(errors.join('\n'))
- }
- /**
- * @param {import('@babel/types').VariableDeclarator | import('@babel/types').TSTypeAliasDeclaration | import('@babel/types').TSInterfaceDeclaration | import('@babel/types').TSDeclareFunction | import('@babel/types').TSInterfaceDeclaration | import('@babel/types').TSEnumDeclaration | import('@babel/types').ClassDeclaration} node
- * @param {import('@babel/types').VariableDeclaration} [parentDecl]
- */
- function processDeclaration(node, parentDecl) {
- if (!node.id) {
- return
- }
- assert(node.id.type === 'Identifier')
- const name = node.id.name
- if (name.startsWith('_')) {
- return
- }
- shouldRemoveExport.add(name)
- if (isExported.has(name)) {
- const start = (parentDecl || node).start
- assert(typeof start === 'number')
- // @ts-ignore
- s.prependLeft(start, `export `)
- }
- }
- const isExported = new Set()
- const shouldRemoveExport = new Set()
- // pass 0: check all exported types
- for (const node of ast.body) {
- if (node.type === 'ExportNamedDeclaration' && !node.source) {
- for (let i = 0; i < node.specifiers.length; i++) {
- const spec = node.specifiers[i]
- if (spec.type === 'ExportSpecifier') {
- isExported.add(
- 'name' in spec.local ? spec.local.name : spec.local.value,
- )
- }
- }
- }
- }
- // pass 1: add exports
- for (const node of ast.body) {
- if (node.type === 'VariableDeclaration') {
- // @ts-expect-error waiting for oxc-parser to expose types
- processDeclaration(node.declarations[0], node)
- if (node.declarations.length > 1) {
- assert(typeof node.start === 'number')
- assert(typeof node.end === 'number')
- throw new Error(
- `unhandled declare const with more than one declarators:\n${code.slice(
- node.start,
- node.end,
- )}`,
- )
- }
- } else if (
- node.type === 'TSTypeAliasDeclaration' ||
- node.type === 'TSInterfaceDeclaration' ||
- node.type === 'TSDeclareFunction' ||
- node.type === 'TSEnumDeclaration' ||
- node.type === 'ClassDeclaration'
- ) {
- // @ts-expect-error waiting for oxc-parser to expose types
- processDeclaration(node)
- }
- }
- // pass 2: remove exports
- for (const node of ast.body) {
- if (node.type === 'ExportNamedDeclaration' && !node.source) {
- // Precompute which specifiers are safe to remove.
- /** @type {boolean[]} */
- const removable = new Array(node.specifiers.length)
- let keptCount = 0
- for (let i = 0; i < node.specifiers.length; i++) {
- const spec = node.specifiers[i]
- const localName =
- 'name' in spec.local ? spec.local.name : spec.local.value
- let canRemove = false
- if (
- spec.type === 'ExportSpecifier' &&
- shouldRemoveExport.has(localName)
- ) {
- assert(spec.exported.type === 'Identifier')
- const exported = spec.exported.name
- if (exported === localName) {
- canRemove = true
- }
- }
- removable[i] = canRemove
- if (!canRemove) keptCount++
- }
- if (keptCount === 0) {
- assert(typeof node.start === 'number')
- assert(typeof node.end === 'number')
- // @ts-ignore
- s.remove(node.start, node.end)
- continue
- }
- // Next kept specifier index for each position (or -1).
- /** @type {number[]} */
- const nextKeptIndex = new Array(node.specifiers.length).fill(-1)
- let nextKept = -1
- for (let i = node.specifiers.length - 1; i >= 0; i--) {
- if (!removable[i]) nextKept = i
- nextKeptIndex[i] = nextKept
- }
- // Build removal ranges by consecutive removable runs.
- /** @type {{ start: number, end: number }[]} */
- const ranges = []
- let i = 0
- let prevKeptIndex = -1
- while (i < node.specifiers.length) {
- if (!removable[i]) {
- prevKeptIndex = i
- i++
- continue
- }
- const runStart = i
- while (i < node.specifiers.length && removable[i]) i++
- const runEnd = i - 1
- const first = node.specifiers[runStart]
- const last = node.specifiers[runEnd]
- assert(typeof first.start === 'number')
- assert(typeof last.end === 'number')
- const nextKept = nextKeptIndex[runEnd]
- if (nextKept !== -1) {
- const nextSpec = node.specifiers[nextKept]
- assert(typeof nextSpec.start === 'number')
- ranges.push({ start: first.start, end: nextSpec.start })
- } else if (prevKeptIndex >= 0) {
- const prev = node.specifiers[prevKeptIndex]
- assert(typeof prev.end === 'number')
- ranges.push({ start: prev.end, end: last.end })
- } else {
- ranges.push({ start: first.start, end: last.end })
- }
- }
- // apply removals from back to front to keep ranges stable
- ranges.sort((a, b) => b.start - a.start)
- for (const range of ranges) {
- // @ts-ignore
- s.remove(range.start, range.end)
- }
- }
- }
- // @ts-ignore
- code = s.toString()
- // append pkg specific types
- const additionalTypeDir = `packages/${pkg}/types`
- if (existsSync(additionalTypeDir)) {
- code +=
- '\n' +
- readdirSync(additionalTypeDir)
- .map(file => readFileSync(`${additionalTypeDir}/${file}`, 'utf-8'))
- .join('\n')
- }
- return code
- },
- }
- }
- /**
- * According to https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-7.html#packagejson-exports-imports-and-self-referencing
- * the only way to correct provide types for both Node ESM and CJS is to have
- * two separate declaration files, so we need to copy vue.d.ts to vue.d.mts
- * upon build.
- *
- * @returns {import('rolldown').Plugin}
- */
- function copyMts() {
- return {
- name: 'copy-vue-mts',
- writeBundle(_, bundle) {
- assert('code' in bundle['vue.d.ts'])
- writeFileSync('packages/vue/dist/vue.d.mts', bundle['vue.d.ts'].code)
- },
- }
- }
|