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({
229 loader: _path3['default'].join(__dirname, 'misc/runtime-loader.js'),
230 options: JSON.stringify(data)
231 });
232
233 callback(null, result);
234 };
235
236 var makeFn = function makeFn(compilation, callback) {
237 if (_this2.warnings.length) {
238 [].push.apply(compilation.warnings, _this2.warnings);
239 }
240
241 if (_this2.errors.length) {
242 [].push.apply(compilation.errors, _this2.errors);
243 }
244
245 _this2.useTools(function (tool) {
246 return tool.addEntry(_this2, compilation, compiler);
247 }).then(function () {
248 callback();
249 })['catch'](function (e) {
250 throw e || new Error('Something went wrong');
251 });
252 };
253
254 var emitFn = function emitFn(compilation, callback) {
255 var runtimeTemplatePath = _path3['default'].resolve(__dirname, '../tpls/runtime-template.js');
256 var hasRuntime = true;
257
258 if (compilation.fileDependencies.indexOf) {
259 hasRuntime = compilation.fileDependencies.indexOf(runtimeTemplatePath) !== -1;
260 } else if (compilation.fileDependencies.has) {
261 hasRuntime = compilation.fileDependencies.has(runtimeTemplatePath);
262 }
263
264 if (!hasRuntime && !_this2.__tests.ignoreRuntime) {
265 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.'));
266 callback();
267 return;
268 }
269
270 var stats = compilation.getStats().toJson();
271
272 // By some reason errors raised here are not fatal,
273 // so we need manually try..catch and exit with error
274 try {
275 _this2.setAssets(compilation);
276 _this2.setHashesMap(compilation);
277
278 // Generate bundle hash manually (from what we have)
279 _this2.hash = _loaderUtils2['default'].getHashDigest(Object.keys(_this2.hashesMap).join(''), 'sha1');
280
281 // Not used yet
282 // this.setNetworkOptions();
283 } catch (e) {
284 callback(e);
285 return;
286 }
287
288 _this2.useTools(function (tool) {
289 return tool.apply(_this2, compilation, compiler);
290 }).then(function () {
291 callback();
292 }, function () {
293 callback(new Error('Something went wrong'));
294 });
295 };
296
297 if (compiler.hooks) {
298 (function () {
299 var plugin = { name: 'OfflinePlugin' };
300
301 compiler.hooks.normalModuleFactory.tap(plugin, function (nmf) {
302 nmf.hooks.afterResolve.tapAsync(plugin, afterResolveFn);
303 });
304
305 compiler.hooks.make.tapAsync(plugin, makeFn);
306 compiler.hooks.emit.tapAsync(plugin, emitFn);
307 })();
308 } else {
309 compiler.plugin('normal-module-factory', function (nmf) {
310 nmf.plugin('after-resolve', afterResolveFn);
311 });
312
313 compiler.plugin('make', makeFn);
314 compiler.plugin('emit', emitFn);
315 }
316 }
317 }, {
318 key: 'setAssets',
319 value: function setAssets(compilation) {
320 var _this3 = this;
321
322 var caches = this.options.caches || _defaultOptions2['default'].caches;
323
324 if (this.options.safeToUseOptionalCaches !== true && (caches.additional && caches.additional.length || caches.optional && caches.optional.length)) {
325 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.'));
326 }
327
328 var excludes = this.options.excludes;
329 var assets = Object.keys(compilation.assets);
330 var externals = this.options.externals;
331
332 if (Array.isArray(excludes) && excludes.length) {
333 assets = assets.filter(function (asset) {
334 if (excludes.some(function (glob) {
335 if ((0, _minimatch2['default'])(asset, glob)) {
336 return true;
337 }
338 })) {
339 return false;
340 }
341
342 return true;
343 });
344 }
345
346 this.externals = this.validatePaths(externals);
347
348 if (caches === 'all') {
349 this.assets = this.validatePaths(assets).concat(this.externals);
350 this.caches = {
351 main: this.assets.concat()
352 };
353 } else {
354 (function () {
355 var restSection = undefined;
356 var externalsSection = undefined;
357
358 var handledCaches = ['main', 'additional', 'optional'].reduce(function (result, key) {
359 var cache = Array.isArray(caches[key]) ? caches[key] : [];
360
361 if (!cache.length) {
362 result[key] = cache;
363 return result;
364 }
365
366 var cacheResult = [];
367
368 cache.some(function (cacheKey) {
369 if (cacheKey === _this3.REST_KEY) {
370 if (restSection) {
371 throw new Error('The ' + _this3.REST_KEY + ' keyword can be used only once');
372 }
373
374 restSection = key;
375 return;
376 }
377
378 if (cacheKey === _this3.EXTERNALS_KEY) {
379 if (externalsSection) {
380 throw new Error('The ' + _this3.EXTERNALS_KEY + ' keyword can be used only once');
381 }
382
383 externalsSection = key;
384 return;
385 }
386
387 var magic = undefined;
388
389 if (typeof cacheKey === 'string') {
390 magic = !(0, _miscUtils.isAbsoluteURL)(cacheKey) && cacheKey[0] !== '/' && cacheKey.indexOf('./') !== 0 && (0, _miscUtils.hasMagic)(cacheKey);
391 } else if (cacheKey instanceof RegExp) {
392 magic = (0, _miscUtils.hasMagic)(cacheKey);
393 } else {
394 // Ignore non-string and non-RegExp keys
395 return;
396 }
397
398 if (magic) {
399 var matched = undefined;
400
401 for (var i = 0, len = assets.length; i < len; i++) {
402 if (!magic.match(assets[i])) continue;
403
404 matched = true;
405 cacheResult.push(assets[i]);
406 assets.splice(i, 1);
407 i--, len--;
408 }
409
410 if (!matched) {
411 compilation.warnings.push(new Error('OfflinePlugin: Cache pattern [' + cacheKey + '] did not match any assets'));
412 }
413
414 return;
415 }
416
417 var index = assets.indexOf(cacheKey);
418
419 __EXTERNALS_CHECK: if (index === -1) {
420 var externalsIndex = externals.indexOf(cacheKey);
421
422 if (externalsIndex !== -1) {
423 externals.splice(externalsIndex, 1);
424 break __EXTERNALS_CHECK;
425 }
426
427 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.'));
428 } else {
429 assets.splice(index, 1);
430 }
431
432 cacheResult.push(cacheKey);
433 });
434
435 result[key] = _this3.validatePaths(cacheResult);
436
437 return result;
438 }, {});
439
440 if (restSection && assets.length) {
441 handledCaches[restSection] = handledCaches[restSection].concat(_this3.validatePaths(assets));
442 }
443
444 if (externalsSection && externals.length) {
445 handledCaches[externalsSection] = handledCaches[externalsSection].concat(_this3.validatePaths(externals));
446 }
447
448 _this3.caches = handledCaches;
449 _this3.assets = [].concat(_this3.caches.main, _this3.caches.additional, _this3.caches.optional);
450 })();
451 }
452 }
453 }, {
454 key: 'setHashesMap',
455 value: function setHashesMap(compilation) {
456 var _this4 = this;
457
458 this.hashesMap = {};
459
460 Object.keys(compilation.assets).forEach(function (key) {
461 var validatedPath = _this4.validatePaths([key])[0];
462
463 if (typeof validatedPath !== 'string' || _this4.assets.indexOf(validatedPath) === -1) return;
464
465 var hash = _loaderUtils2['default'].getHashDigest(compilation.assets[key].source(), 'sha1');
466
467 _this4.hashesMap[hash] = validatedPath;
468 });
469 }
470 }, {
471 key: 'setNetworkOptions',
472 value: function setNetworkOptions() {
473 var alwaysRevalidate = this.options.alwaysRevalidate;
474 var preferOnline = this.options.preferOnline;
475 var ignoreSearch = this.options.ignoreSearch;
476
477 var assets = this.assets;
478
479 // Disable temporarily
480 if (Array.isArray(alwaysRevalidate) && alwaysRevalidate.length) {
481 alwaysRevalidate = assets.filter(function (asset) {
482 if (alwaysRevalidate.some(function (glob) {
483 if ((0, _minimatch2['default'])(asset, glob)) {
484 return true;
485 }
486 })) {
487 return true;
488 }
489
490 return false;
491 });
492
493 if (alwaysRevalidate.length) {
494 this.alwaysRevalidate = alwaysRevalidate;
495 }
496 }
497
498 if (Array.isArray(ignoreSearch) && ignoreSearch.length) {
499 ignoreSearch = assets.filter(function (asset) {
500 if (ignoreSearch.some(function (glob) {
501 if ((0, _minimatch2['default'])(asset, glob)) {
502 return true;
503 }
504 })) {
505 return true;
506 }
507
508 return false;
509 });
510
511 if (ignoreSearch.length) {
512 this.ignoreSearch = ignoreSearch;
513 }
514 }
515
516 if (Array.isArray(preferOnline) && preferOnline.length) {
517 preferOnline = assets.filter(function (asset) {
518 if (preferOnline.some(function (glob) {
519 if ((0, _minimatch2['default'])(asset, glob)) {
520 return true;
521 }
522 })) {
523 return true;
524 }
525
526 return false;
527 });
528
529 if (preferOnline.length) {
530 this.preferOnline = preferOnline;
531 }
532 }
533 }
534 }, {
535 key: 'stringifyCacheMaps',
536 value: function stringifyCacheMaps(cacheMaps) {
537 if (!cacheMaps) {
538 return [];
539 }
540
541 return cacheMaps.map(function (map) {
542 if (map.to != null && typeof map.to !== 'string' && typeof map.to !== 'function') {
543 throw new Error('cacheMaps `to` property must either string, function, undefined or null');
544 }
545
546 if (map.requestTypes != null) {
547 if (Array.isArray(map.requestTypes)) {
548 var types = map.requestTypes.filter(function (item) {
549 if (item === 'navigate' || item === 'same-origin' || item === 'cross-origin') {
550 return false;
551 }
552
553 return true;
554 });
555
556 if (types.length) {
557 throw new Error("cacheMaps `requestTypes` array values could be only: 'navigate', 'same-origin' or 'cross-origin'");
558 }
559 } else {
560 throw new Error('cacheMaps `requestTypes` property must either array, undefined or null');
561 }
562 }
563
564 var to = undefined;
565 var match = undefined;
566
567 if (typeof map.to === 'function') {
568 to = (0, _miscUtils.functionToString)(map.to);
569 } else {
570 to = map.to ? JSON.stringify(map.to) : null;
571 }
572
573 if (typeof map.match === 'function') {
574 match = (0, _miscUtils.functionToString)(map.match);
575 } else {
576 match = map.match + '';
577 }
578
579 return {
580 match: match,
581 to: to,
582 requestTypes: map.requestTypes || null
583 };
584 });
585 }
586 }, {
587 key: 'resolveToolPaths',
588 value: function resolveToolPaths(tool, key, compiler) {
589 // Tool much implement:
590 //
591 // tool.output
592 // tool.publicPath
593 // tool.basePath
594 // tool.location
595 // tool.pathRewrite
596
597 if (!this.relativePaths && !this.publicPath) {
598 throw new Error('OfflinePlugin: Cannot generate base path for ' + key);
599 }
600
601 var isDirectory = tool.output[tool.output.length - 1] === '/';
602
603 if (this.relativePaths) {
604 var compilerOutput = (compiler.options.output || { path: process.cwd() }).path;
605 var absoluteOutput = _path3['default'].resolve(compilerOutput, tool.output);
606
607 var relativeBase = undefined;
608
609 if (isDirectory) {
610 relativeBase = _path3['default'].relative(absoluteOutput, compilerOutput);
611 } else {
612 relativeBase = _path3['default'].relative(_path3['default'].dirname(absoluteOutput), compilerOutput);
613 }
614
615 relativeBase = (0, _slash2['default'])(relativeBase);
616 relativeBase = relativeBase.replace(/\/$/, '');
617
618 if (relativeBase) {
619 relativeBase = relativeBase + '/';
620 }
621
622 tool.basePath = relativeBase[0] === '.' ? relativeBase : './' + relativeBase;
623 } else if (this.publicPath) {
624 tool.basePath = this.publicPath.replace(/\/$/, '') + '/';
625 }
626
627 if (this.relativePaths) {
628 tool.location = tool.output;
629 } else if (this.publicPath && tool.publicPath) {
630 tool.location = tool.publicPath;
631 } else if (this.publicPath) {
632 var publicUrl = _url2['default'].parse(this.publicPath);
633 var publicPath = publicUrl.pathname;
634
635 publicUrl.pathname = _path3['default'].join(publicPath, tool.output);
636 var outerPathname = _path3['default'].join('/outer/', publicPath, tool.output);
637
638 if (outerPathname.indexOf('/outer/') !== 0) {
639 new Error('OfflinePlugin: Wrong ' + key + '.output value. Final ' + key + '.location URL path bounds are outside of publicPath');
640 }
641
642 tool.location = _url2['default'].format(publicUrl);
643 }
644
645 if (this.relativePaths) {
646 tool.pathRewrite = function (_path) {
647 if ((0, _miscUtils.isAbsoluteURL)(_path) || _path[0] === '/') {
648 return _path;
649 }
650
651 return tool.basePath + _path;
652 };
653 } else {
654 tool.pathRewrite = function (path) {
655 return path;
656 };
657 }
658 }
659 }, {
660 key: 'validatePaths',
661 value: function validatePaths(assets) {
662 var _this5 = this;
663
664 return assets.map(this.rewrite).filter(function (asset) {
665 return !!asset;
666 }).map(function (key) {
667 // If absolute url, use it as is
668 if ((0, _miscUtils.isAbsoluteURL)(key)) {
669 return key;
670 }
671
672 if (_this5.relativePaths) {
673 return key.replace(/^\.\//, '');
674 }
675
676 // Absolute path, use it as is
677 if (key[0] === '/') {
678 return key;
679 }
680
681 return _this5.publicPath + key.replace(/^\.?\//, '');
682 });
683 }
684 }, {
685 key: 'useTools',
686 value: function useTools(fn) {
687 var _this6 = this;
688
689 var tools = Object.keys(this.tools).map(function (tool) {
690 return fn(_this6.tools[tool], tool);
691 });
692
693 return Promise.all(tools);
694 }
695 }, {
696 key: 'addTool',
697 value: function addTool(Tool, name) {
698 var options = this.options[name];
699
700 if (options === null || options === false) {
701 // tool is not needed
702 return;
703 }
704
705 this.tools[name] = new Tool(options);
706 }
707 }, {
708 key: 'version',
709 get: function get() {
710 var version = this.options.version;
711 var hash = this.hash;
712
713 if (version == null) {
714 return new Date().toLocaleString();
715 }
716
717 if (typeof version === 'function') {
718 return version(this);
719 }
720
721 return (0, _miscUtils.interpolateString)(version, { hash: hash });
722 }
723 }]);
724
725 return OfflinePlugin;
726})();
727
728exports['default'] = OfflinePlugin;
729
730OfflinePlugin.defaultOptions = _defaultOptions2['default'];
731module.exports = exports['default'];
\No newline at end of file