UNPKG

23.1 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, '__esModule', {
4 value: true
5});
6
7var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
8
9function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
10
11function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
12
13var _appCache = require('./app-cache');
14
15var _appCache2 = _interopRequireDefault(_appCache);
16
17var _serviceWorker = require('./service-worker');
18
19var _serviceWorker2 = _interopRequireDefault(_serviceWorker);
20
21var _defaultOptions = require('./default-options');
22
23var _defaultOptions2 = _interopRequireDefault(_defaultOptions);
24
25var _miscUtils = require('./misc/utils');
26
27var _path2 = require('path');
28
29var _path3 = _interopRequireDefault(_path2);
30
31var _url = require('url');
32
33var _url2 = _interopRequireDefault(_url);
34
35var _deepExtend = require('deep-extend');
36
37var _deepExtend2 = _interopRequireDefault(_deepExtend);
38
39var _minimatch = require('minimatch');
40
41var _minimatch2 = _interopRequireDefault(_minimatch);
42
43var _loaderUtils = require('loader-utils');
44
45var _loaderUtils2 = _interopRequireDefault(_loaderUtils);
46
47var _slash = require('slash');
48
49var _slash2 = _interopRequireDefault(_slash);
50
51var _require = require('../package.json');
52
53var pluginVersion = _require.version;
54
55var hasOwn = ({}).hasOwnProperty;
56var updateStrategies = ['all', 'hash', 'changed'];
57
58var OfflinePlugin = (function () {
59 function OfflinePlugin(options) {
60 var _this = this;
61
62 _classCallCheck(this, OfflinePlugin);
63
64 var AppCacheOptions = options ? options.AppCache : void 0;
65
66 this.options = (0, _deepExtend2['default'])({}, _defaultOptions2['default'], options, {
67 AppCache: false
68 });
69
70 this.hash = null;
71 this.assets = null;
72 this.hashesMap = null;
73 this.externals = null;
74 this.publicPath = this.options.publicPath;
75 this.strategy = this.options.updateStrategy;
76 this.responseStrategy = this.options.responseStrategy;
77 this.relativePaths = this.options.relativePaths;
78 this.pluginVersion = pluginVersion;
79 this.warnings = [];
80 this.errors = [];
81
82 this.__tests = this.options.__tests;
83 this.flags = {};
84
85 var appCacheEnabled = !!(AppCacheOptions || this.__tests.appCacheEnabled);
86
87 if (appCacheEnabled) {
88 this.options.AppCache = (0, _deepExtend2['default'])({}, _defaultOptions.AppCacheOptions, AppCacheOptions);
89 }
90
91 if (this.__tests.pluginVersion) {
92 this.pluginVersion = this.__tests.pluginVersion;
93 }
94
95 var autoUpdate = this.options.autoUpdate;
96
97 if (autoUpdate === true) {
98 this.autoUpdate = _defaultOptions.DEFAULT_AUTO_UPDATE_INTERVAL;
99 } else if (typeof autoUpdate === 'number' && autoUpdate) {
100 this.autoUpdate = autoUpdate;
101 }
102
103 if (this.options.responseStrategy !== "cache-first" && this.options.responseStrategy !== "network-first") {
104 throw new Error('OfflinePlugin: `responseStrategy` option must use ' + '`cache-first` or `network-first` (or be undefined).');
105 }
106
107 if (typeof this.publicPath !== 'string') {
108 this.publicPath = null;
109 }
110
111 if (updateStrategies.indexOf(this.strategy) === -1) {
112 throw new Error('Update strategy must be one of [' + updateStrategies + ']');
113 } else if (this.strategy === 'hash') {
114 this.warnings.push(new Error('OfflinePlugin: `hash` update strategy is deprecated, use `all` strategy and { version: "[hash]" } instead'));
115
116 this.strategy = 'all';
117 this.options.version = '[hash]';
118 }
119
120 if (!Array.isArray(this.options.externals)) {
121 this.options.externals = [];
122 }
123
124 var rewrites = this.options.rewrites || _defaultOptions2['default'].rewrites;
125
126 if (typeof rewrites === 'function') {
127 this.rewrite = function (asset) {
128 if (asset.indexOf(_this.entryPrefix) === 0) {
129 return '';
130 }
131
132 return rewrites(asset);
133 };
134 } else {
135 this.rewrite = function (asset) {
136 if (asset.indexOf(_this.entryPrefix) === 0) {
137 return '';
138 }
139
140 if (!hasOwn.call(rewrites, asset)) {
141 return asset;
142 }
143
144 return rewrites[asset];
145 };
146 }
147
148 if (this.options.appShell && typeof this.options.appShell === 'string') {
149 this.appShell = this.options.appShell;
150 }
151
152 var cacheMaps = this.options.cacheMaps;
153
154 if (this.appShell) {
155 // Make appShell the latest in the chain so it could be overridden
156 cacheMaps = (cacheMaps || []).concat({
157 match: new Function('return new URL(' + JSON.stringify(this.appShell) + ', location);'),
158 requestTypes: ['navigate']
159 });
160 }
161
162 this.cacheMaps = this.stringifyCacheMaps(cacheMaps);
163
164 this.REST_KEY = ':rest:';
165 this.EXTERNALS_KEY = ':externals:';
166 this.entryPrefix = '__offline_';
167 this.tools = {};
168
169 this.addTool(_serviceWorker2['default'], 'ServiceWorker');
170 this.addTool(_appCache2['default'], 'AppCache');
171
172 if (!Object.keys(this.tools).length) {
173 throw new Error('You should have at least one cache service to be specified');
174 }
175 }
176
177 _createClass(OfflinePlugin, [{
178 key: 'apply',
179 value: function apply(compiler) {
180 var _this2 = this;
181
182 var runtimePath = _path3['default'].resolve(__dirname, '../runtime.js');
183 var compilerOptions = compiler.options;
184
185 if (this.relativePaths === true) {
186 this.publicPath = null;
187 }
188
189 if (typeof this.publicPath !== 'string' && compilerOptions && compilerOptions.output && compilerOptions.output.publicPath && this.relativePaths !== true) {
190 this.publicPath = compilerOptions.output.publicPath;
191 this.relativePaths = false;
192 }
193
194 if (this.publicPath) {
195 this.publicPath = this.publicPath.replace(/\/$/, '') + '/';
196 }
197
198 if (this.relativePaths === true && this.publicPath) {
199 this.errors.push(new Error('OfflinePlugin: `publicPath` is used in conjunction with `relativePaths`,\n' + 'choose one of it'));
200
201 this.relativePaths = false;
202 }
203
204 if (this.relativePaths === _defaultOptions2['default'].relativePaths) {
205 this.relativePaths = !this.publicPath;
206 }
207
208 this.useTools(function (tool, key) {
209 _this2.resolveToolPaths(tool, key, compiler);
210 });
211
212 var afterResolveFn = function afterResolveFn(result, callback) {
213 var resource = _path3['default'].resolve(compiler.context, result.resource);
214
215 if (resource !== runtimePath) {
216 callback(null, result);
217 return;
218 }
219
220 var data = {
221 autoUpdate: _this2.autoUpdate
222 };
223
224 _this2.useTools(function (tool, key) {
225 data[key] = tool.getConfig(_this2);
226 });
227
228 result.loaders.push(_path3['default'].join(__dirname, 'misc/runtime-loader.js') + '?' + JSON.stringify(data));
229
230 callback(null, result);
231 };
232
233 var makeFn = function makeFn(compilation, callback) {
234 if (_this2.warnings.length) {
235 [].push.apply(compilation.warnings, _this2.warnings);
236 }
237
238 if (_this2.errors.length) {
239 [].push.apply(compilation.errors, _this2.errors);
240 }
241
242 _this2.useTools(function (tool) {
243 return tool.addEntry(_this2, compilation, compiler);
244 }).then(function () {
245 callback();
246 })['catch'](function (e) {
247 throw e || new Error('Something went wrong');
248 });
249 };
250
251 var emitFn = function emitFn(compilation, callback) {
252 var runtimeTemplatePath = _path3['default'].resolve(__dirname, '../tpls/runtime-template.js');
253 var hasRuntime = true;
254
255 if (compilation.fileDependencies.indexOf) {
256 hasRuntime = compilation.fileDependencies.indexOf(runtimeTemplatePath) !== -1;
257 } else if (compilation.fileDependencies.has) {
258 hasRuntime = compilation.fileDependencies.has(runtimeTemplatePath);
259 }
260
261 if (!hasRuntime && !_this2.__tests.ignoreRuntime) {
262 compilation.errors.push(new Error('OfflinePlugin: Plugin\'s runtime wasn\'t added to one of your bundle entries. See this https://goo.gl/YwewYp for details.'));
263 callback();
264 return;
265 }
266
267 var stats = compilation.getStats().toJson();
268
269 // By some reason errors raised here are not fatal,
270 // so we need manually try..catch and exit with error
271 try {
272 _this2.setAssets(compilation);
273 _this2.setHashesMap(compilation);
274
275 // Generate bundle hash manually (from what we have)
276 _this2.hash = _loaderUtils2['default'].getHashDigest(Object.keys(_this2.hashesMap).join(''), 'sha1');
277
278 // Not used yet
279 // this.setNetworkOptions();
280 } catch (e) {
281 callback(e);
282 return;
283 }
284
285 _this2.useTools(function (tool) {
286 return tool.apply(_this2, compilation, compiler);
287 }).then(function () {
288 callback();
289 }, function () {
290 callback(new Error('Something went wrong'));
291 });
292 };
293
294 if (compiler.hooks) {
295 (function () {
296 var plugin = { name: 'OfflinePlugin' };
297
298 compiler.hooks.normalModuleFactory.tap(plugin, function (nmf) {
299 nmf.hooks.afterResolve.tapAsync(plugin, afterResolveFn);
300 });
301
302 compiler.hooks.make.tapAsync(plugin, makeFn);
303 compiler.hooks.emit.tapAsync(plugin, emitFn);
304 })();
305 } else {
306 compiler.plugin('normal-module-factory', function (nmf) {
307 nmf.plugin('after-resolve', afterResolveFn);
308 });
309
310 compiler.plugin('make', makeFn);
311 compiler.plugin('emit', emitFn);
312 }
313 }
314 }, {
315 key: 'setAssets',
316 value: function setAssets(compilation) {
317 var _this3 = this;
318
319 var caches = this.options.caches || _defaultOptions2['default'].caches;
320
321 if (this.options.safeToUseOptionalCaches !== true && (caches.additional && caches.additional.length || caches.optional && caches.optional.length)) {
322 compilation.warnings.push(new Error('OfflinePlugin: Cache sections `additional` and `optional` could be used ' + 'only when each asset passed to it has unique name (e.g. hash or version in it) and ' + 'is permanently available for given URL. If you think that it\'s your case, ' + 'set `safeToUseOptionalCaches` option to `true`, to remove this warning.'));
323 }
324
325 var excludes = this.options.excludes;
326 var assets = Object.keys(compilation.assets);
327 var externals = this.options.externals;
328
329 if (Array.isArray(excludes) && excludes.length) {
330 assets = assets.filter(function (asset) {
331 if (excludes.some(function (glob) {
332 if ((0, _minimatch2['default'])(asset, glob)) {
333 return true;
334 }
335 })) {
336 return false;
337 }
338
339 return true;
340 });
341 }
342
343 this.externals = this.validatePaths(externals);
344
345 if (caches === 'all') {
346 this.assets = this.validatePaths(assets).concat(this.externals);
347 this.caches = {
348 main: this.assets.concat()
349 };
350 } else {
351 (function () {
352 var restSection = undefined;
353 var externalsSection = undefined;
354
355 var handledCaches = ['main', 'additional', 'optional'].reduce(function (result, key) {
356 var cache = Array.isArray(caches[key]) ? caches[key] : [];
357
358 if (!cache.length) {
359 result[key] = cache;
360 return result;
361 }
362
363 var cacheResult = [];
364
365 cache.some(function (cacheKey) {
366 if (cacheKey === _this3.REST_KEY) {
367 if (restSection) {
368 throw new Error('The ' + _this3.REST_KEY + ' keyword can be used only once');
369 }
370
371 restSection = key;
372 return;
373 }
374
375 if (cacheKey === _this3.EXTERNALS_KEY) {
376 if (externalsSection) {
377 throw new Error('The ' + _this3.EXTERNALS_KEY + ' keyword can be used only once');
378 }
379
380 externalsSection = key;
381 return;
382 }
383
384 var magic = undefined;
385
386 if (typeof cacheKey === 'string') {
387 magic = !(0, _miscUtils.isAbsoluteURL)(cacheKey) && cacheKey[0] !== '/' && cacheKey.indexOf('./') !== 0 && (0, _miscUtils.hasMagic)(cacheKey);
388 } else if (cacheKey instanceof RegExp) {
389 magic = (0, _miscUtils.hasMagic)(cacheKey);
390 } else {
391 // Ignore non-string and non-RegExp keys
392 return;
393 }
394
395 if (magic) {
396 var matched = undefined;
397
398 for (var i = 0, len = assets.length; i < len; i++) {
399 if (!magic.match(assets[i])) continue;
400
401 matched = true;
402 cacheResult.push(assets[i]);
403 assets.splice(i, 1);
404 i--, len--;
405 }
406
407 if (!matched) {
408 compilation.warnings.push(new Error('OfflinePlugin: Cache pattern [' + cacheKey + '] did not match any assets'));
409 }
410
411 return;
412 }
413
414 var index = assets.indexOf(cacheKey);
415
416 __EXTERNALS_CHECK: if (index === -1) {
417 var externalsIndex = externals.indexOf(cacheKey);
418
419 if (externalsIndex !== -1) {
420 externals.splice(externalsIndex, 1);
421 break __EXTERNALS_CHECK;
422 }
423
424 compilation.warnings.push(new Error('OfflinePlugin: Cache asset [' + cacheKey + '] is not found in the output assets, ' + 'if the asset is not processed by webpack, move it to the |externals| option to remove this warning.'));
425 } else {
426 assets.splice(index, 1);
427 }
428
429 cacheResult.push(cacheKey);
430 });
431
432 result[key] = _this3.validatePaths(cacheResult);
433
434 return result;
435 }, {});
436
437 if (restSection && assets.length) {
438 handledCaches[restSection] = handledCaches[restSection].concat(_this3.validatePaths(assets));
439 }
440
441 if (externalsSection && externals.length) {
442 handledCaches[externalsSection] = handledCaches[externalsSection].concat(_this3.validatePaths(externals));
443 }
444
445 _this3.caches = handledCaches;
446 _this3.assets = [].concat(_this3.caches.main, _this3.caches.additional, _this3.caches.optional);
447 })();
448 }
449 }
450 }, {
451 key: 'setHashesMap',
452 value: function setHashesMap(compilation) {
453 var _this4 = this;
454
455 this.hashesMap = {};
456
457 Object.keys(compilation.assets).forEach(function (key) {
458 var validatedPath = _this4.validatePaths([key])[0];
459
460 if (typeof validatedPath !== 'string' || _this4.assets.indexOf(validatedPath) === -1) return;
461
462 var hash = _loaderUtils2['default'].getHashDigest(compilation.assets[key].source(), 'sha1');
463
464 _this4.hashesMap[hash] = validatedPath;
465 });
466 }
467 }, {
468 key: 'setNetworkOptions',
469 value: function setNetworkOptions() {
470 var alwaysRevalidate = this.options.alwaysRevalidate;
471 var preferOnline = this.options.preferOnline;
472 var ignoreSearch = this.options.ignoreSearch;
473
474 var assets = this.assets;
475
476 // Disable temporarily
477 if (Array.isArray(alwaysRevalidate) && alwaysRevalidate.length) {
478 alwaysRevalidate = assets.filter(function (asset) {
479 if (alwaysRevalidate.some(function (glob) {
480 if ((0, _minimatch2['default'])(asset, glob)) {
481 return true;
482 }
483 })) {
484 return true;
485 }
486
487 return false;
488 });
489
490 if (alwaysRevalidate.length) {
491 this.alwaysRevalidate = alwaysRevalidate;
492 }
493 }
494
495 if (Array.isArray(ignoreSearch) && ignoreSearch.length) {
496 ignoreSearch = assets.filter(function (asset) {
497 if (ignoreSearch.some(function (glob) {
498 if ((0, _minimatch2['default'])(asset, glob)) {
499 return true;
500 }
501 })) {
502 return true;
503 }
504
505 return false;
506 });
507
508 if (ignoreSearch.length) {
509 this.ignoreSearch = ignoreSearch;
510 }
511 }
512
513 if (Array.isArray(preferOnline) && preferOnline.length) {
514 preferOnline = assets.filter(function (asset) {
515 if (preferOnline.some(function (glob) {
516 if ((0, _minimatch2['default'])(asset, glob)) {
517 return true;
518 }
519 })) {
520 return true;
521 }
522
523 return false;
524 });
525
526 if (preferOnline.length) {
527 this.preferOnline = preferOnline;
528 }
529 }
530 }
531 }, {
532 key: 'stringifyCacheMaps',
533 value: function stringifyCacheMaps(cacheMaps) {
534 if (!cacheMaps) {
535 return [];
536 }
537
538 return cacheMaps.map(function (map) {
539 if (map.to != null && typeof map.to !== 'string' && typeof map.to !== 'function') {
540 throw new Error('cacheMaps `to` property must either string, function, undefined or null');
541 }
542
543 if (map.requestTypes != null) {
544 if (Array.isArray(map.requestTypes)) {
545 var types = map.requestTypes.filter(function (item) {
546 if (item === 'navigate' || item === 'same-origin' || item === 'cross-origin') {
547 return false;
548 }
549
550 return true;
551 });
552
553 if (types.length) {
554 throw new Error("cacheMaps `requestTypes` array values could be only: 'navigate', 'same-origin' or 'cross-origin'");
555 }
556 } else {
557 throw new Error('cacheMaps `requestTypes` property must either array, undefined or null');
558 }
559 }
560
561 var to = undefined;
562 var match = undefined;
563
564 if (typeof map.to === 'function') {
565 to = (0, _miscUtils.functionToString)(map.to);
566 } else {
567 to = map.to ? JSON.stringify(map.to) : null;
568 }
569
570 if (typeof map.match === 'function') {
571 match = (0, _miscUtils.functionToString)(map.match);
572 } else {
573 match = map.match + '';
574 }
575
576 return {
577 match: match,
578 to: to,
579 requestTypes: map.requestTypes || null
580 };
581 });
582 }
583 }, {
584 key: 'resolveToolPaths',
585 value: function resolveToolPaths(tool, key, compiler) {
586 // Tool much implement:
587 //
588 // tool.output
589 // tool.publicPath
590 // tool.basePath
591 // tool.location
592 // tool.pathRewrite
593
594 if (!this.relativePaths && !this.publicPath) {
595 throw new Error('OfflinePlugin: Cannot generate base path for ' + key);
596 }
597
598 var isDirectory = tool.output[tool.output.length - 1] === '/';
599
600 if (this.relativePaths) {
601 var compilerOutput = (compiler.options.output || { path: process.cwd() }).path;
602 var absoluteOutput = _path3['default'].resolve(compilerOutput, tool.output);
603
604 var relativeBase = undefined;
605
606 if (isDirectory) {
607 relativeBase = _path3['default'].relative(absoluteOutput, compilerOutput);
608 } else {
609 relativeBase = _path3['default'].relative(_path3['default'].dirname(absoluteOutput), compilerOutput);
610 }
611
612 relativeBase = (0, _slash2['default'])(relativeBase);
613 relativeBase = relativeBase.replace(/\/$/, '');
614
615 if (relativeBase) {
616 relativeBase = relativeBase + '/';
617 }
618
619 tool.basePath = relativeBase[0] === '.' ? relativeBase : './' + relativeBase;
620 } else if (this.publicPath) {
621 tool.basePath = this.publicPath.replace(/\/$/, '') + '/';
622 }
623
624 if (this.relativePaths) {
625 tool.location = tool.output;
626 } else if (this.publicPath && tool.publicPath) {
627 tool.location = tool.publicPath;
628 } else if (this.publicPath) {
629 var publicUrl = _url2['default'].parse(this.publicPath);
630 var publicPath = publicUrl.pathname;
631
632 publicUrl.pathname = _path3['default'].join(publicPath, tool.output);
633 var outerPathname = _path3['default'].join('/outer/', publicPath, tool.output);
634
635 if (outerPathname.indexOf('/outer/') !== 0) {
636 new Error('OfflinePlugin: Wrong ' + key + '.output value. Final ' + key + '.location URL path bounds are outside of publicPath');
637 }
638
639 tool.location = _url2['default'].format(publicUrl);
640 }
641
642 if (this.relativePaths) {
643 tool.pathRewrite = function (_path) {
644 if ((0, _miscUtils.isAbsoluteURL)(_path) || _path[0] === '/') {
645 return _path;
646 }
647
648 return tool.basePath + _path;
649 };
650 } else {
651 tool.pathRewrite = function (path) {
652 return path;
653 };
654 }
655 }
656 }, {
657 key: 'validatePaths',
658 value: function validatePaths(assets) {
659 var _this5 = this;
660
661 return assets.map(this.rewrite).filter(function (asset) {
662 return !!asset;
663 }).map(function (key) {
664 // If absolute url, use it as is
665 if ((0, _miscUtils.isAbsoluteURL)(key)) {
666 return key;
667 }
668
669 if (_this5.relativePaths) {
670 return key.replace(/^\.\//, '');
671 }
672
673 // Absolute path, use it as is
674 if (key[0] === '/') {
675 return key;
676 }
677
678 return _this5.publicPath + key.replace(/^\.?\//, '');
679 });
680 }
681 }, {
682 key: 'useTools',
683 value: function useTools(fn) {
684 var _this6 = this;
685
686 var tools = Object.keys(this.tools).map(function (tool) {
687 return fn(_this6.tools[tool], tool);
688 });
689
690 return Promise.all(tools);
691 }
692 }, {
693 key: 'addTool',
694 value: function addTool(Tool, name) {
695 var options = this.options[name];
696
697 if (options === null || options === false) {
698 // tool is not needed
699 return;
700 }
701
702 this.tools[name] = new Tool(options);
703 }
704 }, {
705 key: 'version',
706 get: function get() {
707 var version = this.options.version;
708 var hash = this.hash;
709
710 if (version == null) {
711 return new Date().toLocaleString();
712 }
713
714 if (typeof version === 'function') {
715 return version(this);
716 }
717
718 return (0, _miscUtils.interpolateString)(version, { hash: hash });
719 }
720 }]);
721
722 return OfflinePlugin;
723})();
724
725exports['default'] = OfflinePlugin;
726
727OfflinePlugin.defaultOptions = _defaultOptions2['default'];
728module.exports = exports['default'];
\No newline at end of file