You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

204 lines
6.6 KiB

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