@analogjs/vite-plugin-angular 3.0.0-alpha.3 → 3.0.0-alpha.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/migrations/migrate-setup-vitest/migrate-setup-vitest.d.ts +2 -0
- package/migrations/migrate-setup-vitest/migrate-setup-vitest.js +49 -0
- package/migrations/migrate-setup-vitest/migrate-setup-vitest.js.map +1 -0
- package/migrations/migration.json +7 -1
- package/migrations/migrations.json +9 -0
- package/migrations/update-3-0-0/migrate-setup-vitest.d.ts +2 -0
- package/migrations/update-3-0-0/migrate-setup-vitest.js +36 -0
- package/migrations/update-3-0-0/migrate-setup-vitest.js.map +1 -0
- package/package.json +27 -13
- package/src/index.d.ts +3 -2
- package/src/index.js +6 -2
- package/src/index.js.map +1 -1
- package/src/lib/analog-compiler-plugin.d.ts +14 -0
- package/src/lib/analog-compiler-plugin.js +257 -0
- package/src/lib/analog-compiler-plugin.js.map +1 -0
- package/src/lib/angular-build-optimizer-plugin.d.ts +4 -4
- package/src/lib/angular-build-optimizer-plugin.js +48 -62
- package/src/lib/angular-build-optimizer-plugin.js.map +1 -1
- package/src/lib/angular-jit-plugin.d.ts +3 -3
- package/src/lib/angular-jit-plugin.js +37 -37
- package/src/lib/angular-jit-plugin.js.map +1 -1
- package/src/lib/angular-pending-tasks.plugin.d.ts +7 -7
- package/src/lib/angular-pending-tasks.plugin.js +17 -18
- package/src/lib/angular-pending-tasks.plugin.js.map +1 -1
- package/src/lib/angular-vite-plugin.d.ts +231 -40
- package/src/lib/angular-vite-plugin.js +1948 -964
- package/src/lib/angular-vite-plugin.js.map +1 -1
- package/src/lib/angular-vitest-plugin.d.ts +19 -15
- package/src/lib/angular-vitest-plugin.js +99 -114
- package/src/lib/angular-vitest-plugin.js.map +1 -1
- package/src/lib/compiler-plugin.d.ts +11 -11
- package/src/lib/compiler-plugin.js +43 -44
- package/src/lib/compiler-plugin.js.map +1 -1
- package/src/lib/component-resolvers.d.ts +23 -5
- package/src/lib/component-resolvers.js +153 -63
- package/src/lib/component-resolvers.js.map +1 -1
- package/src/lib/host.d.ts +10 -8
- package/src/lib/host.js +109 -101
- package/src/lib/host.js.map +1 -1
- package/src/lib/live-reload-plugin.d.ts +5 -5
- package/src/lib/live-reload-plugin.js +57 -62
- package/src/lib/live-reload-plugin.js.map +1 -1
- package/src/lib/models.d.ts +9 -9
- package/src/lib/nx-folder-plugin.d.ts +5 -5
- package/src/lib/nx-folder-plugin.js +18 -16
- package/src/lib/nx-folder-plugin.js.map +1 -1
- package/src/lib/plugins/file-replacements.plugin.d.ts +4 -4
- package/src/lib/plugins/file-replacements.plugin.js +40 -62
- package/src/lib/plugins/file-replacements.plugin.js.map +1 -1
- package/src/lib/router-plugin.d.ts +1 -1
- package/src/lib/router-plugin.js +23 -23
- package/src/lib/router-plugin.js.map +1 -1
- package/src/lib/style-pipeline.d.ts +15 -0
- package/src/lib/style-pipeline.js +31 -0
- package/src/lib/style-pipeline.js.map +1 -0
- package/src/lib/style-preprocessor.d.ts +35 -0
- package/src/lib/style-preprocessor.js +35 -0
- package/src/lib/style-preprocessor.js.map +1 -0
- package/src/lib/stylesheet-registry.d.ts +73 -0
- package/src/lib/stylesheet-registry.js +168 -0
- package/src/lib/stylesheet-registry.js.map +1 -0
- package/src/lib/tools/package.json +2 -7
- package/src/lib/tools/src/builders/vite/vite-build.impl.js +31 -38
- package/src/lib/tools/src/builders/vite/vite-build.impl.js.map +1 -1
- package/src/lib/tools/src/builders/vite-dev-server/dev-server.impl.js +51 -62
- package/src/lib/tools/src/builders/vite-dev-server/dev-server.impl.js.map +1 -1
- package/src/lib/tools/src/index.js +0 -2
- package/src/lib/utils/compiler-plugin-options.d.ts +11 -11
- package/src/lib/utils/debug-harness.d.ts +23 -0
- package/src/lib/utils/debug-harness.js +88 -0
- package/src/lib/utils/debug-harness.js.map +1 -0
- package/src/lib/utils/debug-log-file.d.ts +5 -0
- package/src/lib/utils/debug-log-file.js +56 -0
- package/src/lib/utils/debug-log-file.js.map +1 -0
- package/src/lib/utils/debug.d.ts +26 -0
- package/src/lib/utils/debug.js +35 -0
- package/src/lib/utils/debug.js.map +1 -0
- package/src/lib/utils/devkit.d.ts +6 -6
- package/src/lib/utils/devkit.js +34 -38
- package/src/lib/utils/devkit.js.map +1 -1
- package/src/lib/utils/hmr-candidates.d.ts +28 -28
- package/src/lib/utils/plugin-config.d.ts +30 -0
- package/src/lib/utils/plugin-config.js +64 -0
- package/src/lib/utils/plugin-config.js.map +1 -0
- package/src/lib/utils/rolldown.d.ts +2 -0
- package/src/lib/utils/rolldown.js +12 -0
- package/src/lib/utils/rolldown.js.map +1 -0
- package/src/lib/utils/source-file-cache.d.ts +8 -15
- package/src/lib/utils/source-file-cache.js +35 -37
- package/src/lib/utils/source-file-cache.js.map +1 -1
- package/src/lib/utils/virtual-ids.d.ts +8 -0
- package/src/lib/utils/virtual-ids.js +35 -0
- package/src/lib/utils/virtual-ids.js.map +1 -0
- package/src/lib/utils/virtual-resources.d.ts +31 -0
- package/src/lib/utils/virtual-resources.js +60 -0
- package/src/lib/utils/virtual-resources.js.map +1 -0
- package/src/test-setup.d.ts +2 -0
- package/setup-vitest.d.ts +0 -4
- package/setup-vitest.js +0 -215
- package/setup-vitest.js.map +0 -1
- package/src/lib/models.js +0 -1
- package/src/lib/models.js.map +0 -1
- package/src/lib/tools/README.md +0 -3
- package/src/lib/tools/src/index.d.ts +0 -0
- package/src/lib/tools/src/index.js.map +0 -1
- package/src/lib/utils/compiler-plugin-options.js +0 -1
- package/src/lib/utils/compiler-plugin-options.js.map +0 -1
- package/src/lib/utils/hmr-candidates.js +0 -272
- package/src/lib/utils/hmr-candidates.js.map +0 -1
|
@@ -1,982 +1,1966 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
1
|
+
import { angularFullVersion, cjt, createAngularCompilation, sourceFileCache } from "./utils/devkit.js";
|
|
2
|
+
import { getJsTransformConfigKey, isRolldown } from "./utils/rolldown.js";
|
|
3
|
+
import { buildOptimizerPlugin } from "./angular-build-optimizer-plugin.js";
|
|
4
|
+
import { activateDeferredDebug, applyDebugOption, debugCompilationApi, debugCompiler, debugCompilerV, debugHmr, debugHmrV, debugStyles, debugStylesV, debugTailwind, debugTailwindV } from "./utils/debug.js";
|
|
5
|
+
import { jitPlugin } from "./angular-jit-plugin.js";
|
|
6
|
+
import { createCompilerPlugin, createRolldownCompilerPlugin } from "./compiler-plugin.js";
|
|
7
|
+
import { StyleUrlsResolver, TemplateUrlsResolver, getAngularComponentMetadata } from "./component-resolvers.js";
|
|
8
|
+
import { composeStylePreprocessors, normalizeStylesheetDependencies } from "./style-preprocessor.js";
|
|
9
|
+
import { AnalogStylesheetRegistry, preprocessStylesheet, preprocessStylesheetResult, registerStylesheetContent, rewriteRelativeCssImports } from "./stylesheet-registry.js";
|
|
10
|
+
import { augmentHostWithCaching, augmentHostWithResources, augmentProgramWithVersioning, mergeTransformers } from "./host.js";
|
|
11
|
+
import { TS_EXT_REGEX, createTsConfigGetter, getTsConfigPath } from "./utils/plugin-config.js";
|
|
12
|
+
import { toVirtualRawId, toVirtualStyleId } from "./utils/virtual-ids.js";
|
|
13
|
+
import { loadVirtualRawModule, loadVirtualStyleModule } from "./utils/virtual-resources.js";
|
|
14
|
+
import { analogCompilerPlugin } from "./analog-compiler-plugin.js";
|
|
15
|
+
import { angularVitestPlugins } from "./angular-vitest-plugin.js";
|
|
16
|
+
import { pendingTasksPlugin } from "./angular-pending-tasks.plugin.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
|
+
import { configureStylePipelineRegistry, stylePipelinePreprocessorFromPlugins } from "./style-pipeline.js";
|
|
22
|
+
import { union } from "es-toolkit";
|
|
23
|
+
import { createHash } from "node:crypto";
|
|
24
|
+
import { existsSync, mkdirSync, promises, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
25
|
+
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
26
|
+
import * as compilerCli from "@angular/compiler-cli";
|
|
27
|
+
import { createRequire } from "node:module";
|
|
28
|
+
import * as ngCompiler from "@angular/compiler";
|
|
29
|
+
import { globSync } from "tinyglobby";
|
|
30
|
+
import { defaultClientConditions, normalizePath, preprocessCSS } from "vite";
|
|
31
|
+
//#region packages/vite-plugin-angular/src/lib/angular-vite-plugin.ts
|
|
32
|
+
var require = createRequire(import.meta.url);
|
|
33
|
+
var DiagnosticModes = /* @__PURE__ */ function(DiagnosticModes) {
|
|
34
|
+
DiagnosticModes[DiagnosticModes["None"] = 0] = "None";
|
|
35
|
+
DiagnosticModes[DiagnosticModes["Option"] = 1] = "Option";
|
|
36
|
+
DiagnosticModes[DiagnosticModes["Syntactic"] = 2] = "Syntactic";
|
|
37
|
+
DiagnosticModes[DiagnosticModes["Semantic"] = 4] = "Semantic";
|
|
38
|
+
DiagnosticModes[DiagnosticModes["All"] = 7] = "All";
|
|
39
|
+
return DiagnosticModes;
|
|
40
|
+
}({});
|
|
41
|
+
function normalizeIncludeGlob(workspaceRoot, glob) {
|
|
42
|
+
const normalizedWorkspaceRoot = normalizePath(resolve(workspaceRoot));
|
|
43
|
+
const normalizedGlob = normalizePath(glob);
|
|
44
|
+
if (normalizedGlob === normalizedWorkspaceRoot || normalizedGlob.startsWith(`${normalizedWorkspaceRoot}/`)) return normalizedGlob;
|
|
45
|
+
if (normalizedGlob.startsWith("/")) return `${normalizedWorkspaceRoot}${normalizedGlob}`;
|
|
46
|
+
return normalizePath(resolve(normalizedWorkspaceRoot, normalizedGlob));
|
|
47
|
+
}
|
|
48
|
+
var classNames = /* @__PURE__ */ new Map();
|
|
49
|
+
function evictDeletedFileMetadata(file, { removeActiveGraphMetadata, removeStyleOwnerMetadata, classNamesMap, fileTransformMap }) {
|
|
50
|
+
const normalizedFile = normalizePath(file.split("?")[0]);
|
|
51
|
+
removeActiveGraphMetadata(normalizedFile);
|
|
52
|
+
removeStyleOwnerMetadata(normalizedFile);
|
|
53
|
+
classNamesMap.delete(normalizedFile);
|
|
54
|
+
fileTransformMap.delete(normalizedFile);
|
|
55
|
+
}
|
|
56
|
+
function injectViteIgnoreForHmrMetadata(code) {
|
|
57
|
+
let patched = code.replace(/\bimport\(([a-zA-Z_$][\w$]*\.\u0275\u0275getReplaceMetadataURL)/g, "import(/* @vite-ignore */ $1");
|
|
58
|
+
if (patched === code) patched = patched.replace(/import\((\S+getReplaceMetadataURL)/g, "import(/* @vite-ignore */ $1");
|
|
59
|
+
return patched;
|
|
60
|
+
}
|
|
61
|
+
function isIgnoredHmrFile(file) {
|
|
62
|
+
return file.endsWith(".tsbuildinfo");
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Builds a resolved stylePreprocessor function from plugin options.
|
|
66
|
+
*
|
|
67
|
+
* When `tailwindCss` is configured, creates an injector that prepends
|
|
68
|
+
* `@reference "<rootStylesheet>"` into component CSS that uses Tailwind
|
|
69
|
+
* utilities. Uses absolute paths because Angular's externalRuntimeStyles
|
|
70
|
+
* serves component CSS as virtual modules (hash-based IDs) with no
|
|
71
|
+
* meaningful directory — relative paths can't resolve from a hash.
|
|
72
|
+
*
|
|
73
|
+
* If both `tailwindCss` and `stylePreprocessor` are provided, they are
|
|
74
|
+
* chained: Tailwind reference injection runs first, then the user's
|
|
75
|
+
* custom preprocessor.
|
|
76
|
+
*/
|
|
77
|
+
function buildStylePreprocessor(options) {
|
|
78
|
+
const userPreprocessor = options?.stylePreprocessor;
|
|
79
|
+
const stylePipelinePreprocessor = stylePipelinePreprocessorFromPlugins(options?.stylePipeline);
|
|
80
|
+
const tw = options?.tailwindCss;
|
|
81
|
+
if (!tw && !userPreprocessor && !stylePipelinePreprocessor) return;
|
|
82
|
+
let tailwindPreprocessor;
|
|
83
|
+
if (tw) {
|
|
84
|
+
const rootStylesheet = tw.rootStylesheet;
|
|
85
|
+
const prefixes = tw.prefixes;
|
|
86
|
+
debugTailwind("configured", {
|
|
87
|
+
rootStylesheet,
|
|
88
|
+
prefixes
|
|
89
|
+
});
|
|
90
|
+
if (!existsSync(rootStylesheet)) console.warn(`[@analogjs/vite-plugin-angular] tailwindCss.rootStylesheet not found at "${rootStylesheet}". @reference directives will point to a non-existent file, which will cause Tailwind CSS errors. Ensure the path is absolute and the file exists.`);
|
|
91
|
+
tailwindPreprocessor = (code, filename) => {
|
|
92
|
+
if (code.includes("@reference") || code.includes("@import \"tailwindcss\"") || code.includes("@import 'tailwindcss'")) {
|
|
93
|
+
debugTailwindV("skip (already has @reference or is root)", { filename });
|
|
94
|
+
return code;
|
|
95
|
+
}
|
|
96
|
+
if (!(prefixes ? prefixes.some((prefix) => code.includes(prefix)) : code.includes("@apply"))) {
|
|
97
|
+
debugTailwindV("skip (no Tailwind usage detected)", { filename });
|
|
98
|
+
return code;
|
|
99
|
+
}
|
|
100
|
+
debugTailwind("injected @reference via preprocessor", { filename });
|
|
101
|
+
return `@reference "${rootStylesheet}";\n${code}`;
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
if (tailwindPreprocessor && (stylePipelinePreprocessor || userPreprocessor)) debugTailwind("chained with style pipeline or user stylePreprocessor");
|
|
105
|
+
return composeStylePreprocessors([
|
|
106
|
+
tailwindPreprocessor,
|
|
107
|
+
stylePipelinePreprocessor,
|
|
108
|
+
userPreprocessor
|
|
109
|
+
]);
|
|
110
|
+
}
|
|
111
|
+
function angular(options) {
|
|
112
|
+
applyDebugOption(options?.debug, options?.workspaceRoot);
|
|
113
|
+
/**
|
|
114
|
+
* Normalize plugin options so defaults
|
|
115
|
+
* are used for values not provided.
|
|
116
|
+
*/
|
|
117
|
+
const pluginOptions = {
|
|
118
|
+
tsconfigGetter: createTsConfigGetter(options?.tsconfig),
|
|
119
|
+
workspaceRoot: options?.workspaceRoot ?? process.cwd(),
|
|
120
|
+
inlineStylesExtension: options?.inlineStylesExtension ?? "css",
|
|
121
|
+
advanced: { tsTransformers: {
|
|
122
|
+
before: options?.advanced?.tsTransformers?.before ?? [],
|
|
123
|
+
after: options?.advanced?.tsTransformers?.after ?? [],
|
|
124
|
+
afterDeclarations: options?.advanced?.tsTransformers?.afterDeclarations ?? []
|
|
125
|
+
} },
|
|
126
|
+
supportedBrowsers: options?.supportedBrowsers ?? ["safari 15"],
|
|
127
|
+
jit: options?.jit,
|
|
128
|
+
include: options?.include ?? [],
|
|
129
|
+
additionalContentDirs: options?.additionalContentDirs ?? [],
|
|
130
|
+
hmr: options?.hmr ?? options?.liveReload ?? true,
|
|
131
|
+
disableTypeChecking: options?.disableTypeChecking ?? true,
|
|
132
|
+
fileReplacements: options?.fileReplacements ?? [],
|
|
133
|
+
useAngularCompilationAPI: options?.experimental?.useAngularCompilationAPI ?? false,
|
|
134
|
+
hasTailwindCss: !!options?.tailwindCss,
|
|
135
|
+
tailwindCss: options?.tailwindCss,
|
|
136
|
+
stylePreprocessor: buildStylePreprocessor(options)
|
|
137
|
+
};
|
|
138
|
+
let resolvedConfig;
|
|
139
|
+
let tsConfigResolutionContext = null;
|
|
140
|
+
const ts = require("typescript");
|
|
141
|
+
let builder;
|
|
142
|
+
let nextProgram;
|
|
143
|
+
const tsconfigOptionsCache = /* @__PURE__ */ new Map();
|
|
144
|
+
let cachedHost;
|
|
145
|
+
let cachedHostKey;
|
|
146
|
+
let includeCache = [];
|
|
147
|
+
function invalidateFsCaches() {
|
|
148
|
+
includeCache = [];
|
|
149
|
+
}
|
|
150
|
+
function invalidateTsconfigCaches() {
|
|
151
|
+
tsconfigOptionsCache.clear();
|
|
152
|
+
cachedHost = void 0;
|
|
153
|
+
cachedHostKey = void 0;
|
|
154
|
+
}
|
|
155
|
+
let watchMode = false;
|
|
156
|
+
let testWatchMode = isTestWatchMode();
|
|
157
|
+
const activeGraphComponentMetadata = /* @__PURE__ */ new Map();
|
|
158
|
+
const selectorOwners = /* @__PURE__ */ new Map();
|
|
159
|
+
const classNameOwners = /* @__PURE__ */ new Map();
|
|
160
|
+
const transformedStyleOwnerMetadata = /* @__PURE__ */ new Map();
|
|
161
|
+
const styleSourceOwners = /* @__PURE__ */ new Map();
|
|
162
|
+
function shouldEnableHmr() {
|
|
163
|
+
return !!((isTest ? testWatchMode : watchMode) && pluginOptions.hmr);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Determines whether Angular should externalize component styles.
|
|
167
|
+
*
|
|
168
|
+
* When true, Angular emits style references (hash-based IDs) instead of
|
|
169
|
+
* inlining CSS strings. Vite's resolveId → load → transform pipeline
|
|
170
|
+
* then serves these virtual modules, allowing @tailwindcss/vite to
|
|
171
|
+
* process @reference directives.
|
|
172
|
+
*
|
|
173
|
+
* Required for TWO independent use-cases:
|
|
174
|
+
* 1. HMR — Vite needs external modules for hot replacement
|
|
175
|
+
* 2. Tailwind CSS (hasTailwindCss) — styles must pass through Vite's
|
|
176
|
+
* CSS pipeline so @tailwindcss/vite can resolve @apply directives
|
|
177
|
+
*
|
|
178
|
+
* In production builds (!watchMode), styles are NOT externalized — they
|
|
179
|
+
* are inlined after preprocessCSS runs eagerly in transformStylesheet.
|
|
180
|
+
*/
|
|
181
|
+
function shouldExternalizeStyles() {
|
|
182
|
+
if (!(isTest ? testWatchMode : watchMode)) return false;
|
|
183
|
+
return !!(shouldEnableHmr() || pluginOptions.hasTailwindCss);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Validates the Tailwind CSS integration configuration and emits actionable
|
|
187
|
+
* warnings for common misconfigurations that cause silent failures.
|
|
188
|
+
*
|
|
189
|
+
* Called once during `configResolved` when `tailwindCss` is configured.
|
|
190
|
+
*/
|
|
191
|
+
function validateTailwindConfig(config, isWatchMode) {
|
|
192
|
+
const PREFIX = "[@analogjs/vite-plugin-angular]";
|
|
193
|
+
const tw = pluginOptions.tailwindCss;
|
|
194
|
+
if (!tw) return;
|
|
195
|
+
if (!isAbsolute(tw.rootStylesheet)) console.warn(`${PREFIX} tailwindCss.rootStylesheet must be an absolute path. Got: "${tw.rootStylesheet}". Use path.resolve(__dirname, '...') in your vite.config to convert it.`);
|
|
196
|
+
const resolvedPlugins = config.plugins;
|
|
197
|
+
const hasTailwindPlugin = resolvedPlugins.some((p) => p.name.startsWith("@tailwindcss/vite") || p.name.startsWith("tailwindcss"));
|
|
198
|
+
if (isWatchMode && !hasTailwindPlugin) throw new Error(`${PREFIX} tailwindCss is configured but no @tailwindcss/vite plugin was found. Component CSS with @apply directives will not be processed.\n\n Fix: npm install @tailwindcss/vite --save-dev\n Then add tailwindcss() to your vite.config plugins array.\n`);
|
|
199
|
+
if (isWatchMode && tw.rootStylesheet) {
|
|
200
|
+
const projectRoot = config.root;
|
|
201
|
+
if (!tw.rootStylesheet.startsWith(projectRoot)) {
|
|
202
|
+
if (!(config.server?.fs?.allow ?? []).some((allowed) => tw.rootStylesheet.startsWith(allowed))) console.warn(`${PREFIX} tailwindCss.rootStylesheet is outside the Vite project root. The dev server may reject it with 403.\n\n Root: ${projectRoot}\n Stylesheet: ${tw.rootStylesheet}\n\n Fix: server.fs.allow: ['${dirname(tw.rootStylesheet)}']\n`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (tw.prefixes !== void 0 && tw.prefixes.length === 0) console.warn(`${PREFIX} tailwindCss.prefixes is an empty array. No component stylesheets will receive @reference injection. Either remove the prefixes option (to use @apply detection) or specify your prefixes: ['tw:']\n`);
|
|
206
|
+
const analogInstances = resolvedPlugins.filter((p) => p.name === "@analogjs/vite-plugin-angular");
|
|
207
|
+
if (analogInstances.length > 1) throw new Error(`${PREFIX} analog() is registered ${analogInstances.length} times. Each instance creates separate style maps, causing component styles to be lost. Remove duplicate registrations.`);
|
|
208
|
+
if (existsSync(tw.rootStylesheet)) try {
|
|
209
|
+
const rootContent = readFileSync(tw.rootStylesheet, "utf-8");
|
|
210
|
+
if (!rootContent.includes("@import \"tailwindcss\"") && !rootContent.includes("@import 'tailwindcss'")) console.warn(`${PREFIX} tailwindCss.rootStylesheet does not contain @import "tailwindcss". The @reference directive will point to a file without Tailwind configuration.\n\n File: ${tw.rootStylesheet}\n`);
|
|
211
|
+
} catch {}
|
|
212
|
+
}
|
|
213
|
+
function isLikelyPageOnlyComponent(id) {
|
|
214
|
+
return id.includes("/pages/") || /\.page\.[cm]?[jt]sx?$/i.test(id) || /\([^/]+\)\.page\.[cm]?[jt]sx?$/i.test(id);
|
|
215
|
+
}
|
|
216
|
+
function removeActiveGraphMetadata(file) {
|
|
217
|
+
const previous = activeGraphComponentMetadata.get(file);
|
|
218
|
+
if (!previous) return;
|
|
219
|
+
for (const record of previous) {
|
|
220
|
+
const location = `${record.file}#${record.className}`;
|
|
221
|
+
if (record.selector) {
|
|
222
|
+
const selectorSet = selectorOwners.get(record.selector);
|
|
223
|
+
selectorSet?.delete(location);
|
|
224
|
+
if (selectorSet?.size === 0) selectorOwners.delete(record.selector);
|
|
225
|
+
}
|
|
226
|
+
const classNameSet = classNameOwners.get(record.className);
|
|
227
|
+
classNameSet?.delete(location);
|
|
228
|
+
if (classNameSet?.size === 0) classNameOwners.delete(record.className);
|
|
229
|
+
}
|
|
230
|
+
activeGraphComponentMetadata.delete(file);
|
|
231
|
+
}
|
|
232
|
+
function registerActiveGraphMetadata(file, records) {
|
|
233
|
+
removeActiveGraphMetadata(file);
|
|
234
|
+
if (records.length === 0) return;
|
|
235
|
+
activeGraphComponentMetadata.set(file, records);
|
|
236
|
+
for (const record of records) {
|
|
237
|
+
const location = `${record.file}#${record.className}`;
|
|
238
|
+
if (record.selector) {
|
|
239
|
+
let selectorSet = selectorOwners.get(record.selector);
|
|
240
|
+
if (!selectorSet) {
|
|
241
|
+
selectorSet = /* @__PURE__ */ new Set();
|
|
242
|
+
selectorOwners.set(record.selector, selectorSet);
|
|
243
|
+
}
|
|
244
|
+
selectorSet.add(location);
|
|
245
|
+
}
|
|
246
|
+
let classNameSet = classNameOwners.get(record.className);
|
|
247
|
+
if (!classNameSet) {
|
|
248
|
+
classNameSet = /* @__PURE__ */ new Set();
|
|
249
|
+
classNameOwners.set(record.className, classNameSet);
|
|
250
|
+
}
|
|
251
|
+
classNameSet.add(location);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
function removeStyleOwnerMetadata(file) {
|
|
255
|
+
const previous = transformedStyleOwnerMetadata.get(file);
|
|
256
|
+
if (!previous) return;
|
|
257
|
+
for (const record of previous) {
|
|
258
|
+
const owners = styleSourceOwners.get(record.sourcePath);
|
|
259
|
+
owners?.delete(record.ownerFile);
|
|
260
|
+
if (owners?.size === 0) styleSourceOwners.delete(record.sourcePath);
|
|
261
|
+
}
|
|
262
|
+
transformedStyleOwnerMetadata.delete(file);
|
|
263
|
+
}
|
|
264
|
+
function registerStyleOwnerMetadata(file, styleUrls) {
|
|
265
|
+
removeStyleOwnerMetadata(file);
|
|
266
|
+
const records = styleUrls.map((urlSet) => {
|
|
267
|
+
const [, absoluteFileUrl] = urlSet.split("|");
|
|
268
|
+
return absoluteFileUrl ? {
|
|
269
|
+
ownerFile: file,
|
|
270
|
+
sourcePath: normalizePath(absoluteFileUrl)
|
|
271
|
+
} : void 0;
|
|
272
|
+
}).filter((record) => !!record);
|
|
273
|
+
if (records.length === 0) return;
|
|
274
|
+
transformedStyleOwnerMetadata.set(file, records);
|
|
275
|
+
for (const record of records) {
|
|
276
|
+
let owners = styleSourceOwners.get(record.sourcePath);
|
|
277
|
+
if (!owners) {
|
|
278
|
+
owners = /* @__PURE__ */ new Set();
|
|
279
|
+
styleSourceOwners.set(record.sourcePath, owners);
|
|
280
|
+
}
|
|
281
|
+
owners.add(record.ownerFile);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
let stylesheetRegistry;
|
|
285
|
+
const sourceFileCache$1 = new sourceFileCache();
|
|
286
|
+
const isTest = process.env.NODE_ENV === "test" || !!process.env["VITEST"];
|
|
287
|
+
const isVitestVscode = !!process.env["VITEST_VSCODE"];
|
|
288
|
+
const isStackBlitz = !!process.versions["webcontainer"];
|
|
289
|
+
const isAstroIntegration = process.env["ANALOG_ASTRO"] === "true";
|
|
290
|
+
const jit = typeof pluginOptions?.jit !== "undefined" ? pluginOptions.jit : isTest;
|
|
291
|
+
let viteServer;
|
|
292
|
+
const styleUrlsResolver = new StyleUrlsResolver();
|
|
293
|
+
const templateUrlsResolver = new TemplateUrlsResolver();
|
|
294
|
+
let outputFile;
|
|
295
|
+
const outputFiles = /* @__PURE__ */ new Map();
|
|
296
|
+
const fileEmitter = (file) => {
|
|
297
|
+
outputFile?.(file);
|
|
298
|
+
return outputFiles.get(normalizePath(file));
|
|
299
|
+
};
|
|
300
|
+
let initialCompilation = false;
|
|
301
|
+
const declarationFiles = [];
|
|
302
|
+
const fileTransformMap = /* @__PURE__ */ new Map();
|
|
303
|
+
let styleTransform;
|
|
304
|
+
let pendingCompilation;
|
|
305
|
+
let compilationLock = Promise.resolve();
|
|
306
|
+
let angularCompilation;
|
|
307
|
+
function angularPlugin() {
|
|
308
|
+
let isProd = false;
|
|
309
|
+
if (angularFullVersion < 19e4 && pluginOptions.hmr) {
|
|
310
|
+
debugHmr("hmr disabled: Angular version does not support HMR APIs", {
|
|
311
|
+
angularVersion: angularFullVersion,
|
|
312
|
+
isTest
|
|
313
|
+
});
|
|
314
|
+
console.warn("[@analogjs/vite-plugin-angular]: HMR was disabled because Angular v19+ is required for externalRuntimeStyles/_enableHmr support. Detected Angular version: %s.", angularFullVersion);
|
|
315
|
+
pluginOptions.hmr = false;
|
|
316
|
+
}
|
|
317
|
+
if (isTest) {
|
|
318
|
+
pluginOptions.hmr = false;
|
|
319
|
+
debugHmr("hmr disabled", {
|
|
320
|
+
angularVersion: angularFullVersion,
|
|
321
|
+
isTest
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
if (pluginOptions.useAngularCompilationAPI) if (angularFullVersion < 200100) {
|
|
325
|
+
pluginOptions.useAngularCompilationAPI = false;
|
|
326
|
+
debugCompilationApi("disabled: Angular version %s < 20.1", angularFullVersion);
|
|
327
|
+
console.warn("[@analogjs/vite-plugin-angular]: The Angular Compilation API is only available with Angular v20.1 and later");
|
|
328
|
+
} else debugCompilationApi("enabled (Angular %s)", angularFullVersion);
|
|
329
|
+
return {
|
|
330
|
+
name: "@analogjs/vite-plugin-angular",
|
|
331
|
+
async config(config, { command }) {
|
|
332
|
+
activateDeferredDebug(command);
|
|
333
|
+
watchMode = command === "serve";
|
|
334
|
+
isProd = config.mode === "production" || process.env.NODE_ENV === "production";
|
|
335
|
+
tsConfigResolutionContext = {
|
|
336
|
+
root: config.root || ".",
|
|
337
|
+
isProd,
|
|
338
|
+
isLib: !!config?.build?.lib
|
|
339
|
+
};
|
|
340
|
+
const preliminaryTsConfigPath = resolveTsConfigPath();
|
|
341
|
+
const esbuild = pluginOptions.useAngularCompilationAPI ? void 0 : config.esbuild ?? false;
|
|
342
|
+
const oxc = pluginOptions.useAngularCompilationAPI ? void 0 : config.oxc ?? false;
|
|
343
|
+
if (pluginOptions.useAngularCompilationAPI) debugCompilationApi("esbuild/oxc disabled, Angular handles transforms");
|
|
344
|
+
const defineOptions = {
|
|
345
|
+
ngJitMode: "false",
|
|
346
|
+
ngI18nClosureMode: "false",
|
|
347
|
+
...watchMode ? {} : { ngDevMode: "false" }
|
|
348
|
+
};
|
|
349
|
+
const useRolldown = isRolldown();
|
|
350
|
+
const jsTransformConfigKey = getJsTransformConfigKey();
|
|
351
|
+
const jsTransformConfigValue = jsTransformConfigKey === "oxc" ? oxc : esbuild;
|
|
352
|
+
const rolldownOptions = { plugins: [createRolldownCompilerPlugin({
|
|
353
|
+
tsconfig: preliminaryTsConfigPath,
|
|
354
|
+
sourcemap: !isProd,
|
|
355
|
+
advancedOptimizations: isProd,
|
|
356
|
+
jit,
|
|
357
|
+
incremental: watchMode
|
|
358
|
+
}, !isAstroIntegration)] };
|
|
359
|
+
const esbuildOptions = {
|
|
360
|
+
plugins: [createCompilerPlugin({
|
|
361
|
+
tsconfig: preliminaryTsConfigPath,
|
|
362
|
+
sourcemap: !isProd,
|
|
363
|
+
advancedOptimizations: isProd,
|
|
364
|
+
jit,
|
|
365
|
+
incremental: watchMode
|
|
366
|
+
}, isTest, !isAstroIntegration)],
|
|
367
|
+
define: defineOptions
|
|
368
|
+
};
|
|
369
|
+
return {
|
|
370
|
+
[jsTransformConfigKey]: jsTransformConfigValue,
|
|
371
|
+
optimizeDeps: {
|
|
372
|
+
include: [
|
|
373
|
+
"rxjs/operators",
|
|
374
|
+
"rxjs",
|
|
375
|
+
"tslib"
|
|
376
|
+
],
|
|
377
|
+
exclude: ["@angular/platform-server"],
|
|
378
|
+
...useRolldown ? { rolldownOptions } : { esbuildOptions }
|
|
379
|
+
},
|
|
380
|
+
resolve: { conditions: ["style", ...config.resolve?.conditions || defaultClientConditions] }
|
|
381
|
+
};
|
|
382
|
+
},
|
|
383
|
+
configResolved(config) {
|
|
384
|
+
resolvedConfig = config;
|
|
385
|
+
if (pluginOptions.hasTailwindCss) validateTailwindConfig(config, watchMode);
|
|
386
|
+
if (pluginOptions.useAngularCompilationAPI) {
|
|
387
|
+
stylesheetRegistry = new AnalogStylesheetRegistry();
|
|
388
|
+
configureStylePipelineRegistry(pluginOptions.stylePipeline, stylesheetRegistry, { workspaceRoot: pluginOptions.workspaceRoot });
|
|
389
|
+
debugStyles("stylesheet registry initialized (Angular Compilation API)");
|
|
390
|
+
}
|
|
391
|
+
if (!jit) styleTransform = (code, filename) => preprocessCSS(code, filename, config);
|
|
392
|
+
if (isTest) testWatchMode = !(config.server.watch === null) || config.test?.watch === true || testWatchMode;
|
|
393
|
+
},
|
|
394
|
+
configureServer(server) {
|
|
395
|
+
viteServer = server;
|
|
396
|
+
const invalidateCompilationOnFsChange = createFsWatcherCacheInvalidator(invalidateFsCaches, invalidateTsconfigCaches, () => performCompilation(resolvedConfig));
|
|
397
|
+
server.watcher.on("add", invalidateCompilationOnFsChange);
|
|
398
|
+
server.watcher.on("unlink", (file) => {
|
|
399
|
+
evictDeletedFileMetadata(file, {
|
|
400
|
+
removeActiveGraphMetadata,
|
|
401
|
+
removeStyleOwnerMetadata,
|
|
402
|
+
classNamesMap: classNames,
|
|
403
|
+
fileTransformMap
|
|
404
|
+
});
|
|
405
|
+
return invalidateCompilationOnFsChange();
|
|
406
|
+
});
|
|
407
|
+
server.watcher.on("change", (file) => {
|
|
408
|
+
if (file.includes("tsconfig")) invalidateTsconfigCaches();
|
|
409
|
+
});
|
|
410
|
+
},
|
|
411
|
+
async buildStart() {
|
|
412
|
+
if (!isVitestVscode) {
|
|
413
|
+
await performCompilation(resolvedConfig);
|
|
414
|
+
pendingCompilation = null;
|
|
415
|
+
initialCompilation = true;
|
|
416
|
+
}
|
|
417
|
+
},
|
|
418
|
+
async handleHotUpdate(ctx) {
|
|
419
|
+
if (isIgnoredHmrFile(ctx.file)) {
|
|
420
|
+
debugHmr("ignored file change", { file: ctx.file });
|
|
421
|
+
return [];
|
|
422
|
+
}
|
|
423
|
+
if (TS_EXT_REGEX.test(ctx.file)) {
|
|
424
|
+
const [fileId] = ctx.file.split("?");
|
|
425
|
+
debugHmr("TS file changed", {
|
|
426
|
+
file: ctx.file,
|
|
427
|
+
fileId
|
|
428
|
+
});
|
|
429
|
+
pendingCompilation = performCompilation(resolvedConfig, [fileId]);
|
|
430
|
+
let result;
|
|
431
|
+
if (shouldEnableHmr()) {
|
|
432
|
+
await pendingCompilation;
|
|
433
|
+
pendingCompilation = null;
|
|
434
|
+
result = fileEmitter(fileId);
|
|
435
|
+
debugHmr("TS file emitted", {
|
|
436
|
+
fileId,
|
|
437
|
+
hmrEligible: !!result?.hmrEligible,
|
|
438
|
+
hasClassName: !!classNames.get(fileId)
|
|
439
|
+
});
|
|
440
|
+
debugHmrV("ts hmr evaluation", {
|
|
441
|
+
file: ctx.file,
|
|
442
|
+
fileId,
|
|
443
|
+
hasResult: !!result,
|
|
444
|
+
hmrEligible: !!result?.hmrEligible,
|
|
445
|
+
hasClassName: !!classNames.get(fileId),
|
|
446
|
+
className: classNames.get(fileId),
|
|
447
|
+
updateCode: result?.hmrUpdateCode ? describeStylesheetContent(result.hmrUpdateCode) : void 0,
|
|
448
|
+
errors: result?.errors?.length ?? 0,
|
|
449
|
+
warnings: result?.warnings?.length ?? 0,
|
|
450
|
+
hint: result?.hmrEligible ? "A TS-side component change, including inline template edits, produced an Angular HMR payload." : "No Angular HMR payload was emitted for this TS change; the change may not affect component template state."
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
if (shouldEnableHmr() && result?.hmrEligible && classNames.get(fileId)) {
|
|
454
|
+
const relativeFileId = `${normalizePath(relative(process.cwd(), fileId))}@${classNames.get(fileId)}`;
|
|
455
|
+
debugHmr("sending component update", { relativeFileId });
|
|
456
|
+
debugHmrV("ts hmr component update payload", {
|
|
457
|
+
file: ctx.file,
|
|
458
|
+
fileId,
|
|
459
|
+
relativeFileId,
|
|
460
|
+
className: classNames.get(fileId)
|
|
461
|
+
});
|
|
462
|
+
sendHMRComponentUpdate(ctx.server, relativeFileId);
|
|
463
|
+
return ctx.modules.map((mod) => {
|
|
464
|
+
if (mod.id === ctx.file) return markModuleSelfAccepting(mod);
|
|
465
|
+
return mod;
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
if (/\.(html|htm|css|less|sass|scss)$/.test(ctx.file)) {
|
|
470
|
+
debugHmr("resource file changed", { file: ctx.file });
|
|
471
|
+
fileTransformMap.delete(ctx.file.split("?")[0]);
|
|
472
|
+
if (/\.(css|less|sass|scss)$/.test(ctx.file)) refreshStylesheetRegistryForFile(ctx.file, stylesheetRegistry, pluginOptions.stylePreprocessor);
|
|
473
|
+
if (/\.(css|less|sass|scss)$/.test(ctx.file) && existsSync(ctx.file)) try {
|
|
474
|
+
const rawResource = readFileSync(ctx.file, "utf-8");
|
|
475
|
+
debugHmrV("resource source snapshot", {
|
|
476
|
+
file: ctx.file,
|
|
477
|
+
mtimeMs: safeStatMtimeMs(ctx.file),
|
|
478
|
+
...describeStylesheetContent(rawResource)
|
|
479
|
+
});
|
|
480
|
+
} catch (error) {
|
|
481
|
+
debugHmrV("resource source snapshot failed", {
|
|
482
|
+
file: ctx.file,
|
|
483
|
+
error: String(error)
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
const fileModules = await getModulesForChangedFile(ctx.server, ctx.file, ctx.modules, stylesheetRegistry);
|
|
487
|
+
debugHmrV("resource modules resolved", {
|
|
488
|
+
file: ctx.file,
|
|
489
|
+
eventModuleCount: ctx.modules.length,
|
|
490
|
+
fileModuleCount: fileModules.length,
|
|
491
|
+
modules: fileModules.map((mod) => ({
|
|
492
|
+
id: mod.id,
|
|
493
|
+
file: mod.file,
|
|
494
|
+
type: mod.type,
|
|
495
|
+
url: mod.url
|
|
496
|
+
}))
|
|
497
|
+
});
|
|
498
|
+
/**
|
|
499
|
+
* Check to see if this was a direct request
|
|
500
|
+
* for an external resource (styles, html).
|
|
501
|
+
*/
|
|
502
|
+
const isDirect = fileModules.find((mod) => !!mod.id && mod.id.includes("?direct") && isModuleForChangedResource(mod, ctx.file, stylesheetRegistry));
|
|
503
|
+
const isInline = fileModules.find((mod) => !!mod.id && mod.id.includes("?inline") && isModuleForChangedResource(mod, ctx.file, stylesheetRegistry));
|
|
504
|
+
debugHmrV("resource direct/inline detection", {
|
|
505
|
+
file: ctx.file,
|
|
506
|
+
hasDirect: !!isDirect,
|
|
507
|
+
directId: isDirect?.id,
|
|
508
|
+
hasInline: !!isInline,
|
|
509
|
+
inlineId: isInline?.id
|
|
510
|
+
});
|
|
511
|
+
if (isDirect || isInline) {
|
|
512
|
+
if (shouldExternalizeStyles() && isDirect?.id && isDirect.file) {
|
|
513
|
+
const isComponentStyle = isDirect.type === "css" && isComponentStyleSheet(isDirect.id);
|
|
514
|
+
debugHmrV("resource direct branch", {
|
|
515
|
+
file: ctx.file,
|
|
516
|
+
directId: isDirect.id,
|
|
517
|
+
directType: isDirect.type,
|
|
518
|
+
shouldExternalize: shouldExternalizeStyles(),
|
|
519
|
+
isComponentStyle
|
|
520
|
+
});
|
|
521
|
+
if (isComponentStyle) {
|
|
522
|
+
const { encapsulation } = getComponentStyleSheetMeta(isDirect.id);
|
|
523
|
+
const wrapperModules = await findComponentStylesheetWrapperModules(ctx.server, ctx.file, isDirect, fileModules, stylesheetRegistry);
|
|
524
|
+
const stylesheetDiagnosis = diagnoseComponentStylesheetPipeline(ctx.file, isDirect, stylesheetRegistry, wrapperModules, pluginOptions.stylePreprocessor);
|
|
525
|
+
debugStylesV("HMR: component stylesheet changed", {
|
|
526
|
+
file: isDirect.file,
|
|
527
|
+
encapsulation
|
|
528
|
+
});
|
|
529
|
+
debugHmrV("component stylesheet wrapper modules", {
|
|
530
|
+
file: ctx.file,
|
|
531
|
+
wrapperCount: wrapperModules.length,
|
|
532
|
+
wrapperIds: wrapperModules.map((mod) => mod.id),
|
|
533
|
+
availableModuleIds: fileModules.map((mod) => mod.id)
|
|
534
|
+
});
|
|
535
|
+
debugHmrV("component stylesheet pipeline diagnosis", stylesheetDiagnosis);
|
|
536
|
+
ctx.server.moduleGraph.invalidateModule(isDirect);
|
|
537
|
+
debugHmrV("component stylesheet direct module invalidated", {
|
|
538
|
+
file: ctx.file,
|
|
539
|
+
directModuleId: isDirect.id,
|
|
540
|
+
directModuleUrl: isDirect.url,
|
|
541
|
+
reason: "Ensure Vite drops stale direct CSS transform results before wrapper or fallback handling continues."
|
|
542
|
+
});
|
|
543
|
+
const trackedWrapperRequestIds = stylesheetDiagnosis.trackedRequestIds.filter((id) => id.includes("?ngcomp="));
|
|
544
|
+
if (encapsulation !== "shadow" && (wrapperModules.length > 0 || trackedWrapperRequestIds.length > 0)) {
|
|
545
|
+
wrapperModules.forEach((mod) => ctx.server.moduleGraph.invalidateModule(mod));
|
|
546
|
+
debugHmrV("sending css-update for component stylesheet", {
|
|
547
|
+
file: ctx.file,
|
|
548
|
+
path: isDirect.url,
|
|
549
|
+
acceptedPath: isDirect.file,
|
|
550
|
+
wrapperCount: wrapperModules.length,
|
|
551
|
+
trackedWrapperRequestIds,
|
|
552
|
+
hint: wrapperModules.length > 0 ? "Live wrapper modules were found and invalidated before sending the CSS update." : "No live wrapper ModuleNode was available, but the wrapper request id is already tracked, so Analog is trusting the browser-visible wrapper identity and patching the direct stylesheet instead of forcing a reload."
|
|
553
|
+
});
|
|
554
|
+
sendCssUpdate(ctx.server, {
|
|
555
|
+
path: isDirect.url,
|
|
556
|
+
acceptedPath: isDirect.file
|
|
557
|
+
});
|
|
558
|
+
logComponentStylesheetHmrOutcome({
|
|
559
|
+
file: ctx.file,
|
|
560
|
+
encapsulation,
|
|
561
|
+
diagnosis: stylesheetDiagnosis,
|
|
562
|
+
outcome: "css-update",
|
|
563
|
+
directModuleId: isDirect.id,
|
|
564
|
+
wrapperIds: wrapperModules.map((mod) => mod.id)
|
|
565
|
+
});
|
|
566
|
+
return union(fileModules.filter((mod) => {
|
|
567
|
+
return mod.file !== ctx.file || mod.id !== isDirect.id;
|
|
568
|
+
}).map((mod) => {
|
|
569
|
+
if (mod.file === ctx.file) return markModuleSelfAccepting(mod);
|
|
570
|
+
return mod;
|
|
571
|
+
}), wrapperModules.map((mod) => markModuleSelfAccepting(mod)));
|
|
572
|
+
}
|
|
573
|
+
debugHmrV("component stylesheet hmr fallback: full reload", {
|
|
574
|
+
file: ctx.file,
|
|
575
|
+
encapsulation,
|
|
576
|
+
reason: trackedWrapperRequestIds.length === 0 ? "missing-wrapper-module" : encapsulation === "shadow" ? "shadow-encapsulation" : "tracked-wrapper-still-not-patchable",
|
|
577
|
+
directId: isDirect.id,
|
|
578
|
+
trackedRequestIds: stylesheetRegistry?.getRequestIdsForSource(ctx.file) ?? []
|
|
579
|
+
});
|
|
580
|
+
const ownerModules = findStyleOwnerModules(ctx.server, ctx.file, styleSourceOwners);
|
|
581
|
+
debugHmrV("component stylesheet owner fallback lookup", {
|
|
582
|
+
file: ctx.file,
|
|
583
|
+
ownerCount: ownerModules.length,
|
|
584
|
+
ownerIds: ownerModules.map((mod) => mod.id),
|
|
585
|
+
ownerFiles: [...styleSourceOwners.get(normalizePath(ctx.file)) ?? []]
|
|
586
|
+
});
|
|
587
|
+
if (ownerModules.length > 0) {
|
|
588
|
+
pendingCompilation = performCompilation(resolvedConfig, [...ownerModules.map((mod) => mod.id).filter(Boolean)]);
|
|
589
|
+
await pendingCompilation;
|
|
590
|
+
pendingCompilation = null;
|
|
591
|
+
const updates = ownerModules.map((mod) => mod.id).filter((id) => !!id && !!classNames.get(id));
|
|
592
|
+
const derivedUpdates = ownerModules.map((mod) => mod.id).filter((id) => !!id).flatMap((ownerId) => resolveComponentClassNamesForStyleOwner(ownerId, ctx.file).map((className) => ({
|
|
593
|
+
ownerId,
|
|
594
|
+
className,
|
|
595
|
+
via: "raw-component-metadata"
|
|
596
|
+
})));
|
|
597
|
+
debugHmrV("component stylesheet owner fallback compilation", {
|
|
598
|
+
file: ctx.file,
|
|
599
|
+
ownerIds: ownerModules.map((mod) => mod.id),
|
|
600
|
+
updateIds: updates,
|
|
601
|
+
classNames: updates.map((id) => ({
|
|
602
|
+
id,
|
|
603
|
+
className: classNames.get(id)
|
|
604
|
+
})),
|
|
605
|
+
derivedUpdates
|
|
606
|
+
});
|
|
607
|
+
if (derivedUpdates.length > 0) debugHmrV("component stylesheet owner fallback derived updates", {
|
|
608
|
+
file: ctx.file,
|
|
609
|
+
updates: derivedUpdates,
|
|
610
|
+
hint: "Angular did not repopulate classNames during CSS-only owner recompilation, so Analog derived component identities from raw component metadata."
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
logComponentStylesheetHmrOutcome({
|
|
614
|
+
file: ctx.file,
|
|
615
|
+
encapsulation,
|
|
616
|
+
diagnosis: stylesheetDiagnosis,
|
|
617
|
+
outcome: "full-reload",
|
|
618
|
+
directModuleId: isDirect.id,
|
|
619
|
+
wrapperIds: wrapperModules.map((mod) => mod.id),
|
|
620
|
+
ownerIds: ownerModules.map((mod) => mod.id)
|
|
621
|
+
});
|
|
622
|
+
sendFullReload(ctx.server, {
|
|
623
|
+
file: ctx.file,
|
|
624
|
+
encapsulation,
|
|
625
|
+
reason: wrapperModules.length === 0 ? "missing-wrapper-module-and-no-owner-updates" : "shadow-encapsulation",
|
|
626
|
+
directId: isDirect.id,
|
|
627
|
+
trackedRequestIds: stylesheetRegistry?.getRequestIdsForSource(ctx.file) ?? []
|
|
628
|
+
});
|
|
629
|
+
return [];
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
return fileModules;
|
|
633
|
+
}
|
|
634
|
+
if (shouldEnableHmr() && /\.(html|htm)$/.test(ctx.file) && fileModules.length === 0) {
|
|
635
|
+
const ownerModules = findTemplateOwnerModules(ctx.server, ctx.file);
|
|
636
|
+
debugHmrV("template owner lookup", {
|
|
637
|
+
file: ctx.file,
|
|
638
|
+
ownerCount: ownerModules.length,
|
|
639
|
+
ownerIds: ownerModules.map((mod) => mod.id),
|
|
640
|
+
hint: ownerModules.length > 0 ? "The external template has candidate TS owner modules that can be recompiled for HMR." : "No TS owner modules were visible for this external template change; HMR will fall through to the generic importer path."
|
|
641
|
+
});
|
|
642
|
+
if (ownerModules.length > 0) {
|
|
643
|
+
const ownerIds = ownerModules.map((mod) => mod.id).filter(Boolean);
|
|
644
|
+
ownerModules.forEach((mod) => ctx.server.moduleGraph.invalidateModule(mod));
|
|
645
|
+
pendingCompilation = performCompilation(resolvedConfig, ownerIds);
|
|
646
|
+
await pendingCompilation;
|
|
647
|
+
pendingCompilation = null;
|
|
648
|
+
const updates = ownerIds.filter((id) => classNames.get(id));
|
|
649
|
+
debugHmrV("template owner recompilation result", {
|
|
650
|
+
file: ctx.file,
|
|
651
|
+
ownerIds,
|
|
652
|
+
updates,
|
|
653
|
+
updateClassNames: updates.map((id) => ({
|
|
654
|
+
id,
|
|
655
|
+
className: classNames.get(id)
|
|
656
|
+
})),
|
|
657
|
+
hint: updates.length > 0 ? "External template recompilation produced Angular component update targets." : "External template recompilation completed, but no Angular component update targets were surfaced."
|
|
658
|
+
});
|
|
659
|
+
if (updates.length > 0) {
|
|
660
|
+
debugHmr("template owner module invalidation", {
|
|
661
|
+
file: ctx.file,
|
|
662
|
+
ownerIds,
|
|
663
|
+
updateCount: updates.length
|
|
664
|
+
});
|
|
665
|
+
updates.forEach((updateId) => {
|
|
666
|
+
const relativeFileId = `${normalizePath(relative(process.cwd(), updateId))}@${classNames.get(updateId)}`;
|
|
667
|
+
sendHMRComponentUpdate(ctx.server, relativeFileId);
|
|
668
|
+
});
|
|
669
|
+
return ownerModules.map((mod) => markModuleSelfAccepting(mod));
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
const mods = [];
|
|
674
|
+
const updates = [];
|
|
675
|
+
fileModules.forEach((mod) => {
|
|
676
|
+
mod.importers.forEach((imp) => {
|
|
677
|
+
ctx.server.moduleGraph.invalidateModule(imp);
|
|
678
|
+
if (shouldExternalizeStyles() && classNames.get(imp.id)) updates.push(imp.id);
|
|
679
|
+
else mods.push(imp);
|
|
680
|
+
});
|
|
681
|
+
});
|
|
682
|
+
debugHmrV("resource importer analysis", {
|
|
683
|
+
file: ctx.file,
|
|
684
|
+
fileModuleCount: fileModules.length,
|
|
685
|
+
importerCount: fileModules.reduce((count, mod) => count + mod.importers.size, 0),
|
|
686
|
+
updates,
|
|
687
|
+
mods: mods.map((mod) => mod.id)
|
|
688
|
+
});
|
|
689
|
+
pendingCompilation = performCompilation(resolvedConfig, [...mods.map((mod) => mod.id).filter(Boolean), ...updates]);
|
|
690
|
+
if (updates.length > 0) {
|
|
691
|
+
await pendingCompilation;
|
|
692
|
+
pendingCompilation = null;
|
|
693
|
+
debugHmr("resource importer component updates", {
|
|
694
|
+
file: ctx.file,
|
|
695
|
+
updateCount: updates.length
|
|
696
|
+
});
|
|
697
|
+
updates.forEach((updateId) => {
|
|
698
|
+
const impRelativeFileId = `${normalizePath(relative(process.cwd(), updateId))}@${classNames.get(updateId)}`;
|
|
699
|
+
sendHMRComponentUpdate(ctx.server, impRelativeFileId);
|
|
700
|
+
});
|
|
701
|
+
return fileModules.map((mod) => {
|
|
702
|
+
if (mod.id === ctx.file) return markModuleSelfAccepting(mod);
|
|
703
|
+
return mod;
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
return mods;
|
|
707
|
+
}
|
|
708
|
+
debugHmr("full reload — unrecognized file type", { file: ctx.file });
|
|
709
|
+
classNames.clear();
|
|
710
|
+
return ctx.modules;
|
|
711
|
+
},
|
|
712
|
+
resolveId(id, importer) {
|
|
713
|
+
if (id.startsWith("virtual:@analogjs/vite-plugin-angular:inline-style:") || id.startsWith("virtual:@analogjs/vite-plugin-angular:raw:")) return `\0${id}`;
|
|
714
|
+
if (jit && id.startsWith("angular:jit:")) {
|
|
715
|
+
const path = id.split(";")[1];
|
|
716
|
+
const resolved = normalizePath(resolve(dirname(importer), path));
|
|
717
|
+
if (id.includes(":style")) return toVirtualStyleId(resolved);
|
|
718
|
+
return toVirtualRawId(resolved);
|
|
719
|
+
}
|
|
720
|
+
if (id.includes(".html?raw")) {
|
|
721
|
+
const filePath = id.split("?")[0];
|
|
722
|
+
const resolved = isAbsolute(filePath) ? normalizePath(filePath) : importer ? normalizePath(resolve(dirname(importer), filePath)) : void 0;
|
|
723
|
+
if (resolved) return toVirtualRawId(resolved);
|
|
724
|
+
}
|
|
725
|
+
if (/\.(css|scss|sass|less)\?inline$/.test(id)) {
|
|
726
|
+
const filePath = id.split("?")[0];
|
|
727
|
+
const resolved = isAbsolute(filePath) ? normalizePath(filePath) : importer ? normalizePath(resolve(dirname(importer), filePath)) : void 0;
|
|
728
|
+
if (resolved) return toVirtualStyleId(resolved);
|
|
729
|
+
}
|
|
730
|
+
if (isComponentStyleSheet(id)) {
|
|
731
|
+
const filename = getFilenameFromPath(id);
|
|
732
|
+
if (stylesheetRegistry?.hasServed(filename)) {
|
|
733
|
+
debugStylesV("resolveId: kept preprocessed ID", { filename });
|
|
734
|
+
return id;
|
|
735
|
+
}
|
|
736
|
+
const componentStyles = stylesheetRegistry?.resolveExternalSource(filename);
|
|
737
|
+
if (componentStyles) {
|
|
738
|
+
debugStylesV("resolveId: mapped external stylesheet", {
|
|
739
|
+
filename,
|
|
740
|
+
resolvedPath: componentStyles
|
|
741
|
+
});
|
|
742
|
+
return componentStyles + new URL(id, "http://localhost").search;
|
|
743
|
+
}
|
|
744
|
+
debugStyles("resolveId: component stylesheet NOT FOUND in either map", {
|
|
745
|
+
filename,
|
|
746
|
+
inlineMapSize: stylesheetRegistry?.servedCount ?? 0,
|
|
747
|
+
externalMapSize: stylesheetRegistry?.externalCount ?? 0
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
},
|
|
751
|
+
async load(id) {
|
|
752
|
+
const styleModule = await loadVirtualStyleModule(this, id, resolvedConfig);
|
|
753
|
+
if (styleModule !== void 0) return styleModule;
|
|
754
|
+
const rawModule = await loadVirtualRawModule(this, id);
|
|
755
|
+
if (rawModule !== void 0) return rawModule;
|
|
756
|
+
if (/\.(css|scss|sass|less)\?inline$/.test(id)) {
|
|
757
|
+
const filePath = id.split("?")[0];
|
|
758
|
+
const result = await preprocessCSS(await promises.readFile(filePath, "utf-8"), filePath, resolvedConfig);
|
|
759
|
+
return `export default ${JSON.stringify(result.code)}`;
|
|
760
|
+
}
|
|
761
|
+
if (isComponentStyleSheet(id)) {
|
|
762
|
+
const filename = getFilenameFromPath(id);
|
|
763
|
+
const componentStyles = stylesheetRegistry?.getServedContent(filename);
|
|
764
|
+
if (componentStyles) {
|
|
765
|
+
stylesheetRegistry?.registerActiveRequest(id);
|
|
766
|
+
debugHmrV("stylesheet active request registered", {
|
|
767
|
+
requestId: id,
|
|
768
|
+
filename,
|
|
769
|
+
sourcePath: stylesheetRegistry?.resolveExternalSource(filename) ?? stylesheetRegistry?.resolveExternalSource(filename.replace(/^\//, "")),
|
|
770
|
+
trackedRequestIds: stylesheetRegistry?.getRequestIdsForSource(stylesheetRegistry?.resolveExternalSource(filename) ?? stylesheetRegistry?.resolveExternalSource(filename.replace(/^\//, "")) ?? "") ?? []
|
|
771
|
+
});
|
|
772
|
+
debugStylesV("load: served inline component stylesheet", {
|
|
773
|
+
filename,
|
|
774
|
+
length: componentStyles.length,
|
|
775
|
+
requestId: id,
|
|
776
|
+
...describeStylesheetContent(componentStyles)
|
|
777
|
+
});
|
|
778
|
+
return componentStyles;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
},
|
|
782
|
+
transform: {
|
|
783
|
+
filter: { id: {
|
|
784
|
+
include: [TS_EXT_REGEX],
|
|
785
|
+
exclude: [
|
|
786
|
+
/node_modules/,
|
|
787
|
+
"type=script",
|
|
788
|
+
"@ng/component"
|
|
789
|
+
]
|
|
790
|
+
} },
|
|
791
|
+
async handler(code, id) {
|
|
792
|
+
/**
|
|
793
|
+
* Check for options.transformFilter
|
|
794
|
+
*/
|
|
795
|
+
if (options?.transformFilter && !(options?.transformFilter(code, id) ?? true)) return;
|
|
796
|
+
if (pluginOptions.useAngularCompilationAPI) {
|
|
797
|
+
if (!/(Component|Directive|Pipe|Injectable|NgModule)\(/.test(code)) {
|
|
798
|
+
debugCompilationApi("transform skip (non-Angular file)", { id });
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* Skip transforming content files
|
|
804
|
+
*/
|
|
805
|
+
if (id.includes("?") && id.includes("analog-content-")) return;
|
|
806
|
+
/**
|
|
807
|
+
* Encapsulate component stylesheets that use emulated encapsulation.
|
|
808
|
+
* Must run whenever styles are externalized (not just HMR), because
|
|
809
|
+
* Angular's externalRuntimeStyles skips its own encapsulation when
|
|
810
|
+
* styles are external — the build tool is expected to handle it.
|
|
811
|
+
*/
|
|
812
|
+
if (shouldExternalizeStyles() && isComponentStyleSheet(id)) {
|
|
813
|
+
const { encapsulation, componentId } = getComponentStyleSheetMeta(id);
|
|
814
|
+
if (encapsulation === "emulated" && componentId) {
|
|
815
|
+
debugStylesV("applying emulated view encapsulation", {
|
|
816
|
+
stylesheet: id.split("?")[0],
|
|
817
|
+
componentId
|
|
818
|
+
});
|
|
819
|
+
return {
|
|
820
|
+
code: ngCompiler.encapsulateStyle(code, componentId),
|
|
821
|
+
map: null
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
if (id.includes(".ts?")) id = id.replace(/\?(.*)/, "");
|
|
826
|
+
fileTransformMap.set(id, code);
|
|
827
|
+
/**
|
|
828
|
+
* Re-analyze on each transform
|
|
829
|
+
* for test(Vitest)
|
|
830
|
+
*/
|
|
831
|
+
if (isTest) {
|
|
832
|
+
if (isVitestVscode && !initialCompilation) {
|
|
833
|
+
pendingCompilation = performCompilation(resolvedConfig);
|
|
834
|
+
initialCompilation = true;
|
|
835
|
+
}
|
|
836
|
+
const tsMod = viteServer?.moduleGraph.getModuleById(id);
|
|
837
|
+
if (tsMod) {
|
|
838
|
+
const invalidated = tsMod.lastInvalidationTimestamp;
|
|
839
|
+
if (testWatchMode && invalidated) pendingCompilation = performCompilation(resolvedConfig, [id]);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
const hasComponent = code.includes("@Component");
|
|
843
|
+
debugCompilerV("transform", {
|
|
844
|
+
id,
|
|
845
|
+
codeLength: code.length,
|
|
846
|
+
hasComponent
|
|
847
|
+
});
|
|
848
|
+
const templateUrls = hasComponent ? templateUrlsResolver.resolve(code, id) : [];
|
|
849
|
+
const styleUrls = hasComponent ? styleUrlsResolver.resolve(code, id) : [];
|
|
850
|
+
if (hasComponent && watchMode) for (const urlSet of [...templateUrls, ...styleUrls]) {
|
|
851
|
+
const [, absoluteFileUrl] = urlSet.split("|");
|
|
852
|
+
this.addWatchFile(absoluteFileUrl);
|
|
853
|
+
}
|
|
854
|
+
if (pendingCompilation) {
|
|
855
|
+
await pendingCompilation;
|
|
856
|
+
pendingCompilation = null;
|
|
857
|
+
}
|
|
858
|
+
const typescriptResult = fileEmitter(id);
|
|
859
|
+
if (!typescriptResult) {
|
|
860
|
+
if (!id.includes("@ng/component") && /(Component|Directive|Pipe|Injectable|NgModule)\(/.test(code)) this.warn(`[@analogjs/vite-plugin-angular]: "${id}" contains Angular decorators but is not in the TypeScript program. Ensure it is included in your tsconfig.`);
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
if (typescriptResult.warnings && typescriptResult.warnings.length > 0) this.warn(`${typescriptResult.warnings.join("\n")}`);
|
|
864
|
+
if (typescriptResult.errors && typescriptResult.errors.length > 0) this.error(`${typescriptResult.errors.join("\n")}`);
|
|
865
|
+
let data = typescriptResult.content ?? "";
|
|
866
|
+
if (jit && data.includes("angular:jit:")) {
|
|
867
|
+
data = data.replace(/angular:jit:style:inline;/g, "virtual:angular:jit:style:inline;");
|
|
868
|
+
templateUrls.forEach((templateUrlSet) => {
|
|
869
|
+
const [templateFile, resolvedTemplateUrl] = templateUrlSet.split("|");
|
|
870
|
+
data = data.replace(`angular:jit:template:file;${templateFile}`, toVirtualRawId(resolvedTemplateUrl));
|
|
871
|
+
});
|
|
872
|
+
styleUrls.forEach((styleUrlSet) => {
|
|
873
|
+
const [styleFile, resolvedStyleUrl] = styleUrlSet.split("|");
|
|
874
|
+
data = data.replace(`angular:jit:style:file;${styleFile}`, toVirtualStyleId(resolvedStyleUrl));
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
if (data.includes("HmrLoad")) {
|
|
878
|
+
const hasMetaUrl = data.includes("getReplaceMetadataURL");
|
|
879
|
+
debugHmrV("vite-ignore injection", {
|
|
880
|
+
id,
|
|
881
|
+
dataLength: data.length,
|
|
882
|
+
hasMetaUrl
|
|
883
|
+
});
|
|
884
|
+
if (hasMetaUrl) {
|
|
885
|
+
const patched = injectViteIgnoreForHmrMetadata(data);
|
|
886
|
+
if (patched !== data && !patched.includes("@vite-ignore")) debugHmrV("vite-ignore regex fallback", { id });
|
|
887
|
+
data = patched;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
return {
|
|
891
|
+
code: data,
|
|
892
|
+
map: null
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
},
|
|
896
|
+
closeBundle() {
|
|
897
|
+
declarationFiles.forEach(({ declarationFileDir, declarationPath, data }) => {
|
|
898
|
+
mkdirSync(declarationFileDir, { recursive: true });
|
|
899
|
+
writeFileSync(declarationPath, data, "utf-8");
|
|
900
|
+
});
|
|
901
|
+
angularCompilation?.close?.();
|
|
902
|
+
angularCompilation = void 0;
|
|
903
|
+
}
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
pluginOptions.useAnalogCompiler ? analogCompilerPlugin({
|
|
907
|
+
tsconfigGetter: pluginOptions.tsconfigGetter,
|
|
908
|
+
workspaceRoot: pluginOptions.workspaceRoot,
|
|
909
|
+
inlineStylesExtension: pluginOptions.inlineStylesExtension,
|
|
910
|
+
jit,
|
|
911
|
+
liveReload: pluginOptions.liveReload,
|
|
912
|
+
supportedBrowsers: pluginOptions.supportedBrowsers,
|
|
913
|
+
transformFilter: options?.transformFilter,
|
|
914
|
+
isTest,
|
|
915
|
+
isAstroIntegration,
|
|
916
|
+
analogCompilationMode: pluginOptions.analogCompilationMode
|
|
917
|
+
}) : angularPlugin();
|
|
918
|
+
return [
|
|
919
|
+
replaceFiles(pluginOptions.fileReplacements, pluginOptions.workspaceRoot),
|
|
920
|
+
{
|
|
921
|
+
name: "@analogjs/vite-plugin-angular:template-class-binding-guard",
|
|
922
|
+
enforce: "pre",
|
|
923
|
+
transform(code, id) {
|
|
924
|
+
if (id.includes("node_modules")) return;
|
|
925
|
+
const cleanId = id.split("?")[0];
|
|
926
|
+
if (/\.(html|htm)$/i.test(cleanId)) {
|
|
927
|
+
const staticClassIssue = findStaticClassAndBoundClassConflicts(code)[0];
|
|
928
|
+
if (staticClassIssue) throwTemplateClassBindingConflict(cleanId, staticClassIssue);
|
|
929
|
+
const mixedClassIssue = findBoundClassAndNgClassConflicts(code)[0];
|
|
930
|
+
if (mixedClassIssue) this.warn([
|
|
931
|
+
"[Analog Angular] Conflicting class composition.",
|
|
932
|
+
`File: ${cleanId}:${mixedClassIssue.line}:${mixedClassIssue.column}`,
|
|
933
|
+
"This element mixes `[class]` and `[ngClass]`.",
|
|
934
|
+
"Prefer a single class-binding strategy so class merging stays predictable.",
|
|
935
|
+
"Use one `[ngClass]` expression or explicit `[class.foo]` bindings.",
|
|
936
|
+
`Snippet: ${mixedClassIssue.snippet}`
|
|
937
|
+
].join("\n"));
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
if (TS_EXT_REGEX.test(cleanId)) {
|
|
941
|
+
const rawStyleUrls = styleUrlsResolver.resolve(code, cleanId);
|
|
942
|
+
registerStyleOwnerMetadata(cleanId, rawStyleUrls);
|
|
943
|
+
debugHmrV("component stylesheet owner metadata registered", {
|
|
944
|
+
file: cleanId,
|
|
945
|
+
styleUrlCount: rawStyleUrls.length,
|
|
946
|
+
styleUrls: rawStyleUrls,
|
|
947
|
+
ownerSources: [...transformedStyleOwnerMetadata.get(cleanId)?.map((record) => record.sourcePath) ?? []]
|
|
948
|
+
});
|
|
949
|
+
const components = getAngularComponentMetadata(code);
|
|
950
|
+
const inlineTemplateIssue = components.flatMap((component) => component.inlineTemplates.flatMap((template) => findStaticClassAndBoundClassConflicts(template)))[0];
|
|
951
|
+
if (inlineTemplateIssue) throwTemplateClassBindingConflict(cleanId, inlineTemplateIssue);
|
|
952
|
+
const mixedInlineClassIssue = components.flatMap((component) => component.inlineTemplates.flatMap((template) => findBoundClassAndNgClassConflicts(template)))[0];
|
|
953
|
+
if (mixedInlineClassIssue) this.warn([
|
|
954
|
+
"[Analog Angular] Conflicting class composition.",
|
|
955
|
+
`File: ${cleanId}:${mixedInlineClassIssue.line}:${mixedInlineClassIssue.column}`,
|
|
956
|
+
"This element mixes `[class]` and `[ngClass]`.",
|
|
957
|
+
"Prefer a single class-binding strategy so class merging stays predictable.",
|
|
958
|
+
"Use one `[ngClass]` expression or explicit `[class.foo]` bindings.",
|
|
959
|
+
`Snippet: ${mixedInlineClassIssue.snippet}`
|
|
960
|
+
].join("\n"));
|
|
961
|
+
registerActiveGraphMetadata(cleanId, components.map((component) => ({
|
|
962
|
+
file: cleanId,
|
|
963
|
+
className: component.className,
|
|
964
|
+
selector: component.selector
|
|
965
|
+
})));
|
|
966
|
+
for (const component of components) {
|
|
967
|
+
if (!component.selector && !isLikelyPageOnlyComponent(cleanId)) throw new Error([
|
|
968
|
+
"[Analog Angular] Selectorless component detected.",
|
|
969
|
+
`File: ${cleanId}`,
|
|
970
|
+
`Component: ${component.className}`,
|
|
971
|
+
"This component has no `selector`, so Angular will render it as `ng-component`.",
|
|
972
|
+
"That increases the chance of component ID collisions and makes diagnostics harder to interpret.",
|
|
973
|
+
"Add an explicit selector for reusable components.",
|
|
974
|
+
"Selectorless components are only supported for page and route-only files."
|
|
975
|
+
].join("\n"));
|
|
976
|
+
if (component.selector) {
|
|
977
|
+
const selectorEntries = selectorOwners.get(component.selector);
|
|
978
|
+
if (selectorEntries && selectorEntries.size > 1) throw new Error([
|
|
979
|
+
"[Analog Angular] Duplicate component selector detected.",
|
|
980
|
+
`Selector: ${component.selector}`,
|
|
981
|
+
"Multiple components in the active application graph use the same selector.",
|
|
982
|
+
"Selectors must be unique within the active graph to avoid ambiguous rendering and confusing diagnostics.",
|
|
983
|
+
`Locations:\n${formatActiveGraphLocations(selectorEntries)}`
|
|
984
|
+
].join("\n"));
|
|
985
|
+
}
|
|
986
|
+
const classNameEntries = classNameOwners.get(component.className);
|
|
987
|
+
if (classNameEntries && classNameEntries.size > 1) this.warn([
|
|
988
|
+
"[Analog Angular] Duplicate component class name detected.",
|
|
989
|
+
`Class name: ${component.className}`,
|
|
990
|
+
"Two or more Angular components in the active graph share the same exported class name.",
|
|
991
|
+
"Rename one of them to keep HMR, stack traces, and compiler diagnostics unambiguous.",
|
|
992
|
+
`Locations:\n${formatActiveGraphLocations(classNameEntries)}`
|
|
993
|
+
].join("\n"));
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
},
|
|
998
|
+
pluginOptions.hasTailwindCss && {
|
|
999
|
+
name: "@analogjs/vite-plugin-angular:tailwind-reference",
|
|
1000
|
+
enforce: "pre",
|
|
1001
|
+
transform(code, id) {
|
|
1002
|
+
const tw = pluginOptions.tailwindCss;
|
|
1003
|
+
if (!tw || !id.includes(".css")) return;
|
|
1004
|
+
if (id.split("?")[0] === tw.rootStylesheet) return;
|
|
1005
|
+
if (code.includes("@reference") || code.includes("@import \"tailwindcss\"") || code.includes("@import 'tailwindcss'")) return;
|
|
1006
|
+
const rootBasename = basename(tw.rootStylesheet);
|
|
1007
|
+
if (code.includes(rootBasename)) return;
|
|
1008
|
+
const prefixes = tw.prefixes;
|
|
1009
|
+
if (prefixes ? prefixes.some((p) => code.includes(p)) : code.includes("@apply")) {
|
|
1010
|
+
debugTailwind("injected @reference via pre-transform", { id: id.split("/").slice(-2).join("/") });
|
|
1011
|
+
return `@reference "${tw.rootStylesheet}";\n${code}`;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
},
|
|
1015
|
+
angularPlugin(),
|
|
1016
|
+
pluginOptions.hmr && liveReloadPlugin({
|
|
1017
|
+
classNames,
|
|
1018
|
+
fileEmitter
|
|
1019
|
+
}),
|
|
1020
|
+
...isTest && !isStackBlitz ? angularVitestPlugins() : [],
|
|
1021
|
+
jit && jitPlugin({ inlineStylesExtension: pluginOptions.inlineStylesExtension }),
|
|
1022
|
+
buildOptimizerPlugin({
|
|
1023
|
+
supportedBrowsers: pluginOptions.supportedBrowsers,
|
|
1024
|
+
jit
|
|
1025
|
+
}),
|
|
1026
|
+
routerPlugin(),
|
|
1027
|
+
angularFullVersion < 190004 && pendingTasksPlugin(),
|
|
1028
|
+
nxFolderPlugin()
|
|
1029
|
+
].filter(Boolean);
|
|
1030
|
+
function findIncludes() {
|
|
1031
|
+
return globSync(pluginOptions.include.map((glob) => normalizeIncludeGlob(pluginOptions.workspaceRoot, glob)), {
|
|
1032
|
+
dot: true,
|
|
1033
|
+
absolute: true
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
function resolveTsConfigPath() {
|
|
1037
|
+
const tsconfigValue = pluginOptions.tsconfigGetter();
|
|
1038
|
+
return getTsConfigPath(tsConfigResolutionContext.root, tsconfigValue, tsConfigResolutionContext.isProd, isTest, tsConfigResolutionContext.isLib);
|
|
1039
|
+
}
|
|
1040
|
+
/**
|
|
1041
|
+
* Perform compilation using Angular's private Compilation API.
|
|
1042
|
+
*
|
|
1043
|
+
* Key differences from the standard `performCompilation` path:
|
|
1044
|
+
* 1. The compilation instance is reused across rebuilds (nullish-coalescing
|
|
1045
|
+
* assignment below) so Angular retains prior state and can diff it to
|
|
1046
|
+
* produce `templateUpdates` for HMR.
|
|
1047
|
+
* 2. `ids` (modified files) are forwarded to both the source-file cache and
|
|
1048
|
+
* `angularCompilation.update()` so that incremental re-analysis is
|
|
1049
|
+
* scoped to what actually changed.
|
|
1050
|
+
* 3. `fileReplacements` are converted and passed into Angular's host via
|
|
1051
|
+
* `toAngularCompilationFileReplacements`.
|
|
1052
|
+
* 4. `templateUpdates` from the compilation result are mapped back to
|
|
1053
|
+
* file-level HMR metadata (`hmrUpdateCode`, `hmrEligible`, `classNames`).
|
|
1054
|
+
*/
|
|
1055
|
+
async function performAngularCompilation(config, ids) {
|
|
1056
|
+
angularCompilation ??= await createAngularCompilation(!!pluginOptions.jit, false);
|
|
1057
|
+
const modifiedFiles = ids?.length ? new Set(ids.map((file) => normalizePath(file))) : void 0;
|
|
1058
|
+
if (modifiedFiles?.size) sourceFileCache$1.invalidate(modifiedFiles);
|
|
1059
|
+
if (modifiedFiles?.size && angularCompilation.update) {
|
|
1060
|
+
debugCompilationApi("incremental update", { files: [...modifiedFiles] });
|
|
1061
|
+
await angularCompilation.update(modifiedFiles);
|
|
1062
|
+
}
|
|
1063
|
+
const resolvedTsConfigPath = resolveTsConfigPath();
|
|
1064
|
+
const compilationResult = await angularCompilation.initialize(resolvedTsConfigPath, {
|
|
1065
|
+
fileReplacements: toAngularCompilationFileReplacements(pluginOptions.fileReplacements, pluginOptions.workspaceRoot),
|
|
1066
|
+
modifiedFiles,
|
|
1067
|
+
async transformStylesheet(data, containingFile, resourceFile, order, className) {
|
|
1068
|
+
const filename = resourceFile ?? containingFile.replace(".ts", `.${pluginOptions.inlineStylesExtension}`);
|
|
1069
|
+
const preprocessed = preprocessStylesheetResult(data, filename, pluginOptions.stylePreprocessor, {
|
|
1070
|
+
filename,
|
|
1071
|
+
containingFile,
|
|
1072
|
+
resourceFile,
|
|
1073
|
+
className,
|
|
1074
|
+
order,
|
|
1075
|
+
inline: !resourceFile
|
|
1076
|
+
});
|
|
1077
|
+
if (shouldEnableHmr() && className && containingFile) classNames.set(normalizePath(containingFile), className);
|
|
1078
|
+
if (shouldExternalizeStyles()) {
|
|
1079
|
+
const stylesheetId = registerStylesheetContent(stylesheetRegistry, {
|
|
1080
|
+
code: preprocessed.code,
|
|
1081
|
+
dependencies: normalizeStylesheetDependencies(preprocessed.dependencies),
|
|
1082
|
+
diagnostics: preprocessed.diagnostics,
|
|
1083
|
+
tags: preprocessed.tags,
|
|
1084
|
+
containingFile,
|
|
1085
|
+
className,
|
|
1086
|
+
order,
|
|
1087
|
+
inlineStylesExtension: pluginOptions.inlineStylesExtension,
|
|
1088
|
+
resourceFile: resourceFile ?? void 0
|
|
1089
|
+
});
|
|
1090
|
+
debugStyles("stylesheet deferred to Vite pipeline", {
|
|
1091
|
+
stylesheetId,
|
|
1092
|
+
resourceFile: resourceFile ?? "(inline)"
|
|
1093
|
+
});
|
|
1094
|
+
debugStylesV("stylesheet deferred content snapshot", {
|
|
1095
|
+
stylesheetId,
|
|
1096
|
+
filename,
|
|
1097
|
+
resourceFile: resourceFile ?? "(inline)",
|
|
1098
|
+
dependencies: preprocessed.dependencies,
|
|
1099
|
+
diagnostics: preprocessed.diagnostics,
|
|
1100
|
+
tags: preprocessed.tags,
|
|
1101
|
+
...describeStylesheetContent(preprocessed.code)
|
|
1102
|
+
});
|
|
1103
|
+
return stylesheetId;
|
|
1104
|
+
}
|
|
1105
|
+
debugStyles("stylesheet processed inline via preprocessCSS", {
|
|
1106
|
+
filename,
|
|
1107
|
+
resourceFile: resourceFile ?? "(inline)",
|
|
1108
|
+
dataLength: preprocessed.code.length
|
|
1109
|
+
});
|
|
1110
|
+
let stylesheetResult;
|
|
1111
|
+
try {
|
|
1112
|
+
stylesheetResult = await preprocessCSS(preprocessed.code, `${filename}?direct`, resolvedConfig);
|
|
1113
|
+
} catch (e) {
|
|
1114
|
+
debugStyles("preprocessCSS error", {
|
|
1115
|
+
filename,
|
|
1116
|
+
resourceFile: resourceFile ?? "(inline)",
|
|
1117
|
+
error: String(e)
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
return stylesheetResult?.code || "";
|
|
1121
|
+
},
|
|
1122
|
+
processWebWorker(workerFile, containingFile) {
|
|
1123
|
+
return "";
|
|
1124
|
+
}
|
|
1125
|
+
}, (tsCompilerOptions) => {
|
|
1126
|
+
if (shouldExternalizeStyles()) tsCompilerOptions["externalRuntimeStyles"] = true;
|
|
1127
|
+
if (shouldEnableHmr()) {
|
|
1128
|
+
tsCompilerOptions["_enableHmr"] = true;
|
|
1129
|
+
tsCompilerOptions["supportTestBed"] = true;
|
|
1130
|
+
}
|
|
1131
|
+
debugCompiler("tsCompilerOptions (compilation API)", {
|
|
1132
|
+
hmr: pluginOptions.hmr,
|
|
1133
|
+
hasTailwindCss: pluginOptions.hasTailwindCss,
|
|
1134
|
+
watchMode,
|
|
1135
|
+
shouldExternalize: shouldExternalizeStyles(),
|
|
1136
|
+
externalRuntimeStyles: !!tsCompilerOptions["externalRuntimeStyles"],
|
|
1137
|
+
hmrEnabled: !!tsCompilerOptions["_enableHmr"]
|
|
1138
|
+
});
|
|
1139
|
+
if (tsCompilerOptions.compilationMode === "partial") {
|
|
1140
|
+
tsCompilerOptions["supportTestBed"] = true;
|
|
1141
|
+
tsCompilerOptions["supportJitMode"] = true;
|
|
1142
|
+
}
|
|
1143
|
+
if (angularFullVersion >= 2e5) tsCompilerOptions["_enableSelectorless"] = true;
|
|
1144
|
+
if (!isTest && config.build?.lib) {
|
|
1145
|
+
tsCompilerOptions["declaration"] = true;
|
|
1146
|
+
tsCompilerOptions["declarationMap"] = watchMode;
|
|
1147
|
+
tsCompilerOptions["inlineSources"] = true;
|
|
1148
|
+
}
|
|
1149
|
+
if (isTest) tsCompilerOptions["supportTestBed"] = true;
|
|
1150
|
+
return tsCompilerOptions;
|
|
1151
|
+
});
|
|
1152
|
+
debugStyles("external stylesheets from compilation API", {
|
|
1153
|
+
count: compilationResult.externalStylesheets?.size ?? 0,
|
|
1154
|
+
hasPreprocessor: !!pluginOptions.stylePreprocessor,
|
|
1155
|
+
hasInlineMap: !!stylesheetRegistry
|
|
1156
|
+
});
|
|
1157
|
+
const preprocessStats = {
|
|
1158
|
+
total: 0,
|
|
1159
|
+
injected: 0,
|
|
1160
|
+
skipped: 0,
|
|
1161
|
+
errors: 0
|
|
1162
|
+
};
|
|
1163
|
+
compilationResult.externalStylesheets?.forEach((value, key) => {
|
|
1164
|
+
preprocessStats.total++;
|
|
1165
|
+
const angularHash = `${value}.css`;
|
|
1166
|
+
stylesheetRegistry?.registerExternalRequest(angularHash, key);
|
|
1167
|
+
if (stylesheetRegistry && pluginOptions.stylePreprocessor && existsSync(key)) try {
|
|
1168
|
+
const rawCss = readFileSync(key, "utf-8");
|
|
1169
|
+
const preprocessed = preprocessStylesheetResult(rawCss, key, pluginOptions.stylePreprocessor);
|
|
1170
|
+
debugStylesV("external stylesheet raw snapshot", {
|
|
1171
|
+
angularHash,
|
|
1172
|
+
resolvedPath: key,
|
|
1173
|
+
mtimeMs: safeStatMtimeMs(key),
|
|
1174
|
+
...describeStylesheetContent(rawCss)
|
|
1175
|
+
});
|
|
1176
|
+
const servedCss = rewriteRelativeCssImports(preprocessed.code, key);
|
|
1177
|
+
stylesheetRegistry.registerServedStylesheet({
|
|
1178
|
+
publicId: angularHash,
|
|
1179
|
+
sourcePath: key,
|
|
1180
|
+
originalCode: rawCss,
|
|
1181
|
+
normalizedCode: servedCss,
|
|
1182
|
+
dependencies: normalizeStylesheetDependencies(preprocessed.dependencies),
|
|
1183
|
+
diagnostics: preprocessed.diagnostics,
|
|
1184
|
+
tags: preprocessed.tags
|
|
1185
|
+
}, [
|
|
1186
|
+
key,
|
|
1187
|
+
normalizePath(key),
|
|
1188
|
+
basename(key),
|
|
1189
|
+
key.replace(/^\//, "")
|
|
1190
|
+
]);
|
|
1191
|
+
if (servedCss && servedCss !== rawCss) {
|
|
1192
|
+
preprocessStats.injected++;
|
|
1193
|
+
debugStylesV("preprocessed external stylesheet for Tailwind @reference", {
|
|
1194
|
+
angularHash,
|
|
1195
|
+
resolvedPath: key,
|
|
1196
|
+
mtimeMs: safeStatMtimeMs(key),
|
|
1197
|
+
raw: describeStylesheetContent(rawCss),
|
|
1198
|
+
served: describeStylesheetContent(servedCss),
|
|
1199
|
+
dependencies: preprocessed.dependencies,
|
|
1200
|
+
diagnostics: preprocessed.diagnostics,
|
|
1201
|
+
tags: preprocessed.tags
|
|
1202
|
+
});
|
|
1203
|
+
} else {
|
|
1204
|
+
preprocessStats.skipped++;
|
|
1205
|
+
debugStylesV("external stylesheet unchanged after preprocessing", {
|
|
1206
|
+
angularHash,
|
|
1207
|
+
resolvedPath: key,
|
|
1208
|
+
mtimeMs: safeStatMtimeMs(key),
|
|
1209
|
+
raw: describeStylesheetContent(rawCss),
|
|
1210
|
+
served: describeStylesheetContent(servedCss),
|
|
1211
|
+
dependencies: preprocessed.dependencies,
|
|
1212
|
+
diagnostics: preprocessed.diagnostics,
|
|
1213
|
+
tags: preprocessed.tags,
|
|
1214
|
+
hint: "Registry mapping is still registered so Angular component stylesheet HMR can track and refresh this file even when preprocessing makes no textual changes."
|
|
1215
|
+
});
|
|
1216
|
+
}
|
|
1217
|
+
} catch (e) {
|
|
1218
|
+
preprocessStats.errors++;
|
|
1219
|
+
console.warn(`[@analogjs/vite-plugin-angular] failed to preprocess external stylesheet: ${key}: ${e}`);
|
|
1220
|
+
}
|
|
1221
|
+
else {
|
|
1222
|
+
preprocessStats.skipped++;
|
|
1223
|
+
debugStylesV("external stylesheet preprocessing skipped", {
|
|
1224
|
+
filename: angularHash,
|
|
1225
|
+
resolvedPath: key,
|
|
1226
|
+
reason: !stylesheetRegistry ? "no stylesheetRegistry" : !pluginOptions.stylePreprocessor ? "no stylePreprocessor" : "file not found on disk"
|
|
1227
|
+
});
|
|
1228
|
+
}
|
|
1229
|
+
debugStylesV("external stylesheet registered for resolveId mapping", {
|
|
1230
|
+
filename: angularHash,
|
|
1231
|
+
resolvedPath: key
|
|
1232
|
+
});
|
|
1233
|
+
});
|
|
1234
|
+
debugStyles("external stylesheet preprocessing complete", preprocessStats);
|
|
1235
|
+
const diagnostics = await angularCompilation.diagnoseFiles(pluginOptions.disableTypeChecking ? DiagnosticModes.All & ~DiagnosticModes.Semantic : DiagnosticModes.All);
|
|
1236
|
+
const errors = diagnostics.errors?.length ? diagnostics.errors : [];
|
|
1237
|
+
const warnings = diagnostics.warnings?.length ? diagnostics.warnings : [];
|
|
1238
|
+
const templateUpdates = mapTemplateUpdatesToFiles(compilationResult.templateUpdates);
|
|
1239
|
+
if (templateUpdates.size > 0) debugHmr("compilation API template updates", {
|
|
1240
|
+
count: templateUpdates.size,
|
|
1241
|
+
files: [...templateUpdates.keys()]
|
|
1242
|
+
});
|
|
1243
|
+
for (const file of await angularCompilation.emitAffectedFiles()) {
|
|
1244
|
+
const normalizedFilename = normalizePath(file.filename);
|
|
1245
|
+
const templateUpdate = templateUpdates.get(normalizedFilename);
|
|
1246
|
+
if (templateUpdate) classNames.set(normalizedFilename, templateUpdate.className);
|
|
1247
|
+
outputFiles.set(normalizedFilename, {
|
|
1248
|
+
content: file.contents,
|
|
1249
|
+
dependencies: [],
|
|
1250
|
+
errors: errors.map((error) => error.text || ""),
|
|
1251
|
+
warnings: warnings.map((warning) => warning.text || ""),
|
|
1252
|
+
hmrUpdateCode: templateUpdate?.code,
|
|
1253
|
+
hmrEligible: !!templateUpdate?.code
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
async function performCompilation(config, ids) {
|
|
1258
|
+
let resolve;
|
|
1259
|
+
const previousLock = compilationLock;
|
|
1260
|
+
compilationLock = new Promise((r) => {
|
|
1261
|
+
resolve = r;
|
|
1262
|
+
});
|
|
1263
|
+
try {
|
|
1264
|
+
await previousLock;
|
|
1265
|
+
await _doPerformCompilation(config, ids);
|
|
1266
|
+
} finally {
|
|
1267
|
+
resolve();
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
/**
|
|
1271
|
+
* This method share mutable state and performs the actual compilation work.
|
|
1272
|
+
* It should not be called concurrently. Use `performCompilation` which wraps this method in a lock to ensure only one compilation runs at a time.
|
|
1273
|
+
*/
|
|
1274
|
+
async function _doPerformCompilation(config, ids) {
|
|
1275
|
+
if (pluginOptions.useAngularCompilationAPI) {
|
|
1276
|
+
debugCompilationApi("using compilation API path", { modifiedFiles: ids?.length ?? 0 });
|
|
1277
|
+
await performAngularCompilation(config, ids);
|
|
1278
|
+
return;
|
|
1279
|
+
}
|
|
1280
|
+
const isProd = config.mode === "production";
|
|
1281
|
+
const modifiedFiles = new Set(ids ?? []);
|
|
1282
|
+
sourceFileCache$1.invalidate(modifiedFiles);
|
|
1283
|
+
if (ids?.length) for (const id of ids || []) fileTransformMap.delete(id);
|
|
1284
|
+
if (pluginOptions.include.length > 0 && includeCache.length === 0) includeCache = findIncludes();
|
|
1285
|
+
const resolvedTsConfigPath = resolveTsConfigPath();
|
|
1286
|
+
const tsconfigKey = [
|
|
1287
|
+
resolvedTsConfigPath,
|
|
1288
|
+
isProd ? "prod" : "dev",
|
|
1289
|
+
isTest ? "test" : "app",
|
|
1290
|
+
config.build?.lib ? "lib" : "nolib",
|
|
1291
|
+
pluginOptions.hmr ? "hmr" : "nohmr",
|
|
1292
|
+
pluginOptions.hasTailwindCss ? "tw" : "notw"
|
|
1293
|
+
].join("|");
|
|
1294
|
+
let cached = tsconfigOptionsCache.get(tsconfigKey);
|
|
1295
|
+
if (!cached) {
|
|
1296
|
+
const read = compilerCli.readConfiguration(resolvedTsConfigPath, {
|
|
1297
|
+
suppressOutputPathCheck: true,
|
|
1298
|
+
outDir: void 0,
|
|
1299
|
+
sourceMap: false,
|
|
1300
|
+
inlineSourceMap: !isProd,
|
|
1301
|
+
inlineSources: !isProd,
|
|
1302
|
+
declaration: false,
|
|
1303
|
+
declarationMap: false,
|
|
1304
|
+
allowEmptyCodegenFiles: false,
|
|
1305
|
+
annotationsAs: "decorators",
|
|
1306
|
+
enableResourceInlining: false,
|
|
1307
|
+
noEmitOnError: false,
|
|
1308
|
+
mapRoot: void 0,
|
|
1309
|
+
sourceRoot: void 0,
|
|
1310
|
+
supportTestBed: false,
|
|
1311
|
+
supportJitMode: false
|
|
1312
|
+
});
|
|
1313
|
+
cached = {
|
|
1314
|
+
options: read.options,
|
|
1315
|
+
rootNames: read.rootNames
|
|
1316
|
+
};
|
|
1317
|
+
tsconfigOptionsCache.set(tsconfigKey, cached);
|
|
1318
|
+
}
|
|
1319
|
+
const tsCompilerOptions = { ...cached.options };
|
|
1320
|
+
let rootNames = [...cached.rootNames];
|
|
1321
|
+
if (shouldExternalizeStyles()) tsCompilerOptions["externalRuntimeStyles"] = true;
|
|
1322
|
+
if (shouldEnableHmr()) {
|
|
1323
|
+
tsCompilerOptions["_enableHmr"] = true;
|
|
1324
|
+
tsCompilerOptions["supportTestBed"] = true;
|
|
1325
|
+
}
|
|
1326
|
+
debugCompiler("tsCompilerOptions (NgtscProgram path)", {
|
|
1327
|
+
hmr: pluginOptions.hmr,
|
|
1328
|
+
shouldExternalize: shouldExternalizeStyles(),
|
|
1329
|
+
externalRuntimeStyles: !!tsCompilerOptions["externalRuntimeStyles"],
|
|
1330
|
+
hmrEnabled: !!tsCompilerOptions["_enableHmr"]
|
|
1331
|
+
});
|
|
1332
|
+
if (tsCompilerOptions["compilationMode"] === "partial") {
|
|
1333
|
+
tsCompilerOptions["supportTestBed"] = true;
|
|
1334
|
+
tsCompilerOptions["supportJitMode"] = true;
|
|
1335
|
+
}
|
|
1336
|
+
if (angularFullVersion >= 2e5) tsCompilerOptions["_enableSelectorless"] = true;
|
|
1337
|
+
if (!isTest && config.build?.lib) {
|
|
1338
|
+
tsCompilerOptions["declaration"] = true;
|
|
1339
|
+
tsCompilerOptions["declarationMap"] = watchMode;
|
|
1340
|
+
tsCompilerOptions["inlineSources"] = true;
|
|
1341
|
+
}
|
|
1342
|
+
if (isTest) tsCompilerOptions["supportTestBed"] = true;
|
|
1343
|
+
const replacements = pluginOptions.fileReplacements.map((rp) => join(pluginOptions.workspaceRoot, rp.ssr || rp.with));
|
|
1344
|
+
rootNames = union(rootNames, includeCache, replacements);
|
|
1345
|
+
const hostKey = JSON.stringify(tsCompilerOptions);
|
|
1346
|
+
let host;
|
|
1347
|
+
if (cachedHost && cachedHostKey === hostKey) host = cachedHost;
|
|
1348
|
+
else {
|
|
1349
|
+
host = ts.createIncrementalCompilerHost(tsCompilerOptions, {
|
|
1350
|
+
...ts.sys,
|
|
1351
|
+
readFile(path, encoding) {
|
|
1352
|
+
if (fileTransformMap.has(path)) return fileTransformMap.get(path);
|
|
1353
|
+
const file = ts.sys.readFile.call(null, path, encoding);
|
|
1354
|
+
if (file) fileTransformMap.set(path, file);
|
|
1355
|
+
return file;
|
|
1356
|
+
}
|
|
1357
|
+
});
|
|
1358
|
+
cachedHost = host;
|
|
1359
|
+
cachedHostKey = hostKey;
|
|
1360
|
+
if (watchMode) augmentHostWithCaching(host, sourceFileCache$1);
|
|
1361
|
+
}
|
|
1362
|
+
if (!jit) {
|
|
1363
|
+
const externalizeStyles = !!tsCompilerOptions["externalRuntimeStyles"];
|
|
1364
|
+
stylesheetRegistry = externalizeStyles ? new AnalogStylesheetRegistry() : void 0;
|
|
1365
|
+
if (stylesheetRegistry) configureStylePipelineRegistry(pluginOptions.stylePipeline, stylesheetRegistry, { workspaceRoot: pluginOptions.workspaceRoot });
|
|
1366
|
+
debugStyles("stylesheet registry initialized (NgtscProgram path)", { externalizeStyles });
|
|
1367
|
+
augmentHostWithResources(host, styleTransform, {
|
|
1368
|
+
inlineStylesExtension: pluginOptions.inlineStylesExtension,
|
|
1369
|
+
isProd,
|
|
1370
|
+
stylesheetRegistry,
|
|
1371
|
+
sourceFileCache: sourceFileCache$1,
|
|
1372
|
+
stylePreprocessor: pluginOptions.stylePreprocessor
|
|
1373
|
+
});
|
|
1374
|
+
}
|
|
1375
|
+
/**
|
|
1376
|
+
* Creates a new NgtscProgram to analyze/re-analyze
|
|
1377
|
+
* the source files and create a file emitter.
|
|
1378
|
+
* This is shared between an initial build and a hot update.
|
|
1379
|
+
*/
|
|
1380
|
+
let typeScriptProgram;
|
|
1381
|
+
let angularCompiler;
|
|
1382
|
+
const oldBuilder = builder ?? ts.readBuilderProgram(tsCompilerOptions, host);
|
|
1383
|
+
if (!jit) {
|
|
1384
|
+
const angularProgram = new compilerCli.NgtscProgram(rootNames, tsCompilerOptions, host, nextProgram);
|
|
1385
|
+
angularCompiler = angularProgram.compiler;
|
|
1386
|
+
typeScriptProgram = angularProgram.compiler.getCurrentProgram();
|
|
1387
|
+
augmentProgramWithVersioning(typeScriptProgram);
|
|
1388
|
+
builder = ts.createEmitAndSemanticDiagnosticsBuilderProgram(typeScriptProgram, host, oldBuilder);
|
|
1389
|
+
nextProgram = angularProgram;
|
|
1390
|
+
} else {
|
|
1391
|
+
builder = ts.createEmitAndSemanticDiagnosticsBuilderProgram(rootNames, tsCompilerOptions, host, oldBuilder);
|
|
1392
|
+
typeScriptProgram = builder.getProgram();
|
|
1393
|
+
}
|
|
1394
|
+
if (!watchMode) builder = ts.createAbstractBuilder(typeScriptProgram, host, oldBuilder);
|
|
1395
|
+
if (angularCompiler) await angularCompiler.analyzeAsync();
|
|
1396
|
+
const transformers = mergeTransformers({ before: jit ? [compilerCli.constructorParametersDownlevelTransform(builder.getProgram()), cjt(() => builder.getProgram().getTypeChecker())] : [] }, jit ? {} : angularCompiler.prepareEmit().transformers);
|
|
1397
|
+
const fileMetadata = getFileMetadata(builder, angularCompiler, pluginOptions.hmr, pluginOptions.disableTypeChecking);
|
|
1398
|
+
const writeFileCallback = (_filename, content, _a, _b, sourceFiles) => {
|
|
1399
|
+
if (!sourceFiles?.length) return;
|
|
1400
|
+
const filename = normalizePath(sourceFiles[0].fileName);
|
|
1401
|
+
if (filename.includes("ngtypecheck.ts") || filename.includes(".d.")) return;
|
|
1402
|
+
const metadata = watchMode ? fileMetadata(filename) : {};
|
|
1403
|
+
outputFiles.set(filename, {
|
|
1404
|
+
content,
|
|
1405
|
+
dependencies: [],
|
|
1406
|
+
errors: metadata.errors,
|
|
1407
|
+
warnings: metadata.warnings,
|
|
1408
|
+
hmrUpdateCode: metadata.hmrUpdateCode,
|
|
1409
|
+
hmrEligible: metadata.hmrEligible
|
|
1410
|
+
});
|
|
1411
|
+
};
|
|
1412
|
+
const writeOutputFile = (id) => {
|
|
1413
|
+
const sourceFile = builder.getSourceFile(id);
|
|
1414
|
+
if (!sourceFile) return;
|
|
1415
|
+
let content = "";
|
|
1416
|
+
builder.emit(sourceFile, (filename, data) => {
|
|
1417
|
+
if (/\.[cm]?js$/.test(filename)) content = data;
|
|
1418
|
+
if (!watchMode && !isTest && /\.d\.ts/.test(filename) && !filename.includes(".ngtypecheck.")) {
|
|
1419
|
+
const declarationPath = resolve(config.root, config.build.outDir, relative(config.root, filename)).replace("/src/", "/");
|
|
1420
|
+
const declarationFileDir = declarationPath.replace(basename(filename), "").replace("/src/", "/");
|
|
1421
|
+
declarationFiles.push({
|
|
1422
|
+
declarationFileDir,
|
|
1423
|
+
declarationPath,
|
|
1424
|
+
data
|
|
1425
|
+
});
|
|
1426
|
+
}
|
|
1427
|
+
}, void 0, void 0, transformers);
|
|
1428
|
+
writeFileCallback(id, content, false, void 0, [sourceFile]);
|
|
1429
|
+
if (angularCompiler) angularCompiler.incrementalCompilation.recordSuccessfulEmit(sourceFile);
|
|
1430
|
+
};
|
|
1431
|
+
if (watchMode) {
|
|
1432
|
+
if (ids && ids.length > 0) ids.forEach((id) => writeOutputFile(id));
|
|
1433
|
+
else if (isTest) while (builder.emitNextAffectedFile(writeFileCallback, void 0, void 0, transformers));
|
|
1434
|
+
}
|
|
1435
|
+
if (!isTest)
|
|
1436
|
+
/**
|
|
1437
|
+
* Perf: Output files on demand so the dev server
|
|
1438
|
+
* isn't blocked when emitting files.
|
|
1439
|
+
*/
|
|
1440
|
+
outputFile = writeOutputFile;
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
function createFsWatcherCacheInvalidator(invalidateFsCaches, invalidateTsconfigCaches, performCompilation) {
|
|
1444
|
+
return async () => {
|
|
1445
|
+
invalidateFsCaches();
|
|
1446
|
+
invalidateTsconfigCaches();
|
|
1447
|
+
await performCompilation();
|
|
1448
|
+
};
|
|
1449
|
+
}
|
|
31
1450
|
/**
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
// Track if the component uses ShadowDOM encapsulation
|
|
274
|
-
// Shadow DOM components currently require a full reload.
|
|
275
|
-
// Vite's CSS hot replacement does not support shadow root searching.
|
|
276
|
-
if (encapsulation !== 'shadow') {
|
|
277
|
-
ctx.server.ws.send({
|
|
278
|
-
type: 'update',
|
|
279
|
-
updates: [
|
|
280
|
-
{
|
|
281
|
-
type: 'css-update',
|
|
282
|
-
timestamp: Date.now(),
|
|
283
|
-
path: isDirect.url,
|
|
284
|
-
acceptedPath: isDirect.file,
|
|
285
|
-
},
|
|
286
|
-
],
|
|
287
|
-
});
|
|
288
|
-
return ctx.modules
|
|
289
|
-
.filter((mod) => {
|
|
290
|
-
// Component stylesheets will have 2 modules (*.component.scss and *.component.scss?direct&ngcomp=xyz&e=x)
|
|
291
|
-
// 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"
|
|
292
|
-
return mod.file !== ctx.file || mod.id !== isDirect.id;
|
|
293
|
-
})
|
|
294
|
-
.map((mod) => {
|
|
295
|
-
if (mod.file === ctx.file) {
|
|
296
|
-
return markModuleSelfAccepting(mod);
|
|
297
|
-
}
|
|
298
|
-
return mod;
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
return ctx.modules;
|
|
304
|
-
}
|
|
305
|
-
const mods = [];
|
|
306
|
-
const updates = [];
|
|
307
|
-
ctx.modules.forEach((mod) => {
|
|
308
|
-
mod.importers.forEach((imp) => {
|
|
309
|
-
ctx.server.moduleGraph.invalidateModule(imp);
|
|
310
|
-
if (pluginOptions.liveReload && classNames.get(imp.id)) {
|
|
311
|
-
updates.push(imp.id);
|
|
312
|
-
}
|
|
313
|
-
else {
|
|
314
|
-
mods.push(imp);
|
|
315
|
-
}
|
|
316
|
-
});
|
|
317
|
-
});
|
|
318
|
-
pendingCompilation = performCompilation(resolvedConfig, [
|
|
319
|
-
...mods.map((mod) => mod.id),
|
|
320
|
-
...updates,
|
|
321
|
-
]);
|
|
322
|
-
if (updates.length > 0) {
|
|
323
|
-
await pendingCompilation;
|
|
324
|
-
pendingCompilation = null;
|
|
325
|
-
updates.forEach((updateId) => {
|
|
326
|
-
const impRelativeFileId = `${relative(process.cwd(), updateId)}@${classNames.get(updateId)}`;
|
|
327
|
-
sendHMRComponentUpdate(ctx.server, impRelativeFileId);
|
|
328
|
-
});
|
|
329
|
-
return ctx.modules.map((mod) => {
|
|
330
|
-
if (mod.id === ctx.file) {
|
|
331
|
-
return markModuleSelfAccepting(mod);
|
|
332
|
-
}
|
|
333
|
-
return mod;
|
|
334
|
-
});
|
|
335
|
-
}
|
|
336
|
-
return mods;
|
|
337
|
-
}
|
|
338
|
-
// clear HMR updates with a full reload
|
|
339
|
-
classNames.clear();
|
|
340
|
-
return ctx.modules;
|
|
341
|
-
},
|
|
342
|
-
resolveId(id, importer) {
|
|
343
|
-
if (jit && id.startsWith('angular:jit:')) {
|
|
344
|
-
const path = id.split(';')[1];
|
|
345
|
-
return `${normalizePath(resolve(dirname(importer), path))}?${id.includes(':style') ? 'inline' : 'raw'}`;
|
|
346
|
-
}
|
|
347
|
-
// Map angular external styleUrls to the source file
|
|
348
|
-
if (isComponentStyleSheet(id)) {
|
|
349
|
-
const componentStyles = externalComponentStyles?.get(getFilenameFromPath(id));
|
|
350
|
-
if (componentStyles) {
|
|
351
|
-
return componentStyles + new URL(id, 'http://localhost').search;
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
return undefined;
|
|
355
|
-
},
|
|
356
|
-
async load(id) {
|
|
357
|
-
// Map angular inline styles to the source text
|
|
358
|
-
if (isComponentStyleSheet(id)) {
|
|
359
|
-
const componentStyles = inlineComponentStyles?.get(getFilenameFromPath(id));
|
|
360
|
-
if (componentStyles) {
|
|
361
|
-
return componentStyles;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
return;
|
|
365
|
-
},
|
|
366
|
-
transform: {
|
|
367
|
-
filter: {
|
|
368
|
-
id: {
|
|
369
|
-
include: [TS_EXT_REGEX],
|
|
370
|
-
exclude: [/node_modules/, 'type=script', '@ng/component'],
|
|
371
|
-
},
|
|
372
|
-
},
|
|
373
|
-
async handler(code, id) {
|
|
374
|
-
/**
|
|
375
|
-
* Check for options.transformFilter
|
|
376
|
-
*/
|
|
377
|
-
if (options?.transformFilter &&
|
|
378
|
-
!(options?.transformFilter(code, id) ?? true)) {
|
|
379
|
-
return;
|
|
380
|
-
}
|
|
381
|
-
if (pluginOptions.useAngularCompilationAPI) {
|
|
382
|
-
const isAngular = /(Component|Directive|Pipe|Injectable|NgModule)\(/.test(code);
|
|
383
|
-
if (!isAngular) {
|
|
384
|
-
return;
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
/**
|
|
388
|
-
* Skip transforming content files
|
|
389
|
-
*/
|
|
390
|
-
if (id.includes('?') && id.includes('analog-content-')) {
|
|
391
|
-
return;
|
|
392
|
-
}
|
|
393
|
-
/**
|
|
394
|
-
* Encapsulate component stylesheets that use emulated encapsulation
|
|
395
|
-
*/
|
|
396
|
-
if (pluginOptions.liveReload && isComponentStyleSheet(id)) {
|
|
397
|
-
const { encapsulation, componentId } = getComponentStyleSheetMeta(id);
|
|
398
|
-
if (encapsulation === 'emulated' && componentId) {
|
|
399
|
-
const encapsulated = ngCompiler.encapsulateStyle(code, componentId);
|
|
400
|
-
return {
|
|
401
|
-
code: encapsulated,
|
|
402
|
-
map: null,
|
|
403
|
-
};
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
if (id.includes('.ts?')) {
|
|
407
|
-
// Strip the query string off the ID
|
|
408
|
-
// in case of a dynamically loaded file
|
|
409
|
-
id = id.replace(/\?(.*)/, '');
|
|
410
|
-
}
|
|
411
|
-
fileTransformMap.set(id, code);
|
|
412
|
-
/**
|
|
413
|
-
* Re-analyze on each transform
|
|
414
|
-
* for test(Vitest)
|
|
415
|
-
*/
|
|
416
|
-
if (isTest) {
|
|
417
|
-
if (isVitestVscode && !initialCompilation) {
|
|
418
|
-
// Do full initial compilation
|
|
419
|
-
pendingCompilation = performCompilation(resolvedConfig);
|
|
420
|
-
initialCompilation = true;
|
|
421
|
-
}
|
|
422
|
-
const tsMod = viteServer?.moduleGraph.getModuleById(id);
|
|
423
|
-
if (tsMod) {
|
|
424
|
-
const invalidated = tsMod.lastInvalidationTimestamp;
|
|
425
|
-
if (testWatchMode && invalidated) {
|
|
426
|
-
pendingCompilation = performCompilation(resolvedConfig, [id]);
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
const hasComponent = code.includes('@Component');
|
|
431
|
-
const templateUrls = hasComponent
|
|
432
|
-
? templateUrlsResolver.resolve(code, id)
|
|
433
|
-
: [];
|
|
434
|
-
const styleUrls = hasComponent
|
|
435
|
-
? styleUrlsResolver.resolve(code, id)
|
|
436
|
-
: [];
|
|
437
|
-
if (hasComponent && watchMode) {
|
|
438
|
-
for (const urlSet of [...templateUrls, ...styleUrls]) {
|
|
439
|
-
// `urlSet` is a string where a relative path is joined with an
|
|
440
|
-
// absolute path using the `|` symbol.
|
|
441
|
-
// For example: `./app.component.html|/home/projects/analog/src/app/app.component.html`.
|
|
442
|
-
const [, absoluteFileUrl] = urlSet.split('|');
|
|
443
|
-
this.addWatchFile(absoluteFileUrl);
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
if (pendingCompilation) {
|
|
447
|
-
await pendingCompilation;
|
|
448
|
-
pendingCompilation = null;
|
|
449
|
-
}
|
|
450
|
-
const typescriptResult = fileEmitter(id);
|
|
451
|
-
if (typescriptResult?.warnings &&
|
|
452
|
-
typescriptResult?.warnings.length > 0) {
|
|
453
|
-
this.warn(`${typescriptResult.warnings.join('\n')}`);
|
|
454
|
-
}
|
|
455
|
-
if (typescriptResult?.errors && typescriptResult?.errors.length > 0) {
|
|
456
|
-
this.error(`${typescriptResult.errors.join('\n')}`);
|
|
457
|
-
}
|
|
458
|
-
// return fileEmitter
|
|
459
|
-
let data = typescriptResult?.content ?? '';
|
|
460
|
-
if (jit && data.includes('angular:jit:')) {
|
|
461
|
-
data = data.replace(/angular:jit:style:inline;/g, 'virtual:angular:jit:style:inline;');
|
|
462
|
-
templateUrls.forEach((templateUrlSet) => {
|
|
463
|
-
const [templateFile, resolvedTemplateUrl] = templateUrlSet.split('|');
|
|
464
|
-
data = data.replace(`angular:jit:template:file;${templateFile}`, `${resolvedTemplateUrl}?raw`);
|
|
465
|
-
});
|
|
466
|
-
styleUrls.forEach((styleUrlSet) => {
|
|
467
|
-
const [styleFile, resolvedStyleUrl] = styleUrlSet.split('|');
|
|
468
|
-
data = data.replace(`angular:jit:style:file;${styleFile}`, `${resolvedStyleUrl}?inline`);
|
|
469
|
-
});
|
|
470
|
-
}
|
|
471
|
-
return {
|
|
472
|
-
code: data,
|
|
473
|
-
map: null,
|
|
474
|
-
};
|
|
475
|
-
},
|
|
476
|
-
},
|
|
477
|
-
closeBundle() {
|
|
478
|
-
declarationFiles.forEach(({ declarationFileDir, declarationPath, data }) => {
|
|
479
|
-
mkdirSync(declarationFileDir, { recursive: true });
|
|
480
|
-
writeFileSync(declarationPath, data, 'utf-8');
|
|
481
|
-
});
|
|
482
|
-
},
|
|
483
|
-
};
|
|
484
|
-
}
|
|
485
|
-
return [
|
|
486
|
-
replaceFiles(pluginOptions.fileReplacements, pluginOptions.workspaceRoot),
|
|
487
|
-
angularPlugin(),
|
|
488
|
-
pluginOptions.liveReload && liveReloadPlugin({ classNames, fileEmitter }),
|
|
489
|
-
...(isTest && !isStackBlitz ? angularVitestPlugins() : []),
|
|
490
|
-
(jit &&
|
|
491
|
-
jitPlugin({
|
|
492
|
-
inlineStylesExtension: pluginOptions.inlineStylesExtension,
|
|
493
|
-
})),
|
|
494
|
-
buildOptimizerPlugin({
|
|
495
|
-
supportedBrowsers: pluginOptions.supportedBrowsers,
|
|
496
|
-
jit,
|
|
497
|
-
}),
|
|
498
|
-
routerPlugin(),
|
|
499
|
-
angularFullVersion < 190004 && pendingTasksPlugin(),
|
|
500
|
-
nxFolderPlugin(),
|
|
501
|
-
].filter(Boolean);
|
|
502
|
-
function findIncludes() {
|
|
503
|
-
const workspaceRoot = normalizePath(resolve(pluginOptions.workspaceRoot));
|
|
504
|
-
// Map include patterns to absolute workspace paths
|
|
505
|
-
const globs = [
|
|
506
|
-
...pluginOptions.include.map((glob) => `${workspaceRoot}${glob}`),
|
|
507
|
-
];
|
|
508
|
-
// Discover TypeScript files using tinyglobby
|
|
509
|
-
return globSync(globs, {
|
|
510
|
-
dot: true,
|
|
511
|
-
absolute: true,
|
|
512
|
-
});
|
|
513
|
-
}
|
|
514
|
-
function createTsConfigGetter(tsconfigOrGetter) {
|
|
515
|
-
if (typeof tsconfigOrGetter === 'function') {
|
|
516
|
-
return tsconfigOrGetter;
|
|
517
|
-
}
|
|
518
|
-
return () => tsconfigOrGetter || '';
|
|
519
|
-
}
|
|
520
|
-
function getTsConfigPath(root, tsconfig, isProd, isTest, isLib) {
|
|
521
|
-
if (tsconfig && isAbsolute(tsconfig)) {
|
|
522
|
-
if (!existsSync(tsconfig)) {
|
|
523
|
-
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.`);
|
|
524
|
-
}
|
|
525
|
-
return tsconfig;
|
|
526
|
-
}
|
|
527
|
-
let tsconfigFilePath = './tsconfig.app.json';
|
|
528
|
-
if (isLib) {
|
|
529
|
-
tsconfigFilePath = isProd
|
|
530
|
-
? './tsconfig.lib.prod.json'
|
|
531
|
-
: './tsconfig.lib.json';
|
|
532
|
-
}
|
|
533
|
-
if (isTest) {
|
|
534
|
-
tsconfigFilePath = './tsconfig.spec.json';
|
|
535
|
-
}
|
|
536
|
-
if (tsconfig) {
|
|
537
|
-
tsconfigFilePath = tsconfig;
|
|
538
|
-
}
|
|
539
|
-
const resolvedPath = resolve(root, tsconfigFilePath);
|
|
540
|
-
if (!existsSync(resolvedPath)) {
|
|
541
|
-
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.`);
|
|
542
|
-
}
|
|
543
|
-
return resolvedPath;
|
|
544
|
-
}
|
|
545
|
-
function resolveTsConfigPath() {
|
|
546
|
-
const tsconfigValue = pluginOptions.tsconfigGetter();
|
|
547
|
-
return getTsConfigPath(tsConfigResolutionContext.root, tsconfigValue, tsConfigResolutionContext.isProd, isTest, tsConfigResolutionContext.isLib);
|
|
548
|
-
}
|
|
549
|
-
async function performAngularCompilation(config) {
|
|
550
|
-
const compilation = await createAngularCompilation(!!pluginOptions.jit, false);
|
|
551
|
-
const resolvedTsConfigPath = resolveTsConfigPath();
|
|
552
|
-
const compilationResult = await compilation.initialize(resolvedTsConfigPath, {
|
|
553
|
-
async transformStylesheet(data, containingFile, resourceFile, order, className) {
|
|
554
|
-
if (pluginOptions.liveReload) {
|
|
555
|
-
const id = createHash('sha256')
|
|
556
|
-
.update(containingFile)
|
|
557
|
-
.update(className)
|
|
558
|
-
.update(String(order))
|
|
559
|
-
.update(data)
|
|
560
|
-
.digest('hex');
|
|
561
|
-
const filename = id + '.' + pluginOptions.inlineStylesExtension;
|
|
562
|
-
inlineComponentStyles.set(filename, data);
|
|
563
|
-
return filename;
|
|
564
|
-
}
|
|
565
|
-
const filename = resourceFile ??
|
|
566
|
-
containingFile.replace('.ts', `.${options?.inlineStylesExtension}`);
|
|
567
|
-
let stylesheetResult;
|
|
568
|
-
try {
|
|
569
|
-
stylesheetResult = await preprocessCSS(data, `${filename}?direct`, resolvedConfig);
|
|
570
|
-
}
|
|
571
|
-
catch (e) {
|
|
572
|
-
console.error(`${e}`);
|
|
573
|
-
}
|
|
574
|
-
return stylesheetResult?.code || '';
|
|
575
|
-
},
|
|
576
|
-
processWebWorker(workerFile, containingFile) {
|
|
577
|
-
return '';
|
|
578
|
-
},
|
|
579
|
-
}, (tsCompilerOptions) => {
|
|
580
|
-
if (pluginOptions.liveReload && watchMode) {
|
|
581
|
-
tsCompilerOptions['_enableHmr'] = true;
|
|
582
|
-
tsCompilerOptions['externalRuntimeStyles'] = true;
|
|
583
|
-
// Workaround for https://github.com/angular/angular/issues/59310
|
|
584
|
-
// Force extra instructions to be generated for HMR w/defer
|
|
585
|
-
tsCompilerOptions['supportTestBed'] = true;
|
|
586
|
-
}
|
|
587
|
-
if (tsCompilerOptions.compilationMode === 'partial') {
|
|
588
|
-
// These options can't be false in partial mode
|
|
589
|
-
tsCompilerOptions['supportTestBed'] = true;
|
|
590
|
-
tsCompilerOptions['supportJitMode'] = true;
|
|
591
|
-
}
|
|
592
|
-
if (!isTest && config.build?.lib) {
|
|
593
|
-
tsCompilerOptions['declaration'] = true;
|
|
594
|
-
tsCompilerOptions['declarationMap'] = watchMode;
|
|
595
|
-
tsCompilerOptions['inlineSources'] = true;
|
|
596
|
-
}
|
|
597
|
-
if (isTest) {
|
|
598
|
-
// Allow `TestBed.overrideXXX()` APIs.
|
|
599
|
-
tsCompilerOptions['supportTestBed'] = true;
|
|
600
|
-
}
|
|
601
|
-
return tsCompilerOptions;
|
|
602
|
-
});
|
|
603
|
-
compilationResult.externalStylesheets?.forEach((value, key) => {
|
|
604
|
-
externalComponentStyles?.set(`${value}.css`, key);
|
|
605
|
-
});
|
|
606
|
-
const diagnostics = await compilation.diagnoseFiles(pluginOptions.disableTypeChecking
|
|
607
|
-
? DiagnosticModes.All & ~DiagnosticModes.Semantic
|
|
608
|
-
: DiagnosticModes.All);
|
|
609
|
-
const errors = diagnostics.errors?.length ? diagnostics.errors : [];
|
|
610
|
-
const warnings = diagnostics.warnings?.length ? diagnostics.warnings : [];
|
|
611
|
-
for (const file of await compilation.emitAffectedFiles()) {
|
|
612
|
-
outputFiles.set(file.filename, {
|
|
613
|
-
content: file.contents,
|
|
614
|
-
dependencies: [],
|
|
615
|
-
errors: errors.map((error) => error.text || ''),
|
|
616
|
-
warnings: warnings.map((warning) => warning.text || ''),
|
|
617
|
-
});
|
|
618
|
-
}
|
|
619
|
-
compilation.close?.();
|
|
620
|
-
}
|
|
621
|
-
async function performCompilation(config, ids) {
|
|
622
|
-
let resolve;
|
|
623
|
-
const previousLock = compilationLock;
|
|
624
|
-
compilationLock = new Promise((r) => {
|
|
625
|
-
resolve = r;
|
|
626
|
-
});
|
|
627
|
-
try {
|
|
628
|
-
await previousLock;
|
|
629
|
-
await _doPerformCompilation(config, ids);
|
|
630
|
-
}
|
|
631
|
-
finally {
|
|
632
|
-
resolve();
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
/**
|
|
636
|
-
* This method share mutable state and performs the actual compilation work.
|
|
637
|
-
* It should not be called concurrently. Use `performCompilation` which wraps this method in a lock to ensure only one compilation runs at a time.
|
|
638
|
-
*/
|
|
639
|
-
async function _doPerformCompilation(config, ids) {
|
|
640
|
-
if (pluginOptions.useAngularCompilationAPI) {
|
|
641
|
-
await performAngularCompilation(config);
|
|
642
|
-
return;
|
|
643
|
-
}
|
|
644
|
-
const isProd = config.mode === 'production';
|
|
645
|
-
const modifiedFiles = new Set(ids ?? []);
|
|
646
|
-
sourceFileCache.invalidate(modifiedFiles);
|
|
647
|
-
if (ids?.length) {
|
|
648
|
-
for (const id of ids || []) {
|
|
649
|
-
fileTransformMap.delete(id);
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
// Cached include discovery (invalidated only on FS events)
|
|
653
|
-
if (pluginOptions.include.length > 0 && includeCache.length === 0) {
|
|
654
|
-
includeCache = findIncludes();
|
|
655
|
-
}
|
|
656
|
-
const resolvedTsConfigPath = resolveTsConfigPath();
|
|
657
|
-
const tsconfigKey = [
|
|
658
|
-
resolvedTsConfigPath,
|
|
659
|
-
isProd ? 'prod' : 'dev',
|
|
660
|
-
isTest ? 'test' : 'app',
|
|
661
|
-
config.build?.lib ? 'lib' : 'nolib',
|
|
662
|
-
].join('|');
|
|
663
|
-
let cached = tsconfigOptionsCache.get(tsconfigKey);
|
|
664
|
-
if (!cached) {
|
|
665
|
-
const read = compilerCli.readConfiguration(resolvedTsConfigPath, {
|
|
666
|
-
suppressOutputPathCheck: true,
|
|
667
|
-
outDir: undefined,
|
|
668
|
-
sourceMap: false,
|
|
669
|
-
inlineSourceMap: !isProd,
|
|
670
|
-
inlineSources: !isProd,
|
|
671
|
-
declaration: false,
|
|
672
|
-
declarationMap: false,
|
|
673
|
-
allowEmptyCodegenFiles: false,
|
|
674
|
-
annotationsAs: 'decorators',
|
|
675
|
-
enableResourceInlining: false,
|
|
676
|
-
noEmitOnError: false,
|
|
677
|
-
mapRoot: undefined,
|
|
678
|
-
sourceRoot: undefined,
|
|
679
|
-
supportTestBed: false,
|
|
680
|
-
supportJitMode: false,
|
|
681
|
-
});
|
|
682
|
-
cached = { options: read.options, rootNames: read.rootNames };
|
|
683
|
-
tsconfigOptionsCache.set(tsconfigKey, cached);
|
|
684
|
-
}
|
|
685
|
-
// Clone options before mutation (preserve cache purity)
|
|
686
|
-
const tsCompilerOptions = { ...cached.options };
|
|
687
|
-
let rootNames = [...cached.rootNames];
|
|
688
|
-
if (pluginOptions.liveReload && watchMode) {
|
|
689
|
-
tsCompilerOptions['_enableHmr'] = true;
|
|
690
|
-
tsCompilerOptions['externalRuntimeStyles'] = true;
|
|
691
|
-
// Workaround for https://github.com/angular/angular/issues/59310
|
|
692
|
-
// Force extra instructions to be generated for HMR w/defer
|
|
693
|
-
tsCompilerOptions['supportTestBed'] = true;
|
|
694
|
-
}
|
|
695
|
-
if (tsCompilerOptions['compilationMode'] === 'partial') {
|
|
696
|
-
// These options can't be false in partial mode
|
|
697
|
-
tsCompilerOptions['supportTestBed'] = true;
|
|
698
|
-
tsCompilerOptions['supportJitMode'] = true;
|
|
699
|
-
}
|
|
700
|
-
if (!isTest && config.build?.lib) {
|
|
701
|
-
tsCompilerOptions['declaration'] = true;
|
|
702
|
-
tsCompilerOptions['declarationMap'] = watchMode;
|
|
703
|
-
tsCompilerOptions['inlineSources'] = true;
|
|
704
|
-
}
|
|
705
|
-
if (isTest) {
|
|
706
|
-
// Allow `TestBed.overrideXXX()` APIs.
|
|
707
|
-
tsCompilerOptions['supportTestBed'] = true;
|
|
708
|
-
}
|
|
709
|
-
const replacements = pluginOptions.fileReplacements.map((rp) => join(pluginOptions.workspaceRoot, rp.ssr || rp.with));
|
|
710
|
-
// Merge + dedupe root names
|
|
711
|
-
rootNames = [...new Set([...rootNames, ...includeCache, ...replacements])];
|
|
712
|
-
const hostKey = JSON.stringify(tsCompilerOptions);
|
|
713
|
-
let host;
|
|
714
|
-
if (cachedHost && cachedHostKey === hostKey) {
|
|
715
|
-
host = cachedHost;
|
|
716
|
-
}
|
|
717
|
-
else {
|
|
718
|
-
host = ts.createIncrementalCompilerHost(tsCompilerOptions, {
|
|
719
|
-
...ts.sys,
|
|
720
|
-
readFile(path, encoding) {
|
|
721
|
-
if (fileTransformMap.has(path)) {
|
|
722
|
-
return fileTransformMap.get(path);
|
|
723
|
-
}
|
|
724
|
-
const file = ts.sys.readFile.call(null, path, encoding);
|
|
725
|
-
if (file) {
|
|
726
|
-
fileTransformMap.set(path, file);
|
|
727
|
-
}
|
|
728
|
-
return file;
|
|
729
|
-
},
|
|
730
|
-
});
|
|
731
|
-
cachedHost = host;
|
|
732
|
-
cachedHostKey = hostKey;
|
|
733
|
-
// Only store cache if in watch mode
|
|
734
|
-
if (watchMode) {
|
|
735
|
-
augmentHostWithCaching(host, sourceFileCache);
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
if (!jit) {
|
|
739
|
-
inlineComponentStyles = tsCompilerOptions['externalRuntimeStyles']
|
|
740
|
-
? new Map()
|
|
741
|
-
: undefined;
|
|
742
|
-
externalComponentStyles = tsCompilerOptions['externalRuntimeStyles']
|
|
743
|
-
? new Map()
|
|
744
|
-
: undefined;
|
|
745
|
-
augmentHostWithResources(host, styleTransform, {
|
|
746
|
-
inlineStylesExtension: pluginOptions.inlineStylesExtension,
|
|
747
|
-
isProd,
|
|
748
|
-
inlineComponentStyles,
|
|
749
|
-
externalComponentStyles,
|
|
750
|
-
sourceFileCache,
|
|
751
|
-
});
|
|
752
|
-
}
|
|
753
|
-
/**
|
|
754
|
-
* Creates a new NgtscProgram to analyze/re-analyze
|
|
755
|
-
* the source files and create a file emitter.
|
|
756
|
-
* This is shared between an initial build and a hot update.
|
|
757
|
-
*/
|
|
758
|
-
let typeScriptProgram;
|
|
759
|
-
let angularCompiler;
|
|
760
|
-
const oldBuilder = builder ?? ts.readBuilderProgram(tsCompilerOptions, host);
|
|
761
|
-
if (!jit) {
|
|
762
|
-
// Create the Angular specific program that contains the Angular compiler
|
|
763
|
-
const angularProgram = new compilerCli.NgtscProgram(rootNames, tsCompilerOptions, host, nextProgram);
|
|
764
|
-
angularCompiler = angularProgram.compiler;
|
|
765
|
-
typeScriptProgram = angularProgram.compiler.getCurrentProgram();
|
|
766
|
-
augmentProgramWithVersioning(typeScriptProgram);
|
|
767
|
-
builder = ts.createEmitAndSemanticDiagnosticsBuilderProgram(typeScriptProgram, host, oldBuilder);
|
|
768
|
-
nextProgram = angularProgram;
|
|
769
|
-
}
|
|
770
|
-
else {
|
|
771
|
-
builder = ts.createEmitAndSemanticDiagnosticsBuilderProgram(rootNames, tsCompilerOptions, host, oldBuilder);
|
|
772
|
-
typeScriptProgram = builder.getProgram();
|
|
773
|
-
}
|
|
774
|
-
if (!watchMode) {
|
|
775
|
-
// When not in watch mode, the startup cost of the incremental analysis can be avoided by
|
|
776
|
-
// using an abstract builder that only wraps a TypeScript program.
|
|
777
|
-
builder = ts.createAbstractBuilder(typeScriptProgram, host, oldBuilder);
|
|
778
|
-
}
|
|
779
|
-
if (angularCompiler) {
|
|
780
|
-
await angularCompiler.analyzeAsync();
|
|
781
|
-
}
|
|
782
|
-
const beforeTransformers = jit
|
|
783
|
-
? [
|
|
784
|
-
compilerCli.constructorParametersDownlevelTransform(builder.getProgram()),
|
|
785
|
-
createJitResourceTransformer(() => builder.getProgram().getTypeChecker()),
|
|
786
|
-
]
|
|
787
|
-
: [];
|
|
788
|
-
const transformers = mergeTransformers({ before: beforeTransformers }, jit ? {} : angularCompiler.prepareEmit().transformers);
|
|
789
|
-
const fileMetadata = getFileMetadata(builder, angularCompiler, pluginOptions.liveReload, pluginOptions.disableTypeChecking);
|
|
790
|
-
const writeFileCallback = (_filename, content, _a, _b, sourceFiles) => {
|
|
791
|
-
if (!sourceFiles?.length) {
|
|
792
|
-
return;
|
|
793
|
-
}
|
|
794
|
-
const filename = normalizePath(sourceFiles[0].fileName);
|
|
795
|
-
if (filename.includes('ngtypecheck.ts') || filename.includes('.d.')) {
|
|
796
|
-
return;
|
|
797
|
-
}
|
|
798
|
-
const metadata = watchMode ? fileMetadata(filename) : {};
|
|
799
|
-
outputFiles.set(filename, {
|
|
800
|
-
content,
|
|
801
|
-
dependencies: [],
|
|
802
|
-
errors: metadata.errors,
|
|
803
|
-
warnings: metadata.warnings,
|
|
804
|
-
hmrUpdateCode: metadata.hmrUpdateCode,
|
|
805
|
-
hmrEligible: metadata.hmrEligible,
|
|
806
|
-
});
|
|
807
|
-
};
|
|
808
|
-
const writeOutputFile = (id) => {
|
|
809
|
-
const sourceFile = builder.getSourceFile(id);
|
|
810
|
-
if (!sourceFile) {
|
|
811
|
-
return;
|
|
812
|
-
}
|
|
813
|
-
let content = '';
|
|
814
|
-
builder.emit(sourceFile, (filename, data) => {
|
|
815
|
-
if (/\.[cm]?js$/.test(filename)) {
|
|
816
|
-
content = data;
|
|
817
|
-
}
|
|
818
|
-
if (!watchMode &&
|
|
819
|
-
!isTest &&
|
|
820
|
-
/\.d\.ts/.test(filename) &&
|
|
821
|
-
!filename.includes('.ngtypecheck.')) {
|
|
822
|
-
// output to library root instead /src
|
|
823
|
-
const declarationPath = resolve(config.root, config.build.outDir, relative(config.root, filename)).replace('/src/', '/');
|
|
824
|
-
const declarationFileDir = declarationPath
|
|
825
|
-
.replace(basename(filename), '')
|
|
826
|
-
.replace('/src/', '/');
|
|
827
|
-
declarationFiles.push({
|
|
828
|
-
declarationFileDir,
|
|
829
|
-
declarationPath,
|
|
830
|
-
data,
|
|
831
|
-
});
|
|
832
|
-
}
|
|
833
|
-
}, undefined /* cancellationToken */, undefined /* emitOnlyDtsFiles */, transformers);
|
|
834
|
-
writeFileCallback(id, content, false, undefined, [sourceFile]);
|
|
835
|
-
if (angularCompiler) {
|
|
836
|
-
angularCompiler.incrementalCompilation.recordSuccessfulEmit(sourceFile);
|
|
837
|
-
}
|
|
838
|
-
};
|
|
839
|
-
if (watchMode) {
|
|
840
|
-
if (ids && ids.length > 0) {
|
|
841
|
-
ids.forEach((id) => writeOutputFile(id));
|
|
842
|
-
}
|
|
843
|
-
else {
|
|
844
|
-
/**
|
|
845
|
-
* Only block the server from starting up
|
|
846
|
-
* during testing.
|
|
847
|
-
*/
|
|
848
|
-
if (isTest) {
|
|
849
|
-
// TypeScript will loop until there are no more affected files in the program
|
|
850
|
-
while (builder.emitNextAffectedFile(writeFileCallback, undefined, undefined, transformers)) {
|
|
851
|
-
/* empty */
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
if (!isTest) {
|
|
857
|
-
/**
|
|
858
|
-
* Perf: Output files on demand so the dev server
|
|
859
|
-
* isn't blocked when emitting files.
|
|
860
|
-
*/
|
|
861
|
-
outputFile = writeOutputFile;
|
|
862
|
-
}
|
|
863
|
-
}
|
|
1451
|
+
* Convert Analog/Angular CLI-style file replacements into the flat record
|
|
1452
|
+
* expected by `AngularHostOptions.fileReplacements`.
|
|
1453
|
+
*
|
|
1454
|
+
* Only browser replacements (`{ replace, with }`) are converted. SSR-only
|
|
1455
|
+
* replacements (`{ replace, ssr }`) are left for the Vite runtime plugin to
|
|
1456
|
+
* handle — they should not be baked into the Angular compilation host because
|
|
1457
|
+
* that would apply them to both browser and server builds.
|
|
1458
|
+
*
|
|
1459
|
+
* Relative paths are resolved against `workspaceRoot` so that the host
|
|
1460
|
+
* receives the same absolute paths it would get from the Angular CLI.
|
|
1461
|
+
*/
|
|
1462
|
+
function toAngularCompilationFileReplacements(replacements, workspaceRoot) {
|
|
1463
|
+
const mappedReplacements = replacements.flatMap((replacement) => {
|
|
1464
|
+
if (!("with" in replacement)) return [];
|
|
1465
|
+
return [[isAbsolute(replacement.replace) ? replacement.replace : resolve(workspaceRoot, replacement.replace), isAbsolute(replacement.with) ? replacement.with : resolve(workspaceRoot, replacement.with)]];
|
|
1466
|
+
});
|
|
1467
|
+
return mappedReplacements.length ? Object.fromEntries(mappedReplacements) : void 0;
|
|
1468
|
+
}
|
|
1469
|
+
/**
|
|
1470
|
+
* Map Angular's `templateUpdates` (keyed by `encodedFilePath@ClassName`)
|
|
1471
|
+
* back to absolute file paths with their associated HMR code and component
|
|
1472
|
+
* class name.
|
|
1473
|
+
*
|
|
1474
|
+
* Angular's private Compilation API emits template update keys in the form
|
|
1475
|
+
* `encodeURIComponent(relativePath + '@' + className)`. We decode and resolve
|
|
1476
|
+
* them so the caller can look up updates by the same normalized absolute path
|
|
1477
|
+
* used elsewhere in the plugin (`outputFiles`, `classNames`, etc.).
|
|
1478
|
+
*/
|
|
1479
|
+
function mapTemplateUpdatesToFiles(templateUpdates) {
|
|
1480
|
+
const updatesByFile = /* @__PURE__ */ new Map();
|
|
1481
|
+
templateUpdates?.forEach((code, encodedUpdateId) => {
|
|
1482
|
+
const [file, className = ""] = decodeURIComponent(encodedUpdateId).split("@");
|
|
1483
|
+
const resolvedFile = normalizePath(resolve(process.cwd(), file));
|
|
1484
|
+
updatesByFile.set(resolvedFile, {
|
|
1485
|
+
className,
|
|
1486
|
+
code
|
|
1487
|
+
});
|
|
1488
|
+
});
|
|
1489
|
+
return updatesByFile;
|
|
1490
|
+
}
|
|
1491
|
+
/**
|
|
1492
|
+
* Returns every live Vite module that can legitimately represent a changed
|
|
1493
|
+
* Angular resource file.
|
|
1494
|
+
*
|
|
1495
|
+
* For normal files, `getModulesByFile()` is enough. For Angular component
|
|
1496
|
+
* stylesheets, it is not: the browser often holds virtual hashed requests
|
|
1497
|
+
* (`/abc123.css?direct&ngcomp=...` and `/abc123.css?ngcomp=...`) that are no
|
|
1498
|
+
* longer discoverable from the original source path alone. We therefore merge:
|
|
1499
|
+
* - watcher event modules
|
|
1500
|
+
* - module-graph modules by source file
|
|
1501
|
+
* - registry-tracked live request ids resolved back through the module graph
|
|
1502
|
+
*/
|
|
1503
|
+
async function getModulesForChangedFile(server, file, eventModules = [], stylesheetRegistry) {
|
|
1504
|
+
const normalizedFile = normalizePath(file.split("?")[0]);
|
|
1505
|
+
const modules = /* @__PURE__ */ new Map();
|
|
1506
|
+
for (const mod of eventModules) if (mod.id) modules.set(mod.id, mod);
|
|
1507
|
+
server.moduleGraph.getModulesByFile(normalizedFile)?.forEach((mod) => {
|
|
1508
|
+
if (mod.id) modules.set(mod.id, mod);
|
|
1509
|
+
});
|
|
1510
|
+
const stylesheetRequestIds = stylesheetRegistry?.getRequestIdsForSource(normalizedFile) ?? [];
|
|
1511
|
+
const requestIdHits = [];
|
|
1512
|
+
for (const requestId of stylesheetRequestIds) {
|
|
1513
|
+
const candidates = [requestId, requestId.startsWith("/") ? requestId : `/${requestId}`];
|
|
1514
|
+
for (const candidate of candidates) {
|
|
1515
|
+
const mod = await server.moduleGraph.getModuleByUrl(candidate) ?? server.moduleGraph.getModuleById(candidate);
|
|
1516
|
+
requestIdHits.push({
|
|
1517
|
+
requestId,
|
|
1518
|
+
candidate,
|
|
1519
|
+
via: mod?.url === candidate ? "url" : "id",
|
|
1520
|
+
moduleId: mod?.id
|
|
1521
|
+
});
|
|
1522
|
+
if (mod?.id) modules.set(mod.id, mod);
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
debugHmrV("getModulesForChangedFile registry lookup", {
|
|
1526
|
+
file: normalizedFile,
|
|
1527
|
+
stylesheetRequestIds,
|
|
1528
|
+
requestIdHits,
|
|
1529
|
+
resolvedModuleIds: [...modules.keys()]
|
|
1530
|
+
});
|
|
1531
|
+
return [...modules.values()];
|
|
1532
|
+
}
|
|
1533
|
+
function isModuleForChangedResource(mod, changedFile, stylesheetRegistry) {
|
|
1534
|
+
const normalizedChangedFile = normalizePath(changedFile.split("?")[0]);
|
|
1535
|
+
if (normalizePath((mod.file ?? "").split("?")[0]) === normalizedChangedFile) return true;
|
|
1536
|
+
if (!mod.id) return false;
|
|
1537
|
+
const requestPath = getFilenameFromPath(mod.id);
|
|
1538
|
+
return normalizePath((stylesheetRegistry?.resolveExternalSource(requestPath) ?? stylesheetRegistry?.resolveExternalSource(requestPath.replace(/^\//, "")) ?? "").split("?")[0]) === normalizedChangedFile;
|
|
1539
|
+
}
|
|
1540
|
+
function describeStylesheetContent(code) {
|
|
1541
|
+
return {
|
|
1542
|
+
length: code.length,
|
|
1543
|
+
digest: createHash("sha256").update(code).digest("hex").slice(0, 12),
|
|
1544
|
+
preview: code.replace(/\s+/g, " ").trim().slice(0, 160)
|
|
1545
|
+
};
|
|
1546
|
+
}
|
|
1547
|
+
function safeStatMtimeMs(file) {
|
|
1548
|
+
try {
|
|
1549
|
+
return statSync(file).mtimeMs;
|
|
1550
|
+
} catch {
|
|
1551
|
+
return;
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
/**
|
|
1555
|
+
* Refreshes any already-served stylesheet records that map back to a changed
|
|
1556
|
+
* source file.
|
|
1557
|
+
*
|
|
1558
|
+
* This is the critical bridge for externalized Angular component styles during
|
|
1559
|
+
* HMR. Angular's resource watcher can notice that `/src/...component.css`
|
|
1560
|
+
* changed before Angular recompilation has had a chance to repopulate the
|
|
1561
|
+
* stylesheet registry. If we emit a CSS update against the existing virtual
|
|
1562
|
+
* stylesheet id without first refreshing the registry content, the browser gets
|
|
1563
|
+
* a hot update containing stale CSS. By rewriting the existing served records
|
|
1564
|
+
* from disk up front, HMR always pushes the latest source content.
|
|
1565
|
+
*/
|
|
1566
|
+
function refreshStylesheetRegistryForFile(file, stylesheetRegistry, stylePreprocessor) {
|
|
1567
|
+
const normalizedFile = normalizePath(file.split("?")[0]);
|
|
1568
|
+
if (!stylesheetRegistry || !existsSync(normalizedFile)) return;
|
|
1569
|
+
const publicIds = stylesheetRegistry.getPublicIdsForSource(normalizedFile);
|
|
1570
|
+
if (publicIds.length === 0) return;
|
|
1571
|
+
const rawCss = readFileSync(normalizedFile, "utf-8");
|
|
1572
|
+
const preprocessed = preprocessStylesheetResult(rawCss, normalizedFile, stylePreprocessor);
|
|
1573
|
+
const servedCss = rewriteRelativeCssImports(preprocessed.code, normalizedFile);
|
|
1574
|
+
for (const publicId of publicIds) stylesheetRegistry.registerServedStylesheet({
|
|
1575
|
+
publicId,
|
|
1576
|
+
sourcePath: normalizedFile,
|
|
1577
|
+
originalCode: rawCss,
|
|
1578
|
+
normalizedCode: servedCss,
|
|
1579
|
+
dependencies: normalizeStylesheetDependencies(preprocessed.dependencies),
|
|
1580
|
+
diagnostics: preprocessed.diagnostics,
|
|
1581
|
+
tags: preprocessed.tags
|
|
1582
|
+
}, [
|
|
1583
|
+
normalizedFile,
|
|
1584
|
+
normalizePath(normalizedFile),
|
|
1585
|
+
basename(normalizedFile),
|
|
1586
|
+
normalizedFile.replace(/^\//, "")
|
|
1587
|
+
]);
|
|
1588
|
+
debugStylesV("stylesheet registry refreshed from source file", {
|
|
1589
|
+
file: normalizedFile,
|
|
1590
|
+
publicIds,
|
|
1591
|
+
dependencies: preprocessed.dependencies,
|
|
1592
|
+
diagnostics: preprocessed.diagnostics,
|
|
1593
|
+
tags: preprocessed.tags,
|
|
1594
|
+
source: describeStylesheetContent(rawCss),
|
|
1595
|
+
served: describeStylesheetContent(servedCss)
|
|
1596
|
+
});
|
|
1597
|
+
}
|
|
1598
|
+
function diagnoseComponentStylesheetPipeline(changedFile, directModule, stylesheetRegistry, wrapperModules, stylePreprocessor) {
|
|
1599
|
+
const normalizedFile = normalizePath(changedFile.split("?")[0]);
|
|
1600
|
+
const sourceExists = existsSync(normalizedFile);
|
|
1601
|
+
const sourceCode = sourceExists ? readFileSync(normalizedFile, "utf-8") : void 0;
|
|
1602
|
+
const directRequestPath = directModule.id ? getFilenameFromPath(directModule.id) : void 0;
|
|
1603
|
+
const sourcePath = directRequestPath ? stylesheetRegistry?.resolveExternalSource(directRequestPath) ?? stylesheetRegistry?.resolveExternalSource(directRequestPath.replace(/^\//, "")) : normalizedFile;
|
|
1604
|
+
const registryCode = directRequestPath ? stylesheetRegistry?.getServedContent(directRequestPath) : void 0;
|
|
1605
|
+
const trackedRequestIds = stylesheetRegistry?.getRequestIdsForSource(sourcePath ?? "") ?? [];
|
|
1606
|
+
const dependencies = stylesheetRegistry?.getDependenciesForSource(sourcePath ?? "") ?? [];
|
|
1607
|
+
const diagnostics = stylesheetRegistry?.getDiagnosticsForSource(sourcePath ?? "") ?? [];
|
|
1608
|
+
const tags = stylesheetRegistry?.getTagsForSource(sourcePath ?? "") ?? [];
|
|
1609
|
+
const anomalies = [];
|
|
1610
|
+
const hints = [];
|
|
1611
|
+
if (!sourceExists) {
|
|
1612
|
+
anomalies.push("source_file_missing");
|
|
1613
|
+
hints.push("The stylesheet watcher fired for a file that no longer exists on disk.");
|
|
1614
|
+
}
|
|
1615
|
+
if (!registryCode) {
|
|
1616
|
+
anomalies.push("registry_content_missing");
|
|
1617
|
+
hints.push("The stylesheet registry has no served content for the direct module request path.");
|
|
1618
|
+
}
|
|
1619
|
+
if (sourceCode && registryCode) {
|
|
1620
|
+
let expectedRegistryCode = preprocessStylesheet(sourceCode, normalizedFile, stylePreprocessor);
|
|
1621
|
+
expectedRegistryCode = rewriteRelativeCssImports(expectedRegistryCode, normalizedFile);
|
|
1622
|
+
if (describeStylesheetContent(expectedRegistryCode).digest !== describeStylesheetContent(registryCode).digest) {
|
|
1623
|
+
anomalies.push("source_registry_mismatch");
|
|
1624
|
+
hints.push("The source file changed, but the served stylesheet content in the registry is still stale.");
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
if (trackedRequestIds.length === 0) {
|
|
1628
|
+
anomalies.push("no_tracked_requests");
|
|
1629
|
+
hints.push("No live stylesheet requests are tracked for this source file, so HMR has no browser-facing target.");
|
|
1630
|
+
}
|
|
1631
|
+
if (trackedRequestIds.some((id) => id.includes("?ngcomp=")) && wrapperModules.length === 0) {
|
|
1632
|
+
anomalies.push("tracked_wrapper_missing_from_module_graph");
|
|
1633
|
+
hints.push("A wrapper request id is known, but Vite did not expose a live wrapper module during this HMR pass.");
|
|
1634
|
+
}
|
|
1635
|
+
if (trackedRequestIds.every((id) => !id.includes("?ngcomp=")) && wrapperModules.length === 0) {
|
|
1636
|
+
anomalies.push("wrapper_not_yet_tracked");
|
|
1637
|
+
hints.push("Only direct stylesheet requests were tracked during this HMR pass; the wrapper request may be appearing too late.");
|
|
1638
|
+
}
|
|
1639
|
+
return {
|
|
1640
|
+
file: changedFile,
|
|
1641
|
+
sourcePath,
|
|
1642
|
+
source: sourceCode ? describeStylesheetContent(rewriteRelativeCssImports(preprocessStylesheet(sourceCode, normalizedFile, stylePreprocessor), normalizedFile)) : void 0,
|
|
1643
|
+
registry: registryCode ? describeStylesheetContent(registryCode) : void 0,
|
|
1644
|
+
dependencies,
|
|
1645
|
+
diagnostics,
|
|
1646
|
+
tags,
|
|
1647
|
+
directModuleId: directModule.id,
|
|
1648
|
+
directModuleUrl: directModule.url,
|
|
1649
|
+
trackedRequestIds,
|
|
1650
|
+
wrapperCount: wrapperModules.length,
|
|
1651
|
+
anomalies,
|
|
1652
|
+
hints
|
|
1653
|
+
};
|
|
1654
|
+
}
|
|
1655
|
+
async function findComponentStylesheetWrapperModules(server, changedFile, directModule, fileModules, stylesheetRegistry) {
|
|
1656
|
+
const wrapperModules = /* @__PURE__ */ new Map();
|
|
1657
|
+
for (const mod of fileModules) if (mod.id && mod.type === "js" && isComponentStyleSheet(mod.id) && isModuleForChangedResource(mod, changedFile, stylesheetRegistry)) wrapperModules.set(mod.id, mod);
|
|
1658
|
+
const directRequestIds = /* @__PURE__ */ new Set();
|
|
1659
|
+
if (directModule.id) directRequestIds.add(directModule.id);
|
|
1660
|
+
if (directModule.url) directRequestIds.add(directModule.url);
|
|
1661
|
+
const requestPath = directModule.id ? getFilenameFromPath(directModule.id) : void 0;
|
|
1662
|
+
const sourcePath = requestPath ? stylesheetRegistry?.resolveExternalSource(requestPath) ?? stylesheetRegistry?.resolveExternalSource(requestPath.replace(/^\//, "")) : void 0;
|
|
1663
|
+
for (const requestId of stylesheetRegistry?.getRequestIdsForSource(sourcePath ?? "") ?? []) if (requestId.includes("?ngcomp=")) directRequestIds.add(requestId);
|
|
1664
|
+
const candidateWrapperIds = [...directRequestIds].filter((id) => id.includes("?direct&ngcomp=")).map((id) => id.replace("?direct&ngcomp=", "?ngcomp="));
|
|
1665
|
+
const lookupHits = [];
|
|
1666
|
+
for (const candidate of candidateWrapperIds) {
|
|
1667
|
+
const mod = await server.moduleGraph.getModuleByUrl(candidate) ?? server.moduleGraph.getModuleById(candidate);
|
|
1668
|
+
lookupHits.push({
|
|
1669
|
+
candidate,
|
|
1670
|
+
via: mod?.url === candidate ? "url" : mod ? "id" : void 0,
|
|
1671
|
+
moduleId: mod?.id,
|
|
1672
|
+
moduleType: mod?.type
|
|
1673
|
+
});
|
|
1674
|
+
if (mod?.id && mod.type === "js" && isComponentStyleSheet(mod.id) && isModuleForChangedResource(mod, changedFile, stylesheetRegistry)) wrapperModules.set(mod.id, mod);
|
|
1675
|
+
}
|
|
1676
|
+
debugHmrV("component stylesheet wrapper lookup", {
|
|
1677
|
+
file: changedFile,
|
|
1678
|
+
sourcePath,
|
|
1679
|
+
directModuleId: directModule.id,
|
|
1680
|
+
directModuleUrl: directModule.url,
|
|
1681
|
+
candidateWrapperIds,
|
|
1682
|
+
lookupHits
|
|
1683
|
+
});
|
|
1684
|
+
if (wrapperModules.size === 0) debugHmrV("component stylesheet wrapper lookup empty", {
|
|
1685
|
+
file: changedFile,
|
|
1686
|
+
sourcePath,
|
|
1687
|
+
directModuleId: directModule.id,
|
|
1688
|
+
directModuleUrl: directModule.url,
|
|
1689
|
+
candidateWrapperIds
|
|
1690
|
+
});
|
|
1691
|
+
return [...wrapperModules.values()];
|
|
864
1692
|
}
|
|
865
1693
|
function sendHMRComponentUpdate(server, id) {
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
1694
|
+
debugHmrV("ws send: angular component update", {
|
|
1695
|
+
id,
|
|
1696
|
+
timestamp: Date.now()
|
|
1697
|
+
});
|
|
1698
|
+
server.ws.send("angular:component-update", {
|
|
1699
|
+
id: encodeURIComponent(id),
|
|
1700
|
+
timestamp: Date.now()
|
|
1701
|
+
});
|
|
1702
|
+
classNames.delete(id);
|
|
1703
|
+
}
|
|
1704
|
+
function sendCssUpdate(server, update) {
|
|
1705
|
+
const timestamp = Date.now();
|
|
1706
|
+
debugHmrV("ws send: css-update", {
|
|
1707
|
+
...update,
|
|
1708
|
+
timestamp
|
|
1709
|
+
});
|
|
1710
|
+
server.ws.send({
|
|
1711
|
+
type: "update",
|
|
1712
|
+
updates: [{
|
|
1713
|
+
type: "css-update",
|
|
1714
|
+
timestamp,
|
|
1715
|
+
path: update.path,
|
|
1716
|
+
acceptedPath: update.acceptedPath
|
|
1717
|
+
}]
|
|
1718
|
+
});
|
|
1719
|
+
}
|
|
1720
|
+
function sendFullReload(server, details) {
|
|
1721
|
+
debugHmrV("ws send: full-reload", details);
|
|
1722
|
+
server.ws.send("analog:debug-full-reload", details);
|
|
1723
|
+
server.ws.send({ type: "full-reload" });
|
|
1724
|
+
}
|
|
1725
|
+
function resolveComponentClassNamesForStyleOwner(ownerFile, sourcePath) {
|
|
1726
|
+
if (!existsSync(ownerFile)) return [];
|
|
1727
|
+
const components = getAngularComponentMetadata(readFileSync(ownerFile, "utf-8"));
|
|
1728
|
+
const normalizedSourcePath = normalizePath(sourcePath);
|
|
1729
|
+
return components.filter((component) => component.styleUrls.some((styleUrl) => normalizePath(resolve(dirname(ownerFile), styleUrl)) === normalizedSourcePath)).map((component) => component.className);
|
|
1730
|
+
}
|
|
1731
|
+
function findStaticClassAndBoundClassConflicts(template) {
|
|
1732
|
+
const issues = [];
|
|
1733
|
+
for (const { index, snippet } of findOpeningTagSnippets(template)) {
|
|
1734
|
+
if (!snippet.includes("[class]")) continue;
|
|
1735
|
+
const hasStaticClass = /\sclass\s*=\s*(['"])(?:(?!\1)[\s\S])*\1/.test(snippet);
|
|
1736
|
+
const hasBoundClass = /\s\[class\]\s*=\s*(['"])(?:(?!\1)[\s\S])*\1/.test(snippet);
|
|
1737
|
+
if (hasStaticClass && hasBoundClass) {
|
|
1738
|
+
const prefix = template.slice(0, index);
|
|
1739
|
+
const line = prefix.split("\n").length;
|
|
1740
|
+
const column = index - prefix.lastIndexOf("\n");
|
|
1741
|
+
issues.push({
|
|
1742
|
+
line,
|
|
1743
|
+
column,
|
|
1744
|
+
snippet: snippet.replace(/\s+/g, " ").trim()
|
|
1745
|
+
});
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
return issues;
|
|
1749
|
+
}
|
|
1750
|
+
function throwTemplateClassBindingConflict(id, issue) {
|
|
1751
|
+
throw new Error([
|
|
1752
|
+
"[Analog Angular] Invalid template class binding.",
|
|
1753
|
+
`File: ${id}:${issue.line}:${issue.column}`,
|
|
1754
|
+
"The same element uses both a static `class=\"...\"` attribute and a whole-element `[class]=\"...\"` binding.",
|
|
1755
|
+
"That pattern can replace or conflict with static Tailwind classes, which makes styles appear to stop applying.",
|
|
1756
|
+
"Use `[ngClass]` or explicit `[class.foo]` bindings instead of `[class]` when the element also has static classes.",
|
|
1757
|
+
`Snippet: ${issue.snippet}`
|
|
1758
|
+
].join("\n"));
|
|
1759
|
+
}
|
|
1760
|
+
function findBoundClassAndNgClassConflicts(template) {
|
|
1761
|
+
const issues = [];
|
|
1762
|
+
if (!/\[class\]\s*=/.test(template) || !template.includes("[ngClass]")) return issues;
|
|
1763
|
+
for (const { index, snippet } of findOpeningTagSnippets(template)) {
|
|
1764
|
+
if (!/\[class\]\s*=/.test(snippet) || !snippet.includes("[ngClass]")) continue;
|
|
1765
|
+
const prefix = template.slice(0, index);
|
|
1766
|
+
const line = prefix.split("\n").length;
|
|
1767
|
+
const column = index - prefix.lastIndexOf("\n");
|
|
1768
|
+
issues.push({
|
|
1769
|
+
line,
|
|
1770
|
+
column,
|
|
1771
|
+
snippet: snippet.replace(/\s+/g, " ").trim()
|
|
1772
|
+
});
|
|
1773
|
+
}
|
|
1774
|
+
return issues;
|
|
1775
|
+
}
|
|
1776
|
+
function findOpeningTagSnippets(template) {
|
|
1777
|
+
const matches = [];
|
|
1778
|
+
for (let index = 0; index < template.length; index++) {
|
|
1779
|
+
if (template[index] !== "<") continue;
|
|
1780
|
+
const tagStart = template[index + 1];
|
|
1781
|
+
if (!tagStart || !/[a-zA-Z]/.test(tagStart)) continue;
|
|
1782
|
+
let quote = null;
|
|
1783
|
+
for (let end = index + 1; end < template.length; end++) {
|
|
1784
|
+
const char = template[end];
|
|
1785
|
+
if (quote) {
|
|
1786
|
+
if (char === quote) quote = null;
|
|
1787
|
+
continue;
|
|
1788
|
+
}
|
|
1789
|
+
if (char === "\"" || char === "'") {
|
|
1790
|
+
quote = char;
|
|
1791
|
+
continue;
|
|
1792
|
+
}
|
|
1793
|
+
if (char === ">") {
|
|
1794
|
+
matches.push({
|
|
1795
|
+
index,
|
|
1796
|
+
snippet: template.slice(index, end + 1)
|
|
1797
|
+
});
|
|
1798
|
+
index = end;
|
|
1799
|
+
break;
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
return matches;
|
|
1804
|
+
}
|
|
1805
|
+
function formatActiveGraphLocations(entries) {
|
|
1806
|
+
return [...entries].sort().map((entry) => `- ${entry}`).join("\n");
|
|
1807
|
+
}
|
|
1808
|
+
function logComponentStylesheetHmrOutcome(details) {
|
|
1809
|
+
const pitfalls = [];
|
|
1810
|
+
const rejectedPreferredPaths = [];
|
|
1811
|
+
const hints = [];
|
|
1812
|
+
if (details.encapsulation === "shadow") {
|
|
1813
|
+
pitfalls.push("shadow-encapsulation");
|
|
1814
|
+
rejectedPreferredPaths.push("css-update");
|
|
1815
|
+
rejectedPreferredPaths.push("owner-component-update");
|
|
1816
|
+
hints.push("Shadow DOM styles cannot rely on Vite CSS patching because Angular applies them inside a shadow root.");
|
|
1817
|
+
}
|
|
1818
|
+
if (details.diagnosis.anomalies.includes("wrapper_not_yet_tracked")) {
|
|
1819
|
+
pitfalls.push("wrapper-not-yet-tracked");
|
|
1820
|
+
rejectedPreferredPaths.push("css-update");
|
|
1821
|
+
hints.push("The direct stylesheet module exists, but the browser-visible Angular wrapper module was not available in the live graph during this HMR pass.");
|
|
1822
|
+
}
|
|
1823
|
+
if (details.diagnosis.anomalies.includes("tracked_wrapper_missing_from_module_graph")) {
|
|
1824
|
+
pitfalls.push("tracked-wrapper-missing-from-module-graph");
|
|
1825
|
+
rejectedPreferredPaths.push("css-update");
|
|
1826
|
+
hints.push("A wrapper request id is known, but Vite could not resolve a live wrapper module for targeted CSS HMR.");
|
|
1827
|
+
}
|
|
1828
|
+
if ((details.ownerIds?.filter(Boolean).length ?? 0) === 0) {
|
|
1829
|
+
pitfalls.push("no-owner-modules");
|
|
1830
|
+
if (details.outcome === "full-reload") {
|
|
1831
|
+
rejectedPreferredPaths.push("owner-component-update");
|
|
1832
|
+
hints.push("No owning TS component modules were available in the module graph for owner-based fallback.");
|
|
1833
|
+
}
|
|
1834
|
+
} else if ((details.updateIds?.length ?? 0) === 0) {
|
|
1835
|
+
pitfalls.push("owner-modules-without-class-identities");
|
|
1836
|
+
if (details.outcome === "full-reload") {
|
|
1837
|
+
rejectedPreferredPaths.push("owner-component-update");
|
|
1838
|
+
hints.push("Owner modules were found, but Angular did not expose component class identities after recompilation, so no targeted component update could be sent.");
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
debugHmrV("component stylesheet hmr outcome", {
|
|
1842
|
+
file: details.file,
|
|
1843
|
+
outcome: details.outcome,
|
|
1844
|
+
encapsulation: details.encapsulation,
|
|
1845
|
+
directModuleId: details.directModuleId,
|
|
1846
|
+
wrapperIds: details.wrapperIds ?? [],
|
|
1847
|
+
ownerIds: details.ownerIds ?? [],
|
|
1848
|
+
updateIds: details.updateIds ?? [],
|
|
1849
|
+
preferredPath: details.encapsulation === "shadow" ? "full-reload" : "css-update",
|
|
1850
|
+
rejectedPreferredPaths: [...new Set(rejectedPreferredPaths)],
|
|
1851
|
+
pitfalls: [...new Set(pitfalls)],
|
|
1852
|
+
anomalies: details.diagnosis.anomalies,
|
|
1853
|
+
hints: [...new Set([...details.diagnosis.hints, ...hints])]
|
|
1854
|
+
});
|
|
1855
|
+
}
|
|
1856
|
+
function findTemplateOwnerModules(server, resourceFile) {
|
|
1857
|
+
const candidateTsFiles = [normalizePath(resourceFile.split("?")[0]).replace(/\.(html|htm)$/i, ".ts")];
|
|
1858
|
+
const modules = /* @__PURE__ */ new Map();
|
|
1859
|
+
for (const candidate of candidateTsFiles) server.moduleGraph.getModulesByFile(candidate)?.forEach((mod) => {
|
|
1860
|
+
if (mod.id) modules.set(mod.id, mod);
|
|
1861
|
+
});
|
|
1862
|
+
return [...modules.values()];
|
|
1863
|
+
}
|
|
1864
|
+
function findStyleOwnerModules(server, resourceFile, styleSourceOwners) {
|
|
1865
|
+
const normalizedResourceFile = normalizePath(resourceFile.split("?")[0]);
|
|
1866
|
+
const candidateOwnerFiles = [...styleSourceOwners.get(normalizedResourceFile) ?? []];
|
|
1867
|
+
const modules = /* @__PURE__ */ new Map();
|
|
1868
|
+
for (const ownerFile of candidateOwnerFiles) server.moduleGraph.getModulesByFile(ownerFile)?.forEach((mod) => {
|
|
1869
|
+
if (mod.id) modules.set(mod.id, mod);
|
|
1870
|
+
});
|
|
1871
|
+
return [...modules.values()];
|
|
1872
|
+
}
|
|
1873
|
+
function getFileMetadata(program, angularCompiler, hmrEnabled, disableTypeChecking) {
|
|
1874
|
+
const ts = require("typescript");
|
|
1875
|
+
return (file) => {
|
|
1876
|
+
const sourceFile = program.getSourceFile(file);
|
|
1877
|
+
if (!sourceFile) return {};
|
|
1878
|
+
const diagnostics = getDiagnosticsForSourceFile(sourceFile, !!disableTypeChecking, program, angularCompiler);
|
|
1879
|
+
const errors = diagnostics.filter((d) => d.category === ts.DiagnosticCategory?.Error).map((d) => typeof d.messageText === "object" ? d.messageText.messageText : d.messageText);
|
|
1880
|
+
const warnings = diagnostics.filter((d) => d.category === ts.DiagnosticCategory?.Warning).map((d) => d.messageText);
|
|
1881
|
+
let hmrUpdateCode = void 0;
|
|
1882
|
+
let hmrEligible = false;
|
|
1883
|
+
if (hmrEnabled) {
|
|
1884
|
+
for (const node of sourceFile.statements) if (ts.isClassDeclaration(node) && node.name != null) {
|
|
1885
|
+
hmrUpdateCode = angularCompiler?.emitHmrUpdateModule(node);
|
|
1886
|
+
if (hmrUpdateCode) {
|
|
1887
|
+
const className = node.name.getText();
|
|
1888
|
+
classNames.set(file, className);
|
|
1889
|
+
hmrEligible = true;
|
|
1890
|
+
debugHmr("NgtscProgram emitHmrUpdateModule", {
|
|
1891
|
+
file,
|
|
1892
|
+
className
|
|
1893
|
+
});
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
return {
|
|
1898
|
+
errors,
|
|
1899
|
+
warnings,
|
|
1900
|
+
hmrUpdateCode,
|
|
1901
|
+
hmrEligible
|
|
1902
|
+
};
|
|
1903
|
+
};
|
|
903
1904
|
}
|
|
904
1905
|
function getDiagnosticsForSourceFile(sourceFile, disableTypeChecking, program, angularCompiler) {
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
: [];
|
|
915
|
-
return [
|
|
916
|
-
...syntacticDiagnostics,
|
|
917
|
-
...semanticDiagnostics,
|
|
918
|
-
...angularDiagnostics,
|
|
919
|
-
];
|
|
1906
|
+
const syntacticDiagnostics = program.getSyntacticDiagnostics(sourceFile);
|
|
1907
|
+
if (disableTypeChecking) return syntacticDiagnostics;
|
|
1908
|
+
const semanticDiagnostics = program.getSemanticDiagnostics(sourceFile);
|
|
1909
|
+
const angularDiagnostics = angularCompiler ? angularCompiler.getDiagnosticsForFile(sourceFile, 1) : [];
|
|
1910
|
+
return [
|
|
1911
|
+
...syntacticDiagnostics,
|
|
1912
|
+
...semanticDiagnostics,
|
|
1913
|
+
...angularDiagnostics
|
|
1914
|
+
];
|
|
920
1915
|
}
|
|
921
1916
|
function markModuleSelfAccepting(mod) {
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
...mod,
|
|
928
|
-
isSelfAccepting: true,
|
|
929
|
-
};
|
|
1917
|
+
if ("_clientModule" in mod) mod["_clientModule"].isSelfAccepting = true;
|
|
1918
|
+
return {
|
|
1919
|
+
...mod,
|
|
1920
|
+
isSelfAccepting: true
|
|
1921
|
+
};
|
|
930
1922
|
}
|
|
931
1923
|
function isComponentStyleSheet(id) {
|
|
932
|
-
|
|
1924
|
+
return id.includes("ngcomp=");
|
|
933
1925
|
}
|
|
934
1926
|
function getComponentStyleSheetMeta(id) {
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
};
|
|
1927
|
+
const params = new URL(id, "http://localhost").searchParams;
|
|
1928
|
+
return {
|
|
1929
|
+
componentId: params.get("ngcomp"),
|
|
1930
|
+
encapsulation: {
|
|
1931
|
+
"0": "emulated",
|
|
1932
|
+
"2": "none",
|
|
1933
|
+
"3": "shadow"
|
|
1934
|
+
}[params.get("e")]
|
|
1935
|
+
};
|
|
945
1936
|
}
|
|
946
1937
|
/**
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
1938
|
+
* Removes leading / and query string from a url path
|
|
1939
|
+
* e.g. /foo.scss?direct&ngcomp=ng-c3153525609&e=0 returns foo.scss
|
|
1940
|
+
* @param id
|
|
1941
|
+
*/
|
|
951
1942
|
function getFilenameFromPath(id) {
|
|
952
|
-
|
|
1943
|
+
try {
|
|
1944
|
+
return new URL(id, "http://localhost").pathname.replace(/^\//, "");
|
|
1945
|
+
} catch {
|
|
1946
|
+
const queryIndex = id.indexOf("?");
|
|
1947
|
+
return (queryIndex >= 0 ? id.slice(0, queryIndex) : id).replace(/^\//, "");
|
|
1948
|
+
}
|
|
953
1949
|
}
|
|
954
1950
|
/**
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
if (hasNoRun) {
|
|
967
|
-
return true;
|
|
968
|
-
}
|
|
969
|
-
// check for --watch=false or --no-watch
|
|
970
|
-
const hasWatch = args.find((arg) => arg.includes('watch'));
|
|
971
|
-
if (hasWatch && ['false', 'no'].some((neg) => hasWatch.includes(neg))) {
|
|
972
|
-
return false;
|
|
973
|
-
}
|
|
974
|
-
// check for --watch false
|
|
975
|
-
const watchIndex = args.findIndex((arg) => arg.includes('watch'));
|
|
976
|
-
const watchArg = args[watchIndex + 1];
|
|
977
|
-
if (watchArg && watchArg === 'false') {
|
|
978
|
-
return false;
|
|
979
|
-
}
|
|
980
|
-
return true;
|
|
1951
|
+
* Checks for vitest run from the command line
|
|
1952
|
+
* @returns boolean
|
|
1953
|
+
*/
|
|
1954
|
+
function isTestWatchMode(args = process.argv) {
|
|
1955
|
+
if (args.find((arg) => arg.includes("--run"))) return false;
|
|
1956
|
+
if (args.find((arg) => arg.includes("--no-run"))) return true;
|
|
1957
|
+
const hasWatch = args.find((arg) => arg.includes("watch"));
|
|
1958
|
+
if (hasWatch && ["false", "no"].some((neg) => hasWatch.includes(neg))) return false;
|
|
1959
|
+
const watchArg = args[args.findIndex((arg) => arg.includes("watch")) + 1];
|
|
1960
|
+
if (watchArg && watchArg === "false") return false;
|
|
1961
|
+
return true;
|
|
981
1962
|
}
|
|
1963
|
+
//#endregion
|
|
1964
|
+
export { angular };
|
|
1965
|
+
|
|
982
1966
|
//# sourceMappingURL=angular-vite-plugin.js.map
|