@sigx/lynx-plugin 0.4.0 → 0.4.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/css.js +150 -0
- package/dist/entry.js +443 -0
- package/dist/icons.d.ts +25 -5
- package/dist/icons.js +301 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +395 -434
- package/dist/layers.js +5 -0
- package/dist/loaders/hmr-loader.js +70 -19
- package/dist/loaders/ignore-css-loader.js +16 -7
- package/dist/loaders/worklet-loader-mt.js +142 -62
- package/dist/loaders/worklet-loader.js +69 -31
- package/dist/loaders/worklet-utils.d.ts +0 -15
- package/dist/loaders/worklet-utils.js +116 -0
- package/dist/log-server.d.ts +0 -0
- package/dist/log-server.js +0 -0
- package/package.json +14 -12
- package/dist/index.js.map +0 -1
- package/dist/loaders/hmr-loader.js.map +0 -1
- package/dist/loaders/ignore-css-loader.js.map +0 -1
- package/dist/loaders/worklet-loader-mt.js.map +0 -1
- package/dist/loaders/worklet-loader.js.map +0 -1
package/dist/css.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSS extraction pipeline for SignalX Lynx.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the behaviour of `@lynx-js/react-rsbuild-plugin`'s `applyCSS()`:
|
|
5
|
+
* 1. Disables `style-loader` (forces CSS extraction via CssExtractPlugin).
|
|
6
|
+
* 2. Replaces the rsbuild-default CssExtract plugin with
|
|
7
|
+
* `@lynx-js/css-extract-webpack-plugin` which emits Lynx-compatible CSS.
|
|
8
|
+
* 3. Removes `lightningcss-loader` (Lynx has its own CSS processor).
|
|
9
|
+
* 4. Configures the Main-Thread layer to ignore CSS entirely.
|
|
10
|
+
*/
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
import { LAYERS } from './layers.js';
|
|
14
|
+
const _dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
export function applyCSS(api, options) {
|
|
16
|
+
const { enableCSSSelector, enableCSSInvalidation } = options;
|
|
17
|
+
// ① Force CSS extraction (disable style-loader, enable CssExtractPlugin).
|
|
18
|
+
// Without this, rsbuild injects CSS via JS — useless in Lynx's native env.
|
|
19
|
+
api.modifyRsbuildConfig((config, { mergeRsbuildConfig }) => {
|
|
20
|
+
return mergeRsbuildConfig(config, {
|
|
21
|
+
output: { injectStyles: false },
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
// ② Replace the rsbuild-default CSS extraction plugin with the Lynx-aware
|
|
25
|
+
// one, configure loaders per layer, and remove lightningcss.
|
|
26
|
+
api.modifyBundlerChain(async function handler(chain, { CHAIN_ID }) {
|
|
27
|
+
const { CssExtractRspackPlugin, CssExtractWebpackPlugin } = await import('@lynx-js/css-extract-webpack-plugin');
|
|
28
|
+
const CssExtractPlugin = api.context.bundlerType === 'rspack'
|
|
29
|
+
? CssExtractRspackPlugin
|
|
30
|
+
: CssExtractWebpackPlugin;
|
|
31
|
+
const cssRules = [
|
|
32
|
+
CHAIN_ID.RULE.CSS,
|
|
33
|
+
CHAIN_ID.RULE.SASS,
|
|
34
|
+
CHAIN_ID.RULE.LESS,
|
|
35
|
+
CHAIN_ID.RULE.STYLUS,
|
|
36
|
+
];
|
|
37
|
+
cssRules
|
|
38
|
+
.filter((rule) => chain.module.rules.has(rule))
|
|
39
|
+
.forEach((ruleName) => {
|
|
40
|
+
const rule = chain.module.rule(ruleName);
|
|
41
|
+
// Remove lightningcss-loader — Lynx processes CSS natively.
|
|
42
|
+
removeLightningCSS(rule, CHAIN_ID);
|
|
43
|
+
// Use the Lynx CssExtract loader for the Background layer.
|
|
44
|
+
rule
|
|
45
|
+
.issuerLayer(LAYERS.BACKGROUND)
|
|
46
|
+
.use(CHAIN_ID.USE.MINI_CSS_EXTRACT)
|
|
47
|
+
.loader(CssExtractPlugin.loader)
|
|
48
|
+
.end();
|
|
49
|
+
// Clone the existing CSS rule chain for the Main-Thread layer.
|
|
50
|
+
// Main-Thread bundles never contain user CSS — only the PAPI
|
|
51
|
+
// bootstrap code. We replace all loaders with ignore-css + a
|
|
52
|
+
// css-loader configured for `exportOnlyLocals: true`.
|
|
53
|
+
const uses = rule.uses.entries();
|
|
54
|
+
const ruleEntries = rule.entries();
|
|
55
|
+
const cssLoaderRule = uses[CHAIN_ID.USE.CSS]?.entries();
|
|
56
|
+
chain.module
|
|
57
|
+
.rule(`${ruleName}:${LAYERS.MAIN_THREAD}`)
|
|
58
|
+
.merge(ruleEntries)
|
|
59
|
+
.issuerLayer(LAYERS.MAIN_THREAD)
|
|
60
|
+
.use(CHAIN_ID.USE.IGNORE_CSS)
|
|
61
|
+
.loader(path.resolve(_dirname, './loaders/ignore-css-loader'))
|
|
62
|
+
.end()
|
|
63
|
+
.uses.merge(uses)
|
|
64
|
+
.delete(CHAIN_ID.USE.MINI_CSS_EXTRACT)
|
|
65
|
+
.delete(CHAIN_ID.USE.LIGHTNINGCSS)
|
|
66
|
+
.delete(CHAIN_ID.USE.CSS)
|
|
67
|
+
.end();
|
|
68
|
+
// Re-add css-loader with exportOnlyLocals for main-thread
|
|
69
|
+
if (cssLoaderRule) {
|
|
70
|
+
chain.module
|
|
71
|
+
.rule(`${ruleName}:${LAYERS.MAIN_THREAD}`)
|
|
72
|
+
.use(CHAIN_ID.USE.CSS)
|
|
73
|
+
.after(CHAIN_ID.USE.IGNORE_CSS)
|
|
74
|
+
.merge(cssLoaderRule)
|
|
75
|
+
.options(normalizeCssLoaderOptions(cssLoaderRule.options, true))
|
|
76
|
+
.end();
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
// Also strip lightningcss from inline CSS rules (Rsbuild ≥1.3.0).
|
|
80
|
+
const RULE = CHAIN_ID.RULE;
|
|
81
|
+
const inlineCSSRuleNames = [
|
|
82
|
+
'CSS_INLINE',
|
|
83
|
+
'SASS_INLINE',
|
|
84
|
+
'LESS_INLINE',
|
|
85
|
+
'STYLUS_INLINE',
|
|
86
|
+
];
|
|
87
|
+
inlineCSSRuleNames
|
|
88
|
+
.map((key) => RULE[key])
|
|
89
|
+
.filter((ruleName) => !!ruleName && chain.module.rules.has(ruleName))
|
|
90
|
+
.forEach((ruleName) => {
|
|
91
|
+
removeLightningCSS(chain.module.rule(ruleName), CHAIN_ID);
|
|
92
|
+
});
|
|
93
|
+
// ③ Replace the CssExtract plugin instance with the Lynx-aware one
|
|
94
|
+
// and pass through the CSS selector / invalidation options.
|
|
95
|
+
chain
|
|
96
|
+
.plugin(CHAIN_ID.PLUGIN.MINI_CSS_EXTRACT)
|
|
97
|
+
.tap((args) => {
|
|
98
|
+
const [pluginOptions] = args;
|
|
99
|
+
return [
|
|
100
|
+
{
|
|
101
|
+
...pluginOptions,
|
|
102
|
+
enableRemoveCSSScope: true,
|
|
103
|
+
enableCSSSelector,
|
|
104
|
+
enableCSSInvalidation,
|
|
105
|
+
cssPlugins: [],
|
|
106
|
+
},
|
|
107
|
+
];
|
|
108
|
+
})
|
|
109
|
+
.init((_, args) => {
|
|
110
|
+
return new CssExtractPlugin(...args);
|
|
111
|
+
})
|
|
112
|
+
.end()
|
|
113
|
+
.end();
|
|
114
|
+
function removeLightningCSS(rule, ids) {
|
|
115
|
+
if (rule.uses.has(ids.USE.LIGHTNINGCSS)) {
|
|
116
|
+
rule.uses.delete(ids.USE.LIGHTNINGCSS);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Force `exportOnlyLocals: true` on the css-loader modules config.
|
|
123
|
+
* Copied from rsbuild internals — required when the target is not `web`
|
|
124
|
+
* and CSS modules are enabled.
|
|
125
|
+
*/
|
|
126
|
+
const normalizeCssLoaderOptions = (options, exportOnlyLocals) => {
|
|
127
|
+
if (options.modules && exportOnlyLocals) {
|
|
128
|
+
let { modules } = options;
|
|
129
|
+
if (modules === true) {
|
|
130
|
+
modules = { exportOnlyLocals: true };
|
|
131
|
+
}
|
|
132
|
+
else if (typeof modules === 'string') {
|
|
133
|
+
modules = {
|
|
134
|
+
mode: modules,
|
|
135
|
+
exportOnlyLocals: true,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
modules = {
|
|
140
|
+
...modules,
|
|
141
|
+
exportOnlyLocals: true,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
...options,
|
|
146
|
+
modules,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
return options;
|
|
150
|
+
};
|
package/dist/entry.js
ADDED
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dual-thread entry splitting for SignalX Lynx.
|
|
3
|
+
*
|
|
4
|
+
* For each user-defined rsbuild entry, creates two webpack entries:
|
|
5
|
+
* - `<name>__main-thread` on the MAIN_THREAD layer (PAPI bootstrap via @sigx/lynx-runtime-main)
|
|
6
|
+
* - `<name>` on the BACKGROUND layer (sigx renderer + user app)
|
|
7
|
+
*
|
|
8
|
+
* Then registers @lynx-js/template-webpack-plugin to stitch both bundles
|
|
9
|
+
* into a single .lynx template.
|
|
10
|
+
*/
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { existsSync } from 'node:fs';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
import { createRequire } from 'node:module';
|
|
15
|
+
import { LAYERS } from './layers.js';
|
|
16
|
+
const PLUGIN_TEMPLATE = 'lynx:sigx-template';
|
|
17
|
+
const PLUGIN_MARK_MAIN_THREAD = 'lynx:sigx-mark-main-thread';
|
|
18
|
+
const PLUGIN_ENCODE = 'lynx:sigx-encode';
|
|
19
|
+
const DEFAULT_INTERMEDIATE = '.rspeedy';
|
|
20
|
+
const _dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
// sigx lynx-plugin package root — the plugin lives at <pkgRoot>/dist/,
|
|
22
|
+
// so we resolve one level up from _dirname.
|
|
23
|
+
const sigxLynxRoot = path.resolve(_dirname, '..');
|
|
24
|
+
/**
|
|
25
|
+
* SigxMarkMainThreadPlugin forces webpack to generate startup code for MT
|
|
26
|
+
* entry chunks and marks their assets with `lynx:main-thread: true` so
|
|
27
|
+
* LynxTemplatePlugin routes them to lepusCode.root (Lepus bytecode).
|
|
28
|
+
*/
|
|
29
|
+
class SigxMarkMainThreadPlugin {
|
|
30
|
+
mainThreadFilenames;
|
|
31
|
+
constructor(mainThreadFilenames) {
|
|
32
|
+
this.mainThreadFilenames = mainThreadFilenames;
|
|
33
|
+
}
|
|
34
|
+
apply(compiler) {
|
|
35
|
+
const { RuntimeGlobals } = compiler.webpack;
|
|
36
|
+
compiler.hooks.thisCompilation.tap(PLUGIN_MARK_MAIN_THREAD, (compilation) => {
|
|
37
|
+
// Force startup code generation for MT entry chunks.
|
|
38
|
+
compilation.hooks.additionalTreeRuntimeRequirements.tap(PLUGIN_MARK_MAIN_THREAD, (chunk, set) => {
|
|
39
|
+
const entryOptions = chunk.getEntryOptions();
|
|
40
|
+
if (entryOptions?.layer === LAYERS.MAIN_THREAD) {
|
|
41
|
+
set.add(RuntimeGlobals.startup);
|
|
42
|
+
set.add(RuntimeGlobals.require);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
// Mark MT assets with lynx:main-thread: true for LynxTemplatePlugin.
|
|
46
|
+
compilation.hooks.processAssets.tap({
|
|
47
|
+
name: PLUGIN_MARK_MAIN_THREAD,
|
|
48
|
+
stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL,
|
|
49
|
+
}, () => {
|
|
50
|
+
for (const filename of this.mainThreadFilenames) {
|
|
51
|
+
const asset = compilation.getAsset(filename);
|
|
52
|
+
if (asset) {
|
|
53
|
+
compilation.updateAsset(filename, asset.source, {
|
|
54
|
+
...asset.info,
|
|
55
|
+
'lynx:main-thread': true,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
export async function applyEntry(api, opts = {}) {
|
|
64
|
+
// Preload @lynx-js/template-webpack-plugin via dynamic ESM import.
|
|
65
|
+
// rsbuild bundlerChain callbacks are sync, and template-webpack-plugin
|
|
66
|
+
// is pure-ESM (no "require" condition in its exports map), so createRequire
|
|
67
|
+
// fails. Stash the module in closure scope for the sync callback below.
|
|
68
|
+
let templateMod;
|
|
69
|
+
try {
|
|
70
|
+
templateMod = await import('@lynx-js/template-webpack-plugin');
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// Optional peer — if missing, we'll still emit the two JS bundles.
|
|
74
|
+
}
|
|
75
|
+
// Preload @lynx-js/runtime-wrapper-webpack-plugin. This wraps the BG bundle
|
|
76
|
+
// in `__init_card_bundle__(lynxCoreInject, lynx, ...)` so user code inside
|
|
77
|
+
// can reference `lynx` and `lynxCoreInject` as bare identifiers — that's
|
|
78
|
+
// how the BG transport (lynx.getNativeApp().callLepusMethod) and the event
|
|
79
|
+
// dispatcher (lynxCoreInject.tt.publishEvent) get installed properly.
|
|
80
|
+
// Without this wrapper we'd be forced to spelunk through globalThis.multiApps.
|
|
81
|
+
let wrapperMod;
|
|
82
|
+
try {
|
|
83
|
+
wrapperMod = (await import('@lynx-js/runtime-wrapper-webpack-plugin'));
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// Optional peer — if missing, lynx-runtime will still attempt the
|
|
87
|
+
// multiApps[appId]._nativeApp fallback, but proper hosts need the wrapper.
|
|
88
|
+
}
|
|
89
|
+
// Default to all-in-one chunk splitting to avoid async chunks that break
|
|
90
|
+
// Lynx's single-file bundle requirement.
|
|
91
|
+
api.modifyRsbuildConfig((config, { mergeRsbuildConfig }) => {
|
|
92
|
+
const userConfig = api.getRsbuildConfig('original');
|
|
93
|
+
if (!userConfig.performance?.chunkSplit?.strategy) {
|
|
94
|
+
return mergeRsbuildConfig(config, {
|
|
95
|
+
performance: { chunkSplit: { strategy: 'all-in-one' } },
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return config;
|
|
99
|
+
});
|
|
100
|
+
// Exclude main-thread chunks from chunk splitting so each remains
|
|
101
|
+
// self-contained.
|
|
102
|
+
api.modifyRspackConfig((rspackConfig) => {
|
|
103
|
+
if (!rspackConfig.optimization)
|
|
104
|
+
return rspackConfig;
|
|
105
|
+
if (rspackConfig.optimization.splitChunks === false) {
|
|
106
|
+
rspackConfig.optimization.splitChunks = {};
|
|
107
|
+
}
|
|
108
|
+
if (rspackConfig.optimization.splitChunks) {
|
|
109
|
+
const prev = rspackConfig.optimization.splitChunks.chunks;
|
|
110
|
+
// biome-ignore lint/suspicious/noExplicitAny: rspack Chunk type not importable
|
|
111
|
+
rspackConfig.optimization.splitChunks.chunks = (chunk) => {
|
|
112
|
+
if (chunk.name?.includes('__main-thread'))
|
|
113
|
+
return false;
|
|
114
|
+
if (typeof prev === 'function')
|
|
115
|
+
return prev(chunk);
|
|
116
|
+
if (prev === 'all')
|
|
117
|
+
return true;
|
|
118
|
+
if (prev === 'initial')
|
|
119
|
+
return true;
|
|
120
|
+
return false;
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
return rspackConfig;
|
|
124
|
+
});
|
|
125
|
+
// Preload `@sigx/lynx-dev-client/install` — the JS-side console streamer.
|
|
126
|
+
// We resolve it eagerly (rather than relying on the bundler's resolver)
|
|
127
|
+
// so that:
|
|
128
|
+
// * absence of the package is detected once at config time (consumer may
|
|
129
|
+
// not depend on `@sigx/lynx-dev-client`), and
|
|
130
|
+
// * we can pass an absolute path to rspack's entry, sidestepping any
|
|
131
|
+
// subpath-export quirks.
|
|
132
|
+
//
|
|
133
|
+
// In linked / monorepo setups the plugin can live anywhere on disk, so we
|
|
134
|
+
// try multiple resolution bases — `api.context.rootPath`, the current
|
|
135
|
+
// process cwd, and finally the plugin's own location — and stop at the
|
|
136
|
+
// first one that finds it. This covers monorepo workspaces where the
|
|
137
|
+
// dev-client is hoisted to the workspace root as well as per-app installs.
|
|
138
|
+
//
|
|
139
|
+
// Returns `undefined` if the package isn't installed — the BG entry is
|
|
140
|
+
// then left alone and log streaming is a silent no-op for that project.
|
|
141
|
+
const resolveBases = [
|
|
142
|
+
path.join(api.context.rootPath, 'package.json'),
|
|
143
|
+
path.join(process.cwd(), 'package.json'),
|
|
144
|
+
];
|
|
145
|
+
let devClientInstallPath;
|
|
146
|
+
for (const base of resolveBases) {
|
|
147
|
+
try {
|
|
148
|
+
devClientInstallPath = createRequire(base).resolve('@sigx/lynx-dev-client/install');
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
// Subpath export may only declare `import` (Node CJS resolver wants
|
|
153
|
+
// `require`/`default`). Fall back to locating package.json and
|
|
154
|
+
// hand-constructing the path to dist/install.js.
|
|
155
|
+
try {
|
|
156
|
+
const pkgJson = createRequire(base).resolve('@sigx/lynx-dev-client/package.json');
|
|
157
|
+
const candidate = path.join(path.dirname(pkgJson), 'dist', 'install.js');
|
|
158
|
+
if (existsSync(candidate)) {
|
|
159
|
+
devClientInstallPath = candidate;
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
// try next base
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (!devClientInstallPath) {
|
|
169
|
+
try {
|
|
170
|
+
devClientInstallPath = createRequire(import.meta.url).resolve('@sigx/lynx-dev-client/install');
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
devClientInstallPath = undefined;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (devClientInstallPath) {
|
|
177
|
+
api.logger.info(`[sigx-lynx] device console log streaming → enabled`);
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
api.logger.warn(`[sigx-lynx] device console log streaming → disabled (install @sigx/lynx-dev-client as a devDependency of this app). rootPath=${api.context.rootPath}, cwd=${process.cwd()}`);
|
|
181
|
+
}
|
|
182
|
+
api.modifyBundlerChain((chain, { environment, isProd }) => {
|
|
183
|
+
const isRspeedy = api.context.callerName === 'rspeedy';
|
|
184
|
+
if (!isRspeedy)
|
|
185
|
+
return;
|
|
186
|
+
const isDev = !isProd;
|
|
187
|
+
const isLynx = environment.name === 'lynx' || environment.name.startsWith('lynx-');
|
|
188
|
+
const isWeb = environment.name === 'web' || environment.name.startsWith('web-');
|
|
189
|
+
// HMR / Live Reload flags (same logic as vue-lynx / React plugin)
|
|
190
|
+
const { hmr, liveReload } = environment.config.dev ?? {};
|
|
191
|
+
const enabledHMR = isDev && !isWeb && hmr !== false;
|
|
192
|
+
const enabledLiveReload = isDev && !isWeb && liveReload !== false;
|
|
193
|
+
const entries = chain.entryPoints.entries() ?? {};
|
|
194
|
+
chain.entryPoints.clear();
|
|
195
|
+
// Collect all main-thread filenames to mark with lynx:main-thread
|
|
196
|
+
const mainThreadFilenames = [];
|
|
197
|
+
for (const [entryName, entryPoint] of Object.entries(entries)) {
|
|
198
|
+
// Collect user imports from the original entry
|
|
199
|
+
const imports = [];
|
|
200
|
+
const ep = entryPoint;
|
|
201
|
+
for (const val of ep.values()) {
|
|
202
|
+
if (typeof val === 'string') {
|
|
203
|
+
imports.push(val);
|
|
204
|
+
}
|
|
205
|
+
else if (typeof val === 'object' && val !== null && 'import' in val) {
|
|
206
|
+
const imp = val.import;
|
|
207
|
+
if (Array.isArray(imp))
|
|
208
|
+
imports.push(...imp);
|
|
209
|
+
else if (imp)
|
|
210
|
+
imports.push(imp);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// ----------------------------------------------------------------
|
|
214
|
+
// Filenames
|
|
215
|
+
// ----------------------------------------------------------------
|
|
216
|
+
const intermediate = isLynx ? DEFAULT_INTERMEDIATE : '';
|
|
217
|
+
const mainThreadEntry = `${entryName}__main-thread`;
|
|
218
|
+
const mainThreadName = path.posix.join(intermediate, `${entryName}/main-thread.js`);
|
|
219
|
+
const backgroundName = path.posix.join(intermediate, `${entryName}/background${isProd ? '.[contenthash:8]' : ''}.js`);
|
|
220
|
+
if (isLynx || isWeb) {
|
|
221
|
+
mainThreadFilenames.push(mainThreadName);
|
|
222
|
+
}
|
|
223
|
+
// ----------------------------------------------------------------
|
|
224
|
+
// Main Thread bundle – PAPI bootstrap only
|
|
225
|
+
// ----------------------------------------------------------------
|
|
226
|
+
// The MT entry ONLY imports @sigx/lynx-runtime-main, which registers
|
|
227
|
+
// globalThis.renderPage, processData, sigxPatchUpdate and bridges
|
|
228
|
+
// ops from the background thread.
|
|
229
|
+
//
|
|
230
|
+
// MT bundle evaluation order (critical):
|
|
231
|
+
// The bootstrap (entry-main → worklet-runtime → install-hybrid-worklet)
|
|
232
|
+
// is prepended to every user file by `worklet-loader-mt.ts` using
|
|
233
|
+
// absolute paths resolved from the loader's install location. That
|
|
234
|
+
// means we DON'T list those modules here as entry imports — the dep
|
|
235
|
+
// graph that the loader-emitted preamble creates pulls them in, in
|
|
236
|
+
// the right order, without forcing the user's app package.json to
|
|
237
|
+
// declare @lynx-js/react as a direct dep.
|
|
238
|
+
//
|
|
239
|
+
// So the MT entry list is just: user imports. (CSS HMR runtime in
|
|
240
|
+
// dev mode only.) Worklet registrations land via the dep graph.
|
|
241
|
+
const mainThreadImports = !enabledHMR
|
|
242
|
+
? [...imports]
|
|
243
|
+
: [
|
|
244
|
+
'@lynx-js/css-extract-webpack-plugin/runtime/hotModuleReplacement.lepus.cjs',
|
|
245
|
+
...imports,
|
|
246
|
+
];
|
|
247
|
+
chain
|
|
248
|
+
.entry(mainThreadEntry)
|
|
249
|
+
.add({
|
|
250
|
+
layer: LAYERS.MAIN_THREAD,
|
|
251
|
+
import: mainThreadImports,
|
|
252
|
+
filename: mainThreadName,
|
|
253
|
+
})
|
|
254
|
+
.end();
|
|
255
|
+
// ----------------------------------------------------------------
|
|
256
|
+
// Background bundle – sigx renderer + user app
|
|
257
|
+
// ----------------------------------------------------------------
|
|
258
|
+
const bgImports = [];
|
|
259
|
+
bgImports.push(...imports);
|
|
260
|
+
const bgEntry = chain
|
|
261
|
+
.entry(entryName)
|
|
262
|
+
.add({
|
|
263
|
+
layer: LAYERS.BACKGROUND,
|
|
264
|
+
import: bgImports,
|
|
265
|
+
filename: backgroundName,
|
|
266
|
+
});
|
|
267
|
+
// Inject standard rspack HMR client + Lynx WebSocket transport into
|
|
268
|
+
// the BG entry (matching vue-lynx's approach). These must be prepended
|
|
269
|
+
// so they initialise before user code.
|
|
270
|
+
if (enabledHMR) {
|
|
271
|
+
bgEntry.prepend({
|
|
272
|
+
layer: LAYERS.BACKGROUND,
|
|
273
|
+
import: '@rspack/core/hot/dev-server',
|
|
274
|
+
});
|
|
275
|
+
// BG → MT hot-update bridge. Subscribes to the same `webpackHotUpdate`
|
|
276
|
+
// emitter event as `@rspack/core/hot/dev-server`, fetches the matching
|
|
277
|
+
// `main__main-thread.<hash>.hot-update.js`, and forwards extracted
|
|
278
|
+
// `registerWorkletInternal` calls to MT via `callLepusMethod`. Without
|
|
279
|
+
// this, MT's `_workletMap` keeps the old worklet IDs from the static
|
|
280
|
+
// bundle while BG sends ops referencing new content-hash IDs after a
|
|
281
|
+
// save → bind-of-undefined on tap.
|
|
282
|
+
bgEntry.prepend({
|
|
283
|
+
layer: LAYERS.BACKGROUND,
|
|
284
|
+
import: '@sigx/lynx-runtime/mt-hmr-bridge',
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
if (enabledHMR || enabledLiveReload) {
|
|
288
|
+
bgEntry.prepend({
|
|
289
|
+
layer: LAYERS.BACKGROUND,
|
|
290
|
+
import: '@lynx-js/webpack-dev-transport/client',
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
// Auto-install the console log streamer in dev. Prepended LAST so
|
|
294
|
+
// it runs FIRST at runtime (after webpack-dev-transport so the
|
|
295
|
+
// dev URL is plumbed). Skipped if the dev-client package isn't
|
|
296
|
+
// installed in the consuming project.
|
|
297
|
+
if (isDev && !isWeb && devClientInstallPath) {
|
|
298
|
+
bgEntry.prepend({
|
|
299
|
+
layer: LAYERS.BACKGROUND,
|
|
300
|
+
import: devClientInstallPath,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
bgEntry.end();
|
|
304
|
+
// ----------------------------------------------------------------
|
|
305
|
+
// LynxTemplatePlugin – packages both bundles into .lynx template
|
|
306
|
+
// ----------------------------------------------------------------
|
|
307
|
+
if ((isLynx || isWeb) && templateMod) {
|
|
308
|
+
{
|
|
309
|
+
const { LynxTemplatePlugin } = templateMod;
|
|
310
|
+
const templateFilename = (typeof environment.config.output.filename === 'object'
|
|
311
|
+
? environment.config.output.filename
|
|
312
|
+
.bundle
|
|
313
|
+
: environment.config.output.filename) ??
|
|
314
|
+
'[name].[platform].bundle';
|
|
315
|
+
chain
|
|
316
|
+
.plugin(`${PLUGIN_TEMPLATE}-${entryName}`)
|
|
317
|
+
.use(LynxTemplatePlugin, [
|
|
318
|
+
{
|
|
319
|
+
...LynxTemplatePlugin.defaultOptions,
|
|
320
|
+
dsl: 'react_nodiff',
|
|
321
|
+
chunks: [mainThreadEntry, entryName],
|
|
322
|
+
filename: templateFilename
|
|
323
|
+
.replaceAll('[name]', entryName)
|
|
324
|
+
.replaceAll('[platform]', environment.name),
|
|
325
|
+
intermediate: path.posix.join(intermediate, entryName),
|
|
326
|
+
debugInfoOutside: opts.debugInfoOutside ?? true,
|
|
327
|
+
enableCSSSelector: opts.enableCSSSelector ?? true,
|
|
328
|
+
enableCSSInvalidation: opts.enableCSSSelector ?? true,
|
|
329
|
+
enableCSSInheritance: opts.enableCSSInheritance ?? false,
|
|
330
|
+
customCSSInheritanceList: opts.customCSSInheritanceList,
|
|
331
|
+
enableRemoveCSSScope: true,
|
|
332
|
+
enableNewGesture: true,
|
|
333
|
+
removeDescendantSelectorScope: true,
|
|
334
|
+
cssPlugins: [],
|
|
335
|
+
},
|
|
336
|
+
])
|
|
337
|
+
.end();
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
// ------------------------------------------------------------------
|
|
342
|
+
// SigxMarkMainThreadPlugin – mark MT assets for LynxTemplatePlugin
|
|
343
|
+
// ------------------------------------------------------------------
|
|
344
|
+
if ((isLynx || isWeb) && mainThreadFilenames.length > 0) {
|
|
345
|
+
chain
|
|
346
|
+
.plugin(PLUGIN_MARK_MAIN_THREAD)
|
|
347
|
+
.use(SigxMarkMainThreadPlugin, [mainThreadFilenames])
|
|
348
|
+
.end();
|
|
349
|
+
}
|
|
350
|
+
// ------------------------------------------------------------------
|
|
351
|
+
// RuntimeWrapperWebpackPlugin – wrap BG bundle (NOT main-thread.js)
|
|
352
|
+
// in __init_card_bundle__(lynxCoreInject, lynx, ...). Inside the
|
|
353
|
+
// wrapper, lynx-runtime code can reference `lynx` and `lynxCoreInject`
|
|
354
|
+
// as bare identifiers, giving us the official BG → MT bridge and
|
|
355
|
+
// event dispatch hooks.
|
|
356
|
+
// ------------------------------------------------------------------
|
|
357
|
+
if (isLynx && wrapperMod) {
|
|
358
|
+
const { RuntimeWrapperWebpackPlugin } = wrapperMod;
|
|
359
|
+
chain
|
|
360
|
+
.plugin('lynx:sigx-runtime-wrapper')
|
|
361
|
+
.use(RuntimeWrapperWebpackPlugin, [
|
|
362
|
+
{
|
|
363
|
+
// Wrap everything except main-thread.js (and main-thread.[hash].js).
|
|
364
|
+
test: /^(?!.*main-thread(?:\.[A-Fa-f0-9]*)?\.js$).*\.js$/,
|
|
365
|
+
},
|
|
366
|
+
])
|
|
367
|
+
.end();
|
|
368
|
+
}
|
|
369
|
+
// ------------------------------------------------------------------
|
|
370
|
+
// LynxEncodePlugin – binary-encode the .lynx template
|
|
371
|
+
// ------------------------------------------------------------------
|
|
372
|
+
if (isLynx && templateMod) {
|
|
373
|
+
const { LynxEncodePlugin } = templateMod;
|
|
374
|
+
chain
|
|
375
|
+
.plugin(PLUGIN_ENCODE)
|
|
376
|
+
.use(LynxEncodePlugin, [{}])
|
|
377
|
+
.end();
|
|
378
|
+
}
|
|
379
|
+
// ------------------------------------------------------------------
|
|
380
|
+
// HMR loader – inject registerHMRModule() + module.hot.accept()
|
|
381
|
+
// into component files on the BG layer so they self-accept hot
|
|
382
|
+
// updates and patch instances in-place (no structural tree ops).
|
|
383
|
+
// ------------------------------------------------------------------
|
|
384
|
+
if (enabledHMR) {
|
|
385
|
+
chain.module
|
|
386
|
+
.rule('sigx-hmr')
|
|
387
|
+
.test(/\.[jt]sx?$/)
|
|
388
|
+
.issuerLayer(LAYERS.BACKGROUND)
|
|
389
|
+
.exclude
|
|
390
|
+
.add(/node_modules/)
|
|
391
|
+
.add(/dist/)
|
|
392
|
+
.end()
|
|
393
|
+
.enforce('pre')
|
|
394
|
+
.use('sigx-hmr-loader')
|
|
395
|
+
.loader(path.resolve(_dirname, './loaders/hmr-loader'))
|
|
396
|
+
.end();
|
|
397
|
+
}
|
|
398
|
+
// ------------------------------------------------------------------
|
|
399
|
+
// Worklet loaders — both layers run @lynx-js/react/transform.
|
|
400
|
+
// BG layer: target='JS' replaces 'main thread' functions with
|
|
401
|
+
// { _wkltId, _c? } placeholders shipped via SET_WORKLET_EVENT.
|
|
402
|
+
// MT layer: target='LEPUS' produces registerWorkletInternal(...) calls;
|
|
403
|
+
// the loader extracts those + local-import edges.
|
|
404
|
+
//
|
|
405
|
+
// Rules run on every JS/TS file in their respective layer — no
|
|
406
|
+
// package allowlist and no `node_modules`/`dist` rule exclude. The
|
|
407
|
+
// loaders gate themselves on directive presence (cheap regex
|
|
408
|
+
// pre-filter, then SWC). The MT loader additionally branches on the
|
|
409
|
+
// file's path because rspack shares module identity across BG/MT
|
|
410
|
+
// layers — see the decision table in `worklet-loader-mt.ts` — so an
|
|
411
|
+
// MT-side body strip of a library file would wipe its named exports
|
|
412
|
+
// for BG consumers too. That MT-side preservation keeps
|
|
413
|
+
// `@sigx/lynx-runtime-main`'s MT globals (`processData`,
|
|
414
|
+
// `updateGlobalProps`, `sigxRunOnMT`) and lets cross-package
|
|
415
|
+
// consumers like `@sigx/lynx-daisyui` resolve named imports
|
|
416
|
+
// (`useTabs`, `useScreenChrome`) from worklet-shipping packages.
|
|
417
|
+
//
|
|
418
|
+
// The BG loader has no path branch; for directive-bearing files
|
|
419
|
+
// (user or library) it returns the JS-target transform output,
|
|
420
|
+
// which preserves exports while replacing worklet bodies with
|
|
421
|
+
// `{ _wkltId }` placeholders. New packages that ship `'main thread'`
|
|
422
|
+
// directives in their dist are picked up automatically — no
|
|
423
|
+
// manual opt-in.
|
|
424
|
+
chain.module
|
|
425
|
+
.rule('sigx-worklet')
|
|
426
|
+
.test(/\.[jt]sx?$/)
|
|
427
|
+
.issuerLayer(LAYERS.BACKGROUND)
|
|
428
|
+
.enforce('pre')
|
|
429
|
+
.use('sigx-worklet-loader')
|
|
430
|
+
.loader(path.resolve(_dirname, './loaders/worklet-loader'))
|
|
431
|
+
.end();
|
|
432
|
+
chain.module
|
|
433
|
+
.rule('sigx-worklet-mt')
|
|
434
|
+
.test(/\.[jt]sx?$/)
|
|
435
|
+
.issuerLayer(LAYERS.MAIN_THREAD)
|
|
436
|
+
.enforce('pre')
|
|
437
|
+
.use('sigx-worklet-mt-loader')
|
|
438
|
+
.loader(path.resolve(_dirname, './loaders/worklet-loader-mt'))
|
|
439
|
+
.end();
|
|
440
|
+
// Disable IIFE wrapping – Lynx handles module scoping itself
|
|
441
|
+
chain.output.set('iife', false);
|
|
442
|
+
});
|
|
443
|
+
}
|
package/dist/icons.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* 1. Loads `signalx.config.ts` and reads the `iconSets: [...]` field.
|
|
7
7
|
* 2. Statically scans every `.tsx` / `.jsx` / `.ts` / `.js` file under the
|
|
8
|
-
* project root for
|
|
8
|
+
* project root for icon usages (see `scanContent` for the exact patterns).
|
|
9
9
|
* 3. Dynamically imports each adapter package (e.g. `@sigx/lynx-icons-fa-free`)
|
|
10
10
|
* and resolves the used glyphs to `{ codepoint, svg }` records.
|
|
11
11
|
* 4. Writes three generated files into `node_modules/.cache/sigx-lynx-icons/`
|
|
@@ -18,13 +18,33 @@
|
|
|
18
18
|
*
|
|
19
19
|
* The scanner is a one-shot regex pass at plugin start — adding a new icon
|
|
20
20
|
* during `pnpm dev` requires a dev-server restart in v1. A real SWC-AST
|
|
21
|
-
* Rspack loader is the planned upgrade
|
|
21
|
+
* Rspack loader is the planned upgrade and would obviate the regex
|
|
22
|
+
* patterns by inspecting the JSX tree directly.
|
|
23
|
+
*
|
|
24
|
+
* **Patterns the scanner picks up (regex-based; not exhaustive):**
|
|
25
|
+
* - `<Icon set="X" name="Y" />` — both attribute orders
|
|
26
|
+
* - `<FaSolidIcon name="Y" />` / `<FaRegularIcon name="Y" />`
|
|
27
|
+
* / `<FaBrandIcon name="Y" />` / `<LucideIcon name="Y" />` — pinned
|
|
28
|
+
* components whose set id is hardcoded in their implementations. The
|
|
29
|
+
* set id mapping is in `PINNED_COMPONENTS` below.
|
|
30
|
+
* - `{ set: 'X', name: 'Y' }` — `IconSpec` object literals anywhere
|
|
31
|
+
* (prop value, const declaration, function argument). Both key orders.
|
|
32
|
+
*
|
|
33
|
+
* **What still needs `include: [...]` in signalx.config.ts:**
|
|
34
|
+
* - Dynamic names: `<Icon set="fas" name={someVar} />` or
|
|
35
|
+
* `<FaSolidIcon name={someVar} />` — the scanner only matches literal
|
|
36
|
+
* string attributes. JSON-driven UIs and runtime-computed icon names
|
|
37
|
+
* need explicit force-includes (or `include: ['*']` for the whole catalog).
|
|
38
|
+
* - User-defined pinned components — only the four built-in adapter
|
|
39
|
+
* pinned components are known to the scanner. A consumer who writes
|
|
40
|
+
* their own `<MyIcon name="…">` wrapper needs `include`.
|
|
41
|
+
* - Spread props: `<Icon {...spec} />`. Niche; use `include` if needed.
|
|
22
42
|
*/
|
|
23
43
|
import type { RsbuildPluginAPI } from '@rsbuild/core';
|
|
24
44
|
/**
|
|
25
|
-
* Extract
|
|
26
|
-
*
|
|
27
|
-
* calls this once per file from {@link scanProject}.
|
|
45
|
+
* Extract icon usages from a single source string. See the file-level
|
|
46
|
+
* JSDoc for the complete pattern list. Exported for unit testing — the
|
|
47
|
+
* prod path calls this once per file from {@link scanProject}.
|
|
28
48
|
*/
|
|
29
49
|
export declare function scanContent(content: string): Array<{
|
|
30
50
|
set: string;
|