@analogjs/vite-plugin-angular 2.0.0-alpha.9 → 2.0.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/package.json +9 -5
  2. package/src/index.d.ts +0 -2
  3. package/src/index.js +0 -2
  4. package/src/index.js.map +1 -1
  5. package/src/lib/angular-pending-tasks.plugin.js +2 -1
  6. package/src/lib/angular-pending-tasks.plugin.js.map +1 -1
  7. package/src/lib/angular-storybook-plugin.d.ts +2 -2
  8. package/src/lib/angular-storybook-plugin.js +11 -4
  9. package/src/lib/angular-storybook-plugin.js.map +1 -1
  10. package/src/lib/angular-vite-plugin.d.ts +14 -24
  11. package/src/lib/angular-vite-plugin.js +224 -220
  12. package/src/lib/angular-vite-plugin.js.map +1 -1
  13. package/src/lib/component-resolvers.js +1 -1
  14. package/src/lib/component-resolvers.js.map +1 -1
  15. package/src/lib/host.d.ts +0 -5
  16. package/src/lib/host.js +5 -80
  17. package/src/lib/host.js.map +1 -1
  18. package/src/lib/live-reload-plugin.d.ts +6 -0
  19. package/src/lib/live-reload-plugin.js +63 -0
  20. package/src/lib/live-reload-plugin.js.map +1 -0
  21. package/src/lib/models.d.ts +11 -0
  22. package/src/lib/models.js +2 -0
  23. package/src/lib/models.js.map +1 -0
  24. package/src/lib/nx-folder-plugin.d.ts +7 -0
  25. package/src/lib/nx-folder-plugin.js +18 -0
  26. package/src/lib/nx-folder-plugin.js.map +1 -0
  27. package/src/lib/plugins/file-replacements.plugin.d.ts +12 -0
  28. package/src/lib/plugins/file-replacements.plugin.js +64 -0
  29. package/src/lib/plugins/file-replacements.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 +8 -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 +38 -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 +62 -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.js +40 -12
  49. package/src/lib/utils/hmr-candidates.js.map +1 -1
  50. package/esbuild.d.ts +0 -3
  51. package/esbuild.js +0 -31
  52. package/esbuild.js.map +0 -1
  53. package/src/lib/authoring/analog.d.ts +0 -1
  54. package/src/lib/authoring/analog.js +0 -550
  55. package/src/lib/authoring/analog.js.map +0 -1
  56. package/src/lib/authoring/constants.d.ts +0 -26
  57. package/src/lib/authoring/constants.js +0 -47
  58. package/src/lib/authoring/constants.js.map +0 -1
  59. package/src/lib/authoring/frontmatter.d.ts +0 -2
  60. package/src/lib/authoring/frontmatter.js +0 -26
  61. package/src/lib/authoring/frontmatter.js.map +0 -1
  62. package/src/lib/authoring/markdown-transform.d.ts +0 -4
  63. package/src/lib/authoring/markdown-transform.js +0 -19
  64. package/src/lib/authoring/markdown-transform.js.map +0 -1
  65. package/src/lib/authoring/marked-setup.service.d.ts +0 -23
  66. package/src/lib/authoring/marked-setup.service.js +0 -118
  67. package/src/lib/authoring/marked-setup.service.js.map +0 -1
@@ -1,30 +1,29 @@
1
- import { dirname, relative, resolve } from 'node:path';
1
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
2
+ import { basename, dirname, isAbsolute, join, relative, resolve, } from 'node:path';
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';
6
5
  import * as ngCompiler from '@angular/compiler';
6
+ import { defaultClientConditions, normalizePath, preprocessCSS, } from 'vite';
7
+ import { buildOptimizerPlugin } from './angular-build-optimizer-plugin.js';
8
+ import { jitPlugin } from './angular-jit-plugin.js';
7
9
  import { createCompilerPlugin } from './compiler-plugin.js';
8
10
  import { StyleUrlsResolver, TemplateUrlsResolver, } from './component-resolvers.js';
9
11
  import { augmentHostWithCaching, augmentHostWithResources, augmentProgramWithVersioning, mergeTransformers, } from './host.js';
10
- import { jitPlugin } from './angular-jit-plugin.js';
11
- import { buildOptimizerPlugin } from './angular-build-optimizer-plugin.js';
12
- import { createJitResourceTransformer, SourceFileCache, angularMajor, } from './utils/devkit.js';
13
- import { angularVitestPlugins } from './angular-vitest-plugin.js';
14
12
  import { angularStorybookPlugin } from './angular-storybook-plugin.js';
13
+ import { angularVitestPlugins } from './angular-vitest-plugin.js';
14
+ import { angularMajor, createJitResourceTransformer, SourceFileCache, } from './utils/devkit.js';
15
15
  const require = createRequire(import.meta.url);
16
- import { getFrontmatterMetadata } from './authoring/frontmatter.js';
17
- import { defaultMarkdownTemplateTransforms, } from './authoring/markdown-transform.js';
18
- import { routerPlugin } from './router-plugin.js';
19
16
  import { pendingTasksPlugin } from './angular-pending-tasks.plugin.js';
20
- import { analyzeFileUpdates } from './utils/hmr-candidates.js';
17
+ import { liveReloadPlugin } from './live-reload-plugin.js';
18
+ import { nxFolderPlugin } from './nx-folder-plugin.js';
19
+ import { replaceFiles, } from './plugins/file-replacements.plugin.js';
20
+ import { routerPlugin } from './router-plugin.js';
21
21
  /**
22
22
  * TypeScript file extension regex
23
23
  * Match .(c or m)ts, .ts extensions with an optional ? for query params
24
24
  * Ignore .tsx extensions
25
25
  */
26
26
  const TS_EXT_REGEX = /\.[cm]?(ts|analog|ag)[^x]?\??/;
27
- const ANGULAR_COMPONENT_PREFIX = '/@ng/component';
28
27
  const classNames = new Map();
29
28
  export function angular(options) {
30
29
  /**
@@ -32,10 +31,7 @@ export function angular(options) {
32
31
  * are used for values not provided.
33
32
  */
34
33
  const pluginOptions = {
35
- tsconfig: options?.tsconfig ??
36
- (process.env['NODE_ENV'] === 'test'
37
- ? './tsconfig.spec.json'
38
- : './tsconfig.app.json'),
34
+ tsconfigGetter: createTsConfigGetter(options?.tsconfig),
39
35
  workspaceRoot: options?.workspaceRoot ?? process.cwd(),
40
36
  inlineStylesExtension: options?.inlineStylesExtension ?? 'css',
41
37
  advanced: {
@@ -46,24 +42,17 @@ export function angular(options) {
46
42
  },
47
43
  },
48
44
  supportedBrowsers: options?.supportedBrowsers ?? ['safari 15'],
49
- jit: options?.experimental?.supportAnalogFormat ? false : options?.jit,
50
- supportAnalogFormat: options?.experimental?.supportAnalogFormat ?? false,
51
- markdownTemplateTransforms: options?.experimental
52
- ?.markdownTemplateTransforms?.length
53
- ? options.experimental.markdownTemplateTransforms
54
- : defaultMarkdownTemplateTransforms,
45
+ jit: options?.jit,
55
46
  include: options?.include ?? [],
56
47
  additionalContentDirs: options?.additionalContentDirs ?? [],
57
48
  liveReload: options?.liveReload ?? false,
58
49
  disableTypeChecking: options?.disableTypeChecking ?? true,
50
+ fileReplacements: options?.fileReplacements ?? [],
59
51
  };
60
- // The file emitter created during `onStart` that will be used during the build in `onLoad` callbacks for TS files
61
- let fileEmitter;
62
- let compilerOptions = {};
63
- const ts = require('typescript');
64
52
  let resolvedConfig;
65
- let rootNames;
66
- let host;
53
+ let resolvedTsConfigPath = '';
54
+ // Store config context needed for getTsConfigPath resolution
55
+ let tsConfigResolutionContext = null;
67
56
  let nextProgram;
68
57
  let builderProgram;
69
58
  let watchMode = false;
@@ -72,6 +61,7 @@ export function angular(options) {
72
61
  let externalComponentStyles;
73
62
  const sourceFileCache = new SourceFileCache();
74
63
  const isTest = process.env['NODE_ENV'] === 'test' || !!process.env['VITEST'];
64
+ const isVitestVscode = !!process.env['VITEST_VSCODE'];
75
65
  const isStackBlitz = !!process.versions['webcontainer'];
76
66
  const isAstroIntegration = process.env['ANALOG_ASTRO'] === 'true';
77
67
  const isStorybook = process.env['npm_lifecycle_script']?.includes('storybook') ||
@@ -80,9 +70,16 @@ export function angular(options) {
80
70
  process.env['ANALOG_STORYBOOK'] === 'true';
81
71
  const jit = typeof pluginOptions?.jit !== 'undefined' ? pluginOptions.jit : isTest;
82
72
  let viteServer;
83
- let styleTransform;
84
73
  const styleUrlsResolver = new StyleUrlsResolver();
85
74
  const templateUrlsResolver = new TemplateUrlsResolver();
75
+ let outputFile;
76
+ const outputFiles = new Map();
77
+ const fileEmitter = (file) => {
78
+ outputFile?.(file);
79
+ return outputFiles.get(normalizePath(file));
80
+ };
81
+ let initialCompilation = false;
82
+ const declarationFiles = [];
86
83
  function angularPlugin() {
87
84
  let isProd = false;
88
85
  if (angularMajor < 19 || isTest) {
@@ -90,21 +87,19 @@ export function angular(options) {
90
87
  }
91
88
  return {
92
89
  name: '@analogjs/vite-plugin-angular',
93
- async watchChange() {
94
- if (isTest) {
95
- await buildAndAnalyze();
96
- }
97
- },
98
90
  async config(config, { command }) {
99
91
  watchMode = command === 'serve';
100
92
  isProd =
101
93
  config.mode === 'production' ||
102
94
  process.env['NODE_ENV'] === 'production';
103
- pluginOptions.tsconfig =
104
- options?.tsconfig ??
105
- resolve(config.root || '.', process.env['NODE_ENV'] === 'test'
106
- ? './tsconfig.spec.json'
107
- : './tsconfig.app.json');
95
+ // Store the config context for later resolution in configResolved
96
+ tsConfigResolutionContext = {
97
+ root: config.root || '.',
98
+ isProd,
99
+ isLib: !!config?.build?.lib,
100
+ };
101
+ // Do a preliminary resolution for esbuild plugin (before configResolved)
102
+ const preliminaryTsConfigPath = getTsConfigPath(tsConfigResolutionContext.root, pluginOptions.tsconfigGetter(), tsConfigResolutionContext.isProd, isTest, tsConfigResolutionContext.isLib);
108
103
  return {
109
104
  esbuild: config.esbuild ?? false,
110
105
  optimizeDeps: {
@@ -113,7 +108,7 @@ export function angular(options) {
113
108
  esbuildOptions: {
114
109
  plugins: [
115
110
  createCompilerPlugin({
116
- tsconfig: pluginOptions.tsconfig,
111
+ tsconfig: preliminaryTsConfigPath,
117
112
  sourcemap: !isProd,
118
113
  advancedOptimizations: isProd,
119
114
  jit,
@@ -128,12 +123,20 @@ export function angular(options) {
128
123
  },
129
124
  },
130
125
  resolve: {
131
- conditions: ['style'],
126
+ conditions: [
127
+ 'style',
128
+ ...(config.resolve?.conditions || defaultClientConditions),
129
+ ],
132
130
  },
133
131
  };
134
132
  },
135
133
  configResolved(config) {
136
134
  resolvedConfig = config;
135
+ // resolve the tsconfig path after config is fully resolved
136
+ if (tsConfigResolutionContext) {
137
+ const tsconfigValue = pluginOptions.tsconfigGetter();
138
+ resolvedTsConfigPath = getTsConfigPath(tsConfigResolutionContext.root, tsconfigValue, tsConfigResolutionContext.isProd, isTest, tsConfigResolutionContext.isLib);
139
+ }
137
140
  if (isTest) {
138
141
  // set test watch mode
139
142
  // - vite override from vitest-angular
@@ -149,76 +152,30 @@ export function angular(options) {
149
152
  configureServer(server) {
150
153
  viteServer = server;
151
154
  server.watcher.on('add', async () => {
152
- setupCompilation(resolvedConfig);
153
- await buildAndAnalyze();
155
+ await performCompilation(resolvedConfig);
154
156
  });
155
157
  server.watcher.on('unlink', async () => {
156
- setupCompilation(resolvedConfig);
157
- await buildAndAnalyze();
158
+ await performCompilation(resolvedConfig);
158
159
  });
159
- if (pluginOptions.liveReload) {
160
- const angularComponentMiddleware = async (req, res, next) => {
161
- if (req.url === undefined || res.writableEnded) {
162
- return;
163
- }
164
- if (!req.url.includes(ANGULAR_COMPONENT_PREFIX)) {
165
- next();
166
- return;
167
- }
168
- const requestUrl = new URL(req.url, 'http://localhost');
169
- const componentId = requestUrl.searchParams.get('c');
170
- if (!componentId) {
171
- res.statusCode = 400;
172
- res.end();
173
- return;
174
- }
175
- const [fileId] = decodeURIComponent(componentId).split('@');
176
- const resolvedId = resolve(process.cwd(), fileId);
177
- const invalidated = !!server.moduleGraph.getModuleById(resolvedId)
178
- ?.lastInvalidationTimestamp && classNames.get(resolvedId);
179
- // don't send an HMR update until the file has been invalidated
180
- if (!invalidated) {
181
- res.setHeader('Content-Type', 'text/javascript');
182
- res.setHeader('Cache-Control', 'no-cache');
183
- res.end('');
184
- return;
185
- }
186
- const result = await fileEmitter?.(resolvedId);
187
- res.setHeader('Content-Type', 'text/javascript');
188
- res.setHeader('Cache-Control', 'no-cache');
189
- res.end(`${result?.hmrUpdateCode || ''}`);
190
- };
191
- viteServer.middlewares.use(angularComponentMiddleware);
192
- }
193
160
  },
194
161
  async buildStart() {
195
- setupCompilation(resolvedConfig);
196
- // Only store cache if in watch mode
197
- if (watchMode) {
198
- augmentHostWithCaching(host, sourceFileCache);
162
+ // Defer the first compilation in test mode
163
+ if (!isVitestVscode) {
164
+ const { host } = await performCompilation(resolvedConfig);
165
+ initialCompilation = true;
166
+ // Only store cache if in watch mode
167
+ if (watchMode) {
168
+ augmentHostWithCaching(host, sourceFileCache);
169
+ }
199
170
  }
200
- await buildAndAnalyze();
201
171
  },
202
172
  async handleHotUpdate(ctx) {
203
- // The `handleHotUpdate` hook may be called before the `buildStart`,
204
- // which sets the compilation. As a result, the `host` may not be available
205
- // yet for use, leading to build errors such as "cannot read properties of undefined"
206
- // (because `host` is undefined).
207
- if (!host) {
208
- return;
209
- }
210
173
  if (TS_EXT_REGEX.test(ctx.file)) {
211
174
  let [fileId] = ctx.file.split('?');
212
- if (pluginOptions.supportAnalogFormat &&
213
- ['ag', 'analog', 'agx'].some((ext) => fileId.endsWith(ext))) {
214
- fileId += '.ts';
215
- }
216
- const stale = sourceFileCache.get(fileId);
217
- sourceFileCache.invalidate([fileId]);
218
- await buildAndAnalyze();
219
- const result = await fileEmitter?.(fileId, stale);
175
+ await performCompilation(resolvedConfig, [fileId]);
176
+ const result = fileEmitter(fileId);
220
177
  if (pluginOptions.liveReload &&
221
- !!result?.hmrEligible &&
178
+ result?.hmrEligible &&
222
179
  classNames.get(fileId)) {
223
180
  const relativeFileId = `${relative(process.cwd(), fileId)}@${classNames.get(fileId)}`;
224
181
  sendHMRComponentUpdate(ctx.server, relativeFileId);
@@ -236,7 +193,8 @@ export function angular(options) {
236
193
  * for an external resource (styles, html).
237
194
  */
238
195
  const isDirect = ctx.modules.find((mod) => ctx.file === mod.file && mod.id?.includes('?direct'));
239
- if (isDirect) {
196
+ const isInline = ctx.modules.find((mod) => ctx.file === mod.file && mod.id?.includes('?inline'));
197
+ if (isDirect || isInline) {
240
198
  if (pluginOptions.liveReload && isDirect?.id && isDirect.file) {
241
199
  const isComponentStyle = isDirect.type === 'css' && isComponentStyleSheet(isDirect.id);
242
200
  if (isComponentStyle) {
@@ -287,7 +245,10 @@ export function angular(options) {
287
245
  }
288
246
  });
289
247
  });
290
- await buildAndAnalyze();
248
+ await performCompilation(resolvedConfig, [
249
+ ...mods.map((mod) => mod.id),
250
+ ...updates,
251
+ ]);
291
252
  if (updates.length > 0) {
292
253
  updates.forEach((updateId) => {
293
254
  const impRelativeFileId = `${relative(process.cwd(), updateId)}@${classNames.get(updateId)}`;
@@ -320,7 +281,7 @@ export function angular(options) {
320
281
  }
321
282
  return undefined;
322
283
  },
323
- async load(id, options) {
284
+ async load(id) {
324
285
  // Map angular inline styles to the source text
325
286
  if (isComponentStyleSheet(id)) {
326
287
  const componentStyles = inlineComponentStyles?.get(getFilenameFromPath(id));
@@ -328,17 +289,6 @@ export function angular(options) {
328
289
  return componentStyles;
329
290
  }
330
291
  }
331
- if (pluginOptions.liveReload &&
332
- options?.ssr &&
333
- id.includes(ANGULAR_COMPONENT_PREFIX)) {
334
- const requestUrl = new URL(id.slice(1), 'http://localhost');
335
- const componentId = requestUrl.searchParams.get('c');
336
- if (!componentId) {
337
- return;
338
- }
339
- const result = await fileEmitter?.(resolve(process.cwd(), decodeURIComponent(componentId).split('@')[0]));
340
- return result?.hmrUpdateCode || '';
341
- }
342
292
  return;
343
293
  },
344
294
  async transform(code, id) {
@@ -394,12 +344,17 @@ export function angular(options) {
394
344
  * for test(Vitest)
395
345
  */
396
346
  if (isTest) {
347
+ if (isVitestVscode && !initialCompilation) {
348
+ // Do full initial compilation
349
+ await performCompilation(resolvedConfig);
350
+ initialCompilation = true;
351
+ }
397
352
  const tsMod = viteServer?.moduleGraph.getModuleById(id);
398
353
  if (tsMod) {
399
354
  const invalidated = tsMod.lastInvalidationTimestamp;
400
355
  if (testWatchMode && invalidated) {
401
356
  sourceFileCache.invalidate([id]);
402
- await buildAndAnalyze();
357
+ await performCompilation(resolvedConfig, [id]);
403
358
  }
404
359
  }
405
360
  }
@@ -414,7 +369,7 @@ export function angular(options) {
414
369
  this.addWatchFile(absoluteFileUrl);
415
370
  }
416
371
  }
417
- const typescriptResult = await fileEmitter?.(id);
372
+ const typescriptResult = fileEmitter(id);
418
373
  if (typescriptResult?.warnings &&
419
374
  typescriptResult?.warnings.length > 0) {
420
375
  this.warn(`${typescriptResult.warnings.join('\n')}`);
@@ -435,25 +390,6 @@ export function angular(options) {
435
390
  data = data.replace(`angular:jit:style:file;${styleFile}`, `${resolvedStyleUrl}?inline`);
436
391
  });
437
392
  }
438
- if (jit) {
439
- return {
440
- code: data,
441
- map: null,
442
- };
443
- }
444
- if ((id.endsWith('.analog') ||
445
- id.endsWith('.agx') ||
446
- id.endsWith('.ag')) &&
447
- pluginOptions.supportAnalogFormat &&
448
- fileEmitter) {
449
- sourceFileCache.invalidate([`${id}.ts`]);
450
- const ngFileResult = await fileEmitter(`${id}.ts`);
451
- data = ngFileResult?.content || '';
452
- if (id.includes('.agx')) {
453
- const metadata = await getFrontmatterMetadata(code, id, pluginOptions.markdownTemplateTransforms || []);
454
- data += metadata;
455
- }
456
- }
457
393
  return {
458
394
  code: data,
459
395
  map: null,
@@ -461,10 +397,18 @@ export function angular(options) {
461
397
  }
462
398
  return undefined;
463
399
  },
400
+ closeBundle() {
401
+ declarationFiles.forEach(({ declarationFileDir, declarationPath, data }) => {
402
+ mkdirSync(declarationFileDir, { recursive: true });
403
+ writeFileSync(declarationPath, data, 'utf-8');
404
+ });
405
+ },
464
406
  };
465
407
  }
466
408
  return [
409
+ replaceFiles(pluginOptions.fileReplacements, pluginOptions.workspaceRoot),
467
410
  angularPlugin(),
411
+ pluginOptions.liveReload && liveReloadPlugin({ classNames, fileEmitter }),
468
412
  ...(isTest && !isStackBlitz ? angularVitestPlugins() : []),
469
413
  (jit &&
470
414
  jitPlugin({
@@ -474,36 +418,12 @@ export function angular(options) {
474
418
  supportedBrowsers: pluginOptions.supportedBrowsers,
475
419
  jit,
476
420
  }),
477
- (isStorybook && angularStorybookPlugin()),
421
+ (isStorybook &&
422
+ angularStorybookPlugin(pluginOptions.workspaceRoot)),
478
423
  routerPlugin(),
479
424
  pendingTasksPlugin(),
425
+ nxFolderPlugin(),
480
426
  ].filter(Boolean);
481
- function findAnalogFiles(config) {
482
- const analogConfig = pluginOptions.supportAnalogFormat;
483
- if (!analogConfig) {
484
- return [];
485
- }
486
- let extraGlobs = [];
487
- if (typeof analogConfig === 'object') {
488
- if (analogConfig.include) {
489
- extraGlobs = analogConfig.include;
490
- }
491
- }
492
- const fg = require('fast-glob');
493
- const appRoot = normalizePath(resolve(pluginOptions.workspaceRoot, config.root || '.'));
494
- const workspaceRoot = normalizePath(resolve(pluginOptions.workspaceRoot));
495
- const globs = [
496
- `${appRoot}/**/*.{analog,agx,ag}`,
497
- ...extraGlobs.map((glob) => `${workspaceRoot}${glob}.{analog,agx,ag}`),
498
- ...(pluginOptions.additionalContentDirs || [])?.map((glob) => `${workspaceRoot}${glob}/**/*.agx`),
499
- ...pluginOptions.include.map((glob) => `${workspaceRoot}${glob}`.replace(/\.ts$/, '.analog')),
500
- ];
501
- return fg
502
- .sync(globs, {
503
- dot: true,
504
- })
505
- .map((file) => `${file}.ts`);
506
- }
507
427
  function findIncludes() {
508
428
  const fg = require('fast-glob');
509
429
  const workspaceRoot = normalizePath(resolve(pluginOptions.workspaceRoot));
@@ -514,11 +434,41 @@ export function angular(options) {
514
434
  dot: true,
515
435
  });
516
436
  }
517
- function setupCompilation(config, context) {
437
+ function createTsConfigGetter(tsconfigOrGetter) {
438
+ if (typeof tsconfigOrGetter === 'function') {
439
+ return tsconfigOrGetter;
440
+ }
441
+ return () => tsconfigOrGetter || '';
442
+ }
443
+ function getTsConfigPath(root, tsconfig, isProd, isTest, isLib) {
444
+ if (tsconfig && isAbsolute(tsconfig)) {
445
+ if (!existsSync(tsconfig)) {
446
+ console.error(`[@analogjs/vite-plugin-angular]: Unable to resolve tsconfig at ${tsconfig}. This causes compilation issues. Check the path or set the "tsconfig" property with an absolute path.`);
447
+ }
448
+ return tsconfig;
449
+ }
450
+ let tsconfigFilePath = './tsconfig.app.json';
451
+ if (isLib) {
452
+ tsconfigFilePath = isProd
453
+ ? './tsconfig.lib.prod.json'
454
+ : './tsconfig.lib.json';
455
+ }
456
+ if (isTest) {
457
+ tsconfigFilePath = './tsconfig.spec.json';
458
+ }
459
+ if (tsconfig) {
460
+ tsconfigFilePath = tsconfig;
461
+ }
462
+ const resolvedPath = resolve(root, tsconfigFilePath);
463
+ if (!existsSync(resolvedPath)) {
464
+ console.error(`[@analogjs/vite-plugin-angular]: Unable to resolve tsconfig at ${resolvedPath}. This causes compilation issues. Check the path or set the "tsconfig" property with an absolute path.`);
465
+ }
466
+ return resolvedPath;
467
+ }
468
+ async function performCompilation(config, ids) {
518
469
  const isProd = config.mode === 'production';
519
- const analogFiles = findAnalogFiles(config);
520
470
  const includeFiles = findIncludes();
521
- const { options: tsCompilerOptions, rootNames: rn } = compilerCli.readConfiguration(pluginOptions.tsconfig, {
471
+ let { options: tsCompilerOptions, rootNames } = compilerCli.readConfiguration(resolvedTsConfigPath, {
522
472
  suppressOutputPathCheck: true,
523
473
  outDir: undefined,
524
474
  sourceMap: false,
@@ -535,12 +485,6 @@ export function angular(options) {
535
485
  supportTestBed: false,
536
486
  supportJitMode: false,
537
487
  });
538
- if (pluginOptions.supportAnalogFormat) {
539
- // Experimental Local Compilation is necessary
540
- // for the Angular compiler to work with
541
- // AOT and virtually compiled .analog files.
542
- tsCompilerOptions.compilationMode = 'experimental-local';
543
- }
544
488
  if (pluginOptions.liveReload && watchMode) {
545
489
  tsCompilerOptions['_enableHmr'] = true;
546
490
  tsCompilerOptions['externalRuntimeStyles'] = true;
@@ -553,11 +497,17 @@ export function angular(options) {
553
497
  tsCompilerOptions['supportTestBed'] = true;
554
498
  tsCompilerOptions['supportJitMode'] = true;
555
499
  }
556
- rootNames = rn.concat(analogFiles, includeFiles);
557
- compilerOptions = tsCompilerOptions;
558
- host = ts.createIncrementalCompilerHost(compilerOptions);
559
- styleTransform = (code, filename) => preprocessCSS(code, filename, config);
500
+ if (!isTest && config.build?.lib) {
501
+ tsCompilerOptions['declaration'] = true;
502
+ tsCompilerOptions['declarationMap'] = watchMode;
503
+ tsCompilerOptions['inlineSources'] = true;
504
+ }
505
+ const replacements = pluginOptions.fileReplacements.map((rp) => join(pluginOptions.workspaceRoot, rp.ssr || rp.with));
506
+ rootNames = rootNames.concat(includeFiles, replacements);
507
+ const ts = require('typescript');
508
+ const host = ts.createIncrementalCompilerHost(tsCompilerOptions);
560
509
  if (!jit) {
510
+ const styleTransform = (code, filename) => preprocessCSS(code, filename, config);
561
511
  inlineComponentStyles = tsCompilerOptions['externalRuntimeStyles']
562
512
  ? new Map()
563
513
  : undefined;
@@ -566,59 +516,119 @@ export function angular(options) {
566
516
  : undefined;
567
517
  augmentHostWithResources(host, styleTransform, {
568
518
  inlineStylesExtension: pluginOptions.inlineStylesExtension,
569
- supportAnalogFormat: pluginOptions.supportAnalogFormat,
570
519
  isProd,
571
- markdownTemplateTransforms: pluginOptions.markdownTemplateTransforms,
572
520
  inlineComponentStyles,
573
521
  externalComponentStyles,
574
522
  });
575
523
  }
576
- }
577
- /**
578
- * Creates a new NgtscProgram to analyze/re-analyze
579
- * the source files and create a file emitter.
580
- * This is shared between an initial build and a hot update.
581
- */
582
- async function buildAndAnalyze() {
524
+ /**
525
+ * Creates a new NgtscProgram to analyze/re-analyze
526
+ * the source files and create a file emitter.
527
+ * This is shared between an initial build and a hot update.
528
+ */
583
529
  let builder;
584
530
  let typeScriptProgram;
585
531
  let angularCompiler;
586
532
  if (!jit) {
587
533
  // Create the Angular specific program that contains the Angular compiler
588
- const angularProgram = new compilerCli.NgtscProgram(rootNames, compilerOptions, host, nextProgram);
534
+ const angularProgram = new compilerCli.NgtscProgram(rootNames, tsCompilerOptions, host, nextProgram);
589
535
  angularCompiler = angularProgram.compiler;
590
536
  typeScriptProgram = angularProgram.getTsProgram();
591
537
  augmentProgramWithVersioning(typeScriptProgram);
592
- builder = builderProgram =
593
- ts.createEmitAndSemanticDiagnosticsBuilderProgram(typeScriptProgram, host, builderProgram);
538
+ builder = ts.createEmitAndSemanticDiagnosticsBuilderProgram(typeScriptProgram, host, builderProgram);
594
539
  await angularCompiler.analyzeAsync();
595
540
  nextProgram = angularProgram;
541
+ builderProgram =
542
+ builder;
596
543
  }
597
544
  else {
598
- builder = builderProgram =
599
- ts.createEmitAndSemanticDiagnosticsBuilderProgram(rootNames, compilerOptions, host, nextProgram);
545
+ builder = ts.createEmitAndSemanticDiagnosticsBuilderProgram(rootNames, tsCompilerOptions, host, nextProgram);
600
546
  typeScriptProgram = builder.getProgram();
601
- nextProgram = builderProgram;
602
547
  }
603
548
  if (!watchMode) {
604
549
  // When not in watch mode, the startup cost of the incremental analysis can be avoided by
605
550
  // using an abstract builder that only wraps a TypeScript program.
606
551
  builder = ts.createAbstractBuilder(typeScriptProgram, host);
607
552
  }
608
- const getTypeChecker = () => builder.getProgram().getTypeChecker();
609
- fileEmitter = createFileEmitter(builder, mergeTransformers({
610
- before: [
611
- ...(jit
612
- ? [
613
- compilerCli.constructorParametersDownlevelTransform(builder.getProgram()),
614
- createJitResourceTransformer(getTypeChecker),
615
- ]
616
- : []),
617
- ...pluginOptions.advanced.tsTransformers.before,
618
- ],
619
- after: pluginOptions.advanced.tsTransformers.after,
620
- afterDeclarations: pluginOptions.advanced.tsTransformers.afterDeclarations,
621
- }, jit ? {} : angularCompiler.prepareEmit().transformers), () => [], angularCompiler, pluginOptions.liveReload, pluginOptions.disableTypeChecking);
553
+ const beforeTransformers = jit
554
+ ? [
555
+ compilerCli.constructorParametersDownlevelTransform(builder.getProgram()),
556
+ createJitResourceTransformer(() => builder.getProgram().getTypeChecker()),
557
+ ]
558
+ : [];
559
+ const transformers = mergeTransformers({ before: beforeTransformers }, jit ? {} : angularCompiler.prepareEmit().transformers);
560
+ const fileMetadata = getFileMetadata(builder, angularCompiler, pluginOptions.liveReload, pluginOptions.disableTypeChecking);
561
+ const writeFileCallback = (_filename, content, _a, _b, sourceFiles) => {
562
+ if (!sourceFiles?.length) {
563
+ return;
564
+ }
565
+ const filename = normalizePath(sourceFiles[0].fileName);
566
+ if (filename.includes('ngtypecheck.ts') || filename.includes('.d.')) {
567
+ return;
568
+ }
569
+ const metadata = watchMode ? fileMetadata(filename) : {};
570
+ outputFiles.set(filename, {
571
+ content,
572
+ dependencies: [],
573
+ errors: metadata.errors,
574
+ warnings: metadata.warnings,
575
+ hmrUpdateCode: metadata.hmrUpdateCode,
576
+ hmrEligible: metadata.hmrEligible,
577
+ });
578
+ };
579
+ const writeOutputFile = (id) => {
580
+ const sourceFile = builder.getSourceFile(id);
581
+ if (!sourceFile) {
582
+ return;
583
+ }
584
+ let content = '';
585
+ builder.emit(sourceFile, (filename, data) => {
586
+ if (/\.[cm]?js$/.test(filename)) {
587
+ content = data;
588
+ }
589
+ if (!watchMode &&
590
+ !isTest &&
591
+ /\.d\.ts/.test(filename) &&
592
+ !filename.includes('.ngtypecheck.')) {
593
+ // output to library root instead /src
594
+ const declarationPath = resolve(config.root, config.build.outDir, relative(config.root, filename)).replace('/src/', '/');
595
+ const declarationFileDir = declarationPath
596
+ .replace(basename(filename), '')
597
+ .replace('/src/', '/');
598
+ declarationFiles.push({
599
+ declarationFileDir,
600
+ declarationPath,
601
+ data,
602
+ });
603
+ }
604
+ }, undefined /* cancellationToken */, undefined /* emitOnlyDtsFiles */, transformers);
605
+ writeFileCallback(id, content, false, undefined, [sourceFile]);
606
+ };
607
+ if (watchMode) {
608
+ if (ids && ids.length > 0) {
609
+ ids.forEach((id) => writeOutputFile(id));
610
+ }
611
+ else {
612
+ /**
613
+ * Only block the server from starting up
614
+ * during testing.
615
+ */
616
+ if (isTest) {
617
+ // TypeScript will loop until there are no more affected files in the program
618
+ while (builder.emitNextAffectedFile(writeFileCallback, undefined, undefined, transformers)) {
619
+ /* empty */
620
+ }
621
+ }
622
+ }
623
+ }
624
+ if (!isTest) {
625
+ /**
626
+ * Perf: Output files on demand so the dev server
627
+ * isn't blocked when emitting files.
628
+ */
629
+ outputFile = writeOutputFile;
630
+ }
631
+ return { host };
622
632
  }
623
633
  }
624
634
  function sendHMRComponentUpdate(server, id) {
@@ -628,15 +638,12 @@ function sendHMRComponentUpdate(server, id) {
628
638
  });
629
639
  classNames.delete(id);
630
640
  }
631
- export function createFileEmitter(program, transformers = {}, onAfterEmit, angularCompiler, liveReload, disableTypeChecking) {
632
- return async (file, stale) => {
641
+ export function getFileMetadata(program, angularCompiler, liveReload, disableTypeChecking) {
642
+ const ts = require('typescript');
643
+ return (file) => {
633
644
  const sourceFile = program.getSourceFile(file);
634
645
  if (!sourceFile) {
635
- return undefined;
636
- }
637
- if (stale) {
638
- const hmrEligible = !!analyzeFileUpdates(stale, sourceFile, angularCompiler);
639
- return { dependencies: [], hmrEligible };
646
+ return {};
640
647
  }
641
648
  const diagnostics = getDiagnosticsForSourceFile(sourceFile, !!disableTypeChecking, program, angularCompiler);
642
649
  const errors = diagnostics
@@ -648,22 +655,19 @@ export function createFileEmitter(program, transformers = {}, onAfterEmit, angul
648
655
  .filter((d) => d.category === ts.DiagnosticCategory?.Warning)
649
656
  .map((d) => d.messageText);
650
657
  let hmrUpdateCode = undefined;
658
+ let hmrEligible = false;
651
659
  if (liveReload) {
652
660
  for (const node of sourceFile.statements) {
653
661
  if (ts.isClassDeclaration(node) && node.name != null) {
654
662
  hmrUpdateCode = angularCompiler?.emitHmrUpdateModule(node);
655
- !!hmrUpdateCode && classNames.set(file, node.name.getText());
663
+ if (!!hmrUpdateCode) {
664
+ classNames.set(file, node.name.getText());
665
+ hmrEligible = true;
666
+ }
656
667
  }
657
668
  }
658
669
  }
659
- let content;
660
- program.emit(sourceFile, (filename, data) => {
661
- if (/\.[cm]?js$/.test(filename)) {
662
- content = data;
663
- }
664
- }, undefined /* cancellationToken */, undefined /* emitOnlyDtsFiles */, transformers);
665
- onAfterEmit?.(sourceFile);
666
- return { content, dependencies: [], errors, warnings, hmrUpdateCode };
670
+ return { errors, warnings, hmrUpdateCode, hmrEligible };
667
671
  };
668
672
  }
669
673
  function getDiagnosticsForSourceFile(sourceFile, disableTypeChecking, program, angularCompiler) {