Compare commits
18 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
74909e4c94 | |
|
|
571407f960 | |
|
|
4f1ab08827 | |
|
|
8e89ea23de | |
|
|
7f63ba8988 | |
|
|
9e313ef6d1 | |
|
|
a5b10196be | |
|
|
58a3414a36 | |
|
|
2fb24b29be | |
|
|
a1442f2a54 | |
|
|
d91c306c81 | |
|
|
3066de978e | |
|
|
94fc6c98af | |
|
|
e56b2fb600 | |
|
|
c5033f9d95 | |
|
|
d793886a5e | |
|
|
998ef87cf7 | |
|
|
79b0e692f0 |
|
|
@ -1,2 +1,3 @@
|
|||
/node_modules/
|
||||
/dist/
|
||||
examples/*/dist
|
||||
15
README.md
15
README.md
|
|
@ -1,11 +1,11 @@
|
|||
HTML Webpack Plugin
|
||||
===================
|
||||
===================
|
||||
[](http://badge.fury.io/js/html-webpack-plugin) [](https://david-dm.org/ampedandwired/html-webpack-plugin) [](https://www.bithound.io/github/ampedandwired/html-webpack-plugin) [](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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{%=o.htmlWebpackPlugin.options.title || 'Webpack App'%}</title>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -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 |
|
|
@ -0,0 +1,3 @@
|
|||
body {
|
||||
background: snow;
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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')
|
||||
]
|
||||
};
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
require('./main.css');
|
||||
var h1 = document.createElement('h1');
|
||||
h1.innerHTML = 'Hello world!';
|
||||
document.body.appendChild(h1);
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
body {
|
||||
background: snow;
|
||||
}
|
||||
|
|
@ -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()
|
||||
]
|
||||
};
|
||||
|
|
@ -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 |
|
|
@ -0,0 +1,3 @@
|
|||
body {
|
||||
background: snow;
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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')
|
||||
]
|
||||
};
|
||||
|
|
@ -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 |
|
|
@ -0,0 +1,3 @@
|
|||
body {
|
||||
background: snow;
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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')
|
||||
]
|
||||
};
|
||||
|
|
@ -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 |
|
|
@ -0,0 +1,3 @@
|
|||
body {
|
||||
background: snow;
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
<h2>Partial</h2>
|
||||
<img src="logo.png">
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
399
index.js
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue