From: Evan You Date: Thu, 22 Jul 2021 16:53:08 +0000 (-0400) Subject: refactor(compiler-sfc): improve script setup import expose heuristics X-Git-Tag: v3.2.0-beta.5~9 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f0ca233d8bb101b901f535343707d1177698ddb0;p=thirdparty%2Fvuejs%2Fcore.git refactor(compiler-sfc): improve script setup import expose heuristics --- diff --git a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap index cc44a7ab10..5e30973ac1 100644 --- a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap +++ b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap @@ -206,7 +206,7 @@ return { x } exports[`SFC compile `) assertCode(content) + // FooBar: should not be matched by plain text // FooBaz: used as PascalCase component // FooQux: used as kebab-case component // vMyDir: used as directive v-my-dir // x: used in interpolation - expect(content).toMatch(`return { fooBar, FooBaz, FooQux, vMyDir, x }`) + // y: should not be matched by {{ yy }} or 'y' in binding exps + expect(content).toMatch(`return { fooBar, FooBaz, FooQux, vMyDir, x, z }`) }) }) diff --git a/packages/compiler-sfc/src/cache.ts b/packages/compiler-sfc/src/cache.ts new file mode 100644 index 0000000000..42d5b63f7f --- /dev/null +++ b/packages/compiler-sfc/src/cache.ts @@ -0,0 +1,5 @@ +export function createCache(size = 500) { + return __GLOBAL__ || __ESM_BROWSER__ + ? new Map() + : (new (require('lru-cache'))(size) as Map) +} diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index 82b84528d0..8fb6b7ff61 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -2,9 +2,14 @@ import MagicString from 'magic-string' import { BindingMetadata, BindingTypes, + createRoot, locStub, - UNREF -} from '@vue/compiler-core' + NodeTypes, + transform, + parserOptions, + UNREF, + SimpleExpressionNode +} from '@vue/compiler-dom' import { ScriptSetupTextRanges, SFCDescriptor, @@ -14,8 +19,10 @@ import { import { parse as _parse, ParserOptions, ParserPlugin } from '@babel/parser' import { babelParserDefaultPlugins, + camelize, + capitalize, generateCodeFrame, - hyphenate + makeMap } from '@vue/shared' import { Node, @@ -49,6 +56,7 @@ import { import { compileTemplate, SFCTemplateCompileOptions } from './compileTemplate' import { warnExperimental, warnOnce } from './warn' import { rewriteDefault } from './rewriteDefault' +import { createCache } from './cache' // Special compiler macros const DEFINE_PROPS = 'defineProps' @@ -61,6 +69,10 @@ const $COMPUTED = `$computed` const $FROM_REFS = `$fromRefs` const $RAW = `$raw` +const isBuiltInDir = makeMap( + `once,memo,if,else,else-if,slot,text,html,on,bind,model,show,cloak,is` +) + export interface SFCScriptCompileOptions { /** * Scope ID for prefixing injected CSS varialbes. @@ -319,10 +331,10 @@ export function compileScript( } let isUsedInTemplate = true - if (sfc.template && !sfc.template.src) { - isUsedInTemplate = new RegExp( - `\\b(?:${local}|${hyphenate(local)})\\b` - ).test(sfc.template.content) + if (isTS && sfc.template && !sfc.template.src) { + isUsedInTemplate = new RegExp(`\\b${local}\\b`).test( + resolveTemplateUsageCheckString(sfc) + ) } userImports[local] = { @@ -2154,3 +2166,53 @@ function toTextRange(node: Node): TextRange { end: node.end! } } + +const templateUsageCheckCache = createCache() + +function resolveTemplateUsageCheckString(sfc: SFCDescriptor) { + const { content, ast } = sfc.template! + const cached = templateUsageCheckCache.get(content) + if (cached) { + return cached + } + + let code = '' + transform(createRoot([ast]), { + nodeTransforms: [ + node => { + if (node.type === NodeTypes.ELEMENT) { + if ( + !parserOptions.isNativeTag!(node.tag) && + !parserOptions.isBuiltInComponent!(node.tag) + ) { + code += `,${capitalize(camelize(node.tag))}` + } + for (let i = 0; i < node.props.length; i++) { + const prop = node.props[i] + if (prop.type === NodeTypes.DIRECTIVE) { + if (!isBuiltInDir(prop.name)) { + code += `,v${capitalize(camelize(prop.name))}` + } + if (prop.exp) { + code += `,${stripStrings( + (prop.exp as SimpleExpressionNode).content + )}` + } + } + } + } else if (node.type === NodeTypes.INTERPOLATION) { + code += `,${stripStrings( + (node.content as SimpleExpressionNode).content + )}` + } + } + ] + }) + + templateUsageCheckCache.set(content, code) + return code +} + +function stripStrings(exp: string) { + return exp.replace(/'[^']+'|"[^"]+"|`[^`]+`/g, '') +} diff --git a/packages/compiler-sfc/src/parse.ts b/packages/compiler-sfc/src/parse.ts index 019851efab..53eb4ad3fb 100644 --- a/packages/compiler-sfc/src/parse.ts +++ b/packages/compiler-sfc/src/parse.ts @@ -12,6 +12,7 @@ import { TemplateCompiler } from './compileTemplate' import { Statement } from '@babel/types' import { parseCssVars } from './cssVars' import { warnExperimental } from './warn' +import { createCache } from './cache' export interface SFCParseOptions { filename?: string @@ -89,14 +90,7 @@ export interface SFCParseResult { errors: (CompilerError | SyntaxError)[] } -const SFC_CACHE_MAX_SIZE = 500 -const sourceToSFC = - __GLOBAL__ || __ESM_BROWSER__ - ? new Map() - : (new (require('lru-cache'))(SFC_CACHE_MAX_SIZE) as Map< - string, - SFCParseResult - >) +const sourceToSFC = createCache() export function parse( source: string,