Skip to content
This repository was archived by the owner on Dec 25, 2024. It is now read-only.

Commit cfdf869

Browse files
committed
feat: component registeration
1 parent ebae65c commit cfdf869

File tree

4 files changed

+120
-60
lines changed

4 files changed

+120
-60
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Bring `<script setup>` to Vue 2
77
## Status
88

99
- [x] POC
10-
- [ ] Components registration
10+
- [x] Components registration
1111
- [ ] Compile time macros
1212
- [x] Merge with normal scripts
1313

playground/App.vue

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,9 @@
11
<template>
22
<div>
3-
<button @click="inc">
4-
Inc
5-
</button>
6-
<div>{{ count }} x 2 = {{ doubled }}</div>
7-
<button @click="dec()" v-html="decText" />
3+
<hello-world />
84
</div>
95
</template>
106

117
<script setup lang="ts">
12-
import { ref, computed } from '@vue/composition-api'
13-
14-
const count = ref(0)
15-
const doubled = computed(() => count.value * 2)
16-
17-
function inc() {
18-
count.value += 1
19-
}
20-
21-
function dec() {
22-
count.value += 1
23-
}
24-
25-
const decText = '<b>Dec</b>'
26-
</script>
27-
28-
<script lang="ts">
29-
export default {
30-
name: 'App'
31-
}
8+
import HelloWorld from './HelloWorld.vue'
329
</script>

playground/HelloWorld.vue

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<template>
2+
<div>
3+
<button @click="inc">
4+
Inc
5+
</button>
6+
<div>{{ count }} x 2 = {{ doubled }}</div>
7+
<button @click="dec()" v-html="decText" />
8+
</div>
9+
</template>
10+
11+
<script setup lang="ts">
12+
import { ref, computed } from '@vue/composition-api'
13+
14+
const count = ref(0)
15+
const doubled = computed(() => count.value * 2)
16+
17+
function inc() {
18+
count.value += 1
19+
}
20+
21+
function dec() {
22+
count.value += 1
23+
}
24+
25+
const decText = '<b>Dec</b>'
26+
</script>
27+
28+
<script lang="ts">
29+
export default {
30+
name: 'App'
31+
}
32+
</script>

src/parse.ts

Lines changed: 85 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Parser as HTMLParser } from 'htmlparser2'
2-
import { types } from '@babel/core'
2+
import { types as t } from '@babel/core'
33
import { parse, ParserOptions, ParserPlugin } from '@babel/parser'
4-
import { isHTMLTag, isSVGTag, isVoidTag } from '@vue/shared'
4+
import { camelize, capitalize, isHTMLTag, isSVGTag, isVoidTag } from '@vue/shared'
55
import traverse from '@babel/traverse'
66
import generate from '@babel/generator'
77

@@ -11,7 +11,8 @@ interface TagMeta {
1111
contentStart: number
1212
contentEnd: number
1313
content: string
14-
attributes: Record<string, string>
14+
attrs: Record<string, string>
15+
found: boolean
1516
}
1617

1718
export interface ParseResult {
@@ -38,15 +39,17 @@ export function parseVueSFC(code: string): ParseResult {
3839
contentStart: 0,
3940
contentEnd: 0,
4041
content: '',
41-
attributes: {},
42+
attrs: {},
43+
found: false,
4244
}
4345
const script: TagMeta = {
4446
start: 0,
4547
end: 0,
4648
contentStart: 0,
4749
contentEnd: 0,
4850
content: '',
49-
attributes: {},
51+
attrs: {},
52+
found: false,
5053
}
5154

5255
const parser = new HTMLParser({
@@ -56,7 +59,7 @@ export function parseVueSFC(code: string): ParseResult {
5659

5760
if (templateLevel > 0) {
5861
if (!isHTMLTag(name) && !isSVGTag(name) && !isVoidTag(name))
59-
components.add(name)
62+
components.add(capitalize(camelize(name)))
6063
Object.entries(attributes).forEach(([key, value]) => {
6164
if (!value)
6265
return
@@ -71,13 +74,15 @@ export function parseVueSFC(code: string): ParseResult {
7174
if ('setup' in attributes) {
7275
scriptSetup.start = parser.startIndex
7376
scriptSetup.contentStart = parser.endIndex! + 1
74-
scriptSetup.attributes = attributes
77+
scriptSetup.attrs = attributes
78+
scriptSetup.found = true
7579
inScriptSetup = true
7680
}
7781
else {
7882
script.start = parser.startIndex
7983
script.contentStart = parser.endIndex! + 1
80-
script.attributes = attributes
84+
script.attrs = attributes
85+
script.found = true
8186
inScript = true
8287
}
8388
}
@@ -140,16 +145,19 @@ export function getIdentifiersFromCode(code: string, identifiers = new Set<strin
140145
}
141146

142147
export function transformScriptSetup(result: ParseResult) {
143-
if (result.scriptSetup.attributes.lang !== result.script.attributes.lang)
148+
if (result.script.found && result.scriptSetup.found && result.scriptSetup.attrs.lang !== result.script.attrs.lang)
144149
throw new SyntaxError('<script setup> language must be the same as <script>')
145150

151+
const lang = result.scriptSetup.attrs.lang || result.script.attrs.lang
146152
const plugins: ParserPlugin[] = []
147-
if (result.scriptSetup.attributes.lang === 'ts')
153+
if (lang === 'ts')
148154
plugins.push('typescript')
149-
if (result.scriptSetup.attributes.lang === 'jsx')
155+
else if (lang === 'jsx')
150156
plugins.push('jsx')
151-
if (result.scriptSetup.attributes.lang === 'tsx')
157+
else if (lang === 'tsx')
152158
plugins.push('typescript', 'jsx')
159+
else if (lang !== 'js')
160+
throw new SyntaxError(`Unsupported script language: ${lang}`)
153161

154162
const identifiers = new Set<string>()
155163
const scriptSetupAst = parse(result.scriptSetup.content, {
@@ -168,33 +176,28 @@ export function transformScriptSetup(result: ParseResult) {
168176
})
169177

170178
const returns = Array.from(identifiers).filter(i => result.template.identifiers.has(i))
179+
const components = Array.from(identifiers).filter(i =>
180+
result.template.components.has(i)
181+
|| result.template.components.has(camelize(i))
182+
|| result.template.components.has(capitalize(camelize(i))),
183+
)
171184

172185
const imports = scriptSetupAst.program.body.filter(n => n.type === 'ImportDeclaration')
173-
const body = scriptSetupAst.program.body.filter(n => n.type !== 'ImportDeclaration')
186+
const scriptSetupBody = scriptSetupAst.program.body.filter(n => n.type !== 'ImportDeclaration')
174187
// TODO: apply macros
175-
const returnStatement = types.returnStatement(
176-
types.objectExpression(
177-
returns.map((i) => {
178-
const id = types.identifier(i)
179-
return types.objectProperty(id, id, false, true)
180-
}),
181-
),
182-
)
183-
const setup = types.arrowFunctionExpression([], types.blockStatement([
184-
...body,
185-
returnStatement as any,
186-
]))
187188

189+
// append `<script setup>` imports to `<script>`
188190
scriptAst.program.body.unshift(...imports)
189191

190192
// replace `export default` with a temproray variable
193+
// `const __sfc_main = { ... }`
191194
traverse(scriptAst as any, {
192195
ExportDefaultDeclaration(path) {
193196
const decl = path.node.declaration
194197
path.replaceWith(
195-
types.variableDeclaration('const', [
196-
types.variableDeclarator(
197-
types.identifier('__sfc_main'),
198+
t.variableDeclaration('const', [
199+
t.variableDeclarator(
200+
t.identifier('__sfc_main'),
198201
decl as any,
199202
),
200203
]),
@@ -203,15 +206,63 @@ export function transformScriptSetup(result: ParseResult) {
203206
})
204207

205208
// inject setup function
206-
scriptAst.program.body.push(
207-
types.expressionStatement(
208-
types.assignmentExpression('=', types.memberExpression(types.identifier('__sfc_main'), types.identifier('setup')), setup),
209-
) as any,
210-
)
209+
// `__sfc_main.setup = () => {}`
210+
if (scriptSetupBody.length) {
211+
const returnStatement = t.returnStatement(
212+
t.objectExpression(
213+
returns.map((i) => {
214+
const id = t.identifier(i)
215+
return t.objectProperty(id, id, false, true)
216+
}),
217+
),
218+
)
219+
220+
scriptAst.program.body.push(
221+
t.expressionStatement(
222+
t.assignmentExpression('=',
223+
t.memberExpression(t.identifier('__sfc_main'), t.identifier('setup')),
224+
t.arrowFunctionExpression([], t.blockStatement([
225+
...scriptSetupBody,
226+
returnStatement as any,
227+
])),
228+
),
229+
) as any,
230+
)
231+
}
232+
233+
// inject components
234+
// `__sfc_main.components = Object.assign({ ... }, __sfc_main.components)`
235+
if (components.length) {
236+
const componentsObject = t.objectExpression(
237+
components.map((i) => {
238+
const id = t.identifier(i)
239+
return t.objectProperty(id, id, false, true)
240+
}),
241+
)
242+
243+
scriptAst.program.body.push(
244+
t.expressionStatement(
245+
t.assignmentExpression('=',
246+
t.memberExpression(t.identifier('__sfc_main'), t.identifier('components')),
247+
t.callExpression(
248+
t.memberExpression(t.identifier('Object'), t.identifier('assign')),
249+
[
250+
componentsObject,
251+
t.memberExpression(
252+
t.identifier('__sfc_main'),
253+
t.identifier('components'),
254+
),
255+
],
256+
),
257+
),
258+
) as any,
259+
)
260+
}
211261

212262
// re-export
263+
// `export default __sfc_main`
213264
scriptAst.program.body.push(
214-
types.exportDefaultDeclaration(types.identifier('__sfc_main')) as any,
265+
t.exportDefaultDeclaration(t.identifier('__sfc_main')) as any,
215266
)
216267

217268
return {

0 commit comments

Comments
 (0)