1 |
|
2 |
|
3 |
|
4 |
|
5 | 'use strict';
|
6 | var Template = require('./Template');
|
7 | var ModuleHotAcceptDependency = require('./dependencies/ModuleHotAcceptDependency');
|
8 | var ModuleHotDeclineDependency = require('./dependencies/ModuleHotDeclineDependency');
|
9 | var RawSource = require('webpack-sources').RawSource;
|
10 | var ConstDependency = require('./dependencies/ConstDependency');
|
11 | var NullFactory = require('./NullFactory');
|
12 | var ParserHelpers = require('./ParserHelpers');
|
13 | var RaxJsonpMainTemplatePlugin = require('./RaxJsonpMainTemplatePlugin.js');
|
14 |
|
15 | function HotModuleReplacementPlugin(options) {
|
16 | options = options || {};
|
17 | this.multiStep = options.multiStep;
|
18 | this.fullBuildTimeout = options.fullBuildTimeout || 200;
|
19 | }
|
20 | module.exports = HotModuleReplacementPlugin;
|
21 |
|
22 | HotModuleReplacementPlugin.prototype.apply = function(compiler) {
|
23 | var multiStep = this.multiStep;
|
24 | var fullBuildTimeout = this.fullBuildTimeout;
|
25 | var hotUpdateChunkFilename = compiler.options.output.hotUpdateChunkFilename;
|
26 | var hotUpdateMainFilename = compiler.options.output.hotUpdateMainFilename;
|
27 | compiler.plugin('compilation', function(compilation, params) {
|
28 | var hotUpdateChunkTemplate = compilation.hotUpdateChunkTemplate;
|
29 | if (!hotUpdateChunkTemplate) return;
|
30 |
|
31 | var normalModuleFactory = params.normalModuleFactory;
|
32 |
|
33 | compilation.dependencyFactories.set(ConstDependency, new NullFactory());
|
34 | compilation.dependencyTemplates.set(ConstDependency, new ConstDependency.Template());
|
35 |
|
36 | compilation.dependencyFactories.set(ModuleHotAcceptDependency, normalModuleFactory);
|
37 | compilation.dependencyTemplates.set(ModuleHotAcceptDependency, new ModuleHotAcceptDependency.Template());
|
38 |
|
39 | compilation.dependencyFactories.set(ModuleHotDeclineDependency, normalModuleFactory);
|
40 | compilation.dependencyTemplates.set(ModuleHotDeclineDependency, new ModuleHotDeclineDependency.Template());
|
41 |
|
42 | compilation.plugin('record', function(compilation, records) {
|
43 | if (records.hash === this.hash) return;
|
44 | records.hash = compilation.hash;
|
45 | records.moduleHashs = {};
|
46 | this.modules.forEach(function(module) {
|
47 | var identifier = module.identifier();
|
48 | var hash = require('crypto').createHash('md5');
|
49 | module.updateHash(hash);
|
50 | records.moduleHashs[identifier] = hash.digest('hex');
|
51 | });
|
52 | records.chunkHashs = {};
|
53 | this.chunks.forEach(function(chunk) {
|
54 | records.chunkHashs[chunk.id] = chunk.hash;
|
55 | });
|
56 | records.chunkModuleIds = {};
|
57 | this.chunks.forEach(function(chunk) {
|
58 | records.chunkModuleIds[chunk.id] = chunk.modules.map(function(m) {
|
59 | return m.id;
|
60 | });
|
61 | });
|
62 | });
|
63 | var initialPass = false;
|
64 | var recompilation = false;
|
65 | compilation.plugin('after-hash', function() {
|
66 | var records = this.records;
|
67 | if (!records) {
|
68 | initialPass = true;
|
69 | return;
|
70 | }
|
71 | if (!records.hash)
|
72 | initialPass = true;
|
73 | var preHash = records.preHash || 'x';
|
74 | var prepreHash = records.prepreHash || 'x';
|
75 | if (preHash === this.hash) {
|
76 | recompilation = true;
|
77 | this.modifyHash(prepreHash);
|
78 | return;
|
79 | }
|
80 | records.prepreHash = records.hash || 'x';
|
81 | records.preHash = this.hash;
|
82 | this.modifyHash(records.prepreHash);
|
83 | });
|
84 | compilation.plugin('should-generate-chunk-assets', function() {
|
85 | if (multiStep && !recompilation && !initialPass)
|
86 | return false;
|
87 | });
|
88 | compilation.plugin('need-additional-pass', function() {
|
89 | if (multiStep && !recompilation && !initialPass)
|
90 | return true;
|
91 | });
|
92 | compiler.plugin('additional-pass', function(callback) {
|
93 | if (multiStep)
|
94 | return setTimeout(callback, fullBuildTimeout);
|
95 | return callback();
|
96 | });
|
97 | compilation.plugin('additional-chunk-assets', function() {
|
98 | var records = this.records;
|
99 | if (records.hash === this.hash) return;
|
100 | if (!records.moduleHashs || !records.chunkHashs || !records.chunkModuleIds) return;
|
101 | this.modules.forEach(function(module) {
|
102 | var identifier = module.identifier();
|
103 | var hash = require('crypto').createHash('md5');
|
104 | module.updateHash(hash);
|
105 | hash = hash.digest('hex');
|
106 | module.hotUpdate = records.moduleHashs[identifier] !== hash;
|
107 | });
|
108 | var hotUpdateMainContent = {
|
109 | h: this.hash,
|
110 | c: {}
|
111 | };
|
112 | Object.keys(records.chunkHashs).forEach(function(chunkId) {
|
113 | chunkId = isNaN(+chunkId) ? chunkId : +chunkId;
|
114 | var currentChunk = this.chunks.find(chunk => chunk.id === chunkId);
|
115 | if (currentChunk) {
|
116 | var newModules = currentChunk.modules.filter(function(module) {
|
117 | return module.hotUpdate;
|
118 | });
|
119 | var allModules = {};
|
120 | currentChunk.modules.forEach(function(module) {
|
121 | allModules[module.id] = true;
|
122 | });
|
123 | var removedModules = records.chunkModuleIds[chunkId].filter(function(id) {
|
124 | return !allModules[id];
|
125 | });
|
126 | if (newModules.length > 0 || removedModules.length > 0) {
|
127 | var source = hotUpdateChunkTemplate.render(chunkId, newModules, removedModules, this.hash, this.moduleTemplate, this.dependencyTemplates);
|
128 | var filename = this.getPath(hotUpdateChunkFilename, {
|
129 | hash: records.hash,
|
130 | chunk: currentChunk
|
131 | });
|
132 | this.additionalChunkAssets.push(filename);
|
133 | this.assets[filename] = source;
|
134 | hotUpdateMainContent.c[chunkId] = true;
|
135 | currentChunk.files.push(filename);
|
136 | this.applyPlugins('chunk-asset', currentChunk, filename);
|
137 | }
|
138 | } else {
|
139 | hotUpdateMainContent.c[chunkId] = false;
|
140 | }
|
141 | }, this);
|
142 | var source = new RawSource(JSON.stringify(hotUpdateMainContent));
|
143 | var filename = this.getPath(hotUpdateMainFilename, {
|
144 | hash: records.hash
|
145 | });
|
146 | this.assets[filename] = source;
|
147 | });
|
148 |
|
149 | compilation.mainTemplate.plugin('hash', function(hash) {
|
150 | hash.update('HotMainTemplateDecorator');
|
151 | });
|
152 |
|
153 | compilation.mainTemplate.plugin('module-require', function(_, chunk, hash, varModuleId) {
|
154 | return 'hotCreateRequire(' + varModuleId + ')';
|
155 | });
|
156 |
|
157 | compilation.mainTemplate.plugin('require-extensions', function(source) {
|
158 | var buf = [source];
|
159 | buf.push('');
|
160 | buf.push('// __webpack_hash__');
|
161 | buf.push(this.requireFn + '.h = function() { return hotCurrentHash; };');
|
162 | return this.asString(buf);
|
163 | });
|
164 |
|
165 | compilation.mainTemplate.plugin('bootstrap', function(source, chunk, hash) {
|
166 | source = this.applyPluginsWaterfall('rax-hot-bootstrap', source, chunk, hash);
|
167 |
|
168 |
|
169 | return this.asString([
|
170 | require('./globalTemplate.js'),
|
171 | source,
|
172 | '',
|
173 | hotInitCode
|
174 | .replace(/\$require\$/g, this.requireFn)
|
175 | .replace(/\$hash\$/g, JSON.stringify(hash))
|
176 | .replace(/\/\* foreachInstalledChunks \*\//g, chunk.chunks.length > 0 ? 'for(var chunkId in installedChunks)' : 'var chunkId = ' + JSON.stringify(chunk.id) + ';')
|
177 | ]);
|
178 | });
|
179 |
|
180 | compilation.mainTemplate.plugin('global-hash', function() {
|
181 | return true;
|
182 | });
|
183 |
|
184 | compilation.mainTemplate.plugin('current-hash', function(_, length) {
|
185 | if (isFinite(length))
|
186 | return 'hotCurrentHash.substr(0, ' + length + ')';
|
187 | else
|
188 | return 'hotCurrentHash';
|
189 | });
|
190 |
|
191 | compilation.mainTemplate.plugin('module-obj', function(source, chunk, hash, varModuleId) {
|
192 | return this.asString([
|
193 | source + ',',
|
194 | 'hot: hotCreateModule(' + varModuleId + '),',
|
195 | 'parents: (hotCurrentParentsTemp = hotCurrentParents, hotCurrentParents = [], hotCurrentParentsTemp),',
|
196 | 'children: []'
|
197 | ]);
|
198 | });
|
199 |
|
200 | params.normalModuleFactory.plugin('parser', function(parser, parserOptions) {
|
201 | parser.plugin('expression __webpack_hash__', ParserHelpers.toConstantDependency('__webpack_require__.h()'));
|
202 | parser.plugin('evaluate typeof __webpack_hash__', ParserHelpers.evaluateToString('string'));
|
203 | parser.plugin('evaluate Identifier module.hot', function(expr) {
|
204 | return ParserHelpers.evaluateToBoolean(!!this.state.compilation.hotUpdateChunkTemplate)(expr);
|
205 | });
|
206 | parser.plugin('call module.hot.accept', function(expr) {
|
207 | if (!this.state.compilation.hotUpdateChunkTemplate) return false;
|
208 | if (expr.arguments.length >= 1) {
|
209 | var arg = this.evaluateExpression(expr.arguments[0]);
|
210 | var params = [],
|
211 | requests = [];
|
212 | if (arg.isString()) {
|
213 | params = [arg];
|
214 | } else if (arg.isArray()) {
|
215 | params = arg.items.filter(function(param) {
|
216 | return param.isString();
|
217 | });
|
218 | }
|
219 | if (params.length > 0) {
|
220 | params.forEach(function(param, idx) {
|
221 | var request = param.string;
|
222 | var dep = new ModuleHotAcceptDependency(request, param.range);
|
223 | dep.optional = true;
|
224 | dep.loc = Object.create(expr.loc);
|
225 | dep.loc.index = idx;
|
226 | this.state.module.addDependency(dep);
|
227 | requests.push(request);
|
228 | }.bind(this));
|
229 | if (expr.arguments.length > 1)
|
230 | this.applyPluginsBailResult('hot accept callback', expr.arguments[1], requests);
|
231 | else
|
232 | this.applyPluginsBailResult('hot accept without callback', expr, requests);
|
233 | }
|
234 | }
|
235 | });
|
236 | parser.plugin('call module.hot.decline', function(expr) {
|
237 | if (!this.state.compilation.hotUpdateChunkTemplate) return false;
|
238 | if (expr.arguments.length === 1) {
|
239 | var arg = this.evaluateExpression(expr.arguments[0]);
|
240 | var params = [];
|
241 | if (arg.isString()) {
|
242 | params = [arg];
|
243 | } else if (arg.isArray()) {
|
244 | params = arg.items.filter(function(param) {
|
245 | return param.isString();
|
246 | });
|
247 | }
|
248 | params.forEach(function(param, idx) {
|
249 | var dep = new ModuleHotDeclineDependency(param.string, param.range);
|
250 | dep.optional = true;
|
251 | dep.loc = Object.create(expr.loc);
|
252 | dep.loc.index = idx;
|
253 | this.state.module.addDependency(dep);
|
254 | }.bind(this));
|
255 | }
|
256 | });
|
257 | parser.plugin('expression module.hot', ParserHelpers.skipTraversal);
|
258 | });
|
259 | });
|
260 |
|
261 | compiler.plugin('this-compilation', (compilation) => {
|
262 | compilation.mainTemplate.apply(new RaxJsonpMainTemplatePlugin());
|
263 | });
|
264 | };
|
265 |
|
266 | var hotInitCode = Template.getFunctionContent(require('./HotModuleReplacement.runtime.js'));
|