@blotch/rsbuild-plugin-angular-lynx 22.2.1 → 22.2.2

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/dist/index.js CHANGED
@@ -1,23 +1,31 @@
1
1
  /*! LICENSE: index.js.LICENSE.txt */
2
2
  import node_fs, { existsSync } from "node:fs";
3
3
  import node_path, { join, resolve } from "node:path";
4
- import { DiagnosticModes, createAngularCompilation } from "@angular/build/src/tools/angular/compilation";
5
- import { JavaScriptTransformer } from "@angular/build/src/tools/javascript-transformer";
4
+ import { CSSPlugins, LynxEncodePlugin, LynxTemplatePlugin, WebEncodePlugin } from "@lynx-js/template-webpack-plugin";
5
+ import { RuntimeWrapperWebpackPlugin } from "@lynx-js/runtime-wrapper-webpack-plugin";
6
+ import { RuntimeGlobals } from "@lynx-js/webpack-runtime-globals";
7
+ import tiny_invariant from "tiny-invariant";
6
8
  import { createHash } from "node:crypto";
7
9
  import { fileURLToPath } from "node:url";
10
+ import { createRequire } from "node:module";
8
11
  import { availableParallelism } from "node:os";
9
- import node_assert from "node:assert";
10
- import { access, constants } from "node:fs/promises";
11
12
  import { workspaces } from "@angular-devkit/core";
12
13
  import { NodeJsSyncHost } from "@angular-devkit/core/node";
13
- import { CSSPlugins, LynxEncodePlugin, LynxTemplatePlugin, WebEncodePlugin } from "@lynx-js/template-webpack-plugin";
14
- import { RuntimeWrapperWebpackPlugin } from "@lynx-js/runtime-wrapper-webpack-plugin";
15
- import { RuntimeGlobals } from "@lynx-js/webpack-runtime-globals";
16
- import tiny_invariant from "tiny-invariant";
14
+ import { access, constants } from "node:fs/promises";
17
15
  import node_http from "node:http";
18
16
  import { pluginTailwindCSS } from "rsbuild-plugin-tailwindcss";
19
17
  import * as __rspack_external_typescript from "typescript";
20
18
  import * as __rspack_external_node_path_c5b9b54f from "node:path";
19
+ const ANGULAR_CLI_CONTEXT = Symbol.for('@blotch/rsbuild-plugin-angular-lynx:angular-cli-context');
20
+ const ANGULAR_CLI_CONTEXT_PLUGIN_NAME = 'lynx:angular-cli-context';
21
+ const pluginAngularCliContext = (context)=>({
22
+ name: ANGULAR_CLI_CONTEXT_PLUGIN_NAME,
23
+ enforce: 'pre',
24
+ setup: (api)=>{
25
+ api.expose(ANGULAR_CLI_CONTEXT, context);
26
+ }
27
+ });
28
+ const getAngularCliContext = (api)=>api.useExposed(ANGULAR_CLI_CONTEXT);
21
29
  const TYPESCRIPT_FILE = /\.[cm]?tsx?$/;
22
30
  const readFileVersion = (file)=>node_fs.statSync(file, {
23
31
  throwIfNoEntry: false
@@ -76,200 +84,817 @@ const createAngularRebuildCoordinator = ({ compile, getFileVersion = readFileVer
76
84
  }
77
85
  };
78
86
  };
79
- const injectLynxSchema = (source)=>{
80
- if (source.includes('CUSTOM_ELEMENTS_SCHEMA') || source.includes('NO_ERRORS_SCHEMA')) return source;
81
- const importLine = `import { CUSTOM_ELEMENTS_SCHEMA as __LynxCES__ } from '@angular/core';\n`;
82
- const patched = source.replace(/@Component\s*\(\s*\{/g, '@Component({ schemas: [__LynxCES__],');
83
- if (patched === source) return source;
84
- return importLine + patched;
85
- };
86
- const stripTemplateWhitespace = (source)=>source.replace(/(?<=>)[ \t]*\n[ \t]*([^\n<]+?)[ \t]*\n[ \t]*(?=<)/g, '$1');
87
- const buildLynxSchemaSourceFileCache = (tsconfig, shouldInclude = ()=>true)=>{
88
- const sourceFileCache = new Map();
89
- let fileNames;
90
- try {
91
- const configFile = __rspack_external_typescript.readConfigFile(tsconfig, (p)=>node_fs.readFileSync(p, 'utf-8'));
92
- const parsedConfig = __rspack_external_typescript.parseJsonConfigFileContent(configFile.config, __rspack_external_typescript.sys, node_path.dirname(tsconfig));
93
- fileNames = parsedConfig.fileNames;
94
- } catch {
95
- return {
96
- sourceFileCache,
97
- fileNames: []
98
- };
99
- }
100
- for (const filePath of fileNames){
101
- if (filePath.includes('node_modules')) continue;
102
- if (!shouldInclude(filePath)) continue;
103
- let source;
104
- try {
105
- source = node_fs.readFileSync(filePath, 'utf-8');
106
- } catch {
107
- continue;
108
- }
109
- if (!source.includes('@Component')) continue;
110
- const transformed = stripTemplateWhitespace(injectLynxSchema(source));
111
- sourceFileCache.set(filePath, __rspack_external_typescript.createSourceFile(filePath, transformed, __rspack_external_typescript.ScriptTarget.Latest, true));
112
- }
113
- return {
114
- sourceFileCache,
115
- fileNames
116
- };
87
+ const LAYERS = {
88
+ BACKGROUND: 'background',
89
+ MAIN_THREAD: 'main'
117
90
  };
118
- const generateComponentScopeId = (className, containingFile)=>{
119
- const hash = createHash('sha256').update(`${className}|${containingFile}`).digest('hex').slice(0, 10);
120
- return `l${hash}`;
91
+ const applyLayers = (api)=>{
92
+ api.modifyBundlerChain((chain)=>{
93
+ chain.module.rule("typescript").oneOf(LAYERS.BACKGROUND).layer(LAYERS.BACKGROUND).test(/\.[cm]?[jt]sx?$/).use('builtin:swc-loader').loader('builtin:swc-loader').options({
94
+ jsc: {
95
+ target: 'es2015',
96
+ parser: {
97
+ syntax: "typescript"
98
+ }
99
+ }
100
+ }).end();
101
+ chain.module.rule("typescript").oneOf(LAYERS.MAIN_THREAD).layer(LAYERS.MAIN_THREAD).test(/\.[cm]?[jt]sx?$/).use('builtin:swc-loader').loader('builtin:swc-loader').options({
102
+ jsc: {
103
+ target: 'es2019',
104
+ parser: {
105
+ syntax: "typescript"
106
+ }
107
+ }
108
+ }).end();
109
+ });
121
110
  };
122
- const createTransformStylesheet = (params)=>{
123
- const { basePath, scopedCssCacheDir, componentStylesCache, componentScopeIds, onStylesheetWritten } = params;
124
- return async (data, containingFile, stylesheetFile, order, className)=>{
125
- const resolvedClassName = className ?? 'Component';
126
- const scopeId = generateComponentScopeId(resolvedClassName, containingFile);
127
- let componentStyles = componentStylesCache.get(containingFile);
128
- if (!componentStyles) {
129
- componentStyles = {
130
- imports: [],
131
- inlineStyles: [],
132
- scopeId,
133
- processedFiles: new Map()
134
- };
135
- componentStylesCache.set(containingFile, componentStyles);
136
- }
137
- const stylesheetKey = stylesheetFile ?? `__inline_${order}`;
138
- const previousPath = componentStyles.processedFiles.get(stylesheetKey);
139
- if (previousPath && !className) return '';
140
- if (className || !componentScopeIds.has(containingFile)) componentScopeIds.set(containingFile, {
141
- className: resolvedClassName,
142
- scopeId
143
- });
144
- const scopedCss = data;
145
- const writeIfChanged = (filePath, content)=>{
146
- try {
147
- if (node_fs.readFileSync(filePath, 'utf-8') === content) return;
148
- } catch {}
149
- node_fs.writeFileSync(filePath, content);
150
- onStylesheetWritten?.(filePath);
151
- };
152
- let scopedPath;
153
- if (stylesheetFile) {
154
- const relName = node_path.relative(basePath, stylesheetFile);
155
- scopedPath = node_path.join(scopedCssCacheDir, `${relName.replace(/[/\\]/g, '__')}.__scoped_${scopeId}.css`);
156
- } else {
157
- const relDir = node_path.relative(basePath, node_path.dirname(containingFile));
158
- scopedPath = node_path.join(scopedCssCacheDir, `${relDir.replace(/[/\\]/g, '__')}__inline_${resolvedClassName}_${order}.__scoped_${scopeId}.css`);
111
+ const createLynxProcessEvalResultRuntimeModule = (webpack)=>class extends webpack.RuntimeModule {
112
+ constructor(){
113
+ super('Lynx process eval result', webpack.RuntimeModule.STAGE_ATTACH);
159
114
  }
160
- writeIfChanged(scopedPath, scopedCss);
161
- if (previousPath) {
162
- const idx = componentStyles.imports.indexOf(previousPath);
163
- if (idx >= 0) componentStyles.imports[idx] = scopedPath;
164
- else componentStyles.imports.push(scopedPath);
165
- if (previousPath !== scopedPath) try {
166
- node_fs.unlinkSync(previousPath);
167
- } catch {}
168
- } else componentStyles.imports.push(scopedPath);
169
- componentStyles.processedFiles.set(stylesheetKey, scopedPath);
170
- return '';
171
- };
172
- };
173
- const isLynxUnknownElementMessage = (text)=>(text?.includes('is not a known element') || text?.includes("isn't a known property of")) ?? false;
174
- const seedCompilationSourceFiles = (compilation, sourceFileCache)=>{
175
- const { sourceFiles } = compilation;
176
- if (!(sourceFiles instanceof Map)) return false;
177
- for (const [fileName, sourceFile] of sourceFileCache)sourceFiles.set(fileName, sourceFile);
178
- return true;
179
- };
180
- const MAIN_THREAD_DIRECTIVE = 'main thread';
181
- const MAIN_THREAD_DIRECTIVE_ALT = 'main-thread';
182
- const transformWorklets = (code, filename)=>{
183
- if (!code.includes(MAIN_THREAD_DIRECTIVE) && !code.includes(MAIN_THREAD_DIRECTIVE_ALT)) return code;
184
- const sourceFile = __rspack_external_typescript.createSourceFile(filename, code, __rspack_external_typescript.ScriptTarget.Latest, true);
185
- const workletRanges = [];
186
- visitNode(sourceFile, sourceFile, workletRanges);
187
- if (0 === workletRanges.length) return code;
188
- let result = code;
189
- for(let i = workletRanges.length - 1; i >= 0; i--){
190
- const { start, end } = workletRanges[i];
191
- const fnText = result.slice(start, end);
192
- result = result.slice(0, start) + `mainThreadFn(${fnText})` + result.slice(end);
193
- }
194
- result = `import { mainThreadFn } from '@blotch/angular-lynx';\n` + result;
195
- return result;
196
- };
197
- const visitNode = (node, sourceFile, ranges)=>{
198
- if (__rspack_external_typescript.isArrowFunction(node) || __rspack_external_typescript.isFunctionExpression(node)) {
199
- if (hasMainThreadDirective(node) && !isAlreadyWrapped(node)) return void ranges.push({
200
- start: node.getStart(sourceFile),
201
- end: node.getEnd()
202
- });
115
+ generate() {
116
+ const chunk = this.chunk;
117
+ const compilation = this.compilation;
118
+ if (!chunk || !compilation) return '';
119
+ const chunkGroup = chunk._groupsIterable?.[0];
120
+ const modules = compilation.chunkGraph?.getChunkModules(chunk) ?? [];
121
+ modules.sort((a, b)=>{
122
+ const aIdx = chunkGroup?.getModulePreOrderIndex(a) ?? 0;
123
+ const bIdx = chunkGroup?.getModulePreOrderIndex(b) ?? 0;
124
+ return aIdx - bIdx;
125
+ });
126
+ const sortedIds = modules.map((m)=>compilation.chunkGraph.getModuleId(m)).filter((id)=>null != id);
127
+ const moduleOrder = JSON.stringify(sortedIds);
128
+ return `
129
+ ${RuntimeGlobals.lynxProcessEvalResult} = function (result, schema) {
130
+ var chunk = result && result(schema);
131
+ if (chunk && chunk.ids && chunk.modules) {
132
+ ${webpack.RuntimeGlobals.externalInstallChunk}(chunk);
133
+ var moduleOrder = ${moduleOrder};
134
+ for (var i = 0; i < moduleOrder.length; i++) {
135
+ if (chunk.modules[moduleOrder[i]]) {
136
+ ${webpack.RuntimeGlobals.require}(moduleOrder[i]);
137
+ }
203
138
  }
204
- __rspack_external_typescript.forEachChild(node, (child)=>visitNode(child, sourceFile, ranges));
205
- };
206
- const hasMainThreadDirective = (node)=>{
207
- const body = node.body;
208
- if (!__rspack_external_typescript.isBlock(body)) return false;
209
- if (0 === body.statements.length) return false;
210
- const first = body.statements[0];
211
- if (!__rspack_external_typescript.isExpressionStatement(first)) return false;
212
- const expr = first.expression;
213
- if (!__rspack_external_typescript.isStringLiteral(expr)) return false;
214
- const text = expr.text;
215
- return text === MAIN_THREAD_DIRECTIVE || text === MAIN_THREAD_DIRECTIVE_ALT;
216
- };
217
- const isAlreadyWrapped = (node)=>{
218
- const parent = node.parent;
219
- if (!parent || !__rspack_external_typescript.isCallExpression(parent)) return false;
220
- const callee = parent.expression;
221
- if (__rspack_external_typescript.isIdentifier(callee) && 'mainThreadFn' === callee.text) return true;
222
- if (__rspack_external_typescript.isPropertyAccessExpression(callee) && 'mainThreadFn' === callee.name.text) return true;
223
- return false;
224
- };
225
- const buildTransformedCode = (params)=>{
226
- const { resourcePath, componentStyles, scopeInfo } = params;
227
- let code = params.code;
228
- if (componentStyles) {
229
- const { imports } = componentStyles;
230
- let importsString = '';
231
- for(let i = 0; i < imports.length; ++i){
232
- let relativeImport = node_path.relative(node_path.dirname(resourcePath), imports[i]);
233
- if (!relativeImport.startsWith('.')) relativeImport = `./${relativeImport}`;
234
- importsString += `import "${relativeImport}";`;
139
+ return chunk;
140
+ }
141
+ return chunk
142
+ }
143
+ `;
235
144
  }
236
- code = importsString + code;
145
+ };
146
+ class AngularWebpackPlugin {
147
+ options;
148
+ constructor(options){
149
+ this.options = options;
237
150
  }
238
- if (scopeInfo) code += `\n;${scopeInfo.className}.ɵcmp.id = '${scopeInfo.scopeId}';\n`;
239
- return transformWorklets(code, resourcePath);
240
- };
241
- const applyAngularConfig = (api, buildOptions)=>{
242
- const __dirname = node_path.dirname(fileURLToPath(import.meta.url));
243
- api.modifyRsbuildConfig((config)=>{
244
- config.output = config.output ?? {};
245
- if (buildOptions.outputPath) config.output.distPath = {
246
- ...'object' == typeof config.output.distPath ? config.output.distPath : {},
247
- root: buildOptions.outputPath
248
- };
249
- config.output.cleanDistPath = true;
250
- config.source ??= {};
251
- if ('string' == typeof config.source.preEntry) config.source.preEntry = [
252
- config.source.preEntry
253
- ];
254
- else config.source.preEntry ??= [];
255
- config.source.preEntry.push(node_path.resolve(__dirname, './polyfills'));
256
- if (buildOptions.i18n.hasDefinedSourceLocale) config.source.preEntry.push('@angular/localize/init');
257
- const polyfills = buildOptions.polyfills;
258
- if (polyfills) config.source.preEntry.push(...polyfills);
259
- config.source.preEntry.push(...buildOptions.styles);
260
- config.source.tsconfigPath = buildOptions.tsconfig;
261
- config.source.define ??= {};
262
- config.source.define['__LYNX_SOURCE_LOCALE__'] = JSON.stringify(buildOptions.i18n.sourceLocale);
263
- const isProd = 'production' === process.env.NODE_ENV || 'production' === config.mode;
264
- if (isProd) config.source.define.ngDevMode = false;
265
- });
266
- };
267
- const isPresent = (variable)=>'string' == typeof variable && '' !== variable;
268
- const isDisabled = (variable)=>'0' === variable || 'false' === variable.toLowerCase();
269
- const maxWorkersVariable = process.env.NG_BUILD_MAX_WORKERS;
270
- const maxWorkers = isPresent(maxWorkersVariable) ? +maxWorkersVariable : Math.min(4, Math.max(availableParallelism() - 1, 1));
271
- const typeCheckingVariable = process.env.NG_BUILD_TYPE_CHECK;
151
+ static defaultOptions = Object.freeze({
152
+ disableCreateSelectorQueryIncompatibleWarning: false,
153
+ firstScreenSyncTiming: 'immediately',
154
+ enableSSR: false,
155
+ isWeb: false,
156
+ mainThreadChunks: [],
157
+ extractStr: false,
158
+ experimental_isLazyBundle: false
159
+ });
160
+ apply(compiler) {
161
+ const options = Object.assign({}, AngularWebpackPlugin.defaultOptions, this.options);
162
+ if (options.enableSSR && 'jsReady' !== options.firstScreenSyncTiming) options.firstScreenSyncTiming = 'jsReady';
163
+ const { DefinePlugin, EnvironmentPlugin } = compiler.webpack;
164
+ new EnvironmentPlugin({
165
+ DEBUG: null
166
+ }).apply(compiler);
167
+ new DefinePlugin({
168
+ __DEV__: JSON.stringify('development' === compiler.options.mode),
169
+ __PROFILE__: JSON.stringify('development' === compiler.options.mode),
170
+ __EXTRACT_STR__: JSON.stringify(Boolean(options.extractStr)),
171
+ __FIRST_SCREEN_SYNC_TIMING__: JSON.stringify(options.firstScreenSyncTiming),
172
+ __ENABLE_SSR__: JSON.stringify(options.enableSSR),
173
+ __WEB__: JSON.stringify(Boolean(options.isWeb)),
174
+ __DISABLE_CREATE_SELECTOR_QUERY_INCOMPATIBLE_WARNING__: JSON.stringify(options.disableCreateSelectorQueryIncompatibleWarning)
175
+ }).apply(compiler);
176
+ compiler.hooks.thisCompilation.tap(this.constructor.name, (compilation)=>{
177
+ const onceForChunkSet = new WeakSet();
178
+ compilation.hooks.runtimeRequirementInTree.for(compiler.webpack.RuntimeGlobals.ensureChunkHandlers).tap('VanillaWebpackPlugin', (chunk, runtimeRequirements)=>{
179
+ runtimeRequirements.add(RuntimeGlobals.lynxProcessEvalResult);
180
+ });
181
+ compilation.hooks.runtimeRequirementInTree.for(RuntimeGlobals.lynxProcessEvalResult).tap('VanillaWebpackPlugin', (chunk)=>{
182
+ if (onceForChunkSet.has(chunk)) return;
183
+ onceForChunkSet.add(chunk);
184
+ const LynxProcessEvalResultRuntimeModule = createLynxProcessEvalResultRuntimeModule(compiler.webpack);
185
+ compilation.addRuntimeModule(chunk, new LynxProcessEvalResultRuntimeModule());
186
+ });
187
+ compilation.hooks.processAssets.tap({
188
+ name: this.constructor.name,
189
+ stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL
190
+ }, ()=>{
191
+ for (const name of options.mainThreadChunks ?? [])this.#updateMainThreadInfo(compilation, name);
192
+ for (const cg of compilation.chunkGroups.filter((cg)=>!cg.isInitial()).filter((cg)=>cg.origins.every((origin)=>origin.module?.layer === LAYERS.MAIN_THREAD))){
193
+ const files = cg.getFiles();
194
+ for (const name of files.filter((name)=>name.endsWith('.js')))this.#updateMainThreadInfo(compilation, name);
195
+ }
196
+ });
197
+ {
198
+ const { ConcatSource } = compiler.webpack.sources;
199
+ const mainThreadChunkSet = new Set(options.mainThreadChunks ?? []);
200
+ compilation.hooks.processAssets.tap({
201
+ name: `${this.constructor.name}:threadGlobals`,
202
+ stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS
203
+ }, ()=>{
204
+ for (const [name] of Object.entries(compilation.assets).filter(([n])=>n.endsWith('.js'))){
205
+ const isMainThread = mainThreadChunkSet.has(name);
206
+ const prefix = `globalThis["__MAIN_THREAD__"]=${isMainThread};`;
207
+ compilation.updateAsset(name, (old)=>new ConcatSource(prefix, old));
208
+ }
209
+ if (!options.experimental_isLazyBundle) for (const name of options.mainThreadChunks ?? []){
210
+ const asset = compilation.getAsset(name);
211
+ if (!asset) continue;
212
+ const source = asset.source.source().toString();
213
+ const useStrictPrefix = /^(['"])use strict\1;?/.test(source) ? "'use strict';" : '';
214
+ compilation.updateAsset(name, (old)=>new ConcatSource(`${useStrictPrefix}var globDynamicComponentEntry=globDynamicComponentEntry||'__Card__';`, old));
215
+ }
216
+ });
217
+ }
218
+ const hooks = LynxTemplatePlugin.getLynxTemplatePluginHooks(compilation);
219
+ const { ConcatSource } = compiler.webpack.sources;
220
+ hooks.beforeEncode.tap(this.constructor.name, (args)=>{
221
+ const { encodeData } = args;
222
+ encodeData.sourceContent.config = {
223
+ ...encodeData.sourceContent.config,
224
+ enableCSSInlineVariables: true
225
+ };
226
+ if (!encodeData.lepusCode.root) return args;
227
+ if ('card' === encodeData.sourceContent.appType) return args;
228
+ compilation.updateAsset(encodeData.lepusCode.root.name, (old)=>new ConcatSource(`\
229
+ (function (globDynamicComponentEntry) {
230
+ const module = { exports: {} }
231
+ const exports = module.exports
232
+ `, old, `
233
+ ;return module.exports
234
+ })`));
235
+ return args;
236
+ });
237
+ });
238
+ }
239
+ #updateMainThreadInfo(compilation, name) {
240
+ const asset = compilation.getAsset(name);
241
+ tiny_invariant(asset, `Should have main thread asset ${name}`);
242
+ compilation.updateAsset(asset.name, asset.source, {
243
+ ...asset.info,
244
+ 'lynx:main-thread': true
245
+ });
246
+ }
247
+ }
248
+ const S_LYNX_CONFIG = Symbol.for('@lynx-js/rsbuild-plugin:config');
249
+ const PLUGIN_NAME_TEMPLATE = 'lynx:template';
250
+ const PLUGIN_NAME_RUNTIME_WRAPPER = 'lynx:runtime-wrapper';
251
+ const PLUGIN_NAME_ANGULAR = 'lynx:angular';
252
+ const applyEntry = (api, options)=>{
253
+ const { customCSSInheritanceList, debugInfoOutside, defaultDisplayLinear, enableA11y, enableAccessibilityElement, enableCSSInheritance, enableCSSInvalidation, enableCSSSelector, enableNewGesture, enableRemoveCSSScope, removeDescendantSelectorScope, targetSdkVersion } = options;
254
+ api.modifyBundlerChain((chain, { environment, isDev })=>{
255
+ const lynxConfig = api.useExposed(S_LYNX_CONFIG);
256
+ if (!lynxConfig) throw new Error('No Lynx config exposed. `pluginLynx` has to be applied for the Lynx build engine to be configured.');
257
+ const isLynx = 'lynx' === environment.name;
258
+ const isWeb = 'web' === environment.name;
259
+ const { hmr, liveReload } = environment.config.dev ?? {};
260
+ const enabledHMR = isDev && !isWeb && false !== hmr;
261
+ const enabledLiveReload = isDev && !isWeb && false !== liveReload;
262
+ const entries = chain.entryPoints.entries() ?? {};
263
+ chain.entryPoints.clear();
264
+ const mainThreadChunks = [];
265
+ for (const [entryName, entryPoint] of Object.entries(entries)){
266
+ const { imports } = getChunks(entryName, entryPoint.values());
267
+ const templateFilename = lynxConfig.resolveBundleFilename({
268
+ entryName,
269
+ platform: environment.name
270
+ });
271
+ const lazyBundleFilename = lynxConfig.resolveLazyBundleFilename({
272
+ platform: environment.name
273
+ });
274
+ const mainThreadEntry = `${entryName}__main-thread`;
275
+ const mainThreadName = node_path.posix.join(`${entryName}/main-thread.js`);
276
+ const backgroundEntry = entryName;
277
+ const backgroundThreadName = node_path.posix.join(`${entryName}/background-thread.js`);
278
+ mainThreadChunks.push(mainThreadName);
279
+ chain.entry(mainThreadEntry).add({
280
+ layer: LAYERS.MAIN_THREAD,
281
+ import: imports,
282
+ filename: mainThreadName
283
+ }).end();
284
+ chain.entry(backgroundEntry).add({
285
+ layer: LAYERS.BACKGROUND,
286
+ import: imports,
287
+ filename: backgroundThreadName
288
+ }).when(enabledHMR, (entry)=>{
289
+ entry.prepend({
290
+ layer: LAYERS.BACKGROUND,
291
+ import: '@rspack/core/hot/dev-server'
292
+ });
293
+ }).when(enabledHMR || enabledLiveReload, (entry)=>{
294
+ entry.prepend({
295
+ layer: LAYERS.BACKGROUND,
296
+ import: '@lynx-js/webpack-dev-transport/client'
297
+ });
298
+ }).end();
299
+ chain.plugin(`${PLUGIN_NAME_TEMPLATE}-${entryName}`).use(LynxTemplatePlugin, [
300
+ {
301
+ filename: templateFilename,
302
+ ...lazyBundleFilename ? {
303
+ lazyBundleFilename
304
+ } : {},
305
+ intermediate: lynxConfig.resolveIntermediateDir({
306
+ entryName
307
+ }),
308
+ chunks: [
309
+ mainThreadEntry,
310
+ backgroundEntry
311
+ ],
312
+ cssPlugins: [
313
+ CSSPlugins.parserPlugins.removeFunctionWhiteSpace()
314
+ ],
315
+ customCSSInheritanceList,
316
+ debugInfoOutside,
317
+ defaultDisplayLinear,
318
+ enableAccessibilityElement,
319
+ enableCSSInheritance,
320
+ enableCSSInvalidation,
321
+ enableCSSSelector,
322
+ enableNewGesture,
323
+ enableRemoveCSSScope,
324
+ removeDescendantSelectorScope,
325
+ targetSdkVersion,
326
+ enableA11y
327
+ }
328
+ ]).end();
329
+ }
330
+ if (isLynx) {
331
+ chain.plugin(PLUGIN_NAME_RUNTIME_WRAPPER).use(RuntimeWrapperWebpackPlugin, [
332
+ {
333
+ injectVars (vars) {
334
+ return vars.map((name)=>{
335
+ if ('Component' === name) return '__Component';
336
+ return name;
337
+ });
338
+ },
339
+ targetSdkVersion,
340
+ test: /^(?!.*main-thread(?:\.[A-Fa-f0-9]*)?\.js$).*\.js$/
341
+ }
342
+ ]).end();
343
+ chain.plugin(`${LynxEncodePlugin.name}`).use(LynxEncodePlugin, [
344
+ {}
345
+ ]).end();
346
+ }
347
+ if (isWeb) chain.plugin(`${WebEncodePlugin.name}`).use(WebEncodePlugin, []).end();
348
+ chain.plugin(PLUGIN_NAME_ANGULAR).after(PLUGIN_NAME_TEMPLATE).use(AngularWebpackPlugin, [
349
+ {
350
+ mainThreadChunks,
351
+ enableSSR: options.enableSSR,
352
+ isWeb
353
+ }
354
+ ]);
355
+ });
356
+ };
357
+ const getChunks = (entryName, entryValue)=>{
358
+ const chunks = [
359
+ entryName
360
+ ];
361
+ const imports = [];
362
+ for (const item of entryValue){
363
+ if ('string' == typeof item) {
364
+ imports.push(item);
365
+ continue;
366
+ }
367
+ if (Array.isArray(item)) {
368
+ imports.push(...imports);
369
+ continue;
370
+ }
371
+ const { dependOn } = item;
372
+ if (Array.isArray(item.import)) imports.push(...item.import);
373
+ else imports.push(item.import);
374
+ if (dependOn) if ('string' == typeof dependOn) chunks.unshift(dependOn);
375
+ else chunks.unshift(...dependOn);
376
+ }
377
+ return {
378
+ chunks,
379
+ imports
380
+ };
381
+ };
382
+ const BYTES_IN_KILOBYTE = 1000;
383
+ const calculateBytes = (input, baseline, factor = 1)=>{
384
+ const matches = input.trim().match(/^(\d+(?:\.\d+)?)[ \t]*(%|[kmg]?b)?$/i);
385
+ if (!matches) return NaN;
386
+ const baselineBytes = baseline && calculateBytes(baseline) || 0;
387
+ let value = Number(matches[1]);
388
+ switch(matches[2]?.toLowerCase()){
389
+ case '%':
390
+ value = baselineBytes * value / 100;
391
+ break;
392
+ case 'kb':
393
+ value *= BYTES_IN_KILOBYTE;
394
+ break;
395
+ case 'mb':
396
+ value *= BYTES_IN_KILOBYTE * BYTES_IN_KILOBYTE;
397
+ break;
398
+ case 'gb':
399
+ value *= BYTES_IN_KILOBYTE * BYTES_IN_KILOBYTE * BYTES_IN_KILOBYTE;
400
+ break;
401
+ }
402
+ if (0 === baselineBytes) return value;
403
+ return baselineBytes + value * factor;
404
+ };
405
+ const calculateThresholds = (budget)=>{
406
+ const thresholds = [];
407
+ if (budget.maximumWarning) thresholds.push({
408
+ limit: calculateBytes(budget.maximumWarning, budget.baseline, 1),
409
+ type: 'maximum',
410
+ severity: 'warning'
411
+ });
412
+ if (budget.maximumError) thresholds.push({
413
+ limit: calculateBytes(budget.maximumError, budget.baseline, 1),
414
+ type: 'maximum',
415
+ severity: 'error'
416
+ });
417
+ if (budget.minimumWarning) thresholds.push({
418
+ limit: calculateBytes(budget.minimumWarning, budget.baseline, -1),
419
+ type: 'minimum',
420
+ severity: 'warning'
421
+ });
422
+ if (budget.minimumError) thresholds.push({
423
+ limit: calculateBytes(budget.minimumError, budget.baseline, -1),
424
+ type: 'minimum',
425
+ severity: 'error'
426
+ });
427
+ if (budget.warning) thresholds.push({
428
+ limit: calculateBytes(budget.warning, budget.baseline, -1),
429
+ type: 'minimum',
430
+ severity: 'warning'
431
+ }, {
432
+ limit: calculateBytes(budget.warning, budget.baseline, 1),
433
+ type: 'maximum',
434
+ severity: 'warning'
435
+ });
436
+ if (budget.error) thresholds.push({
437
+ limit: calculateBytes(budget.error, budget.baseline, -1),
438
+ type: 'minimum',
439
+ severity: 'error'
440
+ }, {
441
+ limit: calculateBytes(budget.error, budget.baseline, 1),
442
+ type: 'maximum',
443
+ severity: 'error'
444
+ });
445
+ return thresholds;
446
+ };
447
+ const formatSize = (size)=>{
448
+ if (size <= 0) return '0 bytes';
449
+ const abbreviations = [
450
+ 'bytes',
451
+ 'kB',
452
+ 'MB',
453
+ 'GB'
454
+ ];
455
+ const index = Math.floor(Math.log(size) / Math.log(BYTES_IN_KILOBYTE));
456
+ const roundedSize = size / Math.pow(BYTES_IN_KILOBYTE, index);
457
+ const fractionDigits = 0 === index ? 0 : 2;
458
+ return `${roundedSize.toFixed(fractionDigits)} ${abbreviations[index]}`;
459
+ };
460
+ const checkThresholds = (thresholds, size, label)=>thresholds.flatMap(({ limit, type, severity })=>{
461
+ if ('maximum' === type) return size <= limit ? [] : [
462
+ {
463
+ severity,
464
+ label,
465
+ message: `${label} exceeded maximum budget. Budget ${formatSize(limit)} was not met by ${formatSize(size - limit)} with a total of ${formatSize(size)}.`
466
+ }
467
+ ];
468
+ return size >= limit ? [] : [
469
+ {
470
+ severity,
471
+ label,
472
+ message: `${label} failed to meet minimum budget. Budget ${formatSize(limit)} was not met by ${formatSize(limit - size)} with a total of ${formatSize(size)}.`
473
+ }
474
+ ];
475
+ });
476
+ const sum = (sizes)=>sizes.reduce((total, { size })=>total + size, 0);
477
+ const calculateSizes = (budget, assets, componentStyles)=>{
478
+ switch(budget.type){
479
+ case 'initial':
480
+ {
481
+ const bundles = assets.filter((asset)=>void 0 !== asset.entryName);
482
+ return bundles.map((asset)=>({
483
+ size: asset.size,
484
+ label: 1 === bundles.length ? 'bundle initial' : `bundle initial (${asset.entryName})`
485
+ }));
486
+ }
487
+ case 'bundle':
488
+ return budget.name ? [
489
+ {
490
+ size: sum(assets.filter((asset)=>asset.entryName === budget.name)),
491
+ label: budget.name
492
+ }
493
+ ] : [];
494
+ case 'all':
495
+ return [
496
+ {
497
+ size: sum(assets),
498
+ label: 'total'
499
+ }
500
+ ];
501
+ case 'allScript':
502
+ return [
503
+ {
504
+ size: sum(assets),
505
+ label: "total scripts"
506
+ }
507
+ ];
508
+ case 'any':
509
+ case 'anyScript':
510
+ return assets.map((asset)=>({
511
+ size: asset.size,
512
+ label: asset.name
513
+ }));
514
+ case 'anyComponentStyle':
515
+ return componentStyles.map((style)=>({
516
+ ...style
517
+ }));
518
+ default:
519
+ return [];
520
+ }
521
+ };
522
+ const checkBudgets = (budgets, output)=>budgets.flatMap((budget)=>calculateSizes(budget, output.assets, output.componentStyles).flatMap(({ size, label })=>checkThresholds(calculateThresholds(budget), size, label)));
523
+ const PLUGIN_NAME = 'lynx:angular:budgets';
524
+ const MAIN_THREAD_ENTRY_SUFFIX = '__main-thread';
525
+ const toBudgetError = (failure)=>{
526
+ const error = new Error(failure.message);
527
+ error.name = 'error' === failure.severity ? 'AngularBudgetError' : 'AngularBudgetWarning';
528
+ error.hideStack = true;
529
+ return error;
530
+ };
531
+ const bundleEntryName = (chunkGroups, entrypoints)=>chunkGroups.map((chunkGroup)=>chunkGroup.name).find((name)=>!!name && entrypoints.has(name) && !name.endsWith(MAIN_THREAD_ENTRY_SUFFIX));
532
+ const collectBudgetAssets = (assets, entryBundles, intermediateDir)=>assets.filter(({ name, info })=>/\.(bundle|js)$/.test(name) && !info.hotModuleReplacement && !(intermediateDir && name.startsWith(`${intermediateDir}/`))).map(({ name, source })=>{
533
+ const entryName = entryBundles.get(name);
534
+ return {
535
+ name,
536
+ size: source.size(),
537
+ ...void 0 === entryName ? {} : {
538
+ entryName
539
+ }
540
+ };
541
+ });
542
+ class AngularBudgetsPlugin {
543
+ #options;
544
+ constructor(options){
545
+ this.#options = options;
546
+ }
547
+ apply(compiler) {
548
+ const { budgets, componentStyleSizes, intermediateDir } = this.#options;
549
+ compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation)=>{
550
+ const entryBundles = new Map();
551
+ LynxTemplatePlugin.getLynxTemplatePluginHooks(compilation).beforeEmit.tap(PLUGIN_NAME, (args)=>{
552
+ const entryName = bundleEntryName(args.chunkGroups, compilation.entrypoints);
553
+ if (void 0 !== entryName) entryBundles.set(args.outputName, entryName);
554
+ return args;
555
+ });
556
+ compilation.hooks.processAssets.tap({
557
+ name: PLUGIN_NAME,
558
+ stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_REPORT + 2
559
+ }, ()=>{
560
+ const failures = checkBudgets(budgets, {
561
+ assets: collectBudgetAssets(compilation.getAssets(), entryBundles, intermediateDir),
562
+ componentStyles: [
563
+ ...componentStyleSizes.values()
564
+ ]
565
+ });
566
+ for (const failure of failures)('error' === failure.severity ? compilation.errors : compilation.warnings).push(toBudgetError(failure));
567
+ });
568
+ });
569
+ }
570
+ }
571
+ const applyBudgets = (api, { budgets, componentStyleSizes, underAngularCli })=>{
572
+ if (0 === budgets.length) return;
573
+ if (!underAngularCli && 'dev' === api.context.action) return;
574
+ api.modifyBundlerChain((chain, { environment })=>{
575
+ if ('lynx' !== environment.name) return;
576
+ const lynxConfig = api.useExposed(S_LYNX_CONFIG);
577
+ chain.plugin(PLUGIN_NAME).use(AngularBudgetsPlugin, [
578
+ {
579
+ budgets,
580
+ componentStyleSizes,
581
+ intermediateDir: lynxConfig?.resolveIntermediateDir()
582
+ }
583
+ ]);
584
+ });
585
+ };
586
+ const injectLynxSchema = (source)=>{
587
+ if (source.includes('CUSTOM_ELEMENTS_SCHEMA') || source.includes('NO_ERRORS_SCHEMA')) return source;
588
+ const importLine = `import { CUSTOM_ELEMENTS_SCHEMA as __LynxCES__ } from '@angular/core';\n`;
589
+ const patched = source.replace(/@Component\s*\(\s*\{/g, '@Component({ schemas: [__LynxCES__],');
590
+ if (patched === source) return source;
591
+ return importLine + patched;
592
+ };
593
+ const stripTemplateWhitespace = (source)=>source.replace(/(?<=>)[ \t]*\n[ \t]*([^\n<]+?)[ \t]*\n[ \t]*(?=<)/g, '$1');
594
+ const buildLynxSchemaSourceFileCache = (tsconfig, shouldInclude = ()=>true)=>{
595
+ const sourceFileCache = new Map();
596
+ let fileNames;
597
+ try {
598
+ const configFile = __rspack_external_typescript.readConfigFile(tsconfig, (p)=>node_fs.readFileSync(p, 'utf-8'));
599
+ const parsedConfig = __rspack_external_typescript.parseJsonConfigFileContent(configFile.config, __rspack_external_typescript.sys, node_path.dirname(tsconfig));
600
+ fileNames = parsedConfig.fileNames;
601
+ } catch {
602
+ return {
603
+ sourceFileCache,
604
+ fileNames: []
605
+ };
606
+ }
607
+ for (const filePath of fileNames){
608
+ if (filePath.includes('node_modules')) continue;
609
+ if (!shouldInclude(filePath)) continue;
610
+ let source;
611
+ try {
612
+ source = node_fs.readFileSync(filePath, 'utf-8');
613
+ } catch {
614
+ continue;
615
+ }
616
+ if (!source.includes('@Component')) continue;
617
+ const transformed = stripTemplateWhitespace(injectLynxSchema(source));
618
+ sourceFileCache.set(filePath, __rspack_external_typescript.createSourceFile(filePath, transformed, __rspack_external_typescript.ScriptTarget.Latest, true));
619
+ }
620
+ return {
621
+ sourceFileCache,
622
+ fileNames
623
+ };
624
+ };
625
+ const generateComponentScopeId = (className, containingFile)=>{
626
+ const hash = createHash('sha256').update(`${className}|${containingFile}`).digest('hex').slice(0, 10);
627
+ return `l${hash}`;
628
+ };
629
+ const componentStyleSizeKey = (containingFile, stylesheetKey)=>`${containingFile}#${stylesheetKey}`;
630
+ const forgetComponentStyleSizes = (componentStyleSizes, containingFile)=>{
631
+ const prefix = componentStyleSizeKey(containingFile, '');
632
+ for (const key of componentStyleSizes.keys())if (key.startsWith(prefix)) componentStyleSizes.delete(key);
633
+ };
634
+ const createTransformStylesheet = (params)=>{
635
+ const { basePath, scopedCssCacheDir, componentStylesCache, componentScopeIds, onStylesheetWritten, componentStyleSizes } = params;
636
+ return async (data, containingFile, stylesheetFile, order, className)=>{
637
+ const resolvedClassName = className ?? 'Component';
638
+ const scopeId = generateComponentScopeId(resolvedClassName, containingFile);
639
+ let componentStyles = componentStylesCache.get(containingFile);
640
+ if (!componentStyles) {
641
+ componentStyles = {
642
+ imports: [],
643
+ inlineStyles: [],
644
+ scopeId,
645
+ processedFiles: new Map()
646
+ };
647
+ componentStylesCache.set(containingFile, componentStyles);
648
+ }
649
+ const stylesheetKey = stylesheetFile ?? `__inline_${order}`;
650
+ const previousPath = componentStyles.processedFiles.get(stylesheetKey);
651
+ if (previousPath && !className) return '';
652
+ componentStyleSizes?.set(componentStyleSizeKey(containingFile, stylesheetKey), {
653
+ label: node_path.relative(basePath, stylesheetFile ?? containingFile).replace(/\\/g, '/'),
654
+ size: Buffer.byteLength(data)
655
+ });
656
+ if (className || !componentScopeIds.has(containingFile)) componentScopeIds.set(containingFile, {
657
+ className: resolvedClassName,
658
+ scopeId
659
+ });
660
+ const scopedCss = data;
661
+ const writeIfChanged = (filePath, content)=>{
662
+ try {
663
+ if (node_fs.readFileSync(filePath, 'utf-8') === content) return;
664
+ } catch {}
665
+ node_fs.writeFileSync(filePath, content);
666
+ onStylesheetWritten?.(filePath);
667
+ };
668
+ let scopedPath;
669
+ if (stylesheetFile) {
670
+ const relName = node_path.relative(basePath, stylesheetFile);
671
+ scopedPath = node_path.join(scopedCssCacheDir, `${relName.replace(/[/\\]/g, '__')}.__scoped_${scopeId}.css`);
672
+ } else {
673
+ const relDir = node_path.relative(basePath, node_path.dirname(containingFile));
674
+ scopedPath = node_path.join(scopedCssCacheDir, `${relDir.replace(/[/\\]/g, '__')}__inline_${resolvedClassName}_${order}.__scoped_${scopeId}.css`);
675
+ }
676
+ writeIfChanged(scopedPath, scopedCss);
677
+ if (previousPath) {
678
+ const idx = componentStyles.imports.indexOf(previousPath);
679
+ if (idx >= 0) componentStyles.imports[idx] = scopedPath;
680
+ else componentStyles.imports.push(scopedPath);
681
+ if (previousPath !== scopedPath) try {
682
+ node_fs.unlinkSync(previousPath);
683
+ } catch {}
684
+ } else componentStyles.imports.push(scopedPath);
685
+ componentStyles.processedFiles.set(stylesheetKey, scopedPath);
686
+ return '';
687
+ };
688
+ };
689
+ const isLynxUnknownElementMessage = (text)=>(text?.includes('is not a known element') || text?.includes("isn't a known property of")) ?? false;
690
+ const filterAngularDiagnostics = (diagnostics)=>({
691
+ errors: (diagnostics.errors ?? []).filter((message)=>!isLynxUnknownElementMessage(message.text)),
692
+ warnings: (diagnostics.warnings ?? []).filter((message)=>!isLynxUnknownElementMessage(message.text))
693
+ });
694
+ const formatLocation = ({ file, line, column })=>{
695
+ if (!file) return;
696
+ if (void 0 === line) return file;
697
+ return `${file}:${line}:${(column ?? 0) + 1}`;
698
+ };
699
+ const formatCodeFrame = ({ line, column, length, lineText })=>{
700
+ if (void 0 === line || void 0 === lineText) return;
701
+ const gutter = `${line} | `;
702
+ const caretIndent = ' '.repeat(gutter.length - 2) + '| ';
703
+ const caret = ' '.repeat(column ?? 0) + '^'.repeat(Math.max(1, length ?? 1));
704
+ return `${gutter}${lineText}\n${caretIndent}${caret}`;
705
+ };
706
+ const toAngularDiagnosticError = (message, severity)=>{
707
+ const sections = [
708
+ message.text ?? 'Unknown Angular compiler diagnostic'
709
+ ];
710
+ const codeFrame = message.location && formatCodeFrame(message.location);
711
+ if (codeFrame) sections.push(codeFrame);
712
+ for (const note of message.notes ?? []){
713
+ const noteLocation = note.location && formatLocation(note.location);
714
+ sections.push([
715
+ noteLocation,
716
+ note.text
717
+ ].filter((part)=>part).join(': '));
718
+ }
719
+ const error = new Error(sections.join('\n\n'));
720
+ error.name = 'error' === severity ? 'AngularCompilerError' : 'AngularCompilerWarning';
721
+ const file = message.location && formatLocation(message.location);
722
+ if (file) error.file = file;
723
+ error.hideStack = true;
724
+ return error;
725
+ };
726
+ const seedCompilationSourceFiles = (compilation, sourceFileCache)=>{
727
+ const { sourceFiles } = compilation;
728
+ if (!(sourceFiles instanceof Map)) return false;
729
+ for (const [fileName, sourceFile] of sourceFileCache)sourceFiles.set(fileName, sourceFile);
730
+ return true;
731
+ };
732
+ const MAIN_THREAD_DIRECTIVE = 'main thread';
733
+ const MAIN_THREAD_DIRECTIVE_ALT = 'main-thread';
734
+ const transformWorklets = (code, filename)=>{
735
+ if (!code.includes(MAIN_THREAD_DIRECTIVE) && !code.includes(MAIN_THREAD_DIRECTIVE_ALT)) return code;
736
+ const sourceFile = __rspack_external_typescript.createSourceFile(filename, code, __rspack_external_typescript.ScriptTarget.Latest, true);
737
+ const workletRanges = [];
738
+ visitNode(sourceFile, sourceFile, workletRanges);
739
+ if (0 === workletRanges.length) return code;
740
+ let result = code;
741
+ for(let i = workletRanges.length - 1; i >= 0; i--){
742
+ const { start, end } = workletRanges[i];
743
+ const fnText = result.slice(start, end);
744
+ result = result.slice(0, start) + `mainThreadFn(${fnText})` + result.slice(end);
745
+ }
746
+ result = `import { mainThreadFn } from '@blotch/angular-lynx';\n` + result;
747
+ return result;
748
+ };
749
+ const visitNode = (node, sourceFile, ranges)=>{
750
+ if (__rspack_external_typescript.isArrowFunction(node) || __rspack_external_typescript.isFunctionExpression(node)) {
751
+ if (hasMainThreadDirective(node) && !isAlreadyWrapped(node)) return void ranges.push({
752
+ start: node.getStart(sourceFile),
753
+ end: node.getEnd()
754
+ });
755
+ }
756
+ __rspack_external_typescript.forEachChild(node, (child)=>visitNode(child, sourceFile, ranges));
757
+ };
758
+ const hasMainThreadDirective = (node)=>{
759
+ const body = node.body;
760
+ if (!__rspack_external_typescript.isBlock(body)) return false;
761
+ if (0 === body.statements.length) return false;
762
+ const first = body.statements[0];
763
+ if (!__rspack_external_typescript.isExpressionStatement(first)) return false;
764
+ const expr = first.expression;
765
+ if (!__rspack_external_typescript.isStringLiteral(expr)) return false;
766
+ const text = expr.text;
767
+ return text === MAIN_THREAD_DIRECTIVE || text === MAIN_THREAD_DIRECTIVE_ALT;
768
+ };
769
+ const isAlreadyWrapped = (node)=>{
770
+ const parent = node.parent;
771
+ if (!parent || !__rspack_external_typescript.isCallExpression(parent)) return false;
772
+ const callee = parent.expression;
773
+ if (__rspack_external_typescript.isIdentifier(callee) && 'mainThreadFn' === callee.text) return true;
774
+ if (__rspack_external_typescript.isPropertyAccessExpression(callee) && 'mainThreadFn' === callee.name.text) return true;
775
+ return false;
776
+ };
777
+ const buildTransformedCode = (params)=>{
778
+ const { resourcePath, componentStyles, scopeInfo } = params;
779
+ let code = params.code;
780
+ if (componentStyles) {
781
+ const { imports } = componentStyles;
782
+ let importsString = '';
783
+ for(let i = 0; i < imports.length; ++i){
784
+ let relativeImport = node_path.relative(node_path.dirname(resourcePath), imports[i]);
785
+ if (!relativeImport.startsWith('.')) relativeImport = `./${relativeImport}`;
786
+ importsString += `import "${relativeImport}";`;
787
+ }
788
+ code = importsString + code;
789
+ }
790
+ if (scopeInfo) code += `\n;${scopeInfo.className}.ɵcmp.id = '${scopeInfo.scopeId}';\n`;
791
+ return transformWorklets(code, resourcePath);
792
+ };
793
+ const applyAngularConfig = (api, buildOptions)=>{
794
+ const __dirname = node_path.dirname(fileURLToPath(import.meta.url));
795
+ api.modifyRsbuildConfig((config)=>{
796
+ config.output = config.output ?? {};
797
+ if (buildOptions.outputPath) config.output.distPath = {
798
+ ...'object' == typeof config.output.distPath ? config.output.distPath : {},
799
+ root: buildOptions.outputPath
800
+ };
801
+ config.output.cleanDistPath = true;
802
+ config.source ??= {};
803
+ if ('string' == typeof config.source.preEntry) config.source.preEntry = [
804
+ config.source.preEntry
805
+ ];
806
+ else config.source.preEntry ??= [];
807
+ config.source.preEntry.push(node_path.resolve(__dirname, './polyfills'));
808
+ if (buildOptions.i18n.hasDefinedSourceLocale) config.source.preEntry.push('@angular/localize/init');
809
+ const polyfills = buildOptions.polyfills;
810
+ if (polyfills) config.source.preEntry.push(...polyfills);
811
+ config.source.preEntry.push(...buildOptions.styles);
812
+ config.source.tsconfigPath = buildOptions.tsconfig;
813
+ config.source.define ??= {};
814
+ config.source.define['__LYNX_SOURCE_LOCALE__'] = JSON.stringify(buildOptions.i18n.sourceLocale);
815
+ const isProd = 'production' === process.env.NODE_ENV || 'production' === config.mode;
816
+ if (isProd) config.source.define.ngDevMode = false;
817
+ });
818
+ };
819
+ const build_internals_require = createRequire(import.meta.url);
820
+ const angularBuildRoot = node_path.dirname(build_internals_require.resolve('@angular/build/package.json'));
821
+ const loadAngularBuildInternal = (subpath)=>build_internals_require(node_path.join(angularBuildRoot, subpath));
822
+ const { createAngularCompilation: createAngularCompilation, DiagnosticModes: DiagnosticModes } = loadAngularBuildInternal('src/tools/angular/compilation');
823
+ const { JavaScriptTransformer: JavaScriptTransformer } = loadAngularBuildInternal("src/tools/javascript-transformer");
824
+ const { ComponentStylesheetBundler: ComponentStylesheetBundler } = loadAngularBuildInternal('src/tools/esbuild/angular/component-stylesheets');
825
+ const isPresent = (variable)=>'string' == typeof variable && '' !== variable;
826
+ const isDisabled = (variable)=>'0' === variable || 'false' === variable.toLowerCase();
827
+ const maxWorkersVariable = process.env.NG_BUILD_MAX_WORKERS;
828
+ const maxWorkers = isPresent(maxWorkersVariable) ? +maxWorkersVariable : Math.min(4, Math.max(availableParallelism() - 1, 1));
829
+ const typeCheckingVariable = process.env.NG_BUILD_TYPE_CHECK;
272
830
  const useTypeChecking = !isPresent(typeCheckingVariable) || !isDisabled(typeCheckingVariable);
831
+ /**
832
+ * @license
833
+ * Copyright Google LLC All Rights Reserved.
834
+ *
835
+ * Use of this source code is governed by an MIT-style license that can be
836
+ * found in the LICENSE file at https://angular.dev/license
837
+ */ const findUp = (names, from)=>{
838
+ const namesList = Array.isArray(names) ? names : [
839
+ names
840
+ ];
841
+ const root = __rspack_external_node_path_c5b9b54f.parse(from).root;
842
+ let currentDir = from;
843
+ while(currentDir && currentDir !== root){
844
+ for (const name of namesList){
845
+ const p = __rspack_external_node_path_c5b9b54f.join(currentDir, name);
846
+ if (existsSync(p)) return p;
847
+ }
848
+ currentDir = __rspack_external_node_path_c5b9b54f.dirname(currentDir);
849
+ }
850
+ return null;
851
+ };
852
+ const configNames = [
853
+ 'angular.json',
854
+ '.angular.json'
855
+ ];
856
+ const getAngularWorkspace = async (from = process.cwd())=>{
857
+ const host = workspaces.createWorkspaceHost(new NodeJsSyncHost());
858
+ const workspaceFile = findUp(configNames, from);
859
+ if (!workspaceFile) throw new Error("couldn't find workspace file");
860
+ const { workspace } = await workspaces.readWorkspace(workspaceFile, host);
861
+ return {
862
+ basePath: node_path.dirname(workspaceFile),
863
+ workspace
864
+ };
865
+ };
866
+ const findProjectByPath = (workspace, location, basePath)=>{
867
+ const isInside = (base, potential)=>{
868
+ const absoluteBase = node_path.resolve(basePath, base);
869
+ const absolutePotential = node_path.resolve(basePath, potential);
870
+ const relativePotential = node_path.relative(absoluteBase, absolutePotential);
871
+ if (!relativePotential.startsWith('..') && !node_path.isAbsolute(relativePotential)) return true;
872
+ return false;
873
+ };
874
+ const projects = Array.from(workspace.projects).map(([name, project])=>[
875
+ project.root,
876
+ name
877
+ ]).filter((tuple)=>isInside(tuple[0], location)).sort((a, b)=>b[0].length - a[0].length);
878
+ if (0 === projects.length) return null;
879
+ if (projects.length > 1) {
880
+ const found = new Set();
881
+ const sameRoots = projects.filter((v)=>{
882
+ if (!found.has(v[0])) {
883
+ found.add(v[0]);
884
+ return false;
885
+ }
886
+ return true;
887
+ });
888
+ if (sameRoots.length > 0) return null;
889
+ }
890
+ return projects[0][1];
891
+ };
892
+ const getProjectByCwd = (workspace, basePath)=>{
893
+ if (1 === workspace.projects.size) return Array.from(workspace.projects.keys())[0];
894
+ const project = findProjectByPath(workspace, process.cwd(), basePath);
895
+ if (project) return project;
896
+ return null;
897
+ };
273
898
  /**
274
899
  * @license
275
900
  * Copyright Google LLC All Rights Reserved.
@@ -338,29 +963,37 @@ const normalizeOptimization = (optimization = true)=>{
338
963
  };
339
964
  };
340
965
  const readTarget = (project, target = 'build')=>project.targets.get(target);
341
- const readBuildOptions = async (project, basePath, configurationName)=>{
966
+ const resolveIndexPath = (index, project, workspaceRoot)=>{
967
+ if ('boolean' == typeof index) return;
968
+ if ('string' == typeof index) return node_path.join(workspaceRoot, index);
969
+ if (index && 'object' == typeof index) return node_path.join(workspaceRoot, index.input);
970
+ const sourceRoot = project.sourceRoot ?? node_path.join(project.root ?? '', 'src');
971
+ return node_path.join(workspaceRoot, sourceRoot, 'index.html');
972
+ };
973
+ const resolveOutputPath = (outputPath, workspaceRoot)=>{
974
+ if ('string' == typeof outputPath) return node_path.join(workspaceRoot, outputPath);
975
+ if (outputPath && 'object' == typeof outputPath) return node_path.join(workspaceRoot, outputPath.base);
976
+ };
977
+ const readBuildTargetOptions = (project, configurationName)=>{
978
+ const target = readTarget(project);
979
+ if (!target) throw new Error("couldn't find target");
980
+ const resolvedConfigurationName = configurationName ?? target.defaultConfiguration;
981
+ const buildOptions = target.options;
982
+ if (!buildOptions) throw new Error(`No build options found for the "${configurationName ?? 'build'}" target in angular.json. Make sure the target has an "options" section with at least "browser" and "tsConfig" defined.`);
983
+ return {
984
+ ...buildOptions,
985
+ ...resolvedConfigurationName ? target.configurations?.[resolvedConfigurationName] : void 0
986
+ };
987
+ };
988
+ const readBuildOptions = async (project, basePath, configurationName)=>normalizeBuildOptions(readBuildTargetOptions(project, configurationName), project, basePath);
989
+ const normalizeBuildOptions = async (buildOptions, project, basePath)=>{
342
990
  const workspaceRoot = basePath;
343
- const target = readTarget(project);
344
- if (!target) throw new Error("couldn't find target");
345
- const resolvedConfigurationName = configurationName ?? target.defaultConfiguration;
346
- const buildOptions = target?.options;
347
- if (!buildOptions) throw new Error(`No build options found for the "${configurationName ?? 'build'}" target in angular.json. Make sure the target has an "options" section with at least "browser", "tsConfig", and "index" defined.`);
348
- const configurations = target.configurations;
349
- if (configurations) {
350
- for(const name in configurations)if (name === resolvedConfigurationName) {
351
- Object.assign(buildOptions, {
352
- ...configurations[name]
353
- });
354
- break;
355
- }
356
- }
357
- node_assert(!!buildOptions, 'build options is undefined');
358
991
  const aot = buildOptions.aot ?? true;
359
992
  const tsconfig = node_path.join(workspaceRoot, buildOptions.tsConfig);
360
993
  const optimizationOptions = normalizeOptimization(buildOptions.optimization);
361
994
  const sourcemapOptions = normalizeSourceMaps(buildOptions.sourceMap ?? false);
362
995
  const browser = node_path.join(workspaceRoot, buildOptions.browser);
363
- const index = node_path.join(workspaceRoot, buildOptions.index);
996
+ const index = resolveIndexPath(buildOptions.index, project, workspaceRoot);
364
997
  let polyfills = buildOptions.polyfills;
365
998
  polyfills = void 0 === polyfills || Array.isArray(polyfills) ? polyfills : [
366
999
  polyfills
@@ -387,7 +1020,7 @@ const readBuildOptions = async (project, basePath, configurationName)=>{
387
1020
  const cacheOptions = normalizeCacheOptions(buildOptions.projectMetadata, workspaceRoot);
388
1021
  const inlineStyleLanguage = buildOptions.inlineStyleLanguage;
389
1022
  const styles = (buildOptions.styles ?? []).map((stylePath)=>node_path.join(workspaceRoot, stylePath));
390
- const outputPath = node_path.join(workspaceRoot, buildOptions.outputPath);
1023
+ const outputPath = resolveOutputPath(buildOptions.outputPath, workspaceRoot);
391
1024
  const i18n = normalizeI18nOptions(project.extensions['i18n']);
392
1025
  const i18nMissingTranslation = buildOptions.i18nMissingTranslation ?? 'warning';
393
1026
  const normalizedOptions = {
@@ -408,7 +1041,8 @@ const readBuildOptions = async (project, basePath, configurationName)=>{
408
1041
  outputHashing,
409
1042
  styles,
410
1043
  i18n,
411
- i18nMissingTranslation
1044
+ i18nMissingTranslation,
1045
+ budgets: buildOptions.budgets ?? []
412
1046
  };
413
1047
  return normalizedOptions;
414
1048
  };
@@ -432,73 +1066,20 @@ const normalizeI18nOptions = (raw)=>{
432
1066
  hasDefinedSourceLocale
433
1067
  };
434
1068
  };
435
- /**
436
- * @license
437
- * Copyright Google LLC All Rights Reserved.
438
- *
439
- * Use of this source code is governed by an MIT-style license that can be
440
- * found in the LICENSE file at https://angular.dev/license
441
- */ const findUp = (names, from)=>{
442
- const namesList = Array.isArray(names) ? names : [
443
- names
444
- ];
445
- const root = __rspack_external_node_path_c5b9b54f.parse(from).root;
446
- let currentDir = from;
447
- while(currentDir && currentDir !== root){
448
- for (const name of namesList){
449
- const p = __rspack_external_node_path_c5b9b54f.join(currentDir, name);
450
- if (existsSync(p)) return p;
451
- }
452
- currentDir = __rspack_external_node_path_c5b9b54f.dirname(currentDir);
453
- }
454
- return null;
455
- };
456
- const configNames = [
457
- 'angular.json',
458
- '.angular.json'
459
- ];
460
- const findWorkspaceFile = ()=>findUp(configNames, process.cwd());
461
- const getAngularWorkspace = async ()=>{
462
- const host = workspaces.createWorkspaceHost(new NodeJsSyncHost());
463
- const workspaceFile = findWorkspaceFile();
464
- if (!workspaceFile) throw new Error("couldn't find workspace file");
465
- const { workspace } = await workspaces.readWorkspace(workspaceFile, host);
1069
+ const resolvePrimaryBuild = async ({ workspace, basePath, pages, cli })=>{
1070
+ let projectName;
1071
+ if (pages) {
1072
+ const candidates = 'all' === pages ? Array.from(workspace.projects.entries()).filter(([, project])=>'application' === project.extensions['projectType']).map(([name])=>name) : pages;
1073
+ projectName = cli && candidates.includes(cli.project) ? cli.project : candidates[0] ?? null;
1074
+ } else projectName = cli?.project ?? getProjectByCwd(workspace, basePath);
1075
+ if (!projectName) throw new Error("couldn't find the project");
1076
+ const project = workspace.projects.get(projectName);
1077
+ if (!project) throw new Error(`Project "${projectName}" not found in workspace`);
1078
+ const buildOptions = cli?.project === projectName ? await normalizeBuildOptions(cli.buildOptions, project, basePath) : await readBuildOptions(project, basePath);
466
1079
  return {
467
- basePath: node_path.dirname(workspaceFile),
468
- workspace
469
- };
470
- };
471
- const findProjectByPath = (workspace, location, basePath)=>{
472
- const isInside = (base, potential)=>{
473
- const absoluteBase = node_path.resolve(basePath, base);
474
- const absolutePotential = node_path.resolve(basePath, potential);
475
- const relativePotential = node_path.relative(absoluteBase, absolutePotential);
476
- if (!relativePotential.startsWith('..') && !node_path.isAbsolute(relativePotential)) return true;
477
- return false;
1080
+ projectName,
1081
+ buildOptions
478
1082
  };
479
- const projects = Array.from(workspace.projects).map(([name, project])=>[
480
- project.root,
481
- name
482
- ]).filter((tuple)=>isInside(tuple[0], location)).sort((a, b)=>b[0].length - a[0].length);
483
- if (0 === projects.length) return null;
484
- if (projects.length > 1) {
485
- const found = new Set();
486
- const sameRoots = projects.filter((v)=>{
487
- if (!found.has(v[0])) {
488
- found.add(v[0]);
489
- return false;
490
- }
491
- return true;
492
- });
493
- if (sameRoots.length > 0) return null;
494
- }
495
- return projects[0][1];
496
- };
497
- const getProjectByCwd = (workspace, basePath)=>{
498
- if (1 === workspace.projects.size) return Array.from(workspace.projects.keys())[0];
499
- const project = findProjectByPath(workspace, process.cwd(), basePath);
500
- if (project) return project;
501
- return null;
502
1083
  };
503
1084
  const resolvePages = (workspace, basePath, pages)=>{
504
1085
  const projectNames = 'all' === pages ? Array.from(workspace.projects.entries()).filter(([, def])=>'application' === def.extensions['projectType']).map(([name])=>name) : pages;
@@ -618,668 +1199,393 @@ const collectElementTags = (code)=>{
618
1199
  const tags = new Set();
619
1200
  let match;
620
1201
  let tagMatch;
621
- for (const chain of code.matchAll(chainPattern)){
622
- tagExtractor.lastIndex = 0;
623
- while(null !== (tagMatch = tagExtractor.exec(chain[0])))tags.add(tagMatch[1]);
624
- }
625
- while(null !== (match = repeaterPattern.exec(code)))tags.add(match[1]);
626
- return tags;
627
- };
628
- const scanCompiledOutputForHtmlElements = (fileCache)=>{
629
- const diagnostics = [];
630
- for (const [file, contents] of fileCache){
631
- if (file.includes('node_modules')) continue;
632
- const code = 'string' == typeof contents ? contents : Buffer.from(contents).toString();
633
- const tags = collectElementTags(code);
634
- const fileName = node_path.relative(process.cwd(), file);
635
- for (const tag of tags){
636
- if (!(tag in HTML_ELEMENT_SUGGESTIONS)) continue;
637
- const replacement = HTML_ELEMENT_SUGGESTIONS[tag];
638
- const message = null !== replacement ? `${fileName}: <${tag}> is not a Lynx element. Use <${replacement}> instead.` : `${fileName}: <${tag}> has no Lynx equivalent and will not render correctly.`;
639
- diagnostics.push({
640
- file,
641
- message,
642
- category: 'html-element'
643
- });
644
- }
645
- }
646
- return diagnostics;
647
- };
648
- const scanSourcesForUnsupportedPatterns = (fileNames)=>{
649
- const diagnostics = [];
650
- for (const filePath of fileNames){
651
- if (filePath.includes('node_modules')) continue;
652
- let source;
653
- try {
654
- source = node_fs.readFileSync(filePath, 'utf-8');
655
- } catch {
656
- continue;
657
- }
658
- if (source.includes('ViewEncapsulation.ShadowDom')) {
659
- const fileName = node_path.relative(process.cwd(), filePath);
660
- diagnostics.push({
661
- file: filePath,
662
- message: `${fileName}: ViewEncapsulation.ShadowDom is not supported in Lynx (falls back to None). Use Emulated instead.`,
663
- category: 'structural'
664
- });
665
- }
666
- }
667
- return diagnostics;
668
- };
669
- const scanCompiledOutputForStructuralIssues = (fileCache)=>{
670
- const diagnostics = [];
671
- for (const [file, contents] of fileCache){
672
- if (file.includes('node_modules')) continue;
673
- const code = 'string' == typeof contents ? contents : Buffer.from(contents).toString();
674
- const tags = collectElementTags(code);
675
- const fileName = node_path.relative(process.cwd(), file);
676
- if (tags.has('list-item') && !tags.has('list')) diagnostics.push({
677
- file,
678
- message: `${fileName}: <list-item> used without a <list> parent. Wrap list items in a <list> element.`,
679
- category: 'structural'
680
- });
681
- }
682
- return diagnostics;
683
- };
684
- const scanSourcesForUnsupportedCss = (fileNames)=>{
685
- const diagnostics = [];
686
- for (const filePath of fileNames){
687
- if (filePath.includes('node_modules')) continue;
688
- let source;
689
- try {
690
- source = node_fs.readFileSync(filePath, 'utf-8');
691
- } catch {
692
- continue;
693
- }
694
- if (!source.includes('@Component')) continue;
695
- const cssChunks = [];
696
- const stylesBlockMatch = source.match(/styles\s*:\s*\[([\s\S]*?)\]/);
697
- if (stylesBlockMatch) {
698
- for (const m of stylesBlockMatch[1].matchAll(/`([\s\S]*?)`/g))cssChunks.push(m[1]);
699
- if (0 === cssChunks.length) for (const m of stylesBlockMatch[1].matchAll(/(['"])([\s\S]*?)\1/g))cssChunks.push(m[2]);
700
- }
701
- const styleUrlsMatch = source.match(/styleUrls?\s*:\s*\[([\s\S]*?)\]/);
702
- if (styleUrlsMatch) for (const m of styleUrlsMatch[1].matchAll(/['"]([^'"]+)['"]/g)){
703
- const stylePath = node_path.resolve(node_path.dirname(filePath), m[1]);
704
- try {
705
- cssChunks.push(node_fs.readFileSync(stylePath, 'utf-8'));
706
- } catch {}
707
- }
708
- const seen = new Set();
709
- const fileName = node_path.relative(process.cwd(), filePath);
710
- for (const css of cssChunks){
711
- const withoutComments = css.replace(/\/\*[\s\S]*?\*\//g, '').replace(/\/\/.*/g, '');
712
- const propertyPattern = /(?:^|[{;])\s*([a-z-]+)\s*:/gm;
713
- let match;
714
- while(null !== (match = propertyPattern.exec(withoutComments))){
715
- const property = match[1];
716
- if (UNSUPPORTED_CSS[property] && !seen.has(property)) {
717
- seen.add(property);
718
- diagnostics.push({
719
- file: filePath,
720
- message: `${fileName}: '${property}' — ${UNSUPPORTED_CSS[property]}.`,
721
- category: 'css'
722
- });
723
- }
724
- }
725
- }
726
- }
727
- return diagnostics;
728
- };
729
- const reportLynxDiagnostics = (diagnostics)=>{
730
- if (0 === diagnostics.length) return;
731
- const htmlWarnings = diagnostics.filter((d)=>'html-element' === d.category);
732
- const structuralWarnings = diagnostics.filter((d)=>'structural' === d.category);
733
- const cssWarnings = diagnostics.filter((d)=>'css' === d.category);
734
- if (htmlWarnings.length > 0) {
735
- console.warn('[Lynx] HTML elements detected in templates:');
736
- for (const w of htmlWarnings)console.warn(` ${w.message}`);
737
- }
738
- if (structuralWarnings.length > 0) {
739
- console.warn('[Lynx] Structural issues:');
740
- for (const w of structuralWarnings)console.warn(` ${w.message}`);
741
- }
742
- if (cssWarnings.length > 0) {
743
- console.warn('[Lynx] Unsupported CSS properties:');
744
- for (const w of cssWarnings)console.warn(` ${w.message}`);
745
- }
746
- };
747
- const applyAngularRules = async (api, pluginOptions)=>{
748
- const { basePath, workspace } = await getAngularWorkspace();
749
- if (pluginOptions.pages) {
750
- const pages = resolvePages(workspace, basePath, pluginOptions.pages);
751
- api.modifyRsbuildConfig((config)=>{
752
- config.source ??= {};
753
- config.source.entry = Object.fromEntries(pages.map((page)=>[
754
- page.name,
755
- page.browser
756
- ]));
757
- });
758
- }
759
- const primaryProjectName = pluginOptions.pages ? (Array.isArray(pluginOptions.pages) ? pluginOptions.pages[0] : Array.from(workspace.projects.entries()).find(([, def])=>'application' === def.extensions['projectType'])?.[0]) ?? null : getProjectByCwd(workspace, basePath);
760
- if (!primaryProjectName) throw new Error("couldn't find the project");
761
- const projectDefinition = workspace.projects.get(primaryProjectName);
762
- if (!projectDefinition) throw new Error(`Project "${primaryProjectName}" not found in workspace`);
763
- const buildOptions = await readBuildOptions(projectDefinition, basePath);
764
- applyAngularConfig(api, buildOptions);
765
- const sourcemap = !!(!!buildOptions.sourcemapOptions.scripts && (buildOptions.sourcemapOptions.hidden ? 'external' : true));
766
- const thirdPartySourcemaps = buildOptions.sourcemapOptions.vendor;
767
- const advancedOptimizations = buildOptions.advancedOptimizations;
768
- const aot = buildOptions.aot;
769
- const javascriptTransformer = new JavaScriptTransformer({
770
- sourcemap,
771
- thirdPartySourcemaps,
772
- advancedOptimizations,
773
- jit: !aot,
774
- maxConcurrency: maxWorkers
775
- }, void 0);
776
- const tsconfig = buildOptions.tsconfig;
777
- const compilation = await createAngularCompilation(false, aot);
778
- const typeScriptFileCache = new Map();
779
- const scopedCssCacheDir = node_path.join(basePath, 'node_modules', '.cache', 'angular-lynx-css');
780
- node_fs.mkdirSync(scopedCssCacheDir, {
781
- recursive: true
782
- });
783
- const componentStylesCache = new Map();
784
- const componentScopeIds = new Map();
785
- let componentResourceDependencies = new Map();
786
- const writtenStylesheets = new Set();
787
- let knownSourceFiles = new Set();
788
- const transformStylesheet = createTransformStylesheet({
789
- basePath,
790
- scopedCssCacheDir,
791
- componentStylesCache,
792
- componentScopeIds,
793
- onStylesheetWritten: (filePath)=>writtenStylesheets.add(filePath)
794
- });
795
- const compileAngular = async (changedFiles)=>{
796
- const { sourceFileCache, fileNames } = buildLynxSchemaSourceFileCache(tsconfig, changedFiles && ((file)=>changedFiles.has(node_path.normalize(file)) || !knownSourceFiles.has(file)));
797
- knownSourceFiles = new Set(fileNames);
798
- if (changedFiles) await compilation.update?.(new Set(changedFiles));
799
- const seeded = seedCompilationSourceFiles(compilation, sourceFileCache);
800
- const seededFiles = new Set(seeded ? [
801
- ...sourceFileCache.keys()
802
- ].map((file)=>node_path.normalize(file)) : []);
803
- writtenStylesheets.clear();
804
- let result;
805
- try {
806
- result = await compilation.initialize(tsconfig, {
807
- processWebWorker: (workerFile, _containingFile)=>workerFile,
808
- transformStylesheet,
809
- modifiedFiles: changedFiles && new Set([
810
- ...changedFiles
811
- ].filter((file)=>!seededFiles.has(file)))
812
- }, {
813
- sourcemap: !!sourcemap,
814
- preserveSymlinks: false
1202
+ for (const chain of code.matchAll(chainPattern)){
1203
+ tagExtractor.lastIndex = 0;
1204
+ while(null !== (tagMatch = tagExtractor.exec(chain[0])))tags.add(tagMatch[1]);
1205
+ }
1206
+ while(null !== (match = repeaterPattern.exec(code)))tags.add(match[1]);
1207
+ return tags;
1208
+ };
1209
+ const scanCompiledOutputForHtmlElements = (fileCache)=>{
1210
+ const diagnostics = [];
1211
+ for (const [file, contents] of fileCache){
1212
+ if (file.includes('node_modules')) continue;
1213
+ const code = 'string' == typeof contents ? contents : Buffer.from(contents).toString();
1214
+ const tags = collectElementTags(code);
1215
+ const fileName = node_path.relative(process.cwd(), file);
1216
+ for (const tag of tags){
1217
+ if (!(tag in HTML_ELEMENT_SUGGESTIONS)) continue;
1218
+ const replacement = HTML_ELEMENT_SUGGESTIONS[tag];
1219
+ const message = null !== replacement ? `${fileName}: <${tag}> is not a Lynx element. Use <${replacement}> instead.` : `${fileName}: <${tag}> has no Lynx equivalent and will not render correctly.`;
1220
+ diagnostics.push({
1221
+ file,
1222
+ message,
1223
+ category: 'html-element'
815
1224
  });
816
- } catch (error) {
817
- throw new Error(`Angular compilation failed to initialize for "${tsconfig}". This often means a compiler worker crashed under resource pressure — lower NG_BUILD_MAX_WORKERS or reduce parallel build concurrency. Original error: ${error instanceof Error ? error.stack ?? error.message : String(error)}`);
818
1225
  }
819
- componentResourceDependencies = new Map([
820
- ...result.componentResourcesDependencies ?? []
821
- ].map(([file, dependencies])=>[
822
- node_path.normalize(file),
823
- dependencies.map((dependency)=>node_path.normalize(dependency))
824
- ]));
825
- const changedOutputs = new Set();
1226
+ }
1227
+ return diagnostics;
1228
+ };
1229
+ const scanSourcesForUnsupportedPatterns = (fileNames)=>{
1230
+ const diagnostics = [];
1231
+ for (const filePath of fileNames){
1232
+ if (filePath.includes('node_modules')) continue;
1233
+ let source;
826
1234
  try {
827
- for (const { filename, contents } of (await compilation.emitAffectedFiles())){
828
- const file = node_path.normalize(filename);
829
- if (typeScriptFileCache.get(file) !== contents) changedOutputs.add(file);
830
- typeScriptFileCache.set(file, contents);
831
- }
832
- } catch (error) {
833
- throw new Error(`Angular failed to emit compiled output for "${tsconfig}". Original error: ${error instanceof Error ? error.stack ?? error.message : String(error)}`);
834
- }
835
- for (const file of writtenStylesheets)changedOutputs.add(file);
836
- for (const file of changedFiles ?? [])if (!node_fs.existsSync(file)) typeScriptFileCache.delete(file);
837
- reportLynxDiagnostics([
838
- ...scanCompiledOutputForHtmlElements(typeScriptFileCache),
839
- ...scanCompiledOutputForStructuralIssues(typeScriptFileCache),
840
- ...scanSourcesForUnsupportedPatterns(fileNames),
841
- ...scanSourcesForUnsupportedCss(fileNames)
842
- ]);
843
- const diagnostics = await compilation.diagnoseFiles(useTypeChecking ? DiagnosticModes.All : DiagnosticModes.All & ~DiagnosticModes.Semantic);
844
- const actionableErrors = diagnostics.errors?.filter((e)=>!isLynxUnknownElementMessage(e.text));
845
- const actionableWarnings = diagnostics.warnings?.filter((w)=>!isLynxUnknownElementMessage(w.text));
846
- if (actionableErrors?.length || actionableWarnings?.length) console.log({
847
- errors: actionableErrors,
848
- warnings: actionableWarnings
849
- });
850
- return {
851
- changedOutputs,
852
- inputFiles: result.referencedFiles.map((file)=>node_path.normalize(file))
853
- };
854
- };
855
- const coordinator = createAngularRebuildCoordinator({
856
- compile: compileAngular
857
- });
858
- const compilersByEnvironment = new Map();
859
- api.onAfterCreateCompiler(({ compiler })=>{
860
- const compilers = 'compilers' in compiler ? compiler.compilers : [
861
- compiler
862
- ];
863
- for (const environmentCompiler of compilers)if (environmentCompiler.name) compilersByEnvironment.set(environmentCompiler.name, environmentCompiler);
864
- });
865
- let compilationClosed = false;
866
- api.onBeforeEnvironmentCompile(async ({ environment, isWatch })=>{
867
- const compiler = compilersByEnvironment.get(environment.name);
868
- const outdatedFiles = await coordinator.prepare(environment.name, [
869
- ...compiler?.modifiedFiles ?? [],
870
- ...compiler?.removedFiles ?? []
871
- ]);
872
- if (compiler && outdatedFiles.size > 0) compiler.modifiedFiles = new Set([
873
- ...compiler.modifiedFiles ?? [],
874
- ...outdatedFiles
875
- ]);
876
- if (!isWatch && !compilationClosed) {
877
- compilationClosed = true;
878
- await compilation.close?.();
1235
+ source = node_fs.readFileSync(filePath, 'utf-8');
1236
+ } catch {
1237
+ continue;
879
1238
  }
880
- });
881
- api.transform({
882
- test: /\.[cm]?[jt]sx?$/
883
- }, async (context)=>{
884
- const isJs = /\.[cm]?js$/.test(context.resourcePath);
885
- if (isJs) {
886
- const contents = await javascriptTransformer.transformData(context.resourcePath, context.code, {
887
- skipLinker: false,
888
- sideEffects: ()=>Promise.resolve(false)
1239
+ if (source.includes('ViewEncapsulation.ShadowDom')) {
1240
+ const fileName = node_path.relative(process.cwd(), filePath);
1241
+ diagnostics.push({
1242
+ file: filePath,
1243
+ message: `${fileName}: ViewEncapsulation.ShadowDom is not supported in Lynx (falls back to None). Use Emulated instead.`,
1244
+ category: 'structural'
889
1245
  });
890
- return {
891
- code: transformWorklets(Buffer.from(contents).toString(), context.resourcePath)
892
- };
893
1246
  }
894
- const content = typeScriptFileCache.get(context.resourcePath);
895
- if (!content) throw new Error(`No compiled output found for ${context.resourcePath}`);
896
- for (const dependency of componentResourceDependencies.get(context.resourcePath) ?? [])context.addDependency(dependency);
897
- const code = 'string' == typeof content ? content : Buffer.from(content).toString();
898
- return {
899
- code: buildTransformedCode({
900
- code,
901
- resourcePath: context.resourcePath,
902
- componentStyles: componentStylesCache.get(context.resourcePath),
903
- scopeInfo: componentScopeIds.get(context.resourcePath)
904
- })
905
- };
906
- });
907
- };
908
- const LAYERS = {
909
- BACKGROUND: 'background',
910
- MAIN_THREAD: 'main'
1247
+ }
1248
+ return diagnostics;
911
1249
  };
912
- const applyLayers = (api)=>{
913
- api.modifyBundlerChain((chain)=>{
914
- chain.module.rule("typescript").oneOf(LAYERS.BACKGROUND).layer(LAYERS.BACKGROUND).test(/\.[cm]?[jt]sx?$/).use('builtin:swc-loader').loader('builtin:swc-loader').options({
915
- jsc: {
916
- target: 'es2015',
917
- parser: {
918
- syntax: "typescript"
919
- }
920
- }
921
- }).end();
922
- chain.module.rule("typescript").oneOf(LAYERS.MAIN_THREAD).layer(LAYERS.MAIN_THREAD).test(/\.[cm]?[jt]sx?$/).use('builtin:swc-loader').loader('builtin:swc-loader').options({
923
- jsc: {
924
- target: 'es2019',
925
- parser: {
926
- syntax: "typescript"
927
- }
928
- }
929
- }).end();
930
- });
1250
+ const scanCompiledOutputForStructuralIssues = (fileCache)=>{
1251
+ const diagnostics = [];
1252
+ for (const [file, contents] of fileCache){
1253
+ if (file.includes('node_modules')) continue;
1254
+ const code = 'string' == typeof contents ? contents : Buffer.from(contents).toString();
1255
+ const tags = collectElementTags(code);
1256
+ const fileName = node_path.relative(process.cwd(), file);
1257
+ if (tags.has('list-item') && !tags.has('list')) diagnostics.push({
1258
+ file,
1259
+ message: `${fileName}: <list-item> used without a <list> parent. Wrap list items in a <list> element.`,
1260
+ category: 'structural'
1261
+ });
1262
+ }
1263
+ return diagnostics;
931
1264
  };
932
- const applyCSS = (api, options)=>{
933
- const { enableRemoveCSSScope, enableCSSSelector, enableCSSInvalidation, targetSdkVersion } = options;
934
- api.modifyRsbuildConfig((config, { mergeRsbuildConfig })=>mergeRsbuildConfig(config, {
935
- output: {
936
- injectStyles: false
937
- }
938
- }));
939
- const __dirname = node_path.dirname(fileURLToPath(import.meta.url));
940
- api.modifyBundlerChain(async (chain, { CHAIN_ID, environment })=>{
941
- const { CssExtractRspackPlugin } = await import("@lynx-js/css-extract-webpack-plugin");
942
- const removeLightningCSS = (rule)=>{
943
- if (rule.uses.has(CHAIN_ID.USE.LIGHTNINGCSS) && 'lynx' === environment.name) rule.uses.delete(CHAIN_ID.USE.LIGHTNINGCSS);
944
- };
945
- const cssRules = [
946
- CHAIN_ID.RULE.CSS,
947
- CHAIN_ID.RULE.SASS,
948
- CHAIN_ID.RULE.LESS,
949
- CHAIN_ID.RULE.STYLUS
950
- ];
951
- for (const ruleName of cssRules.filter((rule)=>chain.module.rules.has(rule))){
952
- const rule = chain.module.rule(ruleName);
953
- const mainRuleName = ruleName === CHAIN_ID.RULE.CSS ? CHAIN_ID.ONE_OF.CSS_MAIN : ruleName;
954
- const mainRule = rule.oneOf(mainRuleName);
955
- const parentRuleEntries = rule.entries();
956
- removeLightningCSS(mainRule);
957
- mainRule.issuerLayer(LAYERS.BACKGROUND).use(CHAIN_ID.USE.MINI_CSS_EXTRACT).loader(CssExtractRspackPlugin.loader).end();
958
- const uses = mainRule.uses.entries() ?? {};
959
- const ruleEntries = mainRule.entries();
960
- const cssLoader = uses[CHAIN_ID.USE.CSS];
961
- if (!cssLoader) continue;
962
- const cssLoaderRule = cssLoader.entries();
963
- const mainThreadLayerRule = chain.module.rule(`${ruleName}:${LAYERS.MAIN_THREAD}`).test(parentRuleEntries.test).merge(ruleEntries).issuerLayer(LAYERS.MAIN_THREAD);
964
- if (void 0 !== parentRuleEntries.dependency) mainThreadLayerRule.merge({
965
- dependency: parentRuleEntries.dependency
966
- });
967
- mainThreadLayerRule.use(CHAIN_ID.USE.IGNORE_CSS).loader(node_path.resolve(__dirname, './loaders/ignore-css-loader')).end().uses.merge(uses).delete(CHAIN_ID.USE.MINI_CSS_EXTRACT).delete(CHAIN_ID.USE.LIGHTNINGCSS).delete(CHAIN_ID.USE.CSS).end().use(CHAIN_ID.USE.CSS).after(CHAIN_ID.USE.IGNORE_CSS).merge(cssLoaderRule).options(normalizeCssLoaderOptions(cssLoaderRule.options, true)).end();
1265
+ const scanSourcesForUnsupportedCss = (fileNames)=>{
1266
+ const diagnostics = [];
1267
+ for (const filePath of fileNames){
1268
+ if (filePath.includes('node_modules')) continue;
1269
+ let source;
1270
+ try {
1271
+ source = node_fs.readFileSync(filePath, 'utf-8');
1272
+ } catch {
1273
+ continue;
968
1274
  }
969
- for (const ruleName of cssRules.filter((rule)=>chain.module.rules.has(rule))){
970
- const inlineRuleName = ruleName === CHAIN_ID.RULE.CSS ? CHAIN_ID.ONE_OF.CSS_INLINE : `${ruleName}-inline`;
971
- removeLightningCSS(chain.module.rule(ruleName).oneOf(inlineRuleName));
1275
+ if (!source.includes('@Component')) continue;
1276
+ const cssChunks = [];
1277
+ const stylesBlockMatch = source.match(/styles\s*:\s*\[([\s\S]*?)\]/);
1278
+ if (stylesBlockMatch) {
1279
+ for (const m of stylesBlockMatch[1].matchAll(/`([\s\S]*?)`/g))cssChunks.push(m[1]);
1280
+ if (0 === cssChunks.length) for (const m of stylesBlockMatch[1].matchAll(/(['"])([\s\S]*?)\1/g))cssChunks.push(m[2]);
972
1281
  }
973
- if ('lynx' === environment.name && chain.module.rules.has(CHAIN_ID.RULE.FONT)) {
974
- const fontRule = chain.module.rule(CHAIN_ID.RULE.FONT);
975
- fontRule.oneOfs.clear();
976
- fontRule.type('asset/inline');
1282
+ const styleUrlsMatch = source.match(/styleUrls?\s*:\s*\[([\s\S]*?)\]/);
1283
+ if (styleUrlsMatch) for (const m of styleUrlsMatch[1].matchAll(/['"]([^'"]+)['"]/g)){
1284
+ const stylePath = node_path.resolve(node_path.dirname(filePath), m[1]);
1285
+ try {
1286
+ cssChunks.push(node_fs.readFileSync(stylePath, 'utf-8'));
1287
+ } catch {}
977
1288
  }
978
- chain.plugin(CHAIN_ID.PLUGIN.MINI_CSS_EXTRACT).tap(([options])=>[
979
- {
980
- ...options,
981
- enableRemoveCSSScope,
982
- enableCSSSelector,
983
- enableCSSInvalidation,
984
- targetSdkVersion,
985
- cssPlugins: [
986
- CSSPlugins.parserPlugins.removeFunctionWhiteSpace()
987
- ]
988
- }
989
- ]).init((_, args)=>new CssExtractRspackPlugin(...args)).end().end();
990
- chain.module.when(void 0 === enableRemoveCSSScope, (module)=>module.rule('lynx.css.scoped').test(/\.css$/).resourceQuery({
991
- and: [
992
- /cssId/,
993
- {
994
- not: /common/
995
- }
996
- ]
997
- }).sideEffects(false));
998
- });
1289
+ const seen = new Set();
1290
+ const fileName = node_path.relative(process.cwd(), filePath);
1291
+ for (const css of cssChunks){
1292
+ const withoutComments = css.replace(/\/\*[\s\S]*?\*\//g, '').replace(/\/\/.*/g, '');
1293
+ const propertyPattern = /(?:^|[{;])\s*([a-z-]+)\s*:/gm;
1294
+ let match;
1295
+ while(null !== (match = propertyPattern.exec(withoutComments))){
1296
+ const property = match[1];
1297
+ if (UNSUPPORTED_CSS[property] && !seen.has(property)) {
1298
+ seen.add(property);
1299
+ diagnostics.push({
1300
+ file: filePath,
1301
+ message: `${fileName}: '${property}' — ${UNSUPPORTED_CSS[property]}.`,
1302
+ category: 'css'
1303
+ });
1304
+ }
1305
+ }
1306
+ }
1307
+ }
1308
+ return diagnostics;
999
1309
  };
1000
- const normalizeCssLoaderOptions = (options, exportOnlyLocals)=>{
1001
- if (options.modules && exportOnlyLocals) {
1002
- let { modules } = options;
1003
- modules = true === modules ? {
1004
- exportOnlyLocals: true
1005
- } : 'string' == typeof modules ? {
1006
- mode: modules,
1007
- exportOnlyLocals: true
1008
- } : {
1009
- ...modules,
1010
- exportOnlyLocals: true
1011
- };
1012
- return {
1013
- ...options,
1014
- modules
1015
- };
1310
+ const reportLynxDiagnostics = (diagnostics)=>{
1311
+ if (0 === diagnostics.length) return;
1312
+ const htmlWarnings = diagnostics.filter((d)=>'html-element' === d.category);
1313
+ const structuralWarnings = diagnostics.filter((d)=>'structural' === d.category);
1314
+ const cssWarnings = diagnostics.filter((d)=>'css' === d.category);
1315
+ if (htmlWarnings.length > 0) {
1316
+ console.warn('[Lynx] HTML elements detected in templates:');
1317
+ for (const w of htmlWarnings)console.warn(` ${w.message}`);
1318
+ }
1319
+ if (structuralWarnings.length > 0) {
1320
+ console.warn('[Lynx] Structural issues:');
1321
+ for (const w of structuralWarnings)console.warn(` ${w.message}`);
1322
+ }
1323
+ if (cssWarnings.length > 0) {
1324
+ console.warn('[Lynx] Unsupported CSS properties:');
1325
+ for (const w of cssWarnings)console.warn(` ${w.message}`);
1016
1326
  }
1017
- return options;
1018
1327
  };
1019
- const createLynxProcessEvalResultRuntimeModule = (webpack)=>class extends webpack.RuntimeModule {
1020
- constructor(){
1021
- super('Lynx process eval result', webpack.RuntimeModule.STAGE_ATTACH);
1022
- }
1023
- generate() {
1024
- const chunk = this.chunk;
1025
- const compilation = this.compilation;
1026
- if (!chunk || !compilation) return '';
1027
- const chunkGroup = chunk._groupsIterable?.[0];
1028
- const modules = compilation.chunkGraph?.getChunkModules(chunk) ?? [];
1029
- modules.sort((a, b)=>{
1030
- const aIdx = chunkGroup?.getModulePreOrderIndex(a) ?? 0;
1031
- const bIdx = chunkGroup?.getModulePreOrderIndex(b) ?? 0;
1032
- return aIdx - bIdx;
1033
- });
1034
- const sortedIds = modules.map((m)=>compilation.chunkGraph.getModuleId(m)).filter((id)=>null != id);
1035
- const moduleOrder = JSON.stringify(sortedIds);
1036
- return `
1037
- ${RuntimeGlobals.lynxProcessEvalResult} = function (result, schema) {
1038
- var chunk = result && result(schema);
1039
- if (chunk && chunk.ids && chunk.modules) {
1040
- ${webpack.RuntimeGlobals.externalInstallChunk}(chunk);
1041
- var moduleOrder = ${moduleOrder};
1042
- for (var i = 0; i < moduleOrder.length; i++) {
1043
- if (chunk.modules[moduleOrder[i]]) {
1044
- ${webpack.RuntimeGlobals.require}(moduleOrder[i]);
1045
- }
1328
+ const applyAngularRules = async (api, pluginOptions)=>{
1329
+ const cli = getAngularCliContext(api);
1330
+ const { basePath, workspace } = await getAngularWorkspace(cli?.workspaceRoot);
1331
+ if (pluginOptions.pages) {
1332
+ const pages = resolvePages(workspace, basePath, pluginOptions.pages);
1333
+ api.modifyRsbuildConfig((config)=>{
1334
+ config.source ??= {};
1335
+ config.source.entry = Object.fromEntries(pages.map((page)=>[
1336
+ page.name,
1337
+ page.browser
1338
+ ]));
1339
+ });
1046
1340
  }
1047
- return chunk;
1048
- }
1049
- return chunk
1050
- }
1051
- `;
1052
- }
1341
+ const { buildOptions } = await resolvePrimaryBuild({
1342
+ workspace,
1343
+ basePath,
1344
+ pages: pluginOptions.pages,
1345
+ cli
1346
+ });
1347
+ applyAngularConfig(api, buildOptions);
1348
+ const componentStyleSizes = new Map();
1349
+ applyBudgets(api, {
1350
+ budgets: buildOptions.budgets,
1351
+ componentStyleSizes,
1352
+ underAngularCli: void 0 !== cli
1353
+ });
1354
+ const sourcemap = !!(!!buildOptions.sourcemapOptions.scripts && (buildOptions.sourcemapOptions.hidden ? 'external' : true));
1355
+ const thirdPartySourcemaps = buildOptions.sourcemapOptions.vendor;
1356
+ const advancedOptimizations = buildOptions.advancedOptimizations;
1357
+ const aot = buildOptions.aot;
1358
+ const javascriptTransformer = new JavaScriptTransformer({
1359
+ sourcemap,
1360
+ thirdPartySourcemaps,
1361
+ advancedOptimizations,
1362
+ jit: !aot,
1363
+ maxConcurrency: maxWorkers
1364
+ }, void 0);
1365
+ const tsconfig = buildOptions.tsconfig;
1366
+ const compilation = await createAngularCompilation(false, aot);
1367
+ const typeScriptFileCache = new Map();
1368
+ const scopedCssCacheDir = node_path.join(basePath, 'node_modules', '.cache', 'angular-lynx-css');
1369
+ node_fs.mkdirSync(scopedCssCacheDir, {
1370
+ recursive: true
1371
+ });
1372
+ const componentStylesCache = new Map();
1373
+ const componentScopeIds = new Map();
1374
+ let componentResourceDependencies = new Map();
1375
+ const writtenStylesheets = new Set();
1376
+ let knownSourceFiles = new Set();
1377
+ let angularDiagnostics = {
1378
+ errors: [],
1379
+ warnings: []
1053
1380
  };
1054
- class AngularWebpackPlugin {
1055
- options;
1056
- constructor(options){
1057
- this.options = options;
1058
- }
1059
- static defaultOptions = Object.freeze({
1060
- disableCreateSelectorQueryIncompatibleWarning: false,
1061
- firstScreenSyncTiming: 'immediately',
1062
- enableSSR: false,
1063
- isWeb: false,
1064
- mainThreadChunks: [],
1065
- extractStr: false,
1066
- experimental_isLazyBundle: false
1381
+ const transformStylesheet = createTransformStylesheet({
1382
+ basePath,
1383
+ scopedCssCacheDir,
1384
+ componentStylesCache,
1385
+ componentScopeIds,
1386
+ onStylesheetWritten: (filePath)=>writtenStylesheets.add(filePath),
1387
+ componentStyleSizes
1067
1388
  });
1068
- apply(compiler) {
1069
- const options = Object.assign({}, AngularWebpackPlugin.defaultOptions, this.options);
1070
- if (options.enableSSR && 'jsReady' !== options.firstScreenSyncTiming) options.firstScreenSyncTiming = 'jsReady';
1071
- const { DefinePlugin, EnvironmentPlugin } = compiler.webpack;
1072
- new EnvironmentPlugin({
1073
- DEBUG: null
1074
- }).apply(compiler);
1075
- new DefinePlugin({
1076
- __DEV__: JSON.stringify('development' === compiler.options.mode),
1077
- __PROFILE__: JSON.stringify('development' === compiler.options.mode),
1078
- __EXTRACT_STR__: JSON.stringify(Boolean(options.extractStr)),
1079
- __FIRST_SCREEN_SYNC_TIMING__: JSON.stringify(options.firstScreenSyncTiming),
1080
- __ENABLE_SSR__: JSON.stringify(options.enableSSR),
1081
- __WEB__: JSON.stringify(Boolean(options.isWeb)),
1082
- __DISABLE_CREATE_SELECTOR_QUERY_INCOMPATIBLE_WARNING__: JSON.stringify(options.disableCreateSelectorQueryIncompatibleWarning)
1083
- }).apply(compiler);
1084
- compiler.hooks.thisCompilation.tap(this.constructor.name, (compilation)=>{
1085
- const onceForChunkSet = new WeakSet();
1086
- compilation.hooks.runtimeRequirementInTree.for(compiler.webpack.RuntimeGlobals.ensureChunkHandlers).tap('VanillaWebpackPlugin', (chunk, runtimeRequirements)=>{
1087
- runtimeRequirements.add(RuntimeGlobals.lynxProcessEvalResult);
1088
- });
1089
- compilation.hooks.runtimeRequirementInTree.for(RuntimeGlobals.lynxProcessEvalResult).tap('VanillaWebpackPlugin', (chunk)=>{
1090
- if (onceForChunkSet.has(chunk)) return;
1091
- onceForChunkSet.add(chunk);
1092
- const LynxProcessEvalResultRuntimeModule = createLynxProcessEvalResultRuntimeModule(compiler.webpack);
1093
- compilation.addRuntimeModule(chunk, new LynxProcessEvalResultRuntimeModule());
1094
- });
1095
- compilation.hooks.processAssets.tap({
1096
- name: this.constructor.name,
1097
- stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL
1098
- }, ()=>{
1099
- for (const name of options.mainThreadChunks ?? [])this.#updateMainThreadInfo(compilation, name);
1100
- for (const cg of compilation.chunkGroups.filter((cg)=>!cg.isInitial()).filter((cg)=>cg.origins.every((origin)=>origin.module?.layer === LAYERS.MAIN_THREAD))){
1101
- const files = cg.getFiles();
1102
- for (const name of files.filter((name)=>name.endsWith('.js')))this.#updateMainThreadInfo(compilation, name);
1103
- }
1389
+ const compileAngular = async (changedFiles)=>{
1390
+ const { sourceFileCache, fileNames } = buildLynxSchemaSourceFileCache(tsconfig, changedFiles && ((file)=>changedFiles.has(node_path.normalize(file)) || !knownSourceFiles.has(file)));
1391
+ knownSourceFiles = new Set(fileNames);
1392
+ if (changedFiles) await compilation.update?.(new Set(changedFiles));
1393
+ const seeded = seedCompilationSourceFiles(compilation, sourceFileCache);
1394
+ const seededFiles = new Set(seeded ? [
1395
+ ...sourceFileCache.keys()
1396
+ ].map((file)=>node_path.normalize(file)) : []);
1397
+ writtenStylesheets.clear();
1398
+ let result;
1399
+ try {
1400
+ result = await compilation.initialize(tsconfig, {
1401
+ processWebWorker: (workerFile, _containingFile)=>workerFile,
1402
+ transformStylesheet,
1403
+ modifiedFiles: changedFiles && new Set([
1404
+ ...changedFiles
1405
+ ].filter((file)=>!seededFiles.has(file)))
1406
+ }, {
1407
+ sourcemap: !!sourcemap,
1408
+ preserveSymlinks: false
1104
1409
  });
1105
- {
1106
- const { ConcatSource } = compiler.webpack.sources;
1107
- const mainThreadChunkSet = new Set(options.mainThreadChunks ?? []);
1108
- compilation.hooks.processAssets.tap({
1109
- name: `${this.constructor.name}:threadGlobals`,
1110
- stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS
1111
- }, ()=>{
1112
- for (const [name] of Object.entries(compilation.assets).filter(([n])=>n.endsWith('.js'))){
1113
- const isMainThread = mainThreadChunkSet.has(name);
1114
- const prefix = `globalThis["__MAIN_THREAD__"]=${isMainThread};`;
1115
- compilation.updateAsset(name, (old)=>new ConcatSource(prefix, old));
1116
- }
1117
- if (!options.experimental_isLazyBundle) for (const name of options.mainThreadChunks ?? []){
1118
- const asset = compilation.getAsset(name);
1119
- if (!asset) continue;
1120
- const source = asset.source.source().toString();
1121
- const useStrictPrefix = /^(['"])use strict\1;?/.test(source) ? "'use strict';" : '';
1122
- compilation.updateAsset(name, (old)=>new ConcatSource(`${useStrictPrefix}var globDynamicComponentEntry=globDynamicComponentEntry||'__Card__';`, old));
1123
- }
1124
- });
1410
+ } catch (error) {
1411
+ throw new Error(`Angular compilation failed to initialize for "${tsconfig}". This often means a compiler worker crashed under resource pressure — lower NG_BUILD_MAX_WORKERS or reduce parallel build concurrency. Original error: ${error instanceof Error ? error.stack ?? error.message : String(error)}`);
1412
+ }
1413
+ componentResourceDependencies = new Map([
1414
+ ...result.componentResourcesDependencies ?? []
1415
+ ].map(([file, dependencies])=>[
1416
+ node_path.normalize(file),
1417
+ dependencies.map((dependency)=>node_path.normalize(dependency))
1418
+ ]));
1419
+ const changedOutputs = new Set();
1420
+ try {
1421
+ for (const { filename, contents } of (await compilation.emitAffectedFiles())){
1422
+ const file = node_path.normalize(filename);
1423
+ if (typeScriptFileCache.get(file) !== contents) changedOutputs.add(file);
1424
+ typeScriptFileCache.set(file, contents);
1125
1425
  }
1126
- const hooks = LynxTemplatePlugin.getLynxTemplatePluginHooks(compilation);
1127
- const { ConcatSource } = compiler.webpack.sources;
1128
- hooks.beforeEncode.tap(this.constructor.name, (args)=>{
1129
- const { encodeData } = args;
1130
- encodeData.sourceContent.config = {
1131
- ...encodeData.sourceContent.config,
1132
- enableCSSInlineVariables: true
1133
- };
1134
- if (!encodeData.lepusCode.root) return args;
1135
- if ('card' === encodeData.sourceContent.appType) return args;
1136
- compilation.updateAsset(encodeData.lepusCode.root.name, (old)=>new ConcatSource(`\
1137
- (function (globDynamicComponentEntry) {
1138
- const module = { exports: {} }
1139
- const exports = module.exports
1140
- `, old, `
1141
- ;return module.exports
1142
- })`));
1143
- return args;
1144
- });
1145
- });
1146
- }
1147
- #updateMainThreadInfo(compilation, name) {
1148
- const asset = compilation.getAsset(name);
1149
- tiny_invariant(asset, `Should have main thread asset ${name}`);
1150
- compilation.updateAsset(asset.name, asset.source, {
1151
- ...asset.info,
1152
- 'lynx:main-thread': true
1426
+ } catch (error) {
1427
+ throw new Error(`Angular failed to emit compiled output for "${tsconfig}". Original error: ${error instanceof Error ? error.stack ?? error.message : String(error)}`);
1428
+ }
1429
+ for (const file of writtenStylesheets)changedOutputs.add(file);
1430
+ for (const file of changedFiles ?? [])if (!node_fs.existsSync(file)) {
1431
+ typeScriptFileCache.delete(file);
1432
+ forgetComponentStyleSizes(componentStyleSizes, file);
1433
+ }
1434
+ reportLynxDiagnostics([
1435
+ ...scanCompiledOutputForHtmlElements(typeScriptFileCache),
1436
+ ...scanCompiledOutputForStructuralIssues(typeScriptFileCache),
1437
+ ...scanSourcesForUnsupportedPatterns(fileNames),
1438
+ ...scanSourcesForUnsupportedCss(fileNames)
1439
+ ]);
1440
+ angularDiagnostics = filterAngularDiagnostics(await compilation.diagnoseFiles(useTypeChecking ? DiagnosticModes.All : DiagnosticModes.All & ~DiagnosticModes.Semantic));
1441
+ return {
1442
+ changedOutputs,
1443
+ inputFiles: result.referencedFiles.map((file)=>node_path.normalize(file))
1444
+ };
1445
+ };
1446
+ const coordinator = createAngularRebuildCoordinator({
1447
+ compile: compileAngular
1448
+ });
1449
+ const compilersByEnvironment = new Map();
1450
+ api.onAfterCreateCompiler(({ compiler })=>{
1451
+ const compilers = 'compilers' in compiler ? compiler.compilers : [
1452
+ compiler
1453
+ ];
1454
+ for (const environmentCompiler of compilers)if (environmentCompiler.name) compilersByEnvironment.set(environmentCompiler.name, environmentCompiler);
1455
+ compilers[0]?.hooks.thisCompilation.tap('lynx:angular:diagnostics', (rspackCompilation)=>{
1456
+ rspackCompilation.errors.push(...angularDiagnostics.errors.map((message)=>toAngularDiagnosticError(message, 'error')));
1457
+ rspackCompilation.warnings.push(...angularDiagnostics.warnings.map((message)=>toAngularDiagnosticError(message, 'warning')));
1153
1458
  });
1154
- }
1155
- }
1156
- const S_LYNX_CONFIG = Symbol.for('@lynx-js/rsbuild-plugin:config');
1157
- const PLUGIN_NAME_TEMPLATE = 'lynx:template';
1158
- const PLUGIN_NAME_RUNTIME_WRAPPER = 'lynx:runtime-wrapper';
1159
- const PLUGIN_NAME_ANGULAR = 'lynx:angular';
1160
- const applyEntry = (api, options)=>{
1161
- const { customCSSInheritanceList, debugInfoOutside, defaultDisplayLinear, enableAccessibilityElement, enableCSSInheritance, enableCSSInvalidation, enableCSSSelector, enableNewGesture, enableRemoveCSSScope, removeDescendantSelectorScope, targetSdkVersion } = options;
1162
- api.modifyBundlerChain((chain, { environment, isDev })=>{
1163
- const lynxConfig = api.useExposed(S_LYNX_CONFIG);
1164
- if (!lynxConfig) throw new Error('No Lynx config exposed. `pluginLynx` has to be applied for the Lynx build engine to be configured.');
1165
- const isLynx = 'lynx' === environment.name;
1166
- const isWeb = 'web' === environment.name;
1167
- const { hmr, liveReload } = environment.config.dev ?? {};
1168
- const enabledHMR = isDev && !isWeb && false !== hmr;
1169
- const enabledLiveReload = isDev && !isWeb && false !== liveReload;
1170
- const entries = chain.entryPoints.entries() ?? {};
1171
- chain.entryPoints.clear();
1172
- const mainThreadChunks = [];
1173
- for (const [entryName, entryPoint] of Object.entries(entries)){
1174
- const { imports } = getChunks(entryName, entryPoint.values());
1175
- const templateFilename = lynxConfig.resolveBundleFilename({
1176
- entryName,
1177
- platform: environment.name
1459
+ });
1460
+ let compilationClosed = false;
1461
+ api.onBeforeEnvironmentCompile(async ({ environment, isWatch })=>{
1462
+ const compiler = compilersByEnvironment.get(environment.name);
1463
+ const outdatedFiles = await coordinator.prepare(environment.name, [
1464
+ ...compiler?.modifiedFiles ?? [],
1465
+ ...compiler?.removedFiles ?? []
1466
+ ]);
1467
+ if (compiler && outdatedFiles.size > 0) compiler.modifiedFiles = new Set([
1468
+ ...compiler.modifiedFiles ?? [],
1469
+ ...outdatedFiles
1470
+ ]);
1471
+ if (!isWatch && !compilationClosed) {
1472
+ compilationClosed = true;
1473
+ await compilation.close?.();
1474
+ }
1475
+ });
1476
+ api.transform({
1477
+ test: /\.[cm]?[jt]sx?$/
1478
+ }, async (context)=>{
1479
+ const isJs = /\.[cm]?js$/.test(context.resourcePath);
1480
+ if (isJs) {
1481
+ const contents = await javascriptTransformer.transformData(context.resourcePath, context.code, {
1482
+ skipLinker: false,
1483
+ sideEffects: ()=>Promise.resolve(false)
1178
1484
  });
1179
- const mainThreadEntry = `${entryName}__main-thread`;
1180
- const mainThreadName = node_path.posix.join(`${entryName}/main-thread.js`);
1181
- const backgroundEntry = entryName;
1182
- const backgroundThreadName = node_path.posix.join(`${entryName}/background-thread.js`);
1183
- mainThreadChunks.push(mainThreadName);
1184
- chain.entry(mainThreadEntry).add({
1185
- layer: LAYERS.MAIN_THREAD,
1186
- import: imports,
1187
- filename: mainThreadName
1188
- }).end();
1189
- chain.entry(backgroundEntry).add({
1190
- layer: LAYERS.BACKGROUND,
1191
- import: imports,
1192
- filename: backgroundThreadName
1193
- }).when(enabledHMR, (entry)=>{
1194
- entry.prepend({
1195
- layer: LAYERS.BACKGROUND,
1196
- import: '@rspack/core/hot/dev-server'
1197
- });
1198
- }).when(enabledHMR || enabledLiveReload, (entry)=>{
1199
- entry.prepend({
1200
- layer: LAYERS.BACKGROUND,
1201
- import: '@lynx-js/webpack-dev-transport/client'
1202
- });
1203
- }).end();
1204
- chain.plugin(`${PLUGIN_NAME_TEMPLATE}-${entryName}`).use(LynxTemplatePlugin, [
1205
- {
1206
- filename: templateFilename,
1207
- intermediate: lynxConfig.resolveIntermediateDir({
1208
- entryName
1209
- }),
1210
- chunks: [
1211
- mainThreadEntry,
1212
- backgroundEntry
1213
- ],
1214
- cssPlugins: [
1215
- CSSPlugins.parserPlugins.removeFunctionWhiteSpace()
1216
- ],
1217
- customCSSInheritanceList,
1218
- debugInfoOutside,
1219
- defaultDisplayLinear,
1220
- enableAccessibilityElement,
1221
- enableCSSInheritance,
1222
- enableCSSInvalidation,
1223
- enableCSSSelector,
1224
- enableNewGesture,
1225
- enableRemoveCSSScope,
1226
- removeDescendantSelectorScope,
1227
- targetSdkVersion,
1228
- enableA11y: true
1229
- }
1230
- ]).end();
1485
+ return {
1486
+ code: transformWorklets(Buffer.from(contents).toString(), context.resourcePath)
1487
+ };
1231
1488
  }
1232
- if (isLynx) {
1233
- chain.plugin(PLUGIN_NAME_RUNTIME_WRAPPER).use(RuntimeWrapperWebpackPlugin, [
1489
+ const content = typeScriptFileCache.get(context.resourcePath);
1490
+ if (!content) throw new Error(`No compiled output found for ${context.resourcePath}`);
1491
+ for (const dependency of componentResourceDependencies.get(context.resourcePath) ?? [])context.addDependency(dependency);
1492
+ const code = 'string' == typeof content ? content : Buffer.from(content).toString();
1493
+ return {
1494
+ code: buildTransformedCode({
1495
+ code,
1496
+ resourcePath: context.resourcePath,
1497
+ componentStyles: componentStylesCache.get(context.resourcePath),
1498
+ scopeInfo: componentScopeIds.get(context.resourcePath)
1499
+ })
1500
+ };
1501
+ });
1502
+ };
1503
+ const applyCSS = (api, options)=>{
1504
+ const { enableRemoveCSSScope, enableCSSSelector, enableCSSInvalidation, targetSdkVersion } = options;
1505
+ api.modifyRsbuildConfig((config, { mergeRsbuildConfig })=>mergeRsbuildConfig(config, {
1506
+ output: {
1507
+ injectStyles: false
1508
+ }
1509
+ }));
1510
+ const __dirname = node_path.dirname(fileURLToPath(import.meta.url));
1511
+ api.modifyBundlerChain(async (chain, { CHAIN_ID, environment })=>{
1512
+ const { CssExtractRspackPlugin } = await import("@lynx-js/css-extract-webpack-plugin");
1513
+ const removeLightningCSS = (rule)=>{
1514
+ if (rule.uses.has(CHAIN_ID.USE.LIGHTNINGCSS) && 'lynx' === environment.name) rule.uses.delete(CHAIN_ID.USE.LIGHTNINGCSS);
1515
+ };
1516
+ const cssRules = [
1517
+ CHAIN_ID.RULE.CSS,
1518
+ CHAIN_ID.RULE.SASS,
1519
+ CHAIN_ID.RULE.LESS,
1520
+ CHAIN_ID.RULE.STYLUS
1521
+ ];
1522
+ for (const ruleName of cssRules.filter((rule)=>chain.module.rules.has(rule))){
1523
+ const rule = chain.module.rule(ruleName);
1524
+ const mainRuleName = ruleName === CHAIN_ID.RULE.CSS ? CHAIN_ID.ONE_OF.CSS_MAIN : ruleName;
1525
+ const mainRule = rule.oneOf(mainRuleName);
1526
+ const parentRuleEntries = rule.entries();
1527
+ removeLightningCSS(mainRule);
1528
+ mainRule.issuerLayer(LAYERS.BACKGROUND).use(CHAIN_ID.USE.MINI_CSS_EXTRACT).loader(CssExtractRspackPlugin.loader).end();
1529
+ const uses = mainRule.uses.entries() ?? {};
1530
+ const ruleEntries = mainRule.entries();
1531
+ const cssLoader = uses[CHAIN_ID.USE.CSS];
1532
+ if (!cssLoader) continue;
1533
+ const cssLoaderRule = cssLoader.entries();
1534
+ const mainThreadLayerRule = chain.module.rule(`${ruleName}:${LAYERS.MAIN_THREAD}`).test(parentRuleEntries.test).merge(ruleEntries).issuerLayer(LAYERS.MAIN_THREAD);
1535
+ if (void 0 !== parentRuleEntries.dependency) mainThreadLayerRule.merge({
1536
+ dependency: parentRuleEntries.dependency
1537
+ });
1538
+ mainThreadLayerRule.use(CHAIN_ID.USE.IGNORE_CSS).loader(node_path.resolve(__dirname, './loaders/ignore-css-loader')).end().uses.merge(uses).delete(CHAIN_ID.USE.MINI_CSS_EXTRACT).delete(CHAIN_ID.USE.LIGHTNINGCSS).delete(CHAIN_ID.USE.CSS).end().use(CHAIN_ID.USE.CSS).after(CHAIN_ID.USE.IGNORE_CSS).merge(cssLoaderRule).options(normalizeCssLoaderOptions(cssLoaderRule.options, true)).end();
1539
+ }
1540
+ for (const ruleName of cssRules.filter((rule)=>chain.module.rules.has(rule))){
1541
+ const inlineRuleName = ruleName === CHAIN_ID.RULE.CSS ? CHAIN_ID.ONE_OF.CSS_INLINE : `${ruleName}-inline`;
1542
+ removeLightningCSS(chain.module.rule(ruleName).oneOf(inlineRuleName));
1543
+ }
1544
+ if ('lynx' === environment.name && chain.module.rules.has(CHAIN_ID.RULE.FONT)) {
1545
+ const fontRule = chain.module.rule(CHAIN_ID.RULE.FONT);
1546
+ fontRule.oneOfs.clear();
1547
+ fontRule.type('asset/inline');
1548
+ }
1549
+ chain.plugin(CHAIN_ID.PLUGIN.MINI_CSS_EXTRACT).tap(([options])=>[
1234
1550
  {
1235
- injectVars (vars) {
1236
- return vars.map((name)=>{
1237
- if ('Component' === name) return '__Component';
1238
- return name;
1239
- });
1240
- },
1551
+ ...options,
1552
+ enableRemoveCSSScope,
1553
+ enableCSSSelector,
1554
+ enableCSSInvalidation,
1241
1555
  targetSdkVersion,
1242
- test: /^(?!.*main-thread(?:\.[A-Fa-f0-9]*)?\.js$).*\.js$/
1556
+ cssPlugins: [
1557
+ CSSPlugins.parserPlugins.removeFunctionWhiteSpace()
1558
+ ]
1243
1559
  }
1244
- ]).end();
1245
- chain.plugin(`${LynxEncodePlugin.name}`).use(LynxEncodePlugin, [
1246
- {}
1247
- ]).end();
1248
- }
1249
- if (isWeb) chain.plugin(`${WebEncodePlugin.name}`).use(WebEncodePlugin, []).end();
1250
- chain.plugin(PLUGIN_NAME_ANGULAR).after(PLUGIN_NAME_TEMPLATE).use(AngularWebpackPlugin, [
1251
- {
1252
- mainThreadChunks,
1253
- enableSSR: options.enableSSR,
1254
- isWeb
1255
- }
1256
- ]);
1560
+ ]).init((_, args)=>new CssExtractRspackPlugin(...args)).end().end();
1561
+ chain.module.when(void 0 === enableRemoveCSSScope, (module)=>module.rule('lynx.css.scoped').test(/\.css$/).resourceQuery({
1562
+ and: [
1563
+ /cssId/,
1564
+ {
1565
+ not: /common/
1566
+ }
1567
+ ]
1568
+ }).sideEffects(false));
1257
1569
  });
1258
1570
  };
1259
- const getChunks = (entryName, entryValue)=>{
1260
- const chunks = [
1261
- entryName
1262
- ];
1263
- const imports = [];
1264
- for (const item of entryValue){
1265
- if ('string' == typeof item) {
1266
- imports.push(item);
1267
- continue;
1268
- }
1269
- if (Array.isArray(item)) {
1270
- imports.push(...imports);
1271
- continue;
1272
- }
1273
- const { dependOn } = item;
1274
- if (Array.isArray(item.import)) imports.push(...item.import);
1275
- else imports.push(item.import);
1276
- if (dependOn) if ('string' == typeof dependOn) chunks.unshift(dependOn);
1277
- else chunks.unshift(...dependOn);
1571
+ const normalizeCssLoaderOptions = (options, exportOnlyLocals)=>{
1572
+ if (options.modules && exportOnlyLocals) {
1573
+ let { modules } = options;
1574
+ modules = true === modules ? {
1575
+ exportOnlyLocals: true
1576
+ } : 'string' == typeof modules ? {
1577
+ mode: modules,
1578
+ exportOnlyLocals: true
1579
+ } : {
1580
+ ...modules,
1581
+ exportOnlyLocals: true
1582
+ };
1583
+ return {
1584
+ ...options,
1585
+ modules
1586
+ };
1278
1587
  }
1279
- return {
1280
- chunks,
1281
- imports
1282
- };
1588
+ return options;
1283
1589
  };
1284
1590
  const applyGenerator = (api)=>{
1285
1591
  api.modifyBundlerChain({
@@ -1432,8 +1738,7 @@ const applySplitChunksRule = (api, options)=>{
1432
1738
  });
1433
1739
  return config;
1434
1740
  });
1435
- api.modifyRspackConfig((rspackConfig, { environment })=>{
1436
- if ('lynx' !== environment.name) return rspackConfig;
1741
+ api.modifyRspackConfig((rspackConfig)=>{
1437
1742
  rspackConfig.output = rspackConfig.output ?? {};
1438
1743
  if (!options.experimental_isLazyBundle) rspackConfig.output.asyncChunks = false;
1439
1744
  if (!rspackConfig.optimization) return rspackConfig;
@@ -1492,7 +1797,8 @@ const normalizeOptions = (options)=>{
1492
1797
  const pluginAngularLynx = (options)=>({
1493
1798
  name: 'lynx:angular',
1494
1799
  pre: [
1495
- 'lynx:rsbuild:plugin-api'
1800
+ 'lynx:rsbuild:plugin-api',
1801
+ ANGULAR_CLI_CONTEXT_PLUGIN_NAME
1496
1802
  ],
1497
1803
  setup: async (api)=>{
1498
1804
  const normalizedOptions = normalizeOptions(options);
@@ -1507,4 +1813,4 @@ const pluginAngularLynx = (options)=>({
1507
1813
  await angularRules;
1508
1814
  }
1509
1815
  });
1510
- export { pluginAngularLynx };
1816
+ export { pluginAngularCliContext, pluginAngularLynx };