vite-plugin-taro 0.6.14 → 0.6.16
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 +3 -0
- package/README.md +3 -0
- package/dist/node/plugins/conditional/conditional-directives.js +3 -5
- package/dist/node/plugins/h5/create-stencil-client-adapter.js +5 -4
- package/dist/node/plugins/h5/plugins.d.ts +7 -0
- package/dist/node/plugins/h5/plugins.js +20 -15
- package/dist/node/plugins/wx/native/native-component-interface.js +2 -1
- package/dist/node/plugins/wx/output/templates.d.ts +107 -0
- package/dist/node/plugins/wx/output/templates.js +12 -12
- package/dist/node/plugins/wx/render/native.js +8 -14
- package/dist/node/plugins/wx/render/system-js/string-editor.js +2 -1
- package/dist/node/plugins/wx/render/system-js/system-js.js +22 -36
- package/dist/node/plugins/wx/styles/plugins.d.ts +3 -0
- package/dist/node/plugins/wx/styles/plugins.js +20 -17
- package/dist/node/utils/oxc-transform.js +1 -2
- package/dist/node/utils/transform.js +1 -4
- package/package.json +27 -53
- package/src/node/plugins/conditional/conditional-directives.ts +4 -6
- package/src/node/plugins/h5/create-stencil-client-adapter.ts +5 -4
- package/src/node/plugins/h5/plugins.ts +21 -16
- package/src/node/plugins/wx/native/native-component-interface.ts +3 -2
- package/src/node/plugins/wx/output/templates.ts +13 -13
- package/src/node/plugins/wx/render/native.ts +8 -13
- package/src/node/plugins/wx/render/system-js/string-editor.ts +3 -2
- package/src/node/plugins/wx/render/system-js/system-js.ts +22 -32
- package/src/node/plugins/wx/styles/plugins.ts +25 -18
- package/src/node/utils/oxc-transform.ts +1 -2
- package/src/node/utils/transform.ts +1 -5
- package/src/node/babel-plugins.d.ts +0 -20
package/README.en.md
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
# VPT
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/vite-plugin-taro)
|
|
4
|
+
[](https://www.npmjs.com/package/vite-plugin-taro)
|
|
4
5
|

|
|
6
|
+
[](https://github.com/sep2/vite-plugin-taro/actions/workflows/windows.yml)
|
|
7
|
+
[](https://github.com/sep2/vite-plugin-taro/actions/workflows/coverage.yml)
|
|
5
8
|
[](LICENSE)
|
|
6
9
|
|
|
7
10
|
[简体中文](README.md) | English
|
package/README.md
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
# VPT
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/vite-plugin-taro)
|
|
4
|
+
[](https://www.npmjs.com/package/vite-plugin-taro)
|
|
4
5
|

|
|
6
|
+
[](https://github.com/sep2/vite-plugin-taro/actions/workflows/windows.yml)
|
|
7
|
+
[](https://github.com/sep2/vite-plugin-taro/actions/workflows/coverage.yml)
|
|
5
8
|
[](LICENSE)
|
|
6
9
|
|
|
7
10
|
简体中文 | [English](README.en.md)
|
|
@@ -22,7 +22,7 @@ function isConditionalDirectiveSource(id) {
|
|
|
22
22
|
}
|
|
23
23
|
/** Keeps only the source branches active for one build target. */
|
|
24
24
|
function transformConditionalDirectives(code, target) {
|
|
25
|
-
const lines = code.
|
|
25
|
+
const lines = Array.from(code.matchAll(/[^\n]*(?:\n|$)/g), (match) => match[0]);
|
|
26
26
|
const frames = [];
|
|
27
27
|
let transformed = '';
|
|
28
28
|
for (const line of lines) {
|
|
@@ -64,15 +64,13 @@ function parseDirective(line) {
|
|
|
64
64
|
if (!match) {
|
|
65
65
|
return;
|
|
66
66
|
}
|
|
67
|
+
// The regular expression constrains this capture to the complete directive union.
|
|
67
68
|
const name = match[1];
|
|
68
69
|
if (name === 'if' || name === 'elif') {
|
|
69
70
|
throw new Error(`vpt no longer supports #${name}; use #ifdef, #ifndef, or #else`);
|
|
70
71
|
}
|
|
71
|
-
if (name !== 'ifdef' && name !== 'ifndef' && name !== 'else' && name !== 'endif') {
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
72
|
return {
|
|
75
73
|
name,
|
|
76
|
-
target: match[2]
|
|
74
|
+
target: match[2].replace(/\*\/$/, '').trim()
|
|
77
75
|
};
|
|
78
76
|
}
|
|
@@ -69,7 +69,6 @@ function createStencilVisitor(editor) {
|
|
|
69
69
|
// generated-AST type safety, so a broad match could silently alter unrelated runtime
|
|
70
70
|
// behavior when Stencil changes its implementation.
|
|
71
71
|
const [style, query] = node.arguments;
|
|
72
|
-
const selector = query?.type === 'CallExpression' ? query.arguments[0] : undefined;
|
|
73
72
|
if (node.arguments.length !== 2 ||
|
|
74
73
|
style?.type !== 'Identifier' ||
|
|
75
74
|
style.name !== 'styleElm' ||
|
|
@@ -79,9 +78,11 @@ function createStencilVisitor(editor) {
|
|
|
79
78
|
query.callee.object.type !== 'Identifier' ||
|
|
80
79
|
query.callee.object.name !== 'styleContainerNode' ||
|
|
81
80
|
query.callee.property.type !== 'Identifier' ||
|
|
82
|
-
query.callee.property.name !== 'querySelector'
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
query.callee.property.name !== 'querySelector') {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const [selector] = query.arguments;
|
|
85
|
+
if (selector?.type !== 'Literal' || selector.value !== 'link') {
|
|
85
86
|
return;
|
|
86
87
|
}
|
|
87
88
|
// Replace only the second argument expression. Preserving the surrounding Stencil
|
|
@@ -12,3 +12,10 @@ export declare function createH5TargetPlugins(options: VptOptions): PluginOption
|
|
|
12
12
|
* neither branch and remain entirely outside Babel's parser and generator.
|
|
13
13
|
*/
|
|
14
14
|
export declare const h5TaroApiTransformCodeFilter: RegExp;
|
|
15
|
+
/** Babel preset body shared by Rolldown's filtered adapter and direct semantic tests. */
|
|
16
|
+
export declare function h5TaroApiPreset(): {
|
|
17
|
+
plugins: [string, {
|
|
18
|
+
packageName: string;
|
|
19
|
+
definition: any;
|
|
20
|
+
}][];
|
|
21
|
+
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
1
2
|
import babel, { defineRolldownBabelPreset } from '@rolldown/plugin-babel';
|
|
2
3
|
import { WeappTailwindcss } from 'weapp-tailwindcss/vite';
|
|
3
4
|
import { esTarget } from '../../utils/constant.js';
|
|
@@ -126,24 +127,28 @@ function createH5IndexHtmlTags() {
|
|
|
126
127
|
* neither branch and remain entirely outside Babel's parser and generator.
|
|
127
128
|
*/
|
|
128
129
|
export const h5TaroApiTransformCodeFilter = /virtual:taro\/api|\baria[A-Z]/;
|
|
130
|
+
/** Babel preset body shared by Rolldown's filtered adapter and direct semantic tests. */
|
|
131
|
+
export function h5TaroApiPreset() {
|
|
132
|
+
// Resolve Taro's private transform from the H5 package that owns it instead of promoting it into VPT's Babel graph.
|
|
133
|
+
const h5PlatformRequire = createRequire(packageRequire.resolve('@tarojs/plugin-platform-h5/package.json'));
|
|
134
|
+
const transformTaroApiPath = h5PlatformRequire.resolve('babel-plugin-transform-taroapi');
|
|
135
|
+
const definition = packageRequire(packageRequire.resolve('@tarojs/plugin-platform-h5/dist/definition.json'));
|
|
136
|
+
return {
|
|
137
|
+
plugins: [
|
|
138
|
+
[
|
|
139
|
+
transformTaroApiPath,
|
|
140
|
+
{
|
|
141
|
+
packageName: clientTaroApiId,
|
|
142
|
+
definition
|
|
143
|
+
}
|
|
144
|
+
]
|
|
145
|
+
]
|
|
146
|
+
};
|
|
147
|
+
}
|
|
129
148
|
/** Creates a filterable Babel preset containing Taro's upstream, scope-aware API transform. */
|
|
130
149
|
function createH5TaroApiPreset() {
|
|
131
|
-
const transformTaroApiPath = packageRequire.resolve('babel-plugin-transform-taroapi');
|
|
132
|
-
const definition = packageRequire(packageRequire.resolve('@tarojs/plugin-platform-h5/dist/definition.json'));
|
|
133
150
|
return defineRolldownBabelPreset({
|
|
134
|
-
preset:
|
|
135
|
-
return {
|
|
136
|
-
plugins: [
|
|
137
|
-
[
|
|
138
|
-
transformTaroApiPath,
|
|
139
|
-
{
|
|
140
|
-
packageName: clientTaroApiId,
|
|
141
|
-
definition
|
|
142
|
-
}
|
|
143
|
-
]
|
|
144
|
-
]
|
|
145
|
-
};
|
|
146
|
-
},
|
|
151
|
+
preset: h5TaroApiPreset,
|
|
147
152
|
rolldown: {
|
|
148
153
|
// @rolldown/plugin-babel can lift a preset filter into its native transform hook. The Taro plugin must be
|
|
149
154
|
// nested in this preset rather than passed through Babel's top-level `plugins`: explicit plugins may apply
|
|
@@ -141,8 +141,9 @@ function readInterfaceFields(typeArguments, declaredInterfaces, buildError) {
|
|
|
141
141
|
if (typeArguments.params.length !== 1) {
|
|
142
142
|
throw buildError('Native component interface must be one TypeScript object type');
|
|
143
143
|
}
|
|
144
|
+
// The length check above proves the one parameter exists; ESTree models the array element as optional generically.
|
|
144
145
|
const firstType = typeArguments.params[0];
|
|
145
|
-
const members =
|
|
146
|
+
const members = resolveInterfaceMembers(firstType, declaredInterfaces);
|
|
146
147
|
if (!members) {
|
|
147
148
|
throw buildError('Native component interface must be inline or declared in the same module');
|
|
148
149
|
}
|
|
@@ -14,4 +14,111 @@ export declare function createTemplateAssets({ bundle, options, subpackages, nat
|
|
|
14
14
|
nativeComponents: readonly NativeComponentRegistration[];
|
|
15
15
|
isProduction: boolean;
|
|
16
16
|
}): Rolldown.EmittedAsset[];
|
|
17
|
+
/**
|
|
18
|
+
* Adapts Taro's stock template builder to the WXML half of WX App wrapping without changing its recursive renderer.
|
|
19
|
+
*
|
|
20
|
+
* End-to-end contract
|
|
21
|
+
* -------------------
|
|
22
|
+
* React and Taro retain one in-memory ownership tree:
|
|
23
|
+
*
|
|
24
|
+
* App React root
|
|
25
|
+
* -> App host records
|
|
26
|
+
* -> vpt_page_outlet host at App {children}
|
|
27
|
+
* -> independently scheduled Taro Page roots
|
|
28
|
+
*
|
|
29
|
+
* The patched WX document makes the singleton App host a TaroRootElement. App host mutations therefore batch under app.*
|
|
30
|
+
* and fan out to every mounted native Page. Each Page root remains its own TaroRootElement and emits only page.*. The React
|
|
31
|
+
* host renderer marks the outlet and its App ancestors with an ordinary compact `vo` prop after each commit, while patched
|
|
32
|
+
* hydrate() stops at vpt_page_outlet. Page roots remain attached for React Context, lifecycle, events, removal, refs, effects,
|
|
33
|
+
* and HMR without entering app data.
|
|
34
|
+
*
|
|
35
|
+
* Build-time output
|
|
36
|
+
* -----------------
|
|
37
|
+
* createTemplateAssets asks one builder for shared base.wxml, utils.wxs, comp.wxml, custom-wrapper.wxml, and each Page WXML.
|
|
38
|
+
* This adapter specializes the products that own the native join:
|
|
39
|
+
*
|
|
40
|
+
* - shared base.wxml receives branch-local slot forwarding at comp boundaries plus vpt_fragment and vpt_page_outlet
|
|
41
|
+
* template definitions;
|
|
42
|
+
* - each Page WXML replaces Taro's root:root entry with one generic comp bound to app and one caller-owned taro_tmpl bound
|
|
43
|
+
* to page as that component's default slot.
|
|
44
|
+
*
|
|
45
|
+
* utils.wxs remains Taro's normal compact-node dispatcher. comp.wxml and custom-wrapper.wxml retain Taro's generic compact
|
|
46
|
+
* rendering contracts; their JSON companions register recursive boundaries and native components, while runtime configs
|
|
47
|
+
* retain standard event dispatch. CustomWrapper keeps Taro's Page-local ownership model and is not made slot-transparent
|
|
48
|
+
* for App wrapping.
|
|
49
|
+
* There is one shared template namespace, no Page-specific base file, no App/Page mode property, and no Page object threaded
|
|
50
|
+
* through App template data.
|
|
51
|
+
*
|
|
52
|
+
* First native Page
|
|
53
|
+
* -----------------
|
|
54
|
+
* createPageConfig starts the native Page with:
|
|
55
|
+
*
|
|
56
|
+
* app = { nn: 'vpt_fragment', cn: [] }
|
|
57
|
+
* page = { cn: [] }
|
|
58
|
+
*
|
|
59
|
+
* nn lets unchanged comp dispatch one input object even though App JSX may produce one or many root hosts. The record
|
|
60
|
+
* is WXML-only rather than a Taro host, so it needs no sid. After React commits the Page root below the outlet, the framework
|
|
61
|
+
* queues a lazy hydrate(AppRoot).cn value beside the Page root's already-pending page.* payloads. Taro drains both through the
|
|
62
|
+
* native Page's existing first setData, making App wrapping and Page content appear atomically.
|
|
63
|
+
*
|
|
64
|
+
* Native WXML execution
|
|
65
|
+
* ---------------------
|
|
66
|
+
* Data/slot ownership and named-template ownership intentionally travel through different scopes:
|
|
67
|
+
*
|
|
68
|
+
* Page WXML (owns app, page, Page eh, and Page-content light DOM)
|
|
69
|
+
* -> <comp i="{{app}}"> (crosses into a virtual custom-component scope)
|
|
70
|
+
* -> comp.wxml (owns App eh, imports unchanged utils.wxs and shared base.wxml)
|
|
71
|
+
* -> tmpl_0_vpt_fragment (iterates the real App compact roots in app.cn)
|
|
72
|
+
* -> stock Taro templates (render App hosts and recurse through their cn arrays)
|
|
73
|
+
* -> native recursion boundary <comp ...>
|
|
74
|
+
* -> forwards <slot /> only when this compact subtree root has i.vo
|
|
75
|
+
* -> tmpl_0_vpt_page_outlet at React's exact {children} position
|
|
76
|
+
* -> <slot />
|
|
77
|
+
* -> caller-owned <template is="taro_tmpl" data="{{root:page}}" />
|
|
78
|
+
* -> stock Taro templates render this native Page's page.cn records
|
|
79
|
+
*
|
|
80
|
+
* The virtual comp and both private templates add no native layout node. `vo` is part of the existing compact node i, not a
|
|
81
|
+
* component property or template context. App events execute through comp.eh; slotted Page events retain the native Page's
|
|
82
|
+
* eh. Both resolve the original Taro sid through the same event source.
|
|
83
|
+
*
|
|
84
|
+
* Named-template scope
|
|
85
|
+
* --------------------
|
|
86
|
+
* Slots transfer caller-owned light DOM only. They do not transfer the caller's named-template table or WXS modules. App
|
|
87
|
+
* dispatch runs inside comp.wxml, so vpt_fragment and vpt_page_outlet must live in base.wxml imported by that component.
|
|
88
|
+
* Putting those definitions in buildPageTemplate produces Page WXML that compiles, but component runtime dispatch fails with
|
|
89
|
+
* `Template tmpl_0_vpt_fragment not found`. Shared base.wxml makes the names visible in the root and every recursive
|
|
90
|
+
* component scope and emits them once rather than once per Page.
|
|
91
|
+
*
|
|
92
|
+
* Projection-spine ownership
|
|
93
|
+
* --------------------------
|
|
94
|
+
* React's host renderer runs after the final commit tree exists and caches the outlet-to-root Taro host ancestor array. It
|
|
95
|
+
* skips the unchanged root-side suffix, gives old leaf-side nodes the ordinary host prop vo=false, and gives new ones vo=true.
|
|
96
|
+
* Taro's existing lazy structural hydration runs later and therefore serializes those props without projection-specific
|
|
97
|
+
* scheduler or hydrate behavior. If React replaces the outlet host while moving it, the renderer finds the unique new marker
|
|
98
|
+
* once and caches its new ancestor array. At each depth-reset comp boundary, i.vo makes forwarding an O(1) local decision;
|
|
99
|
+
* WXML never searches descendants, and Page trees instantiate no unnamed slot.
|
|
100
|
+
*
|
|
101
|
+
* Steady-state updates and navigation
|
|
102
|
+
* -----------------------------------
|
|
103
|
+
* A page.* setData updates only the caller-owned Page template inside the slot. app is not passed through that template and
|
|
104
|
+
* comp.i does not change, so Page updates cannot invalidate App recursion. Ordinary app.* payloads and outlet-spine marker
|
|
105
|
+
* changes remain in Taro's granular batch before it fans out to every retained native Page. Adding or removing a React Page
|
|
106
|
+
* root mutates the outlet only in memory, and the runtime suppresses that marker's native child update. A newly pushed Page
|
|
107
|
+
* receives the latest complete App snapshot in its initial batch, while existing and hidden Pages require no navigation
|
|
108
|
+
* synchronization.
|
|
109
|
+
*
|
|
110
|
+
* Method responsibilities
|
|
111
|
+
* -----------------------
|
|
112
|
+
* 1. buildBaseTemplate preserves stock host templates, makes each depth-reset comp boundary slot-transparent through i.vo,
|
|
113
|
+
* and adds the two private definitions to the shared namespace.
|
|
114
|
+
* 2. buildPageTemplate owns only the Page boundary: app binding, page binding, and the single Page-content slot.
|
|
115
|
+
* 3. buildXScript delegates unchanged because routing metadata already travels inside compact node i.
|
|
116
|
+
* 4. buildBaseComponentTemplate delegates unchanged so recursive comp remains generic and feature-independent.
|
|
117
|
+
* 5. buildCustomComponentTemplate delegates unchanged so CustomWrapper renders compact children in its own native scope.
|
|
118
|
+
*
|
|
119
|
+
* H5 never calls this WX output builder. Its App continues to receive ordinary Fragment children and none of these native
|
|
120
|
+
* data roots, templates, custom-component boundaries, or slot rules enter the browser build.
|
|
121
|
+
*/
|
|
122
|
+
/** Replaces one pinned Taro fragment and rejects absent or duplicated upstream contracts. */
|
|
123
|
+
export declare function replaceExactlyOnce(source: string, current: string, replacement: string, description: string): string;
|
|
17
124
|
export {};
|
|
@@ -142,28 +142,28 @@ export function createTemplateAssets({ bundle, options, subpackages, nativeCompo
|
|
|
142
142
|
* H5 never calls this WX output builder. Its App continues to receive ordinary Fragment children and none of these native
|
|
143
143
|
* data roots, templates, custom-component boundaries, or slot rules enter the browser build.
|
|
144
144
|
*/
|
|
145
|
+
/** Replaces one pinned Taro fragment and rejects absent or duplicated upstream contracts. */
|
|
146
|
+
export function replaceExactlyOnce(source, current, replacement, description) {
|
|
147
|
+
const firstIndex = source.indexOf(current);
|
|
148
|
+
const duplicateIndex = firstIndex === -1 ? -1 : source.indexOf(current, firstIndex + current.length);
|
|
149
|
+
if (firstIndex === -1 || duplicateIndex !== -1) {
|
|
150
|
+
throw new Error(`Expected one ${description}, found ${firstIndex === -1 ? 0 : 'multiple'}`);
|
|
151
|
+
}
|
|
152
|
+
return `${source.slice(0, firstIndex)}${replacement}${source.slice(firstIndex + current.length)}`;
|
|
153
|
+
}
|
|
145
154
|
function createTemplateBuilder() {
|
|
146
155
|
const platform = new WxPlatform({
|
|
147
156
|
helper: {
|
|
148
157
|
recursiveMerge
|
|
149
158
|
},
|
|
159
|
+
// c8 cannot observe callbacks Taro accepts but intentionally never invokes.
|
|
160
|
+
/* c8 ignore next */
|
|
150
161
|
modifyWebpackChain() { },
|
|
162
|
+
/* c8 ignore next */
|
|
151
163
|
registerPlatform() { }
|
|
152
164
|
}, {}, {});
|
|
153
165
|
platform.modifyTemplate({});
|
|
154
166
|
const taroTemplateBuilder = platform.template;
|
|
155
|
-
/**
|
|
156
|
-
* Replaces one pinned Taro fragment so an upstream template change cannot silently break the coordinated
|
|
157
|
-
* framework/runtime/WXML boundary or partially apply the feature.
|
|
158
|
-
*/
|
|
159
|
-
function replaceExactlyOnce(source, current, replacement, description) {
|
|
160
|
-
const firstIndex = source.indexOf(current);
|
|
161
|
-
const duplicateIndex = firstIndex === -1 ? -1 : source.indexOf(current, firstIndex + current.length);
|
|
162
|
-
if (firstIndex === -1 || duplicateIndex !== -1) {
|
|
163
|
-
throw new Error(`Expected one ${description}, found ${firstIndex === -1 ? 0 : 'multiple'}`);
|
|
164
|
-
}
|
|
165
|
-
return `${source.slice(0, firstIndex)}${replacement}${source.slice(firstIndex + current.length)}`;
|
|
166
|
-
}
|
|
167
167
|
return {
|
|
168
168
|
buildBaseTemplate: (componentConfig) => {
|
|
169
169
|
const source = taroTemplateBuilder.buildTemplate(componentConfig);
|
|
@@ -395,7 +395,7 @@ function importedReferenceContext(node, parent, ancestors) {
|
|
|
395
395
|
while (semanticParent?.type === 'ParenthesizedExpression' && semanticParent.expression === expression) {
|
|
396
396
|
expression = semanticParent;
|
|
397
397
|
ancestorIndex -= 1;
|
|
398
|
-
semanticParent = ancestors[ancestorIndex]
|
|
398
|
+
semanticParent = ancestors[ancestorIndex];
|
|
399
399
|
}
|
|
400
400
|
if (semanticParent?.type === 'CallExpression' && semanticParent.callee === expression)
|
|
401
401
|
return 'unbound';
|
|
@@ -492,18 +492,13 @@ function takeGeneratedName(base, used) {
|
|
|
492
492
|
function moduleExportName(name) {
|
|
493
493
|
return name.type === 'Literal' ? String(name.value) : name.name;
|
|
494
494
|
}
|
|
495
|
-
function declarationBindingNames(declaration,
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
return declaration.declarations.flatMap(({ id }) => bindingNames(id));
|
|
499
|
-
case 'FunctionDeclaration':
|
|
500
|
-
case 'ClassDeclaration':
|
|
501
|
-
if (declaration.id)
|
|
502
|
-
return [declaration.id.name];
|
|
503
|
-
throw unsupported(filename, 'anonymous declaration export');
|
|
504
|
-
default:
|
|
505
|
-
throw unsupported(filename, `declaration export ${declaration.type}`);
|
|
495
|
+
function declarationBindingNames(declaration, _filename) {
|
|
496
|
+
if (declaration.type === 'VariableDeclaration') {
|
|
497
|
+
return declaration.declarations.flatMap(({ id }) => bindingNames(id));
|
|
506
498
|
}
|
|
499
|
+
// Final JavaScript named-declaration exports can otherwise contain only named functions or classes.
|
|
500
|
+
const namedDeclaration = declaration;
|
|
501
|
+
return [namedDeclaration.id.name];
|
|
507
502
|
}
|
|
508
503
|
function bindingNames(pattern) {
|
|
509
504
|
switch (pattern.type) {
|
|
@@ -543,7 +538,6 @@ function createSourceMap(editor, filename) {
|
|
|
543
538
|
sources: generated.sources,
|
|
544
539
|
sourcesContent: generated.sourcesContent,
|
|
545
540
|
names: generated.names,
|
|
546
|
-
mappings: generated.mappings
|
|
547
|
-
...(generated.x_google_ignoreList ? { x_google_ignoreList: generated.x_google_ignoreList } : {})
|
|
541
|
+
mappings: generated.mappings
|
|
548
542
|
};
|
|
549
543
|
}
|
|
@@ -87,7 +87,8 @@ export class StringEditor {
|
|
|
87
87
|
return output;
|
|
88
88
|
}
|
|
89
89
|
#renderInsertions(position) {
|
|
90
|
+
// Callers enumerate this map's keys, so the matching insertion journal is guaranteed to exist.
|
|
90
91
|
const insertions = this.#insertions.get(position);
|
|
91
|
-
return
|
|
92
|
+
return `${insertions.prepend.join('')}${insertions.append.join('')}`;
|
|
92
93
|
}
|
|
93
94
|
}
|
|
@@ -91,18 +91,19 @@ function analyzeModule(program, filename) {
|
|
|
91
91
|
outerBindings.add(name);
|
|
92
92
|
});
|
|
93
93
|
break;
|
|
94
|
-
case 'FunctionDeclaration':
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
94
|
+
case 'FunctionDeclaration': {
|
|
95
|
+
// Anonymous and ambient functions cannot occur as direct declarations in final JavaScript chunks.
|
|
96
|
+
const identifier = node.id;
|
|
98
97
|
functions.push(node);
|
|
99
|
-
outerBindings.add(
|
|
98
|
+
outerBindings.add(identifier.name);
|
|
100
99
|
break;
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
100
|
+
}
|
|
101
|
+
case 'ClassDeclaration': {
|
|
102
|
+
// Anonymous and ambient classes can occur only in source forms rejected before final chunk rendering.
|
|
103
|
+
const identifier = node.id;
|
|
104
|
+
outerBindings.add(identifier.name);
|
|
105
105
|
break;
|
|
106
|
+
}
|
|
106
107
|
}
|
|
107
108
|
}
|
|
108
109
|
requireNoDirectEval(hasDirectEval, filename);
|
|
@@ -143,8 +144,6 @@ function collectImport(declaration, dependencyBySource, importBindings, outerBin
|
|
|
143
144
|
dependency.imports.push({ imported: null, local: specifier.local.name });
|
|
144
145
|
break;
|
|
145
146
|
case 'ImportSpecifier':
|
|
146
|
-
if (specifier.importKind === 'type')
|
|
147
|
-
throw unsupported(filename, 'type-only import specifier');
|
|
148
147
|
dependency.imports.push({ imported: moduleExportName(specifier.imported), local: specifier.local.name });
|
|
149
148
|
break;
|
|
150
149
|
}
|
|
@@ -159,8 +158,6 @@ function collectExports(declaration, exportNamesByLocal, filename) {
|
|
|
159
158
|
throw unsupported(filename, 'declaration exports, re-exports, export attributes, or type-only exports');
|
|
160
159
|
}
|
|
161
160
|
for (const specifier of declaration.specifiers) {
|
|
162
|
-
if (specifier.exportKind === 'type')
|
|
163
|
-
throw unsupported(filename, 'type-only export specifier');
|
|
164
161
|
const local = moduleExportName(specifier.local);
|
|
165
162
|
const exported = moduleExportName(specifier.exported);
|
|
166
163
|
// Aliases are accumulated in declaration order because nested live-binding calls must publish in the same order as Babel.
|
|
@@ -205,11 +202,8 @@ function applyProgramEdits(editor, model) {
|
|
|
205
202
|
}
|
|
206
203
|
/** Changes module variables into assignments to declaration-scope cells shared with setters and hoisted functions. */
|
|
207
204
|
function transformTopLevelVariables(editor, declaration, exportBinding, exportNamesByLocal) {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
editor.remove(declaration.start, declaration.end);
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
205
|
+
// ESTree variable declarations always contain at least one declarator.
|
|
206
|
+
const first = declaration.declarations[0];
|
|
213
207
|
// `const value = init` becomes `(value = init)`: the declaration cell itself is emitted once in the registration scope.
|
|
214
208
|
editor.overwrite(declaration.start, first.start, '(');
|
|
215
209
|
editor.appendLeft(statementTerminatorStart(editor.original, declaration.end), ')');
|
|
@@ -217,11 +211,8 @@ function transformTopLevelVariables(editor, declaration, exportBinding, exportNa
|
|
|
217
211
|
}
|
|
218
212
|
/** Hoists module-scoped var declarations nested in statements without changing their control-flow position. */
|
|
219
213
|
function transformNestedHoistedVariables(editor, declaration, isForIterationBinding, exportBinding, exportNamesByLocal) {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
editor.remove(declaration.start, declaration.end);
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
214
|
+
// ESTree variable declarations always contain at least one declarator.
|
|
215
|
+
const first = declaration.declarations[0];
|
|
225
216
|
editor.overwrite(declaration.start, first.start, isForIterationBinding ? '' : '(');
|
|
226
217
|
if (!isForIterationBinding)
|
|
227
218
|
editor.appendLeft(statementTerminatorStart(editor.original, declaration.end), ')');
|
|
@@ -237,7 +228,8 @@ function transformVariableInitializers(editor, declaration, exportBinding, expor
|
|
|
237
228
|
continue;
|
|
238
229
|
if (declarator.id.type === 'Identifier') {
|
|
239
230
|
// Nested calls publish every alias while preserving the initializer's completion value.
|
|
240
|
-
|
|
231
|
+
// exportedBindings above proves this identifier owns at least one public alias.
|
|
232
|
+
const names = exportNamesByLocal.get(declarator.id.name);
|
|
241
233
|
editor.prependLeft(declarator.start, exportExpressionPrefix(exportBinding, names, ''));
|
|
242
234
|
editor.appendRight(declarator.end, exportExpressionSuffix(names));
|
|
243
235
|
continue;
|
|
@@ -248,10 +240,10 @@ function transformVariableInitializers(editor, declaration, exportBinding, expor
|
|
|
248
240
|
}
|
|
249
241
|
/** Turns a class declaration into the execute-time assignment required by cyclic SystemJS linking. */
|
|
250
242
|
function transformTopLevelClass(editor, declaration, exportBinding, exportNamesByLocal) {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
const names = exportNamesByLocal.get(
|
|
254
|
-
editor.prependLeft(declaration.start, exportExpressionPrefix(exportBinding, names, `${
|
|
243
|
+
// Anonymous classes can occur only in default exports, which analysis rejects before this pass.
|
|
244
|
+
const identifier = declaration.id;
|
|
245
|
+
const names = exportNamesByLocal.get(identifier.name) ?? [];
|
|
246
|
+
editor.prependLeft(declaration.start, exportExpressionPrefix(exportBinding, names, `${identifier.name}=`));
|
|
255
247
|
editor.appendRight(declaration.end, exportExpressionSuffix(names));
|
|
256
248
|
}
|
|
257
249
|
/** Rewrites expression-level module semantics in one scope-aware O(n) traversal. */
|
|
@@ -383,7 +375,7 @@ function createRegistrationShell(model, options) {
|
|
|
383
375
|
}
|
|
384
376
|
/** Produces declaration-time exports for functions and uninitialized variables, matching cyclic ESM availability. */
|
|
385
377
|
function renderEarlyExports(model) {
|
|
386
|
-
const functionNames = new Set(model.functions.
|
|
378
|
+
const functionNames = new Set(model.functions.map((declaration) => declaration.id.name));
|
|
387
379
|
const directVariables = model.program.body.flatMap((node) => (node.type === 'VariableDeclaration' ? [node] : []));
|
|
388
380
|
const nestedHoistedVariables = model.hoistedVariables
|
|
389
381
|
.map(({ declaration }) => declaration)
|
|
@@ -541,11 +533,6 @@ function patternNames(pattern) {
|
|
|
541
533
|
return pattern.properties.flatMap((property) => property.type === 'Property' ? patternNames(property.value) : patternNames(property.argument));
|
|
542
534
|
case 'RestElement':
|
|
543
535
|
return patternNames(pattern.argument);
|
|
544
|
-
case 'TSAsExpression':
|
|
545
|
-
case 'TSSatisfiesExpression':
|
|
546
|
-
case 'TSNonNullExpression':
|
|
547
|
-
case 'TSTypeAssertion':
|
|
548
|
-
return patternNames(pattern.expression);
|
|
549
536
|
default:
|
|
550
537
|
return [];
|
|
551
538
|
}
|
|
@@ -596,7 +583,6 @@ function createSourceMap(editor, filename) {
|
|
|
596
583
|
sources: generated.sources,
|
|
597
584
|
sourcesContent: generated.sourcesContent,
|
|
598
585
|
names: generated.names,
|
|
599
|
-
mappings: generated.mappings
|
|
600
|
-
...(generated.x_google_ignoreList ? { x_google_ignoreList: generated.x_google_ignoreList } : {})
|
|
586
|
+
mappings: generated.mappings
|
|
601
587
|
};
|
|
602
588
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type Plugin } from 'vite';
|
|
2
2
|
import { createContext } from 'weapp-tailwindcss/core';
|
|
3
|
+
import { type WeappTailwindcssGenerator } from 'weapp-tailwindcss/generator';
|
|
3
4
|
/** JavaScript code plus the physical filename required by the Weapp JavaScript transformer. */
|
|
4
5
|
type JavaScriptArtifact = Readonly<{
|
|
5
6
|
code: string;
|
|
@@ -138,4 +139,6 @@ export declare function finalizeOutput(entryIds: readonly string[], styleByModul
|
|
|
138
139
|
javaScript: string[];
|
|
139
140
|
wxss: string;
|
|
140
141
|
}>;
|
|
142
|
+
/** Generates one Tailwind root and disposes only a new compiler that fails before acquiring a retained owner. */
|
|
143
|
+
export declare function generateTailwindRoot(generator: Pick<WeappTailwindcssGenerator, 'dispose' | 'generate'>, reused: boolean): Promise<import("weapp-tailwindcss/generator").WeappTailwindcssGenerateResult>;
|
|
141
144
|
export {};
|
|
@@ -376,26 +376,29 @@ async function compileTailwindRoot(projectRoot, rootId, css, previous) {
|
|
|
376
376
|
cwd: tailwindcssBasedir,
|
|
377
377
|
cssSources: [{ css: css, base: path.dirname(rootId), file: rootId }]
|
|
378
378
|
}));
|
|
379
|
+
// Step 3: authoritative source scanning updates additions and removals in the persistent incremental candidate cache.
|
|
380
|
+
const generated = await generateTailwindRoot(generator, reuse);
|
|
381
|
+
// Step 4: return a complete immutable replacement record; the caller commits it only after this function succeeds.
|
|
382
|
+
return {
|
|
383
|
+
css: generated.css,
|
|
384
|
+
root: {
|
|
385
|
+
source: css,
|
|
386
|
+
classSet: generated.classSet,
|
|
387
|
+
dependencies: new Set(generated.dependencies.map(normalizeModuleId)),
|
|
388
|
+
generator: generator,
|
|
389
|
+
// Source patterns change with compiler inputs, while candidate-only updates can reuse their native matcher.
|
|
390
|
+
scanner: reuse ? previous.scanner : new Scanner({ sources: generated.sources }),
|
|
391
|
+
invalidated: false
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
/** Generates one Tailwind root and disposes only a new compiler that fails before acquiring a retained owner. */
|
|
396
|
+
export async function generateTailwindRoot(generator, reused) {
|
|
379
397
|
try {
|
|
380
|
-
|
|
381
|
-
const generated = await generator.generate({ target: 'web', scanSources: true, incrementalCache: true });
|
|
382
|
-
// Step 4: return a complete immutable replacement record; the caller commits it only after this function succeeds.
|
|
383
|
-
return {
|
|
384
|
-
css: generated.css,
|
|
385
|
-
root: {
|
|
386
|
-
source: css,
|
|
387
|
-
classSet: generated.classSet,
|
|
388
|
-
dependencies: new Set(generated.dependencies.map(normalizeModuleId)),
|
|
389
|
-
generator: generator,
|
|
390
|
-
// Source patterns change with compiler inputs, while candidate-only updates can reuse their native matcher.
|
|
391
|
-
scanner: reuse ? previous.scanner : new Scanner({ sources: generated.sources }),
|
|
392
|
-
invalidated: false
|
|
393
|
-
}
|
|
394
|
-
};
|
|
398
|
+
return await generator.generate({ target: 'web', scanSources: true, incrementalCache: true });
|
|
395
399
|
}
|
|
396
400
|
catch (error) {
|
|
397
|
-
|
|
398
|
-
if (!reuse) {
|
|
401
|
+
if (!reused) {
|
|
399
402
|
generator.dispose?.();
|
|
400
403
|
}
|
|
401
404
|
throw error;
|
|
@@ -50,8 +50,7 @@ export function transformWithOxcWalker({ code, filename, sourcemap, createVisito
|
|
|
50
50
|
sources: generatedMap.sources,
|
|
51
51
|
sourcesContent: generatedMap.sourcesContent,
|
|
52
52
|
names: generatedMap.names,
|
|
53
|
-
mappings: generatedMap.mappings
|
|
54
|
-
...(generatedMap.x_google_ignoreList ? { x_google_ignoreList: generatedMap.x_google_ignoreList } : {})
|
|
53
|
+
mappings: generatedMap.mappings
|
|
55
54
|
}
|
|
56
55
|
: null
|
|
57
56
|
};
|
|
@@ -14,12 +14,9 @@ export async function replaceWithAst(code, filename, replacement, sourcemap = tr
|
|
|
14
14
|
throw new Error(`Failed to replace placeholder ${placeholder} in ${filename}`);
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
|
-
if (sourcemap && !transformed.map) {
|
|
18
|
-
throw new Error(`Failed to generate a source map for ${filename}`);
|
|
19
|
-
}
|
|
20
17
|
return {
|
|
21
18
|
code: transformed.code,
|
|
22
|
-
map: sourcemap ?
|
|
19
|
+
map: sourcemap ? transformed.map : null
|
|
23
20
|
};
|
|
24
21
|
}
|
|
25
22
|
/** Serializes a Babel AST node as a compact expression for Oxc substitution. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vite-plugin-taro",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.16",
|
|
4
4
|
"author": "sep2",
|
|
5
5
|
"description": "Vite 8 plugin for building one React/Taro codebase for WeChat Mini Program and H5 targets.",
|
|
6
6
|
"type": "module",
|
|
@@ -51,75 +51,49 @@
|
|
|
51
51
|
"node": "^22.18.0 || >=24.11.0"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@babel/core": "^
|
|
55
|
-
"@babel/generator": "^
|
|
56
|
-
"@babel/types": "^
|
|
57
|
-
"@oxc-project/types": "0.
|
|
54
|
+
"@babel/core": "^8.0.1",
|
|
55
|
+
"@babel/generator": "^8.0.0",
|
|
56
|
+
"@babel/types": "^8.0.4",
|
|
57
|
+
"@oxc-project/types": "0.147.0",
|
|
58
58
|
"@rolldown/plugin-babel": "^0.2.3",
|
|
59
59
|
"@tailwindcss-mangle/engine": "0.2.0",
|
|
60
60
|
"@tailwindcss/oxide": "4.3.3",
|
|
61
|
-
"@tarojs/components": "4.2.
|
|
62
|
-
"@tarojs/helper": "4.2.
|
|
63
|
-
"@tarojs/plugin-
|
|
64
|
-
"@tarojs/plugin-platform-
|
|
65
|
-
"@tarojs/
|
|
66
|
-
"@tarojs/
|
|
67
|
-
"@
|
|
68
|
-
"@
|
|
69
|
-
"
|
|
61
|
+
"@tarojs/components": "4.2.1",
|
|
62
|
+
"@tarojs/helper": "4.2.1",
|
|
63
|
+
"@tarojs/plugin-framework-react": "npm:vite-plugin-taro-plugin-framework-react@0.6.16",
|
|
64
|
+
"@tarojs/plugin-platform-h5": "4.2.1",
|
|
65
|
+
"@tarojs/plugin-platform-weapp": "4.2.1",
|
|
66
|
+
"@tarojs/react": "npm:vite-plugin-taro-react@0.6.16",
|
|
67
|
+
"@tarojs/router": "4.2.1",
|
|
68
|
+
"@tarojs/runtime": "npm:vite-plugin-taro-runtime@0.6.16",
|
|
69
|
+
"@tarojs/taro": "4.2.1",
|
|
70
|
+
"@vitejs/plugin-react": "^6.1.0",
|
|
71
|
+
"@weapp-tailwindcss/postcss": "3.2.11",
|
|
70
72
|
"oxc-walker": "1.1.1",
|
|
71
73
|
"picocolors": "^1.1.1",
|
|
72
74
|
"react-reconciler": "0.33.0",
|
|
73
75
|
"react-refresh": "^0.18.0",
|
|
74
|
-
"rolldown": "1.2.
|
|
76
|
+
"rolldown": "1.2.5",
|
|
75
77
|
"rxjs": "^7.8.2",
|
|
76
78
|
"tailwindcss": "^4.3.3",
|
|
77
|
-
"weapp-tailwindcss": "^5.
|
|
78
|
-
"@tarojs/plugin-framework-react": "npm:vite-plugin-taro-plugin-framework-react@0.6.14",
|
|
79
|
-
"@tarojs/runtime": "npm:vite-plugin-taro-runtime@0.6.14",
|
|
80
|
-
"@tarojs/react": "npm:vite-plugin-taro-react@0.6.14"
|
|
79
|
+
"weapp-tailwindcss": "^5.3.5"
|
|
81
80
|
},
|
|
82
81
|
"peerDependencies": {
|
|
83
82
|
"react": "^19.0.0",
|
|
84
83
|
"react-dom": "^19.0.0",
|
|
85
|
-
"vite": "8.2.
|
|
86
|
-
},
|
|
87
|
-
"c8": {
|
|
88
|
-
"all": true,
|
|
89
|
-
"include": [
|
|
90
|
-
"src/**/*.{ts,js}"
|
|
91
|
-
],
|
|
92
|
-
"exclude": [
|
|
93
|
-
"src/**/*.test.ts",
|
|
94
|
-
"src/**/*.d.ts",
|
|
95
|
-
"src/options.ts"
|
|
96
|
-
],
|
|
97
|
-
"reporter": [
|
|
98
|
-
"text",
|
|
99
|
-
"html",
|
|
100
|
-
"lcov",
|
|
101
|
-
"json-summary"
|
|
102
|
-
],
|
|
103
|
-
"reports-dir": "coverage",
|
|
104
|
-
"check-coverage": true,
|
|
105
|
-
"statements": 90,
|
|
106
|
-
"branches": 85,
|
|
107
|
-
"functions": 90,
|
|
108
|
-
"lines": 90
|
|
84
|
+
"vite": "8.2.2"
|
|
109
85
|
},
|
|
110
86
|
"devDependencies": {
|
|
111
|
-
"@babel/plugin-transform-dynamic-import": "^
|
|
112
|
-
"@babel/plugin-transform-modules-commonjs": "^
|
|
113
|
-
"@babel/plugin-transform-modules-systemjs": "^
|
|
114
|
-
"@types/
|
|
115
|
-
"@types/
|
|
116
|
-
"@types/
|
|
117
|
-
"@types/react": "^19.2.17",
|
|
118
|
-
"@types/react-dom": "^19.2.3",
|
|
87
|
+
"@babel/plugin-transform-dynamic-import": "^8.0.1",
|
|
88
|
+
"@babel/plugin-transform-modules-commonjs": "^8.0.1",
|
|
89
|
+
"@babel/plugin-transform-modules-systemjs": "^8.0.1",
|
|
90
|
+
"@types/node": "^26.3.0",
|
|
91
|
+
"@types/react": "^19.2.18",
|
|
92
|
+
"@types/react-dom": "^19.2.5",
|
|
119
93
|
"@types/react-refresh": "^0.14.7",
|
|
120
|
-
"c8": "^
|
|
94
|
+
"c8": "^12.0.0",
|
|
121
95
|
"typescript": "^7.0.2",
|
|
122
|
-
"vite": "8.2.
|
|
96
|
+
"vite": "8.2.2"
|
|
123
97
|
},
|
|
124
98
|
"scripts": {
|
|
125
99
|
"build": "node ../../scripts/sync-plugin-readme.ts && node ../../scripts/clean-directory.ts dist && tsc --project tsconfig.build.json",
|
|
@@ -38,7 +38,7 @@ type DirectiveFrame = {
|
|
|
38
38
|
|
|
39
39
|
/** Keeps only the source branches active for one build target. */
|
|
40
40
|
function transformConditionalDirectives(code: string, target: VptTarget): string {
|
|
41
|
-
const lines = code.
|
|
41
|
+
const lines = Array.from(code.matchAll(/[^\n]*(?:\n|$)/g), (match) => match[0])
|
|
42
42
|
const frames: DirectiveFrame[] = []
|
|
43
43
|
let transformed = ''
|
|
44
44
|
|
|
@@ -82,15 +82,13 @@ function parseDirective(line: string): Directive | undefined {
|
|
|
82
82
|
if (!match) {
|
|
83
83
|
return
|
|
84
84
|
}
|
|
85
|
-
|
|
85
|
+
// The regular expression constrains this capture to the complete directive union.
|
|
86
|
+
const name = match[1] as Directive['name'] | 'if' | 'elif'
|
|
86
87
|
if (name === 'if' || name === 'elif') {
|
|
87
88
|
throw new Error(`vpt no longer supports #${name}; use #ifdef, #ifndef, or #else`)
|
|
88
89
|
}
|
|
89
|
-
if (name !== 'ifdef' && name !== 'ifndef' && name !== 'else' && name !== 'endif') {
|
|
90
|
-
return
|
|
91
|
-
}
|
|
92
90
|
return {
|
|
93
91
|
name,
|
|
94
|
-
target: match[2]
|
|
92
|
+
target: match[2].replace(/\*\/$/, '').trim()
|
|
95
93
|
}
|
|
96
94
|
}
|
|
@@ -79,7 +79,6 @@ function createStencilVisitor(editor: RolldownMagicString): WalkerEnter {
|
|
|
79
79
|
// generated-AST type safety, so a broad match could silently alter unrelated runtime
|
|
80
80
|
// behavior when Stencil changes its implementation.
|
|
81
81
|
const [style, query] = node.arguments
|
|
82
|
-
const selector = query?.type === 'CallExpression' ? query.arguments[0] : undefined
|
|
83
82
|
if (
|
|
84
83
|
node.arguments.length !== 2 ||
|
|
85
84
|
style?.type !== 'Identifier' ||
|
|
@@ -90,12 +89,14 @@ function createStencilVisitor(editor: RolldownMagicString): WalkerEnter {
|
|
|
90
89
|
query.callee.object.type !== 'Identifier' ||
|
|
91
90
|
query.callee.object.name !== 'styleContainerNode' ||
|
|
92
91
|
query.callee.property.type !== 'Identifier' ||
|
|
93
|
-
query.callee.property.name !== 'querySelector'
|
|
94
|
-
selector?.type !== 'Literal' ||
|
|
95
|
-
selector.value !== 'link'
|
|
92
|
+
query.callee.property.name !== 'querySelector'
|
|
96
93
|
) {
|
|
97
94
|
return
|
|
98
95
|
}
|
|
96
|
+
const [selector] = query.arguments
|
|
97
|
+
if (selector?.type !== 'Literal' || selector.value !== 'link') {
|
|
98
|
+
return
|
|
99
|
+
}
|
|
99
100
|
|
|
100
101
|
// Replace only the second argument expression. Preserving the surrounding Stencil
|
|
101
102
|
// source avoids Babel-style whole-file regeneration and keeps its formatting, comments,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createRequire } from 'node:module'
|
|
1
2
|
import babel, { defineRolldownBabelPreset } from '@rolldown/plugin-babel'
|
|
2
3
|
import type { HtmlTagDescriptor, Plugin, PluginOption } from 'vite'
|
|
3
4
|
import { WeappTailwindcss } from 'weapp-tailwindcss/vite'
|
|
@@ -138,25 +139,29 @@ function createH5IndexHtmlTags(): HtmlTagDescriptor[] {
|
|
|
138
139
|
*/
|
|
139
140
|
export const h5TaroApiTransformCodeFilter = /virtual:taro\/api|\baria[A-Z]/
|
|
140
141
|
|
|
141
|
-
/**
|
|
142
|
-
function
|
|
143
|
-
|
|
142
|
+
/** Babel preset body shared by Rolldown's filtered adapter and direct semantic tests. */
|
|
143
|
+
export function h5TaroApiPreset() {
|
|
144
|
+
// Resolve Taro's private transform from the H5 package that owns it instead of promoting it into VPT's Babel graph.
|
|
145
|
+
const h5PlatformRequire = createRequire(packageRequire.resolve('@tarojs/plugin-platform-h5/package.json'))
|
|
146
|
+
const transformTaroApiPath = h5PlatformRequire.resolve('babel-plugin-transform-taroapi')
|
|
144
147
|
const definition = packageRequire(packageRequire.resolve('@tarojs/plugin-platform-h5/dist/definition.json'))
|
|
148
|
+
return {
|
|
149
|
+
plugins: [
|
|
150
|
+
[
|
|
151
|
+
transformTaroApiPath,
|
|
152
|
+
{
|
|
153
|
+
packageName: clientTaroApiId,
|
|
154
|
+
definition
|
|
155
|
+
}
|
|
156
|
+
] satisfies [string, object]
|
|
157
|
+
]
|
|
158
|
+
}
|
|
159
|
+
}
|
|
145
160
|
|
|
161
|
+
/** Creates a filterable Babel preset containing Taro's upstream, scope-aware API transform. */
|
|
162
|
+
function createH5TaroApiPreset() {
|
|
146
163
|
return defineRolldownBabelPreset({
|
|
147
|
-
preset:
|
|
148
|
-
return {
|
|
149
|
-
plugins: [
|
|
150
|
-
[
|
|
151
|
-
transformTaroApiPath,
|
|
152
|
-
{
|
|
153
|
-
packageName: clientTaroApiId,
|
|
154
|
-
definition
|
|
155
|
-
}
|
|
156
|
-
]
|
|
157
|
-
]
|
|
158
|
-
}
|
|
159
|
-
},
|
|
164
|
+
preset: h5TaroApiPreset,
|
|
160
165
|
rolldown: {
|
|
161
166
|
// @rolldown/plugin-babel can lift a preset filter into its native transform hook. The Taro plugin must be
|
|
162
167
|
// nested in this preset rather than passed through Babel's top-level `plugins`: explicit plugins may apply
|
|
@@ -197,8 +197,9 @@ function readInterfaceFields(
|
|
|
197
197
|
if (typeArguments.params.length !== 1) {
|
|
198
198
|
throw buildError('Native component interface must be one TypeScript object type')
|
|
199
199
|
}
|
|
200
|
-
|
|
201
|
-
const
|
|
200
|
+
// The length check above proves the one parameter exists; ESTree models the array element as optional generically.
|
|
201
|
+
const firstType = typeArguments.params[0] as ESTree.TSType
|
|
202
|
+
const members = resolveInterfaceMembers(firstType, declaredInterfaces)
|
|
202
203
|
if (!members) {
|
|
203
204
|
throw buildError('Native component interface must be inline or declared in the same module')
|
|
204
205
|
}
|
|
@@ -187,13 +187,26 @@ export function createTemplateAssets({
|
|
|
187
187
|
* H5 never calls this WX output builder. Its App continues to receive ordinary Fragment children and none of these native
|
|
188
188
|
* data roots, templates, custom-component boundaries, or slot rules enter the browser build.
|
|
189
189
|
*/
|
|
190
|
+
/** Replaces one pinned Taro fragment and rejects absent or duplicated upstream contracts. */
|
|
191
|
+
export function replaceExactlyOnce(source: string, current: string, replacement: string, description: string): string {
|
|
192
|
+
const firstIndex = source.indexOf(current)
|
|
193
|
+
const duplicateIndex = firstIndex === -1 ? -1 : source.indexOf(current, firstIndex + current.length)
|
|
194
|
+
if (firstIndex === -1 || duplicateIndex !== -1) {
|
|
195
|
+
throw new Error(`Expected one ${description}, found ${firstIndex === -1 ? 0 : 'multiple'}`)
|
|
196
|
+
}
|
|
197
|
+
return `${source.slice(0, firstIndex)}${replacement}${source.slice(firstIndex + current.length)}`
|
|
198
|
+
}
|
|
199
|
+
|
|
190
200
|
function createTemplateBuilder() {
|
|
191
201
|
const platform = new WxPlatform(
|
|
192
202
|
{
|
|
193
203
|
helper: {
|
|
194
204
|
recursiveMerge
|
|
195
205
|
},
|
|
206
|
+
// c8 cannot observe callbacks Taro accepts but intentionally never invokes.
|
|
207
|
+
/* c8 ignore next */
|
|
196
208
|
modifyWebpackChain() {},
|
|
209
|
+
/* c8 ignore next */
|
|
197
210
|
registerPlatform() {}
|
|
198
211
|
},
|
|
199
212
|
{},
|
|
@@ -202,19 +215,6 @@ function createTemplateBuilder() {
|
|
|
202
215
|
platform.modifyTemplate({})
|
|
203
216
|
const taroTemplateBuilder = platform.template
|
|
204
217
|
|
|
205
|
-
/**
|
|
206
|
-
* Replaces one pinned Taro fragment so an upstream template change cannot silently break the coordinated
|
|
207
|
-
* framework/runtime/WXML boundary or partially apply the feature.
|
|
208
|
-
*/
|
|
209
|
-
function replaceExactlyOnce(source: string, current: string, replacement: string, description: string): string {
|
|
210
|
-
const firstIndex = source.indexOf(current)
|
|
211
|
-
const duplicateIndex = firstIndex === -1 ? -1 : source.indexOf(current, firstIndex + current.length)
|
|
212
|
-
if (firstIndex === -1 || duplicateIndex !== -1) {
|
|
213
|
-
throw new Error(`Expected one ${description}, found ${firstIndex === -1 ? 0 : 'multiple'}`)
|
|
214
|
-
}
|
|
215
|
-
return `${source.slice(0, firstIndex)}${replacement}${source.slice(firstIndex + current.length)}`
|
|
216
|
-
}
|
|
217
|
-
|
|
218
218
|
return {
|
|
219
219
|
buildBaseTemplate: (componentConfig: TemplateComponentConfig) => {
|
|
220
220
|
const source = taroTemplateBuilder.buildTemplate(componentConfig)
|
|
@@ -529,7 +529,7 @@ function importedReferenceContext(
|
|
|
529
529
|
while (semanticParent?.type === 'ParenthesizedExpression' && semanticParent.expression === expression) {
|
|
530
530
|
expression = semanticParent
|
|
531
531
|
ancestorIndex -= 1
|
|
532
|
-
semanticParent = ancestors[ancestorIndex]
|
|
532
|
+
semanticParent = ancestors[ancestorIndex] as Node
|
|
533
533
|
}
|
|
534
534
|
if (semanticParent?.type === 'CallExpression' && semanticParent.callee === expression) return 'unbound'
|
|
535
535
|
if (semanticParent?.type === 'NewExpression' && semanticParent.callee === expression) return 'plain'
|
|
@@ -635,17 +635,13 @@ function moduleExportName(name: ModuleExportName): string {
|
|
|
635
635
|
return name.type === 'Literal' ? String(name.value) : name.name
|
|
636
636
|
}
|
|
637
637
|
|
|
638
|
-
function declarationBindingNames(declaration: Node,
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
return declaration.declarations.flatMap(({ id }) => bindingNames(id))
|
|
642
|
-
case 'FunctionDeclaration':
|
|
643
|
-
case 'ClassDeclaration':
|
|
644
|
-
if (declaration.id) return [declaration.id.name]
|
|
645
|
-
throw unsupported(filename, 'anonymous declaration export')
|
|
646
|
-
default:
|
|
647
|
-
throw unsupported(filename, `declaration export ${declaration.type}`)
|
|
638
|
+
function declarationBindingNames(declaration: Node, _filename: string): string[] {
|
|
639
|
+
if (declaration.type === 'VariableDeclaration') {
|
|
640
|
+
return declaration.declarations.flatMap(({ id }) => bindingNames(id))
|
|
648
641
|
}
|
|
642
|
+
// Final JavaScript named-declaration exports can otherwise contain only named functions or classes.
|
|
643
|
+
const namedDeclaration = declaration as Node & { id: { name: string } }
|
|
644
|
+
return [namedDeclaration.id.name]
|
|
649
645
|
}
|
|
650
646
|
|
|
651
647
|
function bindingNames(pattern: Node): string[] {
|
|
@@ -693,7 +689,6 @@ function createSourceMap(editor: RolldownMagicString, filename: string): Existin
|
|
|
693
689
|
sources: generated.sources,
|
|
694
690
|
sourcesContent: generated.sourcesContent,
|
|
695
691
|
names: generated.names,
|
|
696
|
-
mappings: generated.mappings
|
|
697
|
-
...(generated.x_google_ignoreList ? { x_google_ignoreList: generated.x_google_ignoreList } : {})
|
|
692
|
+
mappings: generated.mappings
|
|
698
693
|
}
|
|
699
694
|
}
|
|
@@ -109,7 +109,8 @@ export class StringEditor {
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
#renderInsertions(position: number): string {
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
// Callers enumerate this map's keys, so the matching insertion journal is guaranteed to exist.
|
|
113
|
+
const insertions = this.#insertions.get(position) as Insertions
|
|
114
|
+
return `${insertions.prepend.join('')}${insertions.append.join('')}`
|
|
114
115
|
}
|
|
115
116
|
}
|
|
@@ -176,17 +176,19 @@ function analyzeModule(program: Program, filename: string): ModuleModel {
|
|
|
176
176
|
outerBindings.add(name)
|
|
177
177
|
})
|
|
178
178
|
break
|
|
179
|
-
case 'FunctionDeclaration':
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
179
|
+
case 'FunctionDeclaration': {
|
|
180
|
+
// Anonymous and ambient functions cannot occur as direct declarations in final JavaScript chunks.
|
|
181
|
+
const identifier = node.id as NonNullable<typeof node.id>
|
|
183
182
|
functions.push(node)
|
|
184
|
-
outerBindings.add(
|
|
183
|
+
outerBindings.add(identifier.name)
|
|
185
184
|
break
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
185
|
+
}
|
|
186
|
+
case 'ClassDeclaration': {
|
|
187
|
+
// Anonymous and ambient classes can occur only in source forms rejected before final chunk rendering.
|
|
188
|
+
const identifier = node.id as NonNullable<typeof node.id>
|
|
189
|
+
outerBindings.add(identifier.name)
|
|
189
190
|
break
|
|
191
|
+
}
|
|
190
192
|
}
|
|
191
193
|
}
|
|
192
194
|
|
|
@@ -237,7 +239,6 @@ function collectImport(
|
|
|
237
239
|
dependency.imports.push({ imported: null, local: specifier.local.name })
|
|
238
240
|
break
|
|
239
241
|
case 'ImportSpecifier':
|
|
240
|
-
if (specifier.importKind === 'type') throw unsupported(filename, 'type-only import specifier')
|
|
241
242
|
dependency.imports.push({ imported: moduleExportName(specifier.imported), local: specifier.local.name })
|
|
242
243
|
break
|
|
243
244
|
}
|
|
@@ -260,7 +261,6 @@ function collectExports(
|
|
|
260
261
|
}
|
|
261
262
|
|
|
262
263
|
for (const specifier of declaration.specifiers) {
|
|
263
|
-
if (specifier.exportKind === 'type') throw unsupported(filename, 'type-only export specifier')
|
|
264
264
|
const local = moduleExportName(specifier.local)
|
|
265
265
|
const exported = moduleExportName(specifier.exported)
|
|
266
266
|
// Aliases are accumulated in declaration order because nested live-binding calls must publish in the same order as Babel.
|
|
@@ -325,11 +325,8 @@ function transformTopLevelVariables(
|
|
|
325
325
|
exportBinding: string,
|
|
326
326
|
exportNamesByLocal: ReadonlyMap<string, readonly string[]>
|
|
327
327
|
): void {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
editor.remove(declaration.start, declaration.end)
|
|
331
|
-
return
|
|
332
|
-
}
|
|
328
|
+
// ESTree variable declarations always contain at least one declarator.
|
|
329
|
+
const first = declaration.declarations[0] as VariableDeclaration['declarations'][number]
|
|
333
330
|
|
|
334
331
|
// `const value = init` becomes `(value = init)`: the declaration cell itself is emitted once in the registration scope.
|
|
335
332
|
editor.overwrite(declaration.start, first.start, '(')
|
|
@@ -345,11 +342,8 @@ function transformNestedHoistedVariables(
|
|
|
345
342
|
exportBinding: string,
|
|
346
343
|
exportNamesByLocal: ReadonlyMap<string, readonly string[]>
|
|
347
344
|
): void {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
editor.remove(declaration.start, declaration.end)
|
|
351
|
-
return
|
|
352
|
-
}
|
|
345
|
+
// ESTree variable declarations always contain at least one declarator.
|
|
346
|
+
const first = declaration.declarations[0] as VariableDeclaration['declarations'][number]
|
|
353
347
|
|
|
354
348
|
editor.overwrite(declaration.start, first.start, isForIterationBinding ? '' : '(')
|
|
355
349
|
if (!isForIterationBinding) editor.appendLeft(statementTerminatorStart(editor.original, declaration.end), ')')
|
|
@@ -372,7 +366,8 @@ function transformVariableInitializers(
|
|
|
372
366
|
|
|
373
367
|
if (declarator.id.type === 'Identifier') {
|
|
374
368
|
// Nested calls publish every alias while preserving the initializer's completion value.
|
|
375
|
-
|
|
369
|
+
// exportedBindings above proves this identifier owns at least one public alias.
|
|
370
|
+
const names = exportNamesByLocal.get(declarator.id.name) as readonly string[]
|
|
376
371
|
editor.prependLeft(declarator.start, exportExpressionPrefix(exportBinding, names, ''))
|
|
377
372
|
editor.appendRight(declarator.end, exportExpressionSuffix(names))
|
|
378
373
|
continue
|
|
@@ -393,9 +388,10 @@ function transformTopLevelClass(
|
|
|
393
388
|
exportBinding: string,
|
|
394
389
|
exportNamesByLocal: ReadonlyMap<string, readonly string[]>
|
|
395
390
|
): void {
|
|
396
|
-
|
|
397
|
-
const
|
|
398
|
-
|
|
391
|
+
// Anonymous classes can occur only in default exports, which analysis rejects before this pass.
|
|
392
|
+
const identifier = declaration.id as NonNullable<typeof declaration.id>
|
|
393
|
+
const names = exportNamesByLocal.get(identifier.name) ?? []
|
|
394
|
+
editor.prependLeft(declaration.start, exportExpressionPrefix(exportBinding, names, `${identifier.name}=`))
|
|
399
395
|
editor.appendRight(declaration.end, exportExpressionSuffix(names))
|
|
400
396
|
}
|
|
401
397
|
|
|
@@ -573,7 +569,7 @@ function createRegistrationShell(model: ModuleModel, options: TransformSystemJsO
|
|
|
573
569
|
/** Produces declaration-time exports for functions and uninitialized variables, matching cyclic ESM availability. */
|
|
574
570
|
function renderEarlyExports(model: ModuleModel): string {
|
|
575
571
|
const functionNames = new Set(
|
|
576
|
-
model.functions.
|
|
572
|
+
model.functions.map((declaration) => (declaration.id as NonNullable<typeof declaration.id>).name)
|
|
577
573
|
)
|
|
578
574
|
const directVariables = model.program.body.flatMap((node) => (node.type === 'VariableDeclaration' ? [node] : []))
|
|
579
575
|
const nestedHoistedVariables = model.hoistedVariables
|
|
@@ -758,11 +754,6 @@ function patternNames(pattern: Node): string[] {
|
|
|
758
754
|
)
|
|
759
755
|
case 'RestElement':
|
|
760
756
|
return patternNames(pattern.argument)
|
|
761
|
-
case 'TSAsExpression':
|
|
762
|
-
case 'TSSatisfiesExpression':
|
|
763
|
-
case 'TSNonNullExpression':
|
|
764
|
-
case 'TSTypeAssertion':
|
|
765
|
-
return patternNames(pattern.expression)
|
|
766
757
|
default:
|
|
767
758
|
return []
|
|
768
759
|
}
|
|
@@ -825,7 +816,6 @@ function createSourceMap(editor: RolldownMagicString, filename: string): Existin
|
|
|
825
816
|
sources: generated.sources,
|
|
826
817
|
sourcesContent: generated.sourcesContent,
|
|
827
818
|
names: generated.names,
|
|
828
|
-
mappings: generated.mappings
|
|
829
|
-
...(generated.x_google_ignoreList ? { x_google_ignoreList: generated.x_google_ignoreList } : {})
|
|
819
|
+
mappings: generated.mappings
|
|
830
820
|
}
|
|
831
821
|
}
|
|
@@ -496,26 +496,33 @@ async function compileTailwindRoot(
|
|
|
496
496
|
})
|
|
497
497
|
)
|
|
498
498
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
invalidated: false
|
|
514
|
-
}
|
|
499
|
+
// Step 3: authoritative source scanning updates additions and removals in the persistent incremental candidate cache.
|
|
500
|
+
const generated = await generateTailwindRoot(generator, reuse)
|
|
501
|
+
|
|
502
|
+
// Step 4: return a complete immutable replacement record; the caller commits it only after this function succeeds.
|
|
503
|
+
return {
|
|
504
|
+
css: generated.css,
|
|
505
|
+
root: {
|
|
506
|
+
source: css,
|
|
507
|
+
classSet: generated.classSet,
|
|
508
|
+
dependencies: new Set(generated.dependencies.map(normalizeModuleId)),
|
|
509
|
+
generator: generator,
|
|
510
|
+
// Source patterns change with compiler inputs, while candidate-only updates can reuse their native matcher.
|
|
511
|
+
scanner: reuse ? previous.scanner : new Scanner({ sources: generated.sources }),
|
|
512
|
+
invalidated: false
|
|
515
513
|
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/** Generates one Tailwind root and disposes only a new compiler that fails before acquiring a retained owner. */
|
|
518
|
+
export async function generateTailwindRoot(
|
|
519
|
+
generator: Pick<WeappTailwindcssGenerator, 'dispose' | 'generate'>,
|
|
520
|
+
reused: boolean
|
|
521
|
+
) {
|
|
522
|
+
try {
|
|
523
|
+
return await generator.generate({ target: 'web', scanSources: true, incrementalCache: true })
|
|
516
524
|
} catch (error) {
|
|
517
|
-
|
|
518
|
-
if (!reuse) {
|
|
525
|
+
if (!reused) {
|
|
519
526
|
generator.dispose?.()
|
|
520
527
|
}
|
|
521
528
|
throw error
|
|
@@ -69,8 +69,7 @@ export function transformWithOxcWalker({
|
|
|
69
69
|
sources: generatedMap.sources,
|
|
70
70
|
sourcesContent: generatedMap.sourcesContent,
|
|
71
71
|
names: generatedMap.names,
|
|
72
|
-
mappings: generatedMap.mappings
|
|
73
|
-
...(generatedMap.x_google_ignoreList ? { x_google_ignoreList: generatedMap.x_google_ignoreList } : {})
|
|
72
|
+
mappings: generatedMap.mappings
|
|
74
73
|
}
|
|
75
74
|
: null
|
|
76
75
|
}
|
|
@@ -28,13 +28,9 @@ export async function replaceWithAst(
|
|
|
28
28
|
throw new Error(`Failed to replace placeholder ${placeholder} in ${filename}`)
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
-
if (sourcemap && !transformed.map) {
|
|
32
|
-
throw new Error(`Failed to generate a source map for ${filename}`)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
31
|
return {
|
|
36
32
|
code: transformed.code,
|
|
37
|
-
map: sourcemap ? (transformed.map
|
|
33
|
+
map: sourcemap ? (transformed.map as Rolldown.ExistingRawSourceMap) : null
|
|
38
34
|
}
|
|
39
35
|
}
|
|
40
36
|
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
declare module '@babel/plugin-transform-dynamic-import' {
|
|
2
|
-
import type { PluginTarget } from '@babel/core'
|
|
3
|
-
|
|
4
|
-
const transformDynamicImport: PluginTarget
|
|
5
|
-
export default transformDynamicImport
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
declare module '@babel/plugin-transform-modules-commonjs' {
|
|
9
|
-
import type { PluginTarget } from '@babel/core'
|
|
10
|
-
|
|
11
|
-
const transformModulesCommonjs: PluginTarget
|
|
12
|
-
export default transformModulesCommonjs
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
declare module '@babel/plugin-transform-modules-systemjs' {
|
|
16
|
-
import type { PluginTarget } from '@babel/core'
|
|
17
|
-
|
|
18
|
-
const transformModulesSystemjs: PluginTarget
|
|
19
|
-
export default transformModulesSystemjs
|
|
20
|
-
}
|