@lithent/hmr-parser 0.1.0
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/README.md +146 -0
- package/dist/helper/calculator.d.ts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/lithentHmrParser.mjs +21509 -0
- package/dist/lithentHmrParser.mjs.map +1 -0
- package/dist/lithentHmrParser.umd.js +216 -0
- package/dist/lithentHmrParser.umd.js.map +1 -0
- package/dist/markerParse.d.ts +2 -0
- package/dist/noMarkerParse.d.ts +2 -0
- package/dist/shared.d.ts +4 -0
- package/dist/strings.d.ts +1 -0
- package/dist/tests/mark.test.d.ts +1 -0
- package/dist/tests/nomark.test.d.ts +1 -0
- package/dist/transform/componentRegister.d.ts +3 -0
- package/dist/transform/renderGuard.d.ts +3 -0
- package/dist/types.d.ts +28 -0
- package/dist/utils/ast/componentCollector.d.ts +8 -0
- package/package.json +52 -0
- package/src/helper/calculator.ts +169 -0
- package/src/index.ts +33 -0
- package/src/markerParse.ts +61 -0
- package/src/noMarkerParse.ts +70 -0
- package/src/shared.ts +109 -0
- package/src/strings.ts +176 -0
- package/src/tests/mark.test.ts +162 -0
- package/src/tests/nomark.test.tsx +100 -0
- package/src/transform/componentRegister.ts +50 -0
- package/src/transform/renderGuard.ts +59 -0
- package/src/types.ts +33 -0
- package/src/utils/ast/componentCollector.ts +119 -0
package/dist/shared.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { MarkerTransformOptions, BaseTransformOptions, MarkerAnalysisResult, BaseAnalysisResult } from './types';
|
|
2
|
+
export declare const analyzeMarker: (options: MarkerTransformOptions) => MarkerAnalysisResult;
|
|
3
|
+
export declare const analyzeNoMarker: (options: BaseTransformOptions) => BaseAnalysisResult;
|
|
4
|
+
export declare const shouldSkipTransform: (code: string) => boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const createHmrBootstrapBlock: (targetExports: string[], componentNames: string[]) => string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { default as MagicString } from 'magic-string';
|
|
2
|
+
import { File } from '@babel/types';
|
|
3
|
+
export interface BaseTransformOptions {
|
|
4
|
+
code: string;
|
|
5
|
+
boundaryImportSpecifier: string;
|
|
6
|
+
tagFunctionImportSpecifier: string;
|
|
7
|
+
}
|
|
8
|
+
export interface MarkerTransformOptions extends BaseTransformOptions {
|
|
9
|
+
markerRegex: RegExp;
|
|
10
|
+
}
|
|
11
|
+
export interface BaseAnalysisResult {
|
|
12
|
+
targetExports: string[];
|
|
13
|
+
shouldTransform: boolean;
|
|
14
|
+
importInsertionPos: number;
|
|
15
|
+
blockInsertionPos: number;
|
|
16
|
+
headerSnippet: string;
|
|
17
|
+
ast: File;
|
|
18
|
+
}
|
|
19
|
+
export interface MarkerAnalysisResult extends BaseAnalysisResult {
|
|
20
|
+
match: RegExpExecArray | null;
|
|
21
|
+
}
|
|
22
|
+
export interface HmrTransformOptions extends MarkerTransformOptions {
|
|
23
|
+
}
|
|
24
|
+
export interface HmrTransformResult {
|
|
25
|
+
transformed: boolean;
|
|
26
|
+
code: string;
|
|
27
|
+
map: ReturnType<MagicString['generateMap']> | null;
|
|
28
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "module",
|
|
3
|
+
"name": "@lithent/hmr-parser",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Lithent HMR parser utilities for build tools",
|
|
6
|
+
"packageManager": "pnpm@9.12.3",
|
|
7
|
+
"main": "./dist/lithentHmrParser.umd.js",
|
|
8
|
+
"module": "./dist/lithentHmrParser.mjs",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/lithentHmrParser.mjs",
|
|
14
|
+
"require": "./dist/lithentHmrParser.umd.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "vite build",
|
|
19
|
+
"watch": "vite build --watch",
|
|
20
|
+
"test": "vitest run --reporter verbose",
|
|
21
|
+
"test:dev": "vitest"
|
|
22
|
+
},
|
|
23
|
+
"author": "superlucky84",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "https://github.com/superlucky84/lithent.git"
|
|
27
|
+
},
|
|
28
|
+
"homepage": "https://github.com/superlucky84/lithent",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"keywords": ["lithent", "hmr", "parser"],
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@babel/parser": "^7.26.2",
|
|
33
|
+
"@babel/types": "^7.26.0",
|
|
34
|
+
"magic-string": "^0.30.11"
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist",
|
|
38
|
+
"src"
|
|
39
|
+
],
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"lithent": "workspace:*",
|
|
42
|
+
"@types/babel__traverse": "^7.20.6",
|
|
43
|
+
"vite": "^5.4.8",
|
|
44
|
+
"vite-plugin-checker": "^0.8.0",
|
|
45
|
+
"vite-plugin-dts": "^4.2.1",
|
|
46
|
+
"vitest": "^2.1.5"
|
|
47
|
+
},
|
|
48
|
+
"volta": {
|
|
49
|
+
"node": "20.3.0",
|
|
50
|
+
"pnpm": "9.12.3"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
File,
|
|
3
|
+
ImportDeclaration,
|
|
4
|
+
ExpressionStatement,
|
|
5
|
+
} from '@babel/types';
|
|
6
|
+
|
|
7
|
+
export const collectExportNames = (ast: File): string[] => {
|
|
8
|
+
const names = new Set<string>();
|
|
9
|
+
|
|
10
|
+
for (const node of ast.program.body) {
|
|
11
|
+
if (node.type === 'ExportDefaultDeclaration') {
|
|
12
|
+
names.add('default');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (node.type === 'ExportNamedDeclaration') {
|
|
16
|
+
const { declaration, specifiers } = node;
|
|
17
|
+
|
|
18
|
+
if (declaration) {
|
|
19
|
+
if (declaration.type === 'FunctionDeclaration' && declaration.id) {
|
|
20
|
+
names.add(declaration.id.name);
|
|
21
|
+
} else if (
|
|
22
|
+
declaration.type === 'VariableDeclaration' &&
|
|
23
|
+
declaration.declarations.length
|
|
24
|
+
) {
|
|
25
|
+
for (const declarator of declaration.declarations) {
|
|
26
|
+
if (declarator.id.type === 'Identifier') {
|
|
27
|
+
names.add(declarator.id.name);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
} else if (declaration.type === 'ClassDeclaration' && declaration.id) {
|
|
31
|
+
names.add(declaration.id.name);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (specifiers?.length) {
|
|
36
|
+
specifiers.forEach(specifier => {
|
|
37
|
+
if (
|
|
38
|
+
specifier.type === 'ExportSpecifier' &&
|
|
39
|
+
specifier.exported.type === 'Identifier'
|
|
40
|
+
) {
|
|
41
|
+
names.add(specifier.exported.name);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return Array.from(names);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const detectLithentUsage = (ast: File): boolean => {
|
|
52
|
+
const watchedNames = new Set(['render', 'mount', 'createApp', 'createRoot']);
|
|
53
|
+
|
|
54
|
+
for (const node of ast.program.body) {
|
|
55
|
+
if (node.type !== 'ImportDeclaration') continue;
|
|
56
|
+
const source = node.source.value;
|
|
57
|
+
if (typeof source !== 'string') continue;
|
|
58
|
+
|
|
59
|
+
if (source === 'lithent') {
|
|
60
|
+
const hasWatched = node.specifiers.some(specifier => {
|
|
61
|
+
if (
|
|
62
|
+
specifier.type === 'ImportSpecifier' &&
|
|
63
|
+
specifier.imported.type === 'Identifier'
|
|
64
|
+
) {
|
|
65
|
+
const importedName = specifier.imported.name;
|
|
66
|
+
return watchedNames.has(importedName);
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
if (hasWatched) return true;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (source.startsWith('lithent/')) {
|
|
75
|
+
if ((node as ImportDeclaration).importKind === 'type') {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return false;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export const collectHeaderInsert = (
|
|
86
|
+
ast: File,
|
|
87
|
+
sourceCode: string,
|
|
88
|
+
boundaryImportSpecifier: string,
|
|
89
|
+
tagFunctionImportSpecifier: string
|
|
90
|
+
) => {
|
|
91
|
+
const importsToPrepend: string[] = [];
|
|
92
|
+
let hasBoundaryImport = false;
|
|
93
|
+
let hasTagImport = false;
|
|
94
|
+
let lastImportEnd = 0;
|
|
95
|
+
let directiveEnd = 0;
|
|
96
|
+
let firstImportStart: number | null = null;
|
|
97
|
+
|
|
98
|
+
for (const node of ast.program.body) {
|
|
99
|
+
if (node.type === 'ImportDeclaration') {
|
|
100
|
+
lastImportEnd = node.end ?? lastImportEnd;
|
|
101
|
+
if (firstImportStart == null) {
|
|
102
|
+
firstImportStart = node.start ?? null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (node.source.value === boundaryImportSpecifier) {
|
|
106
|
+
hasBoundaryImport = true;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (node.source.value === tagFunctionImportSpecifier) {
|
|
110
|
+
const hasTag = node.specifiers.some(specifier => {
|
|
111
|
+
return (
|
|
112
|
+
specifier.type === 'ImportSpecifier' &&
|
|
113
|
+
specifier.imported.type === 'Identifier' &&
|
|
114
|
+
specifier.imported.name === 'TagFunction'
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
if (hasTag) {
|
|
119
|
+
hasTagImport = true;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (
|
|
127
|
+
node.type === 'ExpressionStatement' &&
|
|
128
|
+
node.expression.type === 'StringLiteral'
|
|
129
|
+
) {
|
|
130
|
+
const expressionStatement = node as ExpressionStatement & {
|
|
131
|
+
directive?: string;
|
|
132
|
+
};
|
|
133
|
+
if (typeof expressionStatement.directive === 'string') {
|
|
134
|
+
directiveEnd = expressionStatement.end ?? directiveEnd;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (!hasBoundaryImport) {
|
|
143
|
+
importsToPrepend.push(
|
|
144
|
+
`import { createBoundary } from '${boundaryImportSpecifier}';`
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!hasTagImport) {
|
|
149
|
+
importsToPrepend.push(
|
|
150
|
+
`import type { TagFunction } from '${tagFunctionImportSpecifier}';`
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const importInsertionPos = firstImportStart ?? directiveEnd ?? 0;
|
|
155
|
+
const blockInsertionPos = lastImportEnd || directiveEnd || 0;
|
|
156
|
+
|
|
157
|
+
let snippet = '';
|
|
158
|
+
if (importsToPrepend.length) {
|
|
159
|
+
const precedingChar =
|
|
160
|
+
importInsertionPos > 0 ? sourceCode[importInsertionPos - 1] : undefined;
|
|
161
|
+
const needsLeadingNewline =
|
|
162
|
+
importInsertionPos > 0 && precedingChar !== '\n';
|
|
163
|
+
snippet += needsLeadingNewline ? '\n' : '';
|
|
164
|
+
snippet += importsToPrepend.join('\n');
|
|
165
|
+
snippet += '\n';
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return { importInsertionPos, blockInsertionPos, headerSnippet: snippet };
|
|
169
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { transformWithMarker } from './markerParse';
|
|
2
|
+
import { transformWithoutMarker } from './noMarkerParse';
|
|
3
|
+
import { analyzeMarker, analyzeNoMarker, shouldSkipTransform } from './shared';
|
|
4
|
+
import type {
|
|
5
|
+
MarkerTransformOptions,
|
|
6
|
+
BaseTransformOptions,
|
|
7
|
+
HmrTransformResult,
|
|
8
|
+
} from './types';
|
|
9
|
+
|
|
10
|
+
export const transformWithHmr = (
|
|
11
|
+
options: MarkerTransformOptions
|
|
12
|
+
): HmrTransformResult => {
|
|
13
|
+
const markerResult = transformWithMarker(options);
|
|
14
|
+
if (markerResult.transformed) {
|
|
15
|
+
return markerResult;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const baseOptions: BaseTransformOptions = {
|
|
19
|
+
code: options.code,
|
|
20
|
+
boundaryImportSpecifier: options.boundaryImportSpecifier,
|
|
21
|
+
tagFunctionImportSpecifier: options.tagFunctionImportSpecifier,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
return transformWithoutMarker(baseOptions);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export { analyzeMarker, analyzeNoMarker, shouldSkipTransform };
|
|
28
|
+
export { transformWithMarker, transformWithoutMarker };
|
|
29
|
+
export type {
|
|
30
|
+
MarkerTransformOptions,
|
|
31
|
+
BaseTransformOptions,
|
|
32
|
+
HmrTransformResult,
|
|
33
|
+
} from './types';
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import MagicString from 'magic-string';
|
|
2
|
+
import { createHmrBootstrapBlock } from './strings';
|
|
3
|
+
import type { MarkerTransformOptions, HmrTransformResult } from './types';
|
|
4
|
+
import { analyzeMarker } from './shared';
|
|
5
|
+
import { stitchComponentRegistration } from './transform/componentRegister';
|
|
6
|
+
import { wrapRenderCalls } from './transform/renderGuard';
|
|
7
|
+
import { collectComponentMounts } from './utils/ast/componentCollector';
|
|
8
|
+
|
|
9
|
+
export const transformWithMarker = (
|
|
10
|
+
options: MarkerTransformOptions
|
|
11
|
+
): HmrTransformResult => {
|
|
12
|
+
const analysis = analyzeMarker(options);
|
|
13
|
+
const {
|
|
14
|
+
match,
|
|
15
|
+
targetExports,
|
|
16
|
+
shouldTransform,
|
|
17
|
+
importInsertionPos,
|
|
18
|
+
headerSnippet,
|
|
19
|
+
} = analysis;
|
|
20
|
+
|
|
21
|
+
if (!shouldTransform || !match) {
|
|
22
|
+
return { transformed: false, code: options.code, map: null };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const mounts = collectComponentMounts(analysis.ast, options.code);
|
|
26
|
+
const componentNames = Array.from(
|
|
27
|
+
new Set(
|
|
28
|
+
mounts
|
|
29
|
+
.map(mount => mount.componentName)
|
|
30
|
+
.filter((name): name is string => !!name)
|
|
31
|
+
)
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const ms = new MagicString(options.code);
|
|
35
|
+
wrapRenderCalls(ms, analysis.ast, options.code);
|
|
36
|
+
const transformBlock = createHmrBootstrapBlock(
|
|
37
|
+
targetExports,
|
|
38
|
+
componentNames
|
|
39
|
+
).trimStart();
|
|
40
|
+
|
|
41
|
+
if (headerSnippet) {
|
|
42
|
+
ms.appendLeft(importInsertionPos, headerSnippet);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const precedingChar =
|
|
46
|
+
match.index > 0 ? options.code[match.index - 1] : undefined;
|
|
47
|
+
const needsLeadingNewline = match.index > 0 && precedingChar !== '\n';
|
|
48
|
+
const followingChar = options.code[match.index + match[0].length] ?? null;
|
|
49
|
+
const trailingNewline = followingChar === '\n' ? '\n' : '\n\n';
|
|
50
|
+
const blockSnippet = `${needsLeadingNewline ? '\n' : ''}${transformBlock}${trailingNewline}`;
|
|
51
|
+
|
|
52
|
+
ms.overwrite(match.index, match.index + match[0].length, blockSnippet);
|
|
53
|
+
|
|
54
|
+
stitchComponentRegistration(ms, mounts, options.code, importInsertionPos);
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
transformed: true,
|
|
58
|
+
code: ms.toString(),
|
|
59
|
+
map: ms.generateMap({ hires: true }),
|
|
60
|
+
};
|
|
61
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import MagicString from 'magic-string';
|
|
2
|
+
import { createHmrBootstrapBlock } from './strings';
|
|
3
|
+
import type { BaseTransformOptions, HmrTransformResult } from './types';
|
|
4
|
+
import { analyzeNoMarker } from './shared';
|
|
5
|
+
import { stitchComponentRegistration } from './transform/componentRegister';
|
|
6
|
+
import { wrapRenderCalls } from './transform/renderGuard';
|
|
7
|
+
import { collectComponentMounts } from './utils/ast/componentCollector';
|
|
8
|
+
|
|
9
|
+
export const transformWithoutMarker = (
|
|
10
|
+
options: BaseTransformOptions
|
|
11
|
+
): HmrTransformResult => {
|
|
12
|
+
const analysis = analyzeNoMarker(options);
|
|
13
|
+
const {
|
|
14
|
+
targetExports,
|
|
15
|
+
shouldTransform,
|
|
16
|
+
importInsertionPos,
|
|
17
|
+
blockInsertionPos,
|
|
18
|
+
headerSnippet,
|
|
19
|
+
ast,
|
|
20
|
+
} = analysis;
|
|
21
|
+
|
|
22
|
+
if (!shouldTransform) {
|
|
23
|
+
return { transformed: false, code: options.code, map: null };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const mounts = collectComponentMounts(ast, options.code);
|
|
27
|
+
const componentNames = Array.from(
|
|
28
|
+
new Set(
|
|
29
|
+
mounts
|
|
30
|
+
.map(mount => mount.componentName)
|
|
31
|
+
.filter((name): name is string => !!name)
|
|
32
|
+
)
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const ms = new MagicString(options.code);
|
|
36
|
+
wrapRenderCalls(ms, ast, options.code);
|
|
37
|
+
const transformBlock = createHmrBootstrapBlock(
|
|
38
|
+
targetExports,
|
|
39
|
+
componentNames
|
|
40
|
+
).trimStart();
|
|
41
|
+
|
|
42
|
+
const blockPrecedingChar =
|
|
43
|
+
blockInsertionPos > 0 ? options.code[blockInsertionPos - 1] : undefined;
|
|
44
|
+
const needsLeadingNewlineForBlock =
|
|
45
|
+
blockInsertionPos > 0 && blockPrecedingChar !== '\n';
|
|
46
|
+
const blockSnippet = `${needsLeadingNewlineForBlock ? '\n' : ''}${transformBlock}\n\n`;
|
|
47
|
+
|
|
48
|
+
if (!headerSnippet && !blockSnippet.trim().length) {
|
|
49
|
+
return { transformed: false, code: options.code, map: null };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (headerSnippet) {
|
|
53
|
+
if (blockInsertionPos === importInsertionPos) {
|
|
54
|
+
ms.appendLeft(importInsertionPos, `${headerSnippet}${blockSnippet}`);
|
|
55
|
+
} else {
|
|
56
|
+
ms.appendLeft(importInsertionPos, headerSnippet);
|
|
57
|
+
ms.appendLeft(blockInsertionPos, blockSnippet);
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
ms.appendLeft(blockInsertionPos, blockSnippet);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
stitchComponentRegistration(ms, mounts, options.code, importInsertionPos);
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
transformed: true,
|
|
67
|
+
code: ms.toString(),
|
|
68
|
+
map: ms.generateMap({ hires: true }),
|
|
69
|
+
};
|
|
70
|
+
};
|
package/src/shared.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { parse } from '@babel/parser';
|
|
2
|
+
import type { File } from '@babel/types';
|
|
3
|
+
import type {
|
|
4
|
+
MarkerTransformOptions,
|
|
5
|
+
BaseTransformOptions,
|
|
6
|
+
MarkerAnalysisResult,
|
|
7
|
+
BaseAnalysisResult,
|
|
8
|
+
} from './types';
|
|
9
|
+
import {
|
|
10
|
+
collectExportNames,
|
|
11
|
+
detectLithentUsage,
|
|
12
|
+
collectHeaderInsert,
|
|
13
|
+
} from './helper/calculator';
|
|
14
|
+
|
|
15
|
+
const analyzeBase = (
|
|
16
|
+
code: string,
|
|
17
|
+
boundaryImportSpecifier: string,
|
|
18
|
+
tagFunctionImportSpecifier: string
|
|
19
|
+
) => {
|
|
20
|
+
const ast = parse(code, {
|
|
21
|
+
sourceType: 'module',
|
|
22
|
+
plugins: ['typescript', 'jsx'],
|
|
23
|
+
ranges: true,
|
|
24
|
+
}) as File;
|
|
25
|
+
|
|
26
|
+
const inferredExports = collectExportNames(ast);
|
|
27
|
+
const targetExports =
|
|
28
|
+
inferredExports.length > 0 ? inferredExports : ['default'];
|
|
29
|
+
const { importInsertionPos, blockInsertionPos, headerSnippet } =
|
|
30
|
+
collectHeaderInsert(
|
|
31
|
+
ast,
|
|
32
|
+
code,
|
|
33
|
+
boundaryImportSpecifier,
|
|
34
|
+
tagFunctionImportSpecifier
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
ast,
|
|
39
|
+
targetExports,
|
|
40
|
+
importInsertionPos,
|
|
41
|
+
blockInsertionPos,
|
|
42
|
+
headerSnippet,
|
|
43
|
+
} as const;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const analyzeMarker = (
|
|
47
|
+
options: MarkerTransformOptions
|
|
48
|
+
): MarkerAnalysisResult => {
|
|
49
|
+
const {
|
|
50
|
+
code,
|
|
51
|
+
markerRegex,
|
|
52
|
+
boundaryImportSpecifier,
|
|
53
|
+
tagFunctionImportSpecifier,
|
|
54
|
+
} = options;
|
|
55
|
+
|
|
56
|
+
const {
|
|
57
|
+
ast,
|
|
58
|
+
targetExports,
|
|
59
|
+
importInsertionPos,
|
|
60
|
+
blockInsertionPos,
|
|
61
|
+
headerSnippet,
|
|
62
|
+
} = analyzeBase(code, boundaryImportSpecifier, tagFunctionImportSpecifier);
|
|
63
|
+
|
|
64
|
+
const match = markerRegex.exec(code);
|
|
65
|
+
const rawNames = match?.[1]
|
|
66
|
+
? match[1]
|
|
67
|
+
.split(',')
|
|
68
|
+
.map(token => token.trim())
|
|
69
|
+
.filter(Boolean)
|
|
70
|
+
: [];
|
|
71
|
+
|
|
72
|
+
const finalTargets = rawNames.length ? rawNames : targetExports;
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
targetExports: finalTargets,
|
|
76
|
+
shouldTransform: !!match || detectLithentUsage(ast),
|
|
77
|
+
importInsertionPos,
|
|
78
|
+
blockInsertionPos,
|
|
79
|
+
headerSnippet,
|
|
80
|
+
ast,
|
|
81
|
+
match: match ?? null,
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export const analyzeNoMarker = (
|
|
86
|
+
options: BaseTransformOptions
|
|
87
|
+
): BaseAnalysisResult => {
|
|
88
|
+
const { code, boundaryImportSpecifier, tagFunctionImportSpecifier } = options;
|
|
89
|
+
|
|
90
|
+
const {
|
|
91
|
+
ast,
|
|
92
|
+
targetExports,
|
|
93
|
+
importInsertionPos,
|
|
94
|
+
blockInsertionPos,
|
|
95
|
+
headerSnippet,
|
|
96
|
+
} = analyzeBase(code, boundaryImportSpecifier, tagFunctionImportSpecifier);
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
targetExports,
|
|
100
|
+
shouldTransform: detectLithentUsage(ast),
|
|
101
|
+
importInsertionPos,
|
|
102
|
+
blockInsertionPos,
|
|
103
|
+
headerSnippet,
|
|
104
|
+
ast,
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export const shouldSkipTransform = (code: string): boolean =>
|
|
109
|
+
code.includes('__lithentModuleId');
|