@lithent/hmr-parser 0.2.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/helper/calculator.d.cts +8 -0
- package/dist/helper/calculator.d.ts +1 -1
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +5 -5
- package/dist/lithentHmrParser.mjs +4214 -4370
- package/dist/lithentHmrParser.mjs.map +1 -1
- package/dist/lithentHmrParser.umd.js +137 -216
- package/dist/lithentHmrParser.umd.js.map +1 -1
- package/dist/markerParse.d.cts +2 -0
- package/dist/markerParse.d.ts +1 -1
- package/dist/noMarkerParse.d.cts +2 -0
- package/dist/noMarkerParse.d.ts +1 -1
- package/dist/shared.d.cts +4 -0
- package/dist/shared.d.ts +1 -1
- package/dist/strings.d.cts +1 -0
- package/dist/strings.d.ts +1 -1
- package/dist/tests/bootstrap.test.d.cts +1 -0
- package/dist/tests/bootstrap.test.d.ts +1 -0
- package/dist/tests/mark.test.d.cts +1 -0
- package/dist/tests/nomark.test.d.cts +1 -0
- package/dist/transform/componentRegister.d.cts +3 -0
- package/dist/transform/componentRegister.d.ts +2 -2
- package/dist/transform/exportSnapshot.d.cts +3 -0
- package/dist/transform/exportSnapshot.d.ts +3 -0
- package/dist/transform/renderGuard.d.cts +3 -0
- package/dist/transform/renderGuard.d.ts +1 -1
- package/dist/types.d.cts +28 -0
- package/dist/utils/ast/componentCollector.d.cts +9 -0
- package/dist/utils/ast/componentCollector.d.ts +5 -4
- package/package.json +2 -2
- package/src/helper/calculator.ts +27 -7
- package/src/markerParse.ts +30 -14
- package/src/noMarkerParse.ts +16 -4
- package/src/shared.ts +8 -2
- package/src/strings.ts +105 -177
- package/src/tests/bootstrap.test.ts +312 -0
- package/src/tests/mark.test.ts +61 -181
- package/src/tests/nomark.test.tsx +110 -89
- package/src/transform/componentRegister.ts +13 -304
- package/src/transform/exportSnapshot.ts +77 -0
- package/src/transform/renderGuard.ts +59 -49
- package/src/utils/ast/componentCollector.ts +195 -96
|
@@ -1,119 +1,218 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
Node,
|
|
4
|
-
CallExpression,
|
|
5
|
-
Identifier,
|
|
6
|
-
ObjectPattern,
|
|
7
|
-
ArrayPattern,
|
|
8
|
-
} from '@babel/types';
|
|
1
|
+
import { VISITOR_KEYS } from '@babel/types';
|
|
2
|
+
import type { File, Node } from '@babel/types';
|
|
9
3
|
|
|
10
4
|
export type MountInfo = {
|
|
11
|
-
|
|
12
|
-
propsExpression: string;
|
|
13
|
-
componentName: string | null;
|
|
5
|
+
componentName: string;
|
|
14
6
|
declarationEnd: number;
|
|
7
|
+
start: number;
|
|
8
|
+
end: number;
|
|
9
|
+
declaration: boolean;
|
|
15
10
|
};
|
|
16
11
|
|
|
17
|
-
|
|
18
|
-
node.type === 'CallExpression' &&
|
|
19
|
-
node.callee.type === 'Identifier' &&
|
|
20
|
-
node.callee.name === 'mount';
|
|
21
|
-
|
|
22
|
-
const sliceWithoutType = (
|
|
23
|
-
code: string,
|
|
24
|
-
node: Node & { typeAnnotation?: Node | null }
|
|
25
|
-
): string => {
|
|
26
|
-
const start = node.start ?? 0;
|
|
27
|
-
const end = node.typeAnnotation?.start ?? node.end ?? 0;
|
|
28
|
-
return code.slice(start, end);
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const extractPropsExpression = (
|
|
32
|
-
propsParam: Identifier | ObjectPattern | ArrayPattern | undefined,
|
|
33
|
-
code: string
|
|
34
|
-
): string => {
|
|
35
|
-
if (!propsParam) {
|
|
36
|
-
return 'props';
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
switch (propsParam.type) {
|
|
40
|
-
case 'Identifier':
|
|
41
|
-
return propsParam.name;
|
|
42
|
-
case 'ObjectPattern':
|
|
43
|
-
case 'ArrayPattern':
|
|
44
|
-
return sliceWithoutType(code, propsParam);
|
|
45
|
-
default:
|
|
46
|
-
return 'props';
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
|
|
12
|
+
// Only recognize runtime calls imported from Lithent, not similarly named helpers.
|
|
50
13
|
export const collectComponentMounts = (
|
|
51
14
|
ast: File,
|
|
52
|
-
|
|
15
|
+
_code: string,
|
|
16
|
+
importSpecifier = 'lithent'
|
|
53
17
|
): MountInfo[] => {
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
18
|
+
const factories = new Set<string>();
|
|
19
|
+
const renderers = new Set<string>();
|
|
20
|
+
const namespaces = new Map<string, string>();
|
|
21
|
+
const defaultNames = new Set<string>();
|
|
22
|
+
for (const statement of ast.program.body) {
|
|
23
|
+
if (statement.type === 'ExportDefaultDeclaration') {
|
|
24
|
+
const declaration = statement.declaration;
|
|
25
|
+
if (declaration.type === 'Identifier') defaultNames.add(declaration.name);
|
|
26
|
+
if (declaration.type === 'FunctionDeclaration' && declaration.id)
|
|
27
|
+
defaultNames.add(declaration.id.name);
|
|
59
28
|
}
|
|
60
|
-
|
|
61
|
-
const declaration = node.declarations[0];
|
|
62
29
|
if (
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
30
|
+
statement.type !== 'ImportDeclaration' ||
|
|
31
|
+
statement.importKind === 'type'
|
|
32
|
+
)
|
|
33
|
+
continue;
|
|
34
|
+
for (const specifier of statement.specifiers) {
|
|
35
|
+
if (specifier.type === 'ImportNamespaceSpecifier') {
|
|
36
|
+
namespaces.set(specifier.local.name, statement.source.value);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (
|
|
40
|
+
specifier.type !== 'ImportSpecifier' ||
|
|
41
|
+
specifier.importKind === 'type'
|
|
42
|
+
)
|
|
43
|
+
continue;
|
|
44
|
+
const imported =
|
|
45
|
+
specifier.imported.type === 'Identifier'
|
|
46
|
+
? specifier.imported.name
|
|
47
|
+
: specifier.imported.value;
|
|
48
|
+
if (statement.source.value === importSpecifier) {
|
|
49
|
+
if (imported === 'mount' || imported === 'lmount')
|
|
50
|
+
factories.add(specifier.local.name);
|
|
51
|
+
if (imported === 'h') renderers.add(specifier.local.name);
|
|
52
|
+
}
|
|
53
|
+
if (
|
|
54
|
+
[
|
|
55
|
+
`${importSpecifier}/jsx-runtime`,
|
|
56
|
+
`${importSpecifier}/jsx-dev-runtime`,
|
|
57
|
+
].includes(statement.source.value) &&
|
|
58
|
+
['jsx', 'jsxs', 'jsxDEV'].includes(imported)
|
|
59
|
+
)
|
|
60
|
+
renderers.add(specifier.local.name);
|
|
68
61
|
}
|
|
62
|
+
}
|
|
69
63
|
|
|
70
|
-
|
|
64
|
+
const unwrap = (node: Node): Node => {
|
|
71
65
|
if (
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
66
|
+
node.type === 'TSAsExpression' ||
|
|
67
|
+
node.type === 'TSSatisfiesExpression' ||
|
|
68
|
+
node.type === 'TSNonNullExpression'
|
|
69
|
+
)
|
|
70
|
+
return unwrap(node.expression);
|
|
71
|
+
return node;
|
|
72
|
+
};
|
|
73
|
+
const isRuntimeCall = (node: Node, factory: boolean): boolean => {
|
|
74
|
+
if (node.type !== 'CallExpression') return false;
|
|
75
|
+
const callee = node.callee;
|
|
76
|
+
if (callee.type === 'Identifier')
|
|
77
|
+
return (factory ? factories : renderers).has(callee.name);
|
|
78
|
+
if (
|
|
79
|
+
callee.type !== 'MemberExpression' ||
|
|
80
|
+
callee.object.type !== 'Identifier'
|
|
81
|
+
)
|
|
82
|
+
return false;
|
|
83
|
+
const source = namespaces.get(callee.object.name);
|
|
84
|
+
const name =
|
|
85
|
+
!callee.computed && callee.property.type === 'Identifier'
|
|
86
|
+
? callee.property.name
|
|
87
|
+
: callee.property.type === 'StringLiteral'
|
|
88
|
+
? callee.property.value
|
|
89
|
+
: '';
|
|
90
|
+
return factory
|
|
91
|
+
? source === importSpecifier && ['mount', 'lmount'].includes(name)
|
|
92
|
+
: (source === importSpecifier && name === 'h') ||
|
|
93
|
+
([
|
|
94
|
+
`${importSpecifier}/jsx-runtime`,
|
|
95
|
+
`${importSpecifier}/jsx-dev-runtime`,
|
|
96
|
+
].includes(source || '') &&
|
|
97
|
+
['jsx', 'jsxs', 'jsxDEV'].includes(name));
|
|
98
|
+
};
|
|
99
|
+
const visitChildren = (node: Node, visit: (node: Node) => boolean): boolean =>
|
|
100
|
+
(VISITOR_KEYS[node.type] ?? []).some(key => {
|
|
101
|
+
const value = (node as any)[key];
|
|
102
|
+
return Array.isArray(value)
|
|
103
|
+
? value.some(child => child?.type && visit(child))
|
|
104
|
+
: value?.type && visit(value);
|
|
105
|
+
});
|
|
106
|
+
const isFunction = (node: Node) =>
|
|
107
|
+
node.type === 'ArrowFunctionExpression' ||
|
|
108
|
+
node.type === 'FunctionExpression' ||
|
|
109
|
+
node.type === 'FunctionDeclaration';
|
|
77
110
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
111
|
+
// Follow returned values, not JSX used only for side effects or in unrelated
|
|
112
|
+
// nested functions. Render callbacks in a returned list are part of its output.
|
|
113
|
+
const returnsRender = (node: Node): boolean => {
|
|
114
|
+
if (!isFunction(node)) return false;
|
|
115
|
+
const fn = node as import('@babel/types').Function;
|
|
116
|
+
if (fn.async || fn.generator) return false;
|
|
117
|
+
const locals = new Map<string, Node>();
|
|
118
|
+
const returns: Node[] = [];
|
|
119
|
+
const scan = (child: Node): boolean => {
|
|
120
|
+
if (isFunction(child)) return false;
|
|
121
|
+
if (
|
|
122
|
+
child.type === 'VariableDeclarator' &&
|
|
123
|
+
child.id.type === 'Identifier' &&
|
|
124
|
+
child.init
|
|
125
|
+
)
|
|
126
|
+
locals.set(child.id.name, child.init);
|
|
127
|
+
if (child.type === 'ReturnStatement' && child.argument)
|
|
128
|
+
returns.push(child.argument);
|
|
129
|
+
visitChildren(child, scan);
|
|
130
|
+
return false;
|
|
131
|
+
};
|
|
132
|
+
if (fn.body.type === 'BlockStatement') scan(fn.body);
|
|
133
|
+
else returns.push(fn.body);
|
|
134
|
+
const visiting = new Set<string>();
|
|
135
|
+
const output = (raw: Node): boolean => {
|
|
136
|
+
const child = unwrap(raw);
|
|
137
|
+
if (
|
|
138
|
+
child.type === 'JSXElement' ||
|
|
139
|
+
child.type === 'JSXFragment' ||
|
|
140
|
+
child.type === 'NullLiteral' ||
|
|
141
|
+
(child.type === 'BooleanLiteral' && !child.value)
|
|
142
|
+
)
|
|
143
|
+
return true;
|
|
144
|
+
if (isRuntimeCall(child, false)) return true;
|
|
145
|
+
if (child.type === 'Identifier') {
|
|
146
|
+
if (child.name === 'undefined') return true;
|
|
147
|
+
if (
|
|
148
|
+
fn.params[1]?.type === 'Identifier' &&
|
|
149
|
+
child.name === fn.params[1].name
|
|
150
|
+
)
|
|
151
|
+
return true;
|
|
152
|
+
const local = locals.get(child.name);
|
|
153
|
+
if (!local || visiting.has(child.name)) return false;
|
|
154
|
+
visiting.add(child.name);
|
|
155
|
+
const result = output(local);
|
|
156
|
+
visiting.delete(child.name);
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
if (isFunction(child)) return false;
|
|
160
|
+
if (child.type === 'CallExpression') {
|
|
161
|
+
return child.arguments.some(
|
|
162
|
+
argument => isFunction(argument) && returnsRender(argument)
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
if (child.type === 'ConditionalExpression')
|
|
166
|
+
return output(child.consequent) || output(child.alternate);
|
|
167
|
+
if (child.type === 'LogicalExpression')
|
|
168
|
+
return output(child.left) || output(child.right);
|
|
169
|
+
if (child.type === 'ArrayExpression')
|
|
170
|
+
return (
|
|
171
|
+
!child.elements.length ||
|
|
172
|
+
child.elements.some(item => item && output(item))
|
|
173
|
+
);
|
|
174
|
+
return false;
|
|
175
|
+
};
|
|
176
|
+
return returns.some(output);
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const isComponent = (raw: Node, name: string): boolean => {
|
|
180
|
+
const node = unwrap(raw);
|
|
181
|
+
if (isRuntimeCall(node, true)) return true;
|
|
182
|
+
if (!/^[A-Z]/.test(name) && name !== 'default' && !defaultNames.has(name))
|
|
183
|
+
return false;
|
|
184
|
+
return returnsRender(node);
|
|
185
|
+
};
|
|
94
186
|
|
|
187
|
+
const results: MountInfo[] = [];
|
|
188
|
+
const add = (node: Node, name: string, end: number, declaration = false) => {
|
|
189
|
+
if (!isComponent(node, name)) return;
|
|
95
190
|
results.push({
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
191
|
+
componentName: name,
|
|
192
|
+
declarationEnd: end,
|
|
193
|
+
start: node.start!,
|
|
194
|
+
end: node.end!,
|
|
195
|
+
declaration,
|
|
100
196
|
});
|
|
101
197
|
};
|
|
102
|
-
|
|
103
198
|
for (const statement of ast.program.body) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
199
|
+
const node =
|
|
200
|
+
statement.type === 'ExportNamedDeclaration' ||
|
|
201
|
+
statement.type === 'ExportDefaultDeclaration'
|
|
202
|
+
? statement.declaration
|
|
203
|
+
: statement;
|
|
204
|
+
if (!node) continue;
|
|
205
|
+
if (node.type === 'VariableDeclaration') {
|
|
206
|
+
for (const declaration of node.declarations) {
|
|
207
|
+
if (declaration.id.type === 'Identifier' && declaration.init) {
|
|
208
|
+
add(declaration.init, declaration.id.name, statement.end!);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
} else if (node.type === 'FunctionDeclaration' && node.id) {
|
|
212
|
+
add(node, node.id.name, statement.end!, true);
|
|
213
|
+
} else if (statement.type === 'ExportDefaultDeclaration') {
|
|
214
|
+
add(node, 'default', statement.end!);
|
|
115
215
|
}
|
|
116
216
|
}
|
|
117
|
-
|
|
118
217
|
return results;
|
|
119
218
|
};
|