Skip to content

Commit db55201

Browse files
zzhenryquezzsxzz
andauthored
feat: define single prop and emit macro (#295)
Co-authored-by: 三咲智子 Kevin Deng <sxzz@sxzz.moe>
1 parent 1b9c866 commit db55201

37 files changed

+1005
-142
lines changed

packages/api/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@
4646
"@babel/types": "^7.21.3",
4747
"@vue-macros/common": "workspace:~"
4848
},
49+
"devDependencies": {
50+
"rollup": "^3.20.0"
51+
},
4952
"engines": {
5053
"node": ">=14.19.0"
5154
}

packages/api/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export { MagicString } from '@vue-macros/common'
22

33
export * from './vue'
4+
export * from './resolve'
45
export * from './ts'
56
export * from './utils'

packages/api/src/resolve.ts

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { existsSync } from 'node:fs'
2+
import { readFile } from 'node:fs/promises'
3+
import path from 'node:path'
4+
import { type ResolveTSFileIdImpl, tsFileCache } from './ts'
5+
import type { PluginContext } from 'rollup'
6+
import type { ModuleNode, Plugin } from 'vite'
7+
8+
export const RollupResolve = () => {
9+
const referencedFiles = new Map<
10+
string /* file */,
11+
Set<string /* importer */>
12+
>()
13+
14+
function collectReferencedFile(importer: string, file: string) {
15+
if (!importer) return
16+
if (!referencedFiles.has(file)) {
17+
referencedFiles.set(file, new Set([importer]))
18+
} else {
19+
referencedFiles.get(file)!.add(importer)
20+
}
21+
}
22+
23+
const resolveCache = new Map<
24+
string /* importer */,
25+
Map<string /* id */, string /* result */>
26+
>()
27+
28+
function withResolveCache(id: string, importer: string, result: string) {
29+
if (!resolveCache.has(importer)) {
30+
resolveCache.set(importer, new Map([[id, result]]))
31+
return result
32+
}
33+
resolveCache.get(importer)!.set(id, result)
34+
return result
35+
}
36+
37+
const resolve =
38+
(ctx: PluginContext): ResolveTSFileIdImpl =>
39+
async (id, importer) => {
40+
async function tryPkgEntry() {
41+
try {
42+
const pkgPath = (await ctx.resolve(`${id}/package.json`, importer))
43+
?.id
44+
if (!pkgPath) return
45+
46+
const pkg = JSON.parse(await readFile(pkgPath, 'utf-8'))
47+
const types = pkg.types || pkg.typings
48+
if (!types) return
49+
50+
const entry = path.resolve(pkgPath, '..', types)
51+
return existsSync(entry) ? entry : undefined
52+
} catch {}
53+
}
54+
55+
const tryResolve = async (id: string) => {
56+
try {
57+
return (
58+
(await ctx.resolve(id, importer))?.id ||
59+
(await ctx.resolve(`${id}.d`, importer))?.id
60+
)
61+
} catch {}
62+
63+
return
64+
}
65+
66+
const cached = resolveCache.get(importer)?.get(id)
67+
if (cached) return cached
68+
69+
if (!id.startsWith('.')) {
70+
const entry = await tryPkgEntry()
71+
if (entry) return withResolveCache(id, importer, entry)
72+
}
73+
74+
let resolved = await tryResolve(id)
75+
if (!resolved) return
76+
if (existsSync(resolved)) {
77+
collectReferencedFile(importer, resolved)
78+
return withResolveCache(id, importer, resolved)
79+
}
80+
81+
resolved = await tryResolve(resolved)
82+
if (resolved && existsSync(resolved)) {
83+
collectReferencedFile(importer, resolved)
84+
return withResolveCache(id, importer, resolved)
85+
}
86+
}
87+
88+
const handleHotUpdate: NonNullable<Plugin['handleHotUpdate']> = ({
89+
file,
90+
server,
91+
modules,
92+
}) => {
93+
const cache = new Map<string, Set<ModuleNode>>()
94+
function getAffectedModules(file: string): Set<ModuleNode> {
95+
if (cache.has(file)) return cache.get(file)!
96+
97+
if (!referencedFiles.has(file)) return new Set([])
98+
const modules = new Set<ModuleNode>([])
99+
cache.set(file, modules)
100+
for (const importer of referencedFiles.get(file)!) {
101+
const mods = server.moduleGraph.getModulesByFile(importer)
102+
if (mods) mods.forEach((m) => modules.add(m))
103+
104+
getAffectedModules(importer).forEach((m) => modules.add(m))
105+
}
106+
return modules
107+
}
108+
109+
if (tsFileCache[file]) delete tsFileCache[file]
110+
111+
const affected = getAffectedModules(file)
112+
return [...modules, ...affected]
113+
}
114+
115+
return {
116+
resolve,
117+
handleHotUpdate,
118+
}
119+
}

packages/api/src/vue/utils.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { isTSExports, resolveTSReferencedType } from '../ts'
2+
import type { TSExports, TSResolvedType } from '../ts'
23
import type { Node } from '@babel/types'
3-
import type { TSResolvedType } from '../ts'
44

55
export async function inferRuntimeType(
6-
node: TSResolvedType
6+
node: TSResolvedType | TSExports
77
): Promise<string[]> {
8+
if (isTSExports(node)) return ['Object']
9+
810
switch (node.type.type) {
911
case 'TSStringKeyword':
1012
return ['String']
@@ -80,8 +82,7 @@ export async function inferRuntimeType(
8082
scope: node.scope,
8183
type: node.type.typeParameters.params[1],
8284
})
83-
if (isTSExports(t)) return ['Object']
84-
else if (t) return inferRuntimeType(t)
85+
if (t) return inferRuntimeType(t)
8586
}
8687
return ['null']
8788
case 'Exclude':
@@ -93,8 +94,7 @@ export async function inferRuntimeType(
9394
scope: node.scope,
9495
type: node.type.typeParameters.params[0],
9596
})
96-
if (isTSExports(t)) return ['Object']
97-
else if (t) return inferRuntimeType(t)
97+
if (t) return inferRuntimeType(t)
9898
}
9999
return ['null']
100100
}
@@ -135,3 +135,7 @@ export function attachNodeLoc(node: Node, newNode: Node) {
135135
newNode.start = node.start
136136
newNode.end = node.end
137137
}
138+
139+
export function toRuntimeTypeString(types: string[]) {
140+
return types.length > 1 ? `[${types.join(', ')}]` : types[0]
141+
}

packages/better-define/src/core/index.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
importHelperFn,
66
parseSFC,
77
} from '@vue-macros/common'
8-
import { analyzeSFC } from '@vue-macros/api'
8+
import { analyzeSFC, toRuntimeTypeString } from '@vue-macros/api'
99
import type { TSEmits, TSProps } from '@vue-macros/api'
1010

1111
export async function transformBetterDefine(
@@ -81,7 +81,3 @@ export async function transformBetterDefine(
8181
})
8282
}
8383
}
84-
85-
function toRuntimeTypeString(types: string[]) {
86-
return types.length > 1 ? `[${types.join(', ')}]` : types[0]
87-
}

packages/better-define/src/index.ts

Lines changed: 4 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
1-
import { existsSync } from 'node:fs'
2-
import { readFile } from 'node:fs/promises'
3-
import path from 'node:path'
41
import { createUnplugin } from 'unplugin'
52
import { createFilter } from '@rollup/pluginutils'
63
import { REGEX_SETUP_SFC, REGEX_VUE_SFC } from '@vue-macros/common'
7-
import { setResolveTSFileIdImpl, tsFileCache } from '@vue-macros/api'
4+
import { RollupResolve, setResolveTSFileIdImpl } from '@vue-macros/api'
85
import { transformBetterDefine } from './core'
9-
import type { ModuleNode } from 'vite'
10-
import type { ResolveTSFileIdImpl } from '@vue-macros/api'
116
import type { PluginContext } from 'rollup'
127
import type { FilterPattern } from '@rollup/pluginutils'
138

@@ -36,92 +31,15 @@ export default createUnplugin<Options | undefined, false>(
3631
const options = resolveOptions(userOptions)
3732
const filter = createFilter(options.include, options.exclude)
3833

39-
const referencedFiles = new Map<
40-
string /* file */,
41-
Set<string /* importer */>
42-
>()
43-
44-
function collectReferencedFile(importer: string, file: string) {
45-
if (!importer) return
46-
if (!referencedFiles.has(file)) {
47-
referencedFiles.set(file, new Set([importer]))
48-
} else {
49-
referencedFiles.get(file)!.add(importer)
50-
}
51-
}
52-
53-
const resolveCache = new Map<
54-
string /* importer */,
55-
Map<string /* id */, string /* result */>
56-
>()
57-
58-
function withResolveCache(id: string, importer: string, result: string) {
59-
if (!resolveCache.has(importer)) {
60-
resolveCache.set(importer, new Map([[id, result]]))
61-
return result
62-
}
63-
resolveCache.get(importer)!.set(id, result)
64-
return result
65-
}
66-
67-
const RollupResolve =
68-
(ctx: PluginContext): ResolveTSFileIdImpl =>
69-
async (id, importer) => {
70-
const cached = resolveCache.get(importer)?.get(id)
71-
if (cached) return cached
72-
73-
async function tryPkgEntry() {
74-
try {
75-
const pkgPath = (await ctx.resolve(`${id}/package.json`, importer))
76-
?.id
77-
if (!pkgPath) return
78-
79-
const pkg = JSON.parse(await readFile(pkgPath, 'utf-8'))
80-
const types = pkg.types || pkg.typings
81-
if (!types) return
82-
83-
const entry = path.resolve(pkgPath, '..', types)
84-
return existsSync(entry) ? entry : undefined
85-
} catch {}
86-
}
87-
88-
const tryResolve = async (id: string) => {
89-
try {
90-
return (
91-
(await ctx.resolve(id, importer))?.id ||
92-
(await ctx.resolve(`${id}.d`, importer))?.id
93-
)
94-
} catch {}
95-
96-
return
97-
}
98-
99-
if (!id.startsWith('.')) {
100-
const entry = await tryPkgEntry()
101-
if (entry) return withResolveCache(id, importer, entry)
102-
}
103-
104-
let resolved = await tryResolve(id)
105-
if (!resolved) return
106-
if (existsSync(resolved)) {
107-
collectReferencedFile(importer, resolved)
108-
return withResolveCache(id, importer, resolved)
109-
}
110-
111-
resolved = await tryResolve(resolved)
112-
if (resolved && existsSync(resolved)) {
113-
collectReferencedFile(importer, resolved)
114-
return withResolveCache(id, importer, resolved)
115-
}
116-
}
34+
const { resolve, handleHotUpdate } = RollupResolve()
11735

11836
return {
11937
name,
12038
enforce: 'pre',
12139

12240
buildStart() {
12341
if (meta.framework === 'rollup' || meta.framework === 'vite') {
124-
setResolveTSFileIdImpl(RollupResolve(this as PluginContext))
42+
setResolveTSFileIdImpl(resolve(this as PluginContext))
12543
}
12644
},
12745

@@ -143,28 +61,7 @@ export default createUnplugin<Options | undefined, false>(
14361
options.isProduction = config.isProduction
14462
},
14563

146-
handleHotUpdate({ file, server, modules }) {
147-
const cache = new Map<string, Set<ModuleNode>>()
148-
function getAffectedModules(file: string): Set<ModuleNode> {
149-
if (cache.has(file)) return cache.get(file)!
150-
151-
if (!referencedFiles.has(file)) return new Set([])
152-
const modules = new Set<ModuleNode>([])
153-
cache.set(file, modules)
154-
for (const importer of referencedFiles.get(file)!) {
155-
const mods = server.moduleGraph.getModulesByFile(importer)
156-
if (mods) mods.forEach((m) => modules.add(m))
157-
158-
getAffectedModules(importer).forEach((m) => modules.add(m))
159-
}
160-
return modules
161-
}
162-
163-
if (tsFileCache[file]) delete tsFileCache[file]
164-
165-
const affected = getAffectedModules(file)
166-
return [...modules, ...affected]
167-
},
64+
handleHotUpdate,
16865
},
16966
}
17067
}

packages/common/src/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ export const DEFINE_MODEL_DOLLAR = '$defineModel'
99
export const DEFINE_SETUP_COMPONENT = 'defineSetupComponent'
1010
export const DEFINE_RENDER = 'defineRender'
1111
export const DEFINE_SLOTS = 'defineSlots'
12+
export const DEFINE_PROP = 'defineProp'
13+
export const DEFINE_EMIT = 'defineEmit'
1214

1315
export const REPO_ISSUE_URL =
1416
'https://github.com/sxzz/unplugin-vue-macros/issues'

packages/common/src/vue.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,18 @@ import type { MagicString } from './magic-string'
77
import type {
88
SFCDescriptor,
99
SFCParseResult,
10-
SFCScriptBlock,
10+
SFCScriptBlock as SFCScriptBlockMixed,
1111
} from '@vue/compiler-sfc'
1212

13-
export type _SFCScriptBlock = Omit<
14-
SFCScriptBlock,
13+
export type SFCScriptBlock = Omit<
14+
SFCScriptBlockMixed,
1515
'scriptAst' | 'scriptSetupAst'
1616
>
1717

1818
export type SFC = Omit<SFCDescriptor, 'script' | 'scriptSetup'> & {
1919
sfc: SFCParseResult
20-
script?: _SFCScriptBlock | null
21-
scriptSetup?: _SFCScriptBlock | null
20+
script?: SFCScriptBlock | null
21+
scriptSetup?: SFCScriptBlock | null
2222
lang: string | undefined
2323
getScriptAst(): Program | undefined
2424
getSetupAst(): Program | undefined

packages/macros/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@
100100
"@vue-macros/setup-component": "workspace:*",
101101
"@vue-macros/setup-sfc": "workspace:*",
102102
"@vue-macros/short-emits": "workspace:*",
103+
"@vue-macros/single-define": "workspace:^",
103104
"unplugin": "^1.3.1",
104105
"unplugin-combine": "^0.6.0",
105106
"unplugin-vue-define-options": "workspace:*"

0 commit comments

Comments
 (0)