├── .gitignore ├── default_index.ejs ├── lib ├── errors.js ├── loader.js ├── readDir.js ├── readDirAsync.js ├── chunksorter.js └── compiler.js ├── LICENSE ├── package.json ├── README.md └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | ## node_modules 2 | node_modules/* 3 | 4 | ## test 5 | test/* 6 | 7 | ## webpackconfig 8 | webpack.config.js -------------------------------------------------------------------------------- /default_index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |\n' + this.toString() + ''; 15 | }, 16 | toJsonHtml: function () { 17 | return JSON.stringify(this.toHtml()); 18 | }, 19 | toString: function () { 20 | return prettyError.render(err).replace(/webpack:\/\/\/\./g, context); 21 | } 22 | }; 23 | }; 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 小虫巨蟹 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "muti-html-webpack-plugin", 3 | "version": "1.0.5", 4 | "description": "a webpack plugin to due html", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/zengwenfu/muti-html-webpack-plugin.git" 12 | }, 13 | "keywords": [ 14 | "webpack", 15 | "plugin", 16 | "muti", 17 | "html" 18 | ], 19 | "dependencies": { 20 | "async": "^2.0.1", 21 | "bluebird": "^3.4.1", 22 | "html-minifier": "^2.1.6", 23 | "loader-utils": "^0.2.15", 24 | "lodash": "^4.13.1", 25 | "pretty-error": "^2.0.0", 26 | "toposort": "^1.0.0" 27 | }, 28 | "author": "zengwenfu", 29 | "license": "ISC", 30 | "bugs": { 31 | "url": "https://github.com/zengwenfu/muti-html-webpack-plugin/issues" 32 | }, 33 | "homepage": "https://github.com/zengwenfu/muti-html-webpack-plugin#readme", 34 | "devDependencies": { 35 | "css-loader": "^0.25.0", 36 | "extract-text-webpack-plugin": "^1.0.1", 37 | "html-loader": "^0.4.4", 38 | "style-loader": "^0.13.1", 39 | "url-loader": "^0.5.7", 40 | "webpack": "^1.13.2" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/loader.js: -------------------------------------------------------------------------------- 1 | /* This loader renders the template with underscore if no other loader was found */ 2 | 'use strict'; 3 | 4 | var _ = require('lodash'); 5 | var loaderUtils = require('loader-utils'); 6 | 7 | module.exports = function (source) { 8 | if (this.cacheable) { 9 | this.cacheable(); 10 | } 11 | var allLoadersButThisOne = this.loaders.filter(function (loader) { 12 | // Loader API changed from `loader.module` to `loader.normal` in Webpack 2. 13 | return (loader.module || loader.normal) !== module.exports; 14 | }); 15 | // This loader shouldn't kick in if there is any other loader 16 | if (allLoadersButThisOne.length > 0) { 17 | return source; 18 | } 19 | // Skip .js files 20 | if (/\.js$/.test(this.request)) { 21 | return source; 22 | } 23 | 24 | // The following part renders the tempalte with lodash as aminimalistic loader 25 | // 26 | // Get templating options 27 | var options = loaderUtils.parseQuery(this.query); 28 | // Webpack 2 does not allow with() statements, which lodash templates use to unwrap 29 | // the parameters passed to the compiled template inside the scope. We therefore 30 | // need to unwrap them ourselves here. This is essentially what lodash does internally 31 | // To tell lodash it should not use with we set a variable 32 | var template = _.template(source, _.defaults(options, { variable: 'data' })); 33 | // All templateVariables which should be available 34 | // @see HtmlWebpackPlugin.prototype.executeTemplate 35 | var templateVariables = [ 36 | 'compilation', 37 | 'webpack', 38 | 'webpackConfig', 39 | 'htmlWebpackPlugin' 40 | ]; 41 | return 'var _ = require(' + loaderUtils.stringifyRequest(this, require.resolve('lodash')) + ');' + 42 | 'module.exports = function (templateParams) {' + 43 | // Declare the template variables in the outer scope of the 44 | // lodash template to unwrap them 45 | templateVariables.map(function (variableName) { 46 | return 'var ' + variableName + ' = templateParams.' + variableName; 47 | }).join(';') + ';' + 48 | // Execute the lodash template 49 | 'return (' + template.source + ')();' + 50 | '}'; 51 | }; 52 | -------------------------------------------------------------------------------- /lib/readDir.js: -------------------------------------------------------------------------------- 1 | /** 2 | * create by zengwenfu 3 | */ 4 | 'use strict'; 5 | 6 | var fs = require('fs'); 7 | 8 | module.exports = function(path, suffix, outputPath) { 9 | return new Promise(function(resolve, reject) { 10 | var dirs = []; 11 | var over_dir = []; 12 | var over = {}; 13 | dirs.push(path) 14 | 15 | function forDir(dirs) { 16 | function forFiles(files, file_path) { 17 | var fixdir = []; 18 | files.forEach(function(e, i) { 19 | var e = file_path + '/' + e; 20 | fs.stat(e, function(err, stat) { 21 | if (err) { 22 | console.log(err); 23 | reject(err); 24 | } 25 | if (stat.isDirectory()) { 26 | fixdir.push(e) 27 | over_dir.push(e) 28 | } else { 29 | if(!suffix || e.indexOf(suffix) >=0 ) { 30 | var key = e.substring(path.length + 1); 31 | if(!suffix && key.indexOf('.html')>=0) {//默认过滤掉.html 32 | key = key.substring(0, key.indexOf('.html')); 33 | } else if(!!suffix){ 34 | key = key.substring(0, key.indexOf(suffix)); 35 | } 36 | if(!!outputPath) { 37 | key = outputPath + '/' + key; 38 | } 39 | over[key] = e; 40 | } 41 | } 42 | if (i == files.length - 1) { 43 | if (file_path == over_dir[over_dir.length - 1]) { 44 | resolve(over); 45 | } 46 | if (fixdir.length > 0) { 47 | forDir(fixdir) 48 | } 49 | } 50 | }) 51 | }) 52 | } 53 | 54 | dirs.forEach(function(e, i) { 55 | fs.readdir(e, function(err, files) { 56 | if(err) { 57 | console.log(err); 58 | reject(err); 59 | } 60 | forFiles(files, e) 61 | }) 62 | }); 63 | } 64 | 65 | forDir(dirs); 66 | }); 67 | } 68 | -------------------------------------------------------------------------------- /lib/readDirAsync.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var async = require('async'); 4 | 5 | 6 | //分文件夹输出 7 | function getFileList(dirname, suffix, outputPath, ignore, callback, error) { 8 | var fileJson = {}; 9 | if(ignore && typeof ignore === 'object') { 10 | ignore = ignore.join(','); 11 | } 12 | 13 | var a = (function listFiles(rootPath, fn, error) { 14 | var files; 15 | try { 16 | files = fs.readdirSync(rootPath); 17 | } catch(e) { 18 | console.log(e); 19 | error(e); 20 | return ; 21 | } 22 | async.eachSeries(files, function(item, next) { 23 | var filePath = path.normalize(rootPath + '/' + item); 24 | var info = fs.statSync(filePath); 25 | if (info.isDirectory()) { 26 | listFiles(filePath, function() { 27 | next(); 28 | }); 29 | } else { 30 | if (!suffix || filePath.indexOf(suffix) >= 0) { 31 | var key = filePath.substring(dirname.length + 1); 32 | if(!ignore || ignore.indexOf(key) < 0) { 33 | if (!suffix && key.indexOf('.html') >= 0) { //默认过滤掉.html 34 | key = key.substring(0, key.indexOf('.html')); 35 | } else if (!!suffix) { 36 | key = key.substring(0, key.indexOf(suffix)); 37 | } 38 | if (!!outputPath) { 39 | key = outputPath + '/' + key; 40 | } 41 | fileJson[key] = filePath; 42 | } 43 | } 44 | 45 | next(); 46 | } 47 | }, function(err) { 48 | if (!err) { 49 | fn && fn(); 50 | } else { 51 | console.error("遍历模板路径失败:" + err); 52 | error && error(err); 53 | } 54 | }); 55 | })(dirname, function() { 56 | callback(fileJson); 57 | }, function(err) { 58 | console.log(err); 59 | error(err); 60 | }); 61 | }; 62 | 63 | 64 | module.exports = function(path, suffix, outputPath, ignore) { 65 | var suffix = suffix || false; 66 | var outputPath = outputPath || false; 67 | var ignore = ignore || false; 68 | return new Promise(function(resolve, reject) { 69 | getFileList(path, suffix, outputPath, ignore, function(files) { 70 | resolve(files); 71 | }, function(err) { 72 | console.log(err); 73 | reject(err); 74 | }); 75 | }); 76 | } 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /lib/chunksorter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var toposort = require('toposort'); 4 | 5 | /* 6 | Sorts dependencies between chunks by their "parents" attribute. 7 | 8 | This function sorts chunks based on their dependencies with each other. 9 | The parent relation between chunks as generated by Webpack for each chunk 10 | is used to define a directed (and hopefully acyclic) graph, which is then 11 | topologically sorted in order to retrieve the correct order in which 12 | chunks need to be embedded into HTML. A directed edge in this graph is 13 | describing a "is parent of" relationship from a chunk to another (distinct) 14 | chunk. Thus topological sorting orders chunks from bottom-layer chunks to 15 | highest level chunks that use the lower-level chunks. 16 | 17 | @param {Array} chunks an array of chunks as generated by the html-webpack-plugin. 18 | It is assumed that each entry contains at least the properties "id" 19 | (containing the chunk id) and "parents" (array containing the ids of the 20 | parent chunks). 21 | 22 | @return {Array} A topologically sorted version of the input chunks 23 | */ 24 | module.exports.dependency = function (chunks) { 25 | if (!chunks) { 26 | return chunks; 27 | } 28 | 29 | // We build a map (chunk-id -> chunk) for faster access during graph building. 30 | var nodeMap = {}; 31 | 32 | chunks.forEach(function (chunk) { 33 | nodeMap[chunk.id] = chunk; 34 | }); 35 | 36 | // Next, we add an edge for each parent relationship into the graph 37 | var edges = []; 38 | 39 | chunks.forEach(function (chunk) { 40 | if (chunk.parents) { 41 | // Add an edge for each parent (parent -> child) 42 | chunk.parents.forEach(function (parentId) { 43 | var parentChunk = nodeMap[parentId]; 44 | // If the parent chunk does not exist (e.g. because of an excluded chunk) 45 | // we ignore that parent 46 | if (parentChunk) { 47 | edges.push([parentChunk, chunk]); 48 | } 49 | }); 50 | } 51 | }); 52 | // We now perform a topological sorting on the input chunks and built edges 53 | return toposort.array(chunks, edges); 54 | }; 55 | 56 | /** 57 | * Sorts the chunks based on the chunk id. 58 | * 59 | * @param {Array} chunks the list of chunks to sort 60 | * @return {Array} The sorted list of chunks 61 | */ 62 | module.exports.id = function (chunks) { 63 | return chunks.sort(function orderEntryLast (a, b) { 64 | if (a.entry !== b.entry) { 65 | return b.entry ? 1 : -1; 66 | } else { 67 | return b.id - a.id; 68 | } 69 | }); 70 | }; 71 | 72 | /** 73 | * Performs identity mapping (no-sort). 74 | * @param {Array} chunks the chunks to sort 75 | * @return {Array} The sorted chunks 76 | */ 77 | module.exports.none = function (chunks) { 78 | return chunks; 79 | }; 80 | 81 | /** 82 | * Defines the default sorter. 83 | */ 84 | module.exports.auto = module.exports.id; 85 | 86 | // In webpack 2 the ids have been flipped. 87 | // Therefore the id sort doesn't work the same way as it did for webpack 1 88 | // Luckily the dependency sort is working as expected 89 | if (require('webpack/package.json').version.split('.')[0] === '2') { 90 | module.exports.auto = module.exports.dependency; 91 | } 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # muti-html-webpack-plugin 2 | > webpack不适合多页面应用么?那是因为你写的插件不够 3 | > 改造自html-webpack-plugin,可以解析多个html页面模板,将html也纳入到webpack的管理当中 4 | 5 | ## 安装使用 6 | 1. npm install muti-html-webpack-plugin 7 | 2. 在webpack.config.js中引用之: 8 | `var MutiHtmlWebpackPlugin = require('muti-html-webpack-plugin');` 9 | 3. 配置插件 10 | ``` 11 | plugins: [ 12 | extractCSS, 13 | new MutiHtmlWebpackPlugin({ 14 | templatePath: 'views', 15 | loader: 'html?attrs=img:src img:data-src!compile-nunjucks', 16 | templateSuffix: '.html', 17 | path: '/views', 18 | ignore: ['demo.html', 'abc/abc.html'] 19 | }) 20 | ] 21 | ``` 22 | 23 | ## 参数说明 24 | 1. templatePath: 需要解析的html模板根路径,插件将在这里寻找你的模板 25 | 2. loader: 用于解析模板的loader,配置方法同module.loaders配置,两处配置的loaders同时起作用,所以要注意避免配置重复的loader 26 | ``` 27 | module: { 28 | loaders: [{ 29 | test: /\.css$/, 30 | loader: 'style!css!change-rem', 31 | loader: extractCSS.extract('style', 'css') 32 | }, { 33 | //图片加载器,雷同file-loader,更适合图片,可以将较小的图片转成base64,减少http请求 34 | //如下配置,将小于8192byte的图片转成base64码 35 | test: /\.(png|jpg|gif)$/, 36 | loader: 'url-loader?limit=8192&context=client&name=./img/[name].[ext]?[hash:8]' 37 | // loader: [ 38 | // // url-loader更好用,小于10KB的图片会自动转成base64 dataUrl, 39 | // // 否则则调用file-loader,参数直接传入 40 | // 'url-loader?limit=8192&context=client&name=./img/[name].[ext]?[hash:8]', 41 | // 'image?{bypassOnDebug:true, progressive:true,optimizationLevel:3,pngquant:{quality:"65-80",speed:4}}' 42 | // ] 43 | }, { 44 | //html模板加载器,可以处理引用的静态资源,默认配置参数attrs=img:src,处理图片的src引用的资源 45 | //比如配置,attrs=img:src img:data-src就可以一并处理data-src引用的资源了,就像下面这样 46 | test: /\.html$/, 47 | // include: 'test/css', 48 | //这里的插件对所有.html都起作用 49 | loader: 'html?attrs=img:src img:data-src' 50 | }] 51 | }, 52 | plugins: [ 53 | extractCSS, 54 | new MutiHtmlWebpackPlugin({ 55 | templatePath: 'test/views/', 56 | //这里的loader只对插件要处理的模板起作用 57 | loader: 'html?attrs=img:src img:data-src!compile-nunjucks' 58 | templateSuffix: '.html', 59 | path: '/views', 60 | ignore: ['demo.html', 'abc/abc.html'] 61 | }) 62 | ] 63 | ``` 64 | 示例中,module.loaders对所有.html的文件都起作用 65 | 插件中的loaders对插件配置的的模板文件起作用,模板后缀是.html 66 | 所以相当于对模板文件使用了'html!compile-nunjucks!html'三个解析器,导致解析失败。 67 | 解决办法,module.loaders中的配置增加include过滤 68 | 69 | 3. templateSuffix: 模板后缀,用于过滤非模板文件,不传则解析templatePath下的所有文件 70 | 4. ignore: 过滤掉的模板文件,单个文件可以传string,多个文件传数组。场景:公共的头部模板,只是用于引用,我们是不需要解析成单独的文件的 71 | 72 | ## 使用注意 73 | html中引用webpack打包出来的js和css,可以使用##entry.[name].js/css## 74 | ``` 75 | 76 | 77 | 78 |
83 | HELLO WORLD !!!
84 |
85 |
86 |
87 | ```
88 | 对应于webpack.config.js中的entry配置
89 | ```
90 | entry: {
91 | demo: ['./test/js/demo.js'],
92 | demo1: ['./test/js/demo1.js']
93 | }
94 | ```
95 | 其中entry.[name].css对应于css文件(当然,不使用ExtractTextPlugin插件你是拿不到独立的css文件的),
96 | entry.[name].js对应于js的bundle
97 | 如果匹配不到,那么插件不会做任何替换,页面没有拿到正确的引用,只能只求多福了,阿弥陀佛
98 |
99 | 
100 |
101 |
102 |
103 |
--------------------------------------------------------------------------------
/lib/compiler.js:
--------------------------------------------------------------------------------
1 | /*
2 | * This file uses webpack to compile a template with a child compiler.
3 | *
4 | * [TEMPLATE] -> [JAVASCRIPT]
5 | *
6 | */
7 | 'use strict';
8 | var Promise = require('bluebird');
9 | var _ = require('lodash');
10 | var path = require('path');
11 | var NodeTemplatePlugin = require('webpack/lib/node/NodeTemplatePlugin');
12 | var NodeTargetPlugin = require('webpack/lib/node/NodeTargetPlugin');
13 | var LoaderTargetPlugin = require('webpack/lib/LoaderTargetPlugin');
14 | var LibraryTemplatePlugin = require('webpack/lib/LibraryTemplatePlugin');
15 | var SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin');
16 |
17 | /**
18 | * Compiles the template into a nodejs factory, adds its to the compilation.assets
19 | * and returns a promise of the result asset object.
20 | *
21 | * @param template relative path to the template file
22 | * @param context path context
23 | * @param outputFilename the file name
24 | * @param compilation The webpack compilation object
25 | *
26 | * Returns an object:
27 | * {
28 | * hash: {String} - Base64 hash of the file
29 | * content: {String} - Javascript executable code of the template
30 | * }
31 | *
32 | */
33 | module.exports.compileTemplate = function compileTemplate (template, context, outputFilename, compilation) {
34 | // The entry file is just an empty helper as the dynamic template
35 | // require is added in "loader.js"
36 | var outputOptions = {
37 | filename: '[name].html',
38 | publicPath: compilation.outputOptions.publicPath
39 | };
40 |
41 | // Store the result of the parent compilation before we start the child compilation
42 | var assetsBeforeCompilation = _.assign({}, compilation.assets[outputOptions.filename]);
43 | // Create an additional child compiler which takes the template
44 | // and turns it into an Node.JS html factory.
45 | // This allows us to use loaders during the compilation
46 | var compilerName = getCompilerName(context, outputFilename);
47 | var childCompiler = compilation.createChildCompiler(compilerName, outputOptions);
48 | childCompiler.context = context;
49 | childCompiler.apply(
50 | new NodeTemplatePlugin(outputOptions),
51 | new NodeTargetPlugin(),
52 | new LibraryTemplatePlugin('HTML_WEBPACK_PLUGIN_RESULT', 'var'),
53 | // new SingleEntryPlugin(this.context, template),
54 | new LoaderTargetPlugin('node')
55 | );
56 |
57 | function itemToPlugin(item, name) {
58 | if(Array.isArray(item))
59 | return new MultiEntryPlugin(context, item, name);
60 | else
61 | return new SingleEntryPlugin(context, item, name);
62 | }
63 |
64 | Object.keys(template).forEach(function(name) {
65 | childCompiler.apply(itemToPlugin(template[name], name));
66 | });
67 |
68 | // Fix for "Uncaught TypeError: __webpack_require__(...) is not a function"
69 | // Hot module replacement requires that every child compiler has its own
70 | // cache. @see https://github.com/ampedandwired/html-webpack-plugin/pull/179
71 | childCompiler.plugin('compilation', function (compilation) {
72 | if (compilation.cache) {
73 | if (!compilation.cache[compilerName]) {
74 | compilation.cache[compilerName] = {};
75 | }
76 | compilation.cache = compilation.cache[compilerName];
77 | }
78 | });
79 |
80 | // Compile and return a promise
81 | return new Promise(function (resolve, reject) {
82 | childCompiler.runAsChild(function (err, entries, childCompilation) {
83 | // Resolve / reject the promise
84 | if (childCompilation && childCompilation.errors && childCompilation.errors.length) {
85 | var errorDetails = childCompilation.errors.map(function (error) {
86 | return error.message + (error.error ? ':\n' + error.error : '');
87 | }).join('\n');
88 | reject(new Error('Child compilation failed:\n' + errorDetails));
89 | } else if (err) {
90 | reject(err);
91 | } else {
92 |
93 | var items = [];
94 | for(var i=0 ; i < entries.length; i++) {
95 | var outputName = compilation.mainTemplate.applyPluginsWaterfall('asset-path', entries[i].name + '.html', {
96 | hash: childCompilation.hash,
97 | chunk: entries[i]
98 | });
99 |
100 | compilation.assets[outputName] = assetsBeforeCompilation[outputName];
101 |
102 |
103 | if (assetsBeforeCompilation[outputName] === undefined) {
104 | // If it wasn't there - delete it
105 | delete compilation.assets[outputName];
106 | }
107 | items.push({
108 | // Hash of the template entry point
109 | hash: entries[i].hash,
110 | // Output name
111 | outputName: outputName,
112 | // Compiled code
113 | content: childCompilation.assets[outputName].source()
114 | });
115 | }
116 | resolve(items);
117 |
118 | }
119 | });
120 | });
121 | };
122 |
123 | /**
124 | * Returns the child compiler name e.g. 'html-webpack-plugin for "index.html"'
125 | */
126 | function getCompilerName (context, filename) {
127 | var absolutePath = path.resolve(context, filename);
128 | var relativePath = path.relative(context, absolutePath);
129 | return 'html-webpack-plugin for "' + (absolutePath.length < relativePath.length ? absolutePath : relativePath) + '"';
130 | }
131 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var vm = require('vm');
3 | var fs = require('fs');
4 | var _ = require('lodash');
5 | var Promise = require('bluebird');
6 | var path = require('path');
7 | var childCompiler = require('./lib/compiler.js');
8 | var prettyError = require('./lib/errors.js');
9 | var chunkSorter = require('./lib/chunksorter.js');
10 | var readDir = require('./lib/readDirAsync.js');
11 | Promise.promisifyAll(fs);
12 |
13 | function HtmlWebpackPlugin(options) {
14 | // Default options
15 | this.options = _.extend({
16 | template: path.join(__dirname, 'default_index.ejs'),
17 | templatePath: false,
18 | loader: false,
19 | filename: 'index.html',
20 | hash: false,
21 | inject: true,
22 | compile: true,
23 | favicon: false,
24 | minify: false,
25 | cache: true,
26 | showErrors: true,
27 | chunks: 'all',
28 | excludeChunks: [],
29 | title: 'Webpack App',
30 | xhtml: false,
31 | templateSuffix: false,
32 | path: false,
33 | ignore: false
34 | }, options);
35 | }
36 |
37 | HtmlWebpackPlugin.prototype.apply = function(compiler) {
38 | var self = this;
39 | var isCompilationCached = false;
40 | var compilationPromise;
41 |
42 |
43 | self.outputPath = compiler.options.output.path;
44 |
45 | // this.options.template = this.getFullTemplatePath(this.options.template, compiler.context);
46 |
47 | // convert absolute filename into relative so that webpack can
48 | // generate it at correct location
49 | var filename = this.options.filename;
50 | if (path.resolve(filename) === path.normalize(filename)) {
51 | this.options.filename = path.relative(compiler.options.output.path, filename);
52 | }
53 |
54 | compiler.plugin('make', function(compilation, callback) {
55 | //读取dirs
56 | readDir(path.resolve(compiler.context, self.options.templatePath), self.options.templateSuffix, self.options.path, self.options.ignore).then(function(template) {
57 |
58 | template = self.getFullTemplatePath(template);
59 | compilationPromise = childCompiler.compileTemplate(template, compiler.context, self.options.filename, compilation);
60 | return compilationPromise;
61 | }).catch(function(err) {
62 | compilation.errors.push(prettyError(err, compiler.context).toString());
63 | return {
64 | content: self.options.showErrors ? prettyError(err, compiler.context).toJsonHtml() : 'ERROR',
65 | outputName: self.options.filename
66 | };
67 | }).then(function(compilationResult) {
68 | // If the compilation change didnt change the cache is valid
69 | isCompilationCached = compilationResult.hash && self.childCompilerHash === compilationResult.hash;
70 | self.childCompilerHash = compilationResult.hash;
71 | self.childCompilationOutputName = compilationResult.outputName;
72 | callback();
73 | return compilationResult.content;
74 | });
75 |
76 | // Compile the template (queued)
77 | // compilationPromise = childCompiler.compileTemplate(self.options.template, compiler.context, self.options.filename, compilation)
78 | // .catch(function(err) {
79 | // compilation.errors.push(prettyError(err, compiler.context).toString());
80 | // return {
81 | // content: self.options.showErrors ? prettyError(err, compiler.context).toJsonHtml() : 'ERROR',
82 | // outputName: self.options.filename
83 | // };
84 | // })
85 | // .then(function(compilationResult) {
86 | // // If the compilation change didnt change the cache is valid
87 | // isCompilationCached = compilationResult.hash && self.childCompilerHash === compilationResult.hash;
88 | // self.childCompilerHash = compilationResult.hash;
89 | // self.childCompilationOutputName = compilationResult.outputName;
90 | // callback();
91 | // return compilationResult.content;
92 | // });
93 | });
94 |
95 | compiler.plugin('emit', function(compilation, callback) {
96 | var applyPluginsAsyncWaterfall = Promise.promisify(compilation.applyPluginsAsyncWaterfall, { context: compilation });
97 | // Get all chunks
98 | var chunks = self.filterChunks(compilation.getStats().toJson(), self.options.chunks, self.options.excludeChunks);
99 | // Sort chunks
100 | chunks = self.sortChunks(chunks, self.options.chunksSortMode);
101 | // Let plugins alter the chunks and the chunk sorting
102 | chunks = compilation.applyPluginsWaterfall('html-webpack-plugin-alter-chunks', chunks, { plugin: self });
103 | // Get assets
104 | var assets = self.htmlWebpackPluginAssets(compilation, chunks);
105 |
106 | // If this is a hot update compilation, move on!
107 | // This solves a problem where an `index.html` file is generated for hot-update js files
108 | // It only happens in Webpack 2, where hot updates are emitted separately before the full bundle
109 | if (self.isHotUpdateCompilation(assets)) {
110 | return callback();
111 | }
112 |
113 | // If the template and the assets did not change we don't have to emit the html
114 | var assetJson = JSON.stringify(self.getAssetFiles(assets));
115 | if (isCompilationCached && self.options.cache && assetJson === self.assetJson) {
116 | return callback();
117 | } else {
118 | self.assetJson = assetJson;
119 | }
120 |
121 | Promise.resolve()
122 | // Favicon
123 | .then(function() {
124 | if (self.options.favicon) {
125 | return self.addFileToAssets(self.options.favicon, compilation)
126 | .then(function(faviconBasename) {
127 | var publicPath = compilation.options.output.publicPath || '';
128 | if (publicPath && publicPath.substr(-1) !== '/') {
129 | publicPath += '/';
130 | }
131 | assets.favicon = publicPath + faviconBasename;
132 | });
133 | }
134 | })
135 | // Wait for the compilation to finish
136 | .then(function() {
137 | return compilationPromise;
138 | })
139 | .then(function(compiledTemplate) {
140 |
141 | // Allow to use a custom function / string instead
142 | if (self.options.templateContent) {
143 | return self.options.templateContent;
144 | }
145 | // Once everything is compiled evaluate the html factory
146 | // and replace it with its content
147 | return self.evaluateCompilationResult(compilation, compiledTemplate);
148 | })
149 | // Allow plugins to make changes to the assets before invoking the template
150 | // This only makes sense to use if `inject` is `false`
151 | .then(function(compilationResult) {
152 | return applyPluginsAsyncWaterfall('html-webpack-plugin-before-html-generation', {
153 | assets: assets,
154 | outputName: self.childCompilationOutputName,
155 | plugin: self
156 | })
157 | .then(function() {
158 | return compilationResult;
159 | });
160 | })
161 | // Execute the template
162 | .then(function(compilationResult) {
163 | // If the loader result is a function execute it to retrieve the html
164 | // otherwise use the returned html
165 | return compilationResult;
166 | })
167 | // Allow plugins to change the html before assets are injected
168 | .then(function(html) {
169 |
170 | var pluginArgs = { html: html, assets: assets, plugin: self, outputName: self.childCompilationOutputName };
171 | return applyPluginsAsyncWaterfall('html-webpack-plugin-before-html-processing', pluginArgs)
172 | .then(function() {
173 | return pluginArgs.html;
174 | });
175 | })
176 | .then(function(html) {
177 | // html = html[0];
178 | // Prepare script and link tags
179 |
180 | var assetTags = self.generateAssetTags(assets);
181 | var pluginArgs = { head: assetTags.head, body: assetTags.body, plugin: self, chunks: chunks, outputName: self.childCompilationOutputName };
182 | // Allow plugins to change the assetTag definitions
183 | return applyPluginsAsyncWaterfall('html-webpack-plugin-alter-asset-tags', pluginArgs)
184 | .then(function() {
185 | // Add the stylesheets, scripts and so on to the resulting html
186 | return self.postProcessHtml(html, assets, { body: pluginArgs.body, head: pluginArgs.head });
187 | });
188 | })
189 | // Allow plugins to change the html after assets are injected
190 | .then(function(html) {
191 |
192 | var pluginArgs = { html: html, assets: assets, plugin: self, outputName: self.childCompilationOutputName };
193 | return applyPluginsAsyncWaterfall('html-webpack-plugin-after-html-processing', pluginArgs)
194 | .then(function() {
195 | return pluginArgs.html;
196 | });
197 | })
198 | .catch(function(err) {
199 | // In case anything went wrong the promise is resolved
200 | // with the error message and an error is logged
201 | compilation.errors.push(prettyError(err, compiler.context).toString());
202 | // Prevent caching
203 | self.hash = null;
204 | return self.options.showErrors ? prettyError(err, compiler.context).toHtml() : 'ERROR';
205 | })
206 | .then(function(html) {
207 | var addToAssets = function(source, outputName) {
208 | compilation.assets[outputName] = {
209 | source: function() {
210 | return source;
211 | },
212 | size: function() {
213 | return source.length;
214 | }
215 | };
216 | }
217 | // Replace the compilation result with the evaluated html code
218 | for(var i=0; i