Skip to content

Commit 06051c4

Browse files
committed
feat(experimental): support ref transform for sfc normal <script>
1 parent f173cf0 commit 06051c4

File tree

8 files changed

+226
-74
lines changed

8 files changed

+226
-74
lines changed

packages/compiler-core/src/babelUtils.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import {
44
Node,
55
Function,
66
ObjectProperty,
7-
BlockStatement
7+
BlockStatement,
8+
Program
89
} from '@babel/types'
910
import { walk } from 'estree-walker'
1011

@@ -149,16 +150,23 @@ export function walkFunctionParams(
149150
}
150151

151152
export function walkBlockDeclarations(
152-
block: BlockStatement,
153+
block: BlockStatement | Program,
153154
onIdent: (node: Identifier) => void
154155
) {
155156
for (const stmt of block.body) {
156157
if (stmt.type === 'VariableDeclaration') {
158+
if (stmt.declare) continue
157159
for (const decl of stmt.declarations) {
158160
for (const id of extractIdentifiers(decl.id)) {
159161
onIdent(id)
160162
}
161163
}
164+
} else if (
165+
stmt.type === 'FunctionDeclaration' ||
166+
stmt.type === 'ClassDeclaration'
167+
) {
168+
if (stmt.declare || !stmt.id) continue
169+
onIdent(stmt.id)
162170
}
163171
}
164172
}

packages/compiler-sfc/__tests__/__snapshots__/compileScriptRefSugar.spec.ts.snap

-43
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`sfc ref transform $ unwrapping 1`] = `
4+
"import { ref, shallowRef } from 'vue'
5+
6+
export default {
7+
setup(__props, { expose }) {
8+
expose()
9+
10+
let foo = (ref())
11+
let a = (ref(1))
12+
let b = (shallowRef({
13+
count: 0
14+
}))
15+
let c = () => {}
16+
let d
17+
18+
return { foo, a, b, c, d, ref, shallowRef }
19+
}
20+
21+
}"
22+
`;
23+
24+
exports[`sfc ref transform $ref & $shallowRef declarations 1`] = `
25+
"import { ref as _ref, shallowRef as _shallowRef } from 'vue'
26+
27+
export default {
28+
setup(__props, { expose }) {
29+
expose()
30+
31+
let foo = _ref()
32+
let a = _ref(1)
33+
let b = _shallowRef({
34+
count: 0
35+
})
36+
let c = () => {}
37+
let d
38+
39+
return { foo, a, b, c, d }
40+
}
41+
42+
}"
43+
`;
44+
45+
exports[`sfc ref transform usage in normal <script> 1`] = `
46+
"import { ref as _ref } from 'vue'
47+
48+
export default {
49+
setup() {
50+
let count = _ref(0)
51+
const inc = () => count.value++
52+
return ({ count })
53+
}
54+
}
55+
"
56+
`;
57+
58+
exports[`sfc ref transform usage with normal <script> + <script setup> 1`] = `
59+
"import { ref as _ref } from 'vue'
60+
61+
let a = _ref(0)
62+
let c = _ref(0)
63+
64+
export default {
65+
setup(__props, { expose }) {
66+
expose()
67+
68+
let b = _ref(0)
69+
let c = 0
70+
function change() {
71+
a.value++
72+
b.value++
73+
c++
74+
}
75+
76+
return { a, c, b, change }
77+
}
78+
79+
}"
80+
`;

packages/compiler-sfc/__tests__/compileScriptRefSugar.spec.ts renamed to packages/compiler-sfc/__tests__/compileScriptRefTransform.spec.ts

+56-4
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ import { compileSFCScript as compile, assertCode } from './utils'
33

44
// this file only tests integration with SFC - main test case for the ref
55
// transform can be found in <root>/packages/ref-transform/__tests__
6-
describe('<script setup> ref sugar', () => {
7-
function compileWithRefSugar(src: string) {
6+
describe('sfc ref transform', () => {
7+
function compileWithRefTransform(src: string) {
88
return compile(src, { refSugar: true })
99
}
1010

1111
test('$ unwrapping', () => {
12-
const { content, bindings } = compileWithRefSugar(`<script setup>
12+
const { content, bindings } = compileWithRefTransform(`<script setup>
1313
import { ref, shallowRef } from 'vue'
1414
let foo = $(ref())
1515
let a = $(ref(1))
@@ -46,7 +46,7 @@ describe('<script setup> ref sugar', () => {
4646
})
4747

4848
test('$ref & $shallowRef declarations', () => {
49-
const { content, bindings } = compileWithRefSugar(`<script setup>
49+
const { content, bindings } = compileWithRefTransform(`<script setup>
5050
let foo = $ref()
5151
let a = $ref(1)
5252
let b = $shallowRef({
@@ -81,6 +81,58 @@ describe('<script setup> ref sugar', () => {
8181
})
8282
})
8383

84+
test('usage in normal <script>', () => {
85+
const { content } = compileWithRefTransform(`<script>
86+
export default {
87+
setup() {
88+
let count = $ref(0)
89+
const inc = () => count++
90+
return $$({ count })
91+
}
92+
}
93+
</script>`)
94+
expect(content).not.toMatch(`$ref(0)`)
95+
expect(content).toMatch(`import { ref as _ref } from 'vue'`)
96+
expect(content).toMatch(`let count = _ref(0)`)
97+
expect(content).toMatch(`count.value++`)
98+
expect(content).toMatch(`return ({ count })`)
99+
assertCode(content)
100+
})
101+
102+
test('usage with normal <script> + <script setup>', () => {
103+
const { content, bindings } = compileWithRefTransform(`<script>
104+
let a = $ref(0)
105+
let c = $ref(0)
106+
</script>
107+
<script setup>
108+
let b = $ref(0)
109+
let c = 0
110+
function change() {
111+
a++
112+
b++
113+
c++
114+
}
115+
</script>`)
116+
// should dedupe helper imports
117+
expect(content).toMatch(`import { ref as _ref } from 'vue'`)
118+
119+
expect(content).toMatch(`let a = _ref(0)`)
120+
expect(content).toMatch(`let b = _ref(0)`)
121+
122+
// root level ref binding declared in <script> should be inherited in <script setup>
123+
expect(content).toMatch(`a.value++`)
124+
expect(content).toMatch(`b.value++`)
125+
// c shadowed
126+
expect(content).toMatch(`c++`)
127+
assertCode(content)
128+
expect(bindings).toStrictEqual({
129+
a: BindingTypes.SETUP_REF,
130+
b: BindingTypes.SETUP_REF,
131+
c: BindingTypes.SETUP_REF,
132+
change: BindingTypes.SETUP_CONST
133+
})
134+
})
135+
84136
describe('errors', () => {
85137
test('defineProps/Emit() referencing ref declarations', () => {
86138
expect(() =>

packages/compiler-sfc/src/compileScript.ts

+49-8
Original file line numberDiff line numberDiff line change
@@ -165,12 +165,34 @@ export function compileScript(
165165
return script
166166
}
167167
try {
168-
const scriptAst = _parse(script.content, {
168+
let content = script.content
169+
let map = script.map
170+
const scriptAst = _parse(content, {
169171
plugins,
170172
sourceType: 'module'
171-
}).program.body
172-
const bindings = analyzeScriptBindings(scriptAst)
173-
let content = script.content
173+
}).program
174+
const bindings = analyzeScriptBindings(scriptAst.body)
175+
if (enableRefTransform && shouldTransformRef(content)) {
176+
const s = new MagicString(source)
177+
const startOffset = script.loc.start.offset
178+
const endOffset = script.loc.end.offset
179+
const { importedHelpers } = transformRefAST(scriptAst, s, startOffset)
180+
if (importedHelpers.length) {
181+
s.prepend(
182+
`import { ${importedHelpers
183+
.map(h => `${h} as _${h}`)
184+
.join(', ')} } from 'vue'\n`
185+
)
186+
}
187+
s.remove(0, startOffset)
188+
s.remove(endOffset, source.length)
189+
content = s.toString()
190+
map = s.generateMap({
191+
source: filename,
192+
hires: true,
193+
includeContent: true
194+
}) as unknown as RawSourceMap
195+
}
174196
if (cssVars.length) {
175197
content = rewriteDefault(content, `__default__`, plugins)
176198
content += genNormalScriptCssVarsCode(
@@ -184,8 +206,9 @@ export function compileScript(
184206
return {
185207
...script,
186208
content,
209+
map,
187210
bindings,
188-
scriptAst
211+
scriptAst: scriptAst.body
189212
}
190213
} catch (e) {
191214
// silently fallback if parse fails since user may be using custom
@@ -629,6 +652,23 @@ export function compileScript(
629652
walkDeclaration(node, setupBindings, userImportAlias)
630653
}
631654
}
655+
656+
// apply ref transform
657+
if (enableRefTransform && shouldTransformRef(script.content)) {
658+
warnExperimental(
659+
`ref sugar`,
660+
`https://github.com/vuejs/rfcs/discussions/369`
661+
)
662+
const { rootVars, importedHelpers } = transformRefAST(
663+
scriptAst,
664+
s,
665+
scriptStartOffset!
666+
)
667+
refBindings = rootVars
668+
for (const h of importedHelpers) {
669+
helperImports.add(h)
670+
}
671+
}
632672
}
633673

634674
// 2. parse <script setup> and walk over top level statements
@@ -862,17 +902,18 @@ export function compileScript(
862902
}
863903

864904
// 3. Apply ref sugar transform
865-
if (enableRefTransform && shouldTransformRef(source)) {
905+
if (enableRefTransform && shouldTransformRef(scriptSetup.content)) {
866906
warnExperimental(
867907
`ref sugar`,
868908
`https://github.com/vuejs/rfcs/discussions/369`
869909
)
870910
const { rootVars, importedHelpers } = transformRefAST(
871911
scriptSetupAst,
872912
s,
873-
startOffset
913+
startOffset,
914+
refBindings
874915
)
875-
refBindings = rootVars
916+
refBindings = refBindings ? [...refBindings, ...rootVars] : rootVars
876917
for (const h of importedHelpers) {
877918
helperImports.add(h)
878919
}

packages/ref-transform/__tests__/__snapshots__/refTransform.spec.ts.snap

+5-1
Original file line numberDiff line numberDiff line change
@@ -136,17 +136,21 @@ exports[`nested scopes 1`] = `
136136
b.value++ // outer b
137137
c++ // outer c
138138
139+
let bar = _ref(0)
140+
bar.value++ // outer bar
141+
139142
function foo({ a }) {
140143
a++ // inner a
141144
b.value++ // inner b
142145
let c = _ref(0)
143146
c.value++ // inner c
144147
let d = _ref(0)
145148
146-
const bar = (c) => {
149+
function bar(c) {
147150
c++ // nested c
148151
d.value++ // nested d
149152
}
153+
bar() // inner bar
150154
151155
if (true) {
152156
let a = _ref(0)

0 commit comments

Comments
 (0)