webpack 配置 Vue 多頁應用 —— 從入門到放棄
webpack 配置 Vue 多頁應用 —— 從入門到放棄
一直以來,前端享有無需配置,一個瀏覽器足矣的優勢,直到一大堆構建工具的出現,其中 webpack 就是其中最複雜的一個,因此出現了一個新興職業 『webpack 配置工程師』。其實 webpack 配置本質上來說也就是程式設計,難點在於各種 loader 和 plugin 的選擇和合理搭配,下面就由我來捋一捋。
使用 webpack 配置單頁應用的教程很多,直接使用官方的 vue-cli 工具就非常方便,今天我要說的是如何配置一個多頁應用,這如果會了,單頁應用也當然不在話下。
先放下專案地址專案地址 vue-multi-page-webpack
逼不多裝,先說下要達成的效果。假設我們有兩個目錄
|-- page1
|-- page2
|-- page3
要做到
- 每個頁面都有自己的配置項,包括但不僅限於 title、指令碼等
- 可以打包所有目錄及其子目錄
- 可以做到只開發或打包指定目錄(例如目錄很多,我們只想要開發其中一個頁面)
- 餘下的就是一些基本的配置項了,這些單頁面配置也有,如 eslint、babel、postcss 等等
讓我們愉快的開始吧!注意:一些很基礎的配置我就不提了,可以在我的 github 上看完整配置項
按照慣例,配置分為開發和生產的配置,可以分為 webpack.base.config.js
webpack.dev.config.js
webpack.prod.config.js
先從webpack.base.config.js
說起
entry: {
vendor: ['vue'],
},
output: {
path: consts.DIST_PATH,
filename: '[name].[chunkhash:7].js',
publicPath: '/'
},
因為每個頁面都要用到 vue,因此把它放到 vendor 裡,這裡入口還不急配置入口檔案,因為上面我們說過要實現按需開發和打包,因此這裡是動態配置的,後面會說到。output 裡都是常規配置,可以把一些常量單獨提取出來。
接下來配置 loader,用 eslint-loader 舉例說明
{
enforce: 'pre',
test: /\.(js|vue)$/,
exclude: /node_modules/,
loader: 'eslint-loader',
options: {
formatter: require('eslint-friendly-formatter'),
emitWarning: true,
}
}
loader 配置大同小異,一般就是其中的 test
和 loader
值不同,enforce: 'pre'
表示在其它 loader 之前執行,test
表示對哪種型別的檔案轉換,loader
表示使用的 loader
,對於每種 loader 來說最好去相應的官方文件上去看看用法。這裡說下我用到的。
eslint-loader 進行程式碼檢查的,在根目錄下配置一下 eslintrc ,因為是 vue 專案,所以 plugin 增加 vue,這需要引入 eslint-plugin-vue,然後 env 設定 es6: true
開啟 ES6,需要注意的是最新版本可能有坑,如果你用的以前的 eslint 配置,可能會報錯,可以看看 這個,不要問我為什麼知道... 其它配置可參考我的。
babel-loader json-loader vue-html-loader 處理對應型別的檔案,沒什麼好說的。
值得注意的是對於圖片的處理,這裡使用了 file-loader,要注意 outputPath 的配置,很容易導致圖片404。
對於 css 和 vue 檔案,配置有些不同,稍後再說。
接下來就是重點 webpack.dev.config.js
,之前說過要按需開發,首先科普下一個小知識。我們建立 test.js
// test.js
console.log(process.env.a)
a=b node test.js // 輸出b
之前的 a=b
會被寫入環境變數中,通過 process.env
可以得到,通過這樣就可以實現按需開發。
以前都是直接執行 webpack 命令帶上 開發環境的配置檔案使用自帶的 server 來開發和熱更新,這對於帶引數按需開發不現實,這裡使用 express 和 webpack-dev-middleware 加 webpack-hot-middleware 來達到目的,其實配置差不多。建立一個 server.js
server.js
const express = require('express')
const webpack = require('webpack')
const webpackDevMiddleware = require('webpack-dev-middleware')
const webpackHotMiddleware = require('webpack-hot-middleware')
const app = express();
const config = require('./webpack.dev.config.js')
const compiler = webpack(config)
const devMiddleware = webpackDevMiddleware(compiler, {
stats: {
colors: true,
chunks: false,
children: false,
},
lazy: false,
publicPath: '/',
})
app.use(devMiddleware)
app.use(webpackHotMiddleware(compiler))
app.listen(3000, function () {
console.log('Modules listening on port 3000!\n')
})
我們使用 module=page1 node server.js
啟動,這時 page1 就是環境變數 module 的值。
在 webpack.dev.config.js
配置中,
const moduleName = process.env.module
let moduleList = []
if (moduleName) {
moduleList = moduleName.split(',')
} else {
moduleList = utils.allModules
}
這樣就可以得到需要開發的模組,可以看到其實也支援 module=page1,page2
這樣的形式。
接著就需要將moduleList 中的每一項提取為入口檔案
const configList = utils.loadModules(moduleList)
const moduleContent = utils.getModuleConfigs(configList)
這裡utils 的程式碼有點長,就不放在這了,可以在 GitHub 上看,主要說說是怎麼做的。上面moduleList = ['page1', 'page2']
,尋找 src 目錄下的每一項,即我們要開發的目錄,對於每個模組,可能有自己的 title 和 需要載入的指令碼,因此在每個目錄裡需要一個配置檔案,這裡看下page1 的配置檔案 config.yml
entry: 'page1'
template:
title: 'page1'
scripts:
- 'https://unpkg.com/vue-router/dist/vue-router.min.js'
- 'https://unpkg.com/vuex/dist/vuex.min.js'
這是一個 .yml
檔案,當然你用 .json
檔案什麼的其實也可以,這裡規定每個模組都需要有一個 config.yml
。loadModules 方法把 config.yml
的配置讀取出來,生成
[{
entry: 'page1',
template: {
title: 'page1',
scripts: ['script1', 'script2']
}
}]
根據這個配置檔案,再來生成之前提到的入口配置,{ 'page1/page1': [ './src/page1' ] }
即最後生成的是 page1 目錄下的 page1.js,與此同時,為每一個目錄生成一個 html 檔案,這裡使用了 HtmlWebpackPlugin
外掛,上面的配置檔案中有 title 和 scripts,可以傳入 html 模板中生成相應的部分。
```<!DOCTYPE html>
<html>
<head>
<title><%= htmlWebpackPlugin.options.title %></title>
<meta charset="utf-8">
</head>
<body>
<h1>HELLO WORLD!</h1>
<div id="app"></div>
<!-- 如果有內嵌的js可以放在這裡 -->
<!-- <script defer></script> -->
<!-- 通過引數插入的 script -->
<% for (var i = 0; i < htmlWebpackPlugin.options.scripts.length; i++) { %>
<script src=<%= htmlWebpackPlugin.options.scripts[i] %>></script>
<% } %>
</body>
</html>
<p>使用 <code>HtmlWebpackPlugin</code> 外掛時需要注意 template 即上面的模板,chunks 即打包成的 js 檔案,vendor 是公用的,裡面有 vue,放在最前面,後面就是每個頁面自己的 js 檔案,<code>inject: 'body'</code> 表示放在 body 前,當然你也可以新增自己想要的引數,按照上面模板的方式插進去。</p>
const getHtmlTemplatePlugin = config => {
return new HtmlWebpackPlugin({
filename: ${config.entry}/index.html
,
template: path.join(consts.ROOT_PATH, 'build/index.tpl'),
title: config.template.title,
minify: {
removeComments: true,
collapseWhitespace: true,
minifyCSS: true,
minifyJS: true,
collapseBooleanAttributes: true,
},
inject: 'body',
chunks: ['vendor', config.chunkName],
scripts: config.template.scripts || [],
})
}
<p>接著就是開發環境需要的 loader ,首先是 css 檔案,這裡使用了 postcss,為了方便在根目錄新增 <code>.postcssrc</code> 檔案,postcss-import 可以方便的用 import 命令引用 css 檔案,postcss-cssnext 就是使用一些新的語法,postcss-nested 可以使用巢狀的語法,如果你是開發移動端,使用了 flexible.js,使用 postcss-px2rem 可以將 px 轉成 rem,注意你必須在上面的模板檔案中新增 flexible.js。</p>
module.exports = {
"plugins": {
"postcss-import": {},
"postcss-cssnext": {},
"postcss-nested": {},
"postcss-px2rem": { remUnit: 75 },
}
}
<p>這裡把 css的配置封裝一下,在生產環境中使用 <code>ExtractTextPlugin</code> 將css單獨提取成檔案,開發環境則不需要,<strong>注意三個loader</strong>的順序</p>
getCssLoaderConfig: function(isProduction) {
const config = [{
loader: 'css-loader',
options: {
modules: true,
importLoaders: 1
}
}, {
loader: 'postcss-loader',
}]
return isProduction ? ExtractTextPlugin.extract({
fallback: 'style-loader',
use: config,
}) : [{ loader: 'style-loader '}].concat(config)
},
<p>對於 vue 檔案來說類似,這裡就不放程式碼了。</p>
<p>接下來就是外掛, CommonsChunkPlugin 就是把之前 入口的 vendor 單獨打包,HotModuleReplacementPlugin 就是實現開發環境時熱更新。</p>
new webpack.optimize.CommonsChunkPlugin({
name: ['vendor'],
filename: '[name].[hash:7].js',
minChunks: 88
}),
new webpack.HotModuleReplacementPlugin(),
<p>當然要實現熱更新還是光有上面的外掛還是不夠的,需要給每個入口增加一個<code>webpack-hot-middleware/client?reload=true</code>,這裡通過程式碼來增加</p>
Object.keys(moduleContent.entry).forEach(key => {
if (Array.isArray(moduleContent.entry[key])) {
moduleContent.entry[key].push('webpack-hot-middleware/client?reload=true')
}
})
<p>到此,你就可以開心的使用 <code>module=page1 node server.js</code> 來開發了 page1 模組了,是不是很簡單。加入要開發所有模組,不帶 <code>module=page1</code> 即可,因為有個可以讀取 src 目錄下的所有目錄</p>
get allModules() {
return fs.readdirSync(consts.SRC_PATH)
}
<p>還有一點要提一下的就是假如是巢狀的目錄配置檔案如下其實程式碼也是適用的,只要給每個子目錄新增相應的配置檔案即可</p>
entry: 'page2'
template:
title: 'page2'entry: 'page2/page3'
template:
title: 'page3'
scripts:
- 'https://unpkg.com/vue-router/dist/vue-router.min.js'
- 'https://unpkg.com/vuex/dist/vuex.min.js'
```
最後再來說一說生產環境的,其實和開發環境大體類似。需要一個 build.js,這裡比開發環境簡單一點,只需執行配置就好。
const ora = require('ora')
const webpack = require('webpack')
function runWebpack() {
const webpackConf = require('./webpack.prod.config')
const spinner = ora('building for production...').start()
webpack(webpackConf, (err, stats) => {
spinner.stop()
if (err) throw err
console.log(stats.toString({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false,
}))
})
}
runWebpack()
生產環境配置有一點不同的是要指定output目錄,一般可以是 CDN 或 伺服器的目錄,這裡使用了 dist 目錄,通過 CleanWebpackPlugin 外掛可以在每次打包之前清除 dist 目錄,UglifyJsPlugin 壓縮 js 檔案,ExtractTextPlugin 外掛就是提取 css 為單獨檔案,即上面在使用ExtractTextPlugin.extract
時處理的css。最後為了除錯方便,可以使用一段程式碼生成最後的配置檔案。
fs.writeFile('webpack.prod.config.json', JSON.stringify(prodConfig, null, 2), (err) => {
if (err) throw err
console.log('Dev config file generated')
})
至此,配置基本結束,還有一點,用 module=page1 node server.js
這樣的命令體現不出逼格,因此寫個 makefile 檔案,使命令簡單點。
.PHONY: build
.PHONY: dev
dev:
@sudo module=${module} node build/dev.js
build:
@sudo module=${module} node build/build.js
當使用 make dev
即執行對應的命令,開發所有模組,make dev module=page1
即單獨開發 page1,同理 make build
打包命令。其實make 命令是很強大的,可以更方便的執行多條命令,讓自動化變得更簡單,我也只會這一點毛皮。
在最後的一天工作日終於寫完了,拖延症晚期患者。如果本文及這個小專案對你有用,點個讚唄。專案地址vue-multi-page-webpack。
溜了,溜了,趕火車肥家。大家春節愉快。
原文地址:https://segmentfault.com/a/1190000013285878