@analogjs/vite-plugin-angular 2.0.0-alpha.2 → 2.0.0-alpha.20

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 (69) hide show
  1. package/README.md +2 -0
  2. package/package.json +8 -4
  3. package/src/index.d.ts +0 -2
  4. package/src/index.js +0 -2
  5. package/src/index.js.map +1 -1
  6. package/src/lib/angular-pending-tasks.plugin.js +3 -2
  7. package/src/lib/angular-pending-tasks.plugin.js.map +1 -1
  8. package/src/lib/angular-storybook-plugin.d.ts +2 -2
  9. package/src/lib/angular-storybook-plugin.js +11 -4
  10. package/src/lib/angular-storybook-plugin.js.map +1 -1
  11. package/src/lib/angular-vite-plugin.d.ts +17 -22
  12. package/src/lib/angular-vite-plugin.js +378 -210
  13. package/src/lib/angular-vite-plugin.js.map +1 -1
  14. package/src/lib/angular-vitest-plugin.js +1 -6
  15. package/src/lib/angular-vitest-plugin.js.map +1 -1
  16. package/src/lib/component-resolvers.js +1 -1
  17. package/src/lib/component-resolvers.js.map +1 -1
  18. package/src/lib/host.d.ts +4 -7
  19. package/src/lib/host.js +47 -80
  20. package/src/lib/host.js.map +1 -1
  21. package/src/lib/live-reload-plugin.d.ts +6 -0
  22. package/src/lib/live-reload-plugin.js +63 -0
  23. package/src/lib/live-reload-plugin.js.map +1 -0
  24. package/src/lib/models.d.ts +11 -0
  25. package/src/lib/models.js +2 -0
  26. package/src/lib/models.js.map +1 -0
  27. package/src/lib/nx-folder-plugin.d.ts +7 -0
  28. package/src/lib/nx-folder-plugin.js +18 -0
  29. package/src/lib/nx-folder-plugin.js.map +1 -0
  30. package/src/lib/tools/README.md +3 -0
  31. package/src/lib/tools/builders.json +15 -0
  32. package/src/lib/tools/package.json +7 -0
  33. package/src/lib/tools/src/builders/vite/schema.d.ts +6 -0
  34. package/src/lib/tools/src/builders/vite/schema.json +39 -0
  35. package/src/lib/tools/src/builders/vite/vite-build.impl.d.ts +2 -0
  36. package/src/lib/tools/src/builders/vite/vite-build.impl.js +40 -0
  37. package/src/lib/tools/src/builders/vite/vite-build.impl.js.map +1 -0
  38. package/src/lib/tools/src/builders/vite-dev-server/dev-server.impl.d.ts +2 -0
  39. package/src/lib/tools/src/builders/vite-dev-server/dev-server.impl.js +64 -0
  40. package/src/lib/tools/src/builders/vite-dev-server/dev-server.impl.js.map +1 -0
  41. package/src/lib/tools/src/builders/vite-dev-server/schema.d.ts +5 -0
  42. package/src/lib/tools/src/builders/vite-dev-server/schema.json +25 -0
  43. package/src/lib/tools/src/index.d.ts +0 -0
  44. package/src/lib/tools/src/index.js +1 -0
  45. package/src/lib/tools/src/index.js.map +1 -0
  46. package/src/lib/utils/devkit.js +3 -11
  47. package/src/lib/utils/devkit.js.map +1 -1
  48. package/src/lib/utils/hmr-candidates.d.ts +32 -0
  49. package/src/lib/utils/hmr-candidates.js +272 -0
  50. package/src/lib/utils/hmr-candidates.js.map +1 -0
  51. package/src/lib/utils/source-file-cache.d.ts +2 -2
  52. package/esbuild.d.ts +0 -3
  53. package/esbuild.js +0 -31
  54. package/esbuild.js.map +0 -1
  55. package/src/lib/authoring/analog.d.ts +0 -1
  56. package/src/lib/authoring/analog.js +0 -550
  57. package/src/lib/authoring/analog.js.map +0 -1
  58. package/src/lib/authoring/constants.d.ts +0 -26
  59. package/src/lib/authoring/constants.js +0 -47
  60. package/src/lib/authoring/constants.js.map +0 -1
  61. package/src/lib/authoring/frontmatter.d.ts +0 -2
  62. package/src/lib/authoring/frontmatter.js +0 -26
  63. package/src/lib/authoring/frontmatter.js.map +0 -1
  64. package/src/lib/authoring/markdown-transform.d.ts +0 -4
  65. package/src/lib/authoring/markdown-transform.js +0 -14
  66. package/src/lib/authoring/markdown-transform.js.map +0 -1
  67. package/src/lib/authoring/marked-setup.service.d.ts +0 -23
  68. package/src/lib/authoring/marked-setup.service.js +0 -119
  69. package/src/lib/authoring/marked-setup.service.js.map +0 -1
@@ -1,8 +1,9 @@
1
- import { dirname, relative, resolve } from 'node:path';
1
+ import { basename, dirname, isAbsolute, relative, resolve } from 'node:path';
2
+ import { mkdirSync, writeFileSync } from 'node:fs';
2
3
  import * as compilerCli from '@angular/compiler-cli';
3
- import * as ts from 'typescript';
4
4
  import { createRequire } from 'node:module';
5
- import { normalizePath, preprocessCSS, } from 'vite';
5
+ import { normalizePath, preprocessCSS, defaultClientConditions, } from 'vite';
6
+ import * as ngCompiler from '@angular/compiler';
6
7
  import { createCompilerPlugin } from './compiler-plugin.js';
7
8
  import { StyleUrlsResolver, TemplateUrlsResolver, } from './component-resolvers.js';
8
9
  import { augmentHostWithCaching, augmentHostWithResources, augmentProgramWithVersioning, mergeTransformers, } from './host.js';
@@ -12,17 +13,16 @@ import { createJitResourceTransformer, SourceFileCache, angularMajor, } from './
12
13
  import { angularVitestPlugins } from './angular-vitest-plugin.js';
13
14
  import { angularStorybookPlugin } from './angular-storybook-plugin.js';
14
15
  const require = createRequire(import.meta.url);
15
- import { getFrontmatterMetadata } from './authoring/frontmatter.js';
16
- import { defaultMarkdownTemplateTransforms, } from './authoring/markdown-transform.js';
17
16
  import { routerPlugin } from './router-plugin.js';
18
17
  import { pendingTasksPlugin } from './angular-pending-tasks.plugin.js';
18
+ import { liveReloadPlugin } from './live-reload-plugin.js';
19
+ import { nxFolderPlugin } from './nx-folder-plugin.js';
19
20
  /**
20
21
  * TypeScript file extension regex
21
22
  * Match .(c or m)ts, .ts extensions with an optional ? for query params
22
23
  * Ignore .tsx extensions
23
24
  */
24
25
  const TS_EXT_REGEX = /\.[cm]?(ts|analog|ag)[^x]?\??/;
25
- const ANGULAR_COMPONENT_PREFIX = '/@ng/component';
26
26
  const classNames = new Map();
27
27
  export function angular(options) {
28
28
  /**
@@ -30,10 +30,7 @@ export function angular(options) {
30
30
  * are used for values not provided.
31
31
  */
32
32
  const pluginOptions = {
33
- tsconfig: options?.tsconfig ??
34
- (process.env['NODE_ENV'] === 'test'
35
- ? './tsconfig.spec.json'
36
- : './tsconfig.app.json'),
33
+ tsconfig: options?.tsconfig || '',
37
34
  workspaceRoot: options?.workspaceRoot ?? process.cwd(),
38
35
  inlineStylesExtension: options?.inlineStylesExtension ?? 'css',
39
36
  advanced: {
@@ -44,29 +41,22 @@ export function angular(options) {
44
41
  },
45
42
  },
46
43
  supportedBrowsers: options?.supportedBrowsers ?? ['safari 15'],
47
- jit: options?.experimental?.supportAnalogFormat ? false : options?.jit,
48
- supportAnalogFormat: options?.experimental?.supportAnalogFormat ?? false,
49
- markdownTemplateTransforms: options?.experimental
50
- ?.markdownTemplateTransforms?.length
51
- ? options.experimental.markdownTemplateTransforms
52
- : defaultMarkdownTemplateTransforms,
44
+ jit: options?.jit,
53
45
  include: options?.include ?? [],
54
46
  additionalContentDirs: options?.additionalContentDirs ?? [],
55
47
  liveReload: options?.liveReload ?? false,
48
+ disableTypeChecking: options?.disableTypeChecking ?? true,
56
49
  };
57
- // The file emitter created during `onStart` that will be used during the build in `onLoad` callbacks for TS files
58
- let fileEmitter;
59
- let compilerOptions = {};
60
- const ts = require('typescript');
61
50
  let resolvedConfig;
62
- let rootNames;
63
- let host;
64
51
  let nextProgram;
65
52
  let builderProgram;
66
53
  let watchMode = false;
67
- let testWatchMode = false;
54
+ let testWatchMode = isTestWatchMode();
55
+ let inlineComponentStyles;
56
+ let externalComponentStyles;
68
57
  const sourceFileCache = new SourceFileCache();
69
58
  const isTest = process.env['NODE_ENV'] === 'test' || !!process.env['VITEST'];
59
+ const isVitestVscode = !!process.env['VITEST_VSCODE'];
70
60
  const isStackBlitz = !!process.versions['webcontainer'];
71
61
  const isAstroIntegration = process.env['ANALOG_ASTRO'] === 'true';
72
62
  const isStorybook = process.env['npm_lifecycle_script']?.includes('storybook') ||
@@ -75,9 +65,16 @@ export function angular(options) {
75
65
  process.env['ANALOG_STORYBOOK'] === 'true';
76
66
  const jit = typeof pluginOptions?.jit !== 'undefined' ? pluginOptions.jit : isTest;
77
67
  let viteServer;
78
- let styleTransform;
79
68
  const styleUrlsResolver = new StyleUrlsResolver();
80
69
  const templateUrlsResolver = new TemplateUrlsResolver();
70
+ let outputFile;
71
+ const outputFiles = new Map();
72
+ const fileEmitter = (file) => {
73
+ outputFile?.(file);
74
+ return outputFiles.get(normalizePath(file));
75
+ };
76
+ let initialCompilation = false;
77
+ const declarationFiles = [];
81
78
  function angularPlugin() {
82
79
  let isProd = false;
83
80
  if (angularMajor < 19 || isTest) {
@@ -85,21 +82,12 @@ export function angular(options) {
85
82
  }
86
83
  return {
87
84
  name: '@analogjs/vite-plugin-angular',
88
- async watchChange() {
89
- if (isTest) {
90
- await buildAndAnalyze();
91
- }
92
- },
93
85
  async config(config, { command }) {
94
86
  watchMode = command === 'serve';
95
87
  isProd =
96
88
  config.mode === 'production' ||
97
89
  process.env['NODE_ENV'] === 'production';
98
- pluginOptions.tsconfig =
99
- options?.tsconfig ??
100
- resolve(config.root || '.', process.env['NODE_ENV'] === 'test'
101
- ? './tsconfig.spec.json'
102
- : './tsconfig.app.json');
90
+ pluginOptions.tsconfig = getTsConfigPath(config.root || '.', pluginOptions, isProd, isTest, !!config?.build?.lib);
103
91
  return {
104
92
  esbuild: config.esbuild ?? false,
105
93
  optimizeDeps: {
@@ -123,80 +111,60 @@ export function angular(options) {
123
111
  },
124
112
  },
125
113
  resolve: {
126
- conditions: ['style'],
114
+ conditions: ['style', ...defaultClientConditions],
127
115
  },
128
116
  };
129
117
  },
130
118
  configResolved(config) {
131
119
  resolvedConfig = config;
132
- // set test watch mode
133
- // - vite override from vitest-angular
134
- // - @nx/vite executor set server.watch explicitly to undefined (watch)/null (watch=false)
135
- // - vite config for test.watch variable
136
- testWatchMode =
137
- !(config.server.watch === null) ||
138
- config.test?.watch === true;
120
+ if (isTest) {
121
+ // set test watch mode
122
+ // - vite override from vitest-angular
123
+ // - @nx/vite executor set server.watch explicitly to undefined (watch)/null (watch=false)
124
+ // - vite config for test.watch variable
125
+ // - vitest watch mode detected from the command line
126
+ testWatchMode =
127
+ !(config.server.watch === null) ||
128
+ config.test?.watch === true ||
129
+ testWatchMode;
130
+ }
139
131
  },
140
132
  configureServer(server) {
141
133
  viteServer = server;
142
134
  server.watcher.on('add', async () => {
143
- setupCompilation(resolvedConfig);
144
- await buildAndAnalyze();
135
+ await performCompilation(resolvedConfig);
145
136
  });
146
137
  server.watcher.on('unlink', async () => {
147
- setupCompilation(resolvedConfig);
148
- await buildAndAnalyze();
138
+ await performCompilation(resolvedConfig);
149
139
  });
150
- if (pluginOptions.liveReload) {
151
- const angularComponentMiddleware = async (req, res, next) => {
152
- if (req.url === undefined || res.writableEnded) {
153
- return;
154
- }
155
- if (!req.url.startsWith(ANGULAR_COMPONENT_PREFIX)) {
156
- next();
157
- return;
158
- }
159
- const requestUrl = new URL(req.url, 'http://localhost');
160
- const componentId = requestUrl.searchParams.get('c');
161
- if (!componentId) {
162
- res.statusCode = 400;
163
- res.end();
164
- return;
165
- }
166
- const [fileId] = decodeURIComponent(componentId).split('@');
167
- const result = await fileEmitter?.(resolve(process.cwd(), fileId));
168
- res.setHeader('Content-Type', 'text/javascript');
169
- res.setHeader('Cache-Control', 'no-cache');
170
- res.end(`${result?.hmrUpdateCode || ''}`);
171
- };
172
- viteServer.middlewares.use(angularComponentMiddleware);
173
- }
174
140
  },
175
141
  async buildStart() {
176
- setupCompilation(resolvedConfig);
177
- // Only store cache if in watch mode
178
- if (watchMode) {
179
- augmentHostWithCaching(host, sourceFileCache);
142
+ // Defer the first compilation in test mode
143
+ if (!isVitestVscode) {
144
+ const { host } = await performCompilation(resolvedConfig);
145
+ initialCompilation = true;
146
+ // Only store cache if in watch mode
147
+ if (watchMode) {
148
+ augmentHostWithCaching(host, sourceFileCache);
149
+ }
180
150
  }
181
- await buildAndAnalyze();
182
151
  },
183
152
  async handleHotUpdate(ctx) {
184
- // The `handleHotUpdate` hook may be called before the `buildStart`,
185
- // which sets the compilation. As a result, the `host` may not be available
186
- // yet for use, leading to build errors such as "cannot read properties of undefined"
187
- // (because `host` is undefined).
188
- if (!host) {
189
- return;
190
- }
191
153
  if (TS_EXT_REGEX.test(ctx.file)) {
192
- const fileId = ctx.file.split('?')[0] +
193
- (pluginOptions.supportAnalogFormat ? '.ts' : '');
194
- sourceFileCache.invalidate([fileId]);
195
- await buildAndAnalyze();
196
- if (pluginOptions.liveReload && classNames.get(fileId)) {
154
+ let [fileId] = ctx.file.split('?');
155
+ await performCompilation(resolvedConfig, [fileId]);
156
+ const result = fileEmitter(fileId);
157
+ if (pluginOptions.liveReload &&
158
+ result?.hmrEligible &&
159
+ classNames.get(fileId)) {
197
160
  const relativeFileId = `${relative(process.cwd(), fileId)}@${classNames.get(fileId)}`;
198
161
  sendHMRComponentUpdate(ctx.server, relativeFileId);
199
- return [];
162
+ return ctx.modules.map((mod) => {
163
+ if (mod.id === ctx.file) {
164
+ return markModuleSelfAccepting(mod);
165
+ }
166
+ return mod;
167
+ });
200
168
  }
201
169
  }
202
170
  if (/\.(html|htm|css|less|sass|scss)$/.test(ctx.file)) {
@@ -205,7 +173,42 @@ export function angular(options) {
205
173
  * for an external resource (styles, html).
206
174
  */
207
175
  const isDirect = ctx.modules.find((mod) => ctx.file === mod.file && mod.id?.includes('?direct'));
208
- if (isDirect) {
176
+ const isInline = ctx.modules.find((mod) => ctx.file === mod.file && mod.id?.includes('?inline'));
177
+ if (isDirect || isInline) {
178
+ if (pluginOptions.liveReload && isDirect?.id && isDirect.file) {
179
+ const isComponentStyle = isDirect.type === 'css' && isComponentStyleSheet(isDirect.id);
180
+ if (isComponentStyle) {
181
+ const { encapsulation } = getComponentStyleSheetMeta(isDirect.id);
182
+ // Track if the component uses ShadowDOM encapsulation
183
+ // Shadow DOM components currently require a full reload.
184
+ // Vite's CSS hot replacement does not support shadow root searching.
185
+ if (encapsulation !== 'shadow') {
186
+ ctx.server.ws.send({
187
+ type: 'update',
188
+ updates: [
189
+ {
190
+ type: 'css-update',
191
+ timestamp: Date.now(),
192
+ path: isDirect.url,
193
+ acceptedPath: isDirect.file,
194
+ },
195
+ ],
196
+ });
197
+ return ctx.modules
198
+ .filter((mod) => {
199
+ // Component stylesheets will have 2 modules (*.component.scss and *.component.scss?direct&ngcomp=xyz&e=x)
200
+ // We remove the module with the query params to prevent vite double logging the stylesheet name "hmr update *.component.scss, *.component.scss?direct&ngcomp=xyz&e=x"
201
+ return mod.file !== ctx.file || mod.id !== isDirect.id;
202
+ })
203
+ .map((mod) => {
204
+ if (mod.file === ctx.file) {
205
+ return markModuleSelfAccepting(mod);
206
+ }
207
+ return mod;
208
+ });
209
+ }
210
+ }
211
+ }
209
212
  return ctx.modules;
210
213
  }
211
214
  const mods = [];
@@ -222,16 +225,26 @@ export function angular(options) {
222
225
  }
223
226
  });
224
227
  });
225
- await buildAndAnalyze();
228
+ await performCompilation(resolvedConfig, [
229
+ ...mods.map((mod) => mod.id),
230
+ ...updates,
231
+ ]);
226
232
  if (updates.length > 0) {
227
233
  updates.forEach((updateId) => {
228
234
  const impRelativeFileId = `${relative(process.cwd(), updateId)}@${classNames.get(updateId)}`;
229
235
  sendHMRComponentUpdate(ctx.server, impRelativeFileId);
230
236
  });
231
- return ctx.modules;
237
+ return ctx.modules.map((mod) => {
238
+ if (mod.id === ctx.file) {
239
+ return markModuleSelfAccepting(mod);
240
+ }
241
+ return mod;
242
+ });
232
243
  }
233
244
  return mods;
234
245
  }
246
+ // clear HMR updates with a full reload
247
+ classNames.clear();
235
248
  return ctx.modules;
236
249
  },
237
250
  resolveId(id, importer) {
@@ -239,19 +252,22 @@ export function angular(options) {
239
252
  const path = id.split(';')[1];
240
253
  return `${normalizePath(resolve(dirname(importer), path))}?raw`;
241
254
  }
255
+ // Map angular external styleUrls to the source file
256
+ if (isComponentStyleSheet(id)) {
257
+ const componentStyles = externalComponentStyles?.get(getFilenameFromPath(id));
258
+ if (componentStyles) {
259
+ return componentStyles + new URL(id, 'http://localhost').search;
260
+ }
261
+ }
242
262
  return undefined;
243
263
  },
244
- async load(id, options) {
245
- if (pluginOptions.liveReload &&
246
- options?.ssr &&
247
- id.startsWith(ANGULAR_COMPONENT_PREFIX)) {
248
- const requestUrl = new URL(id.slice(1), 'http://localhost');
249
- const componentId = requestUrl.searchParams.get('c');
250
- if (!componentId) {
251
- return;
264
+ async load(id) {
265
+ // Map angular inline styles to the source text
266
+ if (isComponentStyleSheet(id)) {
267
+ const componentStyles = inlineComponentStyles?.get(getFilenameFromPath(id));
268
+ if (componentStyles) {
269
+ return componentStyles;
252
270
  }
253
- const result = await fileEmitter?.(resolve(process.cwd(), decodeURIComponent(componentId).split('@')[0]));
254
- return result?.hmrUpdateCode || '';
255
271
  }
256
272
  return;
257
273
  },
@@ -284,6 +300,19 @@ export function angular(options) {
284
300
  if (id.includes('analog-content-')) {
285
301
  return;
286
302
  }
303
+ /**
304
+ * Encapsulate component stylesheets that use emulated encapsulation
305
+ */
306
+ if (pluginOptions.liveReload && isComponentStyleSheet(id)) {
307
+ const { encapsulation, componentId } = getComponentStyleSheetMeta(id);
308
+ if (encapsulation === 'emulated' && componentId) {
309
+ const encapsulated = ngCompiler.encapsulateStyle(code, componentId);
310
+ return {
311
+ code: encapsulated,
312
+ map: null,
313
+ };
314
+ }
315
+ }
287
316
  if (TS_EXT_REGEX.test(id)) {
288
317
  if (id.includes('.ts?')) {
289
318
  // Strip the query string off the ID
@@ -295,11 +324,17 @@ export function angular(options) {
295
324
  * for test(Vitest)
296
325
  */
297
326
  if (isTest) {
327
+ if (isVitestVscode && !initialCompilation) {
328
+ // Do full initial compilation
329
+ await performCompilation(resolvedConfig);
330
+ initialCompilation = true;
331
+ }
298
332
  const tsMod = viteServer?.moduleGraph.getModuleById(id);
299
333
  if (tsMod) {
300
- sourceFileCache.invalidate([id]);
301
- if (testWatchMode) {
302
- await buildAndAnalyze();
334
+ const invalidated = tsMod.lastInvalidationTimestamp;
335
+ if (testWatchMode && invalidated) {
336
+ sourceFileCache.invalidate([id]);
337
+ await performCompilation(resolvedConfig, [id]);
303
338
  }
304
339
  }
305
340
  }
@@ -314,7 +349,7 @@ export function angular(options) {
314
349
  this.addWatchFile(absoluteFileUrl);
315
350
  }
316
351
  }
317
- const typescriptResult = await fileEmitter?.(id);
352
+ const typescriptResult = fileEmitter(id);
318
353
  if (typescriptResult?.warnings &&
319
354
  typescriptResult?.warnings.length > 0) {
320
355
  this.warn(`${typescriptResult.warnings.join('\n')}`);
@@ -335,25 +370,6 @@ export function angular(options) {
335
370
  data = data.replace(`angular:jit:style:file;${styleFile}`, `${resolvedStyleUrl}?inline`);
336
371
  });
337
372
  }
338
- if (jit) {
339
- return {
340
- code: data,
341
- map: null,
342
- };
343
- }
344
- if ((id.endsWith('.analog') ||
345
- id.endsWith('.agx') ||
346
- id.endsWith('.ag')) &&
347
- pluginOptions.supportAnalogFormat &&
348
- fileEmitter) {
349
- sourceFileCache.invalidate([`${id}.ts`]);
350
- const ngFileResult = await fileEmitter(`${id}.ts`);
351
- data = ngFileResult?.content || '';
352
- if (id.includes('.agx')) {
353
- const metadata = await getFrontmatterMetadata(code, id, pluginOptions.markdownTemplateTransforms || []);
354
- data += metadata;
355
- }
356
- }
357
373
  return {
358
374
  code: data,
359
375
  map: null,
@@ -361,10 +377,17 @@ export function angular(options) {
361
377
  }
362
378
  return undefined;
363
379
  },
380
+ closeBundle() {
381
+ declarationFiles.forEach(({ declarationFileDir, declarationPath, data }) => {
382
+ mkdirSync(declarationFileDir, { recursive: true });
383
+ writeFileSync(declarationPath, data, 'utf-8');
384
+ });
385
+ },
364
386
  };
365
387
  }
366
388
  return [
367
389
  angularPlugin(),
390
+ pluginOptions.liveReload && liveReloadPlugin({ classNames, fileEmitter }),
368
391
  ...(isTest && !isStackBlitz ? angularVitestPlugins() : []),
369
392
  (jit &&
370
393
  jitPlugin({
@@ -374,36 +397,12 @@ export function angular(options) {
374
397
  supportedBrowsers: pluginOptions.supportedBrowsers,
375
398
  jit,
376
399
  }),
377
- (isStorybook && angularStorybookPlugin()),
400
+ (isStorybook &&
401
+ angularStorybookPlugin(pluginOptions.workspaceRoot)),
378
402
  routerPlugin(),
379
403
  pendingTasksPlugin(),
404
+ nxFolderPlugin(),
380
405
  ].filter(Boolean);
381
- function findAnalogFiles(config) {
382
- const analogConfig = pluginOptions.supportAnalogFormat;
383
- if (!analogConfig) {
384
- return [];
385
- }
386
- let extraGlobs = [];
387
- if (typeof analogConfig === 'object') {
388
- if (analogConfig.include) {
389
- extraGlobs = analogConfig.include;
390
- }
391
- }
392
- const fg = require('fast-glob');
393
- const appRoot = normalizePath(resolve(pluginOptions.workspaceRoot, config.root || '.'));
394
- const workspaceRoot = normalizePath(resolve(pluginOptions.workspaceRoot));
395
- const globs = [
396
- `${appRoot}/**/*.{analog,agx,ag}`,
397
- ...extraGlobs.map((glob) => `${workspaceRoot}${glob}.{analog,agx,ag}`),
398
- ...(pluginOptions.additionalContentDirs || [])?.map((glob) => `${workspaceRoot}${glob}/**/*.agx`),
399
- ...pluginOptions.include.map((glob) => `${workspaceRoot}${glob}`.replace(/\.ts$/, '.analog')),
400
- ];
401
- return fg
402
- .sync(globs, {
403
- dot: true,
404
- })
405
- .map((file) => `${file}.ts`);
406
- }
407
406
  function findIncludes() {
408
407
  const fg = require('fast-glob');
409
408
  const workspaceRoot = normalizePath(resolve(pluginOptions.workspaceRoot));
@@ -414,11 +413,28 @@ export function angular(options) {
414
413
  dot: true,
415
414
  });
416
415
  }
417
- function setupCompilation(config, context) {
416
+ function getTsConfigPath(root, options, isProd, isTest, isLib) {
417
+ if (options.tsconfig && isAbsolute(options.tsconfig)) {
418
+ return options.tsconfig;
419
+ }
420
+ let tsconfigFilePath = './tsconfig.app.json';
421
+ if (isLib) {
422
+ tsconfigFilePath = isProd
423
+ ? './tsconfig.lib.prod.json'
424
+ : './tsconfig.lib.json';
425
+ }
426
+ if (isTest) {
427
+ tsconfigFilePath = './tsconfig.spec.json';
428
+ }
429
+ if (options.tsconfig) {
430
+ tsconfigFilePath = options.tsconfig;
431
+ }
432
+ return resolve(root, tsconfigFilePath);
433
+ }
434
+ async function performCompilation(config, ids) {
418
435
  const isProd = config.mode === 'production';
419
- const analogFiles = findAnalogFiles(config);
420
436
  const includeFiles = findIncludes();
421
- const { options: tsCompilerOptions, rootNames: rn } = compilerCli.readConfiguration(pluginOptions.tsconfig, {
437
+ let { options: tsCompilerOptions, rootNames } = compilerCli.readConfiguration(pluginOptions.tsconfig, {
422
438
  suppressOutputPathCheck: true,
423
439
  outDir: undefined,
424
440
  sourceMap: false,
@@ -435,73 +451,149 @@ export function angular(options) {
435
451
  supportTestBed: false,
436
452
  supportJitMode: false,
437
453
  });
438
- if (pluginOptions.supportAnalogFormat) {
439
- // Experimental Local Compilation is necessary
440
- // for the Angular compiler to work with
441
- // AOT and virtually compiled .analog files.
442
- tsCompilerOptions.compilationMode = 'experimental-local';
443
- }
444
- if (pluginOptions.liveReload) {
454
+ if (pluginOptions.liveReload && watchMode) {
445
455
  tsCompilerOptions['_enableHmr'] = true;
456
+ tsCompilerOptions['externalRuntimeStyles'] = true;
457
+ // Workaround for https://github.com/angular/angular/issues/59310
458
+ // Force extra instructions to be generated for HMR w/defer
459
+ tsCompilerOptions['supportTestBed'] = true;
460
+ }
461
+ if (tsCompilerOptions.compilationMode === 'partial') {
462
+ // These options can't be false in partial mode
463
+ tsCompilerOptions['supportTestBed'] = true;
464
+ tsCompilerOptions['supportJitMode'] = true;
465
+ }
466
+ if (!isTest && config.build?.lib) {
467
+ tsCompilerOptions['declaration'] = true;
468
+ tsCompilerOptions['declarationMap'] = watchMode;
469
+ tsCompilerOptions['inlineSources'] = true;
446
470
  }
447
- rootNames = rn.concat(analogFiles, includeFiles);
448
- compilerOptions = tsCompilerOptions;
449
- host = ts.createIncrementalCompilerHost(compilerOptions);
450
- styleTransform = (code, filename) => preprocessCSS(code, filename, config);
471
+ rootNames = rootNames.concat(includeFiles);
472
+ const ts = require('typescript');
473
+ const host = ts.createIncrementalCompilerHost(tsCompilerOptions);
451
474
  if (!jit) {
475
+ const styleTransform = (code, filename) => preprocessCSS(code, filename, config);
476
+ inlineComponentStyles = tsCompilerOptions['externalRuntimeStyles']
477
+ ? new Map()
478
+ : undefined;
479
+ externalComponentStyles = tsCompilerOptions['externalRuntimeStyles']
480
+ ? new Map()
481
+ : undefined;
452
482
  augmentHostWithResources(host, styleTransform, {
453
483
  inlineStylesExtension: pluginOptions.inlineStylesExtension,
454
- supportAnalogFormat: pluginOptions.supportAnalogFormat,
455
484
  isProd,
456
- markdownTemplateTransforms: pluginOptions.markdownTemplateTransforms,
485
+ inlineComponentStyles,
486
+ externalComponentStyles,
457
487
  });
458
488
  }
459
- }
460
- /**
461
- * Creates a new NgtscProgram to analyze/re-analyze
462
- * the source files and create a file emitter.
463
- * This is shared between an initial build and a hot update.
464
- */
465
- async function buildAndAnalyze() {
489
+ /**
490
+ * Creates a new NgtscProgram to analyze/re-analyze
491
+ * the source files and create a file emitter.
492
+ * This is shared between an initial build and a hot update.
493
+ */
466
494
  let builder;
467
495
  let typeScriptProgram;
468
496
  let angularCompiler;
469
497
  if (!jit) {
470
498
  // Create the Angular specific program that contains the Angular compiler
471
- const angularProgram = new compilerCli.NgtscProgram(rootNames, compilerOptions, host, nextProgram);
499
+ const angularProgram = new compilerCli.NgtscProgram(rootNames, tsCompilerOptions, host, nextProgram);
472
500
  angularCompiler = angularProgram.compiler;
473
501
  typeScriptProgram = angularProgram.getTsProgram();
474
502
  augmentProgramWithVersioning(typeScriptProgram);
475
- builder = builderProgram =
476
- ts.createEmitAndSemanticDiagnosticsBuilderProgram(typeScriptProgram, host, builderProgram);
503
+ builder = ts.createEmitAndSemanticDiagnosticsBuilderProgram(typeScriptProgram, host, builderProgram);
477
504
  await angularCompiler.analyzeAsync();
478
505
  nextProgram = angularProgram;
506
+ builderProgram =
507
+ builder;
479
508
  }
480
509
  else {
481
- builder = builderProgram =
482
- ts.createEmitAndSemanticDiagnosticsBuilderProgram(rootNames, compilerOptions, host, nextProgram);
510
+ builder = ts.createEmitAndSemanticDiagnosticsBuilderProgram(rootNames, tsCompilerOptions, host, nextProgram);
483
511
  typeScriptProgram = builder.getProgram();
484
- nextProgram = builderProgram;
485
512
  }
486
513
  if (!watchMode) {
487
514
  // When not in watch mode, the startup cost of the incremental analysis can be avoided by
488
515
  // using an abstract builder that only wraps a TypeScript program.
489
516
  builder = ts.createAbstractBuilder(typeScriptProgram, host);
490
517
  }
491
- const getTypeChecker = () => builder.getProgram().getTypeChecker();
492
- fileEmitter = createFileEmitter(builder, mergeTransformers({
493
- before: [
494
- ...(jit
495
- ? [
496
- compilerCli.constructorParametersDownlevelTransform(builder.getProgram()),
497
- createJitResourceTransformer(getTypeChecker),
498
- ]
499
- : []),
500
- ...pluginOptions.advanced.tsTransformers.before,
501
- ],
502
- after: pluginOptions.advanced.tsTransformers.after,
503
- afterDeclarations: pluginOptions.advanced.tsTransformers.afterDeclarations,
504
- }, jit ? {} : angularCompiler.prepareEmit().transformers), () => [], angularCompiler, pluginOptions.liveReload);
518
+ const beforeTransformers = jit
519
+ ? [
520
+ compilerCli.constructorParametersDownlevelTransform(builder.getProgram()),
521
+ createJitResourceTransformer(() => builder.getProgram().getTypeChecker()),
522
+ ]
523
+ : [];
524
+ const transformers = mergeTransformers({ before: beforeTransformers }, jit ? {} : angularCompiler.prepareEmit().transformers);
525
+ const fileMetadata = getFileMetadata(builder, angularCompiler, pluginOptions.liveReload, pluginOptions.disableTypeChecking);
526
+ const writeFileCallback = (_filename, content, _a, _b, sourceFiles) => {
527
+ if (!sourceFiles?.length) {
528
+ return;
529
+ }
530
+ const filename = normalizePath(sourceFiles[0].fileName);
531
+ if (filename.includes('ngtypecheck.ts') || filename.includes('.d.')) {
532
+ return;
533
+ }
534
+ const metadata = watchMode ? fileMetadata(filename) : {};
535
+ outputFiles.set(filename, {
536
+ content,
537
+ dependencies: [],
538
+ errors: metadata.errors,
539
+ warnings: metadata.warnings,
540
+ hmrUpdateCode: metadata.hmrUpdateCode,
541
+ hmrEligible: metadata.hmrEligible,
542
+ });
543
+ };
544
+ const writeOutputFile = (id) => {
545
+ const sourceFile = builder.getSourceFile(id);
546
+ if (!sourceFile) {
547
+ return;
548
+ }
549
+ let content = '';
550
+ builder.emit(sourceFile, (filename, data) => {
551
+ if (/\.[cm]?js$/.test(filename)) {
552
+ content = data;
553
+ }
554
+ if (!watchMode &&
555
+ !isTest &&
556
+ /\.d\.ts/.test(filename) &&
557
+ !filename.includes('.ngtypecheck.')) {
558
+ // output to library root instead /src
559
+ const declarationPath = resolve(config.root, config.build.outDir, relative(config.root, filename)).replace('/src/', '/');
560
+ const declarationFileDir = declarationPath
561
+ .replace(basename(filename), '')
562
+ .replace('/src/', '/');
563
+ declarationFiles.push({
564
+ declarationFileDir,
565
+ declarationPath,
566
+ data,
567
+ });
568
+ }
569
+ }, undefined /* cancellationToken */, undefined /* emitOnlyDtsFiles */, transformers);
570
+ writeFileCallback(id, content, false, undefined, [sourceFile]);
571
+ };
572
+ if (watchMode) {
573
+ if (ids && ids.length > 0) {
574
+ ids.forEach((id) => writeOutputFile(id));
575
+ }
576
+ else {
577
+ /**
578
+ * Only block the server from starting up
579
+ * during testing.
580
+ */
581
+ if (isTest) {
582
+ // TypeScript will loop until there are no more affected files in the program
583
+ while (builder.emitNextAffectedFile(writeFileCallback, undefined, undefined, transformers)) {
584
+ /* empty */
585
+ }
586
+ }
587
+ }
588
+ }
589
+ if (!isTest) {
590
+ /**
591
+ * Perf: Output files on demand so the dev server
592
+ * isn't blocked when emitting files.
593
+ */
594
+ outputFile = writeOutputFile;
595
+ }
596
+ return { host };
505
597
  }
506
598
  }
507
599
  function sendHMRComponentUpdate(server, id) {
@@ -511,38 +603,114 @@ function sendHMRComponentUpdate(server, id) {
511
603
  });
512
604
  classNames.delete(id);
513
605
  }
514
- export function createFileEmitter(program, transformers = {}, onAfterEmit, angularCompiler, liveReload) {
515
- return async (file) => {
606
+ export function getFileMetadata(program, angularCompiler, liveReload, disableTypeChecking) {
607
+ const ts = require('typescript');
608
+ return (file) => {
516
609
  const sourceFile = program.getSourceFile(file);
517
610
  if (!sourceFile) {
518
- return undefined;
611
+ return {};
519
612
  }
520
- const diagnostics = angularCompiler
521
- ? angularCompiler.getDiagnosticsForFile(sourceFile, 1)
522
- : [];
613
+ const diagnostics = getDiagnosticsForSourceFile(sourceFile, !!disableTypeChecking, program, angularCompiler);
523
614
  const errors = diagnostics
524
615
  .filter((d) => d.category === ts.DiagnosticCategory?.Error)
525
- .map((d) => d.messageText);
616
+ .map((d) => typeof d.messageText === 'object'
617
+ ? d.messageText.messageText
618
+ : d.messageText);
526
619
  const warnings = diagnostics
527
620
  .filter((d) => d.category === ts.DiagnosticCategory?.Warning)
528
621
  .map((d) => d.messageText);
529
622
  let hmrUpdateCode = undefined;
623
+ let hmrEligible = false;
530
624
  if (liveReload) {
531
625
  for (const node of sourceFile.statements) {
532
626
  if (ts.isClassDeclaration(node) && node.name != null) {
533
627
  hmrUpdateCode = angularCompiler?.emitHmrUpdateModule(node);
534
- classNames.set(file, node.name.getText());
628
+ if (!!hmrUpdateCode) {
629
+ classNames.set(file, node.name.getText());
630
+ hmrEligible = true;
631
+ }
535
632
  }
536
633
  }
537
634
  }
538
- let content;
539
- program.emit(sourceFile, (filename, data) => {
540
- if (/\.[cm]?js$/.test(filename)) {
541
- content = data;
542
- }
543
- }, undefined /* cancellationToken */, undefined /* emitOnlyDtsFiles */, transformers);
544
- onAfterEmit?.(sourceFile);
545
- return { content, dependencies: [], errors, warnings, hmrUpdateCode };
635
+ return { errors, warnings, hmrUpdateCode, hmrEligible };
546
636
  };
547
637
  }
638
+ function getDiagnosticsForSourceFile(sourceFile, disableTypeChecking, program, angularCompiler) {
639
+ const syntacticDiagnostics = program.getSyntacticDiagnostics(sourceFile);
640
+ if (disableTypeChecking) {
641
+ // Syntax errors are cheap to compute and the app will not run if there are any
642
+ // So always show these types of errors regardless if type checking is disabled
643
+ return syntacticDiagnostics;
644
+ }
645
+ const semanticDiagnostics = program.getSemanticDiagnostics(sourceFile);
646
+ const angularDiagnostics = angularCompiler
647
+ ? angularCompiler.getDiagnosticsForFile(sourceFile, 1)
648
+ : [];
649
+ return [
650
+ ...syntacticDiagnostics,
651
+ ...semanticDiagnostics,
652
+ ...angularDiagnostics,
653
+ ];
654
+ }
655
+ function markModuleSelfAccepting(mod) {
656
+ // support Vite 6
657
+ if ('_clientModule' in mod) {
658
+ mod['_clientModule'].isSelfAccepting = true;
659
+ }
660
+ return {
661
+ ...mod,
662
+ isSelfAccepting: true,
663
+ };
664
+ }
665
+ function isComponentStyleSheet(id) {
666
+ return id.includes('ngcomp=');
667
+ }
668
+ function getComponentStyleSheetMeta(id) {
669
+ const params = new URL(id, 'http://localhost').searchParams;
670
+ const encapsulationMapping = {
671
+ '0': 'emulated',
672
+ '2': 'none',
673
+ '3': 'shadow',
674
+ };
675
+ return {
676
+ componentId: params.get('ngcomp'),
677
+ encapsulation: encapsulationMapping[params.get('e')],
678
+ };
679
+ }
680
+ /**
681
+ * Removes leading / and query string from a url path
682
+ * e.g. /foo.scss?direct&ngcomp=ng-c3153525609&e=0 returns foo.scss
683
+ * @param id
684
+ */
685
+ function getFilenameFromPath(id) {
686
+ return new URL(id, 'http://localhost').pathname.replace(/^\//, '');
687
+ }
688
+ /**
689
+ * Checks for vitest run from the command line
690
+ * @returns boolean
691
+ */
692
+ export function isTestWatchMode(args = process.argv) {
693
+ // vitest --run
694
+ const hasRun = args.find((arg) => arg.includes('--run'));
695
+ if (hasRun) {
696
+ return false;
697
+ }
698
+ // vitest --no-run
699
+ const hasNoRun = args.find((arg) => arg.includes('--no-run'));
700
+ if (hasNoRun) {
701
+ return true;
702
+ }
703
+ // check for --watch=false or --no-watch
704
+ const hasWatch = args.find((arg) => arg.includes('watch'));
705
+ if (hasWatch && ['false', 'no'].some((neg) => hasWatch.includes(neg))) {
706
+ return false;
707
+ }
708
+ // check for --watch false
709
+ const watchIndex = args.findIndex((arg) => arg.includes('watch'));
710
+ const watchArg = args[watchIndex + 1];
711
+ if (watchArg && watchArg === 'false') {
712
+ return false;
713
+ }
714
+ return true;
715
+ }
548
716
  //# sourceMappingURL=angular-vite-plugin.js.map