rollup.dts.config.js 6.0 KB

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