webpack 配置 Vue 多页应用 —— 从入门到放弃
一直以来,前端享有无需配置,一个浏览器足矣的优点,直到一大堆构建工具的出现,其中 webpack 就是其中最复杂的一个,所以出现了一个新兴职业 『webpack 配置工程师』。其实 webpack 配置本质上来讲也就是编程,难点在于各类 loader 和 plugin 的选择和合理搭配,下面就由我来捋一捋。css
使用 webpack 配置单页应用的教程不少,直接使用官方的 vue-cli 工具就很是方便,今天我要说的是如何配置一个多页应用,这若是会了,单页应用也固然不在话下。html
先放下项目地址项目地址 vue-multi-page-webpack前端
逼很少装,先说下要达成的效果。假设咱们有两个目录vue
|-- page1 |-- page2 |-- page3
要作到node
- 每一个页面都有本身的配置项,包括但不只限于 title、脚本等
- 能够打包全部目录及其子目录
- 能够作到只开发或打包指定目录(例如目录不少,咱们只想要开发其中一个页面)
- 余下的就是一些基本的配置项了,这些单页面配置也有,如 eslint、babel、postcss 等等
让咱们愉快的开始吧!注意:一些很基础的配置我就不提了,能够在个人 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>
使用 HtmlWebpackPlugin
插件时须要注意 template 即上面的模板,chunks 即打包成的 js 文件,vendor 是公用的,里面有 vue,放在最前面,后面就是每一个页面本身的 js 文件,inject: 'body'
表示放在 body 前,固然你也能够添加本身想要的参数,按照上面模板的方式插进去。
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 || [], }) }
接着就是开发环境须要的 loader ,首先是 css 文件,这里使用了 postcss,为了方便在根目录添加 .postcssrc
文件,postcss-import 能够方便的用 import 命令引用 css 文件,postcss-cssnext 就是使用一些新的语法,postcss-nested 可使用嵌套的语法,若是你是开发移动端,使用了 flexible.js,使用 postcss-px2rem 能够将 px 转成 rem,注意你必须在上面的模板文件中添加 flexible.js。
module.exports = { "plugins": { "postcss-import": {}, "postcss-cssnext": {}, "postcss-nested": {}, "postcss-px2rem": { remUnit: 75 }, } }
这里把 css的配置封装一下,在生产环境中使用 ExtractTextPlugin
将css单独提取成文件,开发环境则不须要,注意三个loader的顺序
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) },
对于 vue 文件来讲相似,这里就不放代码了。
接下来就是插件, CommonsChunkPlugin 就是把以前 入口的 vendor 单独打包,HotModuleReplacementPlugin 就是实现开发环境时热更新。
new webpack.optimize.CommonsChunkPlugin({ name: ['vendor'], filename: '[name].[hash:7].js', minChunks: 88 }), new webpack.HotModuleReplacementPlugin(),
固然要实现热更新仍是光有上面的插件仍是不够的,须要给每一个入口增长一个webpack-hot-middleware/client?reload=true
,这里经过代码来增长
Object.keys(moduleContent.entry).forEach(key => { if (Array.isArray(moduleContent.entry[key])) { moduleContent.entry[key].push('webpack-hot-middleware/client?reload=true') } })
到此,你就能够开心的使用 module=page1 node server.js
来开发了 page1 模块了,是否是很简单。加入要开发全部模块,不带 module=page1
便可,由于有个能够读取 src 目录下的全部目录
get allModules() { return fs.readdirSync(consts.SRC_PATH) }
还有一点要提一下的就是假如是嵌套的目录配置文件以下其实代码也是适用的,只要给每一个子目录添加相应的配置文件便可
- 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。
溜了,溜了,赶火车肥家。你们春节愉快。