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.

191 lines
5.9 KiB

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