@taro-hmr-compile/plugin-webpack 0.0.1-alpha.8 → 0.0.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.
Files changed (46) hide show
  1. package/README.md +101 -0
  2. package/dist/compile/hmr-compiler-loader.js +44 -7
  3. package/dist/compile/hmr-compiler-loader.js.map +1 -1
  4. package/dist/compile/hmr-compiler-plugin.d.ts +7 -7
  5. package/dist/compile/hmr-compiler-plugin.js +2 -29
  6. package/dist/compile/hmr-compiler-plugin.js.map +1 -1
  7. package/dist/compile/hmr-output-plugin.d.ts +28 -0
  8. package/dist/compile/hmr-output-plugin.js +145 -0
  9. package/dist/compile/hmr-output-plugin.js.map +1 -0
  10. package/dist/compile/hmr-style-plugin.d.ts +25 -0
  11. package/dist/compile/hmr-style-plugin.js +53 -0
  12. package/dist/compile/hmr-style-plugin.js.map +1 -0
  13. package/dist/compile/lazy-compiler-loader.d.ts +2 -0
  14. package/dist/compile/lazy-compiler-loader.js +17 -5
  15. package/dist/compile/lazy-compiler-loader.js.map +1 -1
  16. package/dist/constants.d.ts +1 -0
  17. package/dist/constants.js +2 -1
  18. package/dist/constants.js.map +1 -1
  19. package/dist/index.d.ts +2 -0
  20. package/dist/index.js +18 -6
  21. package/dist/index.js.map +1 -1
  22. package/dist/patch/hmr-patch-plugin.d.ts +6 -0
  23. package/dist/patch/hmr-patch-plugin.js +53 -4
  24. package/dist/patch/hmr-patch-plugin.js.map +1 -1
  25. package/dist/patch/hmr-patch-processor.d.ts +2 -1
  26. package/dist/patch/hmr-patch-processor.js +39 -22
  27. package/dist/patch/hmr-patch-processor.js.map +1 -1
  28. package/dist/patch/hmr-patch-runtime-plugin.js +1 -1
  29. package/dist/patch/types.d.ts +1 -0
  30. package/dist/patch/utils.d.ts +2 -0
  31. package/dist/patch/utils.js +11 -5
  32. package/dist/patch/utils.js.map +1 -1
  33. package/package.json +4 -2
  34. package/src/compile/hmr-compiler-loader.ts +43 -7
  35. package/src/compile/hmr-compiler-plugin.ts +9 -34
  36. package/src/compile/hmr-output-plugin.ts +158 -0
  37. package/src/compile/hmr-style-plugin.ts +70 -0
  38. package/src/compile/lazy-compiler-loader.ts +20 -5
  39. package/src/constants.ts +2 -0
  40. package/src/index.ts +23 -9
  41. package/src/patch/hmr-patch-plugin.ts +53 -5
  42. package/src/patch/hmr-patch-processor.ts +48 -28
  43. package/src/patch/hmr-patch-runtime-plugin.ts +1 -1
  44. package/src/patch/types.ts +1 -0
  45. package/src/patch/utils.ts +10 -5
  46. package/src/types/taro-service.d.ts +15 -0
@@ -1,6 +1,7 @@
1
1
  import type {Compilation, Compiler} from 'webpack'
2
2
  import {RawSource} from 'webpack-sources'
3
- import {HMR_PATCH_ASSET} from '../constants'
3
+ import path from 'node:path'
4
+ import {HMR_PATCH_ASSET, HMR_PATCH_STYLE_ASSET} from '../constants'
4
5
  import {HmrCompilerPlugin} from '../compile/hmr-compiler-plugin'
5
6
  import {HmrPatchRuntimePlugin} from './hmr-patch-runtime-plugin'
6
7
  import {HmrPatchProcessor, HmrPatchProcessorOptions} from "./hmr-patch-processor";
@@ -10,6 +11,7 @@ const HOT_UPDATE_MANIFEST_SUFFIX = '.hot-update.json'
10
11
  const HOT_UPDATE_SCRIPT_SUFFIX = '.hot-update.js'
11
12
  const STYLE_ASSET_PATTERN = /\.(?:css|wxss|acss|ttss|qss|jxss)(?:\.map)?$/i
12
13
  const TEMPLATE_RUNTIME_ASSET_PATTERN = /(?:^|\/)(?:base\.wxml|utils\.wxs)$/i
14
+ const STYLE_ENTRY_ASSET = 'app.wxss'
13
15
 
14
16
  type HmrPatchPluginOptions = Partial<HmrPatchProcessorOptions>
15
17
 
@@ -25,6 +27,8 @@ export class HmrPatchPlugin {
25
27
 
26
28
  private readonly patchProcessor: HmrPatchProcessor
27
29
 
30
+ private readonly preservedAssets = new Set([HMR_PATCH_ASSET, STYLE_ENTRY_ASSET, HMR_PATCH_STYLE_ASSET])
31
+
28
32
  private readonly emittedAssetSources = new Map<string, string>()
29
33
 
30
34
  constructor(options: HmrPatchPluginOptions) {
@@ -35,13 +39,18 @@ export class HmrPatchPlugin {
35
39
  new HmrPatchRuntimePlugin().apply(compiler)
36
40
 
37
41
  compiler.hooks.compilation.tap(PLUGIN_NAME, compilation => {
38
- HmrCompilerPlugin.getCompilationHooks(compilation).chunkModulesRendered.tap(PLUGIN_NAME, (source, isBootstrapChunk) => {
39
- this.patchProcessor.processChunkModules({compilation, source, isBootstrapChunk})
42
+ HmrCompilerPlugin.getCompilationHooks(compilation).chunkModulesRendered.tap(PLUGIN_NAME, options => {
43
+ this.patchProcessor.processChunkModules({compilation, ...options})
40
44
  })
41
45
 
42
46
  // 等 Webpack 生成完整的 hot-update assets 后,再统一转换成固定的 patch.js。
43
47
  const stage = compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_REPORT
44
- compilation.hooks.processAssets.tap({name: PLUGIN_NAME, stage}, () => this.processAssets(compilation))
48
+ compilation.hooks.processAssets.tap({name: PLUGIN_NAME, stage}, () => {
49
+ if (compilation.errors.length > 0 || !compilation.hash) return
50
+ this.processStyleAssets(compilation)
51
+ this.processAssets(compilation)
52
+ })
53
+ this.removeHotUpdateAssets(compilation)
45
54
  })
46
55
 
47
56
  compiler.hooks.afterEmit.tap(PLUGIN_NAME, compilation => {
@@ -82,10 +91,40 @@ export class HmrPatchPlugin {
82
91
  this.previousHash = toHash
83
92
  }
84
93
 
94
+ /** 将 Webpack 汇总后的 App 样式放入固定 patch 文件,App 入口只保留稳定引用。 */
95
+ private processStyleAssets(compilation: Compilation) {
96
+ const entryAsset = compilation.getAsset(STYLE_ENTRY_ASSET)
97
+ const source = entryAsset?.source.source().toString()
98
+ if (!source) return
99
+
100
+ const importPath = path.posix.relative(path.posix.dirname(STYLE_ENTRY_ASSET), HMR_PATCH_STYLE_ASSET)
101
+ const patchImport = importPath.startsWith('.') ? importPath : `./${importPath}`
102
+ const alreadyPatched = source.trim() === `@import "${patchImport}";` || source.trim() === `@import '${patchImport}';`
103
+
104
+ if (!alreadyPatched) {
105
+ const patchDirectory = path.posix.dirname(HMR_PATCH_STYLE_ASSET)
106
+ const sourceDirectory = path.posix.dirname(STYLE_ENTRY_ASSET)
107
+ const patchSource = source.replace(/(@import\s+)(["'])([^"']+)\2/g, (match, prefix: string, quote: string, request: string) => {
108
+ if (/^(?:[a-z]+:|\/|#)/i.test(request)) return match
109
+ const resolved = path.posix.normalize(path.posix.join(sourceDirectory, request))
110
+ const relative = path.posix.relative(patchDirectory, resolved)
111
+ return `${prefix}${quote}${relative.startsWith('.') ? relative : `./${relative}`}${quote}`
112
+ })
113
+ this.emitAsset(compilation, HMR_PATCH_STYLE_ASSET, new RawSource(patchSource))
114
+ }
115
+
116
+ this.emitAsset(compilation, STYLE_ENTRY_ASSET, new RawSource(`@import ${JSON.stringify(patchImport)};\n`))
117
+ }
118
+
119
+ private emitAsset(compilation: Compilation, assetName: string, source: RawSource) {
120
+ if (compilation.getAsset(assetName)) compilation.updateAsset(assetName, source)
121
+ else compilation.emitAsset(assetName, source)
122
+ }
123
+
85
124
  /** 热更新转换完成后,保留固定 patch 与发生变化的模板、平台样式资产。 */
86
125
  private removeSupersededAssets(compilation: Compilation) {
87
126
  compilation.getAssets().forEach(asset => {
88
- if (asset.name === HMR_PATCH_ASSET) return
127
+ if (this.preservedAssets.has(asset.name)) return
89
128
  if (!isPassthroughAsset(asset.name)) return compilation.deleteAsset(asset.name)
90
129
 
91
130
  const source = asset.source.source().toString()
@@ -108,4 +147,13 @@ export class HmrPatchPlugin {
108
147
  this.emittedAssetSources.clear()
109
148
  this.patchProcessor.reset()
110
149
  }
150
+ /** 补丁已从 hot-update assets 生成后,在 Webpack 写盘前从本次 compilation 移除它们。 */
151
+ private removeHotUpdateAssets(compilation: Compilation) {
152
+ compilation.getAssets().forEach(asset => {
153
+ if (asset.name.endsWith(HOT_UPDATE_MANIFEST_SUFFIX) || asset.name.endsWith(HOT_UPDATE_SCRIPT_SUFFIX)) {
154
+ compilation.deleteAsset(asset.name)
155
+ }
156
+ })
157
+ }
158
+
111
159
  }
@@ -1,23 +1,18 @@
1
- import {type Compilation,} from "webpack";
2
- import generator from '@babel/generator'
1
+ import {type Chunk, type Compilation,} from "webpack";
3
2
  import {
4
3
  parseHotUpdateAssets,
5
4
  ParseHotUpdateAssetsOptions,
6
5
  parseChunkAsset, mergeUpdatePatches, generatePatchAssetSource,
7
6
  } from "./utils";
8
- import {createHash} from "crypto";
7
+ import isNil from 'lodash/isNil'
9
8
  import {ModuleFactory, UpdatePatchSource} from "./types";
10
9
  import {DEFAULT_HMR_PATCH_HISTORY_LIMIT} from "../constants";
11
10
 
12
- interface ModuleHash {
13
- moduleId: string;
14
- factoryHash: string;
15
- }
16
-
17
11
  interface ProcessChunkModulesOptions {
18
12
  source: string
19
13
  compilation: Compilation,
20
- isBootstrapChunk: boolean
14
+ isBootstrapChunk: boolean,
15
+ chunk: Chunk
21
16
  }
22
17
 
23
18
  export interface HmrPatchProcessorOptions {
@@ -31,7 +26,14 @@ interface ProcessUpdateOptions extends ParseHotUpdateAssetsOptions {
31
26
 
32
27
  type BootstrapFactories = Map<string, ModuleFactory>
33
28
 
34
- type ModuleHashesByFullHash = Map<string, Map<string, ModuleHash>>
29
+ type ModuleHashes = Map<string, Set<string>>
30
+
31
+ type ModuleHashesByFullHash = Map<string, ModuleHashes>
32
+
33
+ const areModuleHashesEqual = (left: Set<string> | undefined, right: Set<string> | undefined): boolean => {
34
+ if (!left || !right || left.size !== right.size) return false
35
+ return Array.from(left).every(hash => right.has(hash))
36
+ }
35
37
 
36
38
  export class HmrPatchProcessor {
37
39
  private readonly bootstrapFactories: BootstrapFactories = new Map()
@@ -62,9 +64,9 @@ export class HmrPatchProcessor {
62
64
 
63
65
  const changedModuleIds = new Set<string>()
64
66
  moduleIds.forEach(moduleId => {
65
- const fromFactoryHash = fromModuleHashes.get(moduleId)?.factoryHash
66
- const toFactoryHash = toModuleHashes.get(moduleId)?.factoryHash
67
- if (fromFactoryHash !== toFactoryHash) changedModuleIds.add(moduleId)
67
+ const fromModuleHash = fromModuleHashes.get(moduleId)
68
+ const toModuleHash = toModuleHashes.get(moduleId)
69
+ if (!areModuleHashesEqual(fromModuleHash, toModuleHash)) changedModuleIds.add(moduleId)
68
70
  })
69
71
 
70
72
  return changedModuleIds
@@ -87,7 +89,9 @@ export class HmrPatchProcessor {
87
89
  return Array.from(tempPatchInfo.removedModules.values()).filter(item => changedModuleIds.has(item))
88
90
  })()
89
91
 
90
- this.recentUpdatePatches.push({type: 'update', moduleFactories, fromHash, toHash, removedModules})
92
+ const {runtimeModules} = tempPatchInfo
93
+
94
+ this.recentUpdatePatches.push({type: 'update', moduleFactories, runtimeModules, fromHash, toHash, removedModules})
91
95
 
92
96
  const patchHistoryLimit = Math.max(1, this.opts.patchHistoryLimit ?? DEFAULT_HMR_PATCH_HISTORY_LIMIT)
93
97
 
@@ -103,28 +107,44 @@ export class HmrPatchProcessor {
103
107
  }
104
108
 
105
109
  processChunkModules(opt: ProcessChunkModulesOptions) {
106
- const {hash: fullHash} = opt.compilation
107
- const modules = parseChunkAsset(opt.source)
110
+ const {chunk, compilation, isBootstrapChunk, source} = opt
111
+ const {hash: fullHash} = compilation
112
+ const modules = parseChunkAsset(source)
108
113
  modules.forEach(moduleFactory => {
109
114
  const {moduleId, factory} = moduleFactory
110
115
 
111
- if (opt.isBootstrapChunk) {
112
- const compilationFactories = this.bootstrapFactoriesByCompilation.get(opt.compilation) ?? new Map()
116
+ if (isBootstrapChunk) {
117
+ const compilationFactories = this.bootstrapFactoriesByCompilation.get(compilation) ?? new Map()
113
118
  compilationFactories.set(moduleId, {moduleId, factory})
114
- this.bootstrapFactoriesByCompilation.set(opt.compilation, compilationFactories)
119
+ this.bootstrapFactoriesByCompilation.set(compilation, compilationFactories)
115
120
  }
121
+ })
122
+
123
+ if (!fullHash) return
124
+
125
+ const moduleHashes = (() => {
126
+ const hasCurFullHashModuleHashMap = this.moduleHashesByFullHash.has(fullHash)
127
+ if (!hasCurFullHashModuleHashMap) this.moduleHashesByFullHash.set(fullHash, new Map())
128
+ return this.moduleHashesByFullHash.get(fullHash)!
129
+ })()
130
+
131
+ const { chunkGraph, codeGenerationResults } = compilation
132
+
133
+ Array.from(chunkGraph.getChunkModulesIterable(chunk)).forEach(module => {
134
+ const moduleId = chunkGraph.getModuleId(module)
135
+
136
+ if (isNil(moduleId)) return
116
137
 
117
- if (!fullHash) return
138
+ const normalizedModuleId = String(moduleId)
118
139
 
119
- const moduleHashes = (() => {
120
- const hasCurFullHashModuleHashMap = this.moduleHashesByFullHash.has(fullHash)
121
- if (!hasCurFullHashModuleHashMap) this.moduleHashesByFullHash.set(fullHash, new Map())
122
- return this.moduleHashesByFullHash.get(fullHash)!
123
- })()
140
+ // 获取模块在当前 chunk.runtime 下的 Hash,优先从 codeGenerationResults 获取,如果没有对应的代码生成结果,则回退到 chunkGraph 获取
141
+ const moduleHash = codeGenerationResults?.has(module, chunk.runtime)
142
+ ? codeGenerationResults.getHash(module, chunk.runtime)
143
+ : chunkGraph.getModuleHash(module, chunk.runtime)
124
144
 
125
- const {code: source} = generator(factory, {compact: true, comments: false, minified: true,})
126
- const factoryHash = createHash('sha256').update(source).digest('hex')
127
- moduleHashes.set(moduleId, {moduleId, factoryHash})
145
+ const hashes = moduleHashes.get(normalizedModuleId) ?? new Set<string>()
146
+ hashes.add(moduleHash)
147
+ moduleHashes.set(normalizedModuleId, hashes)
128
148
  })
129
149
  }
130
150
 
@@ -64,7 +64,7 @@ ${webpack.RuntimeGlobals.require}.miniHmr.preparePatch = function(patch, applyHa
64
64
  currentUpdateChunks = {};
65
65
  currentUpdate = Object.assign({}, patch.modules);
66
66
  currentUpdateRemovedChunks = [];
67
- currentUpdateRuntime = [];
67
+ currentUpdateRuntime = patch.runtimeModules ? patch.runtimeModules.slice() : [];
68
68
 
69
69
  if (patch.removedModules) {
70
70
  patch.removedModules.forEach(function(moduleId) {
@@ -14,6 +14,7 @@ export interface UpdatePatchSource {
14
14
  fromHash: string
15
15
  toHash: string
16
16
  moduleFactories: ModuleFactory[]
17
+ runtimeModules: Expression[]
17
18
  removedModules: string[]
18
19
  }
19
20
 
@@ -17,7 +17,7 @@ const parseModuleFactories = (expression: ObjectExpression) => {
17
17
 
18
18
  const moduleFactories = new Map<string, ModuleFactory>()
19
19
 
20
- if (!t.isObjectExpression(expression)) return moduleFactories.values()
20
+ if (!t.isObjectExpression(expression)) return Array.from(moduleFactories.values())
21
21
 
22
22
  expression.properties.forEach(property => {
23
23
  if (!t.isObjectProperty(property) || !t.isExpression(property.value)) throw new Error('[taro-hmr-compile] HMR 模块对象包含无法识别的属性')
@@ -33,7 +33,7 @@ const parseModuleFactories = (expression: ObjectExpression) => {
33
33
  moduleFactories.set(moduleId, {factory, moduleId})
34
34
  })
35
35
 
36
- return moduleFactories.values()
36
+ return Array.from(moduleFactories.values())
37
37
  }
38
38
 
39
39
 
@@ -50,6 +50,7 @@ export const parseHotUpdateAssets = (opt: ParseHotUpdateAssetsOptions) => {
50
50
  const {manifests, scripts} = opt
51
51
 
52
52
  const moduleFactories = new Map<string, ModuleFactory>()
53
+ const runtimeModules: t.Expression[] = []
53
54
 
54
55
  scripts.forEach(asset => {
55
56
 
@@ -63,11 +64,12 @@ export const parseHotUpdateAssets = (opt: ParseHotUpdateAssetsOptions) => {
63
64
 
64
65
  if (!t.isStringLiteral(callee.property) || !callee.property.value.startsWith('webpackHotUpdate')) return
65
66
 
66
- const [_, modules] = nodePath.node.arguments
67
+ const [, modules, runtime] = nodePath.node.arguments
67
68
 
68
69
  if (!t.isObjectExpression(modules)) return
69
70
 
70
71
  parseModuleFactories(modules).forEach(module => moduleFactories.set(module.moduleId, module))
72
+ if (t.isExpression(runtime)) runtimeModules.push(runtime)
71
73
  },
72
74
  })
73
75
  })
@@ -83,7 +85,7 @@ export const parseHotUpdateAssets = (opt: ParseHotUpdateAssetsOptions) => {
83
85
  tempRemoveModules.forEach(removedModule => removedModules.add(String(removedModule)))
84
86
  }, [])
85
87
 
86
- return {moduleFactories, removedModules}
88
+ return {moduleFactories, runtimeModules, removedModules}
87
89
  }
88
90
 
89
91
  /**
@@ -126,7 +128,8 @@ export const mergeUpdatePatches = (basePatch: UpdatePatchSource, nextPatch: Upda
126
128
  type: 'update',
127
129
  fromHash: basePatch.fromHash,
128
130
  toHash: nextPatch.toHash,
129
- moduleFactories: [...moduleFactories.values()],
131
+ moduleFactories: Array.from(moduleFactories.values()),
132
+ runtimeModules: [...basePatch.runtimeModules, ...nextPatch.runtimeModules],
130
133
  removedModules: [...removedModules],
131
134
  }
132
135
  }
@@ -147,6 +150,7 @@ export const generatePatchAssetSource = (opt: generatePatchAssetSourceOpts): str
147
150
  fromHash: "FROM_HASH",
148
151
  toHash: "TO_HASH",
149
152
  modules: MODULES,
153
+ runtimeModules: RUNTIME_MODULES,
150
154
  removedModules: REMOVED_MODULES
151
155
  })`)
152
156
  const buildPatchAsset = template.program('module.exports = PATCHES;')
@@ -165,6 +169,7 @@ export const generatePatchAssetSource = (opt: generatePatchAssetSourceOpts): str
165
169
  FROM_HASH: patch.fromHash,
166
170
  TO_HASH: patch.toHash,
167
171
  MODULES: t.objectExpression(updateModules),
172
+ RUNTIME_MODULES: t.arrayExpression(patch.runtimeModules),
168
173
  REMOVED_MODULES: t.arrayExpression(patch.removedModules.map(moduleId => t.stringLiteral(moduleId))),
169
174
  })
170
175
  }),
@@ -0,0 +1,15 @@
1
+ import type { IProjectConfig } from '@tarojs/taro/types/compile'
2
+ import type { IComponentConfig } from '@tarojs/taro/types/compile/hooks'
3
+
4
+ declare module '@tarojs/service' {
5
+ interface IPluginContext {
6
+ modifyComponentConfig: (
7
+ callback: (args: {
8
+ componentConfig: IComponentConfig
9
+ config: IProjectConfig
10
+ }) => void,
11
+ ) => void
12
+ }
13
+ }
14
+
15
+ export {}