core/rollup.dts.config.js

205 lines
6.7 KiB
JavaScript

// @ts-check
import assert from 'node:assert/strict'
import { parse } from '@babel/parser'
import { existsSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'
import MagicString from 'magic-string'
import dts from 'rollup-plugin-dts'
if (!existsSync('temp/packages')) {
console.warn(
'no temp dts files found. run `tsc -p tsconfig.build-browser.json && tsc -p tsconfig.build-node.json` first.',
)
process.exit(1)
}
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
export default targetPackages.map(
/** @returns {import('rollup').RollupOptions} */
pkg => {
return {
input: `./temp/packages/${pkg}/src/index.d.ts`,
output: {
file: `packages/${pkg}/dist/${pkg}.d.ts`,
format: 'es',
},
plugins: [dts(), patchTypes(pkg), ...(pkg === 'vue' ? [copyMts()] : [])],
onwarn(warning, warn) {
// during dts rollup, everything is externalized by default
if (
warning.code === 'UNRESOLVED_IMPORT' &&
!warning.exporter?.startsWith('.')
) {
return
}
warn(warning)
},
}
},
)
/**
* Patch the dts generated by rollup-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('rollup').Plugin}
*/
function patchTypes(pkg) {
return {
name: 'patch-types',
renderChunk(code, chunk) {
const s = new MagicString(code)
const ast = parse(code, {
plugins: ['typescript'],
sourceType: 'module',
})
/**
* @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')
s.prependLeft(start, `export `)
}
}
const isExported = new Set()
const shouldRemoveExport = new Set()
// pass 0: check all exported types
for (const node of ast.program.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(spec.local.name)
}
}
}
}
// pass 1: add exports
for (const node of ast.program.body) {
if (node.type === 'VariableDeclaration') {
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'
) {
processDeclaration(node)
}
}
// pass 2: remove exports
for (const node of ast.program.body) {
if (node.type === 'ExportNamedDeclaration' && !node.source) {
let removed = 0
for (let i = 0; i < node.specifiers.length; i++) {
const spec = node.specifiers[i]
if (
spec.type === 'ExportSpecifier' &&
shouldRemoveExport.has(spec.local.name)
) {
assert(spec.exported.type === 'Identifier')
const exported = spec.exported.name
if (exported !== spec.local.name) {
// this only happens if we have something like
// type Foo
// export { Foo as Bar }
continue
}
const next = node.specifiers[i + 1]
if (next) {
assert(typeof spec.start === 'number')
assert(typeof next.start === 'number')
s.remove(spec.start, next.start)
} else {
// last one
const prev = node.specifiers[i - 1]
assert(typeof spec.start === 'number')
assert(typeof spec.end === 'number')
s.remove(
prev
? (assert(typeof prev.end === 'number'), prev.end)
: spec.start,
spec.end,
)
}
removed++
}
}
if (removed === node.specifiers.length) {
assert(typeof node.start === 'number')
assert(typeof node.end === 'number')
s.remove(node.start, node.end)
}
}
}
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('rollup').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)
},
}
}