forked from vitejs/vite-plugin-react
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathproxy-export.ts
More file actions
146 lines (136 loc) · 4.18 KB
/
proxy-export.ts
File metadata and controls
146 lines (136 loc) · 4.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import { tinyassert } from '@hiogawa/utils'
import type { Node, Program } from 'estree'
import MagicString from 'magic-string'
import { extractNames, hasDirective, validateNonAsyncFunction } from './utils'
export type TransformProxyExportOptions = {
/** Required for source map and `keep` options */
code?: string
runtime: (name: string, meta?: { value: string }) => string
ignoreExportAllDeclaration?: boolean
rejectNonAsyncFunction?: boolean
/**
* escape hatch for Waku's `allowServer`
* @default false
*/
keep?: boolean
}
export function transformDirectiveProxyExport(
ast: Program,
options: {
directive: string
} & TransformProxyExportOptions,
):
| {
exportNames: string[]
output: MagicString
}
| undefined {
if (!hasDirective(ast.body, options.directive)) {
return
}
return transformProxyExport(ast, options)
}
export function transformProxyExport(
ast: Program,
options: TransformProxyExportOptions,
): {
exportNames: string[]
output: MagicString
} {
if (options.keep && typeof options.code !== 'string') {
throw new Error('`keep` option requires `code`')
}
const output = new MagicString(options.code ?? ' '.repeat(ast.end))
const exportNames: string[] = []
function createExport(node: Node, names: string[]) {
exportNames.push(...names)
const newCode = names
.map(
(name) =>
(name === 'default' ? `export default` : `export const ${name} =`) +
` /* #__PURE__ */ ${options.runtime(name)};\n`,
)
.join('')
output.update(node.start, node.end, newCode)
}
for (const node of ast.body) {
if (node.type === 'ExportNamedDeclaration') {
if (node.declaration) {
if (
node.declaration.type === 'FunctionDeclaration' ||
node.declaration.type === 'ClassDeclaration'
) {
/**
* export function foo() {}
*/
validateNonAsyncFunction(options, node.declaration)
createExport(node, [node.declaration.id.name])
} else if (node.declaration.type === 'VariableDeclaration') {
/**
* export const foo = 1, bar = 2
*/
for (const decl of node.declaration.declarations) {
if (decl.init) validateNonAsyncFunction(options, decl.init)
}
if (options.keep && options.code) {
if (node.declaration.declarations.length === 1) {
const decl = node.declaration.declarations[0]!
if (decl.id.type === 'Identifier' && decl.init) {
const name = decl.id.name
const value = options.code.slice(decl.init.start, decl.init.end)
const newCode = `export const ${name} = /* #__PURE__ */ ${options.runtime(
name,
{ value },
)};`
output.update(node.start, node.end, newCode)
exportNames.push(name)
continue
}
}
}
const names = node.declaration.declarations.flatMap((decl) =>
extractNames(decl.id),
)
createExport(node, names)
} else {
node.declaration satisfies never
}
} else {
/**
* export { foo, bar as car } from './foo'
* export { foo, bar as car }
*/
const names: string[] = []
for (const spec of node.specifiers) {
tinyassert(spec.exported.type === 'Identifier')
names.push(spec.exported.name)
}
createExport(node, names)
}
continue
}
/**
* export * from './foo'
*/
if (
!options.ignoreExportAllDeclaration &&
node.type === 'ExportAllDeclaration'
) {
throw new Error('unsupported ExportAllDeclaration')
}
/**
* export default function foo() {}
* export default class Foo {}
* export default () => {}
*/
if (node.type === 'ExportDefaultDeclaration') {
validateNonAsyncFunction(options, node.declaration)
createExport(node, ['default'])
continue
}
if (options.keep) continue
// remove all other nodes
output.remove(node.start, node.end)
}
return { exportNames, output }
}