importUsageCheck.ts 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. import { parseExpression } from '@babel/parser'
  2. import { SFCDescriptor } from '../parse'
  3. import {
  4. NodeTypes,
  5. SimpleExpressionNode,
  6. createRoot,
  7. parserOptions,
  8. transform,
  9. walkIdentifiers
  10. } from '@vue/compiler-dom'
  11. import { createCache } from '../cache'
  12. import { camelize, capitalize, isBuiltInDirective } from '@vue/shared'
  13. /**
  14. * Check if an import is used in the SFC's template. This is used to determine
  15. * the properties that should be included in the object returned from setup()
  16. * when not using inline mode.
  17. */
  18. export function isImportUsed(local: string, sfc: SFCDescriptor): boolean {
  19. return new RegExp(
  20. // #4274 escape $ since it's a special char in regex
  21. // (and is the only regex special char that is valid in identifiers)
  22. `[^\\w$_]${local.replace(/\$/g, '\\$')}[^\\w$_]`
  23. ).test(resolveTemplateUsageCheckString(sfc))
  24. }
  25. const templateUsageCheckCache = createCache<string>()
  26. function resolveTemplateUsageCheckString(sfc: SFCDescriptor) {
  27. const { content, ast } = sfc.template!
  28. const cached = templateUsageCheckCache.get(content)
  29. if (cached) {
  30. return cached
  31. }
  32. let code = ''
  33. transform(createRoot([ast]), {
  34. nodeTransforms: [
  35. node => {
  36. if (node.type === NodeTypes.ELEMENT) {
  37. if (
  38. !parserOptions.isNativeTag!(node.tag) &&
  39. !parserOptions.isBuiltInComponent!(node.tag)
  40. ) {
  41. code += `,${camelize(node.tag)},${capitalize(camelize(node.tag))}`
  42. }
  43. for (let i = 0; i < node.props.length; i++) {
  44. const prop = node.props[i]
  45. if (prop.type === NodeTypes.DIRECTIVE) {
  46. if (!isBuiltInDirective(prop.name)) {
  47. code += `,v${capitalize(camelize(prop.name))}`
  48. }
  49. if (prop.exp) {
  50. code += `,${processExp(
  51. (prop.exp as SimpleExpressionNode).content,
  52. prop.name
  53. )}`
  54. }
  55. }
  56. }
  57. } else if (node.type === NodeTypes.INTERPOLATION) {
  58. code += `,${processExp(
  59. (node.content as SimpleExpressionNode).content
  60. )}`
  61. }
  62. }
  63. ]
  64. })
  65. code += ';'
  66. templateUsageCheckCache.set(content, code)
  67. return code
  68. }
  69. const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/
  70. function processExp(exp: string, dir?: string): string {
  71. if (/ as\s+\w|<.*>|:/.test(exp)) {
  72. if (dir === 'slot') {
  73. exp = `(${exp})=>{}`
  74. } else if (dir === 'on') {
  75. exp = `()=>{return ${exp}}`
  76. } else if (dir === 'for') {
  77. const inMatch = exp.match(forAliasRE)
  78. if (inMatch) {
  79. let [, LHS, RHS] = inMatch
  80. // #6088
  81. LHS = LHS.trim().replace(/^\(|\)$/g, '')
  82. return processExp(`(${LHS})=>{}`) + processExp(RHS)
  83. }
  84. }
  85. let ret = ''
  86. // has potential type cast or generic arguments that uses types
  87. const ast = parseExpression(exp, { plugins: ['typescript'] })
  88. walkIdentifiers(ast, node => {
  89. ret += `,` + node.name
  90. })
  91. return ret
  92. }
  93. return stripStrings(exp)
  94. }
  95. function stripStrings(exp: string) {
  96. return exp
  97. .replace(/'[^']*'|"[^"]*"/g, '')
  98. .replace(/`[^`]+`/g, stripTemplateString)
  99. }
  100. function stripTemplateString(str: string): string {
  101. const interpMatch = str.match(/\${[^}]+}/g)
  102. if (interpMatch) {
  103. return interpMatch.map(m => m.slice(2, -1)).join(',')
  104. }
  105. return ''
  106. }