rollup.dts.config.js 7.0 KB

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