Skip to content

Instantly share code, notes, and snippets.

@mikechau
Last active December 23, 2015 09:53
Show Gist options
  • Save mikechau/a040c345ce470fdd36c8 to your computer and use it in GitHub Desktop.
Save mikechau/a040c345ce470fdd36c8 to your computer and use it in GitHub Desktop.
webpack
/**
* Script based on @roman01la's webpack-sri.
*
* https://github.com/roman01la/webpack-sri
*
*/
'use strict';
var WebpackStats = require('webpack/lib/Stats');
var getSriHash = require('./lib/getSriHash');
var DEFAULT_PARAMS = {
algorithm: 'sha384',
regex: (/\.(js|css)$/i)
}
function SriWebpackPlugin(options) {
var params = options || {};
this.algorithm = params.algorithm || DEFAULT_PARAMS.algorithm;
this.regex = params.regex || DEFAULT_PARAMS.regex;
};
SriWebpackPlugin.prototype.apply = function(compiler) {
var allowedExtensions = this.regex;
var sriAlgorithm = this.algorithm;
var sris = {};
compiler.plugin('this-compilation', function(compilation) {
compilation.plugin('optimize-assets', function(assets, callback) {
Object.keys(assets).forEach(function(file) {
var asset = assets[file];
var content;
var integrity;
if (file.match(allowedExtensions)) {
content = asset.source();
integrity = getSriHash(sriAlgorithm, content);
sris[file] = integrity;
}
});
compilation.__CUSTOM_DATA_SRIS = sris;
callback();
});
});
compiler.plugin('after-emit', function(compilation, callback) {
function StatsWithSri() {
WebpackStats.apply(this, arguments);
};
StatsWithSri.prototype = Object.create(WebpackStats.prototype);
StatsWithSri.prototype.constructor = StatsWithSri;
StatsWithSri.prototype.toJson = function(options, forToString) {
var stats = WebpackStats.prototype.toJson.apply(this, arguments);
if (forToString) { return stats; }
this.sris = compilation.__CUSTOM_DATA_SRIS;
stats.sris = this.sris;
return stats;
}
var stats = new StatsWithSri(compilation);
compilation.getStats = function() {
return stats;
};
callback();
});
}
module.exports = SriWebpackPlugin;
var fs = require('fs');
var path = require('path');
var webpack = require('webpack');
var walk = require('walk');
var Format = require('./format');
/**
* Produce a much slimmer version of webpack stats containing only information
* that you would be interested in when creating an asset manifest.
* @param {string} output - The output file path.
* @param {object} options - Options to configure this plugin.
*/
var ManifestRevisionPlugin = function (output, options) {
this.output = output;
this.options = options;
// Set sane defaults for any options.
this.options.rootAssetPath = options.rootAssetPath || './';
this.options.ignorePaths = options.ignorePaths || [];
this.options.format = options.format || 'general';
this.options.customStatsConfig = options.customStatsConfig || {};
};
/**
* When given a logical asset path, produce an array that contains the logical
* path of the asset without the cache hash included as the first element.
* The second element contains the cached version of the asset name.
* @param {string} logicalAssetPath - The path of the asset without the root.
* @param {string} cachedAsset - The file name of the asset with the cache hash.
* @returns {Array}
*/
ManifestRevisionPlugin.prototype.mapAsset = function (logicalAssetPath, cachedAsset) {
if (logicalAssetPath.charAt(0) === '/') {
logicalAssetPath = logicalAssetPath.substr(1);
}
return [logicalAssetPath, cachedAsset];
};
/**
* Take in the modules array from the webpack stats and produce an object that
* only includes assets that matter to us. The key is the asset logical path
* and the value is the logical path but with the cached asset name.
*
* You would use this as a lookup table in your web server.
* @param {string} data - Array of webpack modules.
* @returns {Object}
*/
ManifestRevisionPlugin.prototype.parsedAssets = function (data) {
var assets = {};
for (var i = 0, length = data.length; i < length; i++) {
var item = data[i];
// Attempt to ignore chunked assets and other unimportant assets.
if (item.name.indexOf('multi ') === -1 &&
item.name.indexOf('~/') === -1 &&
item.reasons.length == 0 &&
fs.lstatSync(item.name).isFile() &&
item.hasOwnProperty('assets') &&
item.assets.length == 1) {
var nameWithoutRoot = item.name.replace(this.options.rootAssetPath,
'');
var mappedAsset = this.mapAsset(nameWithoutRoot, item.assets[0]);
assets[mappedAsset[0]] = mappedAsset[1];
}
}
return assets;
};
/**
* Is this asset safe to be added?
* @param {string} path - The path being checked.
* @returns {boolean}
*/
ManifestRevisionPlugin.prototype.isSafeToTrack = function (path) {
var safeResults = [];
for (var i = 0, length = this.options.ignorePaths.length; i < length; i++) {
if (path.indexOf(this.options.ignorePaths[i]) === -1) {
safeResults.push(true);
} else {
safeResults.push(false);
}
}
// Make sure we have no false entries because we need all trues for it to be
// considered safe.
return safeResults.indexOf(false) === -1;
};
/**
* Walk the assets and optionally filter any unwanted sub-paths. This applies
* the PrefetchPlugin plugin to each asset that's not ignored. This makes it
* available to webpack.
* @param {object} compiler - The webpack compiler.
* @returns {null}
*/
ManifestRevisionPlugin.prototype.walkAndPrefetchAssets = function (compiler) {
var self = this;
var walker_options = {
listeners: {
file: function (root, fileStat, next) {
if (self.isSafeToTrack(root)) {
var assetPath = './' + path.join(root, fileStat.name);
compiler.apply(new webpack.PrefetchPlugin(assetPath));
}
next();
}
}
};
walk.walkSync(this.options.rootAssetPath, walker_options);
};
ManifestRevisionPlugin.prototype.apply = function (compiler) {
var self = this;
var output = this.output;
// Micro optimize toJson by eliminating all of the data we do not need.
var options = {};
options.assets = true;
options.version = false;
options.timings = false;
options.chunks = false;
options.chunkModules = false;
options.cached = true;
options.source = false;
options.errorDetails = false;
options.chunkOrigins = false;
self.walkAndPrefetchAssets(compiler);
compiler.plugin('done', function (stats) {
var data = stats.toJson(options);
var parsedAssets = self.parsedAssets(data.modules);
var outputData = null;
console.log('---->', Object.keys(data));
if (typeof self.options.format === 'string' ||
self.options.format instanceof String) {
var format = new Format(data, parsedAssets, customStats);
outputData = format[self.options.format]();
}
else {
outputData = self.options.format(data, parsedAssets, customStats);
}
if (typeof outputData === 'object') {
outputData = JSON.stringify(outputData);
}
fs.writeFileSync(output, String(outputData));
});
};
module.exports = ManifestRevisionPlugin;
[ 'errors',
'warnings',
'version',
'hash',
'time',
'publicPath',
'assetsByChunkName',
'assets',
'chunks',
'modules',
'filteredModules',
'children',
'sris' ]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment