feat(complier-sfc): hoist literal constants for script (#5752)

- Support using literal constants in macros
- fix useCssVars insert position edge case
- fix non-literal-const enum hoisting

close #5750
This commit is contained in:
三咲智子 Kevin Deng 2023-03-28 11:34:29 +08:00 committed by GitHub
parent e224922e97
commit 7def8b15b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 466 additions and 61 deletions

View File

@ -112,7 +112,11 @@ export const enum BindingTypes {
/**
* declared by other options, e.g. computed, inject
*/
OPTIONS = 'options'
OPTIONS = 'options',
/**
* a literal constant, e.g. 'foo', 1, true
*/
LITERAL_CONST = 'literal-const'
}
export type BindingMetadata = {

View File

@ -361,7 +361,8 @@ function resolveSetupReference(name: string, context: TransformContext) {
const fromConst =
checkType(BindingTypes.SETUP_CONST) ||
checkType(BindingTypes.SETUP_REACTIVE_CONST)
checkType(BindingTypes.SETUP_REACTIVE_CONST) ||
checkType(BindingTypes.LITERAL_CONST)
if (fromConst) {
return context.inline
? // in inline mode, const setup bindings (e.g. imports) can be used as-is

View File

@ -128,11 +128,7 @@ export function processExpression(
const isDestructureAssignment =
parent && isInDestructureAssignment(parent, parentStack)
if (
type === BindingTypes.SETUP_CONST ||
type === BindingTypes.SETUP_REACTIVE_CONST ||
localVars[raw]
) {
if (isConst(type) || localVars[raw]) {
return raw
} else if (type === BindingTypes.SETUP_REF) {
return `${raw}.value`
@ -223,7 +219,7 @@ export function processExpression(
if (!asParams && !isScopeVarReference && !isAllowedGlobal && !isLiteral) {
// const bindings exposed from setup can be skipped for patching but
// cannot be hoisted to module scope
if (bindingMetadata[node.content] === BindingTypes.SETUP_CONST) {
if (isConst(bindingMetadata[node.content])) {
node.constType = ConstantTypes.CAN_SKIP_PATCH
}
node.content = rewriteIdentifier(rawExp)
@ -372,3 +368,11 @@ export function stringifyExpression(exp: ExpressionNode | string): string {
.join('')
}
}
function isConst(type: unknown) {
return (
type === BindingTypes.SETUP_CONST ||
type === BindingTypes.LITERAL_CONST ||
type === BindingTypes.SETUP_REACTIVE_CONST
)
}

View File

@ -1,11 +1,12 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`SFC analyze <script> bindings > auto name inference > basic 1`] = `
"export default {
"const a = 1
export default {
__name: 'FooBar',
setup(__props, { expose }) {
expose();
const a = 1
return { a }
}
@ -683,7 +684,9 @@ return { props, get x() { return x } }
`;
exports[`SFC compile <script setup> > defineProps() 1`] = `
"export default {
"const bar = 1
export default {
props: {
foo: String
},
@ -693,7 +696,6 @@ exports[`SFC compile <script setup> > defineProps() 1`] = `
const props = __props;
const bar = 1
return { props, bar }
}
@ -755,12 +757,12 @@ return { a, props, emit }
exports[`SFC compile <script setup> > dev mode import usage check > TS annotations 1`] = `
"import { defineComponent as _defineComponent } from 'vue'
import { Foo, Bar, Baz, Qux, Fred } from './x'
const a = 1
export default /*#__PURE__*/_defineComponent({
setup(__props, { expose }) {
expose();
const a = 1
function b() {}
return { a, b, get Baz() { return Baz } }
@ -772,12 +774,12 @@ return { a, b, get Baz() { return Baz } }
exports[`SFC compile <script setup> > dev mode import usage check > attribute expressions 1`] = `
"import { defineComponent as _defineComponent } from 'vue'
import { bar, baz } from './x'
const cond = true
export default /*#__PURE__*/_defineComponent({
setup(__props, { expose }) {
expose();
const cond = true
return { cond, get bar() { return bar }, get baz() { return baz } }
}
@ -788,12 +790,12 @@ return { cond, get bar() { return bar }, get baz() { return baz } }
exports[`SFC compile <script setup> > dev mode import usage check > components 1`] = `
"import { defineComponent as _defineComponent } from 'vue'
import { FooBar, FooBaz, FooQux, foo } from './x'
const fooBar: FooBar = 1
export default /*#__PURE__*/_defineComponent({
setup(__props, { expose }) {
expose();
const fooBar: FooBar = 1
return { fooBar, get FooBaz() { return FooBaz }, get FooQux() { return FooQux }, get foo() { return foo } }
}
@ -886,7 +888,9 @@ return { get bar() { return bar } }
`;
exports[`SFC compile <script setup> > errors > should allow defineProps/Emit() referencing scope var 1`] = `
"export default {
"const bar = 1
export default {
props: {
foo: {
default: bar => bar + 1
@ -898,7 +902,6 @@ exports[`SFC compile <script setup> > errors > should allow defineProps/Emit() r
setup(__props, { expose }) {
expose();
const bar = 1
@ -1722,8 +1725,7 @@ return { Foo }
exports[`SFC compile <script setup> > with TypeScript > runtime Enum in normal script 1`] = `
"import { defineComponent as _defineComponent } from 'vue'
enum Foo { A = 123 }
export enum D { D = \\"D\\" }
const enum C { C = \\"C\\" }
enum B { B = \\"B\\" }
@ -1732,6 +1734,7 @@ export default /*#__PURE__*/_defineComponent({
setup(__props, { expose }) {
expose();
enum Foo { A = 123 }
return { D, C, B, Foo }
}

View File

@ -0,0 +1,132 @@
// Vitest Snapshot v1
exports[`sfc hoist static > should enable when only script setup 1`] = `
"const foo = 'bar'
export default {
setup(__props) {
const foo = 'bar'
return () => {}
}
}"
`;
exports[`sfc hoist static > should hoist expressions 1`] = `
"const unary = !false
const binary = 1 + 2
const conditional = 1 ? 2 : 3
const sequence = (1, true, 'foo', 1)
export default {
setup(__props) {
return () => {}
}
}"
`;
exports[`sfc hoist static > should hoist literal value 1`] = `
"const string = 'default value'
const number = 123
const boolean = false
const nil = null
const bigint = 100n
const template = \`str\`
const regex = /.*/g
export default {
setup(__props) {
return () => {}
}
}"
`;
exports[`sfc hoist static > should hoist w/ defineProps/Emits 1`] = `
"const defaultValue = 'default value'
export default {
props: {
foo: {
default: defaultValue
}
},
setup(__props) {
return () => {}
}
}"
`;
exports[`sfc hoist static > should not hoist a constant initialized to a reference value 1`] = `
"import { defineComponent as _defineComponent } from 'vue'
export default /*#__PURE__*/_defineComponent({
setup(__props) {
const KEY1 = Boolean
const KEY2 = [Boolean]
const KEY3 = [getCurrentInstance()]
let i = 0;
const KEY4 = (i++, 'foo')
enum KEY5 {
FOO = 1,
BAR = getCurrentInstance(),
}
const KEY6 = \`template\${i}\`
return () => {}
}
})"
`;
exports[`sfc hoist static > should not hoist a function or class 1`] = `
"export default {
setup(__props) {
const fn = () => {}
function fn2() {}
class Foo {}
return () => {}
}
}"
`;
exports[`sfc hoist static > should not hoist a object or array 1`] = `
"export default {
setup(__props) {
const obj = { foo: 'bar' }
const arr = [1, 2, 3]
return () => {}
}
}"
`;
exports[`sfc hoist static > should not hoist a variable 1`] = `
"export default {
setup(__props) {
let KEY1 = 'default value'
var KEY2 = 123
return () => {}
}
}"
`;

View File

@ -51,7 +51,7 @@ export default __default__"
exports[`CSS vars injection > codegen > should ignore comments 1`] = `
"import { useCssVars as _useCssVars, unref as _unref } from 'vue'
const color = 'red';const width = 100
export default {
setup(__props, { expose }) {
expose();
@ -59,7 +59,7 @@ export default {
_useCssVars(_ctx => ({
\\"xxxxxxxx-width\\": (width)
}))
const color = 'red';const width = 100
return { color, width }
}
@ -92,7 +92,7 @@ return { get a() { return a }, set a(v) { a = v }, get b() { return b }, set b(v
exports[`CSS vars injection > codegen > w/ <script setup> 1`] = `
"import { useCssVars as _useCssVars, unref as _unref } from 'vue'
const color = 'red'
export default {
setup(__props, { expose }) {
expose();
@ -100,7 +100,7 @@ export default {
_useCssVars(_ctx => ({
\\"xxxxxxxx-color\\": (color)
}))
const color = 'red'
return { color }
}
@ -109,7 +109,8 @@ return { color }
exports[`CSS vars injection > codegen > w/ <script setup> using the same var multiple times 1`] = `
"import { useCssVars as _useCssVars, unref as _unref } from 'vue'
const color = 'red'
export default {
setup(__props, { expose }) {
expose();
@ -118,7 +119,6 @@ _useCssVars(_ctx => ({
\\"xxxxxxxx-color\\": (color)
}))
const color = 'red'
return { color }
}
@ -146,6 +146,7 @@ export default __default__"
exports[`CSS vars injection > w/ <script setup> binding analysis 1`] = `
"import { useCssVars as _useCssVars, unref as _unref } from 'vue'
import { ref } from 'vue'
const color = 'red'
export default {
props: {
@ -160,7 +161,6 @@ _useCssVars(_ctx => ({
\\"xxxxxxxx-foo\\": (__props.foo)
}))
const color = 'red'
const size = ref('10px')

View File

@ -28,12 +28,12 @@ describe('SFC compile <script setup>', () => {
expect(bindings).toStrictEqual({
x: BindingTypes.SETUP_MAYBE_REF,
a: BindingTypes.SETUP_LET,
b: BindingTypes.SETUP_CONST,
b: BindingTypes.LITERAL_CONST,
c: BindingTypes.SETUP_CONST,
d: BindingTypes.SETUP_CONST,
xx: BindingTypes.SETUP_MAYBE_REF,
aa: BindingTypes.SETUP_LET,
bb: BindingTypes.SETUP_CONST,
bb: BindingTypes.LITERAL_CONST,
cc: BindingTypes.SETUP_CONST,
dd: BindingTypes.SETUP_CONST
})
@ -71,7 +71,7 @@ const bar = 1
// should analyze bindings
expect(bindings).toStrictEqual({
foo: BindingTypes.PROPS,
bar: BindingTypes.SETUP_CONST,
bar: BindingTypes.LITERAL_CONST,
props: BindingTypes.SETUP_REACTIVE_CONST
})
@ -1422,7 +1422,7 @@ const emit = defineEmits(['a', 'b'])
)
assertCode(content)
expect(bindings).toStrictEqual({
Foo: BindingTypes.SETUP_CONST
Foo: BindingTypes.LITERAL_CONST
})
})
@ -1439,10 +1439,10 @@ const emit = defineEmits(['a', 'b'])
)
assertCode(content)
expect(bindings).toStrictEqual({
D: BindingTypes.SETUP_CONST,
C: BindingTypes.SETUP_CONST,
B: BindingTypes.SETUP_CONST,
Foo: BindingTypes.SETUP_CONST
D: BindingTypes.LITERAL_CONST,
C: BindingTypes.LITERAL_CONST,
B: BindingTypes.LITERAL_CONST,
Foo: BindingTypes.LITERAL_CONST
})
})
@ -1450,11 +1450,12 @@ const emit = defineEmits(['a', 'b'])
const { content, bindings } = compile(
`<script setup lang="ts">
const enum Foo { A = 123 }
</script>`
</script>`,
{ hoistStatic: true }
)
assertCode(content)
expect(bindings).toStrictEqual({
Foo: BindingTypes.SETUP_CONST
Foo: BindingTypes.LITERAL_CONST
})
})
@ -1633,7 +1634,7 @@ const emit = defineEmits(['a', 'b'])
test('defineProps/Emit() referencing local var', () => {
expect(() =>
compile(`<script setup>
const bar = 1
let bar = 1
defineProps({
foo: {
default: () => bar
@ -1644,7 +1645,7 @@ const emit = defineEmits(['a', 'b'])
expect(() =>
compile(`<script setup>
const bar = 'hello'
let bar = 'hello'
defineEmits([bar])
</script>`)
).toThrow(`cannot reference locally declared variables`)
@ -1785,7 +1786,7 @@ describe('SFC analyze <script> bindings', () => {
</script>
`)
expect(bindings).toStrictEqual({
foo: BindingTypes.SETUP_CONST
foo: BindingTypes.LITERAL_CONST
})
})
@ -1951,7 +1952,7 @@ describe('SFC analyze <script> bindings', () => {
r: BindingTypes.SETUP_CONST,
a: BindingTypes.SETUP_REF,
b: BindingTypes.SETUP_LET,
c: BindingTypes.SETUP_CONST,
c: BindingTypes.LITERAL_CONST,
d: BindingTypes.SETUP_MAYBE_REF,
e: BindingTypes.SETUP_LET,
foo: BindingTypes.PROPS

View File

@ -0,0 +1,190 @@
import { BindingTypes } from '@vue/compiler-core'
import { SFCScriptCompileOptions } from '../src'
import { compileSFCScript, assertCode } from './utils'
describe('sfc hoist static', () => {
function compile(src: string, options?: Partial<SFCScriptCompileOptions>) {
return compileSFCScript(src, {
inlineTemplate: true,
hoistStatic: true,
...options
})
}
test('should hoist literal value', () => {
const code = `
const string = 'default value'
const number = 123
const boolean = false
const nil = null
const bigint = 100n
const template = \`str\`
const regex = /.*/g
`.trim()
const { content, bindings } = compile(`
<script setup>
${code}
</script>
`)
// should hoist to first line
expect(content.startsWith(code)).toBe(true)
expect(bindings).toStrictEqual({
string: BindingTypes.LITERAL_CONST,
number: BindingTypes.LITERAL_CONST,
boolean: BindingTypes.LITERAL_CONST,
nil: BindingTypes.LITERAL_CONST,
bigint: BindingTypes.LITERAL_CONST,
template: BindingTypes.LITERAL_CONST,
regex: BindingTypes.LITERAL_CONST
})
assertCode(content)
})
test('should hoist expressions', () => {
const code = `
const unary = !false
const binary = 1 + 2
const conditional = 1 ? 2 : 3
const sequence = (1, true, 'foo', 1)
`.trim()
const { content, bindings } = compile(`
<script setup>
${code}
</script>
`)
// should hoist to first line
expect(content.startsWith(code)).toBe(true)
expect(bindings).toStrictEqual({
binary: BindingTypes.LITERAL_CONST,
conditional: BindingTypes.LITERAL_CONST,
unary: BindingTypes.LITERAL_CONST,
sequence: BindingTypes.LITERAL_CONST
})
assertCode(content)
})
test('should hoist w/ defineProps/Emits', () => {
const hoistCode = `const defaultValue = 'default value'`
const { content, bindings } = compile(`
<script setup>
${hoistCode}
defineProps({
foo: {
default: defaultValue
}
})
</script>
`)
// should hoist to first line
expect(content.startsWith(hoistCode)).toBe(true)
expect(bindings).toStrictEqual({
foo: BindingTypes.PROPS,
defaultValue: BindingTypes.LITERAL_CONST
})
assertCode(content)
})
test('should not hoist a variable', () => {
const code = `
let KEY1 = 'default value'
var KEY2 = 123
`.trim()
const { content, bindings } = compile(`
<script setup>
${code}
</script>
`)
expect(bindings).toStrictEqual({
KEY1: BindingTypes.SETUP_LET,
KEY2: BindingTypes.SETUP_LET
})
expect(content).toMatch(`setup(__props) {\n\n ${code}`)
assertCode(content)
})
test('should not hoist a constant initialized to a reference value', () => {
const code = `
const KEY1 = Boolean
const KEY2 = [Boolean]
const KEY3 = [getCurrentInstance()]
let i = 0;
const KEY4 = (i++, 'foo')
enum KEY5 {
FOO = 1,
BAR = getCurrentInstance(),
}
const KEY6 = \`template\${i}\`
`.trim()
const { content, bindings } = compile(`
<script setup lang="ts">
${code}
</script>
`)
expect(bindings).toStrictEqual({
KEY1: BindingTypes.SETUP_MAYBE_REF,
KEY2: BindingTypes.SETUP_CONST,
KEY3: BindingTypes.SETUP_CONST,
KEY4: BindingTypes.SETUP_CONST,
KEY5: BindingTypes.SETUP_CONST,
KEY6: BindingTypes.SETUP_CONST,
i: BindingTypes.SETUP_LET
})
expect(content).toMatch(`setup(__props) {\n\n ${code}`)
assertCode(content)
})
test('should not hoist a object or array', () => {
const code = `
const obj = { foo: 'bar' }
const arr = [1, 2, 3]
`.trim()
const { content, bindings } = compile(`
<script setup>
${code}
</script>
`)
expect(bindings).toStrictEqual({
arr: BindingTypes.SETUP_CONST,
obj: BindingTypes.SETUP_CONST
})
expect(content).toMatch(`setup(__props) {\n\n ${code}`)
assertCode(content)
})
test('should not hoist a function or class', () => {
const code = `
const fn = () => {}
function fn2() {}
class Foo {}
`.trim()
const { content, bindings } = compile(`
<script setup>
${code}
</script>
`)
expect(bindings).toStrictEqual({
Foo: BindingTypes.SETUP_CONST,
fn: BindingTypes.SETUP_CONST,
fn2: BindingTypes.SETUP_CONST
})
expect(content).toMatch(`setup(__props) {\n\n ${code}`)
assertCode(content)
})
test('should enable when only script setup', () => {
const { content, bindings } = compile(`
<script>
const foo = 'bar'
</script>
<script setup>
const foo = 'bar'
</script>
`)
expect(bindings).toStrictEqual({
foo: BindingTypes.LITERAL_CONST
})
assertCode(content)
})
})

View File

@ -43,8 +43,8 @@ describe('sfc props transform', () => {
assertCode(content)
expect(bindings).toStrictEqual({
foo: BindingTypes.PROPS,
bar: BindingTypes.SETUP_CONST,
hello: BindingTypes.SETUP_CONST
bar: BindingTypes.LITERAL_CONST,
hello: BindingTypes.LITERAL_CONST
})
})
@ -259,7 +259,7 @@ describe('sfc props transform', () => {
expect(() =>
compile(
`<script setup>
const x = 1
let x = 1
const {
foo = () => x
} = defineProps(['foo'])

View File

@ -111,6 +111,13 @@ export interface SFCScriptCompileOptions {
* options passed to `compiler-dom`.
*/
templateOptions?: Partial<SFCTemplateCompileOptions>
/**
* Hoist <script setup> static constants.
* - Only enables when one `<script setup>` exists.
* @default true
*/
hoistStatic?: boolean
}
export interface ImportBinding {
@ -144,6 +151,7 @@ export function compileScript(
const enablePropsTransform = !!options.reactivityTransform
const isProd = !!options.isProd
const genSourceMap = options.sourceMap !== false
const hoistStatic = options.hoistStatic !== false && !script
let refBindings: string[] | undefined
if (!options.id) {
@ -743,7 +751,8 @@ export function compileScript(
function checkInvalidScopeReference(node: Node | undefined, method: string) {
if (!node) return
walkIdentifiers(node, id => {
if (setupBindings[id.name]) {
const binding = setupBindings[id.name]
if (binding && (binding !== BindingTypes.LITERAL_CONST || !hoistStatic)) {
error(
`\`${method}()\` in <script setup> cannot reference locally ` +
`declared variables because it will be hoisted outside of the ` +
@ -905,7 +914,7 @@ export function compileScript(
destructured.default.start!,
destructured.default.end!
)
const isLiteral = destructured.default.type.endsWith('Literal')
const isLiteral = isLiteralNode(destructured.default)
return isLiteral ? value : `() => (${value})`
}
}
@ -1276,14 +1285,21 @@ export function compileScript(
}
}
let isAllLiteral = false
// walk declarations to record declared bindings
if (
(node.type === 'VariableDeclaration' ||
node.type === 'FunctionDeclaration' ||
node.type === 'ClassDeclaration') &&
node.type === 'ClassDeclaration' ||
node.type === 'TSEnumDeclaration') &&
!node.declare
) {
walkDeclaration(node, setupBindings, vueImportAliases)
isAllLiteral = walkDeclaration(node, setupBindings, vueImportAliases)
}
// hoist literal constants
if (hoistStatic && isAllLiteral) {
hoistNode(node)
}
// walk statements & named exports / variable declarations for top level
@ -1342,17 +1358,13 @@ export function compileScript(
}
if (isTS) {
// runtime enum
if (node.type === 'TSEnumDeclaration') {
registerBinding(setupBindings, node.id, BindingTypes.SETUP_CONST)
}
// move all Type declarations to outer scope
if (
node.type.startsWith('TS') ||
(node.type === 'ExportNamedDeclaration' &&
node.exportKind === 'type') ||
(node.type === 'VariableDeclaration' && node.declare)
(node.type.startsWith('TS') ||
(node.type === 'ExportNamedDeclaration' &&
node.exportKind === 'type') ||
(node.type === 'VariableDeclaration' && node.declare)) &&
node.type !== 'TSEnumDeclaration'
) {
recordType(node, declaredTypes)
hoistNode(node)
@ -1474,7 +1486,7 @@ export function compileScript(
) {
helperImports.add(CSS_VARS_HELPER)
helperImports.add('unref')
s.prependRight(
s.prependLeft(
startOffset,
`\n${genCssVarsCode(cssVars, bindingMetadata, scopeId, isProd)}\n`
)
@ -1774,9 +1786,17 @@ function walkDeclaration(
node: Declaration,
bindings: Record<string, BindingTypes>,
userImportAliases: Record<string, string>
) {
): boolean {
let isAllLiteral = false
if (node.type === 'VariableDeclaration') {
const isConst = node.kind === 'const'
isAllLiteral =
isConst &&
node.declarations.every(
decl => decl.id.type === 'Identifier' && isStaticNode(decl.init!)
)
// export const foo = ...
for (const { id, init } of node.declarations) {
const isDefineCall = !!(
@ -1789,7 +1809,9 @@ function walkDeclaration(
if (id.type === 'Identifier') {
let bindingType
const userReactiveBinding = userImportAliases['reactive']
if (isCallOf(init, userReactiveBinding)) {
if (isAllLiteral || (isConst && isStaticNode(init!))) {
bindingType = BindingTypes.LITERAL_CONST
} else if (isCallOf(init, userReactiveBinding)) {
// treat reactive() calls as let since it's meant to be mutable
bindingType = isConst
? BindingTypes.SETUP_REACTIVE_CONST
@ -1824,8 +1846,14 @@ function walkDeclaration(
}
}
}
} else if (node.type === 'TSEnumDeclaration') {
isAllLiteral = node.members.every(
member => !member.initializer || isStaticNode(member.initializer)
)
bindings[node.id!.name] = isAllLiteral
? BindingTypes.LITERAL_CONST
: BindingTypes.SETUP_CONST
} else if (
node.type === 'TSEnumDeclaration' ||
node.type === 'FunctionDeclaration' ||
node.type === 'ClassDeclaration'
) {
@ -1833,6 +1861,8 @@ function walkDeclaration(
// export declarations must be named.
bindings[node.id!.name] = BindingTypes.SETUP_CONST
}
return isAllLiteral
}
function walkObjectPattern(
@ -2138,13 +2168,53 @@ function canNeverBeRef(node: Node, userReactiveImport?: string): boolean {
userReactiveImport
)
default:
if (node.type.endsWith('Literal')) {
if (isLiteralNode(node)) {
return true
}
return false
}
}
function isStaticNode(node: Node): boolean {
switch (node.type) {
case 'UnaryExpression': // void 0, !true
return isStaticNode(node.argument)
case 'LogicalExpression': // 1 > 2
case 'BinaryExpression': // 1 + 2
return isStaticNode(node.left) && isStaticNode(node.right)
case 'ConditionalExpression': {
// 1 ? 2 : 3
return (
isStaticNode(node.test) &&
isStaticNode(node.consequent) &&
isStaticNode(node.alternate)
)
}
case 'SequenceExpression': // (1, 2)
case 'TemplateLiteral': // `foo${1}`
return node.expressions.every(expr => isStaticNode(expr))
case 'ParenthesizedExpression': // (1)
case 'TSNonNullExpression': // 1!
case 'TSAsExpression': // 1 as number
case 'TSTypeAssertion': // (<number>2)
return isStaticNode(node.expression)
default:
if (isLiteralNode(node)) {
return true
}
return false
}
}
function isLiteralNode(node: Node) {
return node.type.endsWith('Literal')
}
/**
* Analyze bindings in normal `<script>`
* Note that `compileScriptSetup` already analyzes bindings as part of its