需求:
- 编译 sass,压缩,抽离为单独的文件
- 编译、压缩 ES6 语法的 js,抽离公共文件
- 处理其他资源,image 文件,字体文件等
- 将模板转换成 html 文件
- html 文件自动引入静态资源,转换路径
- 生成多个页面
- 开发模式热更新
- 开发模式接口代理
解决方案:
前面四个问题,在别的文章解释,其他的问题,咱们就在本文讨论
html 文件自动引入静态资源,转换路径
这个参考 将模板转换成 html 文件,html-webpack-plugin 在生成 html 文件时会自动引入资源文件,不过多入口时需要指定引用资源
生成多个页面
简单一点,配置多个入口,然后多整几个 new html-webpack-plugin。但是程序员的本质,是闪电。所以,copy 是不可能的,这辈子都不可能 copy 的,只能写写配置文件,然后批量生成这样子。所以我们可以这样做,先写一个配置文件,放置所有的页面的相关信息,然后写一个入口生成函数,批量生成入口。具体的做法看代码:
新建入口文件entrys.js
const path = require('path')
function resolve (dir) {
return path.join(__dirname, '..', dir)
}
// 这里的配置信息,建议根据 html-webpack-plugin 的配置项信息编写,这样后续的工作会方便一些
let entrys = {
globalParameters: {},
list: [
{
entry: 'index', // 入口名称
entryPath: resolve('src/script/index.js'), // 入口文件路径
title: '默认页面',
template: resolve('src/view/index.html'), // 模板文件路径
},
{
entry: 'home',
entryPath: resolve('src/script/home.js'),
title: '首页',
template: resolve('src/view/home.html'),
},
]
}
module.exports = entrys
然后编写生成配置的函数,需要生成两个东西,一个是 webpack 的入口对象,一个是 html-webpack-plugin 对象
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
function resolve (dir) {
return path.join(__dirname, '..', dir)
}
const isProd = process.env.NODE_ENV === "production";
const defaults = {
title: '',
filename: '',
template: '',
chunks: '',
templateParameters: {}
}
console.log(isProd);
// 构建入口
exports.buildEntry = function (entrys) {
let entry = {};
entrys.list.forEach(item => {
if(!item.entry) return;
entry[item.entry] = item.entryPath;
})
return entry;
}
// 构建生成 HTML 模板的入口
exports.buildTemplateEntry = function (entrys) {
let arr = [],
parameters = entrys.globalParameters;
entrys.list.forEach(item => {
let opt = Object.assign({}, defaults);
opt.title = item.title;
opt.filename = `view/${ item.filename || item.entry }.html`;
opt.template = item.template;
opt.templateParameters = item.templateParameters ?
Object.assign({}, parameters, item.templateParameters) :
Object.assign({}, parameters);
opt.chunks = item.entry ? [item.entry] : [];
arr.push(new HtmlWebpackPlugin(opt));
})
return arr;
}
然后修改 webpack 的配置文件:
const entryHelp = require('./entryHelp')
const entrys = require('../config/entry')
{
// ....
entry: entryHelp.buildEntry(entrys),
// 模板构建
plugins: [
...entryHelp.buildTemplateEntry(entrys)
]
// ....
}
到这里,生成多页面的配置就完成了。
热更新
热更新,顾名思义,修改文件后页面立马更新呈现修改后的内容。包括以下内容变更时的热更新:
- js 代码修改
- 静态资源修改(样式,图片)
- 模板修改
- webpack 或其他配置文件本身被修改
1 和 2 的变动,都可以直接使用 webpack 的热更新配置 devServer 处理就行,这里也还是上一下代码,还有什么疑问的话建议看官方文档 webpack热更新
配置文件的修改:
devServer: {
hot: true
},
plugins: [
// 模块热替换
new webpack.NamedModulesPlugin(),
new webpack.HotModuleReplacementPlugin(),
]
还需要在入口文件的末尾加上以下代码:
if (module.hot) {
module.hot.accept()
}
然后以 webpack-dev-server 命令启动服务,就可以实现热更新了,
webpack-dev-server --config build/webpack.dev.conf.js
html 文件的变动,webpack 没有默认的监听处理,需要自己手动配置,做法是在构建完毕的回调里判断html是否有更新,有更新则往客户端推送更新,具体的配置信息如下:
devServer: {
// html 文件修改时刷新整个页面
before(app, server, compiler) {
const watchFiles = ['.html'];
compiler.hooks.done.tap('done', () => {
const changedFiles = Object.keys(compiler.watchFileSystem.watcher.mtimes);
if (
this.hot
&& changedFiles.some(filePath => watchFiles.includes(path.parse(filePath).ext))
) {
server.sockWrite(server.sockets, 'content-changed');
}
});
},
}
webpack 配置的变更与其他配置文件变更实际上是无法热更新的,只能重启整个服务,虽然重启就那么两个操作
ctrl + c
npm run start
但是程序员皆是闪电,这点操作我也不愿意,能不能我保存配置就自动重启呢?答案是可以的!不过服务启动方式就得变一变了,不能再直接配置命令,然后直接启动,得使用 webpack api 调用的方式启动。首先我们得监听配置文件的变动,这里需要借助一下第三方插件 nodemon 我使用的版本 ^2.0.3。如果文件有变动,则重新执行 指定的 js 文件,nodemon 就是干这事儿的。
需要先写一个启动服务的配置 dev-server.js,以 api 调用的方式启动 webpack 热更新服务
const path = require('path')
const webpack = require('webpack')
const webpackDevServer = require('webpack-dev-server');
const baseConfig = require('../config/idnex') // webpack 的配置文件,具体格式可以参考 vue-cli2 的模板的 conf/index.js 配置文件
const config = require('./webpack.dev.conf')
const compiler = webpack(config);
const HOST = process.env.HOST
const PORT = process.env.PORT && Number(process.env.PORT)
function resolve (dir) {
return path.join(__dirname, '..', dir)
}
const devConfig = {
contentBase: resolve('dist'),
clientLogLevel: 'warning',
hot: true,
host: HOST || baseConfig.dev.host,
port: PORT || baseConfig.dev.port,
proxy: baseConfig.dev.proxyTable,
// html 文件修改时刷新整个页面
before(app, server, compiler) {
const watchFiles = ['.html'];
compiler.hooks.done.tap('done', () => {
const changedFiles = Object.keys(compiler.watchFileSystem.watcher.mtimes);
if (
this.hot
&& changedFiles.some(filePath => watchFiles.includes(path.parse(filePath).ext))
) {
server.sockWrite(server.sockets, 'content-changed');
}
});
},
}
webpackDevServer.addDevServerEntrypoints(config, devConfig);
const server = new webpackDevServer(compiler, devConfig);
server.listen(devConfig.port, 'localhost', () => {
console.log(`dev server listening on port ${ devConfig.port }`);
});
然后编写一个 nodemon 的配置文件,指定监听目录以及其他的一些配置,配置文件是 json 格式,但是这里为了展示,写成 js 格式
nodemon.json
{
"ignore": [], // 希望忽略的目录
"watch": ["./build", "./config"] // 监听目录
}
然后在 package.json 加一条脚本命令
"serve": "nodemon build/dev-server.js"
开发的时候,npm run serve 启动即可,如果修改了 build 或者 config 目录下的文件,则会重新执行对应的脚本,也就是重启 webpack 服务
开发模式接口代理
很多时候,接口服务器与 web 项目配置不在一个地方,这种时候就要配置接口代理,接口代理在 webpack 的 devServer.proxy 中配置,下面展示一下简单的代码,更多配置可以看官方文档的 接口代理
devServer: {
proxy: {
"/api": {
target: "https://other-server.example.com", // 这里的接口地址一定要带上请求协议,不能只写域名
}
}
}
到这里,整个 webpack 生成多页面的开发环境搭建就完成了,项目的 demo 在 GitHub 上, dev 分支,有需要的可以拉下来看看,页面地址:https://github.com/just-a-developer/webpack-template-html/tree/dev