vite-plugin-taro 0.2.2 → 0.3.0

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.
Files changed (133) hide show
  1. package/README.md +83 -106
  2. package/README.zh.md +83 -104
  3. package/dist/node/build-context.d.ts +25 -0
  4. package/dist/node/build-context.js +48 -0
  5. package/dist/node/css/css-pipeline.d.ts +21 -0
  6. package/dist/node/css/css-pipeline.js +129 -0
  7. package/dist/node/plugins/conditional-directives.d.ts +3 -0
  8. package/dist/node/plugins/conditional-directives.js +62 -0
  9. package/dist/node/plugins/taro-runtime.d.ts +5 -0
  10. package/dist/node/plugins/taro-runtime.js +18 -0
  11. package/dist/node/targets/h5/plugin.d.ts +6 -0
  12. package/dist/node/targets/h5/plugin.js +117 -0
  13. package/dist/node/targets/h5/virtual-modules.d.ts +5 -0
  14. package/dist/node/targets/h5/virtual-modules.js +60 -0
  15. package/dist/node/targets/wx/companion-assets.d.ts +18 -0
  16. package/dist/node/targets/wx/companion-assets.js +121 -0
  17. package/dist/node/targets/wx/dev-server/development-session.d.ts +31 -0
  18. package/dist/node/targets/wx/dev-server/development-session.js +202 -0
  19. package/dist/node/targets/wx/dev-server/full-build-scheduler.d.ts +14 -0
  20. package/dist/node/targets/wx/dev-server/full-build-scheduler.js +60 -0
  21. package/dist/node/targets/wx/dev-server/js-utils.d.ts +5 -0
  22. package/dist/node/targets/wx/dev-server/js-utils.js +28 -0
  23. package/dist/node/targets/wx/dev-server/output.d.ts +19 -0
  24. package/dist/node/targets/wx/dev-server/output.js +44 -0
  25. package/dist/node/targets/wx/dev-server/rolldown-runtime-source.d.ts +8 -0
  26. package/dist/node/targets/wx/dev-server/rolldown-runtime-source.js +95 -0
  27. package/dist/node/targets/wx/dev-server/update-server-state.d.ts +65 -0
  28. package/dist/node/targets/wx/dev-server/update-server-state.js +116 -0
  29. package/dist/node/targets/wx/dev-server/update-transport.d.ts +28 -0
  30. package/dist/node/targets/wx/dev-server/update-transport.js +212 -0
  31. package/dist/node/targets/wx/dev-server/vite-bundled-dev-adapter.d.ts +44 -0
  32. package/dist/node/targets/wx/dev-server/vite-bundled-dev-adapter.js +145 -0
  33. package/dist/node/targets/wx/development-files.d.ts +8 -0
  34. package/dist/node/targets/wx/development-files.js +8 -0
  35. package/dist/node/targets/wx/plugin.d.ts +6 -0
  36. package/dist/node/targets/wx/plugin.js +143 -0
  37. package/dist/node/targets/wx/react-refresh.d.ts +4 -0
  38. package/dist/node/targets/wx/react-refresh.js +38 -0
  39. package/dist/node/targets/wx/virtual-modules.d.ts +16 -0
  40. package/dist/node/targets/wx/virtual-modules.js +120 -0
  41. package/dist/node/utils/async.d.ts +8 -0
  42. package/dist/node/utils/async.js +16 -0
  43. package/dist/node/utils/filesystem.d.ts +13 -0
  44. package/dist/node/utils/filesystem.js +43 -0
  45. package/dist/{vite/utils.d.ts → node/utils/modules.d.ts} +2 -2
  46. package/dist/{vite/utils.js → node/utils/modules.js} +10 -3
  47. package/dist/node/utils/packages.d.ts +2 -0
  48. package/dist/node/utils/packages.js +8 -0
  49. package/dist/node/vite-plugin.d.ts +4 -0
  50. package/dist/node/vite-plugin.js +33 -0
  51. package/dist/options.d.ts +21 -0
  52. package/dist/{shim/h5.d.ts → runtime/h5/taro-runtime.d.ts} +3 -1
  53. package/dist/runtime/h5/taro-runtime.js +9 -0
  54. package/dist/runtime/taro/api.d.ts +5 -0
  55. package/dist/{virtual → runtime/taro}/api.js +3 -0
  56. package/dist/runtime/taro/components.d.ts +2 -0
  57. package/dist/runtime/taro/components.js +2 -0
  58. package/dist/runtime/wx/page-update.d.ts +6 -0
  59. package/dist/runtime/wx/page-update.js +157 -0
  60. package/dist/{shim/wx.d.ts → runtime/wx/taro-runtime.d.ts} +1 -0
  61. package/dist/{shim/wx.js → runtime/wx/taro-runtime.js} +1 -0
  62. package/dist/runtime/wx/update-client-state.d.ts +68 -0
  63. package/dist/runtime/wx/update-client-state.js +84 -0
  64. package/dist/runtime/wx/update-client.d.ts +1 -0
  65. package/dist/runtime/wx/update-client.js +170 -0
  66. package/dist/vite.d.ts +2 -2
  67. package/dist/vite.js +1 -1
  68. package/package.json +15 -11
  69. package/src/node/build-context.ts +63 -0
  70. package/src/node/css/css-pipeline.ts +147 -0
  71. package/src/node/plugins/conditional-directives.ts +76 -0
  72. package/src/node/plugins/taro-runtime.ts +22 -0
  73. package/src/node/targets/h5/plugin.ts +135 -0
  74. package/src/node/targets/h5/virtual-modules.ts +71 -0
  75. package/src/node/targets/wx/companion-assets.ts +160 -0
  76. package/src/node/targets/wx/dev-server/development-session.ts +253 -0
  77. package/src/node/targets/wx/dev-server/full-build-scheduler.ts +62 -0
  78. package/src/node/targets/wx/dev-server/js-utils.ts +33 -0
  79. package/src/node/targets/wx/dev-server/output.ts +56 -0
  80. package/src/node/targets/wx/dev-server/rolldown-runtime-source.ts +95 -0
  81. package/src/node/targets/wx/dev-server/update-server-state.ts +183 -0
  82. package/src/node/targets/wx/dev-server/update-transport.ts +261 -0
  83. package/src/node/targets/wx/dev-server/vite-bundled-dev-adapter.ts +203 -0
  84. package/src/node/targets/wx/development-files.ts +11 -0
  85. package/src/node/targets/wx/plugin.ts +163 -0
  86. package/src/node/targets/wx/react-refresh.ts +51 -0
  87. package/src/node/targets/wx/virtual-modules.ts +152 -0
  88. package/src/node/utils/async.ts +19 -0
  89. package/src/node/utils/filesystem.ts +45 -0
  90. package/src/{vite/utils.ts → node/utils/modules.ts} +11 -3
  91. package/src/node/utils/packages.ts +10 -0
  92. package/src/node/vite-plugin.ts +41 -0
  93. package/src/options.ts +25 -0
  94. package/src/runtime/h5/taro-css.d.ts +3 -0
  95. package/src/runtime/h5/taro-runtime.ts +10 -0
  96. package/src/{virtual → runtime/taro}/api.ts +3 -0
  97. package/src/runtime/taro/components.ts +2 -0
  98. package/src/runtime/wx/page-update.ts +230 -0
  99. package/src/runtime/wx/react-refresh.d.ts +20 -0
  100. package/src/{shim/wx.ts → runtime/wx/taro-runtime.ts} +1 -0
  101. package/src/runtime/wx/update-client-state.ts +138 -0
  102. package/src/runtime/wx/update-client.ts +222 -0
  103. package/src/vite.ts +2 -2
  104. package/dist/shim/h5.js +0 -5
  105. package/dist/virtual/api.d.ts +0 -3
  106. package/dist/virtual/components.d.ts +0 -1
  107. package/dist/virtual/components.js +0 -1
  108. package/dist/vite/constants.d.ts +0 -4
  109. package/dist/vite/constants.js +0 -8
  110. package/dist/vite/plugins.d.ts +0 -8
  111. package/dist/vite/plugins.js +0 -186
  112. package/dist/vite/tailwindcss.d.ts +0 -3
  113. package/dist/vite/tailwindcss.js +0 -35
  114. package/dist/vite/targets/h5.d.ts +0 -31
  115. package/dist/vite/targets/h5.js +0 -201
  116. package/dist/vite/targets/wx.d.ts +0 -74
  117. package/dist/vite/targets/wx.js +0 -362
  118. package/dist/vite/types.d.ts +0 -38
  119. package/dist/vite/virtual-modules.d.ts +0 -3
  120. package/dist/vite/virtual-modules.js +0 -13
  121. package/dist/vite/vite-plugin-taro.d.ts +0 -7
  122. package/dist/vite/vite-plugin-taro.js +0 -80
  123. package/src/shim/h5.ts +0 -6
  124. package/src/virtual/components.ts +0 -1
  125. package/src/vite/constants.ts +0 -12
  126. package/src/vite/plugins.ts +0 -218
  127. package/src/vite/tailwindcss.ts +0 -41
  128. package/src/vite/targets/h5.ts +0 -229
  129. package/src/vite/targets/wx.ts +0 -436
  130. package/src/vite/types.ts +0 -48
  131. package/src/vite/virtual-modules.ts +0 -14
  132. package/src/vite/vite-plugin-taro.ts +0 -106
  133. /package/dist/{vite/types.js → options.js} +0 -0
@@ -0,0 +1,28 @@
1
+ import { transformWithOxc } from 'vite';
2
+ const wxJavaScriptTarget = 'es2018';
3
+ /** Lowers generated JavaScript syntax that WeChat's upload parser does not accept. */
4
+ export async function transformWxCompatibleJavaScript(code, filename) {
5
+ const result = await transformWithOxc(code, filename, {
6
+ lang: 'js',
7
+ target: wxJavaScriptTarget,
8
+ sourcemap: false,
9
+ assumptions: {
10
+ // Avoid external Oxc helpers when lowering public class fields in already-bundled chunks.
11
+ setPublicClassFields: true
12
+ }
13
+ });
14
+ return result.code;
15
+ }
16
+ /** Applies the compatibility transform to every JavaScript chunk in a DevEngine output batch. */
17
+ export async function transformWxOutputChunks(output) {
18
+ await Promise.all(output.map(async (item, index) => {
19
+ if (item.type !== 'chunk')
20
+ return;
21
+ output[index] = {
22
+ type: 'chunk',
23
+ fileName: item.fileName,
24
+ modules: item.modules,
25
+ code: await transformWxCompatibleJavaScript(item.code, item.fileName)
26
+ };
27
+ }));
28
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Normalizes private DevEngine output before the development session writes it to the fixed WX project directory.
3
+ * DevEngine rebuilds may be partial, so these helpers mutate only files present in the current output.
4
+ */
5
+ /** File shape emitted by Vite's private bundled-development engine. */
6
+ export type WxOutputFile = {
7
+ type: 'chunk';
8
+ fileName: string;
9
+ code: string;
10
+ modules?: Record<string, unknown>;
11
+ } | {
12
+ type: 'asset';
13
+ fileName: string;
14
+ source: string | Uint8Array;
15
+ };
16
+ /** Extracts Vite's development CSS payloads and materializes them as app.wxss. */
17
+ export declare function normalizeWxBundleStyles(output: WxOutputFile[]): string | undefined;
18
+ export declare function setWxAppStyles(output: WxOutputFile[], source: string): void;
19
+ export declare function isWxFullBuildOutput(output: WxOutputFile[]): boolean;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Normalizes private DevEngine output before the development session writes it to the fixed WX project directory.
3
+ * DevEngine rebuilds may be partial, so these helpers mutate only files present in the current output.
4
+ */
5
+ /** Extracts Vite's development CSS payloads and materializes them as app.wxss. */
6
+ export function normalizeWxBundleStyles(output) {
7
+ const styles = [];
8
+ for (let index = output.length - 1; index >= 0; index--) {
9
+ const item = output[index];
10
+ if (!item)
11
+ continue;
12
+ if (item.type === 'asset' && item.fileName.endsWith('.css')) {
13
+ styles.unshift(typeof item.source === 'string' ? item.source : new TextDecoder().decode(item.source));
14
+ output.splice(index, 1);
15
+ }
16
+ else if (item.type === 'chunk') {
17
+ styles.push(...collectEmbeddedStyles(item.code));
18
+ }
19
+ }
20
+ if (styles.length === 0)
21
+ return;
22
+ const source = styles.join('\n');
23
+ setWxAppStyles(output, source);
24
+ return source;
25
+ }
26
+ export function setWxAppStyles(output, source) {
27
+ const index = output.findIndex((item) => item.type === 'asset' && item.fileName === 'app.wxss');
28
+ const appStyle = { type: 'asset', fileName: 'app.wxss', source };
29
+ if (index >= 0)
30
+ output[index] = appStyle;
31
+ else
32
+ output.push(appStyle);
33
+ }
34
+ export function isWxFullBuildOutput(output) {
35
+ return output.some((item) => item.fileName === 'app.js');
36
+ }
37
+ function collectEmbeddedStyles(code) {
38
+ const styles = [];
39
+ for (const match of code.matchAll(/__vite__css(?:\$\d+)?\s*=\s*("(?:\\.|[^"\\])*");/g)) {
40
+ if (match[1])
41
+ styles.push(JSON.parse(match[1]));
42
+ }
43
+ return styles;
44
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Self-contained source injected into Rolldown's DevEngine as the WX module bootstrap runtime.
3
+ *
4
+ * This TypeScript module runs in Node and only exports source text. The exported JavaScript becomes
5
+ * `runtime.js`, executes before every application module, and therefore cannot import Taro, React,
6
+ * or any module that depends on the runtime it is creating.
7
+ */
8
+ export declare const wxRolldownRuntimeSource = "\nvar BaseDevRuntime = DevRuntime;\nclass WxHotContext {\n callbacks = [];\n data = {};\n _internal = { updateStyle() {}, removeStyle() {} };\n constructor(moduleId) { this.moduleId = moduleId; }\n accept(...args) {\n if (args.length === 0) {\n this.callbacks.push({ deps: this.moduleId, callback: undefined });\n } else if (args.length === 1 && typeof args[0] === 'function') {\n this.callbacks.push({ deps: this.moduleId, callback: args[0] });\n } else if (args.length === 1) {\n this.callbacks.push({ deps: args[0], callback: undefined });\n } else {\n this.callbacks.push({ deps: args[0], callback: args[1] });\n }\n }\n acceptExports(_exports, callback) { this.accept(callback); }\n dispose() {}\n prune() {}\n invalidate() {}\n on() {}\n off() {}\n send() {}\n}\nclass WxRolldownRuntime extends BaseDevRuntime {\n contexts = new Map();\n patchedModules = new Set();\n applyingPatch = false;\n\n createEsmInitializer = (id, initialize, _deduplicate, result) => () => {\n if (!initialize) return result;\n if (!this.applyingPatch && this.patchedModules.has(id)) {\n initialize = undefined;\n return result;\n }\n const callback = initialize;\n initialize = undefined;\n result = callback(id);\n if (this.applyingPatch) this.patchedModules.add(id);\n return result;\n };\n\n createCjsInitializer = (id, initialize, _deduplicate, module) => () => {\n if (module) return module.exports;\n if (!this.applyingPatch && this.patchedModules.has(id)) return this.loadExports(id);\n module = { exports: {} };\n initialize(module.exports, module, id);\n if (this.applyingPatch) this.patchedModules.add(id);\n return module.exports;\n };\n\n createModuleHotContext(moduleId) {\n const previous = this.contexts.get(moduleId);\n const context = new WxHotContext(moduleId);\n if (previous) {\n context.callbacks = previous.callbacks;\n context.data = previous.data;\n }\n this.contexts.set(moduleId, context);\n return context;\n }\n\n beginPatch() { this.applyingPatch = true; }\n endPatch() { this.applyingPatch = false; }\n\n applyUpdates(boundaries) {\n for (const [boundary, acceptedVia] of boundaries) {\n const context = this.contexts.get(boundary);\n if (!context) continue;\n const callbacks = [...context.callbacks];\n if (boundary === acceptedVia) context.callbacks = [];\n for (const { deps, callback } of callbacks) {\n if (!callback) continue;\n if (Array.isArray(deps)) {\n if (deps.includes(acceptedVia)) callback(deps.map((id) => this.loadExports(id)));\n } else if (deps === acceptedVia) {\n callback(this.loadExports(acceptedVia));\n }\n }\n }\n }\n}\n// A full DevEngine runtime starts a new page-update generation before any application chunk executes.\nglobalThis.__VITE_PLUGIN_TARO_WX_PAGE_UPDATE__ = { ready: false };\nglobalThis.__rolldown_runtime__ = new WxRolldownRuntime(undefined, 'vite-plugin-taro-wx');\n";
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Self-contained source injected into Rolldown's DevEngine as the WX module bootstrap runtime.
3
+ *
4
+ * This TypeScript module runs in Node and only exports source text. The exported JavaScript becomes
5
+ * `runtime.js`, executes before every application module, and therefore cannot import Taro, React,
6
+ * or any module that depends on the runtime it is creating.
7
+ */
8
+ export const wxRolldownRuntimeSource = `
9
+ var BaseDevRuntime = DevRuntime;
10
+ class WxHotContext {
11
+ callbacks = [];
12
+ data = {};
13
+ _internal = { updateStyle() {}, removeStyle() {} };
14
+ constructor(moduleId) { this.moduleId = moduleId; }
15
+ accept(...args) {
16
+ if (args.length === 0) {
17
+ this.callbacks.push({ deps: this.moduleId, callback: undefined });
18
+ } else if (args.length === 1 && typeof args[0] === 'function') {
19
+ this.callbacks.push({ deps: this.moduleId, callback: args[0] });
20
+ } else if (args.length === 1) {
21
+ this.callbacks.push({ deps: args[0], callback: undefined });
22
+ } else {
23
+ this.callbacks.push({ deps: args[0], callback: args[1] });
24
+ }
25
+ }
26
+ acceptExports(_exports, callback) { this.accept(callback); }
27
+ dispose() {}
28
+ prune() {}
29
+ invalidate() {}
30
+ on() {}
31
+ off() {}
32
+ send() {}
33
+ }
34
+ class WxRolldownRuntime extends BaseDevRuntime {
35
+ contexts = new Map();
36
+ patchedModules = new Set();
37
+ applyingPatch = false;
38
+
39
+ createEsmInitializer = (id, initialize, _deduplicate, result) => () => {
40
+ if (!initialize) return result;
41
+ if (!this.applyingPatch && this.patchedModules.has(id)) {
42
+ initialize = undefined;
43
+ return result;
44
+ }
45
+ const callback = initialize;
46
+ initialize = undefined;
47
+ result = callback(id);
48
+ if (this.applyingPatch) this.patchedModules.add(id);
49
+ return result;
50
+ };
51
+
52
+ createCjsInitializer = (id, initialize, _deduplicate, module) => () => {
53
+ if (module) return module.exports;
54
+ if (!this.applyingPatch && this.patchedModules.has(id)) return this.loadExports(id);
55
+ module = { exports: {} };
56
+ initialize(module.exports, module, id);
57
+ if (this.applyingPatch) this.patchedModules.add(id);
58
+ return module.exports;
59
+ };
60
+
61
+ createModuleHotContext(moduleId) {
62
+ const previous = this.contexts.get(moduleId);
63
+ const context = new WxHotContext(moduleId);
64
+ if (previous) {
65
+ context.callbacks = previous.callbacks;
66
+ context.data = previous.data;
67
+ }
68
+ this.contexts.set(moduleId, context);
69
+ return context;
70
+ }
71
+
72
+ beginPatch() { this.applyingPatch = true; }
73
+ endPatch() { this.applyingPatch = false; }
74
+
75
+ applyUpdates(boundaries) {
76
+ for (const [boundary, acceptedVia] of boundaries) {
77
+ const context = this.contexts.get(boundary);
78
+ if (!context) continue;
79
+ const callbacks = [...context.callbacks];
80
+ if (boundary === acceptedVia) context.callbacks = [];
81
+ for (const { deps, callback } of callbacks) {
82
+ if (!callback) continue;
83
+ if (Array.isArray(deps)) {
84
+ if (deps.includes(acceptedVia)) callback(deps.map((id) => this.loadExports(id)));
85
+ } else if (deps === acceptedVia) {
86
+ callback(this.loadExports(acceptedVia));
87
+ }
88
+ }
89
+ }
90
+ }
91
+ }
92
+ // A full DevEngine runtime starts a new page-update generation before any application chunk executes.
93
+ globalThis.__VITE_PLUGIN_TARO_WX_PAGE_UPDATE__ = { ready: false };
94
+ globalThis.__rolldown_runtime__ = new WxRolldownRuntime(undefined, 'vite-plugin-taro-wx');
95
+ `;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Pure host-side stop-and-wait protocol state machine.
3
+ *
4
+ * It retains versioned Rolldown deltas and selects one missing range for the active App Service. HTTP and filesystem
5
+ * effects are represented as commands and handled by update-transport.ts.
6
+ */
7
+ type WxUpdateDelta = {
8
+ version: number;
9
+ code: string;
10
+ };
11
+ export type WxUpdateBatch = {
12
+ buildId: string;
13
+ sessionId: string;
14
+ fromVersion: number;
15
+ targetVersion: number;
16
+ deltas: WxUpdateDelta[];
17
+ };
18
+ type WxUpdateServerState = {
19
+ buildId: string;
20
+ hostVersion: number;
21
+ retainedDeltaBytes: number;
22
+ deltas: WxUpdateDelta[];
23
+ activeSessionId?: string;
24
+ retiredSessionIds: string[];
25
+ inFlight?: Pick<WxUpdateBatch, 'sessionId' | 'fromVersion' | 'targetVersion'>;
26
+ };
27
+ type WxUpdateServerEvent = {
28
+ type: 'delta-added';
29
+ code: string;
30
+ bytes?: number;
31
+ } | {
32
+ type: 'client-registered';
33
+ buildId: string;
34
+ sessionId: string;
35
+ version: number;
36
+ } | {
37
+ type: 'client-reported';
38
+ buildId: string;
39
+ sessionId: string;
40
+ version: number;
41
+ } | {
42
+ type: 'batch-publish-failed';
43
+ sessionId: string;
44
+ targetVersion: number;
45
+ } | {
46
+ type: 'full-build-committed';
47
+ buildId: string;
48
+ };
49
+ export type WxUpdateServerCommand = {
50
+ type: 'publish-batch';
51
+ batch: WxUpdateBatch;
52
+ } | {
53
+ type: 'request-full-build';
54
+ reason: 'client-version-ahead' | 'invalid-client-version';
55
+ } | {
56
+ type: 'ignore-client';
57
+ reason: 'stale-build' | 'retired-session' | 'unknown-session';
58
+ };
59
+ type WxUpdateServerTransition = {
60
+ state: WxUpdateServerState;
61
+ commands: WxUpdateServerCommand[];
62
+ };
63
+ export declare function createWxUpdateServerState(buildId: string): WxUpdateServerState;
64
+ export declare function transitionWxUpdateServer(state: WxUpdateServerState, event: WxUpdateServerEvent): WxUpdateServerTransition;
65
+ export {};
@@ -0,0 +1,116 @@
1
+ export function createWxUpdateServerState(buildId) {
2
+ return {
3
+ buildId,
4
+ hostVersion: 0,
5
+ retainedDeltaBytes: 0,
6
+ deltas: [],
7
+ retiredSessionIds: []
8
+ };
9
+ }
10
+ export function transitionWxUpdateServer(state, event) {
11
+ switch (event.type) {
12
+ case 'delta-added': {
13
+ const version = state.hostVersion + 1;
14
+ return transition({
15
+ ...state,
16
+ hostVersion: version,
17
+ retainedDeltaBytes: state.retainedDeltaBytes + (event.bytes ?? event.code.length),
18
+ deltas: [...state.deltas, { version, code: event.code }]
19
+ });
20
+ }
21
+ case 'client-registered':
22
+ return registerClient(state, event);
23
+ case 'client-reported':
24
+ return reportClient(state, event);
25
+ case 'batch-publish-failed':
26
+ if (state.inFlight?.sessionId !== event.sessionId || state.inFlight.targetVersion !== event.targetVersion) {
27
+ return transition(state);
28
+ }
29
+ return transition({ ...state, inFlight: undefined });
30
+ case 'full-build-committed':
31
+ return transition(createWxUpdateServerState(event.buildId));
32
+ }
33
+ }
34
+ function registerClient(state, client) {
35
+ const rejected = rejectClient(state, client);
36
+ if (rejected)
37
+ return rejected;
38
+ if (state.retiredSessionIds.includes(client.sessionId)) {
39
+ return command(state, { type: 'ignore-client', reason: 'retired-session' });
40
+ }
41
+ if (state.activeSessionId === client.sessionId)
42
+ return transition(state);
43
+ const retiredSessionIds = state.activeSessionId
44
+ ? [...new Set([...state.retiredSessionIds, state.activeSessionId])]
45
+ : state.retiredSessionIds;
46
+ const registered = {
47
+ ...state,
48
+ activeSessionId: client.sessionId,
49
+ retiredSessionIds,
50
+ inFlight: undefined
51
+ };
52
+ return transition(registered);
53
+ }
54
+ function reportClient(state, client) {
55
+ const rejected = rejectClient(state, client);
56
+ if (rejected)
57
+ return rejected;
58
+ if (state.retiredSessionIds.includes(client.sessionId)) {
59
+ return command(state, { type: 'ignore-client', reason: 'retired-session' });
60
+ }
61
+ if (client.sessionId !== state.activeSessionId) {
62
+ return command(state, { type: 'ignore-client', reason: 'unknown-session' });
63
+ }
64
+ return synchronizeClient(state, client.version);
65
+ }
66
+ function rejectClient(state, client) {
67
+ if (client.buildId !== state.buildId) {
68
+ return command(state, { type: 'ignore-client', reason: 'stale-build' });
69
+ }
70
+ if (!Number.isInteger(client.version) || client.version < 0) {
71
+ return command(state, { type: 'request-full-build', reason: 'invalid-client-version' });
72
+ }
73
+ if (client.version > state.hostVersion) {
74
+ return command(state, { type: 'request-full-build', reason: 'client-version-ahead' });
75
+ }
76
+ }
77
+ function synchronizeClient(state, clientVersion) {
78
+ let synchronized = state;
79
+ const inFlight = state.inFlight;
80
+ if (inFlight) {
81
+ if (clientVersion === inFlight.fromVersion) {
82
+ const batch = createBatch(state, clientVersion, inFlight.targetVersion);
83
+ return command(state, { type: 'publish-batch', batch });
84
+ }
85
+ if (clientVersion !== inFlight.targetVersion) {
86
+ return command(state, { type: 'request-full-build', reason: 'invalid-client-version' });
87
+ }
88
+ synchronized = { ...state, inFlight: undefined };
89
+ }
90
+ if (clientVersion >= synchronized.hostVersion)
91
+ return transition(synchronized);
92
+ const batch = createBatch(synchronized, clientVersion, synchronized.hostVersion);
93
+ return command({
94
+ ...synchronized,
95
+ inFlight: {
96
+ sessionId: batch.sessionId,
97
+ fromVersion: batch.fromVersion,
98
+ targetVersion: batch.targetVersion
99
+ }
100
+ }, { type: 'publish-batch', batch });
101
+ }
102
+ function createBatch(state, fromVersion, targetVersion) {
103
+ return {
104
+ buildId: state.buildId,
105
+ sessionId: state.activeSessionId,
106
+ fromVersion,
107
+ targetVersion,
108
+ deltas: state.deltas.filter((delta) => delta.version > fromVersion && delta.version <= targetVersion)
109
+ };
110
+ }
111
+ function transition(state) {
112
+ return { state, commands: [] };
113
+ }
114
+ function command(state, value) {
115
+ return { state, commands: [value] };
116
+ }
@@ -0,0 +1,28 @@
1
+ import type { ViteDevServer } from 'vite';
2
+ export declare class WxUpdateTransport {
3
+ private readonly server;
4
+ private readonly requestFullBuild;
5
+ private readonly writeUpdateFile;
6
+ private state;
7
+ private readonly token;
8
+ private readonly pendingPolls;
9
+ private closed;
10
+ constructor(server: ViteDevServer, requestFullBuild: () => void, writeUpdateFile: (buildId: string, source: string) => Promise<void>);
11
+ get retainedDeltaCount(): number;
12
+ get retainedDeltaBytes(): number;
13
+ install(): void;
14
+ close(): void;
15
+ addDelta(code: string): void;
16
+ createBuildId(): string;
17
+ isCurrentBuild(buildId: string): boolean;
18
+ commitFullBuild(buildId: string): void;
19
+ /** Generates late-bound App metadata after Vite has selected its actual listening port. */
20
+ createControlSource(buildId?: string): string;
21
+ private readonly handleRequest;
22
+ private executePollCommands;
23
+ private holdPoll;
24
+ private respondToAll;
25
+ private finishPoll;
26
+ private apply;
27
+ private writeBatch;
28
+ }
@@ -0,0 +1,212 @@
1
+ /**
2
+ * Adapts the pure server protocol to Vite HTTP middleware and literal update-file publication.
3
+ * HTTP messages contain metadata only; executable JavaScript reaches DevTools through the injected file writer.
4
+ */
5
+ import { randomBytes } from 'node:crypto';
6
+ import { createWxUpdateServerState, transitionWxUpdateServer } from './update-server-state.js';
7
+ const endpointPath = '/__vite_plugin_taro_wx_update__';
8
+ // Keep the host response below update-client.ts's 30-second wx.request timeout.
9
+ const pollTimeout = 25_000;
10
+ export class WxUpdateTransport {
11
+ server;
12
+ requestFullBuild;
13
+ writeUpdateFile;
14
+ state = createWxUpdateServerState(createId());
15
+ token = createId();
16
+ pendingPolls = new Map();
17
+ closed = false;
18
+ constructor(server, requestFullBuild, writeUpdateFile) {
19
+ this.server = server;
20
+ this.requestFullBuild = requestFullBuild;
21
+ this.writeUpdateFile = writeUpdateFile;
22
+ }
23
+ get retainedDeltaCount() {
24
+ return this.state.hostVersion;
25
+ }
26
+ get retainedDeltaBytes() {
27
+ return this.state.retainedDeltaBytes;
28
+ }
29
+ install() {
30
+ this.server.middlewares.use(endpointPath, this.handleRequest);
31
+ }
32
+ close() {
33
+ this.closed = true;
34
+ this.respondToAll({ type: 'rebuilding' });
35
+ }
36
+ addDelta(code) {
37
+ this.apply({ type: 'delta-added', code, bytes: Buffer.byteLength(code) });
38
+ this.respondToAll({ type: 'changed' });
39
+ }
40
+ createBuildId() {
41
+ return createId();
42
+ }
43
+ isCurrentBuild(buildId) {
44
+ return this.state.buildId === buildId;
45
+ }
46
+ commitFullBuild(buildId) {
47
+ this.apply({ type: 'full-build-committed', buildId });
48
+ this.respondToAll({ type: 'rebuilding' });
49
+ }
50
+ /** Generates late-bound App metadata after Vite has selected its actual listening port. */
51
+ createControlSource(buildId = this.state.buildId) {
52
+ const address = this.server.httpServer?.address();
53
+ const port = address && typeof address !== 'string' ? address.port : this.server.config.server.port;
54
+ return `globalThis.__VITE_PLUGIN_TARO_WX_CONTROL__ = ${JSON.stringify({
55
+ url: `http://localhost:${port}${endpointPath}`,
56
+ token: this.token,
57
+ buildId
58
+ })};\n`;
59
+ }
60
+ handleRequest = async (request, response) => {
61
+ if (this.closed || request.method !== 'POST') {
62
+ respond(response, 404, { type: 'not-found' });
63
+ return;
64
+ }
65
+ let body;
66
+ try {
67
+ body = JSON.parse(await readBody(request));
68
+ }
69
+ catch {
70
+ respond(response, 400, { type: 'invalid-request' });
71
+ return;
72
+ }
73
+ if (body.token !== this.token || !isClientReport(body)) {
74
+ respond(response, 403, { type: 'forbidden' });
75
+ return;
76
+ }
77
+ if (body.action === 'rebuild') {
78
+ this.requestFullBuild();
79
+ respond(response, 202, { type: 'rebuilding' });
80
+ return;
81
+ }
82
+ if (body.action === 'register') {
83
+ const transition = this.apply({
84
+ type: 'client-registered',
85
+ buildId: body.buildId,
86
+ sessionId: body.sessionId,
87
+ version: body.version
88
+ });
89
+ if (transition.some((command) => command.type === 'request-full-build'))
90
+ this.requestFullBuild();
91
+ respond(response, 200, { type: 'registered' });
92
+ return;
93
+ }
94
+ if (body.action !== 'poll') {
95
+ respond(response, 400, { type: 'invalid-request' });
96
+ return;
97
+ }
98
+ const commands = this.apply({
99
+ type: 'client-reported',
100
+ buildId: body.buildId,
101
+ sessionId: body.sessionId,
102
+ version: body.version
103
+ });
104
+ await this.executePollCommands(body.sessionId, commands, response);
105
+ };
106
+ async executePollCommands(sessionId, commands, response) {
107
+ const publish = commands.find((command) => command.type === 'publish-batch');
108
+ if (publish?.type === 'publish-batch') {
109
+ try {
110
+ await this.writeBatch(publish.batch);
111
+ respond(response, 200, { type: 'batch-published', targetVersion: publish.batch.targetVersion });
112
+ }
113
+ catch {
114
+ this.apply({
115
+ type: 'batch-publish-failed',
116
+ sessionId: publish.batch.sessionId,
117
+ targetVersion: publish.batch.targetVersion
118
+ });
119
+ respond(response, 500, { type: 'publish-failed' });
120
+ }
121
+ return;
122
+ }
123
+ if (commands.some((command) => command.type === 'request-full-build')) {
124
+ this.requestFullBuild();
125
+ respond(response, 202, { type: 'rebuilding' });
126
+ return;
127
+ }
128
+ if (commands.some((command) => command.type === 'ignore-client')) {
129
+ respond(response, 409, { type: 'rebuilding' });
130
+ return;
131
+ }
132
+ this.holdPoll(sessionId, response);
133
+ }
134
+ holdPoll(sessionId, response) {
135
+ const previous = this.pendingPolls.get(sessionId);
136
+ if (previous)
137
+ this.finishPoll(sessionId, previous, { type: 'changed' });
138
+ let pending;
139
+ pending = {
140
+ response,
141
+ timeout: setTimeout(() => this.finishPoll(sessionId, pending, { type: 'idle' }), pollTimeout)
142
+ };
143
+ this.pendingPolls.set(sessionId, pending);
144
+ response.on('close', () => this.finishPoll(sessionId, pending));
145
+ }
146
+ respondToAll(value) {
147
+ for (const [sessionId, pending] of this.pendingPolls)
148
+ this.finishPoll(sessionId, pending, value);
149
+ }
150
+ finishPoll(sessionId, pending, value) {
151
+ if (this.pendingPolls.get(sessionId) !== pending)
152
+ return;
153
+ clearTimeout(pending.timeout);
154
+ this.pendingPolls.delete(sessionId);
155
+ if (value)
156
+ respond(pending.response, 200, value);
157
+ }
158
+ apply(event) {
159
+ const transition = transitionWxUpdateServer(this.state, event);
160
+ this.state = transition.state;
161
+ return transition.commands;
162
+ }
163
+ async writeBatch(batch) {
164
+ await this.writeUpdateFile(batch.buildId, renderBatch(batch, createId()));
165
+ }
166
+ }
167
+ /** Renders executable code for update.js; this source is never included in an HTTP response. */
168
+ function renderBatch(batch, nonce) {
169
+ return `// ${nonce}\nglobalThis.__VITE_PLUGIN_TARO_WX_UPDATE_CLIENT__.receiveBatch(${JSON.stringify({
170
+ buildId: batch.buildId,
171
+ fromVersion: batch.fromVersion,
172
+ targetVersion: batch.targetVersion
173
+ })}, () => {
174
+ ${indent(batch.deltas.map((delta) => delta.code).join('\n'), 4)}
175
+ });\n`;
176
+ }
177
+ function indent(value, spaces) {
178
+ const prefix = ' '.repeat(spaces);
179
+ return value
180
+ .split('\n')
181
+ .map((line) => `${prefix}${line}`)
182
+ .join('\n');
183
+ }
184
+ function isClientReport(value) {
185
+ return (typeof value.token === 'string' &&
186
+ typeof value.action === 'string' &&
187
+ typeof value.buildId === 'string' &&
188
+ typeof value.sessionId === 'string' &&
189
+ typeof value.version === 'number');
190
+ }
191
+ async function readBody(request) {
192
+ const chunks = [];
193
+ let size = 0;
194
+ for await (const chunk of request) {
195
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
196
+ size += buffer.length;
197
+ if (size > 64 * 1024)
198
+ throw new Error('WX update request body is too large.');
199
+ chunks.push(buffer);
200
+ }
201
+ return Buffer.concat(chunks).toString('utf8');
202
+ }
203
+ function respond(response, status, body) {
204
+ if (response.writableEnded)
205
+ return;
206
+ response.statusCode = status;
207
+ response.setHeader('content-type', 'application/json');
208
+ response.end(JSON.stringify(body));
209
+ }
210
+ function createId() {
211
+ return randomBytes(16).toString('hex');
212
+ }
@@ -0,0 +1,44 @@
1
+ import type { ResolvedConfig, ViteDevServer } from 'vite';
2
+ import type { WxOutputFile } from './output.ts';
3
+ /** HMR boundary shape returned by Vite's private WX DevEngine integration. */
4
+ type WxDevEngineBoundary = {
5
+ boundary: string;
6
+ acceptedVia: string;
7
+ };
8
+ /** Update result shape returned by Vite's private bundled-development engine. */
9
+ export type WxDevEngineUpdate = {
10
+ type: 'Noop';
11
+ } | {
12
+ type: 'FullReload';
13
+ reason?: string;
14
+ } | {
15
+ type: 'Patch';
16
+ code: string;
17
+ filename: string;
18
+ hmrBoundaries: WxDevEngineBoundary[];
19
+ };
20
+ type ViteBundledDevCallbacks = {
21
+ onOutput(output: WxOutputFile[]): void;
22
+ onPatch(files: string[], output: WxDevEngineUpdate): boolean;
23
+ onError(message: string): void;
24
+ waitForInitialBundle(): Promise<void>;
25
+ };
26
+ /** Isolates the private Vite bundled-development API used by the WX session. */
27
+ export declare class ViteBundledDevAdapter {
28
+ private readonly config;
29
+ private readonly server;
30
+ private readonly callbacks;
31
+ private bundledDev;
32
+ constructor(config: ResolvedConfig, server: ViteDevServer, callbacks: ViteBundledDevCallbacks);
33
+ install(): void;
34
+ registerBundleModules(output: WxOutputFile[]): number;
35
+ registerPatchModules(code: string): void;
36
+ private registerModules;
37
+ rebuild(): Promise<void>;
38
+ private installOptions;
39
+ private installOutput;
40
+ private installPatches;
41
+ private installListener;
42
+ private handlePayload;
43
+ }
44
+ export {};