vite-plugin-taro 0.5.6 → 0.5.9

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.
@@ -1,2 +1,4 @@
1
+ /** Creates the one startup cleanup allowed before Rolldown begins tracking incremental output in memory. */
2
+ export declare function createInitialOutputDirectoryCleaner(directory: string): () => Promise<void>;
1
3
  /** Empties an output directory without replacing the directory itself or deleting Git metadata. */
2
4
  export declare function emptyOutputDirectory(directory: string): Promise<void>;
@@ -1,5 +1,12 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
+ import { once } from '../../../utils/once.js';
4
+ /** Creates the one startup cleanup allowed before Rolldown begins tracking incremental output in memory. */
5
+ export function createInitialOutputDirectoryCleaner(directory) {
6
+ // Later full builds suppress byte-identical emitted assets. Clearing again would delete those files behind Rolldown's
7
+ // output cache, so the writer would correctly emit no bytes and leave the physical Mini Program incomplete.
8
+ return once(() => emptyOutputDirectory(directory));
9
+ }
3
10
  /** Empties an output directory without replacing the directory itself or deleting Git metadata. */
4
11
  export async function emptyOutputDirectory(directory) {
5
12
  await fs.mkdir(directory, { recursive: true });
@@ -0,0 +1,22 @@
1
+ import type { PluginOption } from 'vite';
2
+ import type { VitePluginTaroOptions } from '../../../../options.ts';
3
+ /**
4
+ * Adds the serve-only bundled-development plugin set for the wx target: the dev adapter,
5
+ * Page HMR activation, and React Refresh adaptation transforms.
6
+ */
7
+ export declare function createWxDevelopmentPlugin(options: VitePluginTaroOptions): PluginOption[];
8
+ /** Ensures the Refresh hook exists before React's renderer evaluates and injects itself. */
9
+ export declare function injectReactRefreshBootstrap(code: string): {
10
+ code: string;
11
+ map: null;
12
+ };
13
+ /** Connects the shared WX dev runtime to the application graph's Taro runtime instance. */
14
+ export declare function injectTaroConnection(code: string): {
15
+ code: string;
16
+ map: null;
17
+ };
18
+ /** Activates development-only lifecycle handling for one plugin-owned Page capsule. */
19
+ export declare function injectPageHmr(code: string, route: string): {
20
+ code: string;
21
+ map: null;
22
+ };
@@ -0,0 +1,158 @@
1
+ import { transformWithOxc } from 'vite';
2
+ import { esTarget } from '../../../utils/constant.js';
3
+ import { memoize } from '../../../utils/memoize.js';
4
+ import { normalizeModuleId } from '../../../utils/modules.js';
5
+ import { appCapsulePath, pageCapsulePath, rolldownRuntimeId, taroRuntimePath } from '../module.js';
6
+ import { createWxDevHost } from './dev-host.js';
7
+ import { createWxReactRefreshTransforms } from './react-refresh.js';
8
+ const taroRuntimeId = '@tarojs/runtime';
9
+ /**
10
+ * Adds the serve-only bundled-development plugin set for the wx target: the dev adapter,
11
+ * Page HMR activation, and React Refresh adaptation transforms.
12
+ */
13
+ export function createWxDevelopmentPlugin(options) {
14
+ let host = null;
15
+ // Portable hook filters stay broad; these exact identities exclude similarly named user modules.
16
+ const normalizedAppCapsulePath = normalizeModuleId(appCapsulePath);
17
+ const normalizedPageCapsulePath = normalizeModuleId(pageCapsulePath);
18
+ const normalizedTaroRuntimePath = normalizeModuleId(taroRuntimePath);
19
+ return [
20
+ {
21
+ name: 'vpt:wx-dev',
22
+ apply: 'serve',
23
+ config() {
24
+ return {
25
+ build: {
26
+ // Disable maps in resolved environment config as well as final output so Oxc and Babel skip producing
27
+ // intermediate maps that Rolldown would discard.
28
+ sourcemap: false
29
+ },
30
+ experimental: {
31
+ // Ask Vite to resolve its bundled-development graph and expose the private adapter instance. The wx
32
+ // configureServer hook replaces only its startup method with the directly writing DevEngine.
33
+ bundledDev: true
34
+ }
35
+ };
36
+ },
37
+ configureServer: {
38
+ // Install after Vite and user plugins have finished configuring the environment, but before server.listen()
39
+ // asks bundledDev to create its hard-coded skip-write DevEngine.
40
+ order: 'post',
41
+ async handler(server) {
42
+ host = await createWxDevHost({ server, options });
43
+ }
44
+ },
45
+ closeBundle() {
46
+ return host?.close();
47
+ }
48
+ },
49
+ {
50
+ name: 'vpt:wx-runtime-lowering',
51
+ apply: 'serve',
52
+ transform: {
53
+ order: 'post',
54
+ // The dev-mode transform assembles the runtime chunk (Rolldown's base runtime
55
+ // plus our injected implement) as this module's transform output, which
56
+ // bypasses the build's es2018 lowering. Real-device engines and WeChat's
57
+ // upload parser predate class fields and nullish operators, so the assembled
58
+ // runtime is lowered here — the only module that needs it. The exact id
59
+ // filter needs no code scan; the id must stay in sync with rolldownRuntimeId
60
+ // in module.ts (kept as a regex for the Rolldown-side filter).
61
+ filter: { id: /^\0rolldown\/runtime\.js(?:\?|$)/ },
62
+ handler(code) {
63
+ // The `setPublicClassFields` assumption emits plain `this.x = ...`
64
+ // assignments instead of external helpers, whose references the later
65
+ // minifier would mangle.
66
+ return fixRolldownRuntime(code);
67
+ }
68
+ }
69
+ },
70
+ {
71
+ name: 'vpt:wx-react-refresh-bootstrap',
72
+ apply: 'serve',
73
+ transform: {
74
+ order: 'post',
75
+ filter: { id: /\/runtime\/wx\/capsule\/app\.js(?:\?|$)/ },
76
+ handler(code, id) {
77
+ if (normalizeModuleId(id) !== normalizedAppCapsulePath)
78
+ return;
79
+ return injectReactRefreshBootstrap(code);
80
+ }
81
+ }
82
+ },
83
+ {
84
+ name: 'vpt:wx-page-hmr',
85
+ apply: 'serve',
86
+ transform: {
87
+ order: 'post',
88
+ filter: { id: /\/runtime\/wx\/capsule\/page\.js(?:\?|$)/ },
89
+ handler(code, id) {
90
+ if (normalizeModuleId(id) !== normalizedPageCapsulePath)
91
+ return;
92
+ return injectPageHmr(code, getPageRoute(id));
93
+ }
94
+ }
95
+ },
96
+ {
97
+ name: 'vpt:wx-taro-hmr',
98
+ apply: 'serve',
99
+ transform: {
100
+ order: 'post',
101
+ filter: { id: /\/runtime\/wx\/capsule\/taro-runtime\.js(?:\?|$)/ },
102
+ handler(code, id) {
103
+ if (normalizeModuleId(id) !== normalizedTaroRuntimePath)
104
+ return;
105
+ return injectTaroConnection(code);
106
+ }
107
+ }
108
+ },
109
+ ...createWxReactRefreshTransforms()
110
+ ];
111
+ }
112
+ /** Ensures the Refresh hook exists before React's renderer evaluates and injects itself. */
113
+ export function injectReactRefreshBootstrap(code) {
114
+ return {
115
+ code: `import ${JSON.stringify('/@react-refresh')};\n${code}`,
116
+ map: null
117
+ };
118
+ }
119
+ /** Connects the shared WX dev runtime to the application graph's Taro runtime instance. */
120
+ export function injectTaroConnection(code) {
121
+ if (!/\bCurrent\b/.test(code) || !/\bdocument\b/.test(code) || !/\binjectPageInstance\b/.test(code)) {
122
+ throw new Error('WX Taro runtime must expose Current, document, and injectPageInstance for HMR');
123
+ }
124
+ const taroImport = `import { Current as __vptCurrent, document as __vptDocument, injectPageInstance as __vptInjectPageInstance } from ${JSON.stringify(taroRuntimeId)};`;
125
+ return {
126
+ code: `${code}\n${taroImport}\n__rolldown_runtime__.connectTaro(__vptCurrent, __vptDocument, __vptInjectPageInstance);`,
127
+ map: null
128
+ };
129
+ }
130
+ /** Activates development-only lifecycle handling for one plugin-owned Page capsule. */
131
+ export function injectPageHmr(code, route) {
132
+ if (!/\bconst\s+config\s*=/.test(code) || !/\bexport\s+default\s+config\b/.test(code)) {
133
+ throw new Error('WX Page capsule must declare and default-export config before HMR injection');
134
+ }
135
+ return {
136
+ code: `${code}\n__rolldown_runtime__.injectPageHmr(config, ${JSON.stringify(route)});`,
137
+ map: null
138
+ };
139
+ }
140
+ /** Reads the stable route carried by every specialized Page capsule ID. */
141
+ function getPageRoute(id) {
142
+ const queryIndex = id.indexOf('?');
143
+ const route = queryIndex < 0 ? null : new URLSearchParams(id.slice(queryIndex + 1)).get('route');
144
+ if (!route)
145
+ throw new Error(`WX Page capsule is missing its route: ${id}`);
146
+ return route;
147
+ }
148
+ // The assembled runtime chunk is byte-identical on every build (the base runtime and the
149
+ // bundled implement are immutable for the server's lifetime), so the lowering runs once
150
+ // and every build reuses it.
151
+ const fixRolldownRuntime = memoize((code) => {
152
+ return transformWithOxc(code, rolldownRuntimeId, {
153
+ lang: 'js',
154
+ target: esTarget,
155
+ sourcemap: false,
156
+ assumptions: { setPublicClassFields: true }
157
+ });
158
+ });
@@ -5,8 +5,7 @@ import type { Plugin } from 'vite';
5
5
  * @vitejs/plugin-react's generated refresh code assumes the web HTML preamble and a browser
6
6
  * global scope; wx has neither. Each transform adapts one piece of that contract:
7
7
  * - the refresh runtime module (id-filtered): the vendored runtime reads and assigns
8
- * `window` protocol globals (rewritten to `global`) and must inject itself at evaluation
9
- * — the preamble's `injectIntoGlobalHook` call has no HTML home in wx;
8
+ * `window` protocol globals (rewritten to `global`) and must inject itself at evaluation;
10
9
  * - react-family modules (filtered on free references): the DevTools hook is read as a free
11
10
  * variable, which the WeChat runtime scope never resolves against `global` — every free
12
11
  * reference becomes an explicit member access;
@@ -20,8 +20,7 @@ const refreshRuntimeWindowGlobals = ['__registerBeforePerformReactRefresh', '__g
20
20
  * @vitejs/plugin-react's generated refresh code assumes the web HTML preamble and a browser
21
21
  * global scope; wx has neither. Each transform adapts one piece of that contract:
22
22
  * - the refresh runtime module (id-filtered): the vendored runtime reads and assigns
23
- * `window` protocol globals (rewritten to `global`) and must inject itself at evaluation
24
- * — the preamble's `injectIntoGlobalHook` call has no HTML home in wx;
23
+ * `window` protocol globals (rewritten to `global`) and must inject itself at evaluation;
25
24
  * - react-family modules (filtered on free references): the DevTools hook is read as a free
26
25
  * variable, which the WeChat runtime scope never resolves against `global` — every free
27
26
  * reference becomes an explicit member access;
@@ -87,7 +86,7 @@ export function createWxReactRefreshTransforms() {
87
86
  * The protocol name is unique, but only reference identifiers are rewritten. Declaration
88
87
  * keys and explicit members such as `global.__REACT_DEVTOOLS_GLOBAL_HOOK__` must remain
89
88
  * 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`.
89
+ * The eagerly evaluated refresh runtime creates the hook on `global` before the renderer loads.
91
90
  */
92
91
  function createReactDevtoolsHookVisitor(editor) {
93
92
  return function enter(node, parent) {
@@ -127,18 +126,17 @@ function createRefreshRuntimeVisitor(editor) {
127
126
  // unimplemented in wx.
128
127
  editor.append('\ninjectIntoGlobalHook(global);');
129
128
  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
+ if (node.type === 'MemberExpression' &&
130
+ !node.computed &&
131
+ node.object.type === 'Identifier' &&
132
+ node.object.name === 'window' &&
133
+ node.property.type === 'Identifier' &&
134
+ refreshRuntimeWindowGlobals.some((globalName) => globalName === node.property.name)) {
135
+ // `global` is the shared wx App heap used by the dev runtime and hook injection. Only
136
+ // replacing the object range preserves the vendored runtime byte-for-byte otherwise
137
+ // and prevents unrelated `window` expressions from being silently adapted.
138
+ editor.overwrite(node.object.start, node.object.end, 'global');
137
139
  }
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');
142
140
  };
143
141
  }
144
142
  /**
@@ -5,7 +5,7 @@ import { once } from '../../../utils/once.js';
5
5
  import { resolvePackageFile } from '../../../utils/packages.js';
6
6
  import { appShellFileName } from '../module.js';
7
7
  import { createWxDevMode } from './create-wx-dev-mode.js';
8
- import { emptyOutputDirectory } from './empty-output-directory.js';
8
+ import { createInitialOutputDirectoryCleaner } from './empty-output-directory.js';
9
9
  /** Installs the physical WX output and runtime conventions over Vite's browser-oriented bundled-development options. */
10
10
  export function installWxDevOptions({ bundledDev, server, options }) {
11
11
  // The buildEnd hook and its one-shot result belong to the same abstraction. Consumers can await startup without receiving
@@ -42,13 +42,14 @@ export function installWxDevOptions({ bundledDev, server, options }) {
42
42
  });
43
43
  rolldownOptions.experimental ??= {};
44
44
  rolldownOptions.experimental.devMode = createWxDevMode(rolldownOptions.experimental.devMode, await bundleRuntimeSource());
45
- const emptyOutputDirectoryPlugin = {
46
- name: 'vpt:wx-empty-output-directory',
45
+ const initializeOutputDirectory = createInitialOutputDirectoryCleaner(server.config.build.outDir);
46
+ const initializeOutputDirectoryPlugin = {
47
+ name: 'vpt:wx-initialize-output-directory',
47
48
  renderStart: {
48
49
  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)
50
+ // DevEngine bypasses Vite's build-only output preparation. Remove stale startup contents once, before
51
+ // Rolldown starts treating its in-memory incremental output as authoritative for later complete builds.
52
+ handler: initializeOutputDirectory
52
53
  }
53
54
  };
54
55
  const reportInitialBuildPlugin = {
@@ -56,7 +57,7 @@ export function installWxDevOptions({ bundledDev, server, options }) {
56
57
  buildEnd: settleInitialBuild
57
58
  };
58
59
  rolldownOptions.plugins = [
59
- emptyOutputDirectoryPlugin,
60
+ initializeOutputDirectoryPlugin,
60
61
  rolldownOptions.plugins,
61
62
  reportInitialBuildPlugin,
62
63
  createViteReporter(server)
@@ -93,7 +94,8 @@ function createEntryBanner(pageFiles) {
93
94
  }
94
95
  if (pageFiles.has(chunk.name)) {
95
96
  const patchesPath = path.posix.relative(path.posix.dirname(chunk.fileName), 'hmr/patches.js');
96
- return `__rolldown_runtime__.applyPatches(require('${patchesPath}'));\n`;
97
+ const route = chunk.name.slice(0, -'.js'.length);
98
+ return `__rolldown_runtime__.applyPatches(require('${patchesPath}'), ${JSON.stringify(route)});\n`;
97
99
  }
98
100
  return '';
99
101
  };
@@ -27,6 +27,8 @@ export declare const pageComponentId = "\0vpt:page-component";
27
27
  export declare const pageCapsuleId = "\0vpt:page-capsule";
28
28
  /** Provides the Page capsule source specialized through a stable route query. */
29
29
  export declare const pageCapsulePath: string;
30
+ /** Identifies the Taro facade shared by the App, Page, and recursive Component capsules. */
31
+ export declare const taroRuntimePath: string;
30
32
  /** Identifies the reusable synchronous native Page shell source. */
31
33
  export declare const pageShellPath: string;
32
34
  export type WxChunk = Rolldown.PreRenderedChunk | Rolldown.RenderedChunk;
@@ -29,6 +29,8 @@ export const pageComponentId = '\0vpt:page-component';
29
29
  export const pageCapsuleId = '\0vpt:page-capsule';
30
30
  /** Provides the Page capsule source specialized through a stable route query. */
31
31
  export const pageCapsulePath = resolvePackageFile('dist/runtime/wx/capsule/page.js');
32
+ /** Identifies the Taro facade shared by the App, Page, and recursive Component capsules. */
33
+ export const taroRuntimePath = resolvePackageFile('dist/runtime/wx/capsule/taro-runtime.js');
32
34
  /** Identifies the reusable synchronous native Page shell source. */
33
35
  export const pageShellPath = resolvePackageFile('dist/runtime/wx/native/page.js');
34
36
  // These fixed source identities describe entry roles independently from the final execution kind. A capsule entry may,
@@ -1,7 +1,7 @@
1
1
  import { esTarget } from '../../utils/constant.js';
2
2
  import { packageRequire } from '../../utils/packages.js';
3
3
  import { clientTaroNativeId } from '../client/constant.js';
4
- import { createWxDevelopmentPlugin } from './dev/plugin.js';
4
+ import { createWxDevelopmentPlugin } from './dev/plugins.js';
5
5
  import { getWxExecutionKind, isTransportModule } from './module.js';
6
6
  import { compileNativeComponentInterface } from './native/compile-native-component-interface.js';
7
7
  import { getNativeComponentAssetBytes } from './native/native-component-assets.js';
@@ -104,8 +104,8 @@ function createWxPlugin(options) {
104
104
  generateBundle: {
105
105
  /*
106
106
  * This hook is registered after createCssPlugins() and shares hook-level `order: 'post'` with the adapted
107
- * upstream hooks and VPT style finalizer. Registration order therefore guarantees that app.wxss is complete
108
- * before native Page/component companions are emitted. Without this order, the CSS finalizer could consume
107
+ * upstream hooks and VPT style finalizer. Registration order therefore guarantees that the imported global
108
+ * stylesheet is complete before native Page/component companions are emitted. Without this order, the finalizer could consume
109
109
  * incomplete Tailwind output or mistake native WXSS companions for additional compiler styles.
110
110
  */
111
111
  order: 'post',
@@ -2,76 +2,6 @@
2
2
  import './app.js';
3
3
  // @ts-expect-error: The wx build replaces this private import with the configured Page component.
4
4
  import PageComponent from '\0vpt:page-component';
5
- import { Current, createPageConfig, document, injectPageInstance } from './taro-runtime.js';
5
+ import { createPageConfig } from './taro-runtime.js';
6
6
  const config = createPageConfig(PageComponent, __VITE_PLUGIN_TARO_PAGE_PATH__, { root: { cn: [] } }, __VITE_PLUGIN_TARO_PAGE_CONFIG__);
7
7
  export default config;
8
- /** Forwards a call with the native page instance as `this`, matching Taro's invocation. */
9
- function forward(handler, receiver, args) {
10
- ;
11
- handler?.apply(receiver, args);
12
- }
13
- // DevTools re-executes the page and replays the replacement lifecycle on every edit. Taro's
14
- // onUnload unmounts the React tree and onLoad mounts a fresh one, destroying state. While
15
- // the runtime says a patch was just delivered, the pair is intercepted instead: onUnload
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
- // Ordinary navigation (no patch) passes through unchanged. The wrappers are regular
20
- // functions so the native page instance (`this`) reaches the original handlers.
21
- if (typeof __rolldown_runtime__ !== 'undefined') {
22
- const originalOnUnload = config.onUnload;
23
- const originalOnLoad = config.onLoad;
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.
27
- let captured;
28
- config.onUnload = function (...args) {
29
- if (__rolldown_runtime__.isHotReloading()) {
30
- const instance = this;
31
- captured = {
32
- $taroPath: instance.$taroPath,
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
37
- };
38
- return;
39
- }
40
- forward(originalOnUnload, this, args);
41
- };
42
- config.onLoad = function (...args) {
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);
49
- // Restore the surviving page identity onto the replacement native instance: the
50
- // re-loaded $taroPath embeds a fresh timestamp, so without this the tree and the
51
- // native side would disagree on the page key.
52
- instance.$taroPath = captured.$taroPath;
53
- instance.$taroParams = captured.$taroParams;
54
- // Rebind the retained tree to the replacement receiver and resync the native
55
- // data: updateChildNodes enqueues the full hydrated node tree (root.cn) — the
56
- // surviving tree's mutations were consumed long ago, so without it the payload
57
- // queue is empty and the new receiver renders nothing.
58
- injectPageInstance(instance, captured.$taroPath);
59
- Current.page = instance;
60
- const pageElement = document.getElementById(captured.$taroPath);
61
- pageElement.ctx = instance;
62
- pageElement.updateChildNodes();
63
- pageElement.performUpdate(true);
64
- return;
65
- }
66
- forward(originalOnLoad, this, args);
67
- };
68
- config.onShow = function (...args) {
69
- if (__rolldown_runtime__.isHotReloading()) {
70
- // The window ends at the first show; the user's onShow must not re-run on a
71
- // synthetic re-execution (it could reset state).
72
- __rolldown_runtime__.clearHotReloading();
73
- return;
74
- }
75
- forward(originalOnShow, this, args);
76
- };
77
- }