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.

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