@bleedingdev/modern-js-server-utils 3.2.0-ultramodern.99 → 3.4.0-ultramodern.1
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/cjs/common/index.js +9 -5
- package/dist/cjs/compilers/typescript/importRewriter.js +242 -0
- package/dist/cjs/compilers/typescript/index.js +109 -39
- package/dist/cjs/compilers/typescript/tsconfigPathsPlugin.js +9 -5
- package/dist/cjs/index.js +9 -5
- package/dist/esm/compilers/typescript/importRewriter.mjs +204 -0
- package/dist/esm/compilers/typescript/index.mjs +90 -33
- package/dist/esm-node/compilers/typescript/importRewriter.mjs +205 -0
- package/dist/esm-node/compilers/typescript/index.mjs +93 -33
- package/dist/types/compilers/typescript/importRewriter.d.ts +12 -0
- package/dist/types/compilers/typescript/index.d.ts +20 -0
- package/dist/types/compilers/typescript/tsconfigPathsPlugin.d.ts +1 -1
- package/package.json +15 -7
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
const IDENTIFIER_CHAR = /[A-Za-z0-9_$]/;
|
|
2
|
+
const WHITESPACE = /\s/;
|
|
3
|
+
const KEYWORDS_BEFORE_REGEX = new Set([
|
|
4
|
+
'return',
|
|
5
|
+
'typeof',
|
|
6
|
+
'instanceof',
|
|
7
|
+
'in',
|
|
8
|
+
'of',
|
|
9
|
+
'new',
|
|
10
|
+
'delete',
|
|
11
|
+
'void',
|
|
12
|
+
'throw',
|
|
13
|
+
'case',
|
|
14
|
+
'do',
|
|
15
|
+
'else',
|
|
16
|
+
'yield',
|
|
17
|
+
'await'
|
|
18
|
+
]);
|
|
19
|
+
const PUNCTUATORS_BEFORE_REGEX = new Set('{}(,;[=:?!&|+-*%^~<>/');
|
|
20
|
+
const isRegexAllowed = (lastToken)=>{
|
|
21
|
+
if (!lastToken) return true;
|
|
22
|
+
if (KEYWORDS_BEFORE_REGEX.has(lastToken)) return true;
|
|
23
|
+
return 1 === lastToken.length && PUNCTUATORS_BEFORE_REGEX.has(lastToken);
|
|
24
|
+
};
|
|
25
|
+
const isSpecifierContext = (prev1, prev2, prev3)=>{
|
|
26
|
+
if (('from' === prev1 || 'import' === prev1) && '.' !== prev2) return true;
|
|
27
|
+
return '(' === prev1 && ('import' === prev2 || 'require' === prev2) && '.' !== prev3;
|
|
28
|
+
};
|
|
29
|
+
const skipRegexLiteral = (content, start)=>{
|
|
30
|
+
let i = start + 1;
|
|
31
|
+
let inClass = false;
|
|
32
|
+
while(i < content.length){
|
|
33
|
+
const ch = content[i];
|
|
34
|
+
if ('\\' === ch) {
|
|
35
|
+
i += 2;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if ('\n' === ch) break;
|
|
39
|
+
if ('[' === ch) inClass = true;
|
|
40
|
+
else if (']' === ch) inClass = false;
|
|
41
|
+
else if ('/' === ch && !inClass) {
|
|
42
|
+
i++;
|
|
43
|
+
while(i < content.length && IDENTIFIER_CHAR.test(content[i]))i++;
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
i++;
|
|
47
|
+
}
|
|
48
|
+
return i;
|
|
49
|
+
};
|
|
50
|
+
const scanSpecifiers = (content)=>{
|
|
51
|
+
const specifiers = [];
|
|
52
|
+
const length = content.length;
|
|
53
|
+
const templateExpressionDepths = [];
|
|
54
|
+
let prev1 = '';
|
|
55
|
+
let prev2 = '';
|
|
56
|
+
let prev3 = '';
|
|
57
|
+
const pushToken = (token)=>{
|
|
58
|
+
prev3 = prev2;
|
|
59
|
+
prev2 = prev1;
|
|
60
|
+
prev1 = token;
|
|
61
|
+
};
|
|
62
|
+
let mode = 'code';
|
|
63
|
+
let i = 0;
|
|
64
|
+
while(i < length){
|
|
65
|
+
const ch = content[i];
|
|
66
|
+
if ('template' === mode) {
|
|
67
|
+
if ('\\' === ch) {
|
|
68
|
+
i += 2;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if ('`' === ch) {
|
|
72
|
+
mode = 'code';
|
|
73
|
+
pushToken('`');
|
|
74
|
+
i++;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if ('$' === ch && '{' === content[i + 1]) {
|
|
78
|
+
templateExpressionDepths.push(0);
|
|
79
|
+
mode = 'code';
|
|
80
|
+
pushToken('{');
|
|
81
|
+
i += 2;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
i++;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (WHITESPACE.test(ch)) {
|
|
88
|
+
i++;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if ('/' === ch) {
|
|
92
|
+
const next = content[i + 1];
|
|
93
|
+
if ('/' === next) {
|
|
94
|
+
const newline = content.indexOf('\n', i + 2);
|
|
95
|
+
i = -1 === newline ? length : newline + 1;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if ('*' === next) {
|
|
99
|
+
const end = content.indexOf('*/', i + 2);
|
|
100
|
+
i = -1 === end ? length : end + 2;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (isRegexAllowed(prev1)) {
|
|
104
|
+
i = skipRegexLiteral(content, i);
|
|
105
|
+
pushToken('/regex/');
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
pushToken('/');
|
|
109
|
+
i++;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if ('`' === ch) {
|
|
113
|
+
mode = 'template';
|
|
114
|
+
i++;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if ('{' === ch) {
|
|
118
|
+
if (templateExpressionDepths.length > 0) templateExpressionDepths[templateExpressionDepths.length - 1]++;
|
|
119
|
+
pushToken('{');
|
|
120
|
+
i++;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if ('}' === ch) {
|
|
124
|
+
if (templateExpressionDepths.length > 0) {
|
|
125
|
+
const top = templateExpressionDepths.length - 1;
|
|
126
|
+
if (0 === templateExpressionDepths[top]) {
|
|
127
|
+
templateExpressionDepths.pop();
|
|
128
|
+
mode = 'template';
|
|
129
|
+
i++;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
templateExpressionDepths[top]--;
|
|
133
|
+
}
|
|
134
|
+
pushToken('}');
|
|
135
|
+
i++;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if ('"' === ch || "'" === ch) {
|
|
139
|
+
const start = i;
|
|
140
|
+
let j = i + 1;
|
|
141
|
+
let terminated = false;
|
|
142
|
+
while(j < length){
|
|
143
|
+
const sch = content[j];
|
|
144
|
+
if ('\\' === sch) {
|
|
145
|
+
j += 2;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (sch === ch) {
|
|
149
|
+
terminated = true;
|
|
150
|
+
j++;
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
if ('\n' === sch) break;
|
|
154
|
+
j++;
|
|
155
|
+
}
|
|
156
|
+
if (terminated && isSpecifierContext(prev1, prev2, prev3)) specifiers.push({
|
|
157
|
+
start: start + 1,
|
|
158
|
+
end: j - 1,
|
|
159
|
+
value: content.slice(start + 1, j - 1)
|
|
160
|
+
});
|
|
161
|
+
pushToken('"string"');
|
|
162
|
+
i = j;
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (IDENTIFIER_CHAR.test(ch)) {
|
|
166
|
+
let j = i + 1;
|
|
167
|
+
while(j < length && IDENTIFIER_CHAR.test(content[j]))j++;
|
|
168
|
+
pushToken(content.slice(i, j));
|
|
169
|
+
i = j;
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
pushToken(ch);
|
|
173
|
+
i++;
|
|
174
|
+
}
|
|
175
|
+
return specifiers;
|
|
176
|
+
};
|
|
177
|
+
const rewriteImportSpecifiers = (content, rewrite)=>{
|
|
178
|
+
const specifiers = scanSpecifiers(content);
|
|
179
|
+
if (0 === specifiers.length) return {
|
|
180
|
+
content,
|
|
181
|
+
changed: false
|
|
182
|
+
};
|
|
183
|
+
let result = '';
|
|
184
|
+
let cursor = 0;
|
|
185
|
+
let changed = false;
|
|
186
|
+
for (const specifier of specifiers){
|
|
187
|
+
if (specifier.value.includes('\\')) continue;
|
|
188
|
+
const next = rewrite(specifier.value);
|
|
189
|
+
if (void 0 !== next && next !== specifier.value) {
|
|
190
|
+
changed = true;
|
|
191
|
+
result += content.slice(cursor, specifier.start) + next;
|
|
192
|
+
cursor = specifier.end;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (!changed) return {
|
|
196
|
+
content,
|
|
197
|
+
changed: false
|
|
198
|
+
};
|
|
199
|
+
return {
|
|
200
|
+
content: result + content.slice(cursor),
|
|
201
|
+
changed: true
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
export { rewriteImportSpecifiers };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { fs, getAliasConfig, logger } from "@modern-js/utils";
|
|
2
2
|
import { spawn } from "child_process";
|
|
3
3
|
import path from "path";
|
|
4
|
+
import { rewriteImportSpecifiers } from "./importRewriter.mjs";
|
|
4
5
|
import { createTsconfigPathsMatcher, getNotAliasedPath } from "./tsconfigPathsPlugin.mjs";
|
|
5
|
-
const importSpecifierRE = /((?:from\s*|import\s*\(\s*|require\s*\(\s*)['"])([^'"]+)(['"])/g;
|
|
6
6
|
const copyFiles = async (from, to, appDirectory)=>{
|
|
7
7
|
if (await fs.pathExists(from)) {
|
|
8
8
|
const relativePath = path.relative(appDirectory, from);
|
|
@@ -15,42 +15,58 @@ const copyFiles = async (from, to, appDirectory)=>{
|
|
|
15
15
|
});
|
|
16
16
|
}
|
|
17
17
|
};
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
let resolvedConfigCount = 0;
|
|
19
|
+
const createResolvedTsgoConfig = async (appDirectory, tsconfigPath, distDir, sourceDirs, moduleType, tsgoBinPath)=>{
|
|
20
|
+
const tsconfigDir = path.dirname(tsconfigPath);
|
|
21
|
+
const output = await runTsgo(tsgoBinPath, [
|
|
20
22
|
'--showConfig',
|
|
21
23
|
'-p',
|
|
22
24
|
tsconfigPath
|
|
23
25
|
], {
|
|
24
|
-
cwd:
|
|
26
|
+
cwd: tsconfigDir
|
|
25
27
|
});
|
|
26
28
|
const config = JSON.parse(output.stdout);
|
|
27
29
|
config.compilerOptions ??= {};
|
|
28
30
|
config.compilerOptions.rootDir = appDirectory;
|
|
29
31
|
config.compilerOptions.outDir = distDir;
|
|
30
|
-
config.files = filterSourceFiles(
|
|
32
|
+
config.files = filterSourceFiles(tsconfigDir, sourceDirs, config.files);
|
|
31
33
|
delete config.include;
|
|
32
34
|
delete config.compilerOptions.baseUrl;
|
|
33
35
|
if ([
|
|
34
36
|
'node',
|
|
35
37
|
'node10'
|
|
36
38
|
].includes(String(config.compilerOptions.moduleResolution).toLowerCase())) delete config.compilerOptions.moduleResolution;
|
|
37
|
-
|
|
39
|
+
if ('module' !== moduleType) {
|
|
40
|
+
if ([
|
|
41
|
+
'preserve',
|
|
42
|
+
'esnext',
|
|
43
|
+
'es2015',
|
|
44
|
+
'es2020',
|
|
45
|
+
'es2022',
|
|
46
|
+
'es6'
|
|
47
|
+
].includes(String(config.compilerOptions.module).toLowerCase())) {
|
|
48
|
+
config.compilerOptions.module = 'commonjs';
|
|
49
|
+
delete config.compilerOptions.moduleResolution;
|
|
50
|
+
}
|
|
51
|
+
config.compilerOptions.verbatimModuleSyntax = false;
|
|
52
|
+
}
|
|
53
|
+
const resolvedConfigPath = path.join(tsconfigDir, `.tsgo.${process.pid}.${resolvedConfigCount++}.resolved.json`);
|
|
38
54
|
await fs.writeFile(resolvedConfigPath, JSON.stringify(config, null, 2));
|
|
39
55
|
return {
|
|
40
56
|
config,
|
|
41
57
|
resolvedConfigPath
|
|
42
58
|
};
|
|
43
59
|
};
|
|
44
|
-
const filterSourceFiles = (
|
|
60
|
+
const filterSourceFiles = (tsconfigDir, sourceDirs, files = [])=>{
|
|
45
61
|
const sourcePosixPaths = sourceDirs.map((sourceDir)=>sourceDir.split(path.sep).join(path.posix.sep));
|
|
46
62
|
return files.filter((fileName)=>{
|
|
47
|
-
const absoluteFileName = path.resolve(
|
|
63
|
+
const absoluteFileName = path.resolve(tsconfigDir, fileName).split(path.sep).join(path.posix.sep);
|
|
48
64
|
return fileName.endsWith('.d.ts') || sourcePosixPaths.some((sourceDir)=>absoluteFileName.includes(sourceDir));
|
|
49
65
|
});
|
|
50
66
|
};
|
|
51
|
-
const runTsgo = (args, options)=>new Promise((resolve, reject)=>{
|
|
67
|
+
const runTsgo = (tsgoBinPath, args, options)=>new Promise((resolve, reject)=>{
|
|
52
68
|
const child = spawn(process.execPath, [
|
|
53
|
-
|
|
69
|
+
tsgoBinPath,
|
|
54
70
|
...args
|
|
55
71
|
], {
|
|
56
72
|
cwd: options.cwd,
|
|
@@ -79,14 +95,51 @@ const runTsgo = (args, options)=>new Promise((resolve, reject)=>{
|
|
|
79
95
|
resolve(result);
|
|
80
96
|
});
|
|
81
97
|
});
|
|
82
|
-
const getTsgoBinPath = (
|
|
98
|
+
const getTsgoBinPath = (appDirectory, resolvePaths = [
|
|
99
|
+
appDirectory,
|
|
100
|
+
__dirname
|
|
101
|
+
])=>{
|
|
102
|
+
try {
|
|
103
|
+
const pkgPath = require.resolve("@typescript/native-preview/package.json", {
|
|
104
|
+
paths: resolvePaths
|
|
105
|
+
});
|
|
106
|
+
return path.join(path.dirname(pkgPath), 'bin/tsgo.js');
|
|
107
|
+
} catch {
|
|
108
|
+
throw new Error('tsgo could not be found! Please install "@typescript/native-preview" in your project to compile BFF/server code.');
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
const OUTPUT_SOURCE_EXTENSIONS = {
|
|
112
|
+
'.js': [
|
|
113
|
+
'.ts',
|
|
114
|
+
'.tsx',
|
|
115
|
+
'.js',
|
|
116
|
+
'.jsx'
|
|
117
|
+
],
|
|
118
|
+
'.mjs': [
|
|
119
|
+
'.mts',
|
|
120
|
+
'.mjs'
|
|
121
|
+
],
|
|
122
|
+
'.cjs': [
|
|
123
|
+
'.cts',
|
|
124
|
+
'.cjs'
|
|
125
|
+
]
|
|
126
|
+
};
|
|
83
127
|
const getSourceFileForOutput = (appDirectory, distDir, outputFile)=>{
|
|
84
128
|
const relativeOutput = path.relative(distDir, outputFile);
|
|
85
129
|
const parsed = path.parse(relativeOutput);
|
|
86
130
|
const sourceBase = path.join(appDirectory, parsed.dir, parsed.name);
|
|
87
|
-
|
|
131
|
+
const extensions = OUTPUT_SOURCE_EXTENSIONS[parsed.ext] ?? OUTPUT_SOURCE_EXTENSIONS['.js'];
|
|
132
|
+
for (const extension of extensions){
|
|
133
|
+
const candidate = `${sourceBase}${extension}`;
|
|
134
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
const sourceMappingUrlRE = /^\/\/[#@] sourceMappingURL=.*$/gm;
|
|
138
|
+
const dropStaleSourceMap = async (outputFile, content)=>{
|
|
139
|
+
const mapFile = `${outputFile}.map`;
|
|
140
|
+
if (await fs.pathExists(mapFile)) await fs.remove(mapFile);
|
|
141
|
+
return content.replace(sourceMappingUrlRE, '');
|
|
88
142
|
};
|
|
89
|
-
const findExistingSource = (filePath)=>fs.existsSync(filePath) ? filePath : void 0;
|
|
90
143
|
const rewriteOutputSpecifiers = async (appDirectory, distDir, baseUrl, paths, moduleType)=>{
|
|
91
144
|
if (0 === Object.keys(paths).length || !await fs.pathExists(distDir)) return;
|
|
92
145
|
const matcher = createTsconfigPathsMatcher(baseUrl, paths);
|
|
@@ -96,14 +149,8 @@ const rewriteOutputSpecifiers = async (appDirectory, distDir, baseUrl, paths, mo
|
|
|
96
149
|
const sourceFile = getSourceFileForOutput(appDirectory, distDir, file);
|
|
97
150
|
if (!sourceFile) return;
|
|
98
151
|
const content = await fs.readFile(file, 'utf8');
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const nextSpecifier = getNotAliasedPath(sourceFile, matcher, specifier, moduleType);
|
|
102
|
-
if (!nextSpecifier || nextSpecifier === specifier) return match;
|
|
103
|
-
changed = true;
|
|
104
|
-
return `${prefix}${nextSpecifier}${suffix}`;
|
|
105
|
-
});
|
|
106
|
-
if (changed) await fs.writeFile(file, rewritten);
|
|
152
|
+
const { content: rewritten, changed } = rewriteImportSpecifiers(content, (specifier)=>getNotAliasedPath(sourceFile, matcher, specifier, moduleType));
|
|
153
|
+
if (changed) await fs.writeFile(file, await dropStaleSourceMap(file, rewritten));
|
|
107
154
|
}));
|
|
108
155
|
};
|
|
109
156
|
const collectOutputFiles = async (dir)=>{
|
|
@@ -129,24 +176,34 @@ const compileByTs = async (appDirectory, config, compileOptions)=>{
|
|
|
129
176
|
tsconfigPath
|
|
130
177
|
});
|
|
131
178
|
const { paths = {}, absoluteBaseUrl = './' } = aliasOption;
|
|
132
|
-
const
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
179
|
+
const tsgoBinPath = getTsgoBinPath(appDirectory);
|
|
180
|
+
const { config: tsgoConfig, resolvedConfigPath } = await createResolvedTsgoConfig(appDirectory, tsconfigPath, distDir, sourceDirs, compileOptions.moduleType, tsgoBinPath);
|
|
181
|
+
let result;
|
|
182
|
+
try {
|
|
183
|
+
result = await runTsgo(tsgoBinPath, [
|
|
184
|
+
'-p',
|
|
185
|
+
resolvedConfigPath
|
|
186
|
+
], {
|
|
187
|
+
cwd: appDirectory,
|
|
188
|
+
reject: false
|
|
189
|
+
});
|
|
190
|
+
} finally{
|
|
191
|
+
await fs.remove(resolvedConfigPath);
|
|
192
|
+
}
|
|
141
193
|
if (result.stderr) logger.error(result.stderr);
|
|
142
194
|
if (result.stdout) logger.info(result.stdout);
|
|
143
195
|
if (0 !== result.code) {
|
|
144
196
|
const noEmitOnError = tsgoConfig.compilerOptions?.noEmitOnError;
|
|
145
|
-
if (void 0 === noEmitOnError || true === noEmitOnError) if (compileOptions.throwErrorInsteadOfExit)
|
|
146
|
-
|
|
197
|
+
if (void 0 === noEmitOnError || true === noEmitOnError) if (compileOptions.throwErrorInsteadOfExit) {
|
|
198
|
+
logger.error('TS-Go compilation failed');
|
|
199
|
+
throw new Error([
|
|
200
|
+
`TS-Go compilation failed with exit code ${result.code}.`,
|
|
201
|
+
result.stderr.trim() || result.stdout.trim()
|
|
202
|
+
].filter(Boolean).join('\n'));
|
|
203
|
+
} else process.exit(1);
|
|
147
204
|
}
|
|
148
205
|
await rewriteOutputSpecifiers(appDirectory, distDir, absoluteBaseUrl, paths, compileOptions.moduleType);
|
|
149
206
|
for (const source of sourceDirs)await copyFiles(source, distDir, appDirectory);
|
|
150
207
|
logger.info("TS-Go compile succeed");
|
|
151
208
|
};
|
|
152
|
-
export { compileByTs };
|
|
209
|
+
export { compileByTs, createResolvedTsgoConfig, getTsgoBinPath, rewriteOutputSpecifiers };
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import "node:module";
|
|
2
|
+
const IDENTIFIER_CHAR = /[A-Za-z0-9_$]/;
|
|
3
|
+
const WHITESPACE = /\s/;
|
|
4
|
+
const KEYWORDS_BEFORE_REGEX = new Set([
|
|
5
|
+
'return',
|
|
6
|
+
'typeof',
|
|
7
|
+
'instanceof',
|
|
8
|
+
'in',
|
|
9
|
+
'of',
|
|
10
|
+
'new',
|
|
11
|
+
'delete',
|
|
12
|
+
'void',
|
|
13
|
+
'throw',
|
|
14
|
+
'case',
|
|
15
|
+
'do',
|
|
16
|
+
'else',
|
|
17
|
+
'yield',
|
|
18
|
+
'await'
|
|
19
|
+
]);
|
|
20
|
+
const PUNCTUATORS_BEFORE_REGEX = new Set('{}(,;[=:?!&|+-*%^~<>/');
|
|
21
|
+
const isRegexAllowed = (lastToken)=>{
|
|
22
|
+
if (!lastToken) return true;
|
|
23
|
+
if (KEYWORDS_BEFORE_REGEX.has(lastToken)) return true;
|
|
24
|
+
return 1 === lastToken.length && PUNCTUATORS_BEFORE_REGEX.has(lastToken);
|
|
25
|
+
};
|
|
26
|
+
const isSpecifierContext = (prev1, prev2, prev3)=>{
|
|
27
|
+
if (('from' === prev1 || 'import' === prev1) && '.' !== prev2) return true;
|
|
28
|
+
return '(' === prev1 && ('import' === prev2 || 'require' === prev2) && '.' !== prev3;
|
|
29
|
+
};
|
|
30
|
+
const skipRegexLiteral = (content, start)=>{
|
|
31
|
+
let i = start + 1;
|
|
32
|
+
let inClass = false;
|
|
33
|
+
while(i < content.length){
|
|
34
|
+
const ch = content[i];
|
|
35
|
+
if ('\\' === ch) {
|
|
36
|
+
i += 2;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if ('\n' === ch) break;
|
|
40
|
+
if ('[' === ch) inClass = true;
|
|
41
|
+
else if (']' === ch) inClass = false;
|
|
42
|
+
else if ('/' === ch && !inClass) {
|
|
43
|
+
i++;
|
|
44
|
+
while(i < content.length && IDENTIFIER_CHAR.test(content[i]))i++;
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
i++;
|
|
48
|
+
}
|
|
49
|
+
return i;
|
|
50
|
+
};
|
|
51
|
+
const scanSpecifiers = (content)=>{
|
|
52
|
+
const specifiers = [];
|
|
53
|
+
const length = content.length;
|
|
54
|
+
const templateExpressionDepths = [];
|
|
55
|
+
let prev1 = '';
|
|
56
|
+
let prev2 = '';
|
|
57
|
+
let prev3 = '';
|
|
58
|
+
const pushToken = (token)=>{
|
|
59
|
+
prev3 = prev2;
|
|
60
|
+
prev2 = prev1;
|
|
61
|
+
prev1 = token;
|
|
62
|
+
};
|
|
63
|
+
let mode = 'code';
|
|
64
|
+
let i = 0;
|
|
65
|
+
while(i < length){
|
|
66
|
+
const ch = content[i];
|
|
67
|
+
if ('template' === mode) {
|
|
68
|
+
if ('\\' === ch) {
|
|
69
|
+
i += 2;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if ('`' === ch) {
|
|
73
|
+
mode = 'code';
|
|
74
|
+
pushToken('`');
|
|
75
|
+
i++;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if ('$' === ch && '{' === content[i + 1]) {
|
|
79
|
+
templateExpressionDepths.push(0);
|
|
80
|
+
mode = 'code';
|
|
81
|
+
pushToken('{');
|
|
82
|
+
i += 2;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
i++;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (WHITESPACE.test(ch)) {
|
|
89
|
+
i++;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if ('/' === ch) {
|
|
93
|
+
const next = content[i + 1];
|
|
94
|
+
if ('/' === next) {
|
|
95
|
+
const newline = content.indexOf('\n', i + 2);
|
|
96
|
+
i = -1 === newline ? length : newline + 1;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if ('*' === next) {
|
|
100
|
+
const end = content.indexOf('*/', i + 2);
|
|
101
|
+
i = -1 === end ? length : end + 2;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (isRegexAllowed(prev1)) {
|
|
105
|
+
i = skipRegexLiteral(content, i);
|
|
106
|
+
pushToken('/regex/');
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
pushToken('/');
|
|
110
|
+
i++;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if ('`' === ch) {
|
|
114
|
+
mode = 'template';
|
|
115
|
+
i++;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if ('{' === ch) {
|
|
119
|
+
if (templateExpressionDepths.length > 0) templateExpressionDepths[templateExpressionDepths.length - 1]++;
|
|
120
|
+
pushToken('{');
|
|
121
|
+
i++;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if ('}' === ch) {
|
|
125
|
+
if (templateExpressionDepths.length > 0) {
|
|
126
|
+
const top = templateExpressionDepths.length - 1;
|
|
127
|
+
if (0 === templateExpressionDepths[top]) {
|
|
128
|
+
templateExpressionDepths.pop();
|
|
129
|
+
mode = 'template';
|
|
130
|
+
i++;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
templateExpressionDepths[top]--;
|
|
134
|
+
}
|
|
135
|
+
pushToken('}');
|
|
136
|
+
i++;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if ('"' === ch || "'" === ch) {
|
|
140
|
+
const start = i;
|
|
141
|
+
let j = i + 1;
|
|
142
|
+
let terminated = false;
|
|
143
|
+
while(j < length){
|
|
144
|
+
const sch = content[j];
|
|
145
|
+
if ('\\' === sch) {
|
|
146
|
+
j += 2;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (sch === ch) {
|
|
150
|
+
terminated = true;
|
|
151
|
+
j++;
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
if ('\n' === sch) break;
|
|
155
|
+
j++;
|
|
156
|
+
}
|
|
157
|
+
if (terminated && isSpecifierContext(prev1, prev2, prev3)) specifiers.push({
|
|
158
|
+
start: start + 1,
|
|
159
|
+
end: j - 1,
|
|
160
|
+
value: content.slice(start + 1, j - 1)
|
|
161
|
+
});
|
|
162
|
+
pushToken('"string"');
|
|
163
|
+
i = j;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (IDENTIFIER_CHAR.test(ch)) {
|
|
167
|
+
let j = i + 1;
|
|
168
|
+
while(j < length && IDENTIFIER_CHAR.test(content[j]))j++;
|
|
169
|
+
pushToken(content.slice(i, j));
|
|
170
|
+
i = j;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
pushToken(ch);
|
|
174
|
+
i++;
|
|
175
|
+
}
|
|
176
|
+
return specifiers;
|
|
177
|
+
};
|
|
178
|
+
const rewriteImportSpecifiers = (content, rewrite)=>{
|
|
179
|
+
const specifiers = scanSpecifiers(content);
|
|
180
|
+
if (0 === specifiers.length) return {
|
|
181
|
+
content,
|
|
182
|
+
changed: false
|
|
183
|
+
};
|
|
184
|
+
let result = '';
|
|
185
|
+
let cursor = 0;
|
|
186
|
+
let changed = false;
|
|
187
|
+
for (const specifier of specifiers){
|
|
188
|
+
if (specifier.value.includes('\\')) continue;
|
|
189
|
+
const next = rewrite(specifier.value);
|
|
190
|
+
if (void 0 !== next && next !== specifier.value) {
|
|
191
|
+
changed = true;
|
|
192
|
+
result += content.slice(cursor, specifier.start) + next;
|
|
193
|
+
cursor = specifier.end;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (!changed) return {
|
|
197
|
+
content,
|
|
198
|
+
changed: false
|
|
199
|
+
};
|
|
200
|
+
return {
|
|
201
|
+
content: result + content.slice(cursor),
|
|
202
|
+
changed: true
|
|
203
|
+
};
|
|
204
|
+
};
|
|
205
|
+
export { rewriteImportSpecifiers };
|