rewriteDefault.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. import { parse } from '@babel/parser'
  2. import MagicString from 'magic-string'
  3. import type { ParserPlugin } from '@babel/parser'
  4. import type { Identifier, Statement } from '@babel/types'
  5. import { resolveParserPlugins } from './script/context'
  6. export function rewriteDefault(
  7. input: string,
  8. as: string,
  9. parserPlugins?: ParserPlugin[],
  10. ): string {
  11. const ast = parse(input, {
  12. sourceType: 'module',
  13. plugins: resolveParserPlugins('js', parserPlugins),
  14. }).program.body
  15. const s = new MagicString(input)
  16. rewriteDefaultAST(ast, s, as)
  17. return s.toString()
  18. }
  19. /**
  20. * Utility for rewriting `export default` in a script block into a variable
  21. * declaration so that we can inject things into it
  22. */
  23. export function rewriteDefaultAST(
  24. ast: Statement[],
  25. s: MagicString,
  26. as: string,
  27. ): void {
  28. if (!hasDefaultExport(ast)) {
  29. s.append(`\nconst ${as} = {}`)
  30. return
  31. }
  32. // if the script somehow still contains `default export`, it probably has
  33. // multi-line comments or template strings. fallback to a full parse.
  34. ast.forEach(node => {
  35. if (node.type === 'ExportDefaultDeclaration') {
  36. if (node.declaration.type === 'ClassDeclaration' && node.declaration.id) {
  37. let start: number =
  38. node.declaration.decorators && node.declaration.decorators.length > 0
  39. ? node.declaration.decorators[
  40. node.declaration.decorators.length - 1
  41. ].end!
  42. : node.start!
  43. s.overwrite(start, node.declaration.id.start!, ` class `)
  44. s.append(`\nconst ${as} = ${node.declaration.id.name}`)
  45. } else {
  46. s.overwrite(node.start!, node.declaration.start!, `const ${as} = `)
  47. }
  48. } else if (node.type === 'ExportNamedDeclaration') {
  49. for (const specifier of node.specifiers) {
  50. if (
  51. specifier.type === 'ExportSpecifier' &&
  52. specifier.exported.type === 'Identifier' &&
  53. specifier.exported.name === 'default'
  54. ) {
  55. if (node.source) {
  56. if (specifier.local.name === 'default') {
  57. s.prepend(
  58. `import { default as __VUE_DEFAULT__ } from '${node.source.value}'\n`,
  59. )
  60. const end = specifierEnd(s, specifier.local.end!, node.end!)
  61. s.remove(specifier.start!, end)
  62. s.append(`\nconst ${as} = __VUE_DEFAULT__`)
  63. continue
  64. } else {
  65. s.prepend(
  66. `import { ${s.slice(
  67. specifier.local.start!,
  68. specifier.local.end!,
  69. )} as __VUE_DEFAULT__ } from '${node.source.value}'\n`,
  70. )
  71. const end = specifierEnd(s, specifier.exported.end!, node.end!)
  72. s.remove(specifier.start!, end)
  73. s.append(`\nconst ${as} = __VUE_DEFAULT__`)
  74. continue
  75. }
  76. }
  77. const end = specifierEnd(s, specifier.end!, node.end!)
  78. s.remove(specifier.start!, end)
  79. s.append(`\nconst ${as} = ${specifier.local.name}`)
  80. }
  81. }
  82. }
  83. })
  84. }
  85. export function hasDefaultExport(ast: Statement[]): boolean {
  86. for (const stmt of ast) {
  87. if (stmt.type === 'ExportDefaultDeclaration') {
  88. return true
  89. } else if (
  90. stmt.type === 'ExportNamedDeclaration' &&
  91. stmt.specifiers.some(
  92. spec => (spec.exported as Identifier).name === 'default',
  93. )
  94. ) {
  95. return true
  96. }
  97. }
  98. return false
  99. }
  100. function specifierEnd(s: MagicString, end: number, nodeEnd: number | null) {
  101. // export { default , foo } ...
  102. let hasCommas = false
  103. let oldEnd = end
  104. while (end < nodeEnd!) {
  105. if (/\s/.test(s.slice(end, end + 1))) {
  106. end++
  107. } else if (s.slice(end, end + 1) === ',') {
  108. end++
  109. hasCommas = true
  110. break
  111. } else if (s.slice(end, end + 1) === '}') {
  112. break
  113. }
  114. }
  115. return hasCommas ? end : oldEnd
  116. }