Merge pull request #279 from ampedandwired/feature/hash-names

Support hashes in filenames
This commit is contained in:
Jan Nicklas 2016-04-18 13:16:02 +02:00
commit 37bf50d662
5 changed files with 65 additions and 16 deletions

View File

@ -1,6 +1,10 @@
Change History
==============
v2.16.0
----
* Add support for dynamic filenames like index[hash].html
v2.15.0
----
* Add full unit test coverage for the webpack 2 beta version

View File

@ -48,13 +48,15 @@ HtmlWebpackPlugin.prototype.apply = function (compiler) {
.catch(function (err) {
compilation.errors.push(prettyError(err, compiler.context).toString());
return {
content: self.options.showErrors ? prettyError(err, compiler.context).toJsonHtml() : 'ERROR'
content: self.options.showErrors ? prettyError(err, compiler.context).toJsonHtml() : 'ERROR',
outputName: self.options.filename
};
})
.then(function (compilationResult) {
// If the compilation change didnt change the cache is valid
isCompilationCached = compilationResult.hash && self.childCompilerHash === compilationResult.hash;
self.childCompilerHash = compilationResult.hash;
self.childCompilationOutputName = compilationResult.outputName;
callback();
return compilationResult.content;
});
@ -107,10 +109,14 @@ HtmlWebpackPlugin.prototype.apply = function (compiler) {
// Allow plugins to make changes to the assets before invoking the template
// This only makes sense to use if `inject` is `false`
.then(function (compilationResult) {
return applyPluginsAsyncWaterfall('html-webpack-plugin-before-html-generation', {assets: assets, plugin: self})
.then(function () {
return compilationResult;
});
return applyPluginsAsyncWaterfall('html-webpack-plugin-before-html-generation', {
assets: assets,
outputName: self.childCompilationOutputName,
plugin: self
})
.then(function () {
return compilationResult;
});
})
// Execute the template
.then(function (compilationResult) {
@ -122,7 +128,7 @@ HtmlWebpackPlugin.prototype.apply = function (compiler) {
})
// Allow plugins to change the html before assets are injected
.then(function (html) {
var pluginArgs = {html: html, assets: assets, plugin: self};
var pluginArgs = {html: html, assets: assets, plugin: self, outputName: self.childCompilationOutputName};
return applyPluginsAsyncWaterfall('html-webpack-plugin-before-html-processing', pluginArgs)
.then(function () {
return pluginArgs.html;
@ -134,7 +140,7 @@ HtmlWebpackPlugin.prototype.apply = function (compiler) {
})
// Allow plugins to change the html after assets are injected
.then(function (html) {
var pluginArgs = {html: html, assets: assets, plugin: self};
var pluginArgs = {html: html, assets: assets, plugin: self, outputName: self.childCompilationOutputName};
return applyPluginsAsyncWaterfall('html-webpack-plugin-after-html-processing', pluginArgs)
.then(function () {
return pluginArgs.html;
@ -150,7 +156,7 @@ HtmlWebpackPlugin.prototype.apply = function (compiler) {
})
.then(function (html) {
// Replace the compilation result with the evaluated html code
compilation.assets[self.options.filename] = {
compilation.assets[self.childCompilationOutputName] = {
source: function () {
return html;
},
@ -162,8 +168,14 @@ HtmlWebpackPlugin.prototype.apply = function (compiler) {
.then(function () {
// Let other plugins know that we are done:
return applyPluginsAsyncWaterfall('html-webpack-plugin-after-emit', {
html: compilation.assets[self.options.filename],
html: compilation.assets[self.childCompilationOutputName],
outputName: self.childCompilationOutputName,
plugin: self
}).catch(function (err) {
console.error(err);
return null;
}).then(function () {
return null;
});
})
// Let webpack continue with it
@ -349,7 +361,7 @@ HtmlWebpackPlugin.prototype.htmlWebpackPluginAssets = function (compilation, chu
// If a hard coded public path exists use it
? compilation.mainTemplate.getPublicPath({hash: webpackStatsJson.hash})
// If no public path was set get a relative url path
: path.relative(path.resolve(compilation.options.output.path, path.dirname(self.options.filename)), compilation.options.output.path)
: path.relative(path.resolve(compilation.options.output.path, path.dirname(self.childCompilationOutputName)), compilation.options.output.path)
.split(path.sep).join('/');
if (publicPath.length && publicPath.substr(-1, 1) !== '/') {

View File

@ -6,6 +6,7 @@
*/
'use strict';
var Promise = require('bluebird');
var _ = require('lodash');
var path = require('path');
var NodeTemplatePlugin = require('webpack/lib/node/NodeTemplatePlugin');
var NodeTargetPlugin = require('webpack/lib/node/NodeTargetPlugin');
@ -36,7 +37,8 @@ module.exports.compileTemplate = function compileTemplate (template, context, ou
filename: outputFilename,
publicPath: compilation.outputOptions.publicPath
};
var cachedAsset = compilation.assets[outputOptions.filename];
// Store the result of the parent compilation before we start the child compilation
var assetsBeforeCompilation = _.assign({}, compilation.assets[outputOptions.filename]);
// Create an additional child compiler which takes the template
// and turns it into an Node.JS html factory.
// This allows us to use loaders during the compilation
@ -66,9 +68,17 @@ module.exports.compileTemplate = function compileTemplate (template, context, ou
// Compile and return a promise
return new Promise(function (resolve, reject) {
childCompiler.runAsChild(function (err, entries, childCompilation) {
compilation.assets[outputOptions.filename] = cachedAsset;
if (cachedAsset === undefined) {
delete compilation.assets[outputOptions.filename];
// Replace [hash] placeholders in filename
var outputName = compilation.mainTemplate.applyPluginsWaterfall('asset-path', outputOptions.filename, {
hash: childCompilation.hash,
chunk: entries[0]
});
// Restore the parent compilation to the state like it
// was before the child compilation
compilation.assets[outputName] = assetsBeforeCompilation[outputName];
if (assetsBeforeCompilation[outputName] === undefined) {
// If it wasn't there - delete it
delete compilation.assets[outputName];
}
// Resolve / reject the promise
if (childCompilation && childCompilation.errors && childCompilation.errors.length) {
@ -82,8 +92,10 @@ module.exports.compileTemplate = function compileTemplate (template, context, ou
resolve({
// Hash of the template entry point
hash: entries[0].hash,
// Output name
outputName: outputName,
// Compiled code
content: childCompilation.assets[outputOptions.filename].source()
content: childCompilation.assets[outputName].source()
});
}
});

View File

@ -1,6 +1,6 @@
{
"name": "html-webpack-plugin",
"version": "2.15.0",
"version": "2.16.0",
"description": "Simplifies creation of HTML files to serve your webpack bundles",
"main": "index.js",
"files": [

View File

@ -38,6 +38,14 @@ function testHtmlPlugin (webpackConfig, expectedResults, outputFile, done, expec
} else {
expect(compilationWarnings).toBe('');
}
if (outputFile instanceof RegExp) {
var matches = Object.keys(stats.compilation.assets).filter(function (item) {
return outputFile.test(item);
});
expect(matches.length).toBe(1);
outputFile = matches[0];
}
expect(outputFile.indexOf('[hash]') === -1).toBe(true);
var outputFileExists = fs.existsSync(path.join(OUTPUT_DIR, outputFile));
expect(outputFileExists).toBe(true);
if (!outputFileExists) {
@ -573,6 +581,19 @@ describe('HtmlWebpackPlugin', function () {
}, ['<script src="index_bundle.js"'], 'test.html', done);
});
it('will replace [hash] in the filename with the child compilation hash', function (done) {
testHtmlPlugin({
entry: path.join(__dirname, 'fixtures/index.js'),
output: {
path: OUTPUT_DIR,
filename: 'index_bundle.js'
},
plugins: [new HtmlWebpackPlugin({
filename: 'test-[hash].html'
})]
}, ['<script src="index_bundle.js"'], /test-\S+\.html$/, done);
});
it('allows you to use an absolute output filename', function (done) {
testHtmlPlugin({
entry: path.join(__dirname, 'fixtures/index.js'),