vite-plugin-taro 0.5.4 → 0.5.6
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.en.md +1 -1
- package/README.md +1 -1
- package/dist/node/plugins/h5/create-stencil-client-adapter.d.ts +10 -13
- package/dist/node/plugins/h5/create-stencil-client-adapter.js +63 -51
- package/dist/node/plugins/h5/plugins.d.ts +10 -0
- package/dist/node/plugins/h5/plugins.js +39 -11
- package/dist/node/plugins/wx/dev/dev-host.js +103 -216
- package/dist/node/plugins/wx/dev/hmr-files.d.ts +4 -5
- package/dist/node/plugins/wx/dev/hmr-files.js +18 -8
- package/dist/node/plugins/wx/dev/patch-publisher.d.ts +25 -5
- package/dist/node/plugins/wx/dev/patch-publisher.js +30 -9
- package/dist/node/plugins/wx/dev/react-refresh.d.ts +12 -0
- package/dist/node/plugins/wx/dev/react-refresh.js +111 -108
- package/dist/node/plugins/wx/dev/wx-dev-options.d.ts +25 -0
- package/dist/node/plugins/wx/dev/wx-dev-options.js +147 -0
- package/dist/node/utils/oxc-transform.d.ts +21 -0
- package/dist/node/utils/oxc-transform.js +58 -0
- package/dist/node/utils/serialized-task-queue.d.ts +6 -2
- package/dist/node/utils/serialized-task-queue.js +10 -2
- package/dist/runtime/wx/capsule/page.js +14 -5
- package/dist/runtime/wx/dev/dev-runtime.js +70 -26
- package/package.json +4 -3
- package/src/node/plugins/h5/create-stencil-client-adapter.ts +74 -73
- package/src/node/plugins/h5/plugins.ts +42 -13
- package/src/node/plugins/wx/dev/dev-host.ts +121 -256
- package/src/node/plugins/wx/dev/hmr-files.ts +20 -8
- package/src/node/plugins/wx/dev/patch-publisher.ts +36 -10
- package/src/node/plugins/wx/dev/react-refresh.ts +125 -129
- package/src/node/plugins/wx/dev/wx-dev-options.ts +200 -0
- package/src/node/utils/oxc-transform.ts +77 -0
- package/src/node/utils/serialized-task-queue.ts +15 -3
- package/src/runtime/wx/capsule/page.ts +23 -9
- package/src/runtime/wx/dev/dev-runtime.ts +80 -28
|
@@ -1,8 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isReferenceIdentifier } from 'oxc-walker';
|
|
2
2
|
import { memoize } from '../../../utils/memoize.js';
|
|
3
|
-
import {
|
|
3
|
+
import { transformWithOxcWalker } from '../../../utils/oxc-transform.js';
|
|
4
4
|
/** The React DevTools hook protocol name; free references must target `global` in wx. */
|
|
5
5
|
const reactDevtoolsHookProtocol = '__REACT_DEVTOOLS_GLOBAL_HOOK__';
|
|
6
|
+
/**
|
|
7
|
+
* Refresh protocol globals that must live on the WeChat `global` object:
|
|
8
|
+
* - `__registerBeforePerformReactRefresh` is assigned at module evaluation so the HMR client can register work that
|
|
9
|
+
* must finish before a refresh. Leaving it on `window` throws before the refresh runtime can initialize.
|
|
10
|
+
* - `__getReactRefreshIgnoredExports` is an optional extension point read while validating a refresh boundary.
|
|
11
|
+
* Leaving that read on the nonexistent `window` crashes every update validation pass.
|
|
12
|
+
*
|
|
13
|
+
* Keeping this list explicit prevents the adapter from rewriting unrelated browser accesses if the vendored runtime
|
|
14
|
+
* gains new code. Any future React Refresh protocol addition therefore requires a deliberate compatibility decision.
|
|
15
|
+
*/
|
|
16
|
+
const refreshRuntimeWindowGlobals = ['__registerBeforePerformReactRefresh', '__getReactRefreshIgnoredExports'];
|
|
6
17
|
/**
|
|
7
18
|
* Creates the serve-only React Refresh adaptation transforms for the wx target.
|
|
8
19
|
*
|
|
@@ -58,7 +69,7 @@ export function createWxReactRefreshTransforms() {
|
|
|
58
69
|
// occurrence, in boundary modules.
|
|
59
70
|
filter: { code: /window\.\$RefreshReg\$/ },
|
|
60
71
|
handler(code, id) {
|
|
61
|
-
return
|
|
72
|
+
return removeRefreshPreambleGuard({ code, id });
|
|
62
73
|
}
|
|
63
74
|
}
|
|
64
75
|
}
|
|
@@ -70,129 +81,121 @@ export function createWxReactRefreshTransforms() {
|
|
|
70
81
|
* The renderer checks the hook with `typeof __REACT_DEVTOOLS_GLOBAL_HOOK__` and injects via
|
|
71
82
|
* `hook.inject(...)` — but in the WeChat runtime, free-variable reads never resolve against
|
|
72
83
|
* `global`'s properties (verified: the free lookup is undefined while
|
|
73
|
-
* `global.__REACT_DEVTOOLS_GLOBAL_HOOK__` exists)
|
|
74
|
-
* injection
|
|
84
|
+
* `global.__REACT_DEVTOOLS_GLOBAL_HOOK__` exists). The renderer would therefore silently
|
|
85
|
+
* skip injection, leaving React Refresh with no renderer on which to schedule re-renders.
|
|
75
86
|
*
|
|
76
|
-
* The name is unique
|
|
77
|
-
*
|
|
78
|
-
*
|
|
87
|
+
* The protocol name is unique, but only reference identifiers are rewritten. Declaration
|
|
88
|
+
* keys and explicit members such as `global.__REACT_DEVTOOLS_GLOBAL_HOOK__` must remain
|
|
89
|
+
* untouched; rewriting those would either produce invalid syntax or double-prefix the hook.
|
|
90
|
+
* The hook itself is created on `global` by the dev runtime chunk in `dev-runtime.ts`.
|
|
79
91
|
*/
|
|
80
|
-
function
|
|
81
|
-
return {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (!types.isIdentifier(node, { name: reactDevtoolsHookProtocol })) {
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
const parent = identifierPath.parentPath;
|
|
90
|
-
// A member expression property (e.g. `global.__REACT_DEVTOOLS_GLOBAL_HOOK__`)
|
|
91
|
-
// is already explicit and must not be rewritten again.
|
|
92
|
-
if (parent.isMemberExpression() && parent.node.property === node) {
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
identifierPath.replaceWith(types.memberExpression(types.identifier('global'), types.identifier(node.name)));
|
|
96
|
-
}
|
|
92
|
+
function createReactDevtoolsHookVisitor(editor) {
|
|
93
|
+
return function enter(node, parent) {
|
|
94
|
+
if (node.type !== 'Identifier' ||
|
|
95
|
+
node.name !== reactDevtoolsHookProtocol ||
|
|
96
|
+
!isReferenceIdentifier(node, parent)) {
|
|
97
|
+
return;
|
|
97
98
|
}
|
|
99
|
+
// An explicit member access is required because WeChat does not expose properties of
|
|
100
|
+
// its global object as free lexical bindings. Removing this edit disables renderer
|
|
101
|
+
// registration even though the hook object itself still exists.
|
|
102
|
+
editor.overwrite(node.start, node.end, `global.${reactDevtoolsHookProtocol}`);
|
|
98
103
|
};
|
|
99
104
|
}
|
|
100
105
|
/**
|
|
101
|
-
* Refresh runtime module: self-inject at evaluation
|
|
106
|
+
* Refresh runtime module: self-inject at evaluation and rewrite its browser protocol globals.
|
|
102
107
|
*
|
|
103
|
-
* In web Vite,
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
* - the dev
|
|
108
|
-
*
|
|
108
|
+
* In web Vite, an HTML preamble calls `injectIntoGlobalHook(window)` before application
|
|
109
|
+
* modules load. wx has no HTML document or preamble, so nothing performs that bootstrap.
|
|
110
|
+
* The call must live in the refresh runtime module itself:
|
|
111
|
+
* - this module's closure owns `helpersByRendererID`, mounted roots, and the update helpers;
|
|
112
|
+
* - the wx dev-runtime chunk cannot call into it because the refresh module is in a later,
|
|
113
|
+
* lazily loaded chunk and does not exist when the dev-runtime chunk evaluates.
|
|
109
114
|
*
|
|
110
|
-
* Unlike the preamble's `$RefreshReg$` globals
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
* roots, so the injection is the one preamble responsibility that must be replicated.
|
|
115
|
+
* Unlike the preamble's `$RefreshReg$` globals, which boundary modules replace with local
|
|
116
|
+
* wrappers, the renderer-hook machinery has no local equivalent. Without this injected call,
|
|
117
|
+
* the runtime never learns about the renderer or mounted roots and updates cannot refresh UI.
|
|
114
118
|
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
+
* Appending the call is safe even when React has already registered its renderer: the refresh
|
|
120
|
+
* runtime replays `hook.renderers` during injection, then its patched commit hooks observe all
|
|
121
|
+
* later mounts and remounts. The same module also contains two browser-only `window` protocol
|
|
122
|
+
* accesses; those must point at `global` or evaluation/update validation throws in WeChat.
|
|
119
123
|
*/
|
|
120
|
-
function
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
124
|
+
function createRefreshRuntimeVisitor(editor) {
|
|
125
|
+
// The declarations must execute before self-injection, so the call is appended instead of
|
|
126
|
+
// prepended. Removing it would leave the web preamble's only essential responsibility
|
|
127
|
+
// unimplemented in wx.
|
|
128
|
+
editor.append('\ninjectIntoGlobalHook(global);');
|
|
129
|
+
return function enter(node) {
|
|
130
|
+
if (node.type !== 'MemberExpression' ||
|
|
131
|
+
node.computed ||
|
|
132
|
+
node.object.type !== 'Identifier' ||
|
|
133
|
+
node.object.name !== 'window' ||
|
|
134
|
+
node.property.type !== 'Identifier' ||
|
|
135
|
+
!refreshRuntimeWindowGlobals.some((globalName) => globalName === node.property.name)) {
|
|
136
|
+
return;
|
|
129
137
|
}
|
|
138
|
+
// `global` is the shared wx App heap used by the dev runtime and hook injection. Only
|
|
139
|
+
// replacing the object range preserves the vendored runtime byte-for-byte otherwise
|
|
140
|
+
// and prevents unrelated `window` expressions from being silently adapted.
|
|
141
|
+
editor.overwrite(node.object.start, node.object.end, 'global');
|
|
130
142
|
};
|
|
131
143
|
}
|
|
132
144
|
/**
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
* The vendored refresh runtime is written for browsers and uses `window` for its protocol
|
|
136
|
-
* globals, but the WeChat runtime has no `window` — the free identifier is undefined — so the
|
|
137
|
-
* top-level assignment would throw at module evaluation, and the ignored-exports read
|
|
138
|
-
* inside the refresh validator would crash every update pass.
|
|
145
|
+
* Removes the web-only `if (!window.$RefreshReg$) throw Error(...)` assertion from boundary modules.
|
|
139
146
|
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
147
|
+
* The assertion verifies that Vite's HTML preamble installed global registration helpers.
|
|
148
|
+
* wx has no preamble and evaluating `window` itself fails. The assertion is unnecessary here:
|
|
149
|
+
* @vitejs/plugin-react generates local `$RefreshReg$` and `$RefreshSig$` wrappers that delegate
|
|
150
|
+
* directly to the imported refresh runtime. Removing the whole statement therefore removes
|
|
151
|
+
* only an invalid platform check; component registration continues through those local wrappers.
|
|
152
|
+
* If this edit is removed, every transformed refresh boundary crashes before its module body runs.
|
|
142
153
|
*/
|
|
143
|
-
function
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
visitor: {
|
|
156
|
-
MemberExpression(memberPath) {
|
|
157
|
-
const member = memberPath.node;
|
|
158
|
-
if (!types.isIdentifier(member.object, { name: 'window' }) ||
|
|
159
|
-
!types.isIdentifier(member.property) ||
|
|
160
|
-
!refreshRuntimeWindowGlobals.includes(member.property.name)) {
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
// `global` is the WeChat global object — the same one the hook injection
|
|
164
|
-
// and the rest of the wx glue speak — so the protocol globals land on the
|
|
165
|
-
// App heap like any other global hook.
|
|
166
|
-
member.object = types.identifier('global');
|
|
167
|
-
}
|
|
154
|
+
function createRefreshPreambleGuardVisitor(editor) {
|
|
155
|
+
return function enter(node) {
|
|
156
|
+
if (node.type !== 'IfStatement' ||
|
|
157
|
+
node.test.type !== 'UnaryExpression' ||
|
|
158
|
+
node.test.operator !== '!' ||
|
|
159
|
+
node.test.argument.type !== 'MemberExpression' ||
|
|
160
|
+
node.test.argument.computed ||
|
|
161
|
+
node.test.argument.object.type !== 'Identifier' ||
|
|
162
|
+
node.test.argument.object.name !== 'window' ||
|
|
163
|
+
node.test.argument.property.type !== 'Identifier' ||
|
|
164
|
+
node.test.argument.property.name !== '$RefreshReg$') {
|
|
165
|
+
return;
|
|
168
166
|
}
|
|
167
|
+
// Matching the complete AST shape prevents an unrelated `$RefreshReg$` use from being
|
|
168
|
+
// removed. Skipping descendants is required because their ranges disappear with the
|
|
169
|
+
// parent and must not receive overlapping MagicString edits.
|
|
170
|
+
editor.remove(node.start, node.end);
|
|
171
|
+
this.skip();
|
|
169
172
|
};
|
|
170
173
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
174
|
+
export function transformRefreshRuntime({ code, id }) {
|
|
175
|
+
return transformWithOxcWalker({
|
|
176
|
+
code,
|
|
177
|
+
filename: id,
|
|
178
|
+
sourcemap: false,
|
|
179
|
+
createVisitor: createRefreshRuntimeVisitor
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
export function transformReactDevtoolsHook({ code, id }) {
|
|
183
|
+
return transformWithOxcWalker({
|
|
184
|
+
code,
|
|
185
|
+
filename: id,
|
|
186
|
+
sourcemap: false,
|
|
187
|
+
createVisitor: createReactDevtoolsHookVisitor
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
export function removeRefreshPreambleGuard({ code, id }) {
|
|
191
|
+
return transformWithOxcWalker({
|
|
192
|
+
code,
|
|
193
|
+
filename: id,
|
|
194
|
+
sourcemap: false,
|
|
195
|
+
createVisitor: createRefreshPreambleGuardVisitor
|
|
196
|
+
});
|
|
193
197
|
}
|
|
194
198
|
// The refresh runtime module and the react-family modules are immutable for the server's
|
|
195
|
-
// lifetime
|
|
196
|
-
|
|
197
|
-
const
|
|
198
|
-
const fixReactDevtoolsHook = memoize(({ code, id }) => transformWithBabel(code, id, [rewriteReactDevtoolsHookGlobal], false), { getCacheKey: ({ code }) => code });
|
|
199
|
+
// lifetime, so their Oxc parses run once per module and every build reuses the output.
|
|
200
|
+
const fixRefreshRuntime = memoize(transformRefreshRuntime, { getCacheKey: ({ code }) => code });
|
|
201
|
+
const fixReactDevtoolsHook = memoize(transformReactDevtoolsHook, { getCacheKey: ({ code }) => code });
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { InputOptions, OutputOptions } from 'rolldown';
|
|
2
|
+
import { type DevEngine } from 'rolldown/experimental';
|
|
3
|
+
import type { ViteDevServer } from 'vite';
|
|
4
|
+
import type { VitePluginTaroOptions } from '../../../../options.ts';
|
|
5
|
+
export type BundledDevRolldownOptions = InputOptions & {
|
|
6
|
+
experimental?: {
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
devMode?: boolean | Record<string, unknown>;
|
|
9
|
+
};
|
|
10
|
+
output?: OutputOptions | OutputOptions[];
|
|
11
|
+
};
|
|
12
|
+
export type BundledDev = {
|
|
13
|
+
_devEngine?: DevEngine;
|
|
14
|
+
getRolldownOptions(): Promise<BundledDevRolldownOptions>;
|
|
15
|
+
listen(): Promise<void>;
|
|
16
|
+
triggerBundleRegenerationIfStale(): Promise<boolean>;
|
|
17
|
+
};
|
|
18
|
+
/** Installs the physical WX output and runtime conventions over Vite's browser-oriented bundled-development options. */
|
|
19
|
+
export declare function installWxDevOptions({ bundledDev, server, options }: {
|
|
20
|
+
bundledDev: BundledDev;
|
|
21
|
+
server: ViteDevServer;
|
|
22
|
+
options: VitePluginTaroOptions;
|
|
23
|
+
}): Promise<void>;
|
|
24
|
+
/** Returns the configured output after rejecting states unsupported by the physical WX engine. */
|
|
25
|
+
export declare function requireSingleOutput(rolldownOptions: BundledDevRolldownOptions): OutputOptions;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { build } from 'rolldown';
|
|
3
|
+
import { viteReporterPlugin } from 'rolldown/experimental';
|
|
4
|
+
import { once } from '../../../utils/once.js';
|
|
5
|
+
import { resolvePackageFile } from '../../../utils/packages.js';
|
|
6
|
+
import { appShellFileName } from '../module.js';
|
|
7
|
+
import { createWxDevMode } from './create-wx-dev-mode.js';
|
|
8
|
+
import { emptyOutputDirectory } from './empty-output-directory.js';
|
|
9
|
+
/** Installs the physical WX output and runtime conventions over Vite's browser-oriented bundled-development options. */
|
|
10
|
+
export function installWxDevOptions({ bundledDev, server, options }) {
|
|
11
|
+
// The buildEnd hook and its one-shot result belong to the same abstraction. Consumers can await startup without receiving
|
|
12
|
+
// a resolver capable of settling it externally.
|
|
13
|
+
const initialBuild = Promise.withResolvers();
|
|
14
|
+
const settleInitialBuild = once((error) => {
|
|
15
|
+
if (error) {
|
|
16
|
+
initialBuild.reject(error);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
initialBuild.resolve();
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
const original = bundledDev.getRolldownOptions.bind(bundledDev);
|
|
23
|
+
bundledDev.getRolldownOptions = async () => {
|
|
24
|
+
const rolldownOptions = await original();
|
|
25
|
+
const output = ensureSingleOutput(rolldownOptions);
|
|
26
|
+
const configuredOutput = server.config.build.rolldownOptions.output;
|
|
27
|
+
if (Array.isArray(configuredOutput)) {
|
|
28
|
+
throw new Error('wx development supports one configured Rolldown output.');
|
|
29
|
+
}
|
|
30
|
+
const configured = configuredOutput ?? {};
|
|
31
|
+
// Every page entry must depend on hmr/patches.js: DevTools classifies a changed Page dependency as Page JavaScript
|
|
32
|
+
// hot reload and re-executes live Pages, which is the only physical patch trigger that preserves the App heap.
|
|
33
|
+
const pageFiles = new Set(options.pages.map((page) => `${page.path}.js`));
|
|
34
|
+
Object.assign(output, configured, {
|
|
35
|
+
assetFileNames: createStableFileNames(configured.assetFileNames, 'assets/[name][extname]'),
|
|
36
|
+
banner: createEntryBanner(pageFiles),
|
|
37
|
+
chunkFileNames: createStableFileNames(configured.chunkFileNames, 'assets/[name].js'),
|
|
38
|
+
entryFileNames: createStableFileNames(configured.entryFileNames, '[name]'),
|
|
39
|
+
format: 'es',
|
|
40
|
+
minify: true,
|
|
41
|
+
sourcemap: false
|
|
42
|
+
});
|
|
43
|
+
rolldownOptions.experimental ??= {};
|
|
44
|
+
rolldownOptions.experimental.devMode = createWxDevMode(rolldownOptions.experimental.devMode, await bundleRuntimeSource());
|
|
45
|
+
const emptyOutputDirectoryPlugin = {
|
|
46
|
+
name: 'vpt:wx-empty-output-directory',
|
|
47
|
+
renderStart: {
|
|
48
|
+
order: 'pre',
|
|
49
|
+
// DevEngine bypasses Vite's build-only output preparation. Preserve the watched directory itself while
|
|
50
|
+
// removing stale contents before each complete physical render.
|
|
51
|
+
handler: () => emptyOutputDirectory(server.config.build.outDir)
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
const reportInitialBuildPlugin = {
|
|
55
|
+
name: 'vpt:wx-report-initial-build',
|
|
56
|
+
buildEnd: settleInitialBuild
|
|
57
|
+
};
|
|
58
|
+
rolldownOptions.plugins = [
|
|
59
|
+
emptyOutputDirectoryPlugin,
|
|
60
|
+
rolldownOptions.plugins,
|
|
61
|
+
reportInitialBuildPlugin,
|
|
62
|
+
createViteReporter(server)
|
|
63
|
+
];
|
|
64
|
+
disableViteOxcSourcemap(rolldownOptions.plugins);
|
|
65
|
+
return rolldownOptions;
|
|
66
|
+
};
|
|
67
|
+
return initialBuild.promise;
|
|
68
|
+
}
|
|
69
|
+
/** Returns the configured output after rejecting states unsupported by the physical WX engine. */
|
|
70
|
+
export function requireSingleOutput(rolldownOptions) {
|
|
71
|
+
if (!rolldownOptions.output || Array.isArray(rolldownOptions.output)) {
|
|
72
|
+
throw new Error('wx development requires exactly one Rolldown output.');
|
|
73
|
+
}
|
|
74
|
+
return rolldownOptions.output;
|
|
75
|
+
}
|
|
76
|
+
/** Creates the one missing output object while rejecting a configured output array. */
|
|
77
|
+
function ensureSingleOutput(rolldownOptions) {
|
|
78
|
+
if (Array.isArray(rolldownOptions.output)) {
|
|
79
|
+
throw new Error('wx development requires one configured Rolldown output.');
|
|
80
|
+
}
|
|
81
|
+
rolldownOptions.output ??= {};
|
|
82
|
+
return rolldownOptions.output;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Prepends entry banners after Rolldown's analysis so their native requires remain physical dependencies rather than chunk
|
|
86
|
+
* graph edges. The App initializes the runtime identity, while every Page explicitly applies the watched patch data before its
|
|
87
|
+
* capsule import continues.
|
|
88
|
+
*/
|
|
89
|
+
function createEntryBanner(pageFiles) {
|
|
90
|
+
return (chunk) => {
|
|
91
|
+
if (chunk.name === appShellFileName) {
|
|
92
|
+
return "__rolldown_runtime__.initialize(require('./hmr/info.js'));\n";
|
|
93
|
+
}
|
|
94
|
+
if (pageFiles.has(chunk.name)) {
|
|
95
|
+
const patchesPath = path.posix.relative(path.posix.dirname(chunk.fileName), 'hmr/patches.js');
|
|
96
|
+
return `__rolldown_runtime__.applyPatches(require('${patchesPath}'));\n`;
|
|
97
|
+
}
|
|
98
|
+
return '';
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function createStableFileNames(addon, fallback) {
|
|
102
|
+
if (typeof addon === 'function') {
|
|
103
|
+
return (value) => toStableFileName(String(addon(value)));
|
|
104
|
+
}
|
|
105
|
+
return toStableFileName(typeof addon === 'string' ? addon : fallback);
|
|
106
|
+
}
|
|
107
|
+
function toStableFileName(fileName) {
|
|
108
|
+
return fileName
|
|
109
|
+
.replace(/(^|\/)\[hash(?::\d+)?\](?=\.|$)/g, '$1[name]')
|
|
110
|
+
.replace(/[-_.]\[hash(?::\d+)?\]/g, '')
|
|
111
|
+
.replace(/\[hash(?::\d+)?\]/g, '[name]');
|
|
112
|
+
}
|
|
113
|
+
function disableViteOxcSourcemap(pluginOption) {
|
|
114
|
+
if (Array.isArray(pluginOption)) {
|
|
115
|
+
pluginOption.forEach(disableViteOxcSourcemap);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (!pluginOption || typeof pluginOption !== 'object')
|
|
119
|
+
return;
|
|
120
|
+
const plugin = pluginOption;
|
|
121
|
+
if (plugin.name === 'builtin:vite-transform' && plugin._options?.transformOptions) {
|
|
122
|
+
plugin._options.transformOptions.sourcemap = false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function createViteReporter(server) {
|
|
126
|
+
const { build, logger, root } = server.config;
|
|
127
|
+
return viteReporterPlugin({
|
|
128
|
+
assetsDir: path.join(build.assetsDir, '/'),
|
|
129
|
+
chunkLimit: 2000,
|
|
130
|
+
isLib: Boolean(build.lib),
|
|
131
|
+
isTty: Boolean(process.stdout.isTTY && !process.env.CI),
|
|
132
|
+
logInfo: (message) => logger.info(message),
|
|
133
|
+
reportCompressedSize: false,
|
|
134
|
+
root,
|
|
135
|
+
warnLargeChunks: false
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
// The runtime source is immutable for the host's lifetime, so the nested bundle is shared by every complete build.
|
|
139
|
+
const bundleRuntimeSource = once(async function bundleRuntimeSource() {
|
|
140
|
+
// write: false keeps this nested helper build from creating a second dist directory in the application project.
|
|
141
|
+
const result = await build({
|
|
142
|
+
input: resolvePackageFile('dist/runtime/wx/dev/dev-runtime.js'),
|
|
143
|
+
output: { format: 'iife', minify: true, sourcemap: false },
|
|
144
|
+
write: false
|
|
145
|
+
});
|
|
146
|
+
return result.output[0].code;
|
|
147
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type WalkerEnter } from 'oxc-walker';
|
|
2
|
+
import { RolldownMagicString } from 'rolldown';
|
|
3
|
+
import type { AstTransformResult } from './transform.ts';
|
|
4
|
+
type OxcTransformOptions = {
|
|
5
|
+
code: string;
|
|
6
|
+
filename: string;
|
|
7
|
+
sourcemap: boolean;
|
|
8
|
+
createVisitor(editor: RolldownMagicString): WalkerEnter;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Parses once with Rolldown's Oxc parser and applies precise edits through its native Rust MagicString.
|
|
12
|
+
*
|
|
13
|
+
* These adapters previously used Babel plugins for one or two local range changes. Babel then
|
|
14
|
+
* cloned and regenerated the complete module, which was unnecessary work for large dependency
|
|
15
|
+
* sources and changed formatting outside the intended edit. Sharing Rolldown's parser and editor
|
|
16
|
+
* keeps one AST implementation across Vite's build engine and these transforms while preserving
|
|
17
|
+
* untouched source exactly. Callers provide only a visitor, so parsing, diagnostics, source-map
|
|
18
|
+
* policy, and result normalization cannot drift between adapters.
|
|
19
|
+
*/
|
|
20
|
+
export declare function transformWithOxcWalker({ code, filename, sourcemap, createVisitor }: OxcTransformOptions): AstTransformResult;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { walk } from 'oxc-walker';
|
|
2
|
+
import { RolldownMagicString } from 'rolldown';
|
|
3
|
+
import { parseSync } from 'rolldown/utils';
|
|
4
|
+
/**
|
|
5
|
+
* Parses once with Rolldown's Oxc parser and applies precise edits through its native Rust MagicString.
|
|
6
|
+
*
|
|
7
|
+
* These adapters previously used Babel plugins for one or two local range changes. Babel then
|
|
8
|
+
* cloned and regenerated the complete module, which was unnecessary work for large dependency
|
|
9
|
+
* sources and changed formatting outside the intended edit. Sharing Rolldown's parser and editor
|
|
10
|
+
* keeps one AST implementation across Vite's build engine and these transforms while preserving
|
|
11
|
+
* untouched source exactly. Callers provide only a visitor, so parsing, diagnostics, source-map
|
|
12
|
+
* policy, and result normalization cannot drift between adapters.
|
|
13
|
+
*/
|
|
14
|
+
export function transformWithOxcWalker({ code, filename, sourcemap, createVisitor }) {
|
|
15
|
+
// `RolldownMagicString` stores edits in Rolldown's native layer. A separate `magic-string`
|
|
16
|
+
// instance would duplicate a dependency already supplied by the build engine and would make
|
|
17
|
+
// source-map generation cross the JavaScript boundary for every recorded segment.
|
|
18
|
+
const editor = new RolldownMagicString(code, { filename });
|
|
19
|
+
const result = parseSync(filename, code);
|
|
20
|
+
// Oxc can return a recoverable AST together with diagnostics. Walking that partial tree could
|
|
21
|
+
// let a visitor edit malformed input and hide the original syntax failure, so diagnostics are
|
|
22
|
+
// rejected before any caller-owned edits run.
|
|
23
|
+
if (result.errors.length > 0) {
|
|
24
|
+
const diagnostics = result.errors.map((error) => error.message).join('; ');
|
|
25
|
+
throw new Error(`Failed to parse ${filename} with Oxc: ${diagnostics}`);
|
|
26
|
+
}
|
|
27
|
+
// All visitor work is O(n) in AST size; range edits remain local and do not trigger a second
|
|
28
|
+
// parse or whole-file code-generation pass.
|
|
29
|
+
walk(result.program, { enter: createVisitor(editor) });
|
|
30
|
+
// Boundary-resolution maps retain exact mappings around edits without the much larger cost
|
|
31
|
+
// of mapping every character. Original content is required for Vite to compose this map with
|
|
32
|
+
// subsequent optimizer or application transforms.
|
|
33
|
+
const generatedMap = sourcemap
|
|
34
|
+
? editor.generateMap({
|
|
35
|
+
file: filename,
|
|
36
|
+
hires: 'boundary',
|
|
37
|
+
includeContent: true,
|
|
38
|
+
source: filename
|
|
39
|
+
})
|
|
40
|
+
: null;
|
|
41
|
+
return {
|
|
42
|
+
code: editor.toString(),
|
|
43
|
+
map: generatedMap
|
|
44
|
+
? {
|
|
45
|
+
// Return a plain source-map value instead of leaking Rolldown's native wrapper
|
|
46
|
+
// through Vite's plugin API. This also keeps the result identical in the normal
|
|
47
|
+
// application pipeline and the optimizer's independent Rolldown build.
|
|
48
|
+
version: generatedMap.version,
|
|
49
|
+
file: generatedMap.file,
|
|
50
|
+
sources: generatedMap.sources,
|
|
51
|
+
sourcesContent: generatedMap.sourcesContent,
|
|
52
|
+
names: generatedMap.names,
|
|
53
|
+
mappings: generatedMap.mappings,
|
|
54
|
+
...(generatedMap.x_google_ignoreList ? { x_google_ignoreList: generatedMap.x_google_ignoreList } : {})
|
|
55
|
+
}
|
|
56
|
+
: null
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
/** Runs
|
|
1
|
+
/** Runs asynchronous tasks in insertion order without letting one failure block later work. */
|
|
2
2
|
export declare class SerializedTaskQueue {
|
|
3
|
+
/** Mutable promise tail confines ordering state to this queue. */
|
|
3
4
|
private tail;
|
|
4
5
|
private readonly reportError;
|
|
5
6
|
constructor(reportError: (operation: string, error: unknown) => void);
|
|
6
|
-
|
|
7
|
+
/** Schedules a background task and reports its failure. */
|
|
8
|
+
enqueue(operation: string, task: () => void | PromiseLike<void>): void;
|
|
9
|
+
/** Schedules a task whose result and failure belong to the caller. */
|
|
10
|
+
run<Result>(task: () => Result | PromiseLike<Result>): Promise<Result>;
|
|
7
11
|
/** Waits for every task that was queued when this method was called. */
|
|
8
12
|
waitForIdle(): Promise<void>;
|
|
9
13
|
}
|
|
@@ -1,15 +1,23 @@
|
|
|
1
|
-
/** Runs
|
|
1
|
+
/** Runs asynchronous tasks in insertion order without letting one failure block later work. */
|
|
2
2
|
export class SerializedTaskQueue {
|
|
3
|
+
/** Mutable promise tail confines ordering state to this queue. */
|
|
3
4
|
tail = Promise.resolve();
|
|
4
5
|
reportError;
|
|
5
6
|
constructor(reportError) {
|
|
6
7
|
this.reportError = reportError;
|
|
7
8
|
}
|
|
9
|
+
/** Schedules a background task and reports its failure. */
|
|
8
10
|
enqueue(operation, task) {
|
|
9
|
-
|
|
11
|
+
void this.run(task).catch((error) => {
|
|
10
12
|
this.reportError(operation, error);
|
|
11
13
|
});
|
|
12
14
|
}
|
|
15
|
+
/** Schedules a task whose result and failure belong to the caller. */
|
|
16
|
+
run(task) {
|
|
17
|
+
const result = this.tail.then(task);
|
|
18
|
+
this.tail = result.then(() => undefined, () => undefined);
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
13
21
|
/** Waits for every task that was queued when this method was called. */
|
|
14
22
|
async waitForIdle() {
|
|
15
23
|
await this.tail;
|
|
@@ -13,22 +13,27 @@ function forward(handler, receiver, args) {
|
|
|
13
13
|
// DevTools re-executes the page and replays the replacement lifecycle on every edit. Taro's
|
|
14
14
|
// onUnload unmounts the React tree and onLoad mounts a fresh one, destroying state. While
|
|
15
15
|
// the runtime says a patch was just delivered, the pair is intercepted instead: onUnload
|
|
16
|
-
// captures the surviving page identity
|
|
17
|
-
//
|
|
18
|
-
// capsule module is cached across re-executions, so this closure holds the capture.
|
|
16
|
+
// captures the surviving page identity and its current native render data, and onLoad
|
|
17
|
+
// immediately paints that snapshot before rebinding and fully synchronizing the retained
|
|
18
|
+
// tree. The capsule module is cached across re-executions, so this closure holds the capture.
|
|
19
19
|
// Ordinary navigation (no patch) passes through unchanged. The wrappers are regular
|
|
20
20
|
// functions so the native page instance (`this`) reaches the original handlers.
|
|
21
21
|
if (typeof __rolldown_runtime__ !== 'undefined') {
|
|
22
22
|
const originalOnUnload = config.onUnload;
|
|
23
23
|
const originalOnLoad = config.onLoad;
|
|
24
24
|
const originalOnShow = config.onShow;
|
|
25
|
+
// This is the sole cross-instance handoff. It is mutable because DevTools destroys the
|
|
26
|
+
// old native Page before creating its replacement; ordinary navigation never reads it.
|
|
25
27
|
let captured;
|
|
26
28
|
config.onUnload = function (...args) {
|
|
27
29
|
if (__rolldown_runtime__.isHotReloading()) {
|
|
28
30
|
const instance = this;
|
|
29
31
|
captured = {
|
|
30
32
|
$taroPath: instance.$taroPath,
|
|
31
|
-
$taroParams: instance.$taroParams
|
|
33
|
+
$taroParams: instance.$taroParams,
|
|
34
|
+
// WeChat owns this serializable view-model snapshot. Retaining it is cheaper
|
|
35
|
+
// than deep-cloning the complete recursive Taro node tree before every edit.
|
|
36
|
+
data: instance.data
|
|
32
37
|
};
|
|
33
38
|
return;
|
|
34
39
|
}
|
|
@@ -36,10 +41,14 @@ if (typeof __rolldown_runtime__ !== 'undefined') {
|
|
|
36
41
|
};
|
|
37
42
|
config.onLoad = function (...args) {
|
|
38
43
|
if (__rolldown_runtime__.isHotReloading() && captured) {
|
|
44
|
+
const instance = this;
|
|
45
|
+
// Paint the last native projection as the first bridge operation. Taro's full
|
|
46
|
+
// performUpdate below runs in a timer, so this direct setData removes most of the
|
|
47
|
+
// empty-page interval without delaying synchronization to the current tree.
|
|
48
|
+
instance.setData(captured.data);
|
|
39
49
|
// Restore the surviving page identity onto the replacement native instance: the
|
|
40
50
|
// re-loaded $taroPath embeds a fresh timestamp, so without this the tree and the
|
|
41
51
|
// native side would disagree on the page key.
|
|
42
|
-
const instance = this;
|
|
43
52
|
instance.$taroPath = captured.$taroPath;
|
|
44
53
|
instance.$taroParams = captured.$taroParams;
|
|
45
54
|
// Rebind the retained tree to the replacement receiver and resync the native
|