@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/layers.js
ADDED
|
@@ -1,20 +1,71 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Rspack loader that injects HMR self-acceptance into component files.
|
|
3
|
+
*
|
|
4
|
+
* For every file that contains a `component(` or `component<` call, the
|
|
5
|
+
* loader prepends:
|
|
6
|
+
* 1. An import of `__registerComponentPlugin` from `@sigx/lynx` — this
|
|
7
|
+
* is the *same* bundle the app uses, so the plugin array is shared.
|
|
8
|
+
* 2. An import of `initHMR` + `registerHMRModule` from `@sigx/lynx-runtime/hmr`.
|
|
9
|
+
* 3. Calls to wire them up: `initHMR(__registerComponentPlugin)` (idempotent)
|
|
10
|
+
* and `registerHMRModule(moduleId)`.
|
|
11
|
+
* 4. (If `signal` is imported) A wrapper that preserves signal objects
|
|
12
|
+
* across HMR cycles via `module.hot.data`, so module-level reactive
|
|
13
|
+
* state (e.g., a route signal) doesn't reset and cause structural tree
|
|
14
|
+
* mutations that crash the Lynx native engine.
|
|
15
|
+
*
|
|
16
|
+
* And appends `module.hot.accept()` + a dispose handler that snapshots
|
|
17
|
+
* preserved signals into `module.hot.data` for the next execution.
|
|
18
|
+
*
|
|
19
|
+
* On re-execution the HMR runtime patches existing component instances
|
|
20
|
+
* in-place — only property-level ops are emitted, avoiding the structural
|
|
21
|
+
* tree mutations that crash Lynx's native engine.
|
|
22
|
+
*/
|
|
23
|
+
// Matches `component(` or `component<`
|
|
24
|
+
const COMPONENT_RE = /\bcomponent\s*[<(]/;
|
|
25
|
+
// Matches an import statement from @sigx/lynx that destructures `signal`.
|
|
26
|
+
// Captures: (1) before-signal names, (2) after-signal names, (3) quote char
|
|
27
|
+
// e.g. import { signal, component } from '@sigx/lynx';
|
|
28
|
+
// group 1: "" group 2: ", component " group 3: "'"
|
|
29
|
+
const SIGNAL_IMPORT_RE = /import\s*\{([^}]*)\bsignal\b([^}]*)\}\s*from\s*(['"])@sigx\/lynx\3/;
|
|
30
|
+
export default function hmrLoader(source) {
|
|
31
|
+
this.cacheable(false);
|
|
32
|
+
if (!COMPONENT_RE.test(source)) {
|
|
33
|
+
return source;
|
|
34
|
+
}
|
|
35
|
+
const moduleId = this.resourcePath
|
|
36
|
+
.replace(/\\/g, '/')
|
|
37
|
+
.replace(/\.(tsx?|jsx)$/, '');
|
|
38
|
+
// Check if the file imports `signal` from @sigx/lynx
|
|
39
|
+
const signalImportMatch = source.match(SIGNAL_IMPORT_RE);
|
|
40
|
+
const hasSignalImport = !!signalImportMatch;
|
|
41
|
+
let transformedSource = source;
|
|
42
|
+
if (hasSignalImport) {
|
|
43
|
+
// Rename `signal` → `__origSignal` in the import statement so we can
|
|
44
|
+
// shadow it with a preserving wrapper. All call-sites automatically
|
|
45
|
+
// use the wrapper because the local binding name stays `signal`.
|
|
46
|
+
transformedSource = source.replace(SIGNAL_IMPORT_RE, (_match, before, after, quote) => `import {${before}signal as __origSignal${after}} from ${quote}@sigx/lynx${quote}`);
|
|
47
|
+
}
|
|
48
|
+
const header = [
|
|
49
|
+
`import { __registerComponentPlugin, __setCurrentInstanceForHMR } from '@sigx/lynx';`,
|
|
50
|
+
`import { initHMR, registerHMRModule } from '@sigx/lynx-runtime/hmr';`,
|
|
51
|
+
`initHMR(__registerComponentPlugin, __setCurrentInstanceForHMR);`,
|
|
52
|
+
`registerHMRModule(${JSON.stringify(moduleId)});`,
|
|
53
|
+
];
|
|
54
|
+
if (hasSignalImport) {
|
|
55
|
+
// Inject a `signal` wrapper that returns the cached signal object from
|
|
56
|
+
// the previous HMR cycle when one exists, or delegates to the real
|
|
57
|
+
// `signal()` for new signals. This preserves module-level reactive
|
|
58
|
+
// state (and component-level state inside setup — matching React Fast
|
|
59
|
+
// Refresh behaviour where hooks state is preserved across HMR).
|
|
60
|
+
header.push(`var __hmrSigPrev = (typeof module !== 'undefined' && module.hot && module.hot.data && module.hot.data.__hmrSigs) || {};`, `var __hmrSigStore = {};`, `var __hmrSigIdx = 0;`, `function signal() {`, ` var k = 's' + __hmrSigIdx++;`, ` if (k in __hmrSigPrev) { __hmrSigStore[k] = __hmrSigPrev[k]; return __hmrSigPrev[k]; }`, ` var s = __origSignal.apply(null, arguments);`, ` __hmrSigStore[k] = s;`, ` return s;`, `}`);
|
|
61
|
+
}
|
|
62
|
+
header.push('');
|
|
63
|
+
const footer = [''];
|
|
64
|
+
if (hasSignalImport) {
|
|
65
|
+
footer.push(`if (typeof module !== 'undefined' && module.hot) {`, ` module.hot.dispose(function(data) { data.__hmrSigs = __hmrSigStore; });`, ` module.hot.accept();`, `}`);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
footer.push(`if (typeof module !== 'undefined' && module.hot) { module.hot.accept(); }`);
|
|
69
|
+
}
|
|
70
|
+
return header.join('\n') + transformedSource + footer.join('\n');
|
|
16
71
|
}
|
|
17
|
-
//#endregion
|
|
18
|
-
export { n as default };
|
|
19
|
-
|
|
20
|
-
//# sourceMappingURL=hmr-loader.js.map
|
|
@@ -1,8 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Webpack/Rspack loader that strips CSS from Main-Thread bundles.
|
|
3
|
+
*
|
|
4
|
+
* In the Lynx dual-thread model, only the Background Thread needs CSS.
|
|
5
|
+
* The Main-Thread layer uses `exportOnlyLocals` from css-loader to get
|
|
6
|
+
* CSS module class name mappings without actual styles.
|
|
7
|
+
*/
|
|
8
|
+
export default function ignoreCssLoader(source) {
|
|
9
|
+
this.cacheable(true);
|
|
10
|
+
// If the source contains ___CSS_LOADER_EXPORT___, it is not a CSS Modules
|
|
11
|
+
// file (exportOnlyLocals is enabled), so we don't need to preserve it.
|
|
12
|
+
if (source.includes('___CSS_LOADER_EXPORT___')) {
|
|
13
|
+
return 'export {}';
|
|
14
|
+
}
|
|
15
|
+
// Preserve CSS modules export for background layer.
|
|
16
|
+
return source;
|
|
4
17
|
}
|
|
5
|
-
//#endregion
|
|
6
|
-
export { e as default };
|
|
7
|
-
|
|
8
|
-
//# sourceMappingURL=ignore-css-loader.js.map
|
|
@@ -1,64 +1,144 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
/**
|
|
2
|
+
* MT-layer rspack loader for the `'main thread'` worklet directive.
|
|
3
|
+
*
|
|
4
|
+
* Runs on every JS/TS file in the MT layer — no rule-level exclude.
|
|
5
|
+
* Decides per-file what to emit based on directive presence and whether
|
|
6
|
+
* the file is library code (`node_modules/` or `dist/`) or user code:
|
|
7
|
+
*
|
|
8
|
+
* | Origin | Directive? | Output |
|
|
9
|
+
* |--------------|------------|------------------------------------------------|
|
|
10
|
+
* | user code | no | bootstrap preamble + side-effect imports |
|
|
11
|
+
* | | | (drops body — sigx's Lepus never executes |
|
|
12
|
+
* | | | React components; `entry-main.ts`'s |
|
|
13
|
+
* | | | `renderPage` builds a placeholder and ops |
|
|
14
|
+
* | | | arrive from BG via `sigxPatchUpdate`) |
|
|
15
|
+
* | user code | yes | bootstrap preamble + side-effect imports |
|
|
16
|
+
* | | | + extracted `registerWorkletInternal(...)` |
|
|
17
|
+
* | library | no | source unchanged |
|
|
18
|
+
* | | | (preserves `@sigx/lynx-runtime-main`'s MT |
|
|
19
|
+
* | | | globals — `processData`, `updateGlobalProps`,|
|
|
20
|
+
* | | | `sigxRunOnMT` — and barrel re-exports that |
|
|
21
|
+
* | | | BG-side consumers like daisyui import by |
|
|
22
|
+
* | | | name; rspack shares module identity across |
|
|
23
|
+
* | | | BG/MT layers) |
|
|
24
|
+
* | library | yes | source unchanged + appended registrations |
|
|
25
|
+
* | | | (keeps named exports AND registers worklets) |
|
|
26
|
+
*
|
|
27
|
+
* Library files skip the bootstrap preamble — the user entry's
|
|
28
|
+
* preamble has already pulled runtime-main in before any library code
|
|
29
|
+
* evaluates.
|
|
30
|
+
*
|
|
31
|
+
* For files WITH a directive, the LEPUS transform's output is sliced
|
|
32
|
+
* via `extractRegistrations` so only the `registerWorkletInternal(...)`
|
|
33
|
+
* calls are kept. The `loadWorkletRuntime` import that upstream emits
|
|
34
|
+
* is dropped — `registerWorkletInternal` is installed as a global by
|
|
35
|
+
* `entry-main.ts`.
|
|
36
|
+
*
|
|
37
|
+
* Mirrors vue-lynx's `worklet-loader-mt.ts`, minus the `?vue` sub-module
|
|
38
|
+
* branch (sigx has no Vue SFC pipeline) and minus the shared-imports path
|
|
39
|
+
* (deferred to Phase 1c).
|
|
40
|
+
*/
|
|
41
|
+
import { transformReactLynxSync } from '@lynx-js/react/transform';
|
|
42
|
+
import { createRequire } from 'node:module';
|
|
43
|
+
import { dirname, join } from 'node:path';
|
|
44
|
+
import { extractLocalImports, extractRegistrations } from './worklet-utils.js';
|
|
45
|
+
// Same idiomatic-barrel matching as the BG loader (see worklet-loader.ts:23
|
|
46
|
+
// for the full rationale). The LEPUS pass emits an `import { loadWorkletRuntime
|
|
47
|
+
// } from '<runtimePkg>'`, but `extractRegistrations` slices the LEPUS output
|
|
48
|
+
// down to `registerWorkletInternal(...)` calls only — the import is dropped
|
|
49
|
+
// before bundling — so the runtimePkg's actual exports don't need to include
|
|
50
|
+
// loadWorkletRuntime. What matters here is identifier tracking: aligning
|
|
51
|
+
// with '@sigx/lynx' means SWC follows `runOnBackground as s` through
|
|
52
|
+
// '@sigx/lynx' barrel imports just like on the BG side.
|
|
53
|
+
const RUNTIME_PKG = '@sigx/lynx';
|
|
54
|
+
/**
|
|
55
|
+
* Prepended to every MT-layer file so webpack's dep graph guarantees the
|
|
56
|
+
* bootstrap modules evaluate BEFORE any user code that calls
|
|
57
|
+
* `registerWorkletInternal(...)`. Listing these as separate entries in
|
|
58
|
+
* `entry.ts` doesn't suffice — rspack walks the chunk graph, which can
|
|
59
|
+
* eval user code first when nothing else explicitly depends on bootstrap.
|
|
60
|
+
*
|
|
61
|
+
* Order matters: entry-main's module body sets globalThis.SystemInfo before
|
|
62
|
+
* worklet-runtime's IIFE reads it.
|
|
63
|
+
*
|
|
64
|
+
* Paths are resolved to absolute file URIs at loader-init time so user apps
|
|
65
|
+
* don't need to declare @lynx-js/react as a direct dep — pnpm hoists it into
|
|
66
|
+
* our package's node_modules and we hand rspack the resolved path. We resolve
|
|
67
|
+
* the package's `package.json` (always reachable, no exports-map games), read
|
|
68
|
+
* the dist subpath relative to it. createRequire(import.meta.url) lets us
|
|
69
|
+
* walk up from the loader's install location via Node's CJS resolver.
|
|
70
|
+
*/
|
|
71
|
+
const _req = createRequire(import.meta.url);
|
|
72
|
+
function resolvePackageSubpath(pkgName, subpath) {
|
|
73
|
+
const pkgJsonPath = _req.resolve(`${pkgName}/package.json`);
|
|
74
|
+
return join(dirname(pkgJsonPath), subpath);
|
|
9
75
|
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
76
|
+
const ENTRY_MAIN_PATH = resolvePackageSubpath('@sigx/lynx-runtime-main', 'dist/entry-main.js');
|
|
77
|
+
const WORKLET_RUNTIME_PATH = resolvePackageSubpath('@lynx-js/react', 'runtime/worklet-runtime/main.js');
|
|
78
|
+
const INSTALL_HYBRID_PATH = resolvePackageSubpath('@sigx/lynx-runtime-main', 'dist/install-hybrid-worklet.js');
|
|
79
|
+
const BOOTSTRAP_PREAMBLE = `import ${JSON.stringify(ENTRY_MAIN_PATH)};\n`
|
|
80
|
+
+ `import ${JSON.stringify(WORKLET_RUNTIME_PATH)};\n`
|
|
81
|
+
+ `import ${JSON.stringify(INSTALL_HYBRID_PATH)};\n`;
|
|
82
|
+
// Cheap pre-filter to skip the SWC parse for files that obviously don't
|
|
83
|
+
// contain a worklet directive. A directive is always followed immediately
|
|
84
|
+
// by a statement terminator — either `;` or a newline (ASI). Requiring
|
|
85
|
+
// one of those after the closing quote rejects substrings inside
|
|
86
|
+
// single-line error strings like
|
|
87
|
+
// `"...inside 'main thread' functions..."` from `@sigx/lynx-runtime`'s
|
|
88
|
+
// dist, where the next char is a space then `functions`.
|
|
89
|
+
//
|
|
90
|
+
// This is not a parser. A truly adversarial input (e.g. the literal
|
|
91
|
+
// string `"'main thread';"`) can slip through, and SWC is the final
|
|
92
|
+
// arbiter — for such files it produces no registrations and the MT
|
|
93
|
+
// output reduces to the library branch's source pass-through (for
|
|
94
|
+
// library paths) or to the no-directive user branch's strip (for user
|
|
95
|
+
// paths). Either way: correct, just with the parse work done.
|
|
96
|
+
const DIRECTIVE_RE = /['"]main thread['"]\s*(?:;|\n)/;
|
|
97
|
+
// Library paths (`node_modules/` and any `dist/`) get the body-preserve
|
|
98
|
+
// branches above. Rspack shares module identity across BG/MT layers, so
|
|
99
|
+
// stripping the MT-side body of a library file would wipe its named
|
|
100
|
+
// exports for BG consumers too — daisyui couldn't resolve `useTabs` from
|
|
101
|
+
// lynx-navigation, runtime-main's MT globals would disappear, etc.
|
|
102
|
+
const LIBRARY_PATH_RE = /[\\/](?:node_modules|dist)[\\/]/;
|
|
103
|
+
export default function workletLoaderMT(source) {
|
|
104
|
+
this.cacheable(true);
|
|
105
|
+
const localImports = extractLocalImports(source);
|
|
106
|
+
if (!DIRECTIVE_RE.test(source)) {
|
|
107
|
+
if (LIBRARY_PATH_RE.test(this.resourcePath)) {
|
|
108
|
+
return source;
|
|
109
|
+
}
|
|
110
|
+
return BOOTSTRAP_PREAMBLE + localImports;
|
|
111
|
+
}
|
|
112
|
+
const filename = this.resourcePath;
|
|
113
|
+
const result = transformReactLynxSync(source, {
|
|
114
|
+
pluginName: 'sigx:worklet-mt',
|
|
115
|
+
filename,
|
|
116
|
+
sourcemap: false,
|
|
117
|
+
cssScope: false,
|
|
118
|
+
shake: false,
|
|
119
|
+
compat: false,
|
|
120
|
+
refresh: false,
|
|
121
|
+
defineDCE: false,
|
|
122
|
+
directiveDCE: false,
|
|
123
|
+
snapshot: false,
|
|
124
|
+
worklet: { target: 'LEPUS', filename, runtimePkg: RUNTIME_PKG },
|
|
125
|
+
});
|
|
126
|
+
if (result.errors && result.errors.length > 0) {
|
|
127
|
+
for (const err of result.errors) {
|
|
128
|
+
this.emitError(new Error(`[sigx-worklet-mt] LEPUS transform: ${err.text}`));
|
|
129
|
+
}
|
|
130
|
+
return localImports;
|
|
131
|
+
}
|
|
132
|
+
const registrations = extractRegistrations(result.code);
|
|
133
|
+
// Library files with a directive (e.g. lynx-navigation's `EdgeBackHandle.js`,
|
|
134
|
+
// `navigator/core.js`): preserve the original source so its named exports
|
|
135
|
+
// survive cross-layer module identity (BG-side consumers like daisyui
|
|
136
|
+
// import them by name), and append the `registerWorkletInternal` calls so
|
|
137
|
+
// the directives are still registered on the MT side. No bootstrap
|
|
138
|
+
// preamble — user entry code's preamble has already pulled runtime-main
|
|
139
|
+
// in before any library code is evaluated.
|
|
140
|
+
if (LIBRARY_PATH_RE.test(this.resourcePath)) {
|
|
141
|
+
return registrations ? `${source}\n${registrations}` : source;
|
|
142
|
+
}
|
|
143
|
+
return BOOTSTRAP_PREAMBLE + [localImports, registrations].filter(Boolean).join('\n');
|
|
25
144
|
}
|
|
26
|
-
//#endregion
|
|
27
|
-
//#region src/loaders/worklet-loader-mt.ts
|
|
28
|
-
var o = "@sigx/lynx-runtime-main", s = n(import.meta.url);
|
|
29
|
-
function c(n, r) {
|
|
30
|
-
return t(e(s.resolve(`${n}/package.json`)), r);
|
|
31
|
-
}
|
|
32
|
-
var l = c("@sigx/lynx-runtime-main", "dist/entry-main.js"), u = c("@lynx-js/react", "runtime/worklet-runtime/main.js"), d = c("@sigx/lynx-runtime-main", "dist/install-hybrid-worklet.js"), f = `import ${JSON.stringify(l)};\nimport ${JSON.stringify(u)};\nimport ${JSON.stringify(d)};\n`, p = /['"]main thread['"]\s*(?:;|\n)/, m = /[\\/](?:node_modules|dist)[\\/]/;
|
|
33
|
-
function h(e) {
|
|
34
|
-
this.cacheable(!0);
|
|
35
|
-
let t = i(e);
|
|
36
|
-
if (!p.test(e)) return m.test(this.resourcePath) ? e : f + t;
|
|
37
|
-
let n = this.resourcePath, s = r(e, {
|
|
38
|
-
pluginName: "sigx:worklet-mt",
|
|
39
|
-
filename: n,
|
|
40
|
-
sourcemap: !1,
|
|
41
|
-
cssScope: !1,
|
|
42
|
-
shake: !1,
|
|
43
|
-
compat: !1,
|
|
44
|
-
refresh: !1,
|
|
45
|
-
defineDCE: !1,
|
|
46
|
-
directiveDCE: !1,
|
|
47
|
-
snapshot: !1,
|
|
48
|
-
worklet: {
|
|
49
|
-
target: "LEPUS",
|
|
50
|
-
filename: n,
|
|
51
|
-
runtimePkg: o
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
if (s.errors && s.errors.length > 0) {
|
|
55
|
-
for (let e of s.errors) this.emitError(/* @__PURE__ */ Error(`[sigx-worklet-mt] LEPUS transform: ${e.text}`));
|
|
56
|
-
return t;
|
|
57
|
-
}
|
|
58
|
-
let c = a(s.code);
|
|
59
|
-
return m.test(this.resourcePath) ? c ? `${e}\n${c}` : e : f + [t, c].filter(Boolean).join("\n");
|
|
60
|
-
}
|
|
61
|
-
//#endregion
|
|
62
|
-
export { h as default };
|
|
63
|
-
|
|
64
|
-
//# sourceMappingURL=worklet-loader-mt.js.map
|
|
@@ -1,32 +1,70 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
1
|
+
/**
|
|
2
|
+
* BG-layer rspack loader for the `'main thread'` worklet directive.
|
|
3
|
+
*
|
|
4
|
+
* Delegates to upstream's `transformReactLynxSync` (a framework-agnostic SWC
|
|
5
|
+
* worklet transform shared with vue-lynx and react-lynx — see
|
|
6
|
+
* docs/mts-upstream-spike.md). With `target: 'JS'`, each `'main thread'`-marked
|
|
7
|
+
* function is replaced by a `{ _wkltId, _c? }` placeholder object that the
|
|
8
|
+
* sigx BG runtime ships through SET_WORKLET_EVENT to MT.
|
|
9
|
+
*
|
|
10
|
+
* Files without the directive are passed through unchanged. The MT side is
|
|
11
|
+
* handled by the sibling `worklet-loader-mt.ts`.
|
|
12
|
+
*/
|
|
13
|
+
import { transformReactLynxSync } from '@lynx-js/react/transform';
|
|
14
|
+
// runtimePkg controls where SWC emits `import { transformToWorklet } from <X>`
|
|
15
|
+
// at the top of transformed BG output (used when `runOnBackground(fn)` is
|
|
16
|
+
// detected inside a `'main thread'` body). Points at @sigx/lynx — the public
|
|
17
|
+
// barrel that re-exports everything from @sigx/lynx-runtime — so the emitted
|
|
18
|
+
// import matches the idiomatic path apps use, with no need for the internal
|
|
19
|
+
// lynx-runtime specifier to be reachable from every consumer.
|
|
20
|
+
//
|
|
21
|
+
// SWC's identifier matching for `runOnBackground` itself is by literal symbol
|
|
22
|
+
// name (see lynx-stack/packages/react/transform/.../extract_ident.rs's
|
|
23
|
+
// `n.callee.sym != "runOnBackground"` check) — it does NOT follow renamed
|
|
24
|
+
// import bindings. Packages that ship precompiled worklet code must therefore
|
|
25
|
+
// preserve the `runOnBackground` identifier in their dist (no mangling). The
|
|
26
|
+
// canonical pattern is the one upstream `@lynx-js/react` uses: `tsc`-only
|
|
27
|
+
// per-file emit, no bundler, no minifier. `@sigx/lynx-gestures` follows that
|
|
28
|
+
// pattern for the same reason — see its package.json `build` script.
|
|
29
|
+
const RUNTIME_PKG = '@sigx/lynx';
|
|
30
|
+
// Cheap pre-filter to skip the SWC parse for files that obviously don't
|
|
31
|
+
// contain a worklet directive. A directive is always followed immediately
|
|
32
|
+
// by a statement terminator — either `;` or a newline (ASI). Requiring
|
|
33
|
+
// one of those after the closing quote rejects substrings inside
|
|
34
|
+
// single-line error strings like
|
|
35
|
+
// `"...inside 'main thread' functions..."` from `@sigx/lynx-runtime`'s
|
|
36
|
+
// dist, where the next char is a space then `functions`.
|
|
37
|
+
//
|
|
38
|
+
// This is not a parser. A truly adversarial input (e.g. the literal
|
|
39
|
+
// string `"'main thread';"`) can slip through, and the SWC transform
|
|
40
|
+
// is the final arbiter — for such files it produces no registrations
|
|
41
|
+
// and the BG output is identical to the no-directive path, just with
|
|
42
|
+
// the parse work done.
|
|
43
|
+
const DIRECTIVE_RE = /['"]main thread['"]\s*(?:;|\n)/;
|
|
44
|
+
export default function workletLoader(source) {
|
|
45
|
+
this.cacheable(true);
|
|
46
|
+
if (!DIRECTIVE_RE.test(source)) {
|
|
47
|
+
return source;
|
|
48
|
+
}
|
|
49
|
+
const filename = this.resourcePath;
|
|
50
|
+
const result = transformReactLynxSync(source, {
|
|
51
|
+
pluginName: 'sigx:worklet',
|
|
52
|
+
filename,
|
|
53
|
+
sourcemap: false,
|
|
54
|
+
cssScope: false,
|
|
55
|
+
shake: false,
|
|
56
|
+
compat: false,
|
|
57
|
+
refresh: false,
|
|
58
|
+
defineDCE: false,
|
|
59
|
+
directiveDCE: false,
|
|
60
|
+
snapshot: false,
|
|
61
|
+
worklet: { target: 'JS', filename, runtimePkg: RUNTIME_PKG },
|
|
62
|
+
});
|
|
63
|
+
if (result.errors && result.errors.length > 0) {
|
|
64
|
+
for (const err of result.errors) {
|
|
65
|
+
this.emitError(new Error(`[sigx-worklet] JS transform: ${err.text}`));
|
|
66
|
+
}
|
|
67
|
+
return source;
|
|
68
|
+
}
|
|
69
|
+
return result.code;
|
|
28
70
|
}
|
|
29
|
-
//#endregion
|
|
30
|
-
export { r as default };
|
|
31
|
-
|
|
32
|
-
//# sourceMappingURL=worklet-loader.js.map
|
|
@@ -28,19 +28,4 @@
|
|
|
28
28
|
* _wkltId is looked up in `lynxWorkletImpl._workletMap`.
|
|
29
29
|
*/
|
|
30
30
|
export declare function extractLocalImports(source: string): string;
|
|
31
|
-
/**
|
|
32
|
-
* Extract `registerWorkletInternal(...)` calls from a LEPUS-target transform output.
|
|
33
|
-
*
|
|
34
|
-
* Upstream's LEPUS output includes:
|
|
35
|
-
* - `import { loadWorkletRuntime as __loadWorkletRuntime } from "<runtimePkg>";`
|
|
36
|
-
* - `var loadWorkletRuntime = __loadWorkletRuntime;`
|
|
37
|
-
* - the original component code (which we drop — never invoked on MT)
|
|
38
|
-
* - `const __workletRuntimeLoaded = loadWorkletRuntime(...);`
|
|
39
|
-
* - `__workletRuntimeLoaded && registerWorkletInternal("main-thread", "<id>", function(...) { ... });`
|
|
40
|
-
*
|
|
41
|
-
* We only need the `registerWorkletInternal(...)` calls — the loadWorkletRuntime
|
|
42
|
-
* gating is redundant because we only inject the calls when the MT bundle is
|
|
43
|
-
* actually being built. Bracket-depth counting handles nested braces in the
|
|
44
|
-
* function body.
|
|
45
|
-
*/
|
|
46
31
|
export declare function extractRegistrations(lepusCode: string): string;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers shared by worklet-loader (BG) and worklet-loader-mt (MT).
|
|
3
|
+
*
|
|
4
|
+
* Mirrors vue-lynx's `worklet-utils.ts` minus the `?vue` sub-module logic
|
|
5
|
+
* (sigx has no Vue Single-File-Component pipeline) and minus the
|
|
6
|
+
* `extractSharedImports` path (deferred to Phase 1c — see plan).
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Extract import statements whose target may contain `'main thread'` worklets
|
|
10
|
+
* the MT bundle needs to register.
|
|
11
|
+
*
|
|
12
|
+
* Two import flavours qualify:
|
|
13
|
+
* - Relative paths (`./foo`, `../bar`) — user code split across files.
|
|
14
|
+
* - `@sigx/*` package paths — workspace component packages like
|
|
15
|
+
* `@sigx/gestures` that ship `<Pressable>`/`<Draggable>` etc. These resolve
|
|
16
|
+
* to workspace `src/` (not node_modules) so the MT loader's rule still
|
|
17
|
+
* processes them; we just need to preserve the edge so rspack walks there.
|
|
18
|
+
*
|
|
19
|
+
* Converts named/default/namespace imports to side-effect-only imports
|
|
20
|
+
* (`import '@sigx/gestures';`) so webpack still follows the dependency graph
|
|
21
|
+
* without executing user code on the MT layer.
|
|
22
|
+
*
|
|
23
|
+
* Critical for the MT loader: an entry like `main.tsx` may not contain any
|
|
24
|
+
* `'main thread'` directives itself, but it imports route/component files
|
|
25
|
+
* that do. Without preserving these edges, webpack would never reach the
|
|
26
|
+
* files with worklet registrations and MT-side hydration of BG worklet ctxs
|
|
27
|
+
* would throw "cannot read property bind of undefined" when the unregistered
|
|
28
|
+
* _wkltId is looked up in `lynxWorkletImpl._workletMap`.
|
|
29
|
+
*/
|
|
30
|
+
export function extractLocalImports(source) {
|
|
31
|
+
// Strip line and block comments so docstring examples like
|
|
32
|
+
// `* import X from './foo'` don't get parsed as real imports.
|
|
33
|
+
// String contents are left intact (we still catch `import './foo'` inside
|
|
34
|
+
// a string template), but real source rarely encodes import-statement
|
|
35
|
+
// shapes inside strings, and the false-positive rate with comments stripped
|
|
36
|
+
// is acceptable for this preserve-edges-only loader.
|
|
37
|
+
const stripped = source
|
|
38
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
39
|
+
.replace(/(^|[^:])\/\/[^\n]*/g, '$1');
|
|
40
|
+
const specifiers = new Set();
|
|
41
|
+
// Relative paths (./foo, ../bar) and @sigx/* package paths.
|
|
42
|
+
const re = /(?:from|import)\s+['"]((?:\.[^'"]+)|(?:@sigx\/[^'"]+))['"]/g;
|
|
43
|
+
let match;
|
|
44
|
+
while ((match = re.exec(stripped)) !== null) {
|
|
45
|
+
specifiers.add(match[1]);
|
|
46
|
+
}
|
|
47
|
+
if (specifiers.size === 0)
|
|
48
|
+
return '';
|
|
49
|
+
return [...specifiers].map((s) => `import '${s}';`).join('\n');
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Extract `registerWorkletInternal(...)` calls from a LEPUS-target transform output.
|
|
53
|
+
*
|
|
54
|
+
* Upstream's LEPUS output includes:
|
|
55
|
+
* - `import { loadWorkletRuntime as __loadWorkletRuntime } from "<runtimePkg>";`
|
|
56
|
+
* - `var loadWorkletRuntime = __loadWorkletRuntime;`
|
|
57
|
+
* - the original component code (which we drop — never invoked on MT)
|
|
58
|
+
* - `const __workletRuntimeLoaded = loadWorkletRuntime(...);`
|
|
59
|
+
* - `__workletRuntimeLoaded && registerWorkletInternal("main-thread", "<id>", function(...) { ... });`
|
|
60
|
+
*
|
|
61
|
+
* We only need the `registerWorkletInternal(...)` calls — the loadWorkletRuntime
|
|
62
|
+
* gating is redundant because we only inject the calls when the MT bundle is
|
|
63
|
+
* actually being built. Bracket-depth counting handles nested braces in the
|
|
64
|
+
* function body.
|
|
65
|
+
*
|
|
66
|
+
* SWC's LEPUS transform preserves JSDoc / comments verbatim. When source code
|
|
67
|
+
* carries documentation that *mentions* the literal token
|
|
68
|
+
* `registerWorkletInternal(...)` (as `lynx-runtime`'s `threading.ts` does), a
|
|
69
|
+
* naive scan would extract that doc text as a "registration" and append it to
|
|
70
|
+
* the MT bundle as invalid JS (`function(...) { ... }` isn't real syntax).
|
|
71
|
+
* Strip comments before scanning so only real statements survive.
|
|
72
|
+
*/
|
|
73
|
+
function stripJsComments(code) {
|
|
74
|
+
// Two passes — block comments first, then line comments. Block-first
|
|
75
|
+
// is important: a `// inside */` sequence inside a `/* ... */` block
|
|
76
|
+
// would otherwise have the inner `//` eaten by the line-comment pass
|
|
77
|
+
// before the outer block is matched, leaving stray `*/` in the output.
|
|
78
|
+
// We don't try to be string-literal-aware (a comment-looking sequence
|
|
79
|
+
// inside a string would be wrongly stripped) because:
|
|
80
|
+
// 1. The input is SWC output, which doesn't put `//` or `/* */` inside
|
|
81
|
+
// strings except in trivial constant cases we don't care about.
|
|
82
|
+
// 2. `extractRegistrations` only cares about call shapes; the surrounding
|
|
83
|
+
// code is discarded anyway.
|
|
84
|
+
return code
|
|
85
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
86
|
+
.replace(/(^|[^:])\/\/[^\n]*/g, '$1');
|
|
87
|
+
}
|
|
88
|
+
export function extractRegistrations(lepusCode) {
|
|
89
|
+
const code = stripJsComments(lepusCode);
|
|
90
|
+
const out = [];
|
|
91
|
+
const marker = 'registerWorkletInternal(';
|
|
92
|
+
let from = 0;
|
|
93
|
+
while (true) {
|
|
94
|
+
const idx = code.indexOf(marker, from);
|
|
95
|
+
if (idx === -1)
|
|
96
|
+
break;
|
|
97
|
+
let depth = 0;
|
|
98
|
+
let i = idx + marker.length - 1; // points at the opening '('
|
|
99
|
+
for (; i < code.length; i++) {
|
|
100
|
+
const ch = code[i];
|
|
101
|
+
if (ch === '(')
|
|
102
|
+
depth++;
|
|
103
|
+
else if (ch === ')') {
|
|
104
|
+
depth--;
|
|
105
|
+
if (depth === 0)
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
let end = i + 1;
|
|
110
|
+
if (end < code.length && code[end] === ';')
|
|
111
|
+
end++;
|
|
112
|
+
out.push(code.slice(idx, end));
|
|
113
|
+
from = end;
|
|
114
|
+
}
|
|
115
|
+
return out.join('\n');
|
|
116
|
+
}
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sigx/lynx-plugin",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "Rspack/Rspeedy plugin for SignalX Lynx dual-thread rendering",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -33,8 +33,9 @@
|
|
|
33
33
|
"url": "https://github.com/signalxjs/lynx/issues"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@lynx-js/react": "^0.
|
|
37
|
-
"
|
|
36
|
+
"@lynx-js/react": "^0.121.0",
|
|
37
|
+
"ws": "^8.20.1",
|
|
38
|
+
"@sigx/lynx-runtime-internal": "0.4.2"
|
|
38
39
|
},
|
|
39
40
|
"peerDependencies": {
|
|
40
41
|
"@rspack/core": ">=1.0.0",
|
|
@@ -43,8 +44,8 @@
|
|
|
43
44
|
"@lynx-js/template-webpack-plugin": ">=0.1.0",
|
|
44
45
|
"@lynx-js/runtime-wrapper-webpack-plugin": ">=0.1.0",
|
|
45
46
|
"@lynx-js/webpack-dev-transport": ">=0.1.0",
|
|
46
|
-
"@sigx/lynx-cli": "^0.4.
|
|
47
|
-
"@sigx/lynx-icons": "^0.4.
|
|
47
|
+
"@sigx/lynx-cli": "^0.4.2",
|
|
48
|
+
"@sigx/lynx-icons": "^0.4.2"
|
|
48
49
|
},
|
|
49
50
|
"peerDependenciesMeta": {
|
|
50
51
|
"@rspack/core": {
|
|
@@ -71,18 +72,19 @@
|
|
|
71
72
|
},
|
|
72
73
|
"devDependencies": {
|
|
73
74
|
"@rsbuild/core": "^1.7.5",
|
|
74
|
-
"@
|
|
75
|
-
"@types/
|
|
75
|
+
"@types/node": "^25.9.1",
|
|
76
|
+
"@types/ws": "^8.5.13",
|
|
77
|
+
"@typescript/native-preview": "7.0.0-dev.20260521.1",
|
|
76
78
|
"typescript": "^6.0.3",
|
|
77
|
-
"
|
|
78
|
-
"@sigx/lynx-
|
|
79
|
-
"@sigx/lynx-icons": "0.4.0"
|
|
79
|
+
"@sigx/lynx-cli": "0.4.2",
|
|
80
|
+
"@sigx/lynx-icons": "0.4.2"
|
|
80
81
|
},
|
|
81
82
|
"publishConfig": {
|
|
82
83
|
"access": "public"
|
|
83
84
|
},
|
|
84
85
|
"scripts": {
|
|
85
|
-
"build": "
|
|
86
|
-
"dev": "
|
|
86
|
+
"build": "node ../../scripts/clean.mjs dist && tsgo",
|
|
87
|
+
"dev": "tsgo --watch",
|
|
88
|
+
"clean": "node ../../scripts/clean.mjs dist .turbo"
|
|
87
89
|
}
|
|
88
90
|
}
|