@blotch/rsbuild-plugin-angular-lynx 22.2.2 → 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 +1 -1
- package/dist/544.js +21 -0
- package/dist/angular-rebuild-coordinator.d.ts +1 -1
- package/dist/angular.d.ts +3 -2
- package/dist/background-entry.d.ts +96 -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 +5 -35
- 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 +23 -2
- package/dist/hot-updates.d.ts +67 -0
- package/dist/hot-updates.test.d.ts +1 -0
- package/dist/index.js +718 -168
- 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/component-scope-id.d.ts +5 -3
- package/dist/utils/options.d.ts +40 -1
- package/package.json +1 -2
- package/dist/loaders/thread-globals-loader.d.ts +0 -8
- package/dist/loaders/thread-globals-loader.js +0 -6
- /package/dist/{loaders/thread-globals-loader.test.d.ts → background-entry.test.d.ts} +0 -0
package/README.md
CHANGED
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 };
|
|
@@ -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:
|
|
@@ -29,4 +30,4 @@ import type { PluginAngularLynxOptions } from './utils/options.js';
|
|
|
29
30
|
* string transform (import-prepend + ɵcmp.id + worklet) to
|
|
30
31
|
* `buildTransformedCode` in transform-module.ts.
|
|
31
32
|
*/
|
|
32
|
-
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,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
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The messages the two hot update clients exchange (hmr-background.ts and
|
|
3
|
+
* hmr-main-thread.ts). They travel over Lynx's cross-thread event channel:
|
|
4
|
+
* the background thread dispatches on `lynx.getCoreContext()` and the main
|
|
5
|
+
* thread listens on `lynx.getJSContext()`, and the reverse. Each payload is a
|
|
6
|
+
* JSON string, as in the runtime's other relays.
|
|
7
|
+
*/
|
|
8
|
+
/** Main → background: fetch a hot update file the main thread needs. */
|
|
9
|
+
export declare const HMR_FETCH = "AngularLynx.HmrFetch";
|
|
10
|
+
/** Background → main: the fetched file, or why it couldn't be fetched. */
|
|
11
|
+
export declare const HMR_FETCH_RESULT = "AngularLynx.HmrFetchResult";
|
|
12
|
+
/** Background → main: check for and apply the update to `hash`. */
|
|
13
|
+
export declare const HMR_CHECK = "AngularLynx.HmrCheck";
|
|
14
|
+
/** Main → background: whether the main thread applied it. */
|
|
15
|
+
export declare const HMR_CHECK_RESULT = "AngularLynx.HmrCheckResult";
|
|
16
|
+
/**
|
|
17
|
+
* Main → background: an update the main thread took in can't be shown, so
|
|
18
|
+
* reload the page. Sent by the stylesheet updater (css-hot-update.ts), which
|
|
19
|
+
* runs after the update itself has already succeeded.
|
|
20
|
+
*/
|
|
21
|
+
export declare const HMR_RELOAD = "AngularLynx.HmrReload";
|
|
22
|
+
export type FetchRequest = {
|
|
23
|
+
id: number;
|
|
24
|
+
url: string;
|
|
25
|
+
};
|
|
26
|
+
export type FetchResult = {
|
|
27
|
+
id: number;
|
|
28
|
+
status: number;
|
|
29
|
+
text: string;
|
|
30
|
+
} | {
|
|
31
|
+
id: number;
|
|
32
|
+
error: string;
|
|
33
|
+
};
|
|
34
|
+
export type CheckRequest = {
|
|
35
|
+
id: number;
|
|
36
|
+
hash: string;
|
|
37
|
+
};
|
|
38
|
+
export type ReloadRequest = {
|
|
39
|
+
reason: string;
|
|
40
|
+
};
|
|
41
|
+
/** Whether the main thread applied an update, and if not, why. */
|
|
42
|
+
export type CheckOutcome = {
|
|
43
|
+
ok: true;
|
|
44
|
+
updatedModules: number;
|
|
45
|
+
} | {
|
|
46
|
+
ok: false;
|
|
47
|
+
reason: string;
|
|
48
|
+
};
|
|
49
|
+
export type CheckResult = {
|
|
50
|
+
id: number;
|
|
51
|
+
} & CheckOutcome;
|
|
52
|
+
/** A thread's end of the cross-thread event channel. */
|
|
53
|
+
export type ThreadContext = {
|
|
54
|
+
addEventListener(type: string, listener: (event: {
|
|
55
|
+
data: string;
|
|
56
|
+
}) => void): void;
|
|
57
|
+
dispatchEvent(event: {
|
|
58
|
+
type: string;
|
|
59
|
+
data: string;
|
|
60
|
+
}): void;
|
|
61
|
+
};
|
|
62
|
+
/** The parts of webpack's `module.hot` API the clients use. */
|
|
63
|
+
export type HotApi = {
|
|
64
|
+
check(autoApply: true): Promise<(string | number)[] | null>;
|
|
65
|
+
status(): string;
|
|
66
|
+
};
|
|
67
|
+
export declare const send: (context: ThreadContext, type: string, message: object) => void;
|
|
68
|
+
export declare const listen: <T>(context: ThreadContext, type: string, listener: (message: T) => void) => void;
|
|
69
|
+
/**
|
|
70
|
+
* An error as text for the logs, with its stack. PrimJS, which runs the main
|
|
71
|
+
* thread, leaves the name and message out of `error.stack`, so they're added
|
|
72
|
+
* when the stack doesn't start with them.
|
|
73
|
+
*/
|
|
74
|
+
export declare const formatError: (error: unknown) => string;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type HotApi, type ThreadContext } from './hmr-protocol.js';
|
|
2
|
+
/** Lynx's `requireModuleAsync`, which the hot update runtime loads with. */
|
|
3
|
+
type RequireModuleAsync = (url: string, callback: (error: (Error & {
|
|
4
|
+
code?: string;
|
|
5
|
+
}) | null, exports?: unknown) => void) => void;
|
|
6
|
+
export type MainThreadHotUpdatesOptions = {
|
|
7
|
+
/** The main thread's `import.meta.webpackHot`. */
|
|
8
|
+
hot: HotApi;
|
|
9
|
+
/** The main thread's `lynx` global. */
|
|
10
|
+
lynx: {
|
|
11
|
+
requireModuleAsync?: RequireModuleAsync;
|
|
12
|
+
};
|
|
13
|
+
/** Lynx's `getJSContext()`, which reaches the background thread. */
|
|
14
|
+
context: ThreadContext;
|
|
15
|
+
/**
|
|
16
|
+
* Where `bootstrapApplication()` leaves its latest promise. Read through a
|
|
17
|
+
* function because each call replaces it.
|
|
18
|
+
*/
|
|
19
|
+
currentBootstrap: () => unknown;
|
|
20
|
+
/** Whether an app is running: the last bootstrap succeeded. */
|
|
21
|
+
appRunning: () => boolean;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Applies the main thread's half of each hot update, when the background
|
|
25
|
+
* thread asks (see background-hot-updates.ts).
|
|
26
|
+
*
|
|
27
|
+
* Webpack's hot update runtime is already in the main-thread bundle, with
|
|
28
|
+
* `module.hot` on every module, but nothing ever asked it to check, and it
|
|
29
|
+
* loads updates with `lynx.requireModuleAsync()`, which the main thread
|
|
30
|
+
* doesn't have. This gives the main thread one that has the background
|
|
31
|
+
* thread fetch the file (only it has network access) and runs it here, then
|
|
32
|
+
* runs the checks.
|
|
33
|
+
*
|
|
34
|
+
* After an update, it waits for the app to bootstrap again if the update
|
|
35
|
+
* re-ran the app's entry: the old app is gone by then, so a failure has to
|
|
36
|
+
* reload the page rather than leave it blank. It reports failure too when no
|
|
37
|
+
* app is running at all.
|
|
38
|
+
*/
|
|
39
|
+
export declare const startMainThreadHotUpdates: ({ hot, lynx, context, currentBootstrap, appRunning, }: MainThreadHotUpdatesOptions) => void;
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { HMR_CHECK_RESULT, HMR_FETCH_RESULT, HMR_CHECK, send, listen, formatError, HMR_FETCH } from "../544.js";
|
|
2
|
+
const evaluateChunk = (source, url)=>{
|
|
3
|
+
const module = {
|
|
4
|
+
exports: {}
|
|
5
|
+
};
|
|
6
|
+
new Function('module', 'exports', `${source}\n//# sourceURL=${url}`)(module, module.exports);
|
|
7
|
+
return module.exports;
|
|
8
|
+
};
|
|
9
|
+
const startMainThreadHotUpdates = ({ hot, lynx, context, currentBootstrap, appRunning })=>{
|
|
10
|
+
const fetches = new Map();
|
|
11
|
+
let nextFetchId = 0;
|
|
12
|
+
listen(context, HMR_FETCH_RESULT, (result)=>{
|
|
13
|
+
fetches.get(result.id)?.(result);
|
|
14
|
+
fetches.delete(result.id);
|
|
15
|
+
});
|
|
16
|
+
lynx.requireModuleAsync ??= (url, callback)=>{
|
|
17
|
+
const id = nextFetchId++;
|
|
18
|
+
fetches.set(id, (result)=>{
|
|
19
|
+
if ('error' in result) return void callback(new Error(`Loading ${url} failed: ${result.error}`));
|
|
20
|
+
if (404 === result.status) {
|
|
21
|
+
const error = new Error(`Cannot find module '${url}'`);
|
|
22
|
+
error.code = 'MODULE_NOT_FOUND';
|
|
23
|
+
callback(error);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (result.status >= 400) return void callback(new Error(`Loading ${url} failed with HTTP ${result.status}`));
|
|
27
|
+
let exports;
|
|
28
|
+
try {
|
|
29
|
+
exports = url.endsWith('.json') ? JSON.parse(result.text) : evaluateChunk(result.text, url);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
callback(error);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
callback(null, exports);
|
|
35
|
+
});
|
|
36
|
+
send(context, HMR_FETCH, {
|
|
37
|
+
id,
|
|
38
|
+
url
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
const apply = async ()=>{
|
|
42
|
+
const status = hot.status();
|
|
43
|
+
if ('idle' !== status) return {
|
|
44
|
+
ok: false,
|
|
45
|
+
reason: `The hot update status is "${status}".`
|
|
46
|
+
};
|
|
47
|
+
const bootstrapBefore = currentBootstrap();
|
|
48
|
+
try {
|
|
49
|
+
const updatedModules = await hot.check(true);
|
|
50
|
+
if (!updatedModules) return {
|
|
51
|
+
ok: false,
|
|
52
|
+
reason: 'Cannot find update.'
|
|
53
|
+
};
|
|
54
|
+
const bootstrap = currentBootstrap();
|
|
55
|
+
if (bootstrap !== bootstrapBefore) await bootstrap;
|
|
56
|
+
if (!appRunning()) return {
|
|
57
|
+
ok: false,
|
|
58
|
+
reason: 'The app is not running.'
|
|
59
|
+
};
|
|
60
|
+
return {
|
|
61
|
+
ok: true,
|
|
62
|
+
updatedModules: updatedModules.length
|
|
63
|
+
};
|
|
64
|
+
} catch (error) {
|
|
65
|
+
return {
|
|
66
|
+
ok: false,
|
|
67
|
+
reason: formatError(error)
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
listen(context, HMR_CHECK, ({ id })=>{
|
|
72
|
+
apply().then((result)=>send(context, HMR_CHECK_RESULT, {
|
|
73
|
+
id,
|
|
74
|
+
...result
|
|
75
|
+
}));
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
export { startMainThreadHotUpdates };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ThreadContext } from '../hmr-protocol.js';
|
|
2
|
+
/**
|
|
3
|
+
* One thread's end of the cross-thread channel, for the client tests. It
|
|
4
|
+
* records what the code under test sends, and `receive()` plays a message
|
|
5
|
+
* from the other thread.
|
|
6
|
+
*/
|
|
7
|
+
export declare const createTestContext: () => {
|
|
8
|
+
context: ThreadContext;
|
|
9
|
+
sent: {
|
|
10
|
+
type: string;
|
|
11
|
+
message: any;
|
|
12
|
+
}[];
|
|
13
|
+
/** The messages of one type, in order. */
|
|
14
|
+
sentOf: (type: string) => any[];
|
|
15
|
+
receive: (type: string, message: object) => void;
|
|
16
|
+
};
|