@simplysm/sd-cli 14.0.38 → 14.0.40

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 (57) hide show
  1. package/dist/angular/angular-build-pipeline.d.ts +1 -1
  2. package/dist/angular/angular-build-pipeline.js +1 -1
  3. package/dist/angular/client-transform-stylesheet.d.ts +1 -1
  4. package/dist/angular/client-transform-stylesheet.js +3 -3
  5. package/dist/dev-server/hmr-client-script.d.ts +2 -2
  6. package/dist/dev-server/hmr-client-script.d.ts.map +1 -1
  7. package/dist/dev-server/hmr-client-script.js +4 -4
  8. package/dist/dev-server/hmr-client-script.js.map +1 -1
  9. package/dist/esbuild/esbuild-client-config.d.ts +0 -2
  10. package/dist/esbuild/esbuild-client-config.d.ts.map +1 -1
  11. package/dist/esbuild/esbuild-client-config.js +20 -10
  12. package/dist/esbuild/esbuild-client-config.js.map +1 -1
  13. package/dist/esbuild/esbuild-postcss-plugin.d.ts +8 -0
  14. package/dist/esbuild/esbuild-postcss-plugin.d.ts.map +1 -0
  15. package/dist/esbuild/esbuild-postcss-plugin.js +105 -0
  16. package/dist/esbuild/esbuild-postcss-plugin.js.map +1 -0
  17. package/dist/esbuild/esbuild-tsc-plugin.d.ts +23 -0
  18. package/dist/esbuild/esbuild-tsc-plugin.d.ts.map +1 -0
  19. package/dist/esbuild/esbuild-tsc-plugin.js +60 -0
  20. package/dist/esbuild/esbuild-tsc-plugin.js.map +1 -0
  21. package/dist/workers/client.worker.d.ts.map +1 -1
  22. package/dist/workers/client.worker.js +94 -26
  23. package/dist/workers/client.worker.js.map +1 -1
  24. package/dist/workers/server-build.worker.d.ts.map +1 -1
  25. package/dist/workers/server-build.worker.js +129 -90
  26. package/dist/workers/server-build.worker.js.map +1 -1
  27. package/dist/workers/server-esbuild-context.d.ts +27 -0
  28. package/dist/workers/server-esbuild-context.d.ts.map +1 -1
  29. package/dist/workers/server-esbuild-context.js +57 -4
  30. package/dist/workers/server-esbuild-context.js.map +1 -1
  31. package/package.json +6 -4
  32. package/src/angular/angular-build-pipeline.ts +2 -2
  33. package/src/angular/client-transform-stylesheet.ts +4 -4
  34. package/src/dev-server/hmr-client-script.ts +4 -4
  35. package/src/esbuild/esbuild-client-config.ts +22 -13
  36. package/src/esbuild/esbuild-postcss-plugin.ts +117 -0
  37. package/src/esbuild/esbuild-tsc-plugin.ts +83 -0
  38. package/src/workers/client.worker.ts +96 -29
  39. package/src/workers/server-build.worker.ts +136 -97
  40. package/src/workers/server-esbuild-context.ts +72 -4
  41. package/tests/angular/client-transform-stylesheet.spec.ts +1 -1
  42. package/tests/esbuild/esbuild-tsc-plugin.acc.spec.ts +349 -0
  43. package/tests/esbuild/esbuild-tsc-plugin.spec.ts +230 -0
  44. package/tests/utils/esbuild-client-config-postcss.verify.md +6 -0
  45. package/tests/utils/esbuild-client-config.acc.spec.ts +34 -20
  46. package/tests/utils/esbuild-client-config.spec.ts +79 -16
  47. package/tests/utils/esbuild-postcss-plugin.acc.spec.ts +299 -0
  48. package/tests/utils/esbuild-postcss-plugin.spec.ts +290 -0
  49. package/tests/utils/esbuild-scss-plugin.acc.spec.ts +1 -0
  50. package/tests/utils/hmr-client-script.acc.spec.ts +8 -8
  51. package/tests/utils/hmr-client-script.spec.ts +5 -5
  52. package/tests/workers/server-build-lint.spec.ts +43 -0
  53. package/tests/workers/server-build-worker-refactoring.verify.md +14 -0
  54. package/tests/workers/server-build-worker.spec.ts +122 -9
  55. package/tests/workers/server-esbuild-context-tsc.verify.md +7 -0
  56. package/tests/workers/server-esbuild-context.acc.spec.ts +188 -2
  57. package/tests/workers/server-esbuild-context.spec.ts +401 -2
@@ -1,14 +1,27 @@
1
1
  import esbuild from "esbuild";
2
+ import { err as errNs } from "@simplysm/core-common";
2
3
  import { createServerEsbuildOptions, writeChangedOutputFiles, } from "../esbuild/esbuild-config.js";
4
+ import { createTscPlugin } from "../esbuild/esbuild-tsc-plugin.js";
3
5
  /** esbuild watch context (모듈 스코프 상태) */
4
6
  let context;
5
7
  /** 마지막 빌드의 metafile (변경 필터링용) */
6
8
  let lastMetafile;
9
+ /** tsc 플러그인 인스턴스 (모듈 스코프 상태) */
10
+ let tscPlugin;
7
11
  /**
8
12
  * esbuild watch context를 생성한다.
9
13
  * dev 모드 전용 (metafile:true, write:false).
10
14
  */
11
15
  export async function createContext(options) {
16
+ if (options.tsc != null) {
17
+ tscPlugin = createTscPlugin({
18
+ pkgDir: options.pkgDir,
19
+ cwd: options.tsc.cwd,
20
+ output: options.tsc.output,
21
+ env: options.tsc.env,
22
+ includeTests: options.tsc.includeTests,
23
+ });
24
+ }
12
25
  const baseOptions = createServerEsbuildOptions({
13
26
  pkgDir: options.pkgDir,
14
27
  entryPoints: options.entryPoints,
@@ -18,6 +31,7 @@ export async function createContext(options) {
18
31
  });
19
32
  context = await esbuild.context({
20
33
  ...baseOptions,
34
+ plugins: tscPlugin != null ? [tscPlugin.plugin] : [],
21
35
  metafile: true,
22
36
  write: false,
23
37
  });
@@ -29,18 +43,32 @@ export async function createContext(options) {
29
43
  export async function rebuild() {
30
44
  if (context == null)
31
45
  return null;
32
- const result = await context.rebuild();
46
+ let result;
47
+ try {
48
+ result = await context.rebuild();
49
+ }
50
+ catch (err) {
51
+ const tscErrors = tscPlugin?.getErrors() ?? [];
52
+ const allErrors = [errNs.message(err), ...tscErrors];
53
+ return {
54
+ success: false,
55
+ errors: allErrors.length > 0 ? allErrors : undefined,
56
+ warnings: undefined,
57
+ };
58
+ }
33
59
  if (result.metafile != null) {
34
60
  lastMetafile = result.metafile;
35
61
  }
36
62
  if (result.outputFiles) {
37
63
  await writeChangedOutputFiles(result.outputFiles);
38
64
  }
39
- const errors = result.errors.map((e) => e.text);
65
+ const esbuildErrors = result.errors.map((e) => e.text);
66
+ const tscErrors = tscPlugin?.getErrors() ?? [];
67
+ const allErrors = [...esbuildErrors, ...tscErrors];
40
68
  const warnings = result.warnings.map((w) => w.text);
41
69
  return {
42
- success: result.errors.length === 0,
43
- errors: errors.length > 0 ? errors : undefined,
70
+ success: allErrors.length === 0,
71
+ errors: allErrors.length > 0 ? allErrors : undefined,
44
72
  warnings: warnings.length > 0 ? warnings : undefined,
45
73
  };
46
74
  }
@@ -57,6 +85,9 @@ export async function recreateContext(options) {
57
85
  const oldContext = context;
58
86
  context = undefined;
59
87
  lastMetafile = undefined;
88
+ if (tscPlugin != null) {
89
+ tscPlugin.resetBuilderProgram();
90
+ }
60
91
  try {
61
92
  await createContext(options);
62
93
  }
@@ -73,6 +104,7 @@ export async function dispose() {
73
104
  const contextToDispose = context;
74
105
  context = undefined;
75
106
  lastMetafile = undefined;
107
+ tscPlugin = undefined;
76
108
  if (contextToDispose != null) {
77
109
  await contextToDispose.dispose();
78
110
  }
@@ -89,4 +121,25 @@ export function getMetafile() {
89
121
  export function hasContext() {
90
122
  return context != null;
91
123
  }
124
+ /**
125
+ * tsc 플러그인의 ts.Program을 반환한다.
126
+ * 플러그인이 없으면 undefined를 반환한다.
127
+ */
128
+ export function getTscProgram() {
129
+ return tscPlugin?.getProgram();
130
+ }
131
+ /**
132
+ * tsc 플러그인의 affected files를 반환한다.
133
+ * 플러그인이 없으면 undefined를 반환한다.
134
+ */
135
+ export function getTscAffectedFiles() {
136
+ return tscPlugin?.getAffectedFiles();
137
+ }
138
+ /**
139
+ * tsc 플러그인의 diagnostics를 반환한다.
140
+ * 플러그인이 없으면 빈 배열을 반환한다.
141
+ */
142
+ export function getTscDiagnostics() {
143
+ return tscPlugin?.getDiagnostics() ?? [];
144
+ }
92
145
  //# sourceMappingURL=server-esbuild-context.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"server-esbuild-context.js","sourceRoot":"","sources":["../../src/workers/server-esbuild-context.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,EACL,0BAA0B,EAC1B,uBAAuB,GACxB,MAAM,2BAA2B,CAAC;AAYnC,wCAAwC;AACxC,IAAI,OAAyC,CAAC;AAE9C,iCAAiC;AACjC,IAAI,YAA0C,CAAC;AAE/C;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,OAA8B;IAChE,MAAM,WAAW,GAAG,0BAA0B,CAAC;QAC7C,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,WAAW,EAAE,OAAO,CAAC,WAAW;QAChC,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,GAAG,EAAE,IAAI;KACV,CAAC,CAAC;IAEH,OAAO,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC;QAC9B,GAAG,WAAW;QACd,QAAQ,EAAE,IAAI;QACd,KAAK,EAAE,KAAK;KACb,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO;IAK3B,IAAI,OAAO,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC;IAEjC,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;IAEvC,IAAI,MAAM,CAAC,QAAQ,IAAI,IAAI,EAAE,CAAC;QAC5B,YAAY,GAAG,MAAM,CAAC,QAAQ,CAAC;IACjC,CAAC;IAED,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;QACvB,MAAM,uBAAuB,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IACpD,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAChD,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAEpD,OAAO;QACL,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;QACnC,MAAM,EAAE,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS;QAC9C,QAAQ,EAAE,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS;KACrD,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,OAA8B;IAClE,MAAM,UAAU,GAAG,OAAO,CAAC;IAC3B,OAAO,GAAG,SAAS,CAAC;IACpB,YAAY,GAAG,SAAS,CAAC;IAEzB,IAAI,CAAC;QACH,MAAM,aAAa,CAAC,OAAO,CAAC,CAAC;IAC/B,CAAC;YAAS,CAAC;QACT,IAAI,UAAU,IAAI,IAAI,EAAE,CAAC;YACvB,MAAM,UAAU,CAAC,OAAO,EAAE,CAAC;QAC7B,CAAC;IACH,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO;IAC3B,MAAM,gBAAgB,GAAG,OAAO,CAAC;IACjC,OAAO,GAAG,SAAS,CAAC;IACpB,YAAY,GAAG,SAAS,CAAC;IAEzB,IAAI,gBAAgB,IAAI,IAAI,EAAE,CAAC;QAC7B,MAAM,gBAAgB,CAAC,OAAO,EAAE,CAAC;IACnC,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW;IACzB,OAAO,YAAY,CAAC;AACtB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU;IACxB,OAAO,OAAO,IAAI,IAAI,CAAC;AACzB,CAAC"}
1
+ {"version":3,"file":"server-esbuild-context.js","sourceRoot":"","sources":["../../src/workers/server-esbuild-context.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.38",
3
+ "version": "14.0.40",
4
4
  "description": "Simplysm package - CLI tool",
5
5
  "author": "simplysm",
6
6
  "license": "Apache-2.0",
@@ -19,6 +19,8 @@
19
19
  ],
20
20
  "sideEffects": false,
21
21
  "dependencies": {
22
+ "acorn": "^8.14.1",
23
+ "acorn-walk": "^8.3.4",
22
24
  "@angular/build": "^21.2.7",
23
25
  "@angular/compiler-cli": "^21.2.8",
24
26
  "@fastify/http-proxy": "^11.4.3",
@@ -38,9 +40,9 @@
38
40
  "typescript": "^5.9.3",
39
41
  "ws": "^8.20.0",
40
42
  "yargs": "^18.0.0",
41
- "@simplysm/core-common": "14.0.38",
42
- "@simplysm/core-node": "14.0.38",
43
- "@simplysm/storage": "14.0.38"
43
+ "@simplysm/storage": "14.0.40",
44
+ "@simplysm/core-common": "14.0.40",
45
+ "@simplysm/core-node": "14.0.40"
44
46
  },
45
47
  "devDependencies": {
46
48
  "@types/semver": "^7.7.1",
@@ -65,7 +65,7 @@ export interface AngularBuildPipelineOptions {
65
65
  compilerOptionsTransformer?: (opts: ts.CompilerOptions) => ts.CompilerOptions;
66
66
 
67
67
  // client 모드 전용
68
- postCssPlugins?: unknown[];
68
+ postcssPlugins?: unknown[];
69
69
  scssCacheDir?: string;
70
70
  }
71
71
 
@@ -210,7 +210,7 @@ export class AngularBuildPipeline {
210
210
  this._options.mode === "client"
211
211
  ? createClientTransformStylesheet({
212
212
  loadPaths,
213
- postCssPlugins: this._options.postCssPlugins,
213
+ postcssPlugins: this._options.postcssPlugins,
214
214
  scssErrors: this._scssErrors,
215
215
  scssDependencies: this._scssDependencies,
216
216
  cacheDir: this._options.scssCacheDir,
@@ -6,7 +6,7 @@ import { compileScssFileAsync, compileScssStringAsync } from "./scss-compiler.js
6
6
 
7
7
  export interface ClientTransformStylesheetOptions {
8
8
  loadPaths: string[];
9
- postCssPlugins?: unknown[];
9
+ postcssPlugins?: unknown[];
10
10
  scssErrors: string[];
11
11
  scssDependencies: Map<string, Set<string>>;
12
12
  /** SCSS 캐시 디렉토리 (미지정 시 캐시 비활성화) */
@@ -57,11 +57,11 @@ async function readFileHash(filePath: string): Promise<string | undefined> {
57
57
  export function createClientTransformStylesheet(
58
58
  options: ClientTransformStylesheetOptions,
59
59
  ): (data: string, containingFile: string, stylesheetFile?: string) => Promise<string | null> {
60
- const { loadPaths, postCssPlugins, scssErrors, scssDependencies, cacheDir } = options;
60
+ const { loadPaths, postcssPlugins, scssErrors, scssDependencies, cacheDir } = options;
61
61
 
62
62
  const postCssProcessor =
63
- postCssPlugins != null && postCssPlugins.length > 0
64
- ? postcss(postCssPlugins as postcss.AcceptedPlugin[])
63
+ postcssPlugins != null && postcssPlugins.length > 0
64
+ ? postcss(postcssPlugins as postcss.AcceptedPlugin[])
65
65
  : undefined;
66
66
 
67
67
  return async (
@@ -3,14 +3,14 @@
3
3
  * Chrome 61+ 호환 문법으로 작성 (optional chaining, nullish coalescing 미사용).
4
4
  * @param basePath basePath (예: "/app/")
5
5
  */
6
- export function getHmrClientScript(_basePath: string): string {
6
+ export function getHmrClientScript(_basePath: string, port: number): string {
7
7
  return [
8
8
  "(function() {",
9
9
  " var ws = null;",
10
10
  " var reconnectDelay = 1000;",
11
11
  "",
12
12
  " function connect() {",
13
- ' ws = new WebSocket("ws://" + location.hostname + ":" + location.port);',
13
+ ` ws = new WebSocket("ws://" + location.hostname + ":${port}");`,
14
14
  "",
15
15
  " ws.onmessage = function(e) {",
16
16
  " var msg = JSON.parse(e.data);",
@@ -60,8 +60,8 @@ export function getHmrClientScript(_basePath: string): string {
60
60
  * Feature 1.2의 GenerateIndexHtmlOptions.postTransform에 전달하여 사용.
61
61
  * @param basePath basePath (예: "/app/")
62
62
  */
63
- export function createHmrPostTransform(basePath: string): (content: string) => Promise<string> {
64
- const script = getHmrClientScript(basePath);
63
+ export function createHmrPostTransform(basePath: string, port: number): (content: string) => Promise<string> {
64
+ const script = getHmrClientScript(basePath, port);
65
65
  const scriptTag = `<script>${script}</script>`;
66
66
 
67
67
  return (html: string): Promise<string> => {
@@ -1,7 +1,9 @@
1
1
  import path from "path";
2
2
  import fs from "fs";
3
+ import { createRequire } from "module";
3
4
  import esbuild from "esbuild";
4
5
  import browserslistToEsbuild from "browserslist-to-esbuild";
6
+ import type { AcceptedPlugin } from "postcss";
5
7
  import {
6
8
  createCompilerPlugin,
7
9
  SourceFileCache,
@@ -9,6 +11,7 @@ import {
9
11
  type BundleStylesheetOptions,
10
12
  } from "@angular/build/private";
11
13
  import { createScssPlugin } from "./esbuild-scss-plugin";
14
+ import { createPostcssPlugin } from "./esbuild-postcss-plugin";
12
15
 
13
16
  export interface CreateClientEsbuildOptions {
14
17
  /** 패키지 디렉토리 경로 */
@@ -29,8 +32,6 @@ export interface CreateClientEsbuildOptions {
29
32
  onEnd?: (result: esbuild.BuildResult) => void | Promise<void>;
30
33
  /** PostCSS 플러그인 ([name, options] 튜플 배열) */
31
34
  postcssPlugins?: [string, (object | string)?][];
32
- /** PostCSS 설정 기준 경로 */
33
- postcssConfigPath?: string;
34
35
  /** 빌드 출력 경로 (기본: pkgDir/dist) */
35
36
  outdir?: string;
36
37
  /** browserslist 쿼리. 미설정 시 "es2022" 기본값 */
@@ -91,18 +92,23 @@ export async function createClientEsbuildContext(
91
92
  path: cachePath,
92
93
  basePath: cachePath,
93
94
  },
94
- postcssConfiguration:
95
- options.postcssPlugins != null
96
- ? {
97
- config: { plugins: options.postcssPlugins },
98
- configPath: options.postcssConfigPath ?? options.pkgDir,
99
- }
100
- : undefined,
95
+ postcssConfiguration: undefined,
101
96
  inlineStyleLanguage: "scss",
102
97
  };
103
98
 
104
99
  const angularPlugin = createCompilerPlugin(pluginOptions, styleOptions);
105
100
 
101
+ // PostCSS 플러그인 로딩 (튜플 → 인스턴스)
102
+ let loadedPostcssPlugins: AcceptedPlugin[] | undefined;
103
+ if (options.postcssPlugins != null && options.postcssPlugins.length > 0) {
104
+ const req = createRequire(path.join(options.pkgDir, "package.json"));
105
+ loadedPostcssPlugins = options.postcssPlugins.map(([name, pluginOpts]) => {
106
+ const pluginFn = req(name);
107
+ const fn = pluginFn.default ?? pluginFn;
108
+ return pluginOpts != null ? fn(pluginOpts) : fn;
109
+ });
110
+ }
111
+
106
112
  // SCSS side-effect import 처리 플러그인
107
113
  const scssPlugin = createScssPlugin({
108
114
  loadPaths: [
@@ -126,10 +132,10 @@ export async function createClientEsbuildContext(
126
132
  }
127
133
 
128
134
  // 커스텀 env
135
+ // esbuild define은 정적 패턴만 치환하므로, import.meta.env 객체 자체를 주입해야
136
+ // env() 함수의 동적 접근(import.meta.env?.[key])이 동작한다.
129
137
  if (options.env != null) {
130
- for (const [key, value] of Object.entries(options.env)) {
131
- define[`import.meta.env.${key}`] = JSON.stringify(value);
132
- }
138
+ define["import.meta.env"] = JSON.stringify(options.env);
133
139
  }
134
140
 
135
141
  // import.meta.hot 폴리필 banner (Angular HMR 런타임용)
@@ -184,9 +190,12 @@ export async function createClientEsbuildContext(
184
190
  },
185
191
  ]
186
192
  : []),
193
+ ...(options.plugins ?? []),
187
194
  angularPlugin,
188
195
  scssPlugin,
189
- ...(options.plugins ?? []),
196
+ ...(loadedPostcssPlugins != null
197
+ ? [createPostcssPlugin({ plugins: loadedPostcssPlugins })]
198
+ : []),
190
199
  ...(options.legacyModule === true
191
200
  ? [
192
201
  {
@@ -0,0 +1,117 @@
1
+ import type esbuild from "esbuild";
2
+ import type { AcceptedPlugin } from "postcss";
3
+ import postcss from "postcss";
4
+ import fs from "fs";
5
+ import * as acorn from "acorn";
6
+ import * as walk from "acorn-walk";
7
+
8
+ export interface CreatePostcssPluginOptions {
9
+ /** 이미 로딩된 PostCSS 플러그인 인스턴스 배열 */
10
+ plugins: AcceptedPlugin[];
11
+ }
12
+
13
+ export function createPostcssPlugin(options: CreatePostcssPluginOptions): esbuild.Plugin {
14
+ return {
15
+ name: "sd-postcss",
16
+ setup(build) {
17
+ build.onEnd(async (result) => {
18
+ if (options.plugins.length === 0) return;
19
+ if (result.metafile == null) return;
20
+
21
+ const processor = postcss(options.plugins);
22
+ const outputFiles = Object.keys(result.metafile.outputs);
23
+
24
+ // .css 파일 처리
25
+ for (const file of outputFiles) {
26
+ if (!file.endsWith(".css")) continue;
27
+
28
+ try {
29
+ const css = await fs.promises.readFile(file, "utf-8");
30
+ const processed = await processor.process(css, { from: file });
31
+ await fs.promises.writeFile(file, processed.css);
32
+ } catch (e: unknown) {
33
+ result.errors.push({
34
+ id: "",
35
+ pluginName: "sd-postcss",
36
+ text: `PostCSS error in ${file}: ${e instanceof Error ? e.message : String(e)}`,
37
+ location: null,
38
+ notes: [],
39
+ detail: undefined,
40
+ });
41
+ }
42
+ }
43
+
44
+ // .js 파일 처리 — ɵɵdefineComponent 내 styles 배열의 문자열에 PostCSS 적용
45
+ for (const file of outputFiles) {
46
+ if (!file.endsWith(".js")) continue;
47
+
48
+ try {
49
+ const code = await fs.promises.readFile(file, "utf-8");
50
+ if (!code.includes("styles")) continue;
51
+
52
+ const ast = acorn.parse(code, {
53
+ ecmaVersion: "latest",
54
+ sourceType: "module",
55
+ });
56
+
57
+ // styles 배열 내 문자열 리터럴의 위치와 PostCSS 결과를 수집
58
+ const replacements: Array<{ start: number; end: number; text: string }> = [];
59
+
60
+ walk.ancestor(ast, {
61
+ Property(node: any, _state: any, ancestors: any[]) {
62
+ // key가 "styles"이고 value가 ArrayExpression인 Property만 대상
63
+ if (node.key.type !== "Identifier" || node.key.name !== "styles") return;
64
+ if (node.value.type !== "ArrayExpression") return;
65
+
66
+ // ancestors에서 ɵɵdefineComponent 호출을 확인
67
+ const inDefineComponent = ancestors.some(
68
+ (a: any) =>
69
+ a.type === "CallExpression" &&
70
+ a.callee?.type === "MemberExpression" &&
71
+ a.callee.property?.type === "Identifier" &&
72
+ a.callee.property.name === "\u0275\u0275defineComponent",
73
+ );
74
+ if (!inDefineComponent) return;
75
+
76
+ for (const element of node.value.elements) {
77
+ if (element == null) continue;
78
+ if (element.type !== "Literal" || typeof element.value !== "string") continue;
79
+
80
+ replacements.push({
81
+ start: element.start,
82
+ end: element.end,
83
+ text: element.value,
84
+ });
85
+ }
86
+ },
87
+ });
88
+
89
+ if (replacements.length === 0) continue;
90
+
91
+ // PostCSS 적용 및 역순 교체
92
+ let modified = code;
93
+ // 끝에서 시작 방향으로 교체하여 offset 유지
94
+ const sorted = replacements.sort((a, b) => b.start - a.start);
95
+
96
+ for (const rep of sorted) {
97
+ const processed = await processor.process(rep.text, { from: file });
98
+ const escaped = JSON.stringify(processed.css);
99
+ modified = modified.slice(0, rep.start) + escaped + modified.slice(rep.end);
100
+ }
101
+
102
+ await fs.promises.writeFile(file, modified);
103
+ } catch (e: unknown) {
104
+ result.errors.push({
105
+ id: "",
106
+ pluginName: "sd-postcss",
107
+ text: `PostCSS error in ${file}: ${e instanceof Error ? e.message : String(e)}`,
108
+ location: null,
109
+ notes: [],
110
+ detail: undefined,
111
+ });
112
+ }
113
+ }
114
+ });
115
+ },
116
+ };
117
+ }
@@ -0,0 +1,83 @@
1
+ import type esbuild from "esbuild";
2
+ import type ts from "typescript";
3
+ import { err as errNs } from "@simplysm/core-common";
4
+ import { parseTsconfig, type TypecheckEnv } from "../utils/tsconfig";
5
+ import { runTscPackageBuild, type TscPackageBuildResult } from "../utils/tsc-build";
6
+ import type { SerializedDiagnostic } from "../typecheck/typecheck-serialization";
7
+
8
+ export interface TscPluginOptions {
9
+ pkgDir: string;
10
+ cwd: string;
11
+ output: { dts: boolean };
12
+ env?: TypecheckEnv;
13
+ includeTests?: boolean;
14
+ }
15
+
16
+ export interface TscPluginResult {
17
+ plugin: esbuild.Plugin;
18
+ getProgram(): ts.Program | undefined;
19
+ getAffectedFiles(): ReadonlySet<string> | undefined;
20
+ getDiagnostics(): SerializedDiagnostic[];
21
+ getErrors(): string[] | undefined;
22
+ resetBuilderProgram(): void;
23
+ }
24
+
25
+ export function createTscPlugin(options: TscPluginOptions): TscPluginResult {
26
+ // 내부 상태
27
+ let lastProgram: ts.Program | undefined;
28
+ let lastAffectedFiles: ReadonlySet<string> | undefined;
29
+ let lastDiagnostics: SerializedDiagnostic[] = [];
30
+ let lastErrors: string[] | undefined;
31
+ let lastBuilderProgram: ts.EmitAndSemanticDiagnosticsBuilderProgram | undefined;
32
+
33
+ // onStart에서 생성한 tsc Promise (onEnd에서 await)
34
+ let tscPromise: Promise<TscPackageBuildResult> | undefined;
35
+
36
+ const plugin: esbuild.Plugin = {
37
+ name: "sd-tsc",
38
+ setup(build) {
39
+ build.onStart(() => {
40
+ // microtask로 tsc 스케줄링 (await하지 않음)
41
+ tscPromise = Promise.resolve().then(() => {
42
+ const parsedConfig = parseTsconfig(options.pkgDir);
43
+ return runTscPackageBuild({
44
+ pkgDir: options.pkgDir,
45
+ cwd: options.cwd,
46
+ output: { js: false, dts: options.output.dts },
47
+ parsedConfig,
48
+ env: options.env,
49
+ includeTests: options.includeTests,
50
+ oldBuilderProgram: lastBuilderProgram,
51
+ });
52
+ });
53
+ });
54
+
55
+ build.onEnd(async () => {
56
+ try {
57
+ const tscResult = await tscPromise!;
58
+ lastProgram = tscResult.program;
59
+ lastAffectedFiles = tscResult.affectedFiles;
60
+ lastDiagnostics = tscResult.diagnostics;
61
+ lastErrors = tscResult.errors;
62
+ lastBuilderProgram = tscResult.builderProgram;
63
+ } catch (err) {
64
+ lastProgram = undefined;
65
+ lastAffectedFiles = undefined;
66
+ lastDiagnostics = [];
67
+ lastErrors = [errNs.message(err)];
68
+ }
69
+ });
70
+ },
71
+ };
72
+
73
+ return {
74
+ plugin,
75
+ getProgram: () => lastProgram,
76
+ getAffectedFiles: () => lastAffectedFiles,
77
+ getDiagnostics: () => lastDiagnostics,
78
+ getErrors: () => lastErrors,
79
+ resetBuilderProgram: () => {
80
+ lastBuilderProgram = undefined;
81
+ },
82
+ };
83
+ }