rolldown.dts.config.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. // @ts-check
  2. import assert from 'node:assert/strict'
  3. import { parseSync } from 'oxc-parser'
  4. import { existsSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'
  5. import MagicString from 'magic-string'
  6. import { dts } from 'rolldown-plugin-dts'
  7. import { createRequire } from 'node:module'
  8. import { fileURLToPath } from 'node:url'
  9. import path from 'node:path'
  10. if (!existsSync('temp/packages')) {
  11. console.warn(
  12. 'no temp dts files found. run `tsc -p tsconfig.build.json --noCheck` first.',
  13. )
  14. process.exit(1)
  15. }
  16. const require = createRequire(import.meta.url)
  17. const __dirname = fileURLToPath(new URL('.', import.meta.url))
  18. const packagesDir = path.resolve(__dirname, 'packages')
  19. const packages = readdirSync('temp/packages')
  20. const targets = process.env.TARGETS ? process.env.TARGETS.split(',') : null
  21. const targetPackages = targets
  22. ? packages.filter(pkg => targets.includes(pkg))
  23. : packages
  24. function resolveExternal(/**@type {string}*/ packageName) {
  25. const pkg = require(`${packagesDir}/${packageName}/package.json`)
  26. return [
  27. ...Object.keys(pkg.dependencies || {}),
  28. ...Object.keys(pkg.devDependencies || {}),
  29. ...Object.keys(pkg.peerDependencies || {}),
  30. ]
  31. }
  32. export default targetPackages.map(
  33. /** @returns {import('rolldown').BuildOptions} */
  34. pkg => {
  35. return {
  36. input: `./temp/packages/${pkg}/src/index${pkg === 'vue' ? '-with-vapor' : ''}.d.ts`,
  37. output: {
  38. file: `packages/${pkg}/dist/${pkg}.d.ts`,
  39. format: 'es',
  40. },
  41. external: resolveExternal(pkg),
  42. plugins: [dts(), patchTypes(pkg), ...(pkg === 'vue' ? [copyMts()] : [])],
  43. onwarn(warning, warn) {
  44. // during dts rolldown, everything is externalized by default
  45. if (
  46. warning.code === 'UNRESOLVED_IMPORT' &&
  47. !warning.exporter?.startsWith('.')
  48. ) {
  49. return
  50. }
  51. warn(warning)
  52. },
  53. }
  54. },
  55. )
  56. /**
  57. * Patch the dts generated by rolldown-plugin-dts
  58. * 1. Convert all types to inline exports
  59. * and remove them from the big export {} declaration
  60. * otherwise it gets weird in vitepress `defineComponent` call with
  61. * "the inferred type cannot be named without a reference"
  62. * 2. Append custom augmentations (jsx, macros)
  63. *
  64. * @param {string} pkg
  65. * @returns {import('rolldown').Plugin}
  66. */
  67. function patchTypes(pkg) {
  68. return {
  69. name: 'patch-types',
  70. renderChunk(code, chunk) {
  71. const s = new MagicString(code)
  72. const { program: ast, errors } = parseSync('x.d.ts', code, {
  73. sourceType: 'module',
  74. })
  75. if (errors.length) {
  76. throw new Error(errors.join('\n'))
  77. }
  78. /**
  79. * @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
  80. * @param {import('@babel/types').VariableDeclaration} [parentDecl]
  81. */
  82. function processDeclaration(node, parentDecl) {
  83. if (!node.id) {
  84. return
  85. }
  86. assert(node.id.type === 'Identifier')
  87. const name = node.id.name
  88. if (name.startsWith('_')) {
  89. return
  90. }
  91. shouldRemoveExport.add(name)
  92. if (isExported.has(name)) {
  93. const start = (parentDecl || node).start
  94. assert(typeof start === 'number')
  95. s.prependLeft(start, `export `)
  96. }
  97. }
  98. const isExported = new Set()
  99. const shouldRemoveExport = new Set()
  100. // pass 0: check all exported types
  101. for (const node of ast.body) {
  102. if (node.type === 'ExportNamedDeclaration' && !node.source) {
  103. for (let i = 0; i < node.specifiers.length; i++) {
  104. const spec = node.specifiers[i]
  105. if (spec.type === 'ExportSpecifier') {
  106. isExported.add(
  107. 'name' in spec.local ? spec.local.name : spec.local.value,
  108. )
  109. }
  110. }
  111. }
  112. }
  113. // pass 1: add exports
  114. for (const node of ast.body) {
  115. if (node.type === 'VariableDeclaration') {
  116. // @ts-expect-error waiting for oxc-parser to expose types
  117. processDeclaration(node.declarations[0], node)
  118. if (node.declarations.length > 1) {
  119. assert(typeof node.start === 'number')
  120. assert(typeof node.end === 'number')
  121. throw new Error(
  122. `unhandled declare const with more than one declarators:\n${code.slice(
  123. node.start,
  124. node.end,
  125. )}`,
  126. )
  127. }
  128. } else if (
  129. node.type === 'TSTypeAliasDeclaration' ||
  130. node.type === 'TSInterfaceDeclaration' ||
  131. node.type === 'TSDeclareFunction' ||
  132. node.type === 'TSEnumDeclaration' ||
  133. node.type === 'ClassDeclaration'
  134. ) {
  135. // @ts-expect-error waiting for oxc-parser to expose types
  136. processDeclaration(node)
  137. }
  138. }
  139. // pass 2: remove exports
  140. for (const node of ast.body) {
  141. if (node.type === 'ExportNamedDeclaration' && !node.source) {
  142. let removed = 0
  143. for (let i = 0; i < node.specifiers.length; i++) {
  144. const spec = node.specifiers[i]
  145. const localName =
  146. 'name' in spec.local ? spec.local.name : spec.local.value
  147. if (
  148. spec.type === 'ExportSpecifier' &&
  149. shouldRemoveExport.has(localName)
  150. ) {
  151. assert(spec.exported.type === 'Identifier')
  152. const exported = spec.exported.name
  153. if (exported !== localName) {
  154. // this only happens if we have something like
  155. // type Foo
  156. // export { Foo as Bar }
  157. continue
  158. }
  159. const next = node.specifiers[i + 1]
  160. if (next) {
  161. assert(typeof spec.start === 'number')
  162. assert(typeof next.start === 'number')
  163. s.remove(spec.start, next.start)
  164. } else {
  165. // last one
  166. const prev = node.specifiers[i - 1]
  167. assert(typeof spec.start === 'number')
  168. assert(typeof spec.end === 'number')
  169. s.remove(
  170. prev
  171. ? (assert(typeof prev.end === 'number'), prev.end)
  172. : spec.start,
  173. spec.end,
  174. )
  175. }
  176. removed++
  177. }
  178. }
  179. if (removed === node.specifiers.length) {
  180. assert(typeof node.start === 'number')
  181. assert(typeof node.end === 'number')
  182. s.remove(node.start, node.end)
  183. }
  184. }
  185. }
  186. code = s.toString()
  187. // append pkg specific types
  188. const additionalTypeDir = `packages/${pkg}/types`
  189. if (existsSync(additionalTypeDir)) {
  190. code +=
  191. '\n' +
  192. readdirSync(additionalTypeDir)
  193. .map(file => readFileSync(`${additionalTypeDir}/${file}`, 'utf-8'))
  194. .join('\n')
  195. }
  196. return code
  197. },
  198. }
  199. }
  200. /**
  201. * According to https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-7.html#packagejson-exports-imports-and-self-referencing
  202. * the only way to correct provide types for both Node ESM and CJS is to have
  203. * two separate declaration files, so we need to copy vue.d.ts to vue.d.mts
  204. * upon build.
  205. *
  206. * @returns {import('rolldown').Plugin}
  207. */
  208. function copyMts() {
  209. return {
  210. name: 'copy-vue-mts',
  211. writeBundle(_, bundle) {
  212. assert('code' in bundle['vue.d.ts'])
  213. writeFileSync('packages/vue/dist/vue.d.mts', bundle['vue.d.ts'].code)
  214. },
  215. }
  216. }