Skip to content

Commit

Permalink
feat(complier-sfc): hoist literal constants for script (vuejs#5752)
Browse files Browse the repository at this point in the history
- Support using literal constants in macros
- fix useCssVars insert position edge case
- fix non-literal-const enum hoisting

close vuejs#5750
  • Loading branch information
sxzz authored and IAmSSH committed Apr 29, 2023
1 parent c85c88c commit 3f2e95a
Show file tree
Hide file tree
Showing 10 changed files with 466 additions and 61 deletions.
6 changes: 5 additions & 1 deletion packages/compiler-core/src/options.ts
Expand Up @@ -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 = {
Expand Down
3 changes: 2 additions & 1 deletion packages/compiler-core/src/transforms/transformElement.ts
Expand Up @@ -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
Expand Down
16 changes: 10 additions & 6 deletions packages/compiler-core/src/transforms/transformExpression.ts
Expand Up @@ -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`
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
)
}
@@ -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 }
}

Expand Down Expand Up @@ -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
},
Expand All @@ -693,7 +696,6 @@ exports[`SFC compile <script setup> > defineProps() 1`] = `
const props = __props;


const bar = 1

return { props, bar }
}
Expand Down Expand Up @@ -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 } }
Expand All @@ -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 } }
}
Expand All @@ -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 } }
}
Expand Down Expand Up @@ -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
Expand All @@ -898,7 +902,6 @@ exports[`SFC compile <script setup> > errors > should allow defineProps/Emit() r
setup(__props, { expose }) {
expose();

const bar = 1



Expand Down Expand Up @@ -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\\" }
Expand All @@ -1732,6 +1734,7 @@ export default /*#__PURE__*/_defineComponent({
setup(__props, { expose }) {
expose();

enum Foo { A = 123 }

return { D, C, B, Foo }
}
Expand Down
@@ -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 () => {}
}
}"
`;
Expand Up @@ -51,15 +51,15 @@ 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();
_useCssVars(_ctx => ({
\\"xxxxxxxx-width\\": (width)
}))
const color = 'red';const width = 100
return { color, width }
}
Expand Down Expand Up @@ -92,15 +92,15 @@ 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();
_useCssVars(_ctx => ({
\\"xxxxxxxx-color\\": (color)
}))
const color = 'red'
return { color }
}
Expand All @@ -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();
Expand All @@ -118,7 +119,6 @@ _useCssVars(_ctx => ({
\\"xxxxxxxx-color\\": (color)
}))
const color = 'red'
return { color }
}
Expand Down Expand Up @@ -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: {
Expand All @@ -160,7 +161,6 @@ _useCssVars(_ctx => ({
\\"xxxxxxxx-foo\\": (__props.foo)
}))
const color = 'red'
const size = ref('10px')
Expand Down

0 comments on commit 3f2e95a

Please sign in to comment.