Compare commits

...

18 Commits

Author SHA1 Message Date
Jan Nicklas 74909e4c94 Pre release 2.0 2015-06-30 11:12:24 +02:00
Jan Nicklas 571407f960 Fix demos 2015-06-29 17:15:41 +02:00
Jan Nicklas 4f1ab08827 Cache loader result 2015-06-29 17:04:36 +02:00
Jan Nicklas 8e89ea23de Use lodash to solve dependency issues for the fallback loader 2015-06-29 16:52:50 +02:00
Jan Nicklas 7f63ba8988 Fix correct autoloading 2015-06-29 16:09:39 +02:00
Jan Nicklas 9e313ef6d1 Fix rebase 2015-06-29 16:08:54 +02:00
Jan Nicklas a5b10196be Fix build 2015-06-29 16:06:51 +02:00
Jan Nicklas 58a3414a36 Refactor examples 2015-06-29 16:06:51 +02:00
Jan Nicklas 2fb24b29be Use blueimp loader instead of blueimp 2015-06-29 16:06:51 +02:00
Jan Nicklas a1442f2a54 Fix webpack-dev-server child compilation on template change 2015-06-29 16:05:24 +02:00
Jan Nicklas d91c306c81 Fix chunk filtering 2015-06-29 16:05:24 +02:00
Jan Nicklas 3066de978e Refactor template loading 2015-06-29 16:04:22 +02:00
Kenny Tran 94fc6c98af Fix typo 2015-06-29 16:02:52 +02:00
Jan Nicklas e56b2fb600 Move compilation into a new function 2015-06-29 16:02:52 +02:00
Jan Nicklas c5033f9d95 Fix image example 2015-06-29 16:02:52 +02:00
Jan Nicklas d793886a5e Fix to resolve path of modules 2015-06-29 16:02:52 +02:00
Jan Nicklas 998ef87cf7 Add minification again 2015-06-29 16:02:52 +02:00
Jan Nicklas 79b0e692f0 Refactoring to support loaders 2015-06-29 16:02:52 +02:00
35 changed files with 478 additions and 292 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
/node_modules/
/dist/
examples/*/dist

View File

@ -1,11 +1,11 @@
HTML Webpack Plugin
===================
===================
[![npm version](https://badge.fury.io/js/html-webpack-plugin.svg)](http://badge.fury.io/js/html-webpack-plugin) [![Dependency Status](https://david-dm.org/ampedandwired/html-webpack-plugin.svg)](https://david-dm.org/ampedandwired/html-webpack-plugin) [![bitHound Score](https://www.bithound.io/github/ampedandwired/html-webpack-plugin/badges/score.svg)](https://www.bithound.io/github/ampedandwired/html-webpack-plugin) [![Build status](https://travis-ci.org/ampedandwired/html-webpack-plugin.svg)](https://travis-ci.org/ampedandwired/html-webpack-plugin)
This is a [webpack](http://webpack.github.io/) plugin that simplifies creation of HTML files to serve your
webpack bundles. This is especially useful for webpack bundles that include
a hash in the filename which changes every compilation. You can either let the plugin generate an HTML file for you or supply
your own template (using [blueimp templates](https://github.com/blueimp/JavaScript-Templates)).
your own template (using lodash/ejs templates.
Installation
------------
@ -63,15 +63,14 @@ Allowed values are as follows:
- `title`: The title to use for the generated HTML document.
- `filename`: The file to write the HTML to. Defaults to `index.html`.
You can specify a subdirectory here too (eg: `assets/admin.html`).
- `template`: A html template (supports [blueimp templates](https://github.com/blueimp/JavaScript-Templates)).
- `templateContent`: A html string or a function returning the html (supports [blueimp templates](https://github.com/blueimp/JavaScript-Templates)).
- `template`: Path to the template. Supports loaders e.g. `html!./index.html`.
- `inject`: `true | 'head' | 'body' | false` Inject all assets into the given `template` or `templateContent` - When passing `true` or `'body'` all javascript resources will be placed at the bottom of the body element. `'head'` will place the scripts in the head element.
- `favicon`: Adds the given favicon path to the output html.
- `minify`: `true | {...} | false` Set to true or 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.
- `chunks`: Allows you to add only some chunks (e.g. only the unit-test chunk)
- `excludeChunks`: Allows you to skip some chunks (e.g. don't add the unit-test chunk)
- `excludeChunks`: Allows you to skip some chunks (e.g. don't add the unit-test chunk)
Here's an example webpack config illustrating how to use these options:
```javascript
@ -122,7 +121,7 @@ and favicon files into the markup.
```javascript
plugins: [
new HtmlWebpackPlugin({
title: 'Custom template',
title: 'Custom template',
template: 'my-index.html', // Load a custom template
inject: 'body' // Inject all scripts into the body
})
@ -136,7 +135,7 @@ plugins: [
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
<title>{%=o.htmlWebpackPlugin.options.title}</title>
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
</body>
@ -154,7 +153,7 @@ plugins: [
]
```
You can use the [blueimp template](https://github.com/blueimp/JavaScript-Templates) syntax out of the box.
You can use the lodash/ejs syntax out of the box.
If the `inject` feature doesn't fit your needs and you want full control over the asset placement use the [default template](https://github.com/ampedandwired/html-webpack-plugin/blob/master/default_index.html)
as a starting point for writing your own.

View File

@ -1,18 +1,9 @@
<!DOCTYPE html>
<html{% if(o.htmlWebpackPlugin.files.manifest) { %} manifest="{%= o.htmlWebpackPlugin.files.manifest %}"{% } %}>
<html>
<head>
<meta charset="UTF-8">
<title>{%=o.htmlWebpackPlugin.options.title || 'Webpack App'%}</title>
{% if (o.htmlWebpackPlugin.files.favicon) { %}
<link rel="shortcut icon" href="{%=o.htmlWebpackPlugin.files.favicon%}">
{% } %}
{% for (var css in o.htmlWebpackPlugin.files.css) { %}
<link href="{%=o.htmlWebpackPlugin.files.css[css] %}" rel="stylesheet">
{% } %}
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
{% for (var chunk in o.htmlWebpackPlugin.files.chunks) { %}
<script src="{%=o.htmlWebpackPlugin.files.chunks[chunk].entry %}"></script>
{% } %}
</body>
</html>
</html>

View File

@ -1,9 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{%=o.htmlWebpackPlugin.options.title || 'Webpack App'%}</title>
</head>
<body>
</body>
</html>

View File

@ -0,0 +1,4 @@
require('./main.css');
var h1 = document.createElement('h1');
h1.innerHTML = 'Hello world!';
document.body.appendChild(h1);

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

View File

@ -0,0 +1,3 @@
body {
background: snow;
}

View File

@ -0,0 +1,11 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Example template</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<img src="logo.png">
</body>
</html>

View File

@ -0,0 +1,22 @@
var HtmlWebpackPlugin = require('..');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
entry: './example.js',
output: {
path: __dirname + '/dist',
publicPath: '',
filename: 'bundle.js'
},
module: {
loaders: [
{ test: /\.css$/, loader: ExtractTextPlugin.extract('style-loader', 'css-loader') },
{ test: /\.png$/, loader: 'file-loader' }
]
},
plugins: [
new HtmlWebpackPlugin({
template: 'template.html'
}),
new ExtractTextPlugin('styles.css')
]
};

4
examples/default/example.js Executable file
View File

@ -0,0 +1,4 @@
require('./main.css');
var h1 = document.createElement('h1');
h1.innerHTML = 'Hello world!';
document.body.appendChild(h1);

View File

@ -0,0 +1,3 @@
body {
background: snow;
}

View File

@ -0,0 +1,18 @@
var HtmlWebpackPlugin = require('..');
module.exports = {
entry: './example.js',
output: {
path: __dirname + '/dist',
publicPath: '',
filename: 'bundle.js'
},
module: {
loaders: [
{ test: /\.css$/, loader: 'style-loader!css-loader' },
{ test: /\.png$/, loader: 'file-loader' }
]
},
plugins: [
new HtmlWebpackPlugin()
]
};

4
examples/favicon/example.js Executable file
View File

@ -0,0 +1,4 @@
require('./main.css');
var h1 = document.createElement('h1');
h1.innerHTML = 'Hello world!';
document.body.appendChild(h1);

Binary file not shown.

After

Width:  |  Height:  |  Size: 766 B

View File

@ -0,0 +1,3 @@
body {
background: snow;
}

View File

@ -0,0 +1,11 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Example template</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<img src="logo.png">
</body>
</html>

View File

@ -0,0 +1,24 @@
var HtmlWebpackPlugin = require('..');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
entry: './example.js',
output: {
path: __dirname + '/dist',
publicPath: '',
filename: 'bundle.js'
},
module: {
loaders: [
{ test: /\.css$/, loader: ExtractTextPlugin.extract('style-loader', 'css-loader') },
{ test: /\.png$/, loader: 'file-loader' }
]
},
plugins: [
new HtmlWebpackPlugin({
title: 'HtmlWebpackPlugin example',
favicon: 'favicon.ico',
filename: 'favicon.html'
}),
new ExtractTextPlugin('styles.css')
]
};

View File

@ -0,0 +1,4 @@
require('./main.css');
var h1 = document.createElement('h1');
h1.innerHTML = 'Hello world!';
document.body.appendChild(h1);

Binary file not shown.

After

Width:  |  Height:  |  Size: 766 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

View File

@ -0,0 +1,3 @@
body {
background: snow;
}

View File

@ -0,0 +1,11 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Example template</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<img src="logo.png">
</body>
</html>

View File

@ -0,0 +1,24 @@
var HtmlWebpackPlugin = require('..');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
entry: './example.js',
output: {
path: __dirname + '/dist',
publicPath: '',
filename: 'bundle.js'
},
module: {
loaders: [
{ test: /\.css$/, loader: ExtractTextPlugin.extract('style-loader', 'css-loader') },
{ test: /\.png$/, loader: 'file-loader' }
]
},
plugins: [
new HtmlWebpackPlugin({
filename: 'html-loader.html',
favicon: 'favicon.ico',
template: 'html!./template.html'
}),
new ExtractTextPlugin('styles.css')
]
};

View File

@ -0,0 +1,4 @@
require('./main.css');
var h1 = document.createElement('h1');
h1.innerHTML = 'Hello world!';
document.body.appendChild(h1);

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

View File

@ -0,0 +1,3 @@
body {
background: snow;
}

View File

@ -0,0 +1,2 @@
<h2>Partial</h2>
<img src="logo.png">

View File

@ -0,0 +1,11 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Example template</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<%- include(require('html!./partial.html'), {}); %>
</body>
</html>

View File

@ -0,0 +1,22 @@
var HtmlWebpackPlugin = require('../..');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
entry: './example.js',
output: {
path: __dirname + '/dist',
publicPath: '',
filename: 'bundle.js'
},
module: {
loaders: [
{ test: /\.css$/, loader: ExtractTextPlugin.extract('style-loader', 'css-loader') },
{ test: /\.png$/, loader: 'file-loader' }
]
},
plugins: [
new HtmlWebpackPlugin({
template: 'template.html'
}),
new ExtractTextPlugin('styles.css')
]
};

399
index.js
View File

@ -1,154 +1,249 @@
'use strict';
var vm = require('vm');
var fs = require('fs');
var path = require('path');
var _ = require('lodash');
var tmpl = require('blueimp-tmpl').tmpl;
var Promise = require('bluebird');
var path = require('path');
Promise.promisifyAll(fs);
var NodeTemplatePlugin = require('webpack/lib/node/NodeTemplatePlugin');
var NodeTargetPlugin = require('webpack/lib/node/NodeTargetPlugin');
var LibraryTemplatePlugin = require('webpack/lib/LibraryTemplatePlugin');
var SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin');
function HtmlWebpackPlugin(options) {
this.options = options || {};
// Default options
this.options = _.extend({
template: __dirname + '/default_index.html',
filename: 'index.html',
hash: false,
inject: true,
compile: true,
favicon: false,
minify: false,
chunks: 'all',
excludeChunks: [],
title: 'Webpack App'
}, options);
// If the template doesn't use a loader use the blueimp template loader
if(this.options.template.indexOf('!') === -1) {
this.options.template = require.resolve('./loader.js') + '!' + path.resolve(this.options.template);
}
// Resolve template path
this.options.template = this.options.template.replace(
/(\!)([^\/\\][^\!\?]+|[^\/\\!?])($|\?.+$)/,
function(match, prefix, filepath, postfix) {
return prefix + path.resolve(filepath) + postfix;
});
}
HtmlWebpackPlugin.prototype.apply = function(compiler) {
var self = this;
compiler.plugin('emit', function(compilation, compileCallback) {
var webpackStatsJson = compilation.getStats().toJson();
var outputFilename = self.options.filename || 'index.html';
var compilationPromise;
self.context = compiler.context;
compiler.plugin('make', function(compilation, callback) {
// Compile the template
compilationPromise = self.compileTemplate(self.options.template, self.options.filename, compilation);
compilationPromise.finally(callback);
});
compiler.plugin('emit', function(compilation, callback) {
// Get all chunks
var chunks = self.filterChunks(compilation.getStats().toJson(), self.options.chunks, self.options.excludeChunks);
// Get assets
var assets = self.htmlWebpackPluginAssets(compilation, chunks);
Promise.resolve()
// Add the favicon
.then(function(callback) {
// Favicon
.then(function() {
if (self.options.favicon) {
return self.addFileToAssets(compilation, self.options.favicon, callback);
return self.addFileToAssets(self.options.favicon, compilation)
.then(function(faviconBasename){
assets.favicon = faviconBasename;
});
}
})
// Generate the html
// Wait for the compilation to finish
.then(function() {
var templateParams = {
webpack: webpackStatsJson,
webpackConfig: compilation.options,
htmlWebpackPlugin: {
files: self.htmlWebpackPluginAssets(compilation, webpackStatsJson, self.options.chunks, self.options.excludeChunks),
options: self.options,
}
};
// Deprecate templateParams.htmlWebpackPlugin.assets
var assets = self.htmlWebpackPluginLegacyAssets(compilation, webpackStatsJson);
Object.defineProperty(templateParams.htmlWebpackPlugin, 'assets', {
get: function() {
compilation.warnings.push(new Error('HtmlWebPackPlugin: htmlWebpackPlugin.assets is deprecated - please use inject or htmlWebpackPlugin.files instead' +
'\nsee: https://github.com/ampedandwired/html-webpack-plugin/issues/52'));
return assets;
}
});
// Get/generate html
return self.getTemplateContent(compilation, templateParams)
.then(function(htmlTemplateContent) {
// Compile and add html to compilation
return self.emitHtml(compilation, htmlTemplateContent, templateParams, outputFilename);
});
return compilationPromise;
})
.then(function(resultAsset) {
// Once everything is compiled evaluate the html factory
// and replace it with its content
return self.evaluateCompilationResult(compilation, resultAsset);
})
// Execute the template
.then(function(compilationResult) {
// If the loader result is a function execute it to retreive the html
// otherwise use the returned html
return typeof compilationResult !== 'function' ? compilationResult :
self.executeTemplate(compilationResult, chunks, assets, compilation);
})
.then(function(html) {
// Add the stylesheets, scripts and so on to the resulting html
return self.postProcessHtml(html, assets);
})
// In case anything went wrong let the user know
.catch(function(err) {
compilation.errors.push(err);
compilation.assets[outputFilename] = {
// In case anything went wrong the promise is resolved
// with the error message and an error is logged
var errorMessage = "HtmlWebpackPlugin Error: " + err;
compilation.errors.push(new Error(errorMessage));
return errorMessage;
})
.then(function(html) {
// Replace the compilation result with the evaluated html code
compilation.assets[self.options.filename] = {
source: function() {
return err.toString();
return html;
},
size: function() {
return err.toString().length;
return html.length;
}
};
})
// Tell the compiler to proceed
.finally(compileCallback);
callback();
});
});
};
/**
* 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
};
// 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));
childCompiler.apply(new LibraryTemplatePlugin('result', 'var'));
childCompiler.apply(new NodeTargetPlugin());
childCompiler.apply(new SingleEntryPlugin(this.context, template));
// Create a subCache (copied from https://github.com/SanderSpies/extract-text-webpack-plugin/blob/master/loader.js)
childCompiler.plugin('compilation', function(compilation) {
if(compilation.cache) {
if(!compilation.cache[compilerName]) {
compilation.cache[compilerName] = {};
}
compilation.cache = compilation.cache[compilerName];
}
});
// Compile and return a promise
return new Promise(function (resolve, reject) {
childCompiler.runAsChild(function(err) {
// Resolve / reject the promise
if (err) {
reject(err);
} else {
resolve(compilation.assets[outputFilename]);
}
});
});
};
/**
* Retrieves the html source depending on `this.options`.
* Supports:
* + options.fileContent as string
* + options.fileContent as sync function
* + options.fileContent as async function
* + options.template as template path
* Returns a Promise
* Evaluates the child compilation result
* Returns a promise
*/
HtmlWebpackPlugin.prototype.getTemplateContent = function(compilation, templateParams) {
HtmlWebpackPlugin.prototype.evaluateCompilationResult = function(compilation, compilationResult) {
if(!compilationResult) {
return Promise.reject('The child compilation didn\'t provide a result');
}
// Strip the leading 'var '
var source = compilationResult.source().substr(3);
// Evaluate code and cast to string
var newSource;
try {
newSource = vm.runInThisContext(source);
} catch (e) {
compilation.errors.push(new Error('Template compilation failed: ' + e));
return Promise.reject(e);
}
return typeof newSource === 'string' || typeof newSource === 'function' ?
Promise.resolve(newSource) :
Promise.reject('The loader "' + this.options.template + '" didn\'t return html.');
};
/**
* Html post processing
*
* Returns a promise
*/
HtmlWebpackPlugin.prototype.executeTemplate = function(templateFunction, chunks, assets, compilation) {
var self = this;
// If config is invalid
if (self.options.templateContent && self.options.template) {
return Promise.reject(new Error('HtmlWebpackPlugin: cannot specify both template and templateContent options'));
}
// If a function is passed
if (typeof self.options.templateContent === 'function') {
return Promise.fromNode(function(callback) {
// allow to specify a sync or an async function to generate the template content
var result = self.options.templateContent(templateParams, compilation, callback);
// if it returns a result expect it to be sync
if (result !== undefined) {
callback(null, result);
return Promise.resolve()
// Template processing
.then(function() {
var templateParams = {
webpack: compilation.getStats().toJson(),
webpackConfig: compilation.options,
htmlWebpackPlugin: {
files: assets,
options: self.options,
}
};
var html = '';
try {
html = templateFunction(templateParams);
} catch (e) {
compilation.errors.push(new Error('Template execution failed: ' + e));
return Promise.reject(e);
}
});
}
// If a string is passed
if (self.options.templateContent) {
return Promise.resolve(self.options.templateContent);
}
// If templateContent is empty use the template option
var templateFile = self.options.template;
if (!templateFile) {
// Use a special index file to prevent double script / style injection if the `inject` option is truthy
templateFile = path.join(__dirname, self.options.inject ? 'default_inject_index.html' : 'default_index.html');
}
compilation.fileDependencies.push(templateFile);
return fs.readFileAsync(templateFile, 'utf8')
// If the file could not be read log a error
.catch(function() {
return Promise.reject(new Error('HtmlWebpackPlugin: Unable to read HTML template "' + templateFile + '"'));
return html;
});
};
/*
* Compile the html template and push the result to the compilation assets
/**
* Html post processing
*
* Returns a promise
*/
HtmlWebpackPlugin.prototype.emitHtml = function(compilation, htmlTemplateContent, templateParams, outputFilename) {
var html;
// blueimp-tmpl processing
try {
html = tmpl(htmlTemplateContent, templateParams);
} catch(e) {
return Promise.reject(new Error('HtmlWebpackPlugin: template error ' + e));
}
// Inject link and script elements into an existing html file
if (this.options.inject) {
html = this.injectAssetsIntoHtml(html, templateParams);
}
// Minify the html output
if (this.options.minify) {
var minify = require('html-minifier').minify;
// If `options.minify` is set to true use the default minify options
var minifyOptions = _.isObject(this.options.minify) ? this.options.minify : {};
html = minify(html, minifyOptions);
}
compilation.assets[outputFilename] = {
source: function() {
HtmlWebpackPlugin.prototype.postProcessHtml = function(html, assets) {
var self = this;
return Promise.resolve()
// Inject
.then(function() {
if (self.options.inject) {
return self.injectAssetsIntoHtml(html, assets);
} else {
return html;
}
})
// Minify
.then(function(html) {
if (self.options.minify) {
var minify = require('html-minifier').minify;
// If `options.minify` is set to true use the default minify options
var minifyOptions = _.isObject(self.options.minify) ? self.options.minify : {};
try {
minify(html, minifyOptions);
} catch(e) {
Promise.reject(e);
}
}
return html;
},
size: function() {
return html.length;
}
};
});
};
/*
* Pushes the content of the given filename to the compilation assets
*/
HtmlWebpackPlugin.prototype.addFileToAssets = function(compilation, filename) {
HtmlWebpackPlugin.prototype.addFileToAssets = function(filename, compilation) {
return Promise.props({
size: fs.statAsync(filename),
source: fs.readFileAsync(filename)
@ -157,24 +252,57 @@ HtmlWebpackPlugin.prototype.addFileToAssets = function(compilation, filename) {
return Promise.reject(new Error('HtmlWebpackPlugin: could not load file ' + filename));
})
.then(function(results) {
var basename = path.basename(filename);
compilation.fileDependencies.push(filename);
compilation.assets[path.basename(filename)] = {
compilation.assets[basename] = {
source: function() {
return results.source;
},
size: function() {
return results.size;
return results.size.size;
}
};
return basename;
});
};
HtmlWebpackPlugin.prototype.htmlWebpackPluginAssets = function(compilation, webpackStatsJson, includedChunks, excludedChunks) {
/**
* Return all chunks from the compilation result which match the exclude and include filters
*/
HtmlWebpackPlugin.prototype.filterChunks = function (webpackStatsJson, includedChunks, excludedChunks) {
var chunks = webpackStatsJson.chunks.filter(function(chunk){
var chunkName = chunk.names[0];
// This chunk doesn't have a name. This script can't handled it.
if (chunkName === undefined) {
return false;
}
// Skip if the chunks should be filtered and the given chunk was not added explicity
if (Array.isArray(includedChunks) && includedChunks.indexOf(chunkName) === -1) {
return false;
}
// Skip if the chunks should be filtered and the given chunk was excluded explicity
if (Array.isArray(excludedChunks) && excludedChunks.indexOf(chunkName) !== -1) {
return false;
}
// Add otherwise
return true;
});
return chunks.sort(function orderEntryLast(a, b) {
if (a.entry !== b.entry) {
return b.entry ? 1 : -1;
} else {
return b.id - a.id;
}
});
};
HtmlWebpackPlugin.prototype.htmlWebpackPluginAssets = function(compilation, chunks) {
var self = this;
var webpackStatsJson = compilation.getStats().toJson();
// Use the configured public path or build a relative path
var publicPath = typeof compilation.options.output.publicPath !== 'undefined' ?
compilation.options.output.publicPath :
compilation.options.output.publicPath :
path.relative(path.dirname(self.options.filename), '.');
if (publicPath.length && publicPath.substr(-1, 1) !== '/') {
@ -188,8 +316,6 @@ HtmlWebpackPlugin.prototype.htmlWebpackPluginAssets = function(compilation, webp
js: [],
// Will contain all css files
css: [],
// Will contain the path to the favicon if it exists
favicon: self.options.favicon ? publicPath + path.basename(self.options.favicon): undefined,
// Will contain the html5 appcache manifest files if it exists
manifest: Object.keys(compilation.assets).filter(function(assetFile){
return path.extname(assetFile) === '.appcache';
@ -202,32 +328,10 @@ HtmlWebpackPlugin.prototype.htmlWebpackPluginAssets = function(compilation, webp
assets.favicon = self.appendHash(assets.favicon, webpackStatsJson.hash);
}
var chunks = webpackStatsJson.chunks.sort(function orderEntryLast(a, b) {
if (a.entry !== b.entry) {
return b.entry ? 1 : -1;
} else {
return b.id - a.id;
}
});
for (var i = 0; i < chunks.length; i++) {
var chunk = chunks[i];
var chunkName = chunk.names[0];
// This chunk doesn't have a name. This script can't handled it.
if(chunkName === undefined) {
continue;
}
// Skip if the chunks should be filtered and the given chunk was not added explicity
if (Array.isArray(includedChunks) && includedChunks.indexOf(chunkName) === -1) {
continue;
}
// Skip if the chunks should be filtered and the given chunk was excluded explicity
if (Array.isArray(excludedChunks) && excludedChunks.indexOf(chunkName) !== -1) {
continue;
}
assets.chunks[chunkName] = {};
// Prepend the public path to all chunk files
@ -268,8 +372,7 @@ HtmlWebpackPlugin.prototype.htmlWebpackPluginAssets = function(compilation, webp
/**
* Injects the assets into the given html string
*/
HtmlWebpackPlugin.prototype.injectAssetsIntoHtml = function(html, templateParams) {
var assets = templateParams.htmlWebpackPlugin.files;
HtmlWebpackPlugin.prototype.injectAssetsIntoHtml = function(html, assets) {
var chunks = Object.keys(assets.chunks);
// Gather all css and script files
@ -324,18 +427,6 @@ HtmlWebpackPlugin.prototype.injectAssetsIntoHtml = function(html, templateParams
return html;
};
/**
* A helper to support the templates written for html-webpack-plugin <= 1.1.0
*/
HtmlWebpackPlugin.prototype.htmlWebpackPluginLegacyAssets = function(compilation, webpackStatsJson) {
var assets = this.htmlWebpackPluginAssets(compilation, webpackStatsJson);
var legacyAssets = {};
Object.keys(assets.chunks).forEach(function(chunkName){
legacyAssets[chunkName] = assets.chunks[chunkName].entry;
});
return legacyAssets;
};
/**
* Appends a cache busting hash
*/

20
loader.js Normal file
View File

@ -0,0 +1,20 @@
var _ = require('lodash');
var loaderUtils = require('loader-utils');
module.exports = function (source) {
'use strict';
if (this.cacheable) {
this.cacheable();
}
var allLoadersButThisOne = this.loaders.filter(function(loader) {
return loader.module !== module.exports;
});
// This loader shouldn't kick in if there is any other loader
if (allLoadersButThisOne.length > 0) {
return source;
}
// Use underscore for a minimalistic loader
var options = loaderUtils.parseQuery(this.query);
var template = _.template(source, options);
return 'module.exports = ' + template;
};

View File

@ -1,10 +1,11 @@
{
"name": "html-webpack-plugin",
"version": "1.5.2",
"version": "2.0.0",
"description": "Simplifies creation of HTML files to serve your webpack bundles",
"main": "index.js",
"files": [
"index.js",
"loader.js",
"default_index.html",
"default_inject_index.html"
],
@ -32,6 +33,7 @@
"css-loader": "^0.12.0",
"extract-text-webpack-plugin": "^0.7.1",
"file-loader": "^0.8.1",
"html-loader": "^0.3.0",
"jasmine-node": "^1.14.5",
"jshint": "^2.7.0",
"rimraf": "^2.3.3",
@ -41,8 +43,8 @@
},
"dependencies": {
"bluebird": "^2.9.25",
"blueimp-tmpl": "~2.5.4",
"html-minifier": "^0.7.2",
"loader-utils": "^0.2.10",
"lodash": "~3.9.3"
}
}

View File

@ -24,6 +24,11 @@ function testHtmlPlugin(webpackConfig, expectedResults, outputFile, done, expect
} else {
expect(compilationWarnings).toBe('');
}
var outputFileExists = fs.existsSync(path.join(OUTPUT_DIR, outputFile));
expect(outputFileExists).toBe(true);
if(!outputFileExists) {
return done();
}
var htmlContent = fs.readFileSync(path.join(OUTPUT_DIR, outputFile)).toString();
for (var i = 0; i < expectedResults.length; i++) {
@ -83,20 +88,6 @@ describe('HtmlWebpackPlugin', function() {
['<script src="app_bundle.js', 'Some unique text'], null, done);
});
it('allows you to specify your own HTML template string', function(done) {
testHtmlPlugin({
entry: {app: path.join(__dirname, 'fixtures/index.js')},
output: {
path: OUTPUT_DIR,
filename: 'app_bundle.js'
},
plugins: [new HtmlWebpackPlugin({
templateContent: fs.readFileSync(path.join(__dirname, 'fixtures/test.html'), 'utf8')
})]
},
['<script src="app_bundle.js'], null, done);
});
it('allows you to inject the assets into a given html file', function (done) {
testHtmlPlugin({
entry: {
@ -148,24 +139,6 @@ describe('HtmlWebpackPlugin', function() {
}, ['<script src="util_bundle.js"', '<script src="app_bundle.js"'], null, done);
});
it('allows you to inject the assets into a html string', function (done) {
testHtmlPlugin({
entry: {
util: path.join(__dirname, 'fixtures/util.js'),
app: path.join(__dirname, 'fixtures/index.js')
},
output: {
path: OUTPUT_DIR,
filename: '[name]_bundle.js'
},
plugins: [new HtmlWebpackPlugin({
inject: true,
chunks: ['util', 'app'],
templateContent: fs.readFileSync(path.join(__dirname, 'fixtures/plain.html'), 'utf8')
})]
}, ['<script src="util_bundle.js"', '<script src="app_bundle.js"'], null, done);
});
it('allows you to inject a specified asset into a given html file', function (done) {
testHtmlPlugin({
entry: {
@ -202,34 +175,6 @@ describe('HtmlWebpackPlugin', function() {
}, ['<script src="app_bundle.js"'], null, done);
});
it('allows you to use the deprecated assets object', function (done) {
testHtmlPlugin({
entry: {
app: path.join(__dirname, 'fixtures/index.js')
},
output: {
path: OUTPUT_DIR,
filename: '[name]_bundle.js'
},
plugins: [new HtmlWebpackPlugin({template: path.join(__dirname, 'fixtures/legacy.html')})]
},
['<script src="app_bundle.js', 'Some unique text'], null, done, false, true);
});
it('allows you to use a deprecated legacy_index template', function (done) {
testHtmlPlugin({
entry: {
app: path.join(__dirname, 'fixtures/index.js')
},
output: {
path: OUTPUT_DIR,
filename: '[name]_bundle.js'
},
plugins: [new HtmlWebpackPlugin({template: path.join(__dirname, 'fixtures/legacy_default_index.html')})]
},
['<script src="app_bundle.js'], null, done, false, true);
});
it('allows you to specify your own HTML template function', function(done) {
testHtmlPlugin({
entry: {app: path.join(__dirname, 'fixtures/index.js')},
@ -246,24 +191,6 @@ describe('HtmlWebpackPlugin', function() {
['<script src="app_bundle.js"'], null, done);
});
it('registers a webpack error both template and template content are specified', function(done) {
webpack({
entry: path.join(__dirname, 'fixtures/index.js'),
output: {
path: OUTPUT_DIR,
filename: 'index_bundle.js'
},
plugins: [new HtmlWebpackPlugin({
template: path.join(__dirname, 'fixtures/test.html'),
templateContent: 'whatever'
})]
}, function(err, stats) {
expect(stats.hasErrors()).toBe(true);
expect(stats.toJson().errors[0]).toContain('HtmlWebpackPlugin');
done();
});
});
it('works with source maps', function(done) {
testHtmlPlugin({
devtool: 'sourcemap',
@ -489,21 +416,6 @@ describe('HtmlWebpackPlugin', function() {
expect(fs.existsSync(path.join(__dirname, 'fixtures/test.html'))).toBe(true);
});
it('registers a webpack error if the template cannot be opened', function(done) {
webpack({
entry: path.join(__dirname, 'fixtures/index.js'),
output: {
path: OUTPUT_DIR,
filename: 'index_bundle.js'
},
plugins: [new HtmlWebpackPlugin({template: 'fixtures/does_not_exist.html'})]
}, function(err, stats) {
expect(stats.hasErrors()).toBe(true);
expect(stats.toJson().errors[0]).toContain('HtmlWebpackPlugin');
done();
});
});
it('exposes the webpack configuration to templates', function(done) {
testHtmlPlugin({
entry: {

View File

@ -1,12 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{%=o.htmlWebpackPlugin.options.title || 'Webpack App'%}</title>
</head>
<body>
{% for (var chunk in o.htmlWebpackPlugin.assets) { %}
<script src="{%=o.htmlWebpackPlugin.assets[chunk]%}"></script>
{% } %}
</body>
</html>

View File

@ -5,7 +5,7 @@
<title>Test</title>
</head>
<body>
<p>Public path is {%=o.webpackConfig.output.publicPath%}</p>
<script src="{%=o.htmlWebpackPlugin.files.chunks.app.entry%}"></script>
<p>Public path is <%= webpackConfig.output.publicPath %></p>
<script src="<%= htmlWebpackPlugin.files.chunks.app.entry %>"></script>
</body>
</html>