一直以來,前端享有無需配置,一個瀏覽器足矣的優點,直到一大堆構建工具的出現,其中 webpack 就是其中最複雜的一個,所以出現了一個新興職業 『webpack 配置工程師』。其實 webpack 配置本質上來講也就是編程,難點在於各類 loader 和 plugin 的選擇和合理搭配,下面就由我來捋一捋。css
使用 webpack 配置單頁應用的教程不少,直接使用官方的 vue-cli 工具就很是方便,今天我要說的是如何配置一個多頁應用,這若是會了,單頁應用也固然不在話下。html
先放下項目地址項目地址 vue-multi-page-webpack前端
逼很少裝,先說下要達成的效果。假設咱們有兩個目錄vue
|-- page1 |-- page2 |-- page3
要作到node
讓咱們愉快的開始吧!注意:一些很基礎的配置我就不提了,能夠在個人 github 上看完整配置項webpack
按照慣例,配置分爲開發和生產的配置,能夠分爲 webpack.base.config.js
webpack.dev.config.js
webpack.prod.config.js
git
先從webpack.base.config.js
提及es6
entry: { vendor: ['vue'], }, output: { path: consts.DIST_PATH, filename: '[name].[chunkhash:7].js', publicPath: '/' },
由於每一個頁面都要用到 vue,所以把它放到 vendor 裏,這裏入口還不急配置入口文件,由於上面咱們說過要實現按需開發和打包,所以這裏是動態配置的,後面會說到。output 裏都是常規配置,能夠把一些常量單獨提取出來。github
接下來配置 loader,用 eslint-loader 舉例說明web
{ 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