vite-plugin-taro 0.2.2 → 0.3.1
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.md +83 -106
- package/README.zh.md +83 -104
- package/dist/node/build-context.d.ts +25 -0
- package/dist/node/build-context.js +48 -0
- package/dist/node/css/css-pipeline.d.ts +21 -0
- package/dist/node/css/css-pipeline.js +129 -0
- package/dist/node/plugins/conditional-directives.d.ts +3 -0
- package/dist/node/plugins/conditional-directives.js +62 -0
- package/dist/node/plugins/taro-runtime.d.ts +5 -0
- package/dist/node/plugins/taro-runtime.js +18 -0
- package/dist/node/targets/h5/plugin.d.ts +6 -0
- package/dist/node/targets/h5/plugin.js +117 -0
- package/dist/node/targets/h5/virtual-modules.d.ts +5 -0
- package/dist/node/targets/h5/virtual-modules.js +60 -0
- package/dist/node/targets/wx/companion-assets.d.ts +19 -0
- package/dist/node/targets/wx/companion-assets.js +129 -0
- package/dist/node/targets/wx/dev-server/development-session.d.ts +31 -0
- package/dist/node/targets/wx/dev-server/development-session.js +202 -0
- package/dist/node/targets/wx/dev-server/full-build-scheduler.d.ts +14 -0
- package/dist/node/targets/wx/dev-server/full-build-scheduler.js +60 -0
- package/dist/node/targets/wx/dev-server/js-utils.d.ts +5 -0
- package/dist/node/targets/wx/dev-server/js-utils.js +28 -0
- package/dist/node/targets/wx/dev-server/output.d.ts +19 -0
- package/dist/node/targets/wx/dev-server/output.js +44 -0
- package/dist/node/targets/wx/dev-server/rolldown-runtime-source.d.ts +8 -0
- package/dist/node/targets/wx/dev-server/rolldown-runtime-source.js +95 -0
- package/dist/node/targets/wx/dev-server/update-server-state.d.ts +65 -0
- package/dist/node/targets/wx/dev-server/update-server-state.js +116 -0
- package/dist/node/targets/wx/dev-server/update-transport.d.ts +28 -0
- package/dist/node/targets/wx/dev-server/update-transport.js +212 -0
- package/dist/node/targets/wx/dev-server/vite-bundled-dev-adapter.d.ts +44 -0
- package/dist/node/targets/wx/dev-server/vite-bundled-dev-adapter.js +145 -0
- package/dist/node/targets/wx/development-files.d.ts +8 -0
- package/dist/node/targets/wx/development-files.js +8 -0
- package/dist/node/targets/wx/plugin.d.ts +6 -0
- package/dist/node/targets/wx/plugin.js +143 -0
- package/dist/node/targets/wx/react-refresh.d.ts +4 -0
- package/dist/node/targets/wx/react-refresh.js +38 -0
- package/dist/node/targets/wx/virtual-modules.d.ts +16 -0
- package/dist/node/targets/wx/virtual-modules.js +120 -0
- package/dist/node/utils/async.d.ts +8 -0
- package/dist/node/utils/async.js +16 -0
- package/dist/node/utils/filesystem.d.ts +13 -0
- package/dist/node/utils/filesystem.js +43 -0
- package/dist/{vite/utils.d.ts → node/utils/modules.d.ts} +2 -2
- package/dist/{vite/utils.js → node/utils/modules.js} +10 -3
- package/dist/node/utils/packages.d.ts +2 -0
- package/dist/node/utils/packages.js +8 -0
- package/dist/node/vite-plugin.d.ts +4 -0
- package/dist/node/vite-plugin.js +33 -0
- package/dist/options.d.ts +21 -0
- package/dist/{shim/h5.d.ts → runtime/h5/taro-runtime.d.ts} +3 -1
- package/dist/runtime/h5/taro-runtime.js +9 -0
- package/dist/runtime/taro/api.d.ts +5 -0
- package/dist/{virtual → runtime/taro}/api.js +3 -0
- package/dist/runtime/taro/components.d.ts +2 -0
- package/dist/runtime/taro/components.js +2 -0
- package/dist/runtime/wx/page-update.d.ts +6 -0
- package/dist/runtime/wx/page-update.js +157 -0
- package/dist/{shim/wx.d.ts → runtime/wx/taro-runtime.d.ts} +1 -0
- package/dist/{shim/wx.js → runtime/wx/taro-runtime.js} +1 -0
- package/dist/runtime/wx/update-client-state.d.ts +68 -0
- package/dist/runtime/wx/update-client-state.js +84 -0
- package/dist/runtime/wx/update-client.d.ts +1 -0
- package/dist/runtime/wx/update-client.js +170 -0
- package/dist/vite.d.ts +2 -2
- package/dist/vite.js +1 -1
- package/package.json +15 -11
- package/src/node/build-context.ts +63 -0
- package/src/node/css/css-pipeline.ts +147 -0
- package/src/node/plugins/conditional-directives.ts +76 -0
- package/src/node/plugins/taro-runtime.ts +22 -0
- package/src/node/targets/h5/plugin.ts +135 -0
- package/src/node/targets/h5/virtual-modules.ts +71 -0
- package/src/node/targets/wx/companion-assets.ts +170 -0
- package/src/node/targets/wx/dev-server/development-session.ts +253 -0
- package/src/node/targets/wx/dev-server/full-build-scheduler.ts +62 -0
- package/src/node/targets/wx/dev-server/js-utils.ts +33 -0
- package/src/node/targets/wx/dev-server/output.ts +56 -0
- package/src/node/targets/wx/dev-server/rolldown-runtime-source.ts +95 -0
- package/src/node/targets/wx/dev-server/update-server-state.ts +183 -0
- package/src/node/targets/wx/dev-server/update-transport.ts +261 -0
- package/src/node/targets/wx/dev-server/vite-bundled-dev-adapter.ts +203 -0
- package/src/node/targets/wx/development-files.ts +11 -0
- package/src/node/targets/wx/plugin.ts +163 -0
- package/src/node/targets/wx/react-refresh.ts +51 -0
- package/src/node/targets/wx/virtual-modules.ts +152 -0
- package/src/node/utils/async.ts +19 -0
- package/src/node/utils/filesystem.ts +45 -0
- package/src/{vite/utils.ts → node/utils/modules.ts} +11 -3
- package/src/node/utils/packages.ts +10 -0
- package/src/node/vite-plugin.ts +41 -0
- package/src/options.ts +25 -0
- package/src/runtime/h5/taro-css.d.ts +3 -0
- package/src/runtime/h5/taro-runtime.ts +10 -0
- package/src/{virtual → runtime/taro}/api.ts +3 -0
- package/src/runtime/taro/components.ts +2 -0
- package/src/runtime/wx/page-update.ts +230 -0
- package/src/runtime/wx/react-refresh.d.ts +20 -0
- package/src/{shim/wx.ts → runtime/wx/taro-runtime.ts} +1 -0
- package/src/runtime/wx/update-client-state.ts +138 -0
- package/src/runtime/wx/update-client.ts +222 -0
- package/src/vite.ts +2 -2
- package/dist/shim/h5.js +0 -5
- package/dist/virtual/api.d.ts +0 -3
- package/dist/virtual/components.d.ts +0 -1
- package/dist/virtual/components.js +0 -1
- package/dist/vite/constants.d.ts +0 -4
- package/dist/vite/constants.js +0 -8
- package/dist/vite/plugins.d.ts +0 -8
- package/dist/vite/plugins.js +0 -186
- package/dist/vite/tailwindcss.d.ts +0 -3
- package/dist/vite/tailwindcss.js +0 -35
- package/dist/vite/targets/h5.d.ts +0 -31
- package/dist/vite/targets/h5.js +0 -201
- package/dist/vite/targets/wx.d.ts +0 -74
- package/dist/vite/targets/wx.js +0 -362
- package/dist/vite/types.d.ts +0 -38
- package/dist/vite/virtual-modules.d.ts +0 -3
- package/dist/vite/virtual-modules.js +0 -13
- package/dist/vite/vite-plugin-taro.d.ts +0 -7
- package/dist/vite/vite-plugin-taro.js +0 -80
- package/src/shim/h5.ts +0 -6
- package/src/virtual/components.ts +0 -1
- package/src/vite/constants.ts +0 -12
- package/src/vite/plugins.ts +0 -218
- package/src/vite/tailwindcss.ts +0 -41
- package/src/vite/targets/h5.ts +0 -229
- package/src/vite/targets/wx.ts +0 -436
- package/src/vite/types.ts +0 -48
- package/src/vite/virtual-modules.ts +0 -14
- package/src/vite/vite-plugin-taro.ts +0 -106
- /package/dist/{vite/types.js → options.js} +0 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
import { recursiveMerge } from '@tarojs/helper'
|
|
3
|
+
import { Weapp as WxPlatform } from '@tarojs/plugin-platform-weapp'
|
|
4
|
+
import type { JsonObject } from '../../../options.ts'
|
|
5
|
+
import type { BuildContext } from '../../build-context.ts'
|
|
6
|
+
import { normalizeModuleId } from '../../utils/modules.ts'
|
|
7
|
+
import { packageRequire } from '../../utils/packages.ts'
|
|
8
|
+
|
|
9
|
+
type WxAssetSource = string | Uint8Array
|
|
10
|
+
|
|
11
|
+
export type WxBundle = Record<
|
|
12
|
+
string,
|
|
13
|
+
{
|
|
14
|
+
type: 'asset' | 'chunk'
|
|
15
|
+
source?: WxAssetSource
|
|
16
|
+
modules?: Record<string, { renderedExports?: string[] }>
|
|
17
|
+
}
|
|
18
|
+
>
|
|
19
|
+
|
|
20
|
+
type WxAssetEmitter = {
|
|
21
|
+
emitFile(asset: { type: 'asset'; fileName: string; source: WxAssetSource }): string
|
|
22
|
+
warn(message: string): void
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type WxTemplateComponentConfig = {
|
|
26
|
+
includes: Set<string>
|
|
27
|
+
exclude: Set<string>
|
|
28
|
+
thirdPartyComponents: Map<string, Set<string>>
|
|
29
|
+
includeAll: boolean
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const taroWxComponentsPath = packageRequire.resolve('@tarojs/plugin-platform-weapp/dist/components-react')
|
|
33
|
+
const templateBuilder = createWxTemplateBuilder()
|
|
34
|
+
|
|
35
|
+
export function emitWxCompanionAssets(emitter: WxAssetEmitter, bundle: WxBundle, context: BuildContext): void {
|
|
36
|
+
for (const asset of createWxCompanionAssets(bundle, context)) {
|
|
37
|
+
if (!asset) continue
|
|
38
|
+
if (asset.source === undefined) {
|
|
39
|
+
emitter.warn(
|
|
40
|
+
`[vite-plugin-taro] WX companion asset "${asset.fileName}" is missing its source and was not emitted.`
|
|
41
|
+
)
|
|
42
|
+
continue
|
|
43
|
+
}
|
|
44
|
+
emitter.emitFile({ type: 'asset', fileName: asset.fileName, source: asset.source })
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function createWxCompanionAssets(
|
|
49
|
+
bundle: WxBundle,
|
|
50
|
+
context: BuildContext
|
|
51
|
+
): ({ fileName: string; source: WxAssetSource } | undefined)[] {
|
|
52
|
+
const json = (value: JsonObject) => (context.development ? JSON.stringify(value, null, 2) : JSON.stringify(value))
|
|
53
|
+
|
|
54
|
+
return [
|
|
55
|
+
{ fileName: 'app.json', source: json(context.project.appConfig) },
|
|
56
|
+
{ fileName: 'app.wxss', source: collectWxBundleStyles(bundle) },
|
|
57
|
+
{
|
|
58
|
+
fileName: 'base.wxml',
|
|
59
|
+
source: templateBuilder.buildTemplate(collectWxTemplateComponentConfig(bundle))
|
|
60
|
+
},
|
|
61
|
+
{ fileName: 'utils.wxs', source: templateBuilder.buildXScript() },
|
|
62
|
+
{ fileName: 'comp.wxml', source: templateBuilder.buildBaseComponentTemplate('.wxml') },
|
|
63
|
+
{ fileName: 'comp.json', source: json(createWxComponentConfig()) },
|
|
64
|
+
{ fileName: 'project.config.json', source: json(createWxProjectConfig(context)) },
|
|
65
|
+
context.project.projectPrivateConfigJson
|
|
66
|
+
? { fileName: 'project.private.config.json', source: json(context.project.projectPrivateConfigJson) }
|
|
67
|
+
: undefined,
|
|
68
|
+
{ fileName: 'sitemap.json', source: json(context.project.sitemapJson) },
|
|
69
|
+
...context.project.pages.flatMap((page) => [
|
|
70
|
+
{
|
|
71
|
+
fileName: `${page.path}.wxml`,
|
|
72
|
+
source: templateBuilder.buildPageTemplate(relativeWxRootAsset(page.path, 'base.wxml'), {
|
|
73
|
+
content: page.config,
|
|
74
|
+
path: page.path
|
|
75
|
+
})
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
fileName: `${page.path}.json`,
|
|
79
|
+
source: json({
|
|
80
|
+
...page.config,
|
|
81
|
+
usingComponents: { comp: relativeWxRootAsset(page.path, 'comp') }
|
|
82
|
+
})
|
|
83
|
+
},
|
|
84
|
+
{ fileName: `${page.path}.wxss`, source: '' }
|
|
85
|
+
])
|
|
86
|
+
]
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function createWxProjectConfig(context: BuildContext): JsonObject {
|
|
90
|
+
const projectConfig = context.project.projectConfigJson
|
|
91
|
+
const setting = isJsonObject(projectConfig.setting) ? projectConfig.setting : {}
|
|
92
|
+
return {
|
|
93
|
+
...projectConfig,
|
|
94
|
+
setting: { ...setting, compileHotReLoad: true }
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function isJsonObject(value: unknown): value is JsonObject {
|
|
99
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function createWxTemplateBuilder() {
|
|
103
|
+
const platform = new WxPlatform(
|
|
104
|
+
{ helper: { recursiveMerge }, modifyWebpackChain() {}, registerPlatform() {} },
|
|
105
|
+
{},
|
|
106
|
+
{}
|
|
107
|
+
)
|
|
108
|
+
platform.modifyTemplate({})
|
|
109
|
+
return platform.template
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function relativeWxRootAsset(pagePath: string, rootAsset: string): string {
|
|
113
|
+
const relativePath = path.posix.relative(path.posix.dirname(pagePath), rootAsset)
|
|
114
|
+
return relativePath.startsWith('.') ? relativePath : `./${relativePath}`
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function collectWxTemplateComponentConfig(bundle: WxBundle): WxTemplateComponentConfig {
|
|
118
|
+
const config: WxTemplateComponentConfig = {
|
|
119
|
+
includes: new Set([
|
|
120
|
+
'view',
|
|
121
|
+
'catch-view',
|
|
122
|
+
'static-view',
|
|
123
|
+
'pure-view',
|
|
124
|
+
'click-view',
|
|
125
|
+
'scroll-view',
|
|
126
|
+
'image',
|
|
127
|
+
'static-image',
|
|
128
|
+
'text',
|
|
129
|
+
'static-text'
|
|
130
|
+
]),
|
|
131
|
+
exclude: new Set(),
|
|
132
|
+
thirdPartyComponents: new Map(),
|
|
133
|
+
includeAll: false
|
|
134
|
+
}
|
|
135
|
+
const components = findBundleModule(bundle, taroWxComponentsPath)
|
|
136
|
+
for (const name of components?.renderedExports ?? []) config.includes.add(toDashed(name))
|
|
137
|
+
return config
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function findBundleModule(bundle: WxBundle, resolvedId: string): { renderedExports?: string[] } | undefined {
|
|
141
|
+
const normalizedResolvedId = normalizeModuleId(resolvedId)
|
|
142
|
+
for (const item of Object.values(bundle)) {
|
|
143
|
+
if (item.type !== 'chunk') continue
|
|
144
|
+
const found = Object.entries(item.modules ?? {}).find(([id]) => normalizeModuleId(id) === normalizedResolvedId)
|
|
145
|
+
if (found) return found[1]
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function toDashed(value: string): string {
|
|
150
|
+
return value.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase()
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function createWxComponentConfig(): JsonObject {
|
|
154
|
+
return {
|
|
155
|
+
component: true,
|
|
156
|
+
styleIsolation: 'apply-shared',
|
|
157
|
+
usingComponents: { comp: './comp' }
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function collectWxBundleStyles(bundle: WxBundle): string {
|
|
162
|
+
const styles: string[] = []
|
|
163
|
+
for (const [fileName, item] of Object.entries(bundle)) {
|
|
164
|
+
if (item.type !== 'asset' || !fileName.endsWith('.css')) continue
|
|
165
|
+
const source = typeof item.source === 'string' ? item.source : new TextDecoder().decode(item.source)
|
|
166
|
+
if (source) styles.push(source)
|
|
167
|
+
delete bundle[fileName]
|
|
168
|
+
}
|
|
169
|
+
return styles.join('\n')
|
|
170
|
+
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Top-level owner of one WX development lifecycle.
|
|
3
|
+
*
|
|
4
|
+
* It serializes every output write, connects DevEngine patches to the update transport, and coalesces conservative full
|
|
5
|
+
* rebuilds. No other module writes plugin-generated WX development files directly.
|
|
6
|
+
*/
|
|
7
|
+
import fs from 'node:fs/promises'
|
|
8
|
+
import path from 'node:path'
|
|
9
|
+
import colors from 'picocolors'
|
|
10
|
+
import type { ViteDevServer } from 'vite'
|
|
11
|
+
import type { BuildContext } from '../../../build-context.ts'
|
|
12
|
+
import { SerializedTaskQueue } from '../../../utils/async.ts'
|
|
13
|
+
import {
|
|
14
|
+
copyDirectoryIfExists,
|
|
15
|
+
copyFileOrRemove,
|
|
16
|
+
writeFileAtomically,
|
|
17
|
+
writeFilesAtomically
|
|
18
|
+
} from '../../../utils/filesystem.ts'
|
|
19
|
+
import { wxDevelopmentDirectory, wxUpdateControlFile, wxUpdateFile } from '../development-files.ts'
|
|
20
|
+
import { FullBuildScheduler } from './full-build-scheduler.ts'
|
|
21
|
+
import { transformWxCompatibleJavaScript, transformWxOutputChunks } from './js-utils.ts'
|
|
22
|
+
import { isWxFullBuildOutput, normalizeWxBundleStyles, setWxAppStyles, type WxOutputFile } from './output.ts'
|
|
23
|
+
import { WxUpdateTransport } from './update-transport.ts'
|
|
24
|
+
import { ViteBundledDevAdapter, type WxDevEngineUpdate } from './vite-bundled-dev-adapter.ts'
|
|
25
|
+
|
|
26
|
+
const maxRetainedDeltaCount = 1_000
|
|
27
|
+
const maxRetainedDeltaBytes = 16 * 1024 * 1024
|
|
28
|
+
const fullBuildDebounceDelay = 100
|
|
29
|
+
|
|
30
|
+
type WxDevServerContext = Pick<BuildContext, 'vite' | 'css'>
|
|
31
|
+
type SessionSnapshot = Readonly<{
|
|
32
|
+
lifecycle: 'open' | 'closed'
|
|
33
|
+
outputPhase: 'starting' | 'ready'
|
|
34
|
+
latestAppStyles: string
|
|
35
|
+
}>
|
|
36
|
+
|
|
37
|
+
/** Owns one WX bundled-development graph and all writes to its fixed output directory. */
|
|
38
|
+
export class WxDevelopmentSession {
|
|
39
|
+
private readonly adapter: ViteBundledDevAdapter
|
|
40
|
+
private readonly updateTransport: WxUpdateTransport
|
|
41
|
+
private readonly outDir: string
|
|
42
|
+
private readonly initialBundle = Promise.withResolvers<void>()
|
|
43
|
+
private readonly outputQueue: SerializedTaskQueue
|
|
44
|
+
private readonly fullBuildScheduler: FullBuildScheduler
|
|
45
|
+
private originalPrintUrls: (() => void) | undefined
|
|
46
|
+
private snapshot: SessionSnapshot = { lifecycle: 'open', outputPhase: 'starting', latestAppStyles: '' }
|
|
47
|
+
|
|
48
|
+
constructor(
|
|
49
|
+
private readonly context: WxDevServerContext,
|
|
50
|
+
private readonly server: ViteDevServer
|
|
51
|
+
) {
|
|
52
|
+
const config = context.vite
|
|
53
|
+
this.outDir = path.resolve(config.root, config.build.outDir)
|
|
54
|
+
this.outputQueue = new SerializedTaskQueue((error) => this.reportOutputError(error))
|
|
55
|
+
this.updateTransport = new WxUpdateTransport(
|
|
56
|
+
server,
|
|
57
|
+
() => this.requestFullBuild(),
|
|
58
|
+
(buildId, source) =>
|
|
59
|
+
this.outputQueue.enqueue(async () => {
|
|
60
|
+
// A full build queued before this write invalidates the old batch instead of letting it overwrite
|
|
61
|
+
// the new build's empty update.js.
|
|
62
|
+
if (!this.updateTransport.isCurrentBuild(buildId)) return
|
|
63
|
+
await writeFileAtomically(path.join(this.outDir, wxUpdateFile), source)
|
|
64
|
+
})
|
|
65
|
+
)
|
|
66
|
+
this.adapter = new ViteBundledDevAdapter(config, server, {
|
|
67
|
+
onOutput: (output) => this.handleBundleOutput(output),
|
|
68
|
+
onPatch: (files, output) => this.handlePatch(files, output),
|
|
69
|
+
onError: (message) => this.handleError(message),
|
|
70
|
+
waitForInitialBundle: () => this.waitForInitialBundle()
|
|
71
|
+
})
|
|
72
|
+
this.fullBuildScheduler = new FullBuildScheduler(
|
|
73
|
+
fullBuildDebounceDelay,
|
|
74
|
+
() => this.adapter.rebuild(),
|
|
75
|
+
(error) => this.reportRebuildError(error)
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private get config() {
|
|
80
|
+
return this.context.vite
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
install(): void {
|
|
84
|
+
this.updateTransport.install()
|
|
85
|
+
this.server.httpServer?.once('listening', this.handleHttpListening)
|
|
86
|
+
this.adapter.install()
|
|
87
|
+
this.originalPrintUrls = this.server.printUrls.bind(this.server)
|
|
88
|
+
this.server.printUrls = this.printUrls
|
|
89
|
+
if (this.config.publicDir) this.server.watcher.add(this.config.publicDir)
|
|
90
|
+
this.server.watcher.on('change', this.handleWatchedFile)
|
|
91
|
+
this.server.watcher.on('add', this.handleWatchedFile)
|
|
92
|
+
this.server.watcher.on('unlink', this.handleWatchedFile)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async close(): Promise<void> {
|
|
96
|
+
if (this.snapshot.lifecycle === 'closed') return
|
|
97
|
+
this.snapshot = { ...this.snapshot, lifecycle: 'closed' }
|
|
98
|
+
this.server.watcher.off('change', this.handleWatchedFile)
|
|
99
|
+
this.server.watcher.off('add', this.handleWatchedFile)
|
|
100
|
+
this.server.watcher.off('unlink', this.handleWatchedFile)
|
|
101
|
+
this.server.httpServer?.off('listening', this.handleHttpListening)
|
|
102
|
+
if (this.server.printUrls === this.printUrls && this.originalPrintUrls) {
|
|
103
|
+
this.server.printUrls = this.originalPrintUrls
|
|
104
|
+
}
|
|
105
|
+
this.updateTransport.close()
|
|
106
|
+
await this.fullBuildScheduler.close()
|
|
107
|
+
await this.outputQueue.waitForIdle()
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private readonly printUrls = (): void => {
|
|
111
|
+
this.originalPrintUrls?.()
|
|
112
|
+
const projectDirectory = relativeToViteConfig(this.outDir, this.config.configFile, this.config.root)
|
|
113
|
+
this.server.config.logger.info(
|
|
114
|
+
` ${colors.green('➜')} ${colors.bold('WeChat DevTools')}: ${colors.cyan(projectDirectory)}`
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private readonly handleHttpListening = (): void => {
|
|
119
|
+
this.outputQueue.enqueue(() =>
|
|
120
|
+
writeFileAtomically(path.join(this.outDir, wxUpdateControlFile), this.updateTransport.createControlSource())
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private readonly handleWatchedFile = (file: string): void => {
|
|
125
|
+
if (isFileInside(file, this.config.publicDir)) {
|
|
126
|
+
const destination = path.join(this.outDir, path.relative(this.config.publicDir, file))
|
|
127
|
+
this.outputQueue.enqueue(() => copyFileOrRemove(file, destination))
|
|
128
|
+
this.requestFullBuild()
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private handleBundleOutput(output: WxOutputFile[]): void {
|
|
133
|
+
const appStyles = normalizeWxBundleStyles(output)
|
|
134
|
+
if (appStyles !== undefined) this.snapshot = { ...this.snapshot, latestAppStyles: appStyles }
|
|
135
|
+
if (isWxFullBuildOutput(output) && this.snapshot.latestAppStyles) {
|
|
136
|
+
setWxAppStyles(output, this.snapshot.latestAppStyles)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (!isWxFullBuildOutput(output)) {
|
|
140
|
+
this.outputQueue.enqueue(async () => {
|
|
141
|
+
await transformWxOutputChunks(output)
|
|
142
|
+
await writeDevelopmentOutput(this.outDir, output)
|
|
143
|
+
})
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const buildId = this.updateTransport.createBuildId()
|
|
148
|
+
setDevelopmentAsset(output, wxUpdateControlFile, this.updateTransport.createControlSource(buildId))
|
|
149
|
+
setDevelopmentAsset(output, wxUpdateFile, 'void 0;\n')
|
|
150
|
+
this.outputQueue.enqueue(async () => {
|
|
151
|
+
await transformWxOutputChunks(output)
|
|
152
|
+
if (this.snapshot.outputPhase === 'starting') {
|
|
153
|
+
// The directory is plugin-owned; clearing it once removes stale files from previous protocol designs.
|
|
154
|
+
await fs.rm(path.join(this.outDir, wxDevelopmentDirectory), { recursive: true, force: true })
|
|
155
|
+
}
|
|
156
|
+
// Invalidate old HTTP reports before writing the new build epoch into the fixed DevTools directory.
|
|
157
|
+
this.updateTransport.commitFullBuild(buildId)
|
|
158
|
+
await writeDevelopmentOutput(this.outDir, output)
|
|
159
|
+
await copyDirectoryIfExists(this.config.publicDir, this.outDir)
|
|
160
|
+
const moduleCount = this.adapter.registerBundleModules(output)
|
|
161
|
+
this.snapshot = { ...this.snapshot, outputPhase: 'ready' }
|
|
162
|
+
this.initialBundle.resolve()
|
|
163
|
+
this.server.config.logger.info(
|
|
164
|
+
`[vite-plugin-taro] WX bundle ready (${moduleCount} modules, ${output.length} files)`
|
|
165
|
+
)
|
|
166
|
+
})
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private handlePatch(files: string[], output: WxDevEngineUpdate): boolean {
|
|
170
|
+
if (!isSafeJavaScriptPatch(files, output)) {
|
|
171
|
+
this.requestFullBuild()
|
|
172
|
+
return false
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
this.adapter.registerPatchModules(output.code)
|
|
176
|
+
this.outputQueue.enqueue(async () => {
|
|
177
|
+
const transformed = await this.context.css.transformWxClassNames(output.code, output.filename)
|
|
178
|
+
const compatibleCode = await transformWxCompatibleJavaScript(transformed.code, output.filename)
|
|
179
|
+
if (
|
|
180
|
+
this.updateTransport.retainedDeltaCount >= maxRetainedDeltaCount ||
|
|
181
|
+
this.updateTransport.retainedDeltaBytes + Buffer.byteLength(compatibleCode) >= maxRetainedDeltaBytes
|
|
182
|
+
) {
|
|
183
|
+
this.requestFullBuild()
|
|
184
|
+
return
|
|
185
|
+
}
|
|
186
|
+
this.updateTransport.addDelta(compatibleCode)
|
|
187
|
+
})
|
|
188
|
+
return true
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private handleError(message: string): void {
|
|
192
|
+
this.server.config.logger.error('[vite-plugin-taro] WX update failed', { error: new Error(message) })
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private requestFullBuild(): void {
|
|
196
|
+
this.fullBuildScheduler.request()
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private async waitForInitialBundle(): Promise<void> {
|
|
200
|
+
await this.initialBundle.promise
|
|
201
|
+
await this.outputQueue.waitForIdle()
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private reportOutputError(error: unknown): void {
|
|
205
|
+
const normalizedError = error instanceof Error ? error : new Error(String(error))
|
|
206
|
+
this.server.config.logger.error(
|
|
207
|
+
`[vite-plugin-taro] WX development output failed: ${normalizedError.stack ?? normalizedError.message}`
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private reportRebuildError(error: unknown): void {
|
|
212
|
+
const normalizedError = error instanceof Error ? error : new Error(String(error))
|
|
213
|
+
this.server.config.logger.error('[vite-plugin-taro] WX rebuild failed', { error: normalizedError })
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function writeDevelopmentOutput(outDir: string, output: WxOutputFile[]): Promise<void> {
|
|
218
|
+
return writeFilesAtomically(
|
|
219
|
+
output.map((item) => ({
|
|
220
|
+
file: path.join(outDir, item.fileName),
|
|
221
|
+
source: item.type === 'chunk' ? item.code : item.source
|
|
222
|
+
}))
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function setDevelopmentAsset(output: WxOutputFile[], fileName: string, source: string): void {
|
|
227
|
+
const index = output.findIndex((item) => item.type === 'asset' && item.fileName === fileName)
|
|
228
|
+
const asset: WxOutputFile = { type: 'asset', fileName, source }
|
|
229
|
+
if (index >= 0) output[index] = asset
|
|
230
|
+
else output.push(asset)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function relativeToViteConfig(outDir: string, configFile: string | undefined, root: string): string {
|
|
234
|
+
const configDirectory = configFile ? path.dirname(configFile) : root
|
|
235
|
+
const relativePath = path.relative(configDirectory, outDir).replaceAll('\\', '/')
|
|
236
|
+
if (!relativePath) return '.'
|
|
237
|
+
return relativePath.startsWith('.') ? relativePath : `./${relativePath}`
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function isFileInside(file: string, directory: string): boolean {
|
|
241
|
+
if (!directory) return false
|
|
242
|
+
const relative = path.relative(directory, file)
|
|
243
|
+
return relative !== '' && !relative.startsWith('..') && !path.isAbsolute(relative)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function isSafeJavaScriptPatch(
|
|
247
|
+
files: string[],
|
|
248
|
+
output: WxDevEngineUpdate
|
|
249
|
+
): output is Extract<WxDevEngineUpdate, { type: 'Patch' }> {
|
|
250
|
+
if (output.type !== 'Patch' || output.hmrBoundaries.length === 0) return false
|
|
251
|
+
if (output.code.includes('__vite__updateStyle') || output.code.includes('.updateStyle(')) return false
|
|
252
|
+
return files.every((file) => /\.[cm]?[jt]sx?$/.test(file))
|
|
253
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/** Full-build scheduling owned by one WX development session. */
|
|
2
|
+
|
|
3
|
+
type FullBuildPhase = 'idle' | 'scheduled' | 'building' | 'building-with-pending-request' | 'closed'
|
|
4
|
+
|
|
5
|
+
/** Coalesces trailing-edge requests while guaranteeing one full build at a time. */
|
|
6
|
+
export class FullBuildScheduler {
|
|
7
|
+
private readonly delay: number
|
|
8
|
+
private readonly runBuild: () => Promise<void>
|
|
9
|
+
private readonly reportError: (error: unknown) => void
|
|
10
|
+
private phase: FullBuildPhase = 'idle'
|
|
11
|
+
private timer: NodeJS.Timeout | undefined
|
|
12
|
+
private work: Promise<void> | undefined
|
|
13
|
+
|
|
14
|
+
constructor(delay: number, runBuild: () => Promise<void>, reportError: (error: unknown) => void) {
|
|
15
|
+
this.delay = delay
|
|
16
|
+
this.runBuild = runBuild
|
|
17
|
+
this.reportError = reportError
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
request(): void {
|
|
21
|
+
switch (this.phase) {
|
|
22
|
+
case 'closed':
|
|
23
|
+
return
|
|
24
|
+
case 'building':
|
|
25
|
+
this.phase = 'building-with-pending-request'
|
|
26
|
+
return
|
|
27
|
+
case 'building-with-pending-request':
|
|
28
|
+
return
|
|
29
|
+
case 'idle':
|
|
30
|
+
case 'scheduled':
|
|
31
|
+
if (this.timer) clearTimeout(this.timer)
|
|
32
|
+
this.phase = 'scheduled'
|
|
33
|
+
this.timer = setTimeout(() => this.start(), this.delay)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async close(): Promise<void> {
|
|
38
|
+
if (this.phase === 'closed') {
|
|
39
|
+
await this.work
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
this.phase = 'closed'
|
|
43
|
+
if (this.timer) clearTimeout(this.timer)
|
|
44
|
+
this.timer = undefined
|
|
45
|
+
await this.work
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private start(): void {
|
|
49
|
+
if (this.phase !== 'scheduled') return
|
|
50
|
+
this.timer = undefined
|
|
51
|
+
this.phase = 'building'
|
|
52
|
+
this.work = this.runBuild()
|
|
53
|
+
.catch(this.reportError)
|
|
54
|
+
.finally(() => {
|
|
55
|
+
this.work = undefined
|
|
56
|
+
if (this.phase === 'closed') return
|
|
57
|
+
const repeat = this.phase === 'building-with-pending-request'
|
|
58
|
+
this.phase = 'idle'
|
|
59
|
+
if (repeat) this.request()
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { transformWithOxc } from 'vite'
|
|
2
|
+
import type { WxOutputFile } from './output.ts'
|
|
3
|
+
|
|
4
|
+
const wxJavaScriptTarget = 'es2018'
|
|
5
|
+
|
|
6
|
+
/** Lowers generated JavaScript syntax that WeChat's upload parser does not accept. */
|
|
7
|
+
export async function transformWxCompatibleJavaScript(code: string, filename: string): Promise<string> {
|
|
8
|
+
const result = await transformWithOxc(code, filename, {
|
|
9
|
+
lang: 'js',
|
|
10
|
+
target: wxJavaScriptTarget,
|
|
11
|
+
sourcemap: false,
|
|
12
|
+
assumptions: {
|
|
13
|
+
// Avoid external Oxc helpers when lowering public class fields in already-bundled chunks.
|
|
14
|
+
setPublicClassFields: true
|
|
15
|
+
}
|
|
16
|
+
})
|
|
17
|
+
return result.code
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Applies the compatibility transform to every JavaScript chunk in a DevEngine output batch. */
|
|
21
|
+
export async function transformWxOutputChunks(output: WxOutputFile[]): Promise<void> {
|
|
22
|
+
await Promise.all(
|
|
23
|
+
output.map(async (item, index) => {
|
|
24
|
+
if (item.type !== 'chunk') return
|
|
25
|
+
output[index] = {
|
|
26
|
+
type: 'chunk',
|
|
27
|
+
fileName: item.fileName,
|
|
28
|
+
modules: item.modules,
|
|
29
|
+
code: await transformWxCompatibleJavaScript(item.code, item.fileName)
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
)
|
|
33
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
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
|
+
|
|
6
|
+
/** File shape emitted by Vite's private bundled-development engine. */
|
|
7
|
+
export type WxOutputFile =
|
|
8
|
+
| {
|
|
9
|
+
type: 'chunk'
|
|
10
|
+
fileName: string
|
|
11
|
+
code: string
|
|
12
|
+
modules?: Record<string, unknown>
|
|
13
|
+
}
|
|
14
|
+
| {
|
|
15
|
+
type: 'asset'
|
|
16
|
+
fileName: string
|
|
17
|
+
source: string | Uint8Array
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Extracts Vite's development CSS payloads and materializes them as app.wxss. */
|
|
21
|
+
export function normalizeWxBundleStyles(output: WxOutputFile[]): string | undefined {
|
|
22
|
+
const styles: string[] = []
|
|
23
|
+
for (let index = output.length - 1; index >= 0; index--) {
|
|
24
|
+
const item = output[index]
|
|
25
|
+
if (!item) continue
|
|
26
|
+
if (item.type === 'asset' && item.fileName.endsWith('.css')) {
|
|
27
|
+
styles.unshift(typeof item.source === 'string' ? item.source : new TextDecoder().decode(item.source))
|
|
28
|
+
output.splice(index, 1)
|
|
29
|
+
} else if (item.type === 'chunk') {
|
|
30
|
+
styles.push(...collectEmbeddedStyles(item.code))
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (styles.length === 0) return
|
|
34
|
+
const source = styles.join('\n')
|
|
35
|
+
setWxAppStyles(output, source)
|
|
36
|
+
return source
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function setWxAppStyles(output: WxOutputFile[], source: string): void {
|
|
40
|
+
const index = output.findIndex((item) => item.type === 'asset' && item.fileName === 'app.wxss')
|
|
41
|
+
const appStyle: WxOutputFile = { type: 'asset', fileName: 'app.wxss', source }
|
|
42
|
+
if (index >= 0) output[index] = appStyle
|
|
43
|
+
else output.push(appStyle)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function isWxFullBuildOutput(output: WxOutputFile[]): boolean {
|
|
47
|
+
return output.some((item) => item.fileName === 'app.js')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function collectEmbeddedStyles(code: string): string[] {
|
|
51
|
+
const styles: string[] = []
|
|
52
|
+
for (const match of code.matchAll(/__vite__css(?:\$\d+)?\s*=\s*("(?:\\.|[^"\\])*");/g)) {
|
|
53
|
+
if (match[1]) styles.push(JSON.parse(match[1]) as string)
|
|
54
|
+
}
|
|
55
|
+
return styles
|
|
56
|
+
}
|
|
@@ -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
|
+
`
|