@bleedingdev/modern-js-server-utils 3.9.0-ultramodern.9 → 3.9.3-ultramodern.10
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 +13 -2
- package/dist/cjs/compilers/rslib.js +313 -0
- package/dist/cjs/compilers/tsgo.js +181 -0
- package/dist/esm/common/index.mjs +13 -2
- package/dist/esm/compilers/rslib.mjs +265 -0
- package/dist/esm/compilers/tsgo.mjs +124 -0
- package/dist/esm-node/common/index.mjs +13 -2
- package/dist/esm-node/compilers/rslib.mjs +266 -0
- package/dist/esm-node/compilers/tsgo.mjs +129 -0
- package/dist/types/common/index.d.ts +0 -3
- package/dist/types/compilers/rslib.d.ts +10 -0
- package/dist/types/compilers/tsgo.d.ts +49 -0
- package/package.json +14 -20
- package/dist/cjs/compilers/typescript/importRewriter.js +0 -175
- package/dist/cjs/compilers/typescript/index.js +0 -314
- package/dist/cjs/compilers/typescript/tsconfigPathsPlugin.js +0 -194
- package/dist/esm/compilers/typescript/importRewriter.mjs +0 -134
- package/dist/esm/compilers/typescript/index.mjs +0 -257
- package/dist/esm/compilers/typescript/tsconfigPathsPlugin.mjs +0 -143
- package/dist/esm-node/compilers/typescript/importRewriter.mjs +0 -135
- package/dist/esm-node/compilers/typescript/index.mjs +0 -262
- package/dist/esm-node/compilers/typescript/tsconfigPathsPlugin.mjs +0 -145
- package/dist/types/compilers/typescript/importRewriter.d.ts +0 -8
- package/dist/types/compilers/typescript/index.d.ts +0 -35
- package/dist/types/compilers/typescript/tsconfigPathsPlugin.d.ts +0 -3
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { fs, logger, mergeAlias, semver } from "@modern-js/utils";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { createResolvedTsgoConfig, getTsgoBinPath, getTsgoModulePath, runTsgo } from "./tsgo.mjs";
|
|
4
|
+
const SCRIPT_RE = /\.(?:[cm]?[jt]s|[jt]sx)$/u;
|
|
5
|
+
const DECLARATION_RE = /\.d\.[cm]?ts$/u;
|
|
6
|
+
const TS_RE = /\.[cm]?tsx?$/u;
|
|
7
|
+
const toPosix = (file)=>file.split(path.sep).join(path.posix.sep);
|
|
8
|
+
const escapeGlob = (file)=>file.replace(/[()[\]{}*?!+@]/gu, '\\$&');
|
|
9
|
+
const walk = async (dir)=>{
|
|
10
|
+
if (!await fs.pathExists(dir)) return [];
|
|
11
|
+
const entries = await fs.readdir(dir, {
|
|
12
|
+
withFileTypes: true
|
|
13
|
+
});
|
|
14
|
+
const files = await Promise.all(entries.map((entry)=>{
|
|
15
|
+
const file = path.join(dir, entry.name);
|
|
16
|
+
if (entry.isDirectory()) return 'node_modules' === entry.name ? [] : walk(file);
|
|
17
|
+
return [
|
|
18
|
+
file
|
|
19
|
+
];
|
|
20
|
+
}));
|
|
21
|
+
return files.flat();
|
|
22
|
+
};
|
|
23
|
+
const outputExtension = (file)=>/\.m[jt]s$/u.test(file) ? '.mjs' : /\.c[jt]s$/u.test(file) ? '.cjs' : '.js';
|
|
24
|
+
const collectSources = async (sourceDirs, excludeFiles)=>{
|
|
25
|
+
const excluded = new Set(excludeFiles.map((file)=>path.normalize(file)));
|
|
26
|
+
return (await Promise.all(sourceDirs.map(walk))).flat().filter((file)=>SCRIPT_RE.test(file) && !DECLARATION_RE.test(file) && !excluded.has(path.normalize(file)));
|
|
27
|
+
};
|
|
28
|
+
const groupEntries = (appDirectory, files)=>{
|
|
29
|
+
const outputs = new Map();
|
|
30
|
+
const entries = {
|
|
31
|
+
'.js': [],
|
|
32
|
+
'.mjs': [],
|
|
33
|
+
'.cjs': []
|
|
34
|
+
};
|
|
35
|
+
for (const file of files){
|
|
36
|
+
const extension = outputExtension(file);
|
|
37
|
+
const output = `${file.replace(SCRIPT_RE, '')}${extension}`;
|
|
38
|
+
const existing = outputs.get(output);
|
|
39
|
+
if (existing) throw new Error(`"${existing}" and "${file}" both compile to "${toPosix(path.relative(appDirectory, output))}". Rename one of them.`);
|
|
40
|
+
outputs.set(output, file);
|
|
41
|
+
entries[extension].push(escapeGlob(toPosix(path.relative(appDirectory, file))));
|
|
42
|
+
}
|
|
43
|
+
return entries;
|
|
44
|
+
};
|
|
45
|
+
const resolveAlias = (appDirectory, alias)=>Object.fromEntries(Object.entries(mergeAlias(alias)).map(([key, target])=>[
|
|
46
|
+
key,
|
|
47
|
+
[].concat(target).map((value)=>value.startsWith('.') ? path.resolve(appDirectory, value) : value)
|
|
48
|
+
]));
|
|
49
|
+
const copyAssets = async (sourceDir, appDirectory, distDir)=>{
|
|
50
|
+
if (!await fs.pathExists(sourceDir)) return;
|
|
51
|
+
await fs.copy(sourceDir, path.join(distDir, path.relative(appDirectory, sourceDir)), {
|
|
52
|
+
filter: (src)=>!SCRIPT_RE.test(src) && !src.endsWith('tsconfig.json')
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
const matchAlias = (alias, request)=>{
|
|
56
|
+
for (const [key, targets] of Object.entries(alias)){
|
|
57
|
+
const exact = key.endsWith('$');
|
|
58
|
+
const name = exact ? key.slice(0, -1) : key;
|
|
59
|
+
if (request === name || !exact && request.startsWith(`${name}/`)) return {
|
|
60
|
+
targets,
|
|
61
|
+
rest: request.slice(name.length)
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
const isBareSpecifier = (specifier)=>!/^[./]/u.test(specifier) && !path.isAbsolute(specifier);
|
|
66
|
+
const relativeSpecifier = (from, to)=>{
|
|
67
|
+
const relative = toPosix(path.relative(from, to));
|
|
68
|
+
return relative.startsWith('.') ? relative : `./${relative}`;
|
|
69
|
+
};
|
|
70
|
+
const importEmittedFile = ({ appDirectory, distDir, alias, sources, reached, assets })=>async ({ request, context, contextInfo, getResolve })=>{
|
|
71
|
+
if (!request || !context || !contextInfo?.issuer || !getResolve) return;
|
|
72
|
+
const resolve = getResolve();
|
|
73
|
+
const resolved = await resolve(context, request).catch(()=>'');
|
|
74
|
+
if (!resolved) return /^[./]/u.test(request) ? void 0 : request;
|
|
75
|
+
const fromApp = path.relative(appDirectory, resolved);
|
|
76
|
+
if (fromApp.startsWith('..') || path.isAbsolute(fromApp) || resolved.includes(`${path.sep}node_modules${path.sep}`)) {
|
|
77
|
+
const aliased = matchAlias(alias, request);
|
|
78
|
+
if (!aliased) return request;
|
|
79
|
+
for (const target of aliased.targets.filter(isBareSpecifier)){
|
|
80
|
+
const specifier = `${target}${aliased.rest}`;
|
|
81
|
+
if (await resolve(context, specifier).catch(()=>'') === resolved) return specifier;
|
|
82
|
+
}
|
|
83
|
+
const outputDir = path.join(distDir, path.relative(appDirectory, path.dirname(contextInfo.issuer)));
|
|
84
|
+
return relativeSpecifier(outputDir, resolved);
|
|
85
|
+
}
|
|
86
|
+
const script = SCRIPT_RE.test(resolved) && !DECLARATION_RE.test(resolved);
|
|
87
|
+
if (script && !sources.has(resolved)) reached.add(resolved);
|
|
88
|
+
if (!script) assets.add(resolved);
|
|
89
|
+
const emitted = script ? `${resolved.replace(SCRIPT_RE, '')}${outputExtension(resolved)}` : resolved;
|
|
90
|
+
return relativeSpecifier(path.dirname(contextInfo.issuer), emitted);
|
|
91
|
+
};
|
|
92
|
+
const compileServerSources = async (appDirectory, config, options)=>{
|
|
93
|
+
const { sourceDirs, distDir, tsconfigPath, moduleType, excludeFiles = [] } = options;
|
|
94
|
+
const sources = new Set(await collectSources(sourceDirs, excludeFiles));
|
|
95
|
+
const copyAllAssets = async ()=>{
|
|
96
|
+
for (const sourceDir of sourceDirs)await copyAssets(sourceDir, appDirectory, distDir);
|
|
97
|
+
};
|
|
98
|
+
const tsgoBinPath = getTsgoBinPath(appDirectory);
|
|
99
|
+
const { config: tsconfig, resolvedConfigPath } = await createResolvedTsgoConfig(appDirectory, tsconfigPath, sourceDirs, tsgoBinPath, excludeFiles);
|
|
100
|
+
const compilerOptions = tsconfig.compilerOptions ?? {};
|
|
101
|
+
const tsconfigDir = path.dirname(tsconfigPath);
|
|
102
|
+
const roots = new Set((tsconfig.files ?? []).map((file)=>path.resolve(tsconfigDir, file)));
|
|
103
|
+
for (const file of sources){
|
|
104
|
+
const checked = true === compilerOptions.allowJs || TS_RE.test(file);
|
|
105
|
+
if (checked && !roots.has(path.resolve(file))) sources.delete(file);
|
|
106
|
+
}
|
|
107
|
+
const failOnTypeError = false !== compilerOptions.noEmitOnError;
|
|
108
|
+
const declaration = true === compilerOptions.declaration;
|
|
109
|
+
const formats = {
|
|
110
|
+
'.js': 'module' === moduleType ? 'esm' : 'cjs',
|
|
111
|
+
'.mjs': 'esm',
|
|
112
|
+
'.cjs': 'cjs'
|
|
113
|
+
};
|
|
114
|
+
const createLibs = (entries, emitDeclarations)=>{
|
|
115
|
+
const libs = Object.keys(entries).filter((extension)=>entries[extension].length > 0).map((extension)=>({
|
|
116
|
+
format: formats[extension],
|
|
117
|
+
bundle: false,
|
|
118
|
+
autoExtension: false,
|
|
119
|
+
outBase: appDirectory,
|
|
120
|
+
source: {
|
|
121
|
+
entry: {
|
|
122
|
+
index: entries[extension]
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
output: {
|
|
126
|
+
filename: {
|
|
127
|
+
js: `[name]${extension}`
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}));
|
|
131
|
+
if (emitDeclarations) {
|
|
132
|
+
libs[0].redirect = {
|
|
133
|
+
dts: {
|
|
134
|
+
extension: 'module' === moduleType
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
libs[0].dts = {
|
|
138
|
+
bundle: false,
|
|
139
|
+
distPath: distDir,
|
|
140
|
+
tsgo: true,
|
|
141
|
+
typescriptPath: getTsgoModulePath(appDirectory),
|
|
142
|
+
abortOnError: failOnTypeError
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
return libs;
|
|
146
|
+
};
|
|
147
|
+
const alias = resolveAlias(appDirectory, config.alias);
|
|
148
|
+
const assets = new Set();
|
|
149
|
+
const build = async ()=>{
|
|
150
|
+
if (0 === sources.size) return;
|
|
151
|
+
const { createRslib } = await import("@rslib/core");
|
|
152
|
+
const appPackage = await fs.readJSON(path.join(appDirectory, 'package.json')).catch(()=>void 0);
|
|
153
|
+
const nodeEngine = appPackage?.engines?.node;
|
|
154
|
+
const nodeRange = 'string' == typeof nodeEngine ? semver.validRange(nodeEngine) : null;
|
|
155
|
+
const nodeVersion = nodeRange ? semver.minVersion(nodeRange) : null;
|
|
156
|
+
const nodeTarget = nodeVersion?.major ? `node${nodeVersion.major}.${nodeVersion.minor}` : void 0;
|
|
157
|
+
let emitDeclarations = declaration;
|
|
158
|
+
for(;;){
|
|
159
|
+
const reached = new Set();
|
|
160
|
+
const rslib = await createRslib({
|
|
161
|
+
cwd: appDirectory,
|
|
162
|
+
config: {
|
|
163
|
+
lib: createLibs(groupEntries(appDirectory, sources), emitDeclarations),
|
|
164
|
+
source: {
|
|
165
|
+
tsconfigPath: resolvedConfigPath,
|
|
166
|
+
...compilerOptions.experimentalDecorators ? {
|
|
167
|
+
decorators: {
|
|
168
|
+
version: 'legacy'
|
|
169
|
+
}
|
|
170
|
+
} : {}
|
|
171
|
+
},
|
|
172
|
+
resolve: {
|
|
173
|
+
alias
|
|
174
|
+
},
|
|
175
|
+
output: {
|
|
176
|
+
target: 'node',
|
|
177
|
+
distPath: {
|
|
178
|
+
root: distDir
|
|
179
|
+
},
|
|
180
|
+
cleanDistPath: false,
|
|
181
|
+
sourceMap: {
|
|
182
|
+
js: 'source-map'
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
performance: {
|
|
186
|
+
buildCache: false
|
|
187
|
+
},
|
|
188
|
+
tools: {
|
|
189
|
+
rspack: (rspackConfig)=>{
|
|
190
|
+
if (nodeTarget) rspackConfig.target = nodeTarget;
|
|
191
|
+
rspackConfig.externals = [
|
|
192
|
+
importEmittedFile({
|
|
193
|
+
appDirectory,
|
|
194
|
+
distDir,
|
|
195
|
+
alias,
|
|
196
|
+
sources,
|
|
197
|
+
reached,
|
|
198
|
+
assets
|
|
199
|
+
}),
|
|
200
|
+
...[
|
|
201
|
+
rspackConfig.externals ?? []
|
|
202
|
+
].flat()
|
|
203
|
+
];
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
await rslib.build();
|
|
209
|
+
emitDeclarations = false;
|
|
210
|
+
if (0 === reached.size) return;
|
|
211
|
+
for (const file of reached)sources.add(file);
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
const typeCheck = async ()=>{
|
|
215
|
+
if (0 === roots.size) return;
|
|
216
|
+
const result = await runTsgo(tsgoBinPath, [
|
|
217
|
+
'-p',
|
|
218
|
+
resolvedConfigPath,
|
|
219
|
+
'--noEmit'
|
|
220
|
+
], {
|
|
221
|
+
cwd: appDirectory,
|
|
222
|
+
reject: false
|
|
223
|
+
});
|
|
224
|
+
if (0 === result.code) return;
|
|
225
|
+
const diagnostics = result.stdout.trim() || result.stderr.trim();
|
|
226
|
+
if (!failOnTypeError) return void logger.warn(`TS-Go type check found errors:\n${diagnostics}`);
|
|
227
|
+
throw new Error([
|
|
228
|
+
`TS-Go type check failed with exit code ${result.code}.`,
|
|
229
|
+
diagnostics
|
|
230
|
+
].filter(Boolean).join('\n'));
|
|
231
|
+
};
|
|
232
|
+
const checkReachedSources = async ()=>{
|
|
233
|
+
const reachedRoots = [
|
|
234
|
+
...sources
|
|
235
|
+
].filter((file)=>!roots.has(path.resolve(file)) && (true === compilerOptions.allowJs || TS_RE.test(file)));
|
|
236
|
+
if (0 === reachedRoots.length) return typeCheck();
|
|
237
|
+
for (const file of reachedRoots)roots.add(path.resolve(file));
|
|
238
|
+
tsconfig.files = [
|
|
239
|
+
...tsconfig.files ?? [],
|
|
240
|
+
...reachedRoots.map((file)=>toPosix(path.relative(tsconfigDir, file)))
|
|
241
|
+
];
|
|
242
|
+
await fs.writeFile(resolvedConfigPath, JSON.stringify(tsconfig, null, 2));
|
|
243
|
+
return typeCheck();
|
|
244
|
+
};
|
|
245
|
+
let results;
|
|
246
|
+
try {
|
|
247
|
+
const built = await Promise.allSettled([
|
|
248
|
+
build()
|
|
249
|
+
]);
|
|
250
|
+
results = [
|
|
251
|
+
...await Promise.allSettled([
|
|
252
|
+
checkReachedSources()
|
|
253
|
+
]),
|
|
254
|
+
...built
|
|
255
|
+
];
|
|
256
|
+
} finally{
|
|
257
|
+
await fs.remove(resolvedConfigPath);
|
|
258
|
+
}
|
|
259
|
+
const failure = results.find((result)=>'rejected' === result.status);
|
|
260
|
+
if (failure) throw failure.reason;
|
|
261
|
+
await copyAllAssets();
|
|
262
|
+
for (const asset of assets)await fs.copy(asset, path.join(distDir, path.relative(appDirectory, asset)));
|
|
263
|
+
logger.info('Server sources compiled');
|
|
264
|
+
};
|
|
265
|
+
export { compileServerSources };
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { fs } from "@modern-js/utils";
|
|
2
|
+
import { spawn } from "child_process";
|
|
3
|
+
import path from "path";
|
|
4
|
+
let resolvedConfigCount = 0;
|
|
5
|
+
const createResolvedTsgoConfig = async (appDirectory, tsconfigPath, sourceDirs, tsgoBinPath, excludeFiles = [])=>{
|
|
6
|
+
const tsconfigDir = path.dirname(tsconfigPath);
|
|
7
|
+
const output = await runTsgo(tsgoBinPath, [
|
|
8
|
+
'--showConfig',
|
|
9
|
+
'-p',
|
|
10
|
+
tsconfigPath
|
|
11
|
+
], {
|
|
12
|
+
cwd: tsconfigDir
|
|
13
|
+
});
|
|
14
|
+
const config = JSON.parse(output.stdout);
|
|
15
|
+
config.compilerOptions ??= {};
|
|
16
|
+
const options = config.compilerOptions;
|
|
17
|
+
options.rootDir = appDirectory;
|
|
18
|
+
options.composite = false;
|
|
19
|
+
options.declarationMap = false;
|
|
20
|
+
options.incremental = false;
|
|
21
|
+
options.noEmit = true !== options.declaration;
|
|
22
|
+
options.jsx ??= 'react-jsx';
|
|
23
|
+
delete options.emitDeclarationOnly;
|
|
24
|
+
delete options.outDir;
|
|
25
|
+
delete options.tsBuildInfoFile;
|
|
26
|
+
config.files = filterSourceFiles(tsconfigDir, sourceDirs, config.files, excludeFiles);
|
|
27
|
+
delete config.include;
|
|
28
|
+
delete config.references;
|
|
29
|
+
if (void 0 !== options.baseUrl) {
|
|
30
|
+
const baseUrl = path.resolve(tsconfigDir, options.baseUrl);
|
|
31
|
+
if (options.paths) options.paths = Object.fromEntries(Object.entries(options.paths).map(([key, targets])=>[
|
|
32
|
+
key,
|
|
33
|
+
targets.map((target)=>{
|
|
34
|
+
const relative = path.relative(tsconfigDir, path.resolve(baseUrl, target)).split(path.sep).join(path.posix.sep);
|
|
35
|
+
return relative.startsWith('.') ? relative : `./${relative}`;
|
|
36
|
+
})
|
|
37
|
+
]));
|
|
38
|
+
delete options.baseUrl;
|
|
39
|
+
}
|
|
40
|
+
if ([
|
|
41
|
+
'node',
|
|
42
|
+
'node10'
|
|
43
|
+
].includes(String(options.moduleResolution).toLowerCase())) delete options.moduleResolution;
|
|
44
|
+
const resolvedConfigPath = path.join(tsconfigDir, `.tsgo.${process.pid}.${resolvedConfigCount++}.resolved.json`);
|
|
45
|
+
await fs.writeFile(resolvedConfigPath, JSON.stringify(config, null, 2));
|
|
46
|
+
return {
|
|
47
|
+
config,
|
|
48
|
+
resolvedConfigPath
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
const toPosix = (file)=>file.split(path.sep).join(path.posix.sep);
|
|
52
|
+
const filterSourceFiles = (tsconfigDir, sourceDirs, files = [], excludeFiles = [])=>{
|
|
53
|
+
const sourcePosixPaths = sourceDirs.map(toPosix);
|
|
54
|
+
const excludedPaths = new Set(excludeFiles.map((file)=>toPosix(path.normalize(file))));
|
|
55
|
+
return files.filter((fileName)=>{
|
|
56
|
+
const absoluteFileName = toPosix(path.resolve(tsconfigDir, fileName));
|
|
57
|
+
if (excludedPaths.has(absoluteFileName)) return false;
|
|
58
|
+
return fileName.endsWith('.d.ts') || sourcePosixPaths.some((sourceDir)=>absoluteFileName.includes(sourceDir));
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
const runTsgo = (tsgoBinPath, args, options)=>new Promise((resolve, reject)=>{
|
|
62
|
+
const child = spawn(process.execPath, [
|
|
63
|
+
tsgoBinPath,
|
|
64
|
+
...args
|
|
65
|
+
], {
|
|
66
|
+
cwd: options.cwd,
|
|
67
|
+
stdio: [
|
|
68
|
+
'ignore',
|
|
69
|
+
'pipe',
|
|
70
|
+
'pipe'
|
|
71
|
+
]
|
|
72
|
+
});
|
|
73
|
+
let stdout = '';
|
|
74
|
+
let stderr = '';
|
|
75
|
+
child.stdout.on('data', (data)=>{
|
|
76
|
+
stdout += data;
|
|
77
|
+
});
|
|
78
|
+
child.stderr.on('data', (data)=>{
|
|
79
|
+
stderr += data;
|
|
80
|
+
});
|
|
81
|
+
child.on('error', reject);
|
|
82
|
+
child.on('close', (code)=>{
|
|
83
|
+
const result = {
|
|
84
|
+
stdout,
|
|
85
|
+
stderr,
|
|
86
|
+
code: code ?? 1
|
|
87
|
+
};
|
|
88
|
+
if (false !== options.reject && 0 !== result.code) return void reject(new Error(stderr || stdout || `tsgo exited with ${result.code}`));
|
|
89
|
+
resolve(result);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
const resolveTypeScriptPackage = (resolvePaths)=>{
|
|
93
|
+
try {
|
|
94
|
+
return require.resolve("typescript/package.json", {
|
|
95
|
+
paths: resolvePaths
|
|
96
|
+
});
|
|
97
|
+
} catch {
|
|
98
|
+
throw new Error('TypeScript 7.0.2 could not be found! Please install "typescript@7.0.2" in your project to type-check BFF/server code.');
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
const getTsgoBinPath = (appDirectory, resolvePaths = [
|
|
102
|
+
appDirectory,
|
|
103
|
+
__dirname
|
|
104
|
+
])=>{
|
|
105
|
+
const pkgPath = resolveTypeScriptPackage(resolvePaths);
|
|
106
|
+
const pkgDir = path.dirname(pkgPath);
|
|
107
|
+
const pkg = require(pkgPath);
|
|
108
|
+
if ("typescript" !== pkg.name || '7.0.2' !== pkg.version) throw new Error(`Server compilation requires typescript@7.0.2; found ${pkg.name ?? 'unnamed package'}@${pkg.version ?? 'unknown version'} at ${pkgPath}.`);
|
|
109
|
+
if ('string' != typeof pkg.bin?.tsc || !pkg.bin.tsc) throw new Error(`typescript@7.0.2 declares no tsc executable: ${pkgPath}.`);
|
|
110
|
+
const binPath = path.resolve(pkgDir, pkg.bin.tsc);
|
|
111
|
+
const relativeBinPath = path.relative(pkgDir, binPath);
|
|
112
|
+
if (relativeBinPath.startsWith(`..${path.sep}`) || path.isAbsolute(relativeBinPath) || !fs.existsSync(binPath) || !fs.statSync(binPath).isFile()) throw new Error(`typescript@7.0.2 declared tsc executable is missing or outside its package: ${binPath}.`);
|
|
113
|
+
return binPath;
|
|
114
|
+
};
|
|
115
|
+
const getTsgoModulePath = (appDirectory, resolvePaths = [
|
|
116
|
+
appDirectory,
|
|
117
|
+
__dirname
|
|
118
|
+
])=>{
|
|
119
|
+
resolveTypeScriptPackage(resolvePaths);
|
|
120
|
+
return require.resolve("typescript", {
|
|
121
|
+
paths: resolvePaths
|
|
122
|
+
});
|
|
123
|
+
};
|
|
124
|
+
export { createResolvedTsgoConfig, getTsgoBinPath, getTsgoModulePath, runTsgo };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import "node:module";
|
|
2
|
+
import { logger } from "@modern-js/utils";
|
|
2
3
|
import * as __rspack_external_path from "path";
|
|
3
4
|
const FILE_EXTENSIONS = [
|
|
4
5
|
'.js',
|
|
@@ -17,7 +18,17 @@ const compile = async (appDirectory, modernConfig, compileOptions)=>{
|
|
|
17
18
|
validateAbsolutePaths(sourceDirs, (dir)=>`source dir ${dir} is not an absolute path.`);
|
|
18
19
|
validateAbsolutePath(distDir, `dist dir ${distDir} is not an absolute path.`);
|
|
19
20
|
validateAbsolutePaths(excludeFiles, (file)=>`excluded file ${file} is not an absolute path.`);
|
|
20
|
-
|
|
21
|
-
await
|
|
21
|
+
if (!tsconfigPath) return;
|
|
22
|
+
const { compileServerSources } = await import("../compilers/rslib.mjs");
|
|
23
|
+
try {
|
|
24
|
+
await compileServerSources(appDirectory, modernConfig, {
|
|
25
|
+
...compileOptions,
|
|
26
|
+
tsconfigPath
|
|
27
|
+
});
|
|
28
|
+
} catch (error) {
|
|
29
|
+
if (compileOptions.throwErrorInsteadOfExit) throw error;
|
|
30
|
+
logger.error(error);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
22
33
|
};
|
|
23
34
|
export { FILE_EXTENSIONS, compile };
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import "node:module";
|
|
2
|
+
import { fs, logger, mergeAlias, semver } from "@modern-js/utils";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { createResolvedTsgoConfig, getTsgoBinPath, getTsgoModulePath, runTsgo } from "./tsgo.mjs";
|
|
5
|
+
const SCRIPT_RE = /\.(?:[cm]?[jt]s|[jt]sx)$/u;
|
|
6
|
+
const DECLARATION_RE = /\.d\.[cm]?ts$/u;
|
|
7
|
+
const TS_RE = /\.[cm]?tsx?$/u;
|
|
8
|
+
const toPosix = (file)=>file.split(path.sep).join(path.posix.sep);
|
|
9
|
+
const escapeGlob = (file)=>file.replace(/[()[\]{}*?!+@]/gu, '\\$&');
|
|
10
|
+
const walk = async (dir)=>{
|
|
11
|
+
if (!await fs.pathExists(dir)) return [];
|
|
12
|
+
const entries = await fs.readdir(dir, {
|
|
13
|
+
withFileTypes: true
|
|
14
|
+
});
|
|
15
|
+
const files = await Promise.all(entries.map((entry)=>{
|
|
16
|
+
const file = path.join(dir, entry.name);
|
|
17
|
+
if (entry.isDirectory()) return 'node_modules' === entry.name ? [] : walk(file);
|
|
18
|
+
return [
|
|
19
|
+
file
|
|
20
|
+
];
|
|
21
|
+
}));
|
|
22
|
+
return files.flat();
|
|
23
|
+
};
|
|
24
|
+
const outputExtension = (file)=>/\.m[jt]s$/u.test(file) ? '.mjs' : /\.c[jt]s$/u.test(file) ? '.cjs' : '.js';
|
|
25
|
+
const collectSources = async (sourceDirs, excludeFiles)=>{
|
|
26
|
+
const excluded = new Set(excludeFiles.map((file)=>path.normalize(file)));
|
|
27
|
+
return (await Promise.all(sourceDirs.map(walk))).flat().filter((file)=>SCRIPT_RE.test(file) && !DECLARATION_RE.test(file) && !excluded.has(path.normalize(file)));
|
|
28
|
+
};
|
|
29
|
+
const groupEntries = (appDirectory, files)=>{
|
|
30
|
+
const outputs = new Map();
|
|
31
|
+
const entries = {
|
|
32
|
+
'.js': [],
|
|
33
|
+
'.mjs': [],
|
|
34
|
+
'.cjs': []
|
|
35
|
+
};
|
|
36
|
+
for (const file of files){
|
|
37
|
+
const extension = outputExtension(file);
|
|
38
|
+
const output = `${file.replace(SCRIPT_RE, '')}${extension}`;
|
|
39
|
+
const existing = outputs.get(output);
|
|
40
|
+
if (existing) throw new Error(`"${existing}" and "${file}" both compile to "${toPosix(path.relative(appDirectory, output))}". Rename one of them.`);
|
|
41
|
+
outputs.set(output, file);
|
|
42
|
+
entries[extension].push(escapeGlob(toPosix(path.relative(appDirectory, file))));
|
|
43
|
+
}
|
|
44
|
+
return entries;
|
|
45
|
+
};
|
|
46
|
+
const resolveAlias = (appDirectory, alias)=>Object.fromEntries(Object.entries(mergeAlias(alias)).map(([key, target])=>[
|
|
47
|
+
key,
|
|
48
|
+
[].concat(target).map((value)=>value.startsWith('.') ? path.resolve(appDirectory, value) : value)
|
|
49
|
+
]));
|
|
50
|
+
const copyAssets = async (sourceDir, appDirectory, distDir)=>{
|
|
51
|
+
if (!await fs.pathExists(sourceDir)) return;
|
|
52
|
+
await fs.copy(sourceDir, path.join(distDir, path.relative(appDirectory, sourceDir)), {
|
|
53
|
+
filter: (src)=>!SCRIPT_RE.test(src) && !src.endsWith('tsconfig.json')
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
const matchAlias = (alias, request)=>{
|
|
57
|
+
for (const [key, targets] of Object.entries(alias)){
|
|
58
|
+
const exact = key.endsWith('$');
|
|
59
|
+
const name = exact ? key.slice(0, -1) : key;
|
|
60
|
+
if (request === name || !exact && request.startsWith(`${name}/`)) return {
|
|
61
|
+
targets,
|
|
62
|
+
rest: request.slice(name.length)
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
const isBareSpecifier = (specifier)=>!/^[./]/u.test(specifier) && !path.isAbsolute(specifier);
|
|
67
|
+
const relativeSpecifier = (from, to)=>{
|
|
68
|
+
const relative = toPosix(path.relative(from, to));
|
|
69
|
+
return relative.startsWith('.') ? relative : `./${relative}`;
|
|
70
|
+
};
|
|
71
|
+
const importEmittedFile = ({ appDirectory, distDir, alias, sources, reached, assets })=>async ({ request, context, contextInfo, getResolve })=>{
|
|
72
|
+
if (!request || !context || !contextInfo?.issuer || !getResolve) return;
|
|
73
|
+
const resolve = getResolve();
|
|
74
|
+
const resolved = await resolve(context, request).catch(()=>'');
|
|
75
|
+
if (!resolved) return /^[./]/u.test(request) ? void 0 : request;
|
|
76
|
+
const fromApp = path.relative(appDirectory, resolved);
|
|
77
|
+
if (fromApp.startsWith('..') || path.isAbsolute(fromApp) || resolved.includes(`${path.sep}node_modules${path.sep}`)) {
|
|
78
|
+
const aliased = matchAlias(alias, request);
|
|
79
|
+
if (!aliased) return request;
|
|
80
|
+
for (const target of aliased.targets.filter(isBareSpecifier)){
|
|
81
|
+
const specifier = `${target}${aliased.rest}`;
|
|
82
|
+
if (await resolve(context, specifier).catch(()=>'') === resolved) return specifier;
|
|
83
|
+
}
|
|
84
|
+
const outputDir = path.join(distDir, path.relative(appDirectory, path.dirname(contextInfo.issuer)));
|
|
85
|
+
return relativeSpecifier(outputDir, resolved);
|
|
86
|
+
}
|
|
87
|
+
const script = SCRIPT_RE.test(resolved) && !DECLARATION_RE.test(resolved);
|
|
88
|
+
if (script && !sources.has(resolved)) reached.add(resolved);
|
|
89
|
+
if (!script) assets.add(resolved);
|
|
90
|
+
const emitted = script ? `${resolved.replace(SCRIPT_RE, '')}${outputExtension(resolved)}` : resolved;
|
|
91
|
+
return relativeSpecifier(path.dirname(contextInfo.issuer), emitted);
|
|
92
|
+
};
|
|
93
|
+
const compileServerSources = async (appDirectory, config, options)=>{
|
|
94
|
+
const { sourceDirs, distDir, tsconfigPath, moduleType, excludeFiles = [] } = options;
|
|
95
|
+
const sources = new Set(await collectSources(sourceDirs, excludeFiles));
|
|
96
|
+
const copyAllAssets = async ()=>{
|
|
97
|
+
for (const sourceDir of sourceDirs)await copyAssets(sourceDir, appDirectory, distDir);
|
|
98
|
+
};
|
|
99
|
+
const tsgoBinPath = getTsgoBinPath(appDirectory);
|
|
100
|
+
const { config: tsconfig, resolvedConfigPath } = await createResolvedTsgoConfig(appDirectory, tsconfigPath, sourceDirs, tsgoBinPath, excludeFiles);
|
|
101
|
+
const compilerOptions = tsconfig.compilerOptions ?? {};
|
|
102
|
+
const tsconfigDir = path.dirname(tsconfigPath);
|
|
103
|
+
const roots = new Set((tsconfig.files ?? []).map((file)=>path.resolve(tsconfigDir, file)));
|
|
104
|
+
for (const file of sources){
|
|
105
|
+
const checked = true === compilerOptions.allowJs || TS_RE.test(file);
|
|
106
|
+
if (checked && !roots.has(path.resolve(file))) sources.delete(file);
|
|
107
|
+
}
|
|
108
|
+
const failOnTypeError = false !== compilerOptions.noEmitOnError;
|
|
109
|
+
const declaration = true === compilerOptions.declaration;
|
|
110
|
+
const formats = {
|
|
111
|
+
'.js': 'module' === moduleType ? 'esm' : 'cjs',
|
|
112
|
+
'.mjs': 'esm',
|
|
113
|
+
'.cjs': 'cjs'
|
|
114
|
+
};
|
|
115
|
+
const createLibs = (entries, emitDeclarations)=>{
|
|
116
|
+
const libs = Object.keys(entries).filter((extension)=>entries[extension].length > 0).map((extension)=>({
|
|
117
|
+
format: formats[extension],
|
|
118
|
+
bundle: false,
|
|
119
|
+
autoExtension: false,
|
|
120
|
+
outBase: appDirectory,
|
|
121
|
+
source: {
|
|
122
|
+
entry: {
|
|
123
|
+
index: entries[extension]
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
output: {
|
|
127
|
+
filename: {
|
|
128
|
+
js: `[name]${extension}`
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}));
|
|
132
|
+
if (emitDeclarations) {
|
|
133
|
+
libs[0].redirect = {
|
|
134
|
+
dts: {
|
|
135
|
+
extension: 'module' === moduleType
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
libs[0].dts = {
|
|
139
|
+
bundle: false,
|
|
140
|
+
distPath: distDir,
|
|
141
|
+
tsgo: true,
|
|
142
|
+
typescriptPath: getTsgoModulePath(appDirectory),
|
|
143
|
+
abortOnError: failOnTypeError
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
return libs;
|
|
147
|
+
};
|
|
148
|
+
const alias = resolveAlias(appDirectory, config.alias);
|
|
149
|
+
const assets = new Set();
|
|
150
|
+
const build = async ()=>{
|
|
151
|
+
if (0 === sources.size) return;
|
|
152
|
+
const { createRslib } = await import("@rslib/core");
|
|
153
|
+
const appPackage = await fs.readJSON(path.join(appDirectory, 'package.json')).catch(()=>void 0);
|
|
154
|
+
const nodeEngine = appPackage?.engines?.node;
|
|
155
|
+
const nodeRange = 'string' == typeof nodeEngine ? semver.validRange(nodeEngine) : null;
|
|
156
|
+
const nodeVersion = nodeRange ? semver.minVersion(nodeRange) : null;
|
|
157
|
+
const nodeTarget = nodeVersion?.major ? `node${nodeVersion.major}.${nodeVersion.minor}` : void 0;
|
|
158
|
+
let emitDeclarations = declaration;
|
|
159
|
+
for(;;){
|
|
160
|
+
const reached = new Set();
|
|
161
|
+
const rslib = await createRslib({
|
|
162
|
+
cwd: appDirectory,
|
|
163
|
+
config: {
|
|
164
|
+
lib: createLibs(groupEntries(appDirectory, sources), emitDeclarations),
|
|
165
|
+
source: {
|
|
166
|
+
tsconfigPath: resolvedConfigPath,
|
|
167
|
+
...compilerOptions.experimentalDecorators ? {
|
|
168
|
+
decorators: {
|
|
169
|
+
version: 'legacy'
|
|
170
|
+
}
|
|
171
|
+
} : {}
|
|
172
|
+
},
|
|
173
|
+
resolve: {
|
|
174
|
+
alias
|
|
175
|
+
},
|
|
176
|
+
output: {
|
|
177
|
+
target: 'node',
|
|
178
|
+
distPath: {
|
|
179
|
+
root: distDir
|
|
180
|
+
},
|
|
181
|
+
cleanDistPath: false,
|
|
182
|
+
sourceMap: {
|
|
183
|
+
js: 'source-map'
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
performance: {
|
|
187
|
+
buildCache: false
|
|
188
|
+
},
|
|
189
|
+
tools: {
|
|
190
|
+
rspack: (rspackConfig)=>{
|
|
191
|
+
if (nodeTarget) rspackConfig.target = nodeTarget;
|
|
192
|
+
rspackConfig.externals = [
|
|
193
|
+
importEmittedFile({
|
|
194
|
+
appDirectory,
|
|
195
|
+
distDir,
|
|
196
|
+
alias,
|
|
197
|
+
sources,
|
|
198
|
+
reached,
|
|
199
|
+
assets
|
|
200
|
+
}),
|
|
201
|
+
...[
|
|
202
|
+
rspackConfig.externals ?? []
|
|
203
|
+
].flat()
|
|
204
|
+
];
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
await rslib.build();
|
|
210
|
+
emitDeclarations = false;
|
|
211
|
+
if (0 === reached.size) return;
|
|
212
|
+
for (const file of reached)sources.add(file);
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
const typeCheck = async ()=>{
|
|
216
|
+
if (0 === roots.size) return;
|
|
217
|
+
const result = await runTsgo(tsgoBinPath, [
|
|
218
|
+
'-p',
|
|
219
|
+
resolvedConfigPath,
|
|
220
|
+
'--noEmit'
|
|
221
|
+
], {
|
|
222
|
+
cwd: appDirectory,
|
|
223
|
+
reject: false
|
|
224
|
+
});
|
|
225
|
+
if (0 === result.code) return;
|
|
226
|
+
const diagnostics = result.stdout.trim() || result.stderr.trim();
|
|
227
|
+
if (!failOnTypeError) return void logger.warn(`TS-Go type check found errors:\n${diagnostics}`);
|
|
228
|
+
throw new Error([
|
|
229
|
+
`TS-Go type check failed with exit code ${result.code}.`,
|
|
230
|
+
diagnostics
|
|
231
|
+
].filter(Boolean).join('\n'));
|
|
232
|
+
};
|
|
233
|
+
const checkReachedSources = async ()=>{
|
|
234
|
+
const reachedRoots = [
|
|
235
|
+
...sources
|
|
236
|
+
].filter((file)=>!roots.has(path.resolve(file)) && (true === compilerOptions.allowJs || TS_RE.test(file)));
|
|
237
|
+
if (0 === reachedRoots.length) return typeCheck();
|
|
238
|
+
for (const file of reachedRoots)roots.add(path.resolve(file));
|
|
239
|
+
tsconfig.files = [
|
|
240
|
+
...tsconfig.files ?? [],
|
|
241
|
+
...reachedRoots.map((file)=>toPosix(path.relative(tsconfigDir, file)))
|
|
242
|
+
];
|
|
243
|
+
await fs.writeFile(resolvedConfigPath, JSON.stringify(tsconfig, null, 2));
|
|
244
|
+
return typeCheck();
|
|
245
|
+
};
|
|
246
|
+
let results;
|
|
247
|
+
try {
|
|
248
|
+
const built = await Promise.allSettled([
|
|
249
|
+
build()
|
|
250
|
+
]);
|
|
251
|
+
results = [
|
|
252
|
+
...await Promise.allSettled([
|
|
253
|
+
checkReachedSources()
|
|
254
|
+
]),
|
|
255
|
+
...built
|
|
256
|
+
];
|
|
257
|
+
} finally{
|
|
258
|
+
await fs.remove(resolvedConfigPath);
|
|
259
|
+
}
|
|
260
|
+
const failure = results.find((result)=>'rejected' === result.status);
|
|
261
|
+
if (failure) throw failure.reason;
|
|
262
|
+
await copyAllAssets();
|
|
263
|
+
for (const asset of assets)await fs.copy(asset, path.join(distDir, path.relative(appDirectory, asset)));
|
|
264
|
+
logger.info('Server sources compiled');
|
|
265
|
+
};
|
|
266
|
+
export { compileServerSources };
|