# HTML处理
在项目中除了需要 JavaScript、CSS 和图片等静态资源,还需要页面来承载这些内容和页面结构,怎么在 Webpack 中处理 HTML。并且项目也不仅仅是单页应用(Single-Page Application,SPA),也可能是多页应用,所以还需要使用 Webpack 来给多页应用做打包。本小节将讲解这俩问题。
# html-webpack-plugin
有了 JavaScript 文件,还缺 HTML 页面,要让 Webpack 处理 HTML 页面需要只需要使用html-webpack-plugin (opens new window)插件即可,首先安装它:
npm i html-webpack-plugin --save-dev
然后修改对应的 webpack.config.js 内容:
const HtmlWebPackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
entry: {
main: './src/index.js'
},
plugins: [new HtmlWebPackPlugin()]
};
只需要简单配置,执行webpack
打包之后,发现 log 中显示,在dist
文件夹中生成一个index.html
的文件:
打开后发现 HTML 的内容如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Webpack App</title>
<meta name="viewport" content="width=device-width, initial-scale=1"><script defer src="main.js"></script></head>
<body>
</body>
</html>
除了 HTML 外,的 entry 也被主动插入到了页面中,这样打开index.html
就直接加载了main.js
了。
如果要修改 HTML 的title
和名称,可以如下配置:
const HtmlWebPackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
entry: {
main: './src/index.js'
},
plugins: [new HtmlWebPackPlugin({title: 'hello', filename: 'foo.html'})]
};
形成出来的foo.html
的文件如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>hello</title>
<meta name="viewport" content="width=device-width, initial-scale=1"><script defer src="main.js"></script></head>
<body>
</body>
</html>
# Template
虽然可以简单的修改 Title 这里自定义内容,但是对于日常项目来说,这远远不够。
希望 HTML 页面需要根据的意愿来生成,也就是说内容是来定的,甚至根据打包的 entry 最后结果来定,这时候就需要使用html-webpack-plugin
的template
功能了。
比如我在index.js
中,给id="app"
的节点添加内容,这时候 HTML 的内容就需要自定义了,至少应该包含一个含有id="app"
的 DIV 元素:
<div id="app"></div>
可以创建一个自己想要的 HTML 文件,比如index.html
,在里面写上想要的内容:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Webpack</title>
</head>
<body>
<h1>hello world</h1>
<div id="app"></div>
</body>
</html>
把 webpack.config.js 更改如下:
const HtmlWebPackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
entry: {
main: './src/index.js'
},
plugins: [
new HtmlWebPackPlugin({
template: './src/index.html'
})
]
};
这时候,打包之后的 HTML 内容就变成了:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Webpack</title>
<script defer src="main.js"></script></head>
<body>
<h1>hello world</h1>
<div id="app"></div>
</body>
</html>
也是添加上了main.js
内容。
# 使用JS模板引擎
HTML 毕竟还是有限,这时候还可以使用 JavaScript 模板引擎来创建html-webpack-plugin
的 Template 文件。
下面我以pug (opens new window)模板引擎为例,来说明下怎么使用模板引擎文件的 Template。
首先创建个index.pug
文件,内容如下:
doctype html
html(lang="en")
head
title="Hello Pug"
script(type='text/javascript').
console.log('pug inline js')
body
h1 Pug - node template engine
#app
include includes/footer.pug
如果不理解 Pug 模板的语法,可以简单看下文档,我这里简单解释下,首先在头部加了 title 和一个script
标签,然后在 body 中内容为 h1、id="app"
的 div 和引入(include)了一个 footer.pug
的文件:
footer#footer
p Copyright @Copyright 2019
这时候需要修改 webpack.config.js 内容:
const HtmlWebPackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
entry: {
main: './src/index.js'
},
plugins: [
new HtmlWebPackPlugin({
template: './src/index.pug'
})
]
};
但是只修改template='src/index.pug'
是不够的,因为.pug
这样的文件 Webpack 是不会解析的,所以需要加上 Pug 的 loader:pug-html-loader (opens new window),除了这个插件还需要安装html-loader (opens new window)。首先通过npm i -D pug-html-loader html-loader
安装它们,然后修改 webpack.config.js 内容,添加 rule:
const HtmlWebPackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
entry: {
main: './src/index.js'
},
plugins: [
new HtmlWebPackPlugin({
template: './src/index.pug'
})
],
module: {
rules: [{test: /\.pug$/, loader: ['html-loader', 'pug-html-loader']}]
}
};
最后,得到的index.html
内容如下:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Hello Pug</title>
<script type="text/javascript">
console.log('pug inline js');
</script>
</head>
<body>
<h1>Pug - node template engine</h1>
<div id="app"></div>
<footer id="footer"><p>Copyright @Copyright 2019</p></footer>
<script src="main.js"></script>
</body>
</html>
Pug 引擎被转换成 HTML代码,里面包含了:main.js
和footer.pug
的内容。
关于html-webpack-plugin (opens new window)的参数这里就不展开了,可以查阅它的 README 文档。
Tips:使用 JavaScript 模板引擎,还可以定义一些变量,通过 html-webpack-plugin 传入进去。
# 多页项目配置
要做一个多页项目的配置,那么需要考虑以下几个问题:
- 多页应用,顾名思义最后打包生成的页面也是多个,即 HTML 是多个;
- 多页应用不仅仅是页面多个,入口文件也是多个;
- 多页应用可能页面之间页面结构是不同的,比如一个网站项目,典型的三个页面是:首页、列表页和详情页,肯定每个页面都不一样。
下面来一个一个的问题解决:
# 多页面问题
多页面就是指的多个 HTML 页面,这时候可以直接借助 html-webpack-plugin 插件来实现,只需要多次实例化一个 html-webpack-plugin 的实例即可,例如:
下面是同一个 template,那么可以只修改filename
输出不同名的 HTML 即可:
const HtmlWebPackPlugin = require('html-webpack-plugin');
const indexPage = new HtmlWebPackPlugin({
template: './src/index.html',
filename: 'index.html'
});
const listPage = new HtmlWebPackPlugin({
template: './src/index.html',
filename: 'list.html'
});
module.exports = {
mode: 'development',
entry: {
main: './src/index.js'
},
plugins: [indexPage, listPage]
};
对于页面结构不同的 HTML 页面的配置,使用不同的 template 即可。
const HtmlWebPackPlugin = require('html-webpack-plugin');
const indexPage = new HtmlWebPackPlugin({
template: './src/index.html',
filename: 'index.html'
});
const listPage = new HtmlWebPackPlugin({
template: './src/list.html',
filename: 'list.html'
});
module.exports = {
mode: 'development',
entry: {
main: './src/index.js'
},
plugins: [indexPage, listPage]
};
# 多入口问题
上面的多页面解决是多次实例化 html-webpack-plugin,根据传入的参数不同(主要是 filename 不同),打包出两个文件,但是这两个文件的特点是引入的 JavaScript 文件都是一样的,即都是main.js
。
对于多入口,并且入口需要区分的情况,那么需要怎么处理呢?
这时候就需要借助 html-webpack-plugin 的两个参数了:chunks
和excludeChunks
:
chunks
是当前页面包含的 chunk 有哪些,可以直接用 entry 的key
来命名- excludeChunks`则是排除某些 chunks。
例如,现在有两个 entry,分别是index.js
和list.js
,希望index.html
跟index.js
是一组,list.html
跟list.js
是一组,那么 webpack.config.js 需要修改为:
const HtmlWebPackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
entry: {
index: './src/index.js',
list: './src/list.js'
},
plugins: [
new HtmlWebPackPlugin({template: './public/index.html', filename: 'index.html', chunks: ['index']}),
new HtmlWebPackPlugin({template: './public/list.html', filename: 'list.html', chunks: ['list']})
]
};
打包出来内容:
# 最佳实践
现在说下我在项目中的一般做法,个人认为这是多页应用的最佳实践。
步骤与思路:
- 约定的目录结构(方便写逻辑代码);
- 使用js读取目录结构与文件;
- 创建entries数组;
- 创建HtmlWebPackPlugin数组;
- 最后拼接成webpack配置。
首先,需要规定下目录结构规范,一般项目会有下面的目录规范:
.
├── dist
│ ├── index.html
│ ├── index.js
│ ├── list.html
│ └── list.js
├── package-lock.json
├── package.json
├── scripts
│ ├── resolve.js
│ └── utils.js
├── src
│ └── pages
├── template
│ ├── index.html
│ └── list.html
└── webpack.config.js
保证 template 和实际的 entry 是固定的目录,并且名字都是对应的。
这时候可以写个 Node.js 代码遍历对应的路径,然后生成 webpack.config.js 的 entry
和html-webpack-plugin
内容。
这里我使用了globby (opens new window)这个 NPM 模块,先写了读取src/pages/*.js
的内容,然后生成entry
:
注意:
globby这个库,目前12.x的版本是ES Module的版本,用法发生了重大变化。
import {globby} from 'globby';
const paths = await globby(['*', '!cake']);
console.log(paths);
//=> ['unicorn', 'rainbow']
而上面的代码,可以参考如下命令安装:
npm i globby@11
scripts/resolve.js
文件:
const path = require('path')
// 项目根目录
module.exports.resolve = (src) => path.join(path.resolve(__dirname, '../'), src)
// src目录
module.exports.src = (src) => path.join(path.resolve(__dirname, '../src'), src)
scripts/utils.js
文件:
const path = require('path');
const globby = require('globby');
const src = require('./resolve').src
const HtmlWebPackPlugin = require('html-webpack-plugin');
const getEntry = (source = './pages', ext='.js') => {
// 异步方式获取所有的路径
const res = src(source)
const paths = globby.sync(res, {
cwd: src(source)
});
const rs = {};
paths.forEach(v => {
// 计算 filename
const name = path.basename(v, ext);
rs[name] = v;
});
return rs;
};
exports.getEntry = getEntry
// 遍历`entry`对象,然后生成 html-webpack-plugins 的数组了
exports.getHtmlWebpackPlugins = () => {
const entries = getEntry('../template', '.html');
return Object.keys(entries).reduce((plugins, filename) => {
plugins.push(
new HtmlWebPackPlugin({
template: entries[filename],
filename: `${filename}.html`,
chunks: [filename]
})
);
return plugins;
}, []);
};
在 webpack.config.js 用的时候,直接require
引入刚刚写的这个文件,然后:
const { getEntry, getHtmlWebpackPlugins } = require('./scripts/utils')
const entry = getEntry()
console.log('🚀 ~ file: webpack.config.js ~ line 4 ~ entry', entry)
const htmls = getHtmlWebpackPlugins()
console.log('🚀 ~ file: webpack.config.js ~ line 7 ~ htmls', htmls)
module.exports = {
mode: 'development',
entry: entry,
plugins: [
//...
...getHtmlWebpackPlugins()
]
};
打包结果:
这里还有一份webpack4中尝试的项目:项目地址 (opens new window)