All files / compiler-sfc/src rewriteDefault.ts

100% Statements 52/52
100% Branches 22/22
100% Functions 4/4
100% Lines 52/52

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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