vite-plugin-taro 0.5.1 → 0.5.4

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 (31) hide show
  1. package/README.en.md +16 -16
  2. package/README.md +17 -16
  3. package/dist/node/plugins/client/client-taro.d.ts +4 -2
  4. package/dist/node/plugins/client/client-taro.js +43 -4
  5. package/dist/node/plugins/client/inject-taro-framework-apis.d.ts +5 -0
  6. package/dist/node/plugins/client/inject-taro-framework-apis.js +9 -0
  7. package/dist/node/plugins/css/plugins.d.ts +1 -3
  8. package/dist/node/plugins/css/plugins.js +91 -58
  9. package/dist/node/plugins/h5/create-stencil-client-adapter.d.ts +24 -0
  10. package/dist/node/plugins/h5/create-stencil-client-adapter.js +80 -0
  11. package/dist/node/plugins/h5/plugins.js +11 -54
  12. package/dist/node/plugins/wx/dev/dev-host.js +0 -3
  13. package/dist/node/plugins/wx/placement/placer.js +1 -2
  14. package/dist/node/plugins/wx/plugins.js +10 -1
  15. package/dist/node/vite-plugin.js +1 -1
  16. package/dist/runtime/client/taro/api.d.ts +2 -3
  17. package/dist/runtime/client/taro/api.js +9 -8
  18. package/package.json +12 -12
  19. package/src/node/plugins/client/client-taro.ts +52 -4
  20. package/src/node/plugins/client/inject-taro-framework-apis.ts +11 -0
  21. package/src/node/plugins/css/plugins.ts +89 -53
  22. package/src/node/plugins/h5/create-stencil-client-adapter.ts +109 -0
  23. package/src/node/plugins/h5/plugins.ts +11 -76
  24. package/src/node/plugins/wx/dev/dev-host.ts +0 -3
  25. package/src/node/plugins/wx/placement/placer.ts +1 -2
  26. package/src/node/plugins/wx/plugins.ts +10 -1
  27. package/src/node/vite-plugin.ts +1 -1
  28. package/src/runtime/client/taro/api.ts +9 -9
  29. package/dist/node/plugins/wx/dev/publish-wx-dev-style.d.ts +0 -11
  30. package/dist/node/plugins/wx/dev/publish-wx-dev-style.js +0 -12
  31. package/src/node/plugins/wx/dev/publish-wx-dev-style.ts +0 -26
@@ -36,8 +36,11 @@ function createWxPlugin(options) {
36
36
  },
37
37
  build: {
38
38
  modulePreload: false,
39
+ // Mini Program styles are intentionally global. This guarantees one compiler stylesheet for the CSS
40
+ // finalizer; enabling splitting would require Page ownership and must not be silently flattened.
39
41
  cssCodeSplit: false,
40
- // Let weapp-tailwindcss own final WXSS transformation.
42
+ // Preserve readable source for the final WX compatibility pass; browser minification can emit syntax
43
+ // unsupported by WeChat and would make the subsequent whole-file conversion harder to reason about.
41
44
  cssMinify: false,
42
45
  // No base64 assets: Taro warns on image srcs above ~2KB, and inlined
43
46
  // images bloat the JS bundle toward the mini program package limit.
@@ -99,6 +102,12 @@ function createWxPlugin(options) {
99
102
  }
100
103
  },
101
104
  generateBundle: {
105
+ /*
106
+ * This hook is registered after createCssPlugins() and shares hook-level `order: 'post'` with the adapted
107
+ * upstream hooks and VPT style finalizer. Registration order therefore guarantees that app.wxss is complete
108
+ * before native Page/component companions are emitted. Without this order, the CSS finalizer could consume
109
+ * incomplete Tailwind output or mistake native WXSS companions for additional compiler styles.
110
+ */
102
111
  order: 'post',
103
112
  async handler(_, bundle) {
104
113
  const subpackages = placer.getSubpackages(bundle);
@@ -8,7 +8,7 @@ import { createWxTargetPlugins } from './plugins/wx/plugins.js';
8
8
  export default function vitePluginTaro(options) {
9
9
  return [
10
10
  createConditionalDirectivePlugin(options.target),
11
- createClientTaroPlugin(),
11
+ createClientTaroPlugin(options.target),
12
12
  ...createCssPlugins(options.target),
13
13
  ...react(),
14
14
  ...(options.target === 'wx' ? createWxTargetPlugins(options) : []),
@@ -1,4 +1,3 @@
1
- import '@tarojs/plugin-framework-react/dist/runtime';
2
- import Taro from '@tarojs/taro';
1
+ import taro from '@tarojs/taro';
3
2
  export * from '@tarojs/taro';
4
- export default Taro;
3
+ export default taro;
@@ -1,9 +1,10 @@
1
- import '@tarojs/plugin-framework-react/dist/runtime';
2
- import { hooks } from '@tarojs/runtime';
3
- import Taro from '@tarojs/taro';
4
- if (hooks.isExist('initNativeApi')) {
5
- hooks.call('initNativeApi', Taro);
6
- }
7
- // @ts-expect-error @tarojs/taro declares export= types, but the facade also exposes its runtime named exports.
1
+ /*
2
+ * This physical module is the shared facade, not the generic Taro implementation. Its own `@tarojs/taro` request is
3
+ * resolved by the client plugin to the selected platform APIs, while application requests resolve back to this facade.
4
+ * React's API loader then extends this same object with framework lifecycle hooks.
5
+ */
6
+ import taro from '@tarojs/taro';
7
+ // Re-export platform APIs while preserving object identity for the framework loader's lifecycle assignments.
8
+ // @ts-expect-error @tarojs/taro uses export= types while Rolldown exposes its runtime properties as named exports.
8
9
  export * from '@tarojs/taro';
9
- export default Taro;
10
+ export default taro;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-taro",
3
- "version": "0.5.1",
3
+ "version": "0.5.4",
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",
@@ -12,7 +12,7 @@
12
12
  "bugs": {
13
13
  "url": "https://github.com/sep2/vite-plugin-taro/issues"
14
14
  },
15
- "homepage": "https://github.com/sep2/vite-plugin-taro/tree/main/packages/vite-plugin-taro#readme",
15
+ "homepage": "https://vpt.js.org",
16
16
  "main": "./dist/vite.js",
17
17
  "module": "./dist/vite.js",
18
18
  "types": "./dist/vite.d.ts",
@@ -58,24 +58,24 @@
58
58
  "@babel/plugin-transform-modules-systemjs": "^7.29.8",
59
59
  "@rolldown/plugin-babel": "^0.2.3",
60
60
  "@tailwindcss-mangle/engine": "0.2.0",
61
- "@tarojs/components": "^4.2.0",
62
- "@tarojs/helper": "^4.2.0",
63
- "@tarojs/plugin-platform-h5": "^4.2.0",
64
- "@tarojs/plugin-platform-weapp": "^4.2.0",
65
- "@tarojs/router": "^4.2.0",
66
- "@tarojs/runtime": "^4.2.0",
67
- "@tarojs/taro": "^4.2.0",
61
+ "@tarojs/components": "4.2.0",
62
+ "@tarojs/helper": "4.2.0",
63
+ "@tarojs/plugin-platform-h5": "4.2.0",
64
+ "@tarojs/plugin-platform-weapp": "4.2.0",
65
+ "@tarojs/router": "4.2.0",
66
+ "@tarojs/runtime": "4.2.0",
67
+ "@tarojs/taro": "4.2.0",
68
68
  "@vitejs/plugin-react": "^6.0.5",
69
69
  "@weapp-tailwindcss/postcss": "3.2.8",
70
- "babel-plugin-transform-taroapi": "^4.2.0",
70
+ "babel-plugin-transform-taroapi": "4.2.0",
71
71
  "picocolors": "^1.1.1",
72
72
  "react-reconciler": "0.33.0",
73
73
  "react-refresh": "^0.18.0",
74
74
  "rolldown": "1.2.3",
75
75
  "tailwindcss": "^4.3.3",
76
76
  "weapp-tailwindcss": "^5.2.11",
77
- "@tarojs/plugin-framework-react": "npm:vite-plugin-taro-plugin-framework-react@0.5.1",
78
- "@tarojs/react": "npm:vite-plugin-taro-react@0.5.1"
77
+ "@tarojs/plugin-framework-react": "npm:vite-plugin-taro-plugin-framework-react@0.5.4",
78
+ "@tarojs/react": "npm:vite-plugin-taro-react@0.5.4"
79
79
  },
80
80
  "peerDependencies": {
81
81
  "react": "^19.0.0",
@@ -1,23 +1,71 @@
1
1
  import type { Plugin } from 'vite'
2
+ import type { VitePluginTaroTarget } from '../../../options.ts'
3
+ import { normalizeModuleId } from '../../utils/modules.ts'
2
4
  import { resolvePackageFile } from '../../utils/packages.ts'
3
5
  import { clientTaroNativeId } from './constant.ts'
6
+ import { injectTaroFrameworkApis } from './inject-taro-framework-apis.ts'
4
7
 
8
+ /*
9
+ * Taro API resolution uses one public facade while avoiding a recursive `@tarojs/taro` import:
10
+ *
11
+ * 1. Application imports of `virtual:taro/api` or `@tarojs/taro` resolve to the physical `api.js` facade.
12
+ * 2. The facade itself imports `@tarojs/taro`; its importer identifies that request as the platform implementation.
13
+ * 3. H5 receives `@tarojs/plugin-platform-h5` APIs, while WX receives the generic `@tarojs/taro` implementation.
14
+ * 4. React's framework API loader transforms the facade, assigning lifecycle hooks such as `useLaunch` to the same
15
+ * platform object and exposing them as named exports.
16
+ *
17
+ * The importer-sensitive second step removes the need for another public-looking virtual module while retaining one
18
+ * facade object for platform APIs and framework lifecycles.
19
+ */
20
+ /** Public facade used by transformed application API imports. */
5
21
  export const clientTaroApiId = 'virtual:taro/api'
22
+
23
+ const clientTaroApiPath = resolvePackageFile('dist/runtime/client/taro/api.js')
24
+ const normalizedClientTaroApiPath = normalizeModuleId(clientTaroApiPath)
25
+
6
26
  const clientTaroComponentId = 'virtual:taro/components'
27
+
7
28
  const clientTaroModules = new Map([
8
- [clientTaroApiId, resolvePackageFile('dist/runtime/client/taro/api.js')],
29
+ [clientTaroApiId, clientTaroApiPath],
9
30
  [clientTaroComponentId, resolvePackageFile('dist/runtime/client/taro/component.js')],
10
31
  [clientTaroNativeId, resolvePackageFile('dist/runtime/client/taro/define-native-component.js')]
11
32
  ])
12
33
 
13
- /** Creates the target-neutral Taro facade plugin. */
14
- export function createClientTaroPlugin(): Plugin {
34
+ /** Creates the shared Taro facade backed by the selected target's API implementation. */
35
+ export function createClientTaroPlugin(target: VitePluginTaroTarget): Plugin {
36
+ const platformTaroId = resolvePlatformTaroId(target)
37
+
15
38
  return {
16
39
  name: 'vpt:client-taro',
17
40
  enforce: 'pre',
18
41
 
19
- resolveId(id) {
42
+ async resolveId(id, importer) {
43
+ if (id === '@tarojs/taro') {
44
+ if (!isClientTaroFacade(importer)) {
45
+ return clientTaroApiPath
46
+ }
47
+
48
+ // Delegate the platform backend to Vite instead of returning an absolute dependency path. H5 marks this
49
+ // backend as an optimization root, so delegation lets Vite substitute its prebundled facade. Removing it
50
+ // bypasses CommonJS interop and exposes backend details such as base64-js directly to the browser.
51
+ return this.resolve(platformTaroId, importer, { skipSelf: true })
52
+ }
20
53
  return clientTaroModules.get(id)
54
+ },
55
+
56
+ // Taro's framework loader transforms source code, so apply it only to the shared physical facade.
57
+ transform(code, id) {
58
+ if (normalizeModuleId(id) === normalizedClientTaroApiPath) {
59
+ return injectTaroFrameworkApis(code)
60
+ }
21
61
  }
22
62
  }
23
63
  }
64
+
65
+ function resolvePlatformTaroId(target: VitePluginTaroTarget): string {
66
+ return target === 'h5' ? '@tarojs/plugin-platform-h5/dist/runtime/apis' : '@tarojs/taro'
67
+ }
68
+
69
+ function isClientTaroFacade(importer: string | undefined): boolean {
70
+ return importer !== undefined && normalizeModuleId(importer) === normalizedClientTaroApiPath
71
+ }
@@ -0,0 +1,11 @@
1
+ import { packageRequire } from '../../utils/packages.ts'
2
+
3
+ const apiLoader: (source: string) => string = packageRequire('@tarojs/plugin-framework-react/dist/api-loader')
4
+
5
+ /**
6
+ * Applies Taro React's standard source-to-source API loader. It imports the framework lifecycle hooks, assigns them to
7
+ * the facade's `taro` object, and emits matching named exports; keeping the hook inventory owned by Taro avoids drift.
8
+ */
9
+ export function injectTaroFrameworkApis(source: string): string {
10
+ return apiLoader(source)
11
+ }
@@ -5,10 +5,24 @@ import { WeappTailwindcss } from 'weapp-tailwindcss/vite'
5
5
  import type { VitePluginTaroTarget } from '../../../options.ts'
6
6
  import { packageRequire } from '../../utils/packages.ts'
7
7
 
8
- // Keep the Vite plugin and the compatibility finalizer on identical WX conversion settings. A difference here can
9
- // make the second pass preserve browser units or apply a transformation that the first pass did not expect.
8
+ /*
9
+ * CSS output order for WX:
10
+ *
11
+ * weapp-tailwindcss output hooks
12
+ * → vpt:wx-style-finalizer
13
+ * → vpt:wx native companion emission
14
+ *
15
+ * All three generateBundle hooks retain hook-level `order: 'post'` and therefore execute in registration order. The
16
+ * upstream plugin normally also uses plugin-level `enforce: 'post'`, which would move it behind both VPT plugins and
17
+ * break this sequence. `alignWxGenerateBundleOrder` removes only that broader phase from upstream output hooks.
18
+ */
19
+
20
+ // Tailwind belongs to VPT, not necessarily to the application. Resolving from VPT keeps strict package managers and
21
+ // bundled development from looking for Tailwind in the application's node_modules.
10
22
  const tailwindcssBasedir = path.dirname(packageRequire.resolve('tailwindcss/package.json'))
11
23
 
24
+ // Both upstream generation and VPT's final whole-file pass use one conversion policy. If these options diverge, the
25
+ // second pass can preserve browser units or reinterpret syntax that the first pass generated.
12
26
  const wxStyleOptions = {
13
27
  cssCalc: false,
14
28
  autoprefixer: false,
@@ -16,84 +30,106 @@ const wxStyleOptions = {
16
30
  px2rpx: true
17
31
  } as const
18
32
 
19
- const transformWxss = createStyleHandler(wxStyleOptions)
20
-
21
- /** Completes the final compatibility pass required by generated WXSS. */
22
- export async function adaptWxss(source: string): Promise<string> {
23
- return (await transformWxss(source)).css
24
- }
33
+ // The handler is immutable and reusable across builds; only each emitted asset's source is replaced.
34
+ const transformWxStyle = createStyleHandler(wxStyleOptions)
25
35
 
26
- /** Creates the target-aware Tailwind CSS plugins. */
36
+ /** Creates the target-aware Tailwind pipeline. */
27
37
  export function createCssPlugins(target: VitePluginTaroTarget): PluginOption[] {
28
38
  const wx = target === 'wx'
29
39
 
30
- return [
31
- ...(WeappTailwindcss({
40
+ const tailwindPlugins =
41
+ WeappTailwindcss({
42
+ // VPT is a custom Vite compiler. Using Taro's adapter would import Taro-specific CSS ownership rules.
32
43
  appType: 'weapp-vite',
33
- // WX must enable this for split Tailwind imports such as `tailwindcss/theme.css`. Otherwise Vite's
34
- // PostCSS resolver tries to resolve those imports from the application and fails when Tailwind is owned
35
- // by vpt. The web generator consumes the imports before that resolver runs, so H5 keeps the
36
- // upstream default.
44
+ // WX generation rewrites Tailwind's split package imports before Vite tries to resolve them in the app.
45
+ // Without this, strict workspaces fail on imports such as `tailwindcss/theme.css`.
37
46
  rewriteCssImports: wx,
38
- // Tailwind is a plugin dependency, not an application dependency. Give weapp-tailwindcss the owning package
39
- // directory explicitly so bundled development and strict package managers resolve split CSS imports equally.
47
+ platform: wx ? 'weapp' : 'web',
40
48
  tailwindcssBasedir,
41
49
  generator: {
42
50
  target: wx ? 'weapp' : 'web'
43
- // webCompat: {
44
- // preset: 'legacy-web'
45
- // }
46
51
  },
47
52
  cssOptions: {
48
53
  ...wxStyleOptions,
54
+ // Browser output still needs vendor prefixes; WXSS does not support or need that browser pass.
49
55
  autoprefixer: !wx
50
56
  },
51
57
  logLevel: 'warn'
52
- }) ?? []),
53
- wx ? createWxssCompatibilityFinalizer() : undefined
58
+ }) ?? []
59
+
60
+ return [
61
+ ...(wx ? tailwindPlugins.map(alignWxGenerateBundleOrder) : tailwindPlugins),
62
+ wx ? createWxStyleFinalizer() : undefined
54
63
  ]
55
64
  }
56
65
 
57
66
  /**
58
- * Completes WXSS adaptation that weapp-tailwindcss leaves pending after rewriting split Tailwind imports.
67
+ * Finalizes the one global stylesheet after upstream Tailwind generation.
59
68
  *
60
- * `rewriteCssImports: true` makes the early Vite transform generate the Tailwind CSS, but the non-web generator
61
- * also defers CSS adaptation. The generated asset is consequently browser-shaped
62
- * CSS containing values and syntax such as `rem`, escaped class selectors, and `@property`. It is then recorded as a
63
- * processed Vite asset, so the upstream output finalizer does not perform the missing complete WXSS adaptation.
64
- *
65
- * This plugin runs after the upstream finalizer, repeats only the compatibility transform, and restores the captured
66
- * global asset to WeChat's required `app.wxss` path. Exact path correlation leaves Page WXSS companions untouched.
67
- * Remove it when upstream both completes adaptation and preserves the bundler-selected filename.
69
+ * `cssCodeSplit: false` makes the compiler style global, but upstream can name it `.css` or `.wxss` depending on build
70
+ * mode. This hook converts its complete final contents once and gives it the root `app.wxss` identity required by
71
+ * WeChat. Running earlier loses CSS from dynamic chunks; running after native companion emission would also see Page
72
+ * and native-component WXSS files that must remain opaque.
68
73
  */
69
- function createWxssCompatibilityFinalizer(): Plugin {
74
+ function createWxStyleFinalizer(): Plugin {
70
75
  return {
71
- name: 'vpt:wxss-compatibility-finalizer',
72
- enforce: 'post',
76
+ name: 'vpt:wx-style-finalizer',
73
77
  generateBundle: {
74
78
  order: 'post',
75
79
  async handler(_, bundle) {
76
- // With cssCodeSplit: false the global style is the only asset with a logical
77
- // .css source name, even when an earlier hook already changed its final suffix.
78
- const globalStyleAsset = Object.values(bundle).find(
79
- (output): output is Rolldown.OutputAsset =>
80
- output.type === 'asset' &&
81
- (output.fileName.replaceAll('\\', '/').endsWith('.css') ||
82
- output.names.some((name) => name.replaceAll('\\', '/').endsWith('.css')))
83
- )
84
- if (!globalStyleAsset) return
85
-
86
- // Both finalizers use a post-ordered generateBundle hook. Array order places this hook after the
87
- // upstream finalizer, where the single Vite global style asset has its final contents and filename.
88
- const source =
89
- typeof globalStyleAsset.source === 'string'
90
- ? globalStyleAsset.source
91
- : new TextDecoder().decode(globalStyleAsset.source)
92
- if (source.length > 0) {
93
- globalStyleAsset.source = await adaptWxss(source)
80
+ const styles = Object.values(bundle).filter(isStyleAsset)
81
+ // More than one compiler style means cssCodeSplit was re-enabled. Choosing one would silently lose CSS.
82
+ if (styles.length > 1) {
83
+ throw new Error('WX builds require one global compiler-emitted stylesheet')
94
84
  }
95
- globalStyleAsset.fileName = 'app.wxss'
85
+ // CSS is optional; applications without styles do not need an empty app.wxss.
86
+ if (styles.length === 0) return
87
+
88
+ const [style] = styles
89
+ const source = typeof style.source === 'string' ? style.source : new TextDecoder().decode(style.source)
90
+ // generateBundle exposes the final asset as mutable so conversion and native placement remain atomic.
91
+ // Without the compatibility pass, browser-only selectors, escaped classes, rem and @property can reach
92
+ // WeChat. Without the rename, bundled development writes paths such as src/app.wxss, which WeChat does
93
+ // not load as the application's global stylesheet.
94
+ style.source = (await transformWxStyle(source)).css
95
+ style.fileName = 'app.wxss'
96
96
  }
97
97
  }
98
98
  }
99
99
  }
100
+
101
+ /**
102
+ * Adapts upstream plugin descriptors without mutating `weapp-tailwindcss` or patching node_modules.
103
+ *
104
+ * Vite first groups whole plugins by `enforce`, then orders individual hooks. Upstream's output plugins specify both
105
+ * `enforce: 'post'` and `generateBundle.order: 'post'`. The plugin-level phase overrides their earlier registration and
106
+ * places them after VPT's normal plugins, so VPT observes incomplete CSS. Making all of VPT post-enforced would fix that
107
+ * one hook while unnecessarily reordering resolution and transforms.
108
+ *
109
+ * For upstream plugins that actually own generateBundle, clone the descriptor without plugin-level enforcement. Keep
110
+ * hook-level `order: 'post'`: it still waits for ordinary bundle generation, while registration order becomes the sole
111
+ * tie-breaker between upstream generation, VPT finalization and native output. H5 descriptors remain untouched.
112
+ */
113
+ function alignWxGenerateBundleOrder(pluginOption: PluginOption): PluginOption {
114
+ // PluginOption permits nested arrays. Preserve their shape while adapting every concrete plugin recursively.
115
+ if (Array.isArray(pluginOption)) return pluginOption.map(alignWxGenerateBundleOrder)
116
+
117
+ if (
118
+ !pluginOption ||
119
+ typeof pluginOption !== 'object' ||
120
+ !('enforce' in pluginOption) ||
121
+ pluginOption.enforce !== 'post' ||
122
+ !('generateBundle' in pluginOption) ||
123
+ pluginOption.generateBundle === undefined
124
+ ) {
125
+ return pluginOption
126
+ }
127
+
128
+ // Clone rather than mutate: upstream may retain or reuse the descriptor returned by its factory.
129
+ return { ...pluginOption, enforce: undefined }
130
+ }
131
+
132
+ /** Selects only the compiler stylesheet; native WXSS assets are emitted by the later WX hook. */
133
+ function isStyleAsset(output: Rolldown.OutputBundle[string]): output is Rolldown.OutputAsset {
134
+ return output.type === 'asset' && /\.(?:css|wxss)$/.test(output.fileName)
135
+ }
@@ -0,0 +1,109 @@
1
+ import type { types as BabelTypes } from '@babel/core'
2
+ import { type NodePath, type PluginObj, transformAsync, types } from '@babel/core'
3
+ import type { Plugin } from 'vite'
4
+ import { normalizeModuleId } from '../../utils/modules.ts'
5
+ import { packageRequire } from '../../utils/packages.ts'
6
+
7
+ const stencilClientPath = packageRequire.resolve('@stencil/core/internal/client', {
8
+ paths: [packageRequire.resolve('@tarojs/components/package.json')]
9
+ })
10
+ const normalizedStencilClientPath = normalizeModuleId(stencilClientPath)
11
+
12
+ /**
13
+ * Creates the compiler-owned adaptation of Stencil's client style insertion.
14
+ *
15
+ * Taro components inject their styles through this internal client. Its default insertion point places those styles
16
+ * after application CSS, allowing component defaults to override application rules. The adapter is registered in both
17
+ * Vite's application pipeline and the independent dependency-optimization build: removing either registration leaves
18
+ * production or development with an unadapted client. This explicit dual registration removes the former optimization
19
+ * exclusion while keeping one transformation implementation.
20
+ */
21
+ export function createStencilClientAdapter(): Plugin {
22
+ return {
23
+ name: 'vpt:h5-stencil-client',
24
+ transform: adaptStencilClient
25
+ }
26
+ }
27
+
28
+ /** Applies the shared Stencil adaptation in either the application or dependency-optimization pipeline. */
29
+ export async function adaptStencilClient(code: string, id: string) {
30
+ if (normalizeModuleId(id) !== normalizedStencilClientPath) {
31
+ return
32
+ }
33
+
34
+ const transformed = await transformAsync(code, {
35
+ babelrc: false,
36
+ configFile: false,
37
+ filename: stencilClientPath,
38
+ plugins: [rewriteStencilStyleInsertion],
39
+ sourceFileName: stencilClientPath,
40
+ sourceMaps: true
41
+ })
42
+
43
+ if (transformed?.code === undefined || transformed.code === null) {
44
+ throw new Error(`Failed to adapt Stencil client: ${stencilClientPath}`)
45
+ }
46
+
47
+ return {
48
+ code: transformed.code,
49
+ map: transformed.map
50
+ }
51
+ }
52
+
53
+ /** Keeps Stencil-injected Taro component styles before application stylesheets. */
54
+ function rewriteStencilStyleInsertion(): PluginObj {
55
+ return {
56
+ name: 'vpt:rewrite-stencil-style-insertion',
57
+ visitor: {
58
+ CallExpression(callPath) {
59
+ if (!isStencilStyleInsertBeforeCall(callPath)) {
60
+ return
61
+ }
62
+
63
+ callPath
64
+ .get('arguments.1')
65
+ .replaceWith(
66
+ types.conditionalExpression(
67
+ types.callExpression(
68
+ types.memberExpression(types.identifier('scopeId'), types.identifier('startsWith')),
69
+ [types.stringLiteral('sc-taro-')]
70
+ ),
71
+ createStyleQuery('style,link[rel="stylesheet"]'),
72
+ createStyleQuery('link')
73
+ )
74
+ )
75
+ }
76
+ }
77
+ }
78
+ }
79
+
80
+ /** Identifies Stencil's default component-style insertion call. */
81
+ function isStencilStyleInsertBeforeCall(callPath: NodePath<BabelTypes.CallExpression>): boolean {
82
+ const { callee, arguments: callArguments } = callPath.node
83
+ return (
84
+ types.isMemberExpression(callee) &&
85
+ types.isIdentifier(callee.object, { name: 'styleContainerNode' }) &&
86
+ types.isIdentifier(callee.property, { name: 'insertBefore' }) &&
87
+ types.isIdentifier(callArguments[0], { name: 'styleElm' }) &&
88
+ isStyleQuery(callArguments[1], 'link')
89
+ )
90
+ }
91
+
92
+ /** Identifies one style-container querySelector call. */
93
+ function isStyleQuery(node: BabelTypes.Node | null | undefined, selector: string): boolean {
94
+ return (
95
+ types.isCallExpression(node) &&
96
+ types.isMemberExpression(node.callee) &&
97
+ types.isIdentifier(node.callee.object, { name: 'styleContainerNode' }) &&
98
+ types.isIdentifier(node.callee.property, { name: 'querySelector' }) &&
99
+ types.isStringLiteral(node.arguments[0], { value: selector })
100
+ )
101
+ }
102
+
103
+ /** Creates one style-container querySelector call. */
104
+ function createStyleQuery(selector: string): ReturnType<typeof types.callExpression> {
105
+ return types.callExpression(
106
+ types.memberExpression(types.identifier('styleContainerNode'), types.identifier('querySelector')),
107
+ [types.stringLiteral(selector)]
108
+ )
109
+ }
@@ -1,5 +1,3 @@
1
- import type { types as BabelTypes } from '@babel/core'
2
- import { type NodePath, type PluginObj, types } from '@babel/core'
3
1
  import babel from '@rolldown/plugin-babel'
4
2
  import type { HtmlTagDescriptor, Plugin, PluginOption } from 'vite'
5
3
  import type { VitePluginTaroOptions } from '../../../options.ts'
@@ -8,6 +6,7 @@ import { toViteFileImportPath } from '../../utils/modules.ts'
8
6
  import { packageRequire } from '../../utils/packages.ts'
9
7
  import { clientTaroApiId } from '../client/client-taro.ts'
10
8
  import { h5AppPath } from './constant.ts'
9
+ import { createStencilClientAdapter } from './create-stencil-client-adapter.ts'
11
10
  import { createModuleResolver } from './resolver/module-resolver.ts'
12
11
 
13
12
  /** Creates the plugins that own the H5 target. */
@@ -28,12 +27,6 @@ function createH5TargetPlugin(options: VitePluginTaroOptions): Plugin {
28
27
  resolve: {
29
28
  mainFields: ['main:h5', 'browser', 'module', 'jsnext:main', 'jsnext'],
30
29
  alias: [
31
- {
32
- find: /^@stencil\/core\/internal\/client$/,
33
- replacement: packageRequire.resolve('@stencil/core/internal/client', {
34
- paths: [packageRequire.resolve('@tarojs/components/package.json')]
35
- })
36
- },
37
30
  {
38
31
  find: /^@tarojs\/components$/,
39
32
  replacement: packageRequire.resolve('@tarojs/components/lib/react')
@@ -41,15 +34,19 @@ function createH5TargetPlugin(options: VitePluginTaroOptions): Plugin {
41
34
  {
42
35
  find: /^@tarojs\/components\/dist\/components$/,
43
36
  replacement: packageRequire.resolve('@tarojs/components/dist/components')
44
- },
45
- {
46
- find: /^@tarojs\/taro$/,
47
- replacement: packageRequire.resolve('@tarojs/plugin-platform-h5/dist/runtime/apis')
48
37
  }
49
38
  ]
50
39
  },
51
40
  optimizeDeps: {
52
- exclude: ['@stencil/core/internal/client']
41
+ // The compiler-owned H5 app and Taro facade are injected after Vite's initial HTML scan. Prebundle
42
+ // the facade's platform backend as one boundary so its CommonJS implementation details receive
43
+ // interop without duplicating their package list. ReactDOM needs the same treatment for the H5 app.
44
+ include: ['@tarojs/plugin-platform-h5/dist/runtime/apis', 'react-dom/client'],
45
+ // Dependency optimization is its own Rolldown build and does not run application transform plugins.
46
+ // Register the same adapter there so optimized Taro components cannot embed Stencil's original client.
47
+ rolldownOptions: {
48
+ plugins: [createStencilClientAdapter()]
49
+ }
53
50
  },
54
51
  build: {
55
52
  target: esTarget
@@ -105,11 +102,7 @@ function createH5IndexHtmlTags(): HtmlTagDescriptor[] {
105
102
  /** Creates H5-only Babel transforms for Stencil CSS ordering and Taro API imports. */
106
103
  function createH5SupportPlugins(): PluginOption[] {
107
104
  return [
108
- babel({
109
- include: /[\\/]@stencil[\\/]core[\\/]internal[\\/]client[\\/]index\.js(?:\?.*)?$/,
110
- exclude: [],
111
- plugins: [rewriteStencilStyleInsertion]
112
- }),
105
+ createStencilClientAdapter(),
113
106
  babel({
114
107
  plugins: [
115
108
  [
@@ -126,64 +119,6 @@ function createH5SupportPlugins(): PluginOption[] {
126
119
  ]
127
120
  }
128
121
 
129
- /** Keeps Stencil-injected Taro component styles before application stylesheets. */
130
- function rewriteStencilStyleInsertion(): PluginObj {
131
- return {
132
- name: 'vpt:rewrite-stencil-style-insertion',
133
- visitor: {
134
- CallExpression(callPath) {
135
- if (!isStencilStyleInsertBeforeCall(callPath)) {
136
- return
137
- }
138
-
139
- callPath
140
- .get('arguments.1')
141
- .replaceWith(
142
- types.conditionalExpression(
143
- types.callExpression(
144
- types.memberExpression(types.identifier('scopeId'), types.identifier('startsWith')),
145
- [types.stringLiteral('sc-taro-')]
146
- ),
147
- createStyleQuery('style,link[rel="stylesheet"]'),
148
- createStyleQuery('link')
149
- )
150
- )
151
- }
152
- }
153
- }
154
- }
155
-
156
- /** Identifies Stencil's default component-style insertion call. */
157
- function isStencilStyleInsertBeforeCall(callPath: NodePath<BabelTypes.CallExpression>): boolean {
158
- const { callee, arguments: callArguments } = callPath.node
159
- return (
160
- types.isMemberExpression(callee) &&
161
- types.isIdentifier(callee.object, { name: 'styleContainerNode' }) &&
162
- types.isIdentifier(callee.property, { name: 'insertBefore' }) &&
163
- types.isIdentifier(callArguments[0], { name: 'styleElm' }) &&
164
- isStyleQuery(callArguments[1], 'link')
165
- )
166
- }
167
-
168
- /** Identifies one style-container querySelector call. */
169
- function isStyleQuery(node: BabelTypes.Node | null | undefined, selector: string): boolean {
170
- return (
171
- types.isCallExpression(node) &&
172
- types.isMemberExpression(node.callee) &&
173
- types.isIdentifier(node.callee.object, { name: 'styleContainerNode' }) &&
174
- types.isIdentifier(node.callee.property, { name: 'querySelector' }) &&
175
- types.isStringLiteral(node.arguments[0], { value: selector })
176
- )
177
- }
178
-
179
- /** Creates one style-container querySelector call. */
180
- function createStyleQuery(selector: string): ReturnType<typeof types.callExpression> {
181
- return types.callExpression(
182
- types.memberExpression(types.identifier('styleContainerNode'), types.identifier('querySelector')),
183
- [types.stringLiteral(selector)]
184
- )
185
- }
186
-
187
122
  /** Creates H5 Taro compile-time constants. */
188
123
  function createH5Defines(): Record<string, string> {
189
124
  return {