@blotch/rsbuild-plugin-angular-lynx 22.2.1 → 22.2.3
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/README.md +20 -3
- package/dist/544.js +21 -0
- package/dist/angular-cli-context.d.ts +34 -0
- package/dist/angular-diagnostics.d.ts +58 -0
- package/dist/angular-diagnostics.test.d.ts +1 -0
- package/dist/angular-rebuild-coordinator.d.ts +1 -1
- package/dist/angular.d.ts +5 -3
- package/dist/background-entry.d.ts +96 -0
- package/dist/background-entry.test.d.ts +1 -0
- package/dist/budgets-plugin.d.ts +64 -0
- package/dist/budgets-plugin.test.d.ts +1 -0
- package/dist/client/background-hot-updates.d.ts +50 -0
- package/dist/client/background-hot-updates.js +77 -0
- package/dist/client/background-hot-updates.test.d.ts +1 -0
- package/dist/client/css-hot-update.cjs +9 -0
- package/dist/client/css-hot-update.d.ts +44 -0
- package/dist/client/css-hot-update.js +42 -0
- package/dist/client/css-hot-update.test.d.ts +1 -0
- package/dist/client/hmr-background.js +16 -0
- package/dist/client/hmr-main-thread.js +9 -0
- package/dist/client/hmr-protocol.d.ts +74 -0
- package/dist/client/main-thread-hot-updates.d.ts +40 -0
- package/dist/client/main-thread-hot-updates.js +78 -0
- package/dist/client/main-thread-hot-updates.test.d.ts +1 -0
- package/dist/client/testing/test-context.d.ts +16 -0
- package/dist/component-styles-cache.d.ts +13 -31
- package/dist/component-styles.d.ts +73 -0
- package/dist/component-styles.test.d.ts +1 -0
- package/dist/cross-thread-transform.d.ts +38 -0
- package/dist/cross-thread-transform.test.d.ts +1 -0
- package/dist/css.d.ts +2 -2
- package/dist/entry.d.ts +28 -2
- package/dist/hot-updates.d.ts +67 -0
- package/dist/hot-updates.test.d.ts +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1494 -638
- package/dist/layers.d.ts +6 -4
- package/dist/logger/dev-logger-middleware.d.ts +13 -7
- package/dist/logger/dev-logger.d.ts +13 -0
- package/dist/polyfills.js +56 -0
- package/dist/polyfills.test.d.ts +1 -0
- package/dist/pure-classes.d.ts +27 -0
- package/dist/pure-classes.test.d.ts +1 -0
- package/dist/resolve.d.ts +19 -0
- package/dist/resolve.test.d.ts +1 -0
- package/dist/split-chunks.d.ts +2 -2
- package/dist/styles/blur-view.css +16 -0
- package/dist/transform-module.d.ts +43 -17
- package/dist/utils/angular/budgets.d.ts +72 -0
- package/dist/utils/angular/budgets.test.d.ts +1 -0
- package/dist/utils/angular/build-internals.d.ts +14 -0
- package/dist/utils/angular/component-scope-id.d.ts +5 -3
- package/dist/utils/angular/component-style-bundler.d.ts +1 -1
- package/dist/utils/angular/options.d.ts +80 -2
- package/dist/utils/angular/read-workspace.d.ts +5 -1
- package/dist/utils/angular/resolve-primary-build.d.ts +26 -0
- package/dist/utils/angular/resolve-primary-build.test.d.ts +1 -0
- package/dist/utils/options.d.ts +40 -1
- package/package.json +13 -8
- package/dist/loaders/thread-globals-loader.d.ts +0 -8
- package/dist/loaders/thread-globals-loader.js +0 -6
- package/patch/patch-angular-build.cjs +0 -27
- /package/dist/{loaders/thread-globals-loader.test.d.ts → angular-cli-context.test.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -4,12 +4,14 @@ Rsbuild plugin that compiles Angular apps for the [Lynx](https://lynxjs.org/) na
|
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
|
+
> **Using the Angular CLI?** You don't need this package directly. `ng add @blotch/angular-lynx` installs [`@blotch/angular-lynx-build`](../build), whose `ng build` and `ng serve` builders depend on this plugin and add it to rspeedy's config. To pass it options, import it from there: `import { pluginAngularLynx } from '@blotch/angular-lynx-build'`.
|
|
8
|
+
|
|
9
|
+
To run rspeedy directly:
|
|
10
|
+
|
|
7
11
|
```bash
|
|
8
12
|
npm install @blotch/rsbuild-plugin-angular-lynx
|
|
9
13
|
```
|
|
10
14
|
|
|
11
|
-
> Starting a new project? `ng add @blotch/angular-lynx` sets this up automatically.
|
|
12
|
-
|
|
13
15
|
## Usage
|
|
14
16
|
|
|
15
17
|
Add the plugin to your `lynx.config.ts`:
|
|
@@ -32,7 +34,22 @@ export default defineConfig({
|
|
|
32
34
|
- Splits each entry into **main-thread** and **background-thread** bundles for Lynx's dual-thread model
|
|
33
35
|
- Bundles component stylesheets and processes CSS
|
|
34
36
|
- Registers `CUSTOM_ELEMENTS_SCHEMA` so Angular accepts Lynx elements (`<view>`, `<text>`, etc.)
|
|
37
|
+
- Enforces the build target's `budgets` with `ng build`'s units and messages: a warning is printed, an error fails the build
|
|
38
|
+
|
|
39
|
+
## Budgets
|
|
40
|
+
|
|
41
|
+
Budgets in angular.json are checked against the `lynx` environment, the bundles devices load. The `web` preview isn't checked. A Lynx app ships `.bundle` files rather than separate scripts and stylesheets, so each budget type measures:
|
|
42
|
+
|
|
43
|
+
| Type | Measures |
|
|
44
|
+
| ------------------- | ------------------------------------------------------------------ |
|
|
45
|
+
| `initial` | Each entry's bundle on its own, since a Lynx page loads one bundle |
|
|
46
|
+
| `bundle` | The bundle of the entry named by `name` |
|
|
47
|
+
| `all` / `allScript` | All bundles and scripts together (scripts ship inside the bundles) |
|
|
48
|
+
| `any` / `anyScript` | Each bundle and script |
|
|
49
|
+
| `anyComponentStyle` | Each component stylesheet, compiled |
|
|
50
|
+
|
|
51
|
+
Media and fonts aren't counted. Under plain `rspeedy dev` budgets are skipped: the plugin then reads angular.json's default configuration, usually production, whose budgets don't fit a development build. `ng serve` checks them when the configuration it serves has them, as Angular's dev server does.
|
|
35
52
|
|
|
36
53
|
## Documentation
|
|
37
54
|
|
|
38
|
-
Full docs at [angularlynx.
|
|
55
|
+
Full docs at [angularlynx.js.org](https://angularlynx.js.org).
|
package/dist/544.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const HMR_FETCH = 'AngularLynx.HmrFetch';
|
|
2
|
+
const HMR_FETCH_RESULT = 'AngularLynx.HmrFetchResult';
|
|
3
|
+
const HMR_CHECK = 'AngularLynx.HmrCheck';
|
|
4
|
+
const HMR_CHECK_RESULT = 'AngularLynx.HmrCheckResult';
|
|
5
|
+
const HMR_RELOAD = 'AngularLynx.HmrReload';
|
|
6
|
+
const send = (context, type, message)=>{
|
|
7
|
+
context.dispatchEvent({
|
|
8
|
+
type,
|
|
9
|
+
data: JSON.stringify(message)
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
const listen = (context, type, listener)=>{
|
|
13
|
+
context.addEventListener(type, (event)=>listener(JSON.parse(event.data)));
|
|
14
|
+
};
|
|
15
|
+
const formatError = (error)=>{
|
|
16
|
+
if (!(error instanceof Error)) return String(error);
|
|
17
|
+
const heading = `${error.name}: ${error.message}`;
|
|
18
|
+
if (!error.stack) return heading;
|
|
19
|
+
return error.stack.startsWith(heading) ? error.stack : `${heading}\n${error.stack}`;
|
|
20
|
+
};
|
|
21
|
+
export { HMR_CHECK, HMR_CHECK_RESULT, HMR_FETCH, HMR_FETCH_RESULT, HMR_RELOAD, formatError, listen, send };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { RsbuildPlugin, RsbuildPluginAPI } from '@lynx-js/rspeedy';
|
|
2
|
+
/**
|
|
3
|
+
* What Angular CLI knows about the build that a plain rspeedy run doesn't:
|
|
4
|
+
* which project it is for, and its build target's options as Angular CLI
|
|
5
|
+
* resolved them. `ng build` and `ng serve` (@blotch/angular-lynx-build) hand
|
|
6
|
+
* it to the plugin through {@link pluginAngularCliContext}.
|
|
7
|
+
*/
|
|
8
|
+
export type AngularCliContext = {
|
|
9
|
+
/** The workspace root, where angular.json is. */
|
|
10
|
+
readonly workspaceRoot: string;
|
|
11
|
+
/** The angular.json project being built. */
|
|
12
|
+
readonly project: string;
|
|
13
|
+
/**
|
|
14
|
+
* The project's build target options: its base options, then each
|
|
15
|
+
* configuration selected with `-c` (or the default one), then command-line
|
|
16
|
+
* overrides, merged by Angular CLI.
|
|
17
|
+
*/
|
|
18
|
+
readonly buildOptions: Readonly<Record<string, unknown>>;
|
|
19
|
+
};
|
|
20
|
+
export declare const ANGULAR_CLI_CONTEXT_PLUGIN_NAME = "lynx:angular-cli-context";
|
|
21
|
+
/**
|
|
22
|
+
* Hands the Angular CLI context to `pluginAngularLynx()` through Rsbuild's
|
|
23
|
+
* plugin-to-plugin API (`api.expose` / `api.useExposed`). The builders add it
|
|
24
|
+
* to rspeedy's config; lynx.config.ts never lists it.
|
|
25
|
+
*
|
|
26
|
+
* It works whether the builders added `pluginAngularLynx()` themselves or
|
|
27
|
+
* lynx.config.ts lists it with options, which a constructor argument couldn't.
|
|
28
|
+
*/
|
|
29
|
+
export declare const pluginAngularCliContext: (context: AngularCliContext) => RsbuildPlugin;
|
|
30
|
+
/**
|
|
31
|
+
* The Angular CLI context, or undefined when rspeedy runs on its own (then
|
|
32
|
+
* the plugin reads angular.json itself).
|
|
33
|
+
*/
|
|
34
|
+
export declare const getAngularCliContext: (api: Pick<RsbuildPluginAPI, "useExposed">) => AngularCliContext | undefined;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where an Angular diagnostic points. Mirrors the esbuild `Location` shape that
|
|
3
|
+
* `@angular/build` converts TypeScript and Angular compiler diagnostics into:
|
|
4
|
+
* `line` is 1-based and `column` 0-based.
|
|
5
|
+
*/
|
|
6
|
+
export type AngularDiagnosticLocation = {
|
|
7
|
+
readonly file?: string;
|
|
8
|
+
readonly line?: number;
|
|
9
|
+
readonly column?: number;
|
|
10
|
+
readonly length?: number;
|
|
11
|
+
readonly lineText?: string;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* One diagnostic from `compilation.diagnoseFiles()`: an esbuild
|
|
15
|
+
* `PartialMessage`, whose `text` starts with the code (`TS2307: ...`,
|
|
16
|
+
* `NG8001: ...`).
|
|
17
|
+
*/
|
|
18
|
+
export type AngularDiagnosticMessage = {
|
|
19
|
+
readonly text?: string;
|
|
20
|
+
readonly location?: AngularDiagnosticLocation | null;
|
|
21
|
+
readonly notes?: readonly {
|
|
22
|
+
readonly text?: string;
|
|
23
|
+
readonly location?: AngularDiagnosticLocation | null;
|
|
24
|
+
}[];
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* The diagnostics of one Angular compile, normalized so callers don't have to
|
|
28
|
+
* handle `diagnoseFiles()` leaving either list out.
|
|
29
|
+
*/
|
|
30
|
+
export type AngularDiagnostics = {
|
|
31
|
+
readonly errors: readonly AngularDiagnosticMessage[];
|
|
32
|
+
readonly warnings: readonly AngularDiagnosticMessage[];
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* The shape rspack accepts in `compilation.errors` and `compilation.warnings`.
|
|
36
|
+
* rsbuild prints `file` as the error's `File:` line, so a `path:line:column`
|
|
37
|
+
* value makes it clickable in terminals and editors.
|
|
38
|
+
*/
|
|
39
|
+
export type AngularDiagnosticError = Error & {
|
|
40
|
+
file?: string;
|
|
41
|
+
hideStack: boolean;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Keeps only the diagnostics a user can act on. Angular's template checker
|
|
45
|
+
* doesn't know Lynx's native elements and their attributes, so it reports
|
|
46
|
+
* them as unknown even though the renderer supports them.
|
|
47
|
+
*/
|
|
48
|
+
export declare const filterAngularDiagnostics: (diagnostics: {
|
|
49
|
+
errors?: readonly AngularDiagnosticMessage[];
|
|
50
|
+
warnings?: readonly AngularDiagnosticMessage[];
|
|
51
|
+
}) => AngularDiagnostics;
|
|
52
|
+
/**
|
|
53
|
+
* Converts an Angular diagnostic into an error rspack can report. Pushing it
|
|
54
|
+
* into `compilation.errors` is what makes `rspeedy build` exit non-zero (and
|
|
55
|
+
* the dev server show it) the way `ng build` and `ng serve` do; printing it
|
|
56
|
+
* with `console.log` let builds with type errors succeed and ship.
|
|
57
|
+
*/
|
|
58
|
+
export declare const toAngularDiagnosticError: (message: AngularDiagnosticMessage, severity: "error" | "warning") => AngularDiagnosticError;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -18,7 +18,7 @@ export type AngularCompileResult = {
|
|
|
18
18
|
/**
|
|
19
19
|
* Every file the compilation read: program sources plus component
|
|
20
20
|
* resources such as external templates and stylesheets. Changes to any
|
|
21
|
-
* other non-TypeScript file (global CSS, assets, the
|
|
21
|
+
* other non-TypeScript file (global CSS, assets, the component-CSS cache this
|
|
22
22
|
* plugin writes itself) can't affect Angular's output, so they don't
|
|
23
23
|
* trigger a recompile.
|
|
24
24
|
*/
|
package/dist/angular.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { RsbuildPluginAPI } from '@lynx-js/rspeedy';
|
|
2
|
-
import type {
|
|
2
|
+
import type { BackgroundEntries } from './background-entry.js';
|
|
3
|
+
import type { NormalizedPluginAngularLynxOptions } from './utils/options.js';
|
|
3
4
|
/**
|
|
4
5
|
* Wires Angular's AOT/JIT compilation pipeline into the rsbuild/rspack build
|
|
5
6
|
* graph. This is the heart of the AngularLynx build — it bridges three worlds:
|
|
@@ -21,11 +22,12 @@ import type { PluginAngularLynxOptions } from './utils/options.js';
|
|
|
21
22
|
* → compilation.update (rebuilds only: invalidate the changed files)
|
|
22
23
|
* → compilation.initialize (Angular AOT walk + transformStylesheet callback)
|
|
23
24
|
* → emitAffectedFiles (refreshes typeScriptFileCache)
|
|
24
|
-
* → diagnoseFiles (filtered to suppress Lynx-element false positives
|
|
25
|
+
* → diagnoseFiles (filtered to suppress Lynx-element false positives, then
|
|
26
|
+
* reported as rspack errors/warnings so type errors fail the build)
|
|
25
27
|
* → rspack rebuilds the changed files plus any whose compiled output changed
|
|
26
28
|
*
|
|
27
29
|
* Then for each module request the `api.transform` hook delegates the per-module
|
|
28
30
|
* string transform (import-prepend + ɵcmp.id + worklet) to
|
|
29
31
|
* `buildTransformedCode` in transform-module.ts.
|
|
30
32
|
*/
|
|
31
|
-
export declare const applyAngularRules: (api: RsbuildPluginAPI, pluginOptions:
|
|
33
|
+
export declare const applyAngularRules: (api: RsbuildPluginAPI, pluginOptions: NormalizedPluginAngularLynxOptions, backgroundEntries: BackgroundEntries, appEntries: ReadonlySet<string>) => Promise<void>;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The background half of each bundle starts from a module this file
|
|
3
|
+
* generates, not from the app's own entry.
|
|
4
|
+
*
|
|
5
|
+
* Angular runs on the main thread only (issue #81). The background thread
|
|
6
|
+
* needs three things from the bundle:
|
|
7
|
+
*
|
|
8
|
+
* - **the runtime**, whose module-level code installs the background half of
|
|
9
|
+
* each bridge (the call relay, the log relay, the exposure bridge and so on);
|
|
10
|
+
* - **each module-level `backgroundFn()`**, so the function registers there;
|
|
11
|
+
* - **each module-level `createMainThreadRef()` / `new MainThreadRef()`**, so a
|
|
12
|
+
* ref the main thread passes to a background function arrives as the
|
|
13
|
+
* background thread's copy.
|
|
14
|
+
*
|
|
15
|
+
* Starting from `main.ts` reached every component, the router and Angular
|
|
16
|
+
* itself, which the background thread evaluated on every launch and never ran
|
|
17
|
+
* (issue #84). The generated entry imports the runtime and the modules that
|
|
18
|
+
* make those calls, and nothing else. They load at startup, so a background
|
|
19
|
+
* function in a lazy route's file is there before the route is.
|
|
20
|
+
*
|
|
21
|
+
* The modules are the app's own files, found in Angular's compiled output, and
|
|
22
|
+
* the files of any installed package that depends on the runtime (see
|
|
23
|
+
* {@link findLibraryBackgroundModules}).
|
|
24
|
+
*/
|
|
25
|
+
export type BackgroundEntries = {
|
|
26
|
+
/**
|
|
27
|
+
* The file the background half of `entryName` starts from, in
|
|
28
|
+
* `environmentName`. Created with the runtime alone if it doesn't exist yet,
|
|
29
|
+
* so the entry resolves even before the first compile has written it.
|
|
30
|
+
*/
|
|
31
|
+
fileFor: (environmentName: string, entryName: string) => string;
|
|
32
|
+
/**
|
|
33
|
+
* Whether `file` is one of the background entry's modules: a file it
|
|
34
|
+
* imports. entry.ts marks them as having side effects in the background
|
|
35
|
+
* layer, because the entry imports them only for their side effects and a
|
|
36
|
+
* package's `"sideEffects": false` would otherwise drop them.
|
|
37
|
+
*/
|
|
38
|
+
isBackgroundModule: (file: string) => boolean;
|
|
39
|
+
/** Whether `file` is one of the generated entries. */
|
|
40
|
+
isEntryFile: (file: string) => boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Writes `environmentName`'s entry files for the compile about to start.
|
|
43
|
+
*
|
|
44
|
+
* @returns The files whose contents changed. The caller adds them to the
|
|
45
|
+
* compiler's `modifiedFiles`, so rspack rebuilds them in this compile.
|
|
46
|
+
*/
|
|
47
|
+
update: (environmentName: string, options: {
|
|
48
|
+
/** The app's modules that define a cross-thread value. */
|
|
49
|
+
appModules: Iterable<string>;
|
|
50
|
+
/**
|
|
51
|
+
* Set for a watch build's compiles. The entry then also changes on every
|
|
52
|
+
* compile with {@link rebuilt} set: see {@link renderBackgroundEntry}.
|
|
53
|
+
*/
|
|
54
|
+
watch: boolean;
|
|
55
|
+
/** Whether something the bundle is built from changed. */
|
|
56
|
+
rebuilt: boolean;
|
|
57
|
+
}) => string[];
|
|
58
|
+
};
|
|
59
|
+
export declare const createBackgroundEntries: ({ rootPath, findLibraryModules, }: {
|
|
60
|
+
rootPath: string;
|
|
61
|
+
findLibraryModules?: (rootPath: string) => string[];
|
|
62
|
+
}) => BackgroundEntries;
|
|
63
|
+
/**
|
|
64
|
+
* The background entry's source.
|
|
65
|
+
*
|
|
66
|
+
* @param options.modules - The files to import, in order.
|
|
67
|
+
* @param options.rebuild - In a watch build, the number of compiles so far
|
|
68
|
+
* that rebuilt something. It's written into the entry so that every such
|
|
69
|
+
* compile changes the background bundle. The main thread doesn't run
|
|
70
|
+
* webpack's HMR runtime (issue #9), so a hot update only reaches the
|
|
71
|
+
* background thread, and an edit to a component would otherwise give it
|
|
72
|
+
* nothing to apply: the HMR client would report the app up to date and keep
|
|
73
|
+
* showing the old screen. With the entry changed, the update reaches a
|
|
74
|
+
* module that doesn't accept it, so it aborts, and the client falls back to
|
|
75
|
+
* a full reload, as it did when the background bundle held the whole app.
|
|
76
|
+
*/
|
|
77
|
+
export declare const renderBackgroundEntry: ({ modules, rebuild, }: {
|
|
78
|
+
modules: readonly string[];
|
|
79
|
+
rebuild?: number;
|
|
80
|
+
}) => string;
|
|
81
|
+
/**
|
|
82
|
+
* The files in installed packages that define a cross-thread value at module
|
|
83
|
+
* level, such as a UI library with its own background functions.
|
|
84
|
+
*
|
|
85
|
+
* A package can only call the runtime's functions if it depends on the runtime,
|
|
86
|
+
* so only the packages whose `package.json` lists `@blotch/angular-lynx` (as a
|
|
87
|
+
* dependency, peer dependency or optional dependency) are read: every one of
|
|
88
|
+
* their JavaScript files, outside nested `node_modules` folders. Packages are
|
|
89
|
+
* found by walking the dependencies from the app's `package.json`, resolved as
|
|
90
|
+
* Node does, so a nested or hoisted copy is found where the app's build
|
|
91
|
+
* finds it.
|
|
92
|
+
*
|
|
93
|
+
* @param rootPath - The app's directory, which holds its `package.json`.
|
|
94
|
+
* @returns The files' real paths, sorted.
|
|
95
|
+
*/
|
|
96
|
+
export declare const findLibraryBackgroundModules: (rootPath: string) => string[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { RsbuildPluginAPI, Rspack } from '@lynx-js/rspeedy';
|
|
2
|
+
import { type BudgetAsset, type BudgetFailure, type ComponentStyleSize } from './utils/angular/budgets.js';
|
|
3
|
+
import type { Budget } from './utils/angular/options.js';
|
|
4
|
+
/** The shape rspack accepts in `compilation.errors` and `compilation.warnings`. */
|
|
5
|
+
export type BudgetError = Error & {
|
|
6
|
+
hideStack: boolean;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Turns a budget failure into an rspack error or warning. As an error it
|
|
10
|
+
* fails `ng build` / `rspeedy build`, like a budget error fails `ng build`
|
|
11
|
+
* for a web app; as a warning it's printed and the build succeeds.
|
|
12
|
+
*/
|
|
13
|
+
export declare const toBudgetError: (failure: BudgetFailure) => BudgetError;
|
|
14
|
+
/**
|
|
15
|
+
* The entry (page) a Lynx template was built from, or undefined for a lazy
|
|
16
|
+
* bundle. A template covers an entry and its main-thread twin; lazy bundles
|
|
17
|
+
* cover async chunk groups, which aren't entry points.
|
|
18
|
+
*/
|
|
19
|
+
export declare const bundleEntryName: (chunkGroups: readonly {
|
|
20
|
+
readonly name?: string | null;
|
|
21
|
+
}[], entrypoints: {
|
|
22
|
+
has: (name: string) => boolean;
|
|
23
|
+
}) => string | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* The emitted files budgets count: the bundles and any scripts left outside
|
|
26
|
+
* them. Leaves out source maps and hot updates (as Angular does), media and
|
|
27
|
+
* fonts, and the build's intermediate directory, where the Lynx template
|
|
28
|
+
* plugin writes debug copies of what it encoded into the bundles.
|
|
29
|
+
*/
|
|
30
|
+
export declare const collectBudgetAssets: (assets: readonly {
|
|
31
|
+
readonly name: string;
|
|
32
|
+
readonly source: {
|
|
33
|
+
size: () => number;
|
|
34
|
+
};
|
|
35
|
+
readonly info: {
|
|
36
|
+
readonly hotModuleReplacement?: boolean;
|
|
37
|
+
};
|
|
38
|
+
}[], entryBundles: ReadonlyMap<string, string>, intermediateDir: string | undefined) => BudgetAsset[];
|
|
39
|
+
export type AngularBudgetsPluginOptions = {
|
|
40
|
+
readonly budgets: readonly Budget[];
|
|
41
|
+
/** Kept current by the Angular compile, which runs before each compilation. */
|
|
42
|
+
readonly componentStyleSizes: ReadonlyMap<string, ComponentStyleSize>;
|
|
43
|
+
readonly intermediateDir: string | undefined;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Checks the budgets after every compilation of the `lynx` environment,
|
|
47
|
+
* including watch rebuilds, once its bundles are final.
|
|
48
|
+
*/
|
|
49
|
+
export declare class AngularBudgetsPlugin {
|
|
50
|
+
#private;
|
|
51
|
+
constructor(options: AngularBudgetsPluginOptions);
|
|
52
|
+
apply(compiler: Rspack.Compiler): void;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Enforces angular.json's `budgets` on the `lynx` environment, the bundles
|
|
56
|
+
* devices load. The `web` environment only builds a browser preview, so its
|
|
57
|
+
* sizes say nothing about the app.
|
|
58
|
+
*/
|
|
59
|
+
export declare const applyBudgets: (api: Pick<RsbuildPluginAPI, "context" | "modifyBundlerChain" | "useExposed">, { budgets, componentStyleSizes, underAngularCli, }: {
|
|
60
|
+
readonly budgets: readonly Budget[];
|
|
61
|
+
readonly componentStyleSizes: ReadonlyMap<string, ComponentStyleSize>;
|
|
62
|
+
/** Whether `ng build` / `ng serve` resolved the build options. */
|
|
63
|
+
readonly underAngularCli: boolean;
|
|
64
|
+
}) => void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type HotApi, type ThreadContext } from './hmr-protocol.js';
|
|
2
|
+
/**
|
|
3
|
+
* How long the background thread waits for the main thread to apply an
|
|
4
|
+
* update. A main thread without the client, or one stuck in a module that
|
|
5
|
+
* never returns, would otherwise block every later update.
|
|
6
|
+
*/
|
|
7
|
+
export declare const MAIN_THREAD_TIMEOUT_MS = 60000;
|
|
8
|
+
type Log = (level: 'info' | 'warning', message: string) => void;
|
|
9
|
+
export type BackgroundHotUpdatesOptions = {
|
|
10
|
+
/** The background thread's `import.meta.webpackHot`. */
|
|
11
|
+
hot: HotApi;
|
|
12
|
+
/** Where the dev transport announces each new build's hash. */
|
|
13
|
+
emitter: {
|
|
14
|
+
on(event: 'webpackHotUpdate', listener: (hash: string) => void): void;
|
|
15
|
+
};
|
|
16
|
+
/** The hash of the code the background thread runs, `__webpack_hash__`. */
|
|
17
|
+
currentHash: () => string;
|
|
18
|
+
/** Lynx's `getCoreContext()`, which reaches the main thread. */
|
|
19
|
+
context: ThreadContext;
|
|
20
|
+
fetch: (url: string) => Promise<{
|
|
21
|
+
status: number;
|
|
22
|
+
text(): Promise<string>;
|
|
23
|
+
}>;
|
|
24
|
+
/** Reloads the whole page, through Lynx DevTool. */
|
|
25
|
+
reloadPage: () => void;
|
|
26
|
+
log: Log;
|
|
27
|
+
logApplyResult: (updatedModules: (string | number)[], renewedModules: (string | number)[]) => void;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Applies hot updates on both threads, replacing `@rspack/core/hot/dev-server`
|
|
31
|
+
* on the background thread.
|
|
32
|
+
*
|
|
33
|
+
* Webpack's hot update client runs on the background thread, where the dev
|
|
34
|
+
* server's connection is, and only updates the code there. But on Lynx the
|
|
35
|
+
* app runs on the main thread, which has no network access of its own (its
|
|
36
|
+
* `fetch` is the runtime's relay to this thread). So for each new
|
|
37
|
+
* build this:
|
|
38
|
+
*
|
|
39
|
+
* 1. applies the update on the background thread, which registers the
|
|
40
|
+
* updated modules' background functions again;
|
|
41
|
+
* 2. asks the main thread to apply its own half of the update (see
|
|
42
|
+
* main-thread-hot-updates.ts), and fetches the files it needs for it;
|
|
43
|
+
* 3. falls back to a full page reload if either thread can't apply it, so the
|
|
44
|
+
* screen never keeps showing old code.
|
|
45
|
+
*
|
|
46
|
+
* The background thread goes first so that a background function the updated
|
|
47
|
+
* app calls straight away is already the new one.
|
|
48
|
+
*/
|
|
49
|
+
export declare const startBackgroundHotUpdates: ({ hot, emitter, currentHash, context, fetch, reloadPage, log, logApplyResult, }: BackgroundHotUpdatesOptions) => void;
|
|
50
|
+
export {};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { HMR_CHECK_RESULT, HMR_FETCH_RESULT, HMR_CHECK, listen, HMR_FETCH, HMR_RELOAD, send, formatError } from "../544.js";
|
|
2
|
+
const MAIN_THREAD_TIMEOUT_MS = 60000;
|
|
3
|
+
const startBackgroundHotUpdates = ({ hot, emitter, currentHash, context, fetch, reloadPage, log, logApplyResult })=>{
|
|
4
|
+
listen(context, HMR_FETCH, ({ id, url })=>{
|
|
5
|
+
fetch(url).then(async (response)=>({
|
|
6
|
+
id,
|
|
7
|
+
status: response.status,
|
|
8
|
+
text: await response.text()
|
|
9
|
+
})).catch((error)=>({
|
|
10
|
+
id,
|
|
11
|
+
error: formatError(error)
|
|
12
|
+
})).then((result)=>send(context, HMR_FETCH_RESULT, result));
|
|
13
|
+
});
|
|
14
|
+
const checks = new Map();
|
|
15
|
+
let nextCheckId = 0;
|
|
16
|
+
listen(context, HMR_CHECK_RESULT, (result)=>{
|
|
17
|
+
checks.get(result.id)?.(result);
|
|
18
|
+
});
|
|
19
|
+
const checkMainThread = (hash)=>{
|
|
20
|
+
const id = nextCheckId++;
|
|
21
|
+
return new Promise((resolve)=>{
|
|
22
|
+
const timer = setTimeout(()=>finish({
|
|
23
|
+
id,
|
|
24
|
+
ok: false,
|
|
25
|
+
reason: `The main thread didn't apply the update within ${MAIN_THREAD_TIMEOUT_MS / 1000}s.`
|
|
26
|
+
}), MAIN_THREAD_TIMEOUT_MS);
|
|
27
|
+
const finish = (result)=>{
|
|
28
|
+
clearTimeout(timer);
|
|
29
|
+
checks.delete(id);
|
|
30
|
+
resolve(result);
|
|
31
|
+
};
|
|
32
|
+
checks.set(id, finish);
|
|
33
|
+
send(context, HMR_CHECK, {
|
|
34
|
+
id,
|
|
35
|
+
hash
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
const fullReload = (reason)=>{
|
|
40
|
+
log('warning', '[HMR] Cannot apply update. Need to do a full reload!');
|
|
41
|
+
log('warning', `[HMR] ${reason}`);
|
|
42
|
+
reloadPage();
|
|
43
|
+
};
|
|
44
|
+
listen(context, HMR_RELOAD, ({ reason })=>fullReload(`Main thread: ${reason}`));
|
|
45
|
+
let lastHash = '';
|
|
46
|
+
let checking = false;
|
|
47
|
+
const upToDate = ()=>lastHash.includes(currentHash());
|
|
48
|
+
const check = async ()=>{
|
|
49
|
+
const hash = lastHash;
|
|
50
|
+
let updatedModules;
|
|
51
|
+
try {
|
|
52
|
+
updatedModules = await hot.check(true);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
const status = hot.status();
|
|
55
|
+
if ('abort' === status || 'fail' === status) fullReload(formatError(error));
|
|
56
|
+
else log('warning', `[HMR] Update failed: ${formatError(error)}`);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (!updatedModules) return void fullReload('Cannot find update.');
|
|
60
|
+
const mainThread = await checkMainThread(hash);
|
|
61
|
+
if (!mainThread.ok) return void fullReload(`Main thread: ${mainThread.reason}`);
|
|
62
|
+
logApplyResult(updatedModules, updatedModules);
|
|
63
|
+
if (!upToDate()) return void await check();
|
|
64
|
+
log('info', '[HMR] App is up to date.');
|
|
65
|
+
};
|
|
66
|
+
emitter.on('webpackHotUpdate', (hash)=>{
|
|
67
|
+
lastHash = hash;
|
|
68
|
+
if (upToDate() || checking || 'idle' !== hot.status()) return;
|
|
69
|
+
log('info', '[HMR] Checking for updates on the server...');
|
|
70
|
+
checking = true;
|
|
71
|
+
check().finally(()=>{
|
|
72
|
+
checking = false;
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
log('info', '[HMR] Waiting for update signal from WDS...');
|
|
76
|
+
};
|
|
77
|
+
export { MAIN_THREAD_TIMEOUT_MS, startBackgroundHotUpdates };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const { createCssHotUpdate } = require('./css-hot-update.js');
|
|
3
|
+
module.exports = createCssHotUpdate({
|
|
4
|
+
isMainThread: __MAIN_THREAD__,
|
|
5
|
+
lynx,
|
|
6
|
+
publicPath: __webpack_require__.p,
|
|
7
|
+
cssHotUpdateList: ()=>__webpack_require__.cssHotUpdateList,
|
|
8
|
+
flushElementTree: ()=>__FlushElementTree()
|
|
9
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type ThreadContext } from './hmr-protocol.js';
|
|
2
|
+
/** The CSS hot update file css-extract writes for each chunk. */
|
|
3
|
+
type CssHotUpdate = {
|
|
4
|
+
/** The chunk's whole stylesheet, encoded as Lynx's CSS binary in base64. */
|
|
5
|
+
content?: string;
|
|
6
|
+
/** For each CSS ID, the IDs of the stylesheets that import it. */
|
|
7
|
+
deps: Record<string, string[]>;
|
|
8
|
+
};
|
|
9
|
+
export type CssHotUpdateOptions = {
|
|
10
|
+
isMainThread: boolean;
|
|
11
|
+
lynx: {
|
|
12
|
+
requireModuleAsync?: (url: string, callback: (error: Error | null, update?: CssHotUpdate) => void) => void;
|
|
13
|
+
getDevtool?: () => {
|
|
14
|
+
replaceStyleSheetByIdWithBase64?: (id: number, base64: string) => void;
|
|
15
|
+
} | undefined;
|
|
16
|
+
getJSContext(): ThreadContext;
|
|
17
|
+
};
|
|
18
|
+
/** `__webpack_require__.p`, the dev server's address. */
|
|
19
|
+
publicPath: string;
|
|
20
|
+
/** `__webpack_require__.cssHotUpdateList`: each chunk's CSS update file. */
|
|
21
|
+
cssHotUpdateList: () => [chunkName: string, path: string][] | null;
|
|
22
|
+
flushElementTree: () => void;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Applies a stylesheet edit on the main thread. It replaces css-extract's
|
|
26
|
+
* `runtime/hotModuleReplacement.cjs` (entry.ts swaps the module), which every
|
|
27
|
+
* CSS module calls when a hot update replaces it.
|
|
28
|
+
*
|
|
29
|
+
* css-extract's version runs on the background thread: it loads the new
|
|
30
|
+
* stylesheet there and sends it to a main-thread listener React Lynx adds. In
|
|
31
|
+
* an AngularLynx app the main thread takes its own hot updates (see
|
|
32
|
+
* main-thread-hot-updates.ts), which include the CSS modules, so the stylesheet
|
|
33
|
+
* is loaded and applied there, with the same Lynx call React Lynx's listener
|
|
34
|
+
* makes. On the background thread the CSS modules do nothing.
|
|
35
|
+
*
|
|
36
|
+
* Lynx can't apply a stylesheet if `replaceStyleSheetByIdWithBase64` is
|
|
37
|
+
* missing, or the file can't be loaded. Then it asks the background thread to
|
|
38
|
+
* reload the page, rather than leave the old styles on screen.
|
|
39
|
+
*
|
|
40
|
+
* @returns css-extract's `update(moduleId, options, cssId)`, which returns the
|
|
41
|
+
* function each CSS module calls when it's replaced.
|
|
42
|
+
*/
|
|
43
|
+
export declare const createCssHotUpdate: ({ isMainThread, lynx, publicPath, cssHotUpdateList, flushElementTree, }: CssHotUpdateOptions) => (_moduleId: unknown, _options: unknown, cssId?: string) => (() => void);
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { send, HMR_RELOAD, formatError } from "../544.js";
|
|
2
|
+
const createCssHotUpdate = ({ isMainThread, lynx, publicPath, cssHotUpdateList, flushElementTree })=>{
|
|
3
|
+
const fail = (reason)=>send(lynx.getJSContext(), HMR_RELOAD, {
|
|
4
|
+
reason: `Can't apply the stylesheet update: ${reason}`
|
|
5
|
+
});
|
|
6
|
+
const apply = (update, ids)=>{
|
|
7
|
+
const { content } = update;
|
|
8
|
+
if (!content) return;
|
|
9
|
+
const devtool = lynx.getDevtool?.();
|
|
10
|
+
if (!devtool?.replaceStyleSheetByIdWithBase64) return void fail('lynx.getDevtool().replaceStyleSheetByIdWithBase64 is missing.');
|
|
11
|
+
try {
|
|
12
|
+
for (const id of ids){
|
|
13
|
+
for (const dependent of update.deps[id] ?? [])devtool.replaceStyleSheetByIdWithBase64(Number(dependent), content);
|
|
14
|
+
devtool.replaceStyleSheetByIdWithBase64(id, content);
|
|
15
|
+
}
|
|
16
|
+
flushElementTree();
|
|
17
|
+
} catch (error) {
|
|
18
|
+
fail(formatError(error));
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
const pending = new Set();
|
|
22
|
+
let timer;
|
|
23
|
+
const updateStyles = ()=>{
|
|
24
|
+
const ids = new Set(pending);
|
|
25
|
+
pending.clear();
|
|
26
|
+
timer = void 0;
|
|
27
|
+
for (const [, path] of cssHotUpdateList() ?? [])lynx.requireModuleAsync?.(publicPath + path, (error, update)=>{
|
|
28
|
+
if (error) return void fail(formatError(error));
|
|
29
|
+
apply(update, ids);
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
return (_moduleId, _options, cssId)=>{
|
|
33
|
+
if (!isMainThread) return ()=>{};
|
|
34
|
+
const id = Number(cssId || 0);
|
|
35
|
+
return ()=>{
|
|
36
|
+
pending.add(id);
|
|
37
|
+
if (void 0 !== timer) clearTimeout(timer);
|
|
38
|
+
timer = setTimeout(updateStyles, 50);
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
export { createCssHotUpdate };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { reloadPage } from '@lynx-js/webpack-dev-transport/reloadApp';
|
|
2
|
+
import { emitter } from '@rspack/core/hot/emitter.js';
|
|
3
|
+
import { logApplyResult } from '@rspack/core/hot/log-apply-result.js';
|
|
4
|
+
import { log } from '@rspack/core/hot/log.js';
|
|
5
|
+
import { startBackgroundHotUpdates } from './background-hot-updates.js';
|
|
6
|
+
if (!import.meta.webpackHot) throw new Error('[HMR] Hot Module Replacement is disabled.');
|
|
7
|
+
startBackgroundHotUpdates({
|
|
8
|
+
hot: import.meta.webpackHot,
|
|
9
|
+
emitter,
|
|
10
|
+
currentHash: ()=>__webpack_hash__,
|
|
11
|
+
context: lynx.getCoreContext(),
|
|
12
|
+
fetch: (url)=>fetch(url),
|
|
13
|
+
reloadPage,
|
|
14
|
+
log,
|
|
15
|
+
logApplyResult
|
|
16
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { startMainThreadHotUpdates } from './main-thread-hot-updates.js';
|
|
2
|
+
if (!import.meta.webpackHot) throw new Error('[HMR] Hot Module Replacement is disabled.');
|
|
3
|
+
startMainThreadHotUpdates({
|
|
4
|
+
hot: import.meta.webpackHot,
|
|
5
|
+
lynx,
|
|
6
|
+
context: lynx.getJSContext(),
|
|
7
|
+
currentBootstrap: ()=>globalThis.__LYNX_ANGULAR_BOOTSTRAP__,
|
|
8
|
+
appRunning: ()=>void 0 !== globalThis.__LYNX_ANGULAR_APP_REF__
|
|
9
|
+
});
|