@simplysm/sd-cli 14.0.45 → 14.0.47
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/commands/publish/git-phase.js +1 -1
- package/dist/commands/publish/git-phase.js.map +1 -1
- package/dist/deps/server-externals/server-production-files.d.ts.map +1 -1
- package/dist/deps/server-externals/server-production-files.js +5 -3
- package/dist/deps/server-externals/server-production-files.js.map +1 -1
- package/dist/esbuild/esbuild-angular-compiler-plugin.d.ts.map +1 -1
- package/dist/esbuild/esbuild-angular-compiler-plugin.js +6 -2
- package/dist/esbuild/esbuild-angular-compiler-plugin.js.map +1 -1
- package/dist/esbuild/esbuild-config.d.ts.map +1 -1
- package/dist/esbuild/esbuild-config.js +2 -5
- package/dist/esbuild/esbuild-config.js.map +1 -1
- package/dist/esbuild/esbuild-worker-plugin.d.ts +36 -2
- package/dist/esbuild/esbuild-worker-plugin.d.ts.map +1 -1
- package/dist/esbuild/esbuild-worker-plugin.js +171 -41
- package/dist/esbuild/esbuild-worker-plugin.js.map +1 -1
- package/dist/utils/output-path-rewriter.d.ts +5 -2
- package/dist/utils/output-path-rewriter.d.ts.map +1 -1
- package/dist/utils/output-path-rewriter.js +60 -12
- package/dist/utils/output-path-rewriter.js.map +1 -1
- package/package.json +10 -8
- package/src/commands/publish/git-phase.ts +1 -1
- package/src/deps/server-externals/server-production-files.ts +5 -3
- package/src/esbuild/esbuild-angular-compiler-plugin.ts +6 -2
- package/src/esbuild/esbuild-config.ts +2 -7
- package/src/esbuild/esbuild-worker-plugin.ts +229 -56
- package/src/utils/output-path-rewriter.ts +65 -17
- package/tests/deps/server-externals/mise-toml-parse-intent.verify.md +16 -0
- package/tests/esbuild/esbuild-worker-plugin.spec.ts +547 -1
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ESM 출력에서 확장자가 없는 상대 import/export 경로에 .js 확장자를 추가한다.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* es-module-lexer를 사용하여 import/export specifier 위치를 정확히 파악한다.
|
|
5
|
+
* 주석, 문자열 리터럴 내부의 패턴은 무시된다.
|
|
6
|
+
*
|
|
7
|
+
* 매칭: from "./foo", import("./bar"), from "../baz", export { x } from "./qux"
|
|
5
8
|
* 스킵: bare 지정자("lodash"), 이미 알려진 확장자로 끝나는 경로 (.js, .json, .css 등)
|
|
6
9
|
*/
|
|
7
10
|
export declare function addJsExtensionToImports(text: string): string;
|
|
@@ -9,7 +12,7 @@ export declare function addJsExtensionToImports(text: string): string;
|
|
|
9
12
|
* emit된 JS 텍스트에서 상대 .scss import를 .css로 변환한다.
|
|
10
13
|
* 변환된 텍스트와 원본 .scss import 경로 목록을 반환한다.
|
|
11
14
|
*
|
|
12
|
-
*
|
|
15
|
+
* es-module-lexer를 사용하여 import specifier 위치를 정확히 파악한다.
|
|
13
16
|
* 상대 import(./ 또는 ../ 시작)만 처리한다.
|
|
14
17
|
*/
|
|
15
18
|
export declare function rewriteScssImports(text: string): {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"output-path-rewriter.d.ts","sourceRoot":"","sources":["../../src/utils/output-path-rewriter.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"output-path-rewriter.d.ts","sourceRoot":"","sources":["../../src/utils/output-path-rewriter.ts"],"names":[],"mappings":"AAqBA;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAmB5D;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,EAAE,CAAA;CAAE,CA0BxF;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAc7F;AAED;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,GACb,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CA+BhE"}
|
|
@@ -1,32 +1,80 @@
|
|
|
1
1
|
import path from "path";
|
|
2
2
|
import { pathx } from "@simplysm/core-node";
|
|
3
|
+
import { init, parse } from "es-module-lexer";
|
|
4
|
+
await init;
|
|
5
|
+
const KNOWN_JS_EXTENSIONS = /\.(js|mjs|cjs|json|css|scss|wasm|node)$/i;
|
|
6
|
+
/**
|
|
7
|
+
* es-module-lexer의 import에서 specifier 내용의 시작/끝 위치를 반환한다.
|
|
8
|
+
*
|
|
9
|
+
* static import (d === -1): s..e가 따옴표 없는 specifier 내용
|
|
10
|
+
* dynamic import (d >= 0): s..e가 따옴표를 포함한 문자열 리터럴
|
|
11
|
+
*/
|
|
12
|
+
function getSpecifierRange(imp) {
|
|
13
|
+
if (imp.d >= 0) {
|
|
14
|
+
return [imp.s + 1, imp.e - 1];
|
|
15
|
+
}
|
|
16
|
+
return [imp.s, imp.e];
|
|
17
|
+
}
|
|
3
18
|
/**
|
|
4
19
|
* ESM 출력에서 확장자가 없는 상대 import/export 경로에 .js 확장자를 추가한다.
|
|
5
20
|
*
|
|
6
|
-
*
|
|
21
|
+
* es-module-lexer를 사용하여 import/export specifier 위치를 정확히 파악한다.
|
|
22
|
+
* 주석, 문자열 리터럴 내부의 패턴은 무시된다.
|
|
23
|
+
*
|
|
24
|
+
* 매칭: from "./foo", import("./bar"), from "../baz", export { x } from "./qux"
|
|
7
25
|
* 스킵: bare 지정자("lodash"), 이미 알려진 확장자로 끝나는 경로 (.js, .json, .css 등)
|
|
8
26
|
*/
|
|
9
27
|
export function addJsExtensionToImports(text) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
28
|
+
const [imports] = parse(text);
|
|
29
|
+
if (imports.length === 0)
|
|
30
|
+
return text;
|
|
31
|
+
// 역순으로 치환하여 위치 밀림 방지
|
|
32
|
+
const sorted = [...imports].sort((a, b) => b.s - a.s);
|
|
33
|
+
let result = text;
|
|
34
|
+
for (const imp of sorted) {
|
|
35
|
+
const specifier = imp.n;
|
|
36
|
+
if (specifier == null)
|
|
37
|
+
continue;
|
|
38
|
+
if (!specifier.startsWith("./") && !specifier.startsWith("../"))
|
|
39
|
+
continue;
|
|
40
|
+
if (KNOWN_JS_EXTENSIONS.test(specifier))
|
|
41
|
+
continue;
|
|
42
|
+
const [start, end] = getSpecifierRange(imp);
|
|
43
|
+
result = result.slice(0, start) + specifier + ".js" + result.slice(end);
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
15
46
|
}
|
|
16
47
|
/**
|
|
17
48
|
* emit된 JS 텍스트에서 상대 .scss import를 .css로 변환한다.
|
|
18
49
|
* 변환된 텍스트와 원본 .scss import 경로 목록을 반환한다.
|
|
19
50
|
*
|
|
20
|
-
*
|
|
51
|
+
* es-module-lexer를 사용하여 import specifier 위치를 정확히 파악한다.
|
|
21
52
|
* 상대 import(./ 또는 ../ 시작)만 처리한다.
|
|
22
53
|
*/
|
|
23
54
|
export function rewriteScssImports(text) {
|
|
55
|
+
const [imports] = parse(text);
|
|
56
|
+
if (imports.length === 0)
|
|
57
|
+
return { text, scssImports: [] };
|
|
24
58
|
const scssImports = [];
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
59
|
+
// 역순으로 치환하여 위치 밀림 방지
|
|
60
|
+
const sorted = [...imports].sort((a, b) => b.s - a.s);
|
|
61
|
+
let result = text;
|
|
62
|
+
for (const imp of sorted) {
|
|
63
|
+
const specifier = imp.n;
|
|
64
|
+
if (specifier == null)
|
|
65
|
+
continue;
|
|
66
|
+
if (!specifier.startsWith("./") && !specifier.startsWith("../"))
|
|
67
|
+
continue;
|
|
68
|
+
if (!specifier.endsWith(".scss"))
|
|
69
|
+
continue;
|
|
70
|
+
scssImports.push(specifier);
|
|
71
|
+
const newSpec = specifier.slice(0, -5) + ".css";
|
|
72
|
+
const [start, end] = getSpecifierRange(imp);
|
|
73
|
+
result = result.slice(0, start) + newSpec + result.slice(end);
|
|
74
|
+
}
|
|
75
|
+
// 역순으로 수집되었으므로 원래 순서로 복원
|
|
76
|
+
scssImports.reverse();
|
|
77
|
+
return { text: result, scssImports };
|
|
30
78
|
}
|
|
31
79
|
/**
|
|
32
80
|
* .d.ts.map 파일의 sources 경로를 새 위치에 맞게 조정한다.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"output-path-rewriter.js","sourceRoot":"","sources":["../../src/utils/output-path-rewriter.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"output-path-rewriter.js","sourceRoot":"","sources":["../../src/utils/output-path-rewriter.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simplysm/sd-cli",
|
|
3
|
-
"version": "14.0.
|
|
3
|
+
"version": "14.0.47",
|
|
4
4
|
"description": "Simplysm package - CLI tool",
|
|
5
5
|
"author": "simplysm",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -20,31 +20,33 @@
|
|
|
20
20
|
"sideEffects": false,
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@angular/build": "^21.2.7",
|
|
23
|
-
"@angular/compiler-cli": "^21.2.
|
|
24
|
-
"@fastify/http-proxy": "^11.4.
|
|
23
|
+
"@angular/compiler-cli": "^21.2.9",
|
|
24
|
+
"@fastify/http-proxy": "^11.4.4",
|
|
25
25
|
"@inquirer/prompts": "^8.4.1",
|
|
26
26
|
"acorn": "^8.16.0",
|
|
27
27
|
"acorn-walk": "^8.3.5",
|
|
28
28
|
"browserslist-to-esbuild": "^2.1.1",
|
|
29
29
|
"consola": "^3.4.2",
|
|
30
|
+
"es-module-lexer": "^2.0.0",
|
|
30
31
|
"esbuild": "^0.28.0",
|
|
31
32
|
"eslint": "^9.39.4",
|
|
32
33
|
"glob": "^13.0.6",
|
|
33
34
|
"jiti": "^2.6.1",
|
|
34
|
-
"lmdb": "^3.5.
|
|
35
|
+
"lmdb": "^3.5.4",
|
|
35
36
|
"mime": "^4.1.0",
|
|
36
|
-
"postcss": "^8.5.
|
|
37
|
+
"postcss": "^8.5.10",
|
|
37
38
|
"sass": "^1.99.0",
|
|
38
39
|
"semver": "^7.7.4",
|
|
39
40
|
"sharp": "^0.34.5",
|
|
41
|
+
"smol-toml": "^1.6.1",
|
|
40
42
|
"ssh2": "^1.17.0",
|
|
41
43
|
"typescript": "^5.9.3",
|
|
42
44
|
"ws": "^8.20.0",
|
|
43
45
|
"yaml": "^2.8.3",
|
|
44
46
|
"yargs": "^18.0.0",
|
|
45
|
-
"@simplysm/core-
|
|
46
|
-
"@simplysm/core-
|
|
47
|
-
"@simplysm/storage": "14.0.
|
|
47
|
+
"@simplysm/core-common": "14.0.47",
|
|
48
|
+
"@simplysm/core-node": "14.0.47",
|
|
49
|
+
"@simplysm/storage": "14.0.47"
|
|
48
50
|
},
|
|
49
51
|
"devDependencies": {
|
|
50
52
|
"@types/semver": "^7.7.1",
|
|
@@ -2,6 +2,7 @@ import type { ServerBuildInfo } from "../../workers/server-build.worker";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
import fs from "fs";
|
|
4
4
|
import YAML from "yaml";
|
|
5
|
+
import TOML from "smol-toml";
|
|
5
6
|
import { cpx } from "@simplysm/core-node";
|
|
6
7
|
import { consola } from "consola";
|
|
7
8
|
import { collectAllDependencyExternals } from "../../esbuild/esbuild-config";
|
|
@@ -110,9 +111,10 @@ export function generateProductionFiles(
|
|
|
110
111
|
let nodeVersion = "20";
|
|
111
112
|
if (fs.existsSync(rootMiseTomlPath)) {
|
|
112
113
|
const miseContent = fs.readFileSync(rootMiseTomlPath, "utf-8");
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
// mise.toml은 저장소에서 관리되는 설정 파일이므로, 파싱 실패 시 폴백하지 않고 예외를 전파하여 설정 오류를 즉시 드러낸다.
|
|
115
|
+
const miseConfig = TOML.parse(miseContent) as { tools?: { node?: string } };
|
|
116
|
+
if (miseConfig.tools?.node != null) {
|
|
117
|
+
nodeVersion = miseConfig.tools.node;
|
|
116
118
|
}
|
|
117
119
|
}
|
|
118
120
|
fs.writeFileSync(path.join(distDir, "mise.toml"), `[tools]\nnode = "${nodeVersion}"\n`);
|
|
@@ -340,7 +340,11 @@ export function createAngularCompilerPlugin(
|
|
|
340
340
|
// ── emitResults → typeScriptFileCache (Worker 패턴 처리 포함) ──
|
|
341
341
|
for (const { contents, sourceFileName } of compileResult.emitResults ?? []) {
|
|
342
342
|
const normalized = path.normalize(sourceFileName);
|
|
343
|
-
|
|
343
|
+
// emitResults.contents는 ngtsc가 이미 JS로 방출한 결과이므로 TS 재변환 스킵.
|
|
344
|
+
// sourceFileName은 원본 .ts 경로이지만 content는 JS이다.
|
|
345
|
+
const workerResult = transformWorkerPatterns(contents, normalized, build, {
|
|
346
|
+
skipTsTransform: true,
|
|
347
|
+
});
|
|
344
348
|
if (workerResult != null) {
|
|
345
349
|
typeScriptFileCache.set(normalized, workerResult.contents);
|
|
346
350
|
errors.push(...workerResult.errors);
|
|
@@ -541,7 +545,7 @@ export function createAngularCompilerPlugin(
|
|
|
541
545
|
sideEffects,
|
|
542
546
|
);
|
|
543
547
|
|
|
544
|
-
// Worker 패턴 처리
|
|
548
|
+
// Worker 패턴 처리
|
|
545
549
|
const textContents = new TextDecoder().decode(contents);
|
|
546
550
|
const workerResult = transformWorkerPatterns(textContents, request, build);
|
|
547
551
|
if (workerResult != null) {
|
|
@@ -4,6 +4,7 @@ import fs from "fs/promises";
|
|
|
4
4
|
import { createRequire } from "module";
|
|
5
5
|
import type esbuild from "esbuild";
|
|
6
6
|
import { consola } from "consola";
|
|
7
|
+
import { addJsExtensionToImports } from "../utils/output-path-rewriter";
|
|
7
8
|
|
|
8
9
|
const logger = consola.withTag("sd:cli:esbuild-config");
|
|
9
10
|
|
|
@@ -19,13 +20,7 @@ export async function writeChangedOutputFiles(outputFiles: esbuild.OutputFile[])
|
|
|
19
20
|
await Promise.all(
|
|
20
21
|
outputFiles.map(async (file) => {
|
|
21
22
|
const finalText = file.path.endsWith(".js")
|
|
22
|
-
? file.text
|
|
23
|
-
/((?:from|import)\s*["'])(\.\.?\/[^"']*?)(["'])/g,
|
|
24
|
-
(_match, prefix: string, importPath: string, suffix: string) => {
|
|
25
|
-
if (/\.(js|mjs|cjs|json|css|wasm|node)$/i.test(importPath)) return _match;
|
|
26
|
-
return `${prefix}${importPath}.js${suffix}`;
|
|
27
|
-
},
|
|
28
|
-
)
|
|
23
|
+
? addJsExtensionToImports(file.text)
|
|
29
24
|
: file.text;
|
|
30
25
|
|
|
31
26
|
try {
|
|
@@ -1,27 +1,146 @@
|
|
|
1
1
|
import path from "path";
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
import type esbuild from "esbuild";
|
|
4
|
+
import * as acorn from "acorn";
|
|
5
|
+
import * as walk from "acorn-walk";
|
|
6
|
+
|
|
7
|
+
//#region AST 기반 Worker 패턴 탐지
|
|
4
8
|
|
|
5
9
|
/**
|
|
6
|
-
*
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
* AST에서 탐지된 Worker 패턴 하나를 나타낸다.
|
|
11
|
+
*/
|
|
12
|
+
export interface WorkerMatch {
|
|
13
|
+
type: "browser" | "node";
|
|
14
|
+
/** 전체 표현식의 start/end (치환 범위) */
|
|
15
|
+
start: number;
|
|
16
|
+
end: number;
|
|
17
|
+
/** Worker/SharedWorker 이름 (browser만) */
|
|
18
|
+
workerType?: string;
|
|
19
|
+
/** URL 경로 문자열 값 */
|
|
20
|
+
urlPath: string;
|
|
21
|
+
/** 옵션 객체의 원본 소스 텍스트 (browser만, 없으면 undefined) */
|
|
22
|
+
existingOpts?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* MemberExpression이 import.meta.url인지 확인한다.
|
|
12
27
|
*/
|
|
13
|
-
|
|
14
|
-
|
|
28
|
+
function isImportMetaUrl(node: any): boolean {
|
|
29
|
+
return (
|
|
30
|
+
node.type === "MemberExpression" &&
|
|
31
|
+
node.object.type === "MetaProperty" &&
|
|
32
|
+
node.object.meta.name === "import" &&
|
|
33
|
+
node.object.property.name === "meta" &&
|
|
34
|
+
node.property.type === "Identifier" &&
|
|
35
|
+
node.property.name === "url"
|
|
36
|
+
);
|
|
37
|
+
}
|
|
15
38
|
|
|
16
39
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
40
|
+
* acorn AST를 사용하여 소스 코드에서 Worker/SharedWorker 및 import.meta.resolve 패턴을 탐지한다.
|
|
41
|
+
*
|
|
42
|
+
* 정규식과 달리 주석, 문자열 리터럴 내부의 패턴을 오탐하지 않는다.
|
|
43
|
+
* 파싱 실패 시 빈 배열을 반환한다.
|
|
19
44
|
*
|
|
20
|
-
*
|
|
21
|
-
* - Group 1: 상대 경로 (예: `"../workers/service-protocol.worker"`)
|
|
45
|
+
* @param content - JavaScript 소스 코드. TypeScript는 상위 `transformWorkerPatterns()`가 사전 변환한다.
|
|
22
46
|
*/
|
|
23
|
-
|
|
24
|
-
|
|
47
|
+
export function findWorkerPatterns(content: string): WorkerMatch[] {
|
|
48
|
+
let ast: acorn.Node;
|
|
49
|
+
try {
|
|
50
|
+
ast = acorn.parse(content, {
|
|
51
|
+
ecmaVersion: "latest",
|
|
52
|
+
sourceType: "module",
|
|
53
|
+
});
|
|
54
|
+
} catch {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const matches: WorkerMatch[] = [];
|
|
59
|
+
|
|
60
|
+
walk.simple(ast, {
|
|
61
|
+
NewExpression(node: any) {
|
|
62
|
+
// new Worker(new URL("path", import.meta.url), opts?)
|
|
63
|
+
// new SharedWorker(new URL("path", import.meta.url), opts?)
|
|
64
|
+
if (
|
|
65
|
+
node.callee.type !== "Identifier" ||
|
|
66
|
+
(node.callee.name !== "Worker" && node.callee.name !== "SharedWorker")
|
|
67
|
+
) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const args = node.arguments;
|
|
72
|
+
if (args.length < 1) return;
|
|
73
|
+
|
|
74
|
+
const urlArg = args[0];
|
|
75
|
+
if (
|
|
76
|
+
urlArg.type !== "NewExpression" ||
|
|
77
|
+
urlArg.callee.type !== "Identifier" ||
|
|
78
|
+
urlArg.callee.name !== "URL"
|
|
79
|
+
) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const urlArgs = urlArg.arguments;
|
|
84
|
+
if (urlArgs.length < 2) return;
|
|
85
|
+
|
|
86
|
+
// 첫 번째 인자: 문자열 리터럴 (경로)
|
|
87
|
+
if (urlArgs[0].type !== "Literal" || typeof urlArgs[0].value !== "string") return;
|
|
88
|
+
|
|
89
|
+
// 두 번째 인자: import.meta.url
|
|
90
|
+
if (!isImportMetaUrl(urlArgs[1])) return;
|
|
91
|
+
|
|
92
|
+
const match: WorkerMatch = {
|
|
93
|
+
type: "browser",
|
|
94
|
+
start: node.start,
|
|
95
|
+
end: node.end,
|
|
96
|
+
workerType: node.callee.name,
|
|
97
|
+
urlPath: urlArgs[0].value,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// 옵션 객체 (두 번째 인자)
|
|
101
|
+
if (args.length >= 2) {
|
|
102
|
+
match.existingOpts = content.slice(args[1].start, args[1].end);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
matches.push(match);
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
CallExpression(node: any) {
|
|
109
|
+
// import.meta.resolve("./relative-path")
|
|
110
|
+
const callee = node.callee;
|
|
111
|
+
if (
|
|
112
|
+
callee.type !== "MemberExpression" ||
|
|
113
|
+
callee.object.type !== "MetaProperty" ||
|
|
114
|
+
callee.object.meta.name !== "import" ||
|
|
115
|
+
callee.object.property.name !== "meta" ||
|
|
116
|
+
callee.property.type !== "Identifier" ||
|
|
117
|
+
callee.property.name !== "resolve"
|
|
118
|
+
) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const args = node.arguments;
|
|
123
|
+
if (args.length < 1) return;
|
|
124
|
+
|
|
125
|
+
if (args[0].type !== "Literal" || typeof args[0].value !== "string") return;
|
|
126
|
+
|
|
127
|
+
const urlPath = args[0].value as string;
|
|
128
|
+
// 상대 경로만 처리
|
|
129
|
+
if (!urlPath.startsWith("./") && !urlPath.startsWith("../")) return;
|
|
130
|
+
|
|
131
|
+
matches.push({
|
|
132
|
+
type: "node",
|
|
133
|
+
start: node.start,
|
|
134
|
+
end: node.end,
|
|
135
|
+
urlPath,
|
|
136
|
+
});
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
return matches.sort((a, b) => a.start - b.start);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
//#endregion
|
|
25
144
|
|
|
26
145
|
/**
|
|
27
146
|
* Worker 번들 빌드 결과를 포함하는 transform 결과.
|
|
@@ -36,6 +155,17 @@ export interface TransformWorkerResult {
|
|
|
36
155
|
workerMetafile?: esbuild.Metafile;
|
|
37
156
|
}
|
|
38
157
|
|
|
158
|
+
/**
|
|
159
|
+
* transformWorkerPatterns 옵션.
|
|
160
|
+
*/
|
|
161
|
+
export interface TransformWorkerOptions {
|
|
162
|
+
/**
|
|
163
|
+
* true이면 .ts/.cts/.mts 확장자여도 esbuild.transformSync(loader: "ts")를 스킵한다.
|
|
164
|
+
* 호출자가 content가 이미 JS임을 보장하는 경우에만 사용한다 (예: ngtsc emit 결과).
|
|
165
|
+
*/
|
|
166
|
+
skipTsTransform?: boolean;
|
|
167
|
+
}
|
|
168
|
+
|
|
39
169
|
/**
|
|
40
170
|
* Worker 파일을 esbuild.buildSync()로 별도 ESM 번들로 빌드한다.
|
|
41
171
|
* esbuild-angular-compiler-plugin.ts의 bundleWebWorker를 기반으로 작성.
|
|
@@ -85,7 +215,7 @@ function bundleWorker(
|
|
|
85
215
|
* 파일 내용에서 Worker/SharedWorker 패턴을 감지하여 Worker 파일을 번들링하고
|
|
86
216
|
* URL 경로를 번들된 파일 경로로 치환한다.
|
|
87
217
|
*
|
|
88
|
-
* Angular 플러그인 등 외부에서 직접
|
|
218
|
+
* Angular 컴파일러 플러그인 등 외부에서 직접 호출한다.
|
|
89
219
|
*
|
|
90
220
|
* @returns 변환 결과. 패턴이 없으면 undefined.
|
|
91
221
|
*/
|
|
@@ -93,21 +223,54 @@ export function transformWorkerPatterns(
|
|
|
93
223
|
content: string,
|
|
94
224
|
filePath: string,
|
|
95
225
|
build: esbuild.PluginBuild,
|
|
226
|
+
options?: TransformWorkerOptions,
|
|
96
227
|
): TransformWorkerResult | undefined {
|
|
97
|
-
// 빠른 사전 필터
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (hasNodeWorker) NODE_WORKER_PATTERN.lastIndex = 0;
|
|
104
|
-
|
|
105
|
-
if (!hasBrowserWorker && !hasNodeWorker) {
|
|
228
|
+
// 빠른 사전 필터 — new Worker(), new SharedWorker(), import.meta.resolve()의 단어 경계
|
|
229
|
+
// 호출 형태만 통과시킨다. 식별자·타입·interface로만 등장하는 Worker 키워드는 차단하여
|
|
230
|
+
// TS transformSync 오버헤드를 줄인다. 정확성은 후속 AST 판별(findWorkerPatterns)이 담당한다.
|
|
231
|
+
// new 와 Worker/SharedWorker 사이에 주석이 낀 호출(예: `new /* c */ Worker`)은 의도된
|
|
232
|
+
// 트레이드오프로 탈락한다. 실발생 가능성이 희박하여 현행을 유지한다.
|
|
233
|
+
if (!/\b(new\s+Worker|new\s+SharedWorker|import\.meta\.resolve)\b/.test(content)) {
|
|
106
234
|
return undefined;
|
|
107
235
|
}
|
|
108
236
|
|
|
109
237
|
const errors: esbuild.PartialMessage[] = [];
|
|
110
238
|
const warnings: esbuild.PartialMessage[] = [];
|
|
239
|
+
|
|
240
|
+
// TS(.ts/.cts/.mts)는 JS로 변환한 후 AST 파싱. acorn은 TS 구문을 처리하지 못하므로
|
|
241
|
+
// import type, 타입 어노테이션 등이 있으면 파싱 실패로 Worker 패턴이 조용히 누락된다.
|
|
242
|
+
// skipTsTransform이 true이면 호출자가 content가 이미 JS임을 보장하므로 변환을 스킵한다
|
|
243
|
+
// (예: Angular 컴파일러 플러그인이 ngtsc emit 결과를 전달하는 경로).
|
|
244
|
+
let effectiveContent = content;
|
|
245
|
+
if (options?.skipTsTransform !== true && /\.[cm]?ts$/.test(filePath)) {
|
|
246
|
+
try {
|
|
247
|
+
const transformed = build.esbuild.transformSync(content, {
|
|
248
|
+
loader: "ts",
|
|
249
|
+
sourcemap: false,
|
|
250
|
+
});
|
|
251
|
+
effectiveContent = transformed.code;
|
|
252
|
+
warnings.push(...transformed.warnings);
|
|
253
|
+
} catch (e) {
|
|
254
|
+
const failure = e as {
|
|
255
|
+
errors?: esbuild.PartialMessage[];
|
|
256
|
+
warnings?: esbuild.PartialMessage[];
|
|
257
|
+
};
|
|
258
|
+
return {
|
|
259
|
+
contents: content,
|
|
260
|
+
errors: failure.errors ?? [
|
|
261
|
+
{ text: `TS transform failed: ${String(e)}`, location: null },
|
|
262
|
+
],
|
|
263
|
+
warnings: failure.warnings ?? [],
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// AST 기반 패턴 탐지 (변환된 JS 기준)
|
|
269
|
+
const matches = findWorkerPatterns(effectiveContent);
|
|
270
|
+
if (matches.length === 0) {
|
|
271
|
+
return undefined;
|
|
272
|
+
}
|
|
273
|
+
|
|
111
274
|
const allOutputFiles: esbuild.OutputFile[] = [];
|
|
112
275
|
let mergedMetafile: esbuild.Metafile | undefined;
|
|
113
276
|
|
|
@@ -170,42 +333,49 @@ export function transformWorkerPatterns(
|
|
|
170
333
|
return path.relative(outdir, workerCodeFile.path).replaceAll("\\", "/");
|
|
171
334
|
}
|
|
172
335
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
336
|
+
// 정방향 chunks 패턴으로 치환 (esbuild-postcss-plugin.ts 동일 패턴)
|
|
337
|
+
const replacements: Array<{ start: number; end: number; text: string }> = [];
|
|
338
|
+
|
|
339
|
+
for (const match of matches) {
|
|
340
|
+
if (match.type === "browser") {
|
|
341
|
+
const fullWorkerPath = path.resolve(containingDir, match.urlPath);
|
|
342
|
+
const workerCodePath = processWorkerBundle(fullWorkerPath, "browser");
|
|
343
|
+
if (workerCodePath == null) continue;
|
|
344
|
+
|
|
345
|
+
const optsStr = match.existingOpts ?? '{ type: "module" }';
|
|
346
|
+
replacements.push({
|
|
347
|
+
start: match.start,
|
|
348
|
+
end: match.end,
|
|
349
|
+
text: `new ${match.workerType}(new URL("${workerCodePath}", import.meta.url), ${optsStr})`,
|
|
350
|
+
});
|
|
351
|
+
} else {
|
|
352
|
+
const fullWorkerPath = path.resolve(containingDir, match.urlPath);
|
|
353
|
+
const workerCodePath = processWorkerBundle(
|
|
354
|
+
fullWorkerPath,
|
|
355
|
+
build.initialOptions.platform ?? "browser",
|
|
356
|
+
);
|
|
357
|
+
if (workerCodePath == null) continue;
|
|
358
|
+
|
|
359
|
+
replacements.push({
|
|
360
|
+
start: match.start,
|
|
361
|
+
end: match.end,
|
|
362
|
+
text: `new URL("${workerCodePath}", import.meta.url).href`,
|
|
363
|
+
});
|
|
364
|
+
}
|
|
188
365
|
}
|
|
189
366
|
|
|
190
|
-
//
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
fullWorkerPath,
|
|
198
|
-
build.initialOptions.platform ?? "browser",
|
|
199
|
-
);
|
|
200
|
-
if (workerCodePath == null) return match;
|
|
201
|
-
|
|
202
|
-
return `new URL("${workerCodePath}", import.meta.url).href`;
|
|
203
|
-
},
|
|
204
|
-
);
|
|
367
|
+
// chunks 조립 (변환된 JS 기준 — 매치의 start/end는 effectiveContent 오프셋)
|
|
368
|
+
const chunks: string[] = [];
|
|
369
|
+
let cursor = 0;
|
|
370
|
+
for (const rep of replacements) {
|
|
371
|
+
chunks.push(effectiveContent.slice(cursor, rep.start));
|
|
372
|
+
chunks.push(rep.text);
|
|
373
|
+
cursor = rep.end;
|
|
205
374
|
}
|
|
375
|
+
chunks.push(effectiveContent.slice(cursor));
|
|
206
376
|
|
|
207
377
|
return {
|
|
208
|
-
contents:
|
|
378
|
+
contents: chunks.join(""),
|
|
209
379
|
errors,
|
|
210
380
|
warnings,
|
|
211
381
|
workerOutputFiles: allOutputFiles.length > 0 ? allOutputFiles : undefined,
|
|
@@ -241,9 +411,12 @@ export function createWorkerBundlePlugin(): esbuild.Plugin {
|
|
|
241
411
|
});
|
|
242
412
|
}
|
|
243
413
|
|
|
414
|
+
// TS(.ts/.cts/.mts)는 transformWorkerPatterns 내부에서 JS로 변환되어 반환되므로
|
|
415
|
+
// loader는 "js". .tsx/.jsx는 변환하지 않으므로 esbuild가 JSX를 처리하도록 "tsx".
|
|
416
|
+
const isJsx = /\.[cm]?tsx$/.test(args.path) || args.path.endsWith(".jsx");
|
|
244
417
|
return {
|
|
245
418
|
contents: result.contents,
|
|
246
|
-
loader:
|
|
419
|
+
loader: isJsx ? ("tsx" as const) : ("js" as const),
|
|
247
420
|
errors: result.errors.length > 0 ? result.errors : undefined,
|
|
248
421
|
warnings: result.warnings.length > 0 ? result.warnings : undefined,
|
|
249
422
|
};
|