rewriteDefault.ts 3.6 KB

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