rewriteDefault.ts 2.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
  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 = /((?:^|\n|;)\s*)export\s+default\s+class\s+([\w$]+)/
  6. /**
  7. * Utility for rewriting `export default` in a script block into a variable
  8. * declaration so that we can inject things into it
  9. */
  10. export function rewriteDefault(
  11. input: string,
  12. as: string,
  13. parserPlugins?: ParserPlugin[]
  14. ): string {
  15. if (!hasDefaultExport(input)) {
  16. return input + `\nconst ${as} = {}`
  17. }
  18. let replaced: string | undefined
  19. const classMatch = input.match(exportDefaultClassRE)
  20. if (classMatch) {
  21. replaced =
  22. input.replace(exportDefaultClassRE, '$1class $2') +
  23. `\nconst ${as} = ${classMatch[2]}`
  24. } else {
  25. replaced = input.replace(defaultExportRE, `$1const ${as} =`)
  26. }
  27. if (!hasDefaultExport(replaced)) {
  28. return replaced
  29. }
  30. // if the script somehow still contains `default export`, it probably has
  31. // multi-line comments or template strings. fallback to a full parse.
  32. const s = new MagicString(input)
  33. const ast = parse(input, {
  34. sourceType: 'module',
  35. plugins: parserPlugins
  36. }).program.body
  37. ast.forEach(node => {
  38. if (node.type === 'ExportDefaultDeclaration') {
  39. s.overwrite(node.start!, node.declaration.start!, `const ${as} = `)
  40. }
  41. if (node.type === 'ExportNamedDeclaration') {
  42. node.specifiers.forEach(specifier => {
  43. if (
  44. specifier.type === 'ExportSpecifier' &&
  45. specifier.exported.type === 'Identifier' &&
  46. specifier.exported.name === 'default'
  47. ) {
  48. const end = specifier.end!
  49. s.overwrite(
  50. specifier.start!,
  51. input.charAt(end) === ',' ? end + 1 : end,
  52. ``
  53. )
  54. s.append(`\nconst ${as} = ${specifier.local.name}`)
  55. }
  56. })
  57. }
  58. })
  59. return s.toString()
  60. }
  61. export function hasDefaultExport(input: string): boolean {
  62. return defaultExportRE.test(input) || namedDefaultExportRE.test(input)
  63. }