diff --git a/packages/compiler-core/src/options.ts b/packages/compiler-core/src/options.ts
index 1221772f706..65bbcb36dd6 100644
--- a/packages/compiler-core/src/options.ts
+++ b/packages/compiler-core/src/options.ts
@@ -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 = {
diff --git a/packages/compiler-core/src/transforms/transformElement.ts b/packages/compiler-core/src/transforms/transformElement.ts
index 4bbe891e209..52612edd612 100644
--- a/packages/compiler-core/src/transforms/transformElement.ts
+++ b/packages/compiler-core/src/transforms/transformElement.ts
@@ -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
diff --git a/packages/compiler-core/src/transforms/transformExpression.ts b/packages/compiler-core/src/transforms/transformExpression.ts
index 43c69559688..112dc63cb7a 100644
--- a/packages/compiler-core/src/transforms/transformExpression.ts
+++ b/packages/compiler-core/src/transforms/transformExpression.ts
@@ -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
+ )
+}
diff --git a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap
index 2813a0be788..4615b20fe73 100644
--- a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap
+++ b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap
@@ -1,11 +1,12 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`SFC analyze `
+ `,
+ { 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(``)
).toThrow(`cannot reference locally declared variables`)
@@ -1785,7 +1786,7 @@ describe('SFC analyze
`)
expect(bindings).toStrictEqual({
- foo: BindingTypes.SETUP_CONST
+ foo: BindingTypes.LITERAL_CONST
})
})
@@ -1951,7 +1952,7 @@ describe('SFC analyze
+ `)
+
+ // 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(`
+
+ `)
+ // 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(`
+
+ `)
+
+ // 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(`
+
+ `)
+ 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(`
+
+ `)
+ 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(`
+
+ `)
+ 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(`
+
+ `)
+ 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(`
+
+
+ `)
+ expect(bindings).toStrictEqual({
+ foo: BindingTypes.LITERAL_CONST
+ })
+ assertCode(content)
+ })
+})
diff --git a/packages/compiler-sfc/__tests__/compileScriptPropsTransform.spec.ts b/packages/compiler-sfc/__tests__/compileScriptPropsTransform.spec.ts
index 05c7989b8f1..5401a9e0305 100644
--- a/packages/compiler-sfc/__tests__/compileScriptPropsTransform.spec.ts
+++ b/packages/compiler-sfc/__tests__/compileScriptPropsTransform.spec.ts
@@ -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(
`