UNPKG

4.6 kBJavaScriptView Raw
1'use strict'; // eslint-disable-line strict
2
3const os = require('os');
4const mkdirp = require('mkdirp');
5const webpackSources = require('webpack-sources');
6const workerFarm = require('worker-farm');
7const pify = require('pify');
8const ModuleFilenameHelpers = require('webpack/lib/ModuleFilenameHelpers');
9
10const SourceMapSource = webpackSources.SourceMapSource;
11const RawSource = webpackSources.RawSource;
12const cache = require('./cache');
13const tmpFile = require('./tmp-file');
14/**
15 * Determines how many workers to create.
16 * Should be available cpus minus 1 or the number of assets to minify, whichever is smaller.
17 */
18function workerCount(options, assetCount) {
19 if (options.workerCount) {
20 return options.workerCount;
21 }
22 return Math.min(assetCount, Math.max(1, os.cpus().length - 1));
23}
24
25const usedCacheKeys = [];
26
27function processAssets(compilation, options) {
28 const assetHash = compilation.assets;
29 const useSourceMaps = options.sourceMap || false;
30 if (options.cacheDir) {
31 mkdirp.sync(options.cacheDir);
32 }
33
34 // Create a copy of the options object that omits the cacheDir field. This is necessary because
35 // we include the options object when creating cache keys, and some cache directory paths may not
36 // be stable across multiple runs.
37 const optionsWithoutCacheDir = Object.assign({}, options);
38 optionsWithoutCacheDir.cacheDir = undefined;
39
40 // By default the `test` config should match every file ending in .js
41 options.test = options.test || /\.js$/i; // eslint-disable-line no-param-reassign
42 const assets = Object.keys(assetHash)
43 .filter(ModuleFilenameHelpers.matchObject.bind(null, options));
44
45 // For assets that are cached, we read from the cache here rather than doing so in the worker.
46 // This is a relatively fast operation, so this lets us avoid creating several workers in cases
47 // when we have a near 100% cache hit rate.
48 const cacheKeysOnDisk = new Set(cache.getCacheKeysFromDisk(options.cacheDir));
49 const uncachedAssets = [];
50
51 assets.forEach((assetName) => {
52 // sourceAndMap() is an expensive function, so we'll create the cache key from just source(),
53 // and whether or not we should be using source maps.
54 const source = assetHash[assetName].source();
55 const cacheKey = cache.createCacheKey(source + useSourceMaps, optionsWithoutCacheDir);
56 usedCacheKeys.push(cacheKey);
57 if (cacheKeysOnDisk.has(cacheKey)) {
58 // Cache hit, so let's read from the disk and mark this cache key as used.
59 const content = JSON.parse(cache.retrieveFromCache(cacheKey, options.cacheDir));
60 if (content.map) {
61 assetHash[assetName] = new SourceMapSource(content.source, assetName, content.map);
62 } else {
63 assetHash[assetName] = new RawSource(content.source);
64 }
65 } else {
66 // Cache miss, so we'll need to minify this in a worker.
67 uncachedAssets.push(assetName);
68 }
69 });
70
71 const farm = workerFarm({
72 autoStart: true,
73 maxConcurrentCallsPerWorker: 1,
74 maxConcurrentWorkers: workerCount(options, uncachedAssets.length),
75 maxRetries: 2, // Allow for a couple of transient errors.
76 },
77 require.resolve('./worker'),
78 ['processMessage']
79 );
80
81 const minify = pify(farm.processMessage);
82
83 const minificationPromises = uncachedAssets.map((assetName) => {
84 const asset = assetHash[assetName];
85 const tmpFileName = tmpFile.create(JSON.stringify({
86 assetName,
87 options: optionsWithoutCacheDir,
88 source: asset.source(),
89 map: useSourceMaps ? asset.map() : null,
90 cacheDir: options.cacheDir,
91 }));
92
93 return minify(tmpFileName)
94 .then(() => {
95 const content = tmpFile.read(tmpFileName);
96 const msg = JSON.parse(content);
97 if (msg.map) {
98 assetHash[assetName] = new SourceMapSource(msg.source, assetName, msg.map); // eslint-disable-line no-param-reassign, max-len
99 } else {
100 assetHash[assetName] = new RawSource(msg.source); // eslint-disable-line no-param-reassign, max-len
101 }
102 })
103 .catch((e) => {
104 const builtError = new Error(`Encountered an error while minifying ${assetName}:\n${e}`);
105 compilation.errors.push(builtError);
106 });
107 });
108
109 function endWorkers() {
110 workerFarm.end(farm); // at this point we're done w/ the farm, it can be killed
111 }
112
113 return Promise.all(minificationPromises)
114 .then(endWorkers)
115 .catch(endWorkers);
116}
117
118function pruneCache(options) {
119 cache.pruneCache(usedCacheKeys, cache.getCacheKeysFromDisk(options.cacheDir), options.cacheDir);
120}
121
122module.exports = {
123 processAssets,
124 pruneCache,
125 workerCount,
126};