Move compiler to its own file
Improve error messages Fix global HTML_WEBPACK_PLUGIN variable
This commit is contained in:
parent
54528a5b41
commit
1622c7d2e2
|
|
@ -68,6 +68,8 @@ Allowed values are as follows:
|
|||
- `minify`: `{...} | false` Pass a [html-minifier](https://github.com/kangax/html-minifier#options-quick-reference) options object to minify the output.
|
||||
- `hash`: `true | false` if `true` then append a unique webpack compilation hash to all
|
||||
included scripts and css files. This is useful for cache busting.
|
||||
- `cache`: `true | false` if `true` (default) try to emit the file only if it was changed.
|
||||
- `showErrors`: `true | false` if `true` (default) errors details will be written into the html page.
|
||||
- `chunks`: Allows you to add only some chunks (e.g. only the unit-test chunk)
|
||||
- `chunksSortMode`: Allows to controll how chunks should be sorted before they are included to the html. Allowed values: 'none' | 'default' | {function} - default: 'auto'
|
||||
- `excludeChunks`: Allows you to skip some chunks (e.g. don't add the unit-test chunk)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ module.exports = {
|
|||
{ test: /\.jade$/, loader: 'jade'}
|
||||
]
|
||||
},
|
||||
devtool: 'eval',
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({
|
||||
filename: 'index.html',
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
# isomorphic javascript example
|
||||
|
||||
This example shows how to generate a template on the fly using javascript.
|
||||
This example shows how to generate a template on the fly using javascript.
|
||||
|
||||
The best way to debug the compilation result is `devTool:eval`
|
||||
|
|
@ -1,5 +1,10 @@
|
|||
// This file is used for frontend and backend
|
||||
'use strict';
|
||||
|
||||
// If compiled by the html-webpack-plugin
|
||||
// HTML_WEBPACK_PLUGIN is set to true:
|
||||
var backend = typeof HTML_WEBPACK_PLUGIN !== 'undefined';
|
||||
|
||||
module.exports = function() {
|
||||
return "Hello World";
|
||||
return 'Hello World from ' + (backend ? 'backend' : 'frontend');
|
||||
};
|
||||
|
|
@ -14,6 +14,7 @@ module.exports = {
|
|||
{ test: /\.html$/, loader: 'html-loader' }
|
||||
]
|
||||
},
|
||||
devtool: 'eval',
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({
|
||||
template: 'template.js'
|
||||
|
|
|
|||
115
index.js
115
index.js
|
|
@ -4,15 +4,10 @@ var fs = require('fs');
|
|||
var _ = require('lodash');
|
||||
var Promise = require('bluebird');
|
||||
var path = require('path');
|
||||
var childCompiler = require('./lib/compiler.js');
|
||||
var prettyError = require('./lib/errors.js');
|
||||
Promise.promisifyAll(fs);
|
||||
|
||||
var webpack = require('webpack');
|
||||
var NodeTemplatePlugin = require('webpack/lib/node/NodeTemplatePlugin');
|
||||
var NodeTargetPlugin = require('webpack/lib/node/NodeTargetPlugin');
|
||||
var LoaderTargetPlugin = require('webpack/lib/LoaderTargetPlugin');
|
||||
var LibraryTemplatePlugin = require('webpack/lib/LibraryTemplatePlugin');
|
||||
var SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin');
|
||||
|
||||
function HtmlWebpackPlugin(options) {
|
||||
// Default options
|
||||
this.options = _.extend({
|
||||
|
|
@ -24,13 +19,14 @@ function HtmlWebpackPlugin(options) {
|
|||
favicon: false,
|
||||
minify: false,
|
||||
cache: true,
|
||||
showErrors: true,
|
||||
chunks: 'all',
|
||||
excludeChunks: [],
|
||||
title: 'Webpack App'
|
||||
}, options);
|
||||
// If the template doesn't use a loader use the lodash template loader
|
||||
if(this.options.template.indexOf('!') === -1) {
|
||||
this.options.template = require.resolve('./loader.js') + '!' + path.resolve(this.options.template);
|
||||
this.options.template = require.resolve('./lib/loader.js') + '!' + path.resolve(this.options.template);
|
||||
}
|
||||
// Resolve template path
|
||||
this.options.template = this.options.template.replace(
|
||||
|
|
@ -42,17 +38,26 @@ function HtmlWebpackPlugin(options) {
|
|||
|
||||
HtmlWebpackPlugin.prototype.apply = function(compiler) {
|
||||
var self = this;
|
||||
var isCompilationCached = false;
|
||||
var compilationPromise;
|
||||
self.context = compiler.context;
|
||||
|
||||
compiler.plugin('make', function(compilation, callback) {
|
||||
// Compile the template (queued)
|
||||
compilationPromise = getNextCompilationSlot(compiler, function() {
|
||||
return self.compileTemplate(self.options.template, self.options.filename, compilation)
|
||||
return childCompiler.compileTemplate(self.options.template, compiler.context, self.options.filename, compilation)
|
||||
.catch(function(err) {
|
||||
return new Error(err);
|
||||
compilation.errors.push(prettyError(err, compiler.context).toString());
|
||||
return {
|
||||
content: self.options.showErrors ? prettyError(err, compiler.context).toJsonHtml() : 'ERROR',
|
||||
};
|
||||
})
|
||||
.finally(callback);
|
||||
.then(function(compilationResult) {
|
||||
// If the compilation change didnt change the cache is valid
|
||||
isCompilationCached = compilationResult.hash && self.hash === compilationResult.hash;
|
||||
self.hash = compilation.hash;
|
||||
callback();
|
||||
return compilationResult.content;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -73,7 +78,7 @@ HtmlWebpackPlugin.prototype.apply = function(compiler) {
|
|||
|
||||
// If the template and the assets did not change we don't have to emit the html
|
||||
var assetJson = JSON.stringify(assets);
|
||||
if (self.options.cache && !self.built && assetJson === self.assetJson) {
|
||||
if (isCompilationCached && self.options.cache && assetJson === self.assetJson) {
|
||||
return callback();
|
||||
} else {
|
||||
self.assetJson = assetJson;
|
||||
|
|
@ -94,9 +99,6 @@ HtmlWebpackPlugin.prototype.apply = function(compiler) {
|
|||
return compilationPromise;
|
||||
})
|
||||
.then(function(compiledTemplate) {
|
||||
if (compiledTemplate instanceof Error) {
|
||||
return Promise.reject(compiledTemplate);
|
||||
}
|
||||
// Allow to use a custom function / string instead
|
||||
if (self.options.templateContent) {
|
||||
return self.options.templateContent;
|
||||
|
|
@ -135,9 +137,8 @@ HtmlWebpackPlugin.prototype.apply = function(compiler) {
|
|||
.catch(function(err) {
|
||||
// In case anything went wrong the promise is resolved
|
||||
// with the error message and an error is logged
|
||||
var errorMessage = "HtmlWebpackPlugin " + err;
|
||||
compilation.errors.push(new Error(errorMessage));
|
||||
return errorMessage;
|
||||
compilation.errors.push(prettyError(err, compiler.context).toString());
|
||||
return self.options.showErrors ? prettyError(err, compiler.context).toHtml() : 'ERROR';
|
||||
})
|
||||
.then(function(html) {
|
||||
// Replace the compilation result with the evaluated html code
|
||||
|
|
@ -168,74 +169,15 @@ HtmlWebpackPlugin.prototype.apply = function(compiler) {
|
|||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the child compiler name
|
||||
*/
|
||||
HtmlWebpackPlugin.prototype.getCompilerName = function() {
|
||||
var absolutePath = path.resolve(this.context, this.options.filename);
|
||||
var relativePath = path.relative(this.context, absolutePath);
|
||||
return 'html-webpack-plugin for "' + (absolutePath.length < relativePath.length ? absolutePath : relativePath) + '"';
|
||||
};
|
||||
|
||||
/**
|
||||
* Compiles the template into a nodejs factory, adds its to the compilation.assets
|
||||
* and returns a promise of the result asset object.
|
||||
*/
|
||||
HtmlWebpackPlugin.prototype.compileTemplate = function(template, outputFilename, compilation) {
|
||||
// The entry file is just an empty helper as the dynamic template
|
||||
// require is added in "loader.js"
|
||||
var outputOptions = {
|
||||
filename: outputFilename,
|
||||
publicPath: compilation.outputOptions.publicPath
|
||||
};
|
||||
var cachedAsset = 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
|
||||
var compilerName = this.getCompilerName();
|
||||
var childCompiler = compilation.createChildCompiler(compilerName, outputOptions);
|
||||
childCompiler.apply(
|
||||
new NodeTemplatePlugin(outputOptions),
|
||||
new NodeTargetPlugin(),
|
||||
new LibraryTemplatePlugin('HTML_WEBPACK_PLUGIN_RESULT', 'var'),
|
||||
new SingleEntryPlugin(this.context, template),
|
||||
new LoaderTargetPlugin('node'),
|
||||
new webpack.DefinePlugin({ HTML_WEBPACK_PLUGIN : 'true' })
|
||||
);
|
||||
|
||||
// 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];
|
||||
}
|
||||
// Resolve / reject the promise
|
||||
if (childCompilation.errors && childCompilation.errors.length) {
|
||||
var errorDetails = childCompilation.errors.map(function(error) {
|
||||
return error.message + (error.error ? ':\n' + error.error: '');
|
||||
}).join('\n');
|
||||
|
||||
reject('Child compilation failed:\n' + errorDetails);
|
||||
} else {
|
||||
this.built = this.hash !== entries[0].hash;
|
||||
this.hash = entries[0].hash;
|
||||
resolve(childCompilation.assets[outputOptions.filename]);
|
||||
}
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
* Evaluates the child compilation result
|
||||
* Returns a promise
|
||||
*/
|
||||
HtmlWebpackPlugin.prototype.evaluateCompilationResult = function(compilation, compilationResult) {
|
||||
if(!compilationResult) {
|
||||
HtmlWebpackPlugin.prototype.evaluateCompilationResult = function(compilation, source) {
|
||||
if (!source) {
|
||||
return Promise.reject('The child compilation didn\'t provide a result');
|
||||
}
|
||||
|
||||
var source = compilationResult.source();
|
||||
// The LibraryTemplatePlugin stores the template result in a local variable.
|
||||
// To extract the result during the evaluation this part has to be removed.
|
||||
source = source.replace('var HTML_WEBPACK_PLUGIN_RESULT =', '');
|
||||
|
|
@ -243,15 +185,8 @@ HtmlWebpackPlugin.prototype.evaluateCompilationResult = function(compilation, co
|
|||
// Evaluate code and cast to string
|
||||
var newSource;
|
||||
try {
|
||||
newSource = vm.runInThisContext(source);
|
||||
newSource = vm.runInNewContext(source, {HTML_WEBPACK_PLUGIN: true}, {filename: 'html-plugin-evaluation'});
|
||||
} catch (e) {
|
||||
// Log syntax error
|
||||
var syntaxError = require('syntax-error')(source);
|
||||
var errorMessage = 'Template compilation failed: ' + e +
|
||||
(syntaxError ? '\n' + syntaxError + '\n\n\n' + source.split('\n').map(function(row, i) {
|
||||
return (1 + i) + ' - ' + row;
|
||||
}).join('\n') : '');
|
||||
compilation.errors.push(new Error(errorMessage));
|
||||
return Promise.reject(e);
|
||||
}
|
||||
return typeof newSource === 'string' || typeof newSource === 'function' ?
|
||||
|
|
@ -556,8 +491,8 @@ HtmlWebpackPlugin.prototype.appendHash = function (url, hash) {
|
|||
};
|
||||
|
||||
/**
|
||||
* Helper to prevent compilation in parallel
|
||||
* Fixes an issue where incomplete cache modules were used
|
||||
* Helper to prevent html-plugin compilation in parallel
|
||||
* Fixes "No source available" where incomplete cache modules were used
|
||||
*/
|
||||
function getNextCompilationSlot(compiler, newEntry) {
|
||||
compiler.HtmlWebpackPluginQueue = (compiler.HtmlWebpackPluginQueue || Promise.resolve())
|
||||
|
|
|
|||
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* This file uses webpack to compile a template with a child compiler.
|
||||
*
|
||||
* [TEMPLATE] -> [JAVASCRIPT]
|
||||
*
|
||||
*/
|
||||
'use strict';
|
||||
var Promise = require('bluebird');
|
||||
var path = require('path');
|
||||
var webpack = require('webpack');
|
||||
var NodeTemplatePlugin = require('webpack/lib/node/NodeTemplatePlugin');
|
||||
var NodeTargetPlugin = require('webpack/lib/node/NodeTargetPlugin');
|
||||
var LoaderTargetPlugin = require('webpack/lib/LoaderTargetPlugin');
|
||||
var LibraryTemplatePlugin = require('webpack/lib/LibraryTemplatePlugin');
|
||||
var SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin');
|
||||
|
||||
/**
|
||||
* Compiles the template into a nodejs factory, adds its to the compilation.assets
|
||||
* and returns a promise of the result asset object.
|
||||
*
|
||||
* @param template relative path to the template file
|
||||
* @param context path context
|
||||
* @param outputFilename the file name
|
||||
* @param compilation The webpack compilation object
|
||||
*
|
||||
* Returns an object:
|
||||
* {
|
||||
* hash: {String} - Base64 hash of the file
|
||||
* content: {String} - Javascript executable code of the template
|
||||
* }
|
||||
*
|
||||
*/
|
||||
module.exports.compileTemplate = function compileTemplate(template, context, outputFilename, compilation) {
|
||||
// The entry file is just an empty helper as the dynamic template
|
||||
// require is added in "loader.js"
|
||||
var outputOptions = {
|
||||
filename: outputFilename,
|
||||
publicPath: compilation.outputOptions.publicPath
|
||||
};
|
||||
var cachedAsset = 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
|
||||
var compilerName = getCompilerName(context, outputFilename);
|
||||
var childCompiler = compilation.createChildCompiler(compilerName, outputOptions);
|
||||
childCompiler.apply(
|
||||
new NodeTemplatePlugin(outputOptions),
|
||||
new NodeTargetPlugin(),
|
||||
new LibraryTemplatePlugin('HTML_WEBPACK_PLUGIN_RESULT', 'var'),
|
||||
new SingleEntryPlugin(this.context, template),
|
||||
new LoaderTargetPlugin('node')
|
||||
);
|
||||
|
||||
// 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];
|
||||
}
|
||||
// Resolve / reject the promise
|
||||
if (childCompilation.errors && childCompilation.errors.length) {
|
||||
var errorDetails = childCompilation.errors.map(function(error) {
|
||||
return error.message + (error.error ? ':\n' + error.error: '');
|
||||
}).join('\n');
|
||||
reject(new Error('Child compilation failed:\n' + errorDetails));
|
||||
} else {
|
||||
resolve({
|
||||
// Hash of the template entry point
|
||||
hash: entries[0].hash,
|
||||
// Compiled code
|
||||
content: childCompilation.assets[outputOptions.filename].source()
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the child compiler name e.g. 'html-webpack-plugin for "index.html"'
|
||||
*/
|
||||
function getCompilerName (context, filename) {
|
||||
var absolutePath = path.resolve(context, filename);
|
||||
var relativePath = path.relative(context, absolutePath);
|
||||
return 'html-webpack-plugin for "' + (absolutePath.length < relativePath.length ? absolutePath : relativePath) + '"';
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
'use strict';
|
||||
var PrettyError = require('pretty-error');
|
||||
var prettyError = new PrettyError();
|
||||
prettyError.withoutColors();
|
||||
prettyError.skipPackage(['html-plugin-evaluation']);
|
||||
prettyError.skipNodeFiles();
|
||||
prettyError.skip(function(traceLine) {
|
||||
return traceLine.path === 'html-plugin-evaluation';
|
||||
});
|
||||
|
||||
module.exports = function(err, context) {
|
||||
return {
|
||||
toHtml: function() {
|
||||
return 'Html Webpack Plugin:\n<pre>\n' + this.toString() + '</pre>';
|
||||
},
|
||||
toJsonHtml: function() {
|
||||
return JSON.stringify(this.toHtml());
|
||||
},
|
||||
toString: function() {
|
||||
return prettyError.render(err).replace(/webpack:\/\/\/\./g, context);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
/* This loader renders the template with underscore if no other loader was found */
|
||||
'use strict';
|
||||
|
||||
var _ = require('lodash');
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "html-webpack-plugin",
|
||||
"version": "2.5.0",
|
||||
"version": "2.6.0",
|
||||
"description": "Simplifies creation of HTML files to serve your webpack bundles",
|
||||
"main": "index.js",
|
||||
"files": [
|
||||
|
|
@ -45,10 +45,10 @@
|
|||
"webpack": "^1.12.10"
|
||||
},
|
||||
"dependencies": {
|
||||
"loader-utils": "^0.2.12",
|
||||
"syntax-error": "^1.1.4",
|
||||
"bluebird": "^3.1.1",
|
||||
"html-minifier": "^1.1.1",
|
||||
"lodash": "^3.10.1"
|
||||
"loader-utils": "^0.2.12",
|
||||
"lodash": "^3.10.1",
|
||||
"pretty-error": "^2.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ describe('HtmlWebpackPlugin', function() {
|
|||
template: path.join(__dirname, 'fixtures/invalid.html')
|
||||
})]
|
||||
},
|
||||
['HtmlWebpackPlugin ReferenceError: foo is not defined'], null, done, true);
|
||||
['ReferenceError: foo is not defined'], null, done, true);
|
||||
});
|
||||
|
||||
it('uses a custom loader from webpacks config', function(done) {
|
||||
|
|
@ -840,7 +840,7 @@ describe('HtmlWebpackPlugin', function() {
|
|||
template: path.join(__dirname, 'fixtures/non-existing-template.html')
|
||||
})
|
||||
]
|
||||
}, ["HtmlWebpackPlugin Error: Child compilation failed:\nEntry module not found: Error: Cannot resolve 'file' or 'directory'"], null, done, true);
|
||||
}, ["Child compilation failed:\n Entry module not found: Error: Cannot resolve 'file' or 'directory'"], null, done, true);
|
||||
});
|
||||
|
||||
it('should short the chunks', function(done) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue