@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.
Files changed (28) hide show
  1. package/dist/commands/publish/git-phase.js +1 -1
  2. package/dist/commands/publish/git-phase.js.map +1 -1
  3. package/dist/deps/server-externals/server-production-files.d.ts.map +1 -1
  4. package/dist/deps/server-externals/server-production-files.js +5 -3
  5. package/dist/deps/server-externals/server-production-files.js.map +1 -1
  6. package/dist/esbuild/esbuild-angular-compiler-plugin.d.ts.map +1 -1
  7. package/dist/esbuild/esbuild-angular-compiler-plugin.js +6 -2
  8. package/dist/esbuild/esbuild-angular-compiler-plugin.js.map +1 -1
  9. package/dist/esbuild/esbuild-config.d.ts.map +1 -1
  10. package/dist/esbuild/esbuild-config.js +2 -5
  11. package/dist/esbuild/esbuild-config.js.map +1 -1
  12. package/dist/esbuild/esbuild-worker-plugin.d.ts +36 -2
  13. package/dist/esbuild/esbuild-worker-plugin.d.ts.map +1 -1
  14. package/dist/esbuild/esbuild-worker-plugin.js +171 -41
  15. package/dist/esbuild/esbuild-worker-plugin.js.map +1 -1
  16. package/dist/utils/output-path-rewriter.d.ts +5 -2
  17. package/dist/utils/output-path-rewriter.d.ts.map +1 -1
  18. package/dist/utils/output-path-rewriter.js +60 -12
  19. package/dist/utils/output-path-rewriter.js.map +1 -1
  20. package/package.json +10 -8
  21. package/src/commands/publish/git-phase.ts +1 -1
  22. package/src/deps/server-externals/server-production-files.ts +5 -3
  23. package/src/esbuild/esbuild-angular-compiler-plugin.ts +6 -2
  24. package/src/esbuild/esbuild-config.ts +2 -7
  25. package/src/esbuild/esbuild-worker-plugin.ts +229 -56
  26. package/src/utils/output-path-rewriter.ts +65 -17
  27. package/tests/deps/server-externals/mise-toml-parse-intent.verify.md +16 -0
  28. 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
- * 매칭: from "./foo", import("./bar"), from "../baz"
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
- * 매칭: import "./foo.scss", import("./bar.scss"), from "../baz.scss"
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":"AAGA;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQ5D;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,EAAE,CAAA;CAAE,CAUxF;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
+ {"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
- * 매칭: from "./foo.js", import("./bar.js"), from "../baz.js"
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
- return text.replace(/((?:from|import)\s*(?:\(\s*)?["'])(\.\.?\/[^"']*?)(["'](?:\s*\))?)/g, (_match, prefix, importPath, suffix) => {
11
- if (/\.(js|mjs|cjs|json|css|scss|wasm|node)$/i.test(importPath))
12
- return _match;
13
- return `${prefix}${importPath}.js${suffix}`;
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
- * 매칭: import "./foo.scss", import("./bar.scss"), from "../baz.scss"
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
- const rewritten = text.replace(/((?:from|import)\s*(?:\(\s*)?["'])(\.\.?\/[^"']*?)(\.scss)(["'](?:\s*\))?)/g, (_match, prefix, importBase, _ext, suffix) => {
26
- scssImports.push(`${importBase}.scss`);
27
- return `${prefix}${importBase}.css${suffix}`;
28
- });
29
- return { text: rewritten, scssImports };
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;AAE5C;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,IAAY;IAClD,OAAO,IAAI,CAAC,OAAO,CACjB,qEAAqE,EACrE,CAAC,MAAM,EAAE,MAAc,EAAE,UAAkB,EAAE,MAAc,EAAE,EAAE;QAC7D,IAAI,0CAA0C,CAAC,IAAI,CAAC,UAAU,CAAC;YAAE,OAAO,MAAM,CAAC;QAC/E,OAAO,GAAG,MAAM,GAAG,UAAU,MAAM,MAAM,EAAE,CAAC;IAC9C,CAAC,CACF,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,MAAM,WAAW,GAAa,EAAE,CAAC;IACjC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAC5B,6EAA6E,EAC7E,CAAC,MAAM,EAAE,MAAc,EAAE,UAAkB,EAAE,IAAY,EAAE,MAAc,EAAE,EAAE;QAC3E,WAAW,CAAC,IAAI,CAAC,GAAG,UAAU,OAAO,CAAC,CAAC;QACvC,OAAO,GAAG,MAAM,GAAG,UAAU,OAAO,MAAM,EAAE,CAAC;IAC/C,CAAC,CACF,CAAC;IACF,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC;AAC1C,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAe,EAAE,WAAmB,EAAE,MAAc;IACnF,IAAI,WAAW,KAAK,MAAM;QAAE,OAAO,OAAO,CAAC;IAC3C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAA2B,CAAC;QAC1D,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YAC/B,GAAG,CAAC,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;gBACvC,MAAM,cAAc,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;gBACzD,OAAO,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC;YAC5D,CAAC,CAAC,CAAC;QACL,CAAC;QACD,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC;IACjB,CAAC;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,wBAAwB,CACtC,MAAc;IAEd,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACtC,MAAM,OAAO,GAAG,KAAK,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnD,MAAM,UAAU,GAAG,OAAO,GAAG,GAAG,CAAC;IACjC,wCAAwC;IACxC,MAAM,eAAe,GAAG,KAAK,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,GAAG,GAAG,CAAC;IAE1E,OAAO,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE;QAC3B,QAAQ,GAAG,KAAK,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;QAExC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,UAAU,CAAC;YAAE,OAAO,IAAI,CAAC;QAElD,IAAI,QAAQ,CAAC,UAAU,CAAC,eAAe,CAAC,EAAE,CAAC;YACzC,qDAAqD;YACrD,MAAM,QAAQ,GAAG,KAAK,CAAC,YAAY,CAAC,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC;YACrF,IAAI,QAAQ,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;gBACnE,OAAO,GAAG,gBAAgB,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;YACtF,CAAC;YACD,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC7B,CAAC;QAED,+CAA+C;QAC/C,MAAM,WAAW,GAAG,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;QACtD,MAAM,QAAQ,GAAG,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACxC,IAAI,QAAQ,CAAC,MAAM,IAAI,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,KAAK,KAAK,EAAE,CAAC;YAClD,OAAO,IAAI,CAAC;QACd,CAAC;QAED,qCAAqC;QACrC,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC7B,CAAC,CAAC;AACJ,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.45",
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.8",
24
- "@fastify/http-proxy": "^11.4.3",
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.1",
35
+ "lmdb": "^3.5.4",
35
36
  "mime": "^4.1.0",
36
- "postcss": "^8.5.9",
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-node": "14.0.45",
46
- "@simplysm/core-common": "14.0.45",
47
- "@simplysm/storage": "14.0.45"
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",
@@ -27,7 +27,7 @@ export async function ensureCleanWorkingTree(
27
27
  "/sd-commit",
28
28
  "--dangerously-skip-permissions",
29
29
  "--model",
30
- "haiku",
30
+ "claude-haiku-4-5",
31
31
  "--no-session-persistence",
32
32
  "--strict-mcp-config",
33
33
  ],
@@ -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
- const match = /node\s*=\s*"([^"]+)"/.exec(miseContent);
114
- if (match != null) {
115
- nodeVersion = match[1];
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
- const workerResult = transformWorkerPatterns(contents, normalized, build);
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 패턴 처리 (D2)
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.replace(
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
- * Worker/SharedWorker + new URL + import.meta.url 패턴을 감지하는 정규식.
7
- *
8
- * 캡처 그룹:
9
- * - Group 1: `Worker` 또는 `SharedWorker`
10
- * - Group 2: URL 경로 (예: `"./worker.ts"`)
11
- * - Group 3: 옵션 객체 (예: `{ type: "module" }`) — 없으면 undefined
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
- const WORKER_PATTERN =
14
- /\bnew\s+(Worker|SharedWorker)\s*\(\s*new\s+URL\s*\(\s*["']([^"']+)["']\s*,\s*import\.meta\.url\s*\)\s*(?:,\s*(\{[^}]*\}))?\s*\)/g;
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
- * Node.js import.meta.resolve 패턴을 감지하는 정규식.
18
- * 상대 경로(./ 또는 ../)만 감지 — 절대 모듈 경로("some-package")는 무시.
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
- const NODE_WORKER_PATTERN =
24
- /\bimport\.meta\.resolve\s*\(\s*["'](\.\.?\/[^"']+)["']\s*\)/g;
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 플러그인 등 외부에서 직접 호출할 수 있도록 export한다 (D2).
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
- const hasBrowserWorker = content.includes("Worker") && WORKER_PATTERN.test(content);
99
- if (hasBrowserWorker) WORKER_PATTERN.lastIndex = 0;
100
-
101
- const hasNodeWorker =
102
- content.includes("import.meta.resolve") && NODE_WORKER_PATTERN.test(content);
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
- let transformed = content;
174
-
175
- // 1. 브라우저 Worker 패턴 처리 (new Worker(new URL("path", import.meta.url)))
176
- if (hasBrowserWorker) {
177
- transformed = transformed.replace(
178
- WORKER_PATTERN,
179
- (match, workerType: string, urlPath: string, existingOpts?: string) => {
180
- const fullWorkerPath = path.resolve(containingDir, urlPath);
181
- const workerCodePath = processWorkerBundle(fullWorkerPath, "browser");
182
- if (workerCodePath == null) return match;
183
-
184
- const optsStr = existingOpts != null ? existingOpts : '{ type: "module" }';
185
- return `new ${workerType}(new URL("${workerCodePath}", import.meta.url), ${optsStr})`;
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
- // 2. Node.js import.meta.resolve 패턴 처리
191
- if (hasNodeWorker) {
192
- transformed = transformed.replace(
193
- NODE_WORKER_PATTERN,
194
- (match, resolvePath: string) => {
195
- const fullWorkerPath = path.resolve(containingDir, resolvePath);
196
- const workerCodePath = processWorkerBundle(
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: transformed,
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: /\.[cm]?tsx?$/.test(args.path) ? ("ts" as const) : ("js" as const),
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
  };