UNPKG

10.7 kBJavaScriptView Raw
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5'use strict';
6var Template = require('./Template');
7var ModuleHotAcceptDependency = require('./dependencies/ModuleHotAcceptDependency');
8var ModuleHotDeclineDependency = require('./dependencies/ModuleHotDeclineDependency');
9var RawSource = require('webpack-sources').RawSource;
10var ConstDependency = require('./dependencies/ConstDependency');
11var NullFactory = require('./NullFactory');
12var ParserHelpers = require('./ParserHelpers');
13var RaxJsonpMainTemplatePlugin = require('./RaxJsonpMainTemplatePlugin.js');
14
15function HotModuleReplacementPlugin(options) {
16 options = options || {};
17 this.multiStep = options.multiStep;
18 this.fullBuildTimeout = options.fullBuildTimeout || 200;
19}
20module.exports = HotModuleReplacementPlugin;
21
22HotModuleReplacementPlugin.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 // cross-platform weex this is undefined
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
266var hotInitCode = Template.getFunctionContent(require('./HotModuleReplacement.runtime.js'));