需求:
- 編譯 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