@taro-hmr-compile/plugin-webpack 0.0.1-alpha.7 → 0.0.1-alpha.8

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.
@@ -0,0 +1,173 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.generatePatchAssetSource = exports.mergeUpdatePatches = exports.parseChunkAsset = exports.parseHotUpdateAssets = void 0;
40
+ const t = __importStar(require("@babel/types"));
41
+ const parser_1 = require("@babel/parser");
42
+ const traverse_1 = __importDefault(require("@babel/traverse"));
43
+ const template_1 = __importDefault(require("@babel/template"));
44
+ const generator_1 = __importDefault(require("@babel/generator"));
45
+ /**
46
+ * 解析模块 Factory
47
+ * @param expression
48
+ */
49
+ const parseModuleFactories = (expression) => {
50
+ const moduleFactories = new Map();
51
+ if (!t.isObjectExpression(expression))
52
+ return moduleFactories.values();
53
+ expression.properties.forEach(property => {
54
+ if (!t.isObjectProperty(property) || !t.isExpression(property.value))
55
+ throw new Error('[taro-hmr-compile] HMR 模块对象包含无法识别的属性');
56
+ const { key, value: factory } = property;
57
+ const moduleId = (() => {
58
+ if (t.isStringLiteral(key) || t.isNumericLiteral(key))
59
+ return String(key.value);
60
+ if (t.isIdentifier(key) && !property.computed)
61
+ return key.name;
62
+ throw new Error('[taro-hmr-compile] Chunk 模块对象包含无法识别的模块 ID');
63
+ })();
64
+ moduleFactories.set(moduleId, { factory, moduleId });
65
+ });
66
+ return moduleFactories.values();
67
+ };
68
+ /**
69
+ * 解析热更新产物
70
+ * @param opt
71
+ */
72
+ const parseHotUpdateAssets = (opt) => {
73
+ const { manifests, scripts } = opt;
74
+ const moduleFactories = new Map();
75
+ scripts.forEach(asset => {
76
+ const ast = (0, parser_1.parse)(asset.source.source().toString(), { sourceType: 'script' });
77
+ (0, traverse_1.default)(ast, {
78
+ CallExpression(nodePath) {
79
+ const { callee } = nodePath.node;
80
+ if (!t.isMemberExpression(callee) || !t.isIdentifier(callee.object, { name: 'wx' }))
81
+ return;
82
+ if (!t.isStringLiteral(callee.property) || !callee.property.value.startsWith('webpackHotUpdate'))
83
+ return;
84
+ const [_, modules] = nodePath.node.arguments;
85
+ if (!t.isObjectExpression(modules))
86
+ return;
87
+ parseModuleFactories(modules).forEach(module => moduleFactories.set(module.moduleId, module));
88
+ },
89
+ });
90
+ });
91
+ const removedModules = new Set();
92
+ manifests.forEach((module) => {
93
+ const manifest = JSON.parse(module.source.source().toString());
94
+ const { m: tempRemoveModules = [] } = manifest;
95
+ tempRemoveModules.forEach(removedModule => removedModules.add(String(removedModule)));
96
+ }, []);
97
+ return { moduleFactories, removedModules };
98
+ };
99
+ exports.parseHotUpdateAssets = parseHotUpdateAssets;
100
+ /**
101
+ * 解析 chunk 产物
102
+ * @param source
103
+ */
104
+ const parseChunkAsset = (source) => {
105
+ const ast = (0, parser_1.parse)(`(${source})`, { sourceType: 'script' });
106
+ const [statement] = ast.program.body;
107
+ if (!t.isExpressionStatement(statement) || !t.isObjectExpression(statement.expression))
108
+ throw new Error('[taro-hmr-compile] Chunk 模块段不是对象表达式');
109
+ const moduleFactories = new Map();
110
+ parseModuleFactories(statement.expression).forEach(module => moduleFactories.set(module.moduleId, module));
111
+ return moduleFactories;
112
+ };
113
+ exports.parseChunkAsset = parseChunkAsset;
114
+ const mergeUpdatePatches = (basePatch, nextPatch) => {
115
+ if (basePatch.toHash !== nextPatch.fromHash)
116
+ throw new Error(`[taro-hmr-compile] 无法合并不连续的 HMR patch:${basePatch.toHash} !== ${nextPatch.fromHash}`);
117
+ const moduleFactories = new Map(basePatch.moduleFactories.map(moduleFactory => [moduleFactory.moduleId, moduleFactory]));
118
+ const removedModules = new Set(basePatch.removedModules);
119
+ nextPatch.moduleFactories.forEach(moduleFactory => {
120
+ const { moduleId } = moduleFactory;
121
+ moduleFactories.set(moduleId, moduleFactory);
122
+ removedModules.delete(moduleId);
123
+ });
124
+ nextPatch.removedModules.forEach(moduleId => {
125
+ moduleFactories.delete(moduleId);
126
+ removedModules.add(moduleId);
127
+ });
128
+ return {
129
+ type: 'update',
130
+ fromHash: basePatch.fromHash,
131
+ toHash: nextPatch.toHash,
132
+ moduleFactories: [...moduleFactories.values()],
133
+ removedModules: [...removedModules],
134
+ };
135
+ };
136
+ exports.mergeUpdatePatches = mergeUpdatePatches;
137
+ const generatePatchAssetSource = (opt) => {
138
+ const { bootstrapFactories, recentUpdatePatches } = opt;
139
+ const buildBootstrapPatch = template_1.default.expression(`({
140
+ type: 'bootstrap',
141
+ modules: MODULES
142
+ })`);
143
+ const buildUpdatePatch = template_1.default.expression(`({
144
+ type: 'update',
145
+ fromHash: "FROM_HASH",
146
+ toHash: "TO_HASH",
147
+ modules: MODULES,
148
+ removedModules: REMOVED_MODULES
149
+ })`);
150
+ const buildPatchAsset = template_1.default.program('module.exports = PATCHES;');
151
+ const bootstrapModules = Array.from(bootstrapFactories.values()).map((moduleFactory) => {
152
+ const { moduleId, factory } = moduleFactory;
153
+ return t.objectProperty(t.stringLiteral(moduleId), factory);
154
+ });
155
+ const patchNodes = [
156
+ buildBootstrapPatch({ MODULES: t.objectExpression(bootstrapModules) }),
157
+ ...recentUpdatePatches.map(patch => {
158
+ const updateModules = patch.moduleFactories.map((moduleFactory) => {
159
+ const { moduleId, factory } = moduleFactory;
160
+ return t.objectProperty(t.stringLiteral(moduleId), factory);
161
+ });
162
+ return buildUpdatePatch({
163
+ FROM_HASH: patch.fromHash,
164
+ TO_HASH: patch.toHash,
165
+ MODULES: t.objectExpression(updateModules),
166
+ REMOVED_MODULES: t.arrayExpression(patch.removedModules.map(moduleId => t.stringLiteral(moduleId))),
167
+ });
168
+ }),
169
+ ];
170
+ return (0, generator_1.default)(buildPatchAsset({ PATCHES: t.arrayExpression(patchNodes) })).code;
171
+ };
172
+ exports.generatePatchAssetSource = generatePatchAssetSource;
173
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/patch/utils.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taro-hmr-compile/plugin-webpack",
3
- "version": "0.0.1-alpha.7",
3
+ "version": "0.0.1-alpha.8",
4
4
  "description": "",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -44,7 +44,7 @@
44
44
  "@babel/template": "^7.29.7",
45
45
  "@babel/traverse": "^7.29.8",
46
46
  "@babel/types": "^7.29.8",
47
- "@taro-hmr-compile/core": "0.0.1-alpha.7",
47
+ "@taro-hmr-compile/core": "0.0.1-alpha.8",
48
48
  "debug": "^4.4.3",
49
49
  "tapable": "^2.3.3",
50
50
  "webpack-sources": "^3.5.1"
@@ -29,12 +29,16 @@ export interface HmrCompilerPluginOptions {
29
29
  debugNamespaces?: string
30
30
  }
31
31
 
32
+ type CompileRequested = SyncHook<[page: string]>
33
+
34
+ type ChunkModulesRendered = SyncHook<[source: string, isPageChunk: boolean]>
35
+
32
36
  export interface HmrCompilerHooks {
33
- compileRequested: SyncHook<[page: string]>
37
+ compileRequested: CompileRequested
34
38
  }
35
39
 
36
40
  export interface HmrCompilationHooks {
37
- pageChunkModules: SyncHook<[source: string]>
41
+ chunkModulesRendered: ChunkModulesRendered
38
42
  }
39
43
 
40
44
  export const applyPatchBeforeStartup = (source: Source, request: string) => {
@@ -67,21 +71,19 @@ export class HmrCompilerPlugin {
67
71
  }
68
72
 
69
73
  static getCompilerHooks(compiler: Compiler): HmrCompilerHooks {
70
- let hooks = HmrCompilerPlugin.compilerHooksMap.get(compiler)
71
- if (!hooks) {
72
- hooks = {compileRequested: new SyncHook(['page'])}
73
- HmrCompilerPlugin.compilerHooksMap.set(compiler, hooks)
74
+ if (!HmrCompilerPlugin.compilerHooksMap.get(compiler)) {
75
+ const compileRequested: CompileRequested = new SyncHook(['page'])
76
+ HmrCompilerPlugin.compilerHooksMap.set(compiler, {compileRequested})
74
77
  }
75
- return hooks
78
+ return HmrCompilerPlugin.compilerHooksMap.get(compiler)!
76
79
  }
77
80
 
78
81
  static getCompilationHooks(compilation: Compilation): HmrCompilationHooks {
79
- let hooks = HmrCompilerPlugin.compilationHooksMap.get(compilation)
80
- if (!hooks) {
81
- hooks = {pageChunkModules: new SyncHook(['source'])}
82
- HmrCompilerPlugin.compilationHooksMap.set(compilation, hooks)
82
+ if (!HmrCompilerPlugin.compilationHooksMap.get(compilation)) {
83
+ const chunkModulesRendered: ChunkModulesRendered = new SyncHook(['source', 'isBootstrapChunk'])
84
+ HmrCompilerPlugin.compilationHooksMap.set(compilation, {chunkModulesRendered})
83
85
  }
84
- return hooks
86
+ return HmrCompilerPlugin.compilationHooksMap.get(compilation)!
85
87
  }
86
88
 
87
89
  apply(compiler: Compiler) {
@@ -124,16 +126,16 @@ export class HmrCompilerPlugin {
124
126
 
125
127
  const loaderOptions = {miniType: normalModule.miniType, debugNamespaces: this.options.debugNamespaces}
126
128
  // Loader 从右向左执行;插到 taro-loader 左侧才能处理它生成的入口注册代码。
127
- normalModule.loaders.splice(taroLoaderIndex, 0, { loader: HMR_COMPILER_LOADER, options: loaderOptions})
129
+ normalModule.loaders.splice(taroLoaderIndex, 0, {loader: HMR_COMPILER_LOADER, options: loaderOptions})
128
130
  },
129
131
  )
130
132
 
131
133
  // 只修改 Page Chunk 的 JS transport。Webpack 的 ModuleGraph / ChunkGraph 保持不变,
132
134
  // 因而 hash、HMR diff 和 hot-update 仍以完整模块图为准。
133
135
  javascriptHooks.renderChunk.tap({name: PLUGIN_NAME, stage: -10_000}, (source, {chunk}) => {
134
- if (!isPageChunk(chunk)) return source
135
- hmrHooks.pageChunkModules.call(source.source().toString())
136
- return new RawSource('{}')
136
+ const isBootstrapChunk = isPageChunk(chunk)
137
+ hmrHooks.chunkModulesRendered.call(source.source().toString(), isBootstrapChunk)
138
+ return isBootstrapChunk ? new RawSource('{}') : source
137
139
  })
138
140
 
139
141
  javascriptHooks.renderStartup.tap(PLUGIN_NAME, (source, _module, {chunk}) => {
@@ -1,217 +1,90 @@
1
- import generator from '@babel/generator'
2
- import {parse} from '@babel/parser'
3
- import template from '@babel/template'
4
- import traverse from '@babel/traverse'
5
- import * as t from '@babel/types'
6
1
  import type {Compilation, Compiler} from 'webpack'
7
2
  import {RawSource} from 'webpack-sources'
8
- import {DEFAULT_HMR_PATCH_HISTORY_LIMIT, HMR_PATCH_ASSET} from '../constants'
3
+ import {HMR_PATCH_ASSET} from '../constants'
9
4
  import {HmrCompilerPlugin} from '../compile/hmr-compiler-plugin'
10
- import {HmrPatchManager} from './hmr-patch-manager'
11
- import type {PatchSource, UpdatePatchSource} from './hmr-patch-manager'
12
5
  import {HmrPatchRuntimePlugin} from './hmr-patch-runtime-plugin'
6
+ import {HmrPatchProcessor, HmrPatchProcessorOptions} from "./hmr-patch-processor";
13
7
 
14
8
  const PLUGIN_NAME = 'HmrPatchPlugin'
15
9
  const HOT_UPDATE_MANIFEST_SUFFIX = '.hot-update.json'
16
10
  const HOT_UPDATE_SCRIPT_SUFFIX = '.hot-update.js'
17
- const HOT_UPDATE_HASH_PATTERN = /\.([0-9a-f]+)\.hot-update\.json$/i
18
11
  const STYLE_ASSET_PATTERN = /\.(?:css|wxss|acss|ttss|qss|jxss)(?:\.map)?$/i
19
12
  const TEMPLATE_RUNTIME_ASSET_PATTERN = /(?:^|\/)(?:base\.wxml|utils\.wxs)$/i
20
13
 
21
- interface HotUpdateManifest {
22
- m?: Array<string | number>
23
- }
24
-
25
- type CompilationAsset = ReturnType<Compilation['getAssets']>[number]
26
-
27
- interface HotUpdateAssets {
28
- manifests: CompilationAsset[]
29
- scripts: CompilationAsset[]
30
- }
31
-
32
- export interface HmrPatchPluginOptions {
33
- patchHistoryLimit?: number
34
- }
35
-
36
- const collectModuleFactories = (source: string, moduleFactories: Map<string, t.Expression>) => {
37
- const ast = parse(`(${source})`, {sourceType: 'script'})
38
- const statement = ast.program.body[0]
39
- if (!t.isExpressionStatement(statement) || !t.isObjectExpression(statement.expression)) {
40
- throw new Error('[taro-hmr-compile] Page Chunk 模块段不是对象表达式')
41
- }
42
-
43
- statement.expression.properties.forEach(property => {
44
- if (!t.isObjectProperty(property) || !t.isExpression(property.value)) {
45
- throw new Error('[taro-hmr-compile] Page Chunk 模块对象包含无法识别的属性')
46
- }
47
-
48
- const {key} = property
49
- let moduleId: string
50
- if (t.isStringLiteral(key) || t.isNumericLiteral(key)) moduleId = String(key.value)
51
- else if (t.isIdentifier(key) && !property.computed) moduleId = key.name
52
- else throw new Error('[taro-hmr-compile] Page Chunk 模块对象包含无法识别的模块 ID')
53
-
54
- moduleFactories.set(moduleId, property.value)
55
- })
56
- }
57
-
58
- /** 将本轮 Webpack hot-update 产物转换为内部 update 表示。 */
59
- const createUpdatePatch = (toHash: string, assets: HotUpdateAssets): UpdatePatchSource => {
60
- const manifestAsset = assets.manifests[0]
61
- const hashMatch = manifestAsset.name.match(HOT_UPDATE_HASH_PATTERN)
62
- if (!hashMatch) throw new Error(`[taro-hmr-compile] 无法从 ${manifestAsset.name} 提取 HMR 基线 hash`)
63
-
64
- const moduleFactories = new Map<string, t.Expression>()
65
- assets.scripts.forEach(asset => {
66
- const ast = parse(asset.source.source().toString(), {sourceType: 'script'})
67
-
68
- traverse(ast, {
69
- CallExpression(nodePath) {
70
- const {callee} = nodePath.node
71
- if (!t.isMemberExpression(callee) || !t.isIdentifier(callee.object, {name: 'wx'})) return
72
- if (!t.isStringLiteral(callee.property) || !callee.property.value.startsWith('webpackHotUpdate')) return
73
-
74
- const modules = nodePath.node.arguments[1]
75
- if (!t.isObjectExpression(modules)) return
76
-
77
- modules.properties.forEach(property => {
78
- if (!t.isObjectProperty(property) || !t.isExpression(property.value)) {
79
- throw new Error('[taro-hmr-compile] HMR 模块对象包含无法识别的属性')
80
- }
81
-
82
- const {key} = property
83
- let moduleId: string
84
- if (t.isStringLiteral(key) || t.isNumericLiteral(key)) moduleId = String(key.value)
85
- else if (t.isIdentifier(key) && !property.computed) moduleId = key.name
86
- else throw new Error('[taro-hmr-compile] HMR 模块对象包含无法识别的模块 ID')
87
-
88
- moduleFactories.set(moduleId, property.value)
89
- })
90
- },
91
- })
92
- })
93
-
94
- const removedModules = new Set<string>()
95
- assets.manifests.forEach(asset => {
96
- const manifest = JSON.parse(asset.source.source().toString()) as HotUpdateManifest
97
- manifest.m?.forEach(moduleId => removedModules.add(String(moduleId)))
98
- })
99
-
100
- return {
101
- type: 'update',
102
- fromHash: hashMatch[1],
103
- toHash,
104
- moduleFactories,
105
- removedModules: Array.from(removedModules),
106
- }
107
- }
108
-
109
- /** 将内部 Patch 表示序列化为固定 patch.js。 */
110
- const createPatchAssetSource = (patches: PatchSource[]): string => {
111
- const buildBootstrapPatchNode = template.expression(`({
112
- type: 'bootstrap',
113
- modules: %%MODULES%%
114
- })`)
115
- const buildUpdatePatchNode = template.expression(`({
116
- type: 'update',
117
- fromHash: %%FROM_HASH%%,
118
- toHash: %%TO_HASH%%,
119
- modules: %%MODULES%%,
120
- removedModules: %%REMOVED_MODULES%%
121
- })`)
122
- const patchNodes = patches.map(patch => {
123
- const modules = t.objectExpression(Array.from(
124
- patch.moduleFactories,
125
- ([moduleId, factory]) => t.objectProperty(t.stringLiteral(moduleId), factory),
126
- ))
127
- if (patch.type === 'bootstrap') return buildBootstrapPatchNode({MODULES: modules})
128
-
129
- return buildUpdatePatchNode({
130
- FROM_HASH: t.stringLiteral(patch.fromHash),
131
- TO_HASH: t.stringLiteral(patch.toHash),
132
- MODULES: modules,
133
- REMOVED_MODULES: t.arrayExpression(patch.removedModules.map(moduleId => t.stringLiteral(moduleId))),
134
- })
135
- })
136
-
137
- const statements = template.statements('module.exports = %%PATCHES%%;')({PATCHES: t.arrayExpression(patchNodes)})
138
- return generator(t.program(statements)).code
139
- }
14
+ type HmrPatchPluginOptions = Partial<HmrPatchProcessorOptions>
140
15
 
141
16
  const isPassthroughAsset = (assetName: string) => {
142
17
  return STYLE_ASSET_PATTERN.test(assetName) || TEMPLATE_RUNTIME_ASSET_PATTERN.test(assetName)
143
18
  }
144
19
 
145
20
  export class HmrPatchPlugin {
146
- private hasCompletedInitialBuild = false
147
21
 
148
- private readonly patchManager: HmrPatchManager
22
+ private previousHash: string = ''
23
+
24
+ private initialBuildDone = false
25
+
26
+ private readonly patchProcessor: HmrPatchProcessor
149
27
 
150
28
  private readonly emittedAssetSources = new Map<string, string>()
151
29
 
152
30
  constructor(options: HmrPatchPluginOptions) {
153
- this.patchManager = new HmrPatchManager(options.patchHistoryLimit ?? DEFAULT_HMR_PATCH_HISTORY_LIMIT)
31
+ this.patchProcessor = new HmrPatchProcessor(options)
154
32
  }
155
33
 
156
34
  apply(compiler: Compiler) {
157
35
  new HmrPatchRuntimePlugin().apply(compiler)
158
36
 
159
37
  compiler.hooks.compilation.tap(PLUGIN_NAME, compilation => {
160
- const initialPageFactories = new Map<string, t.Expression>()
161
-
162
- HmrCompilerPlugin.getCompilationHooks(compilation).pageChunkModules.tap(PLUGIN_NAME, source => {
163
- collectModuleFactories(source, initialPageFactories)
38
+ HmrCompilerPlugin.getCompilationHooks(compilation).chunkModulesRendered.tap(PLUGIN_NAME, (source, isBootstrapChunk) => {
39
+ this.patchProcessor.processChunkModules({compilation, source, isBootstrapChunk})
164
40
  })
165
41
 
166
42
  // 等 Webpack 生成完整的 hot-update assets 后,再统一转换成固定的 patch.js。
167
43
  const stage = compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_REPORT
168
- compilation.hooks.processAssets.tap({name: PLUGIN_NAME, stage}, () => {
169
- this.processAssets(compilation, initialPageFactories)
170
- })
44
+ compilation.hooks.processAssets.tap({name: PLUGIN_NAME, stage}, () => this.processAssets(compilation))
171
45
  })
172
46
 
173
47
  compiler.hooks.afterEmit.tap(PLUGIN_NAME, compilation => {
174
48
  const isSuccessful = compilation.errors.length === 0 && Boolean(compilation.hash)
175
- this.hasCompletedInitialBuild = this.hasCompletedInitialBuild || isSuccessful
49
+ this.initialBuildDone = this.initialBuildDone || isSuccessful
176
50
  })
177
51
 
178
52
  compiler.hooks.watchClose.tap(PLUGIN_NAME, () => this.reset())
179
53
  }
180
54
 
181
- private processAssets(compilation: Compilation, initialPageFactories: Map<string, t.Expression>) {
182
- if (!compilation.hash || compilation.errors.length > 0) return
183
-
184
- const originalAssets = compilation.getAssets()
185
- if (!this.hasCompletedInitialBuild) {
186
- this.patchManager.clear()
187
- this.emittedAssetSources.clear()
188
- originalAssets.forEach(asset => {
189
- if (isPassthroughAsset(asset.name)) {
190
- this.emittedAssetSources.set(asset.name, asset.source.source().toString())
191
- }
55
+ private processAssets(compilation: Compilation) {
56
+ const {previousHash: fromHash, initialBuildDone} = this
57
+
58
+ const {hash: toHash, errors: compilationErrors} = compilation
59
+
60
+ if (!toHash || compilationErrors.length > 0) return
61
+ // 首次编译完成过滤文件输出
62
+ if (!initialBuildDone) {
63
+ this.patchProcessor.commitBootstrapFactories(compilation)
64
+ compilation.getAssets().forEach(asset => {
65
+ if (!isPassthroughAsset(asset.name)) return
66
+ this.emittedAssetSources.set(asset.name, asset.source.source().toString())
192
67
  })
193
- this.patchManager.setBootstrap(initialPageFactories)
194
- this.emitPatchAsset(compilation, false)
195
- return
68
+ this.previousHash = toHash
69
+ return this.emitPatchAsset(compilation, false)
196
70
  }
197
71
 
198
- const hotUpdateAssets: HotUpdateAssets = {
199
- manifests: originalAssets.filter(asset => asset.name.endsWith(HOT_UPDATE_MANIFEST_SUFFIX)),
200
- scripts: originalAssets.filter(asset => asset.name.endsWith(HOT_UPDATE_SCRIPT_SUFFIX)),
201
- }
72
+ const manifests = compilation.getAssets().filter(asset => asset.name.endsWith(HOT_UPDATE_MANIFEST_SUFFIX))
202
73
 
203
- if (hotUpdateAssets.manifests.length) {
204
- this.patchManager.appendUpdate(createUpdatePatch(compilation.hash, hotUpdateAssets))
205
- this.emitPatchAsset(compilation, true)
206
- }
74
+ const scripts = compilation.getAssets().filter(asset => asset.name.endsWith(HOT_UPDATE_SCRIPT_SUFFIX))
75
+
76
+ this.patchProcessor.processUpdate({toHash, fromHash, manifests, scripts})
77
+
78
+ this.emitPatchAsset(compilation, true)
79
+
80
+ this.removeSupersededAssets(compilation)
207
81
 
208
- // 增量 compilation 仍可能包含普通 chunk 资产,不能让它们覆盖初始 PageShell。
209
- this.removeSupersededAssets(compilation, originalAssets)
82
+ this.previousHash = toHash
210
83
  }
211
84
 
212
85
  /** 热更新转换完成后,保留固定 patch 与发生变化的模板、平台样式资产。 */
213
- private removeSupersededAssets(compilation: Compilation, assets: CompilationAsset[]) {
214
- assets.forEach(asset => {
86
+ private removeSupersededAssets(compilation: Compilation) {
87
+ compilation.getAssets().forEach(asset => {
215
88
  if (asset.name === HMR_PATCH_ASSET) return
216
89
  if (!isPassthroughAsset(asset.name)) return compilation.deleteAsset(asset.name)
217
90
 
@@ -223,15 +96,16 @@ export class HmrPatchPlugin {
223
96
  }
224
97
 
225
98
  private emitPatchAsset(compilation: Compilation, isHotUpdate: boolean) {
226
- const source = new RawSource(createPatchAssetSource(this.patchManager.getPatches()))
99
+ const source = new RawSource(this.patchProcessor.generate())
227
100
  const assetInfo = {hotModuleReplacement: isHotUpdate}
228
101
  if (compilation.getAsset(HMR_PATCH_ASSET)) compilation.updateAsset(HMR_PATCH_ASSET, source, assetInfo)
229
102
  else compilation.emitAsset(HMR_PATCH_ASSET, source, assetInfo)
230
103
  }
231
104
 
232
105
  private reset() {
233
- this.hasCompletedInitialBuild = false
106
+ this.previousHash = ''
107
+ this.initialBuildDone = false
234
108
  this.emittedAssetSources.clear()
235
- this.patchManager.clear()
109
+ this.patchProcessor.reset()
236
110
  }
237
111
  }