详细的配置可以参考官网:https://doc.webpack-china.org/guides/
一开始做项目时都是直接从组里前辈搭建好的脚手架开始写代码,到后来自己写新项目时又是拷贝之前的工程作为脚手架开始。对于脚手架本身却不甚了解,不仅不思考为什么更是没有改进的想法,怪不得工作满一年了却总觉得自己的技术水平在原地踏步,就是没有总结和思考。
目前组里的技术栈都是使用vue+koa,使用webpack的好处一是方便写vue的单文件组件,二是打包文件方便生产部署再加上能无所顾虑的应用语言的新特性。
1. 配置文件
官方文档推荐写webpack配置文件时,先写出一个基本配置文件(base)包含入口、输出等,再根据开发/生产环境所需要插件的不同,利用webpack-merge生成三个配置文件: dev、prod、analyze
package.json 依赖参考
"devDependencies": { "babel-core": "^6.18.2", "babel-loader": "^7.1.1", "babel-preset-es2015": "^6.18.0", "css-loader": "^0.28.4", "koa-webpack-dev-middleware": "^1.4.0", "koa-webpack-hot-middleware": "^1.0.3", "less": "^2.7.1", "less-loader": "^4.0.5", "style-loader": "^0.18.2", "url-loader": "^0.5.7", "vue-loader": "^12.2.2", "webpack": "^3.3.0", "webpack-bundle-analyzer": "^2.8.3", "webpack-dev-middleware": "^1.11.0", "webpack-dev-server": "^2.6.1", "webpack-hot-middleware": "^2.13.1", "webpack-koa-hot-middleware": "^0.1.2", "webpack-manifest-plugin": "^1.2.1", "webpack-merge": "^4.1.0" }
webpack.config.base.js
1 'use strict' 2 let path = require('path'); 3 let webpack = require('webpack'); 4 let WebpackManifestPlugin = require("webpack-manifest-plugin"); 5 6 module.exports = { 7 output: { 8 path: path.resolve(__dirname, '..', 'build') 9 }, 10 resolve: { 11 extensions: ['.js', '.vue'], 12 modules: ['node_modules'], 13 alias: { 14 'leafletCSS': 'leaflet/dist/leaflet.css', 15 'leaflet$': 'leaflet/dist/leaflet.js', 16 'vue$': 'vue/dist/vue.min.js', 17 'vue-resource$': 'vue-resource/dist/vue-resource.min.js' 18 } 19 }, 20 module: { 21 rules: [ 22 { 23 test: /.vue$/, 24 loader: 'vue-loader', 25 }, 26 { 27 test: /.js$/, 28 exclude: /(node_modules|bower_components)/, 29 use: { 30 loader: 'babel-loader', 31 options: { 32 presets: ['es2015'] 33 } 34 } 35 }, 36 { 37 test: /.css$/, 38 use: [ 39 'style-loader', 40 'css-loader' 41 ] 42 }, 43 { 44 test: /.(png|jpe?g|gif|svg|woff2?|ttf|otf)(?.*)?$/, 45 loader: 'url-loader', 46 }, 47 { 48 test: /.less$/, 49 use: [ 50 'style-loader', 51 'css-loader', 52 'less-loader' 53 ] 54 } 55 ] 56 }, 57 plugins: [ 58 new WebpackManifestPlugin(), 59 new webpack.optimize.CommonsChunkPlugin({ 60 name: 'vendor', 61 minChunks: function (module) { 62 // this assumes your vendor imports exist in the node_modules directory 63 return module.context && module.context.indexOf('node_modules') !== -1; 64 } 65 }), 66 new webpack.optimize.CommonsChunkPlugin({ 67 name: 'common', 68 chunks: ['qq', 'navi', 'log', 'guide', 'apply', 'voice', 'pianhang', 'dynamic'], //这里输入需要提取公共代码的entry 69 minChunks: 2 70 }), 71 //CommonChunksPlugin will now extract all the common modules from vendor and main bundles 72 new webpack.optimize.CommonsChunkPlugin({ 73 name: 'manifest', //But since there are no more common modules between them we end up with just the runtime code included in the manifest file 74 }), 75 ] 76 }
第16行代码可以参考这里和这里,默认NPM包导出的是运行时构建,Vue2的运行时构建不支持单文件组件的template
第58行的 WebpackManifestPlugin 作用是将输出文件名保存在文件中 (当输出文件名带 chunkhash 时很有用,参考这里)
第59~74行的 CommonsChunkPlugin 作用是从打包后的 bundle 文件中提取公共模块,将 npm install 的公共模块和业务代码分开,这样浏览器就可以一直缓存公共模块的bundle,参考这里。第一个 CommonsChunkPlugin 作用是将 node_modules 里的模块提取到 vendor.js 里;第二个 CommonsChunkPlugin 作用是将 entry 里的公共代码提取出来放在 common.js 里,参考这里;第三个 CommonsChunkPlugin 作用是将 webpack 运行时代码放在 manifest.js 里
webpack.config.dev.js
1 "use strict" 2 let webpack = require('webpack'); 3 let merge = require('webpack-merge'); 4 let base_config = require('./webpack.config.base'); 5 6 module.exports = merge(base_config, { 7 entry: { 8 qq: ['./src/qq/qq.js', 'webpack-hot-middleware/client'], 9 navi: ['./src/navi/navi.js', 'webpack-hot-middleware/client'], 10 log: ['./src/log/log.js', 'webpack-hot-middleware/client'], 11 guide: ['./src/guide/guide.js', 'webpack-hot-middleware/client'], 12 apply: ['./src/apply/apply.js', 'webpack-hot-middleware/client'], 13 voice: ['./src/voice/voice.js', 'webpack-hot-middleware/client'], 14 pianhang: ['./src/pianhang/pianhang.js', 'webpack-hot-middleware/client'], 15 dynamic: ['./src/dynamic/dynamic.js', 'webpack-hot-middleware/client'] 16 }, 17 output: { 18 filename: '[name].js', 19 }, 20 devtool: '#eval-source-map', 21 plugins: [ 22 new webpack.HotModuleReplacementPlugin(), 23 ] 24 });
第 20 行设置source-map,方便用浏览器查看源代码
webpack.config.prod.js
1 "use strict" 2 let webpack = require('webpack'); 3 let merge = require('webpack-merge'); 4 let base_config = require('./webpack.config.base'); 5 6 module.exports = merge(base_config, { 7 entry: { 8 qq: ['./src/qq/qq.js'], 9 navi: ['./src/navi/navi.js'], 10 log: ['./src/log/log.js'], 11 guide: ['./src/guide/guide.js'], 12 apply: ['./src/apply/apply.js'], 13 voice: ['./src/voice/voice.js'], 14 pianhang: ['./src/pianhang/pianhang.js'], 15 dynamic: ['./src/dynamic/dynamic.js'], 16 }, 17 output: { 18 filename: '[name].[chunkhash].js', 19 }, 20 plugins: [ 21 new webpack.optimize.UglifyJsPlugin() 22 ] 23 });
第 21 行 UglifyJsPlugin 的作用是压缩、混淆代码
webpack.config.analyze.js
1 "use strict" 2 3 let webpack = require('webpack'); 4 let merge = require('webpack-merge'); 5 var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; 6 let prod_config = require('./webpack.config.prod'); 7 8 module.exports = merge(prod_config, { 9 plugins: [ 10 new BundleAnalyzerPlugin() 11 ] 12 });
analyze 文件的作用利用 webpack-bundle-analyzer 插件分析打包情况,
webpack --config ./config/webpack.config.analyze.js
运行结果如下:
webpack的配置文件自定义程度很高,所以在参考他人配置时最好能弄清楚为什么要这样写
2. webpack-dev-server
在开发过程中另一个重要的东西是 webpack-dev-server,它的作用是当你改动源代码后能自动重新打包,再加上 webpack 的HMR-模块热替换特性,这样改动代码就能直接在浏览器里看到效果,省却了代码手动打包+刷新浏览器的步骤。使用 webpack-dev-server 有 CLI 和 API 两种使用方法。
CLI 方式设置 dev 文件中的 HotModuleReplacementPlugin 和 devServer 启用 HMR。启动的命令为:
webpack-dev-server --config ./config/webpack.config.dev.js
API 方式直接在命令里设置参数,如:(这里用到了 Unix Domin Socket,也可以直接指定 ip和端口)
webpack-dev-server --config config/webpack.dev.config.js --public 0.0.0.0:8056 --progress --inline --hot --socket .dev-shared/sockets/webpack.sock
webpack-dev-server 的方式配置简单,缺点是引入 bundle 比较麻烦,需要指定其它端口
3. webpack-dev-middleware
上述配置文件就是使用的该方法,需要有 koa-webpack-dev-middleware、koa-webpack-hot-middleware(热更新)。然后在 index.js 里写:
1 if (process.env.NODE_ENV == 'dev') { 2 let webpack = require('webpack'); 3 let webpackConfig = require('./config/webpack.config.dev.js'); 4 let webpackDevMiddleware = require('koa-webpack-dev-middleware'); 5 let webpackHotMiddleware = require('koa-webpack-hot-middleware'); 6 let compiler = webpack(webpackConfig); 7 app.use(webpackDevMiddleware(compiler)); 8 app.use(webpackHotMiddleware(compiler)); 9 }
这样就不需要通过额外的端口获取 bundle 文件了,注意这里是 koa 环境
4. 如何在前端框架里引入 bundle
由于 webpack prod 配置文件里使用了 chunkhash 作为 bundle 的名字的一部分,修改业务代码,chunkhash 会发生改变,所以需要通过一些方法自动将 bundle 名字注入到前端页面里:
第一种方法是通过在后端 controller 里读取 manifest.json 里的内容,然后通过模板引擎,注入到页面里,例如 nunjucks:
{% for path in paths %} <script src="{{path}}"></script> {% endfor %}
第二种方法是拓展模板引擎命令,例如 xtpl:
{{{ xScript('manifest.js') }}} {{{ xScript('vendor.js') }}} {{{ xScript('common.js') }}} {{{ xScript('apply.js') }}}
xtpl 命令拓展示例 xtpl.ext.js :
1 let xtplApp = require('xtpl/lib/koa'); 2 let xtpl = require('xtpl/lib/xtpl'); 3 let XTemplate = xtpl.XTemplate; 4 5 XTemplate.addCommand('xScript', function(scope, option){ 6 let name = option.params[0]; 7 if (process.env.NODE_ENV !== 'dev') { 8 let assets_map = require('./manifest'); 9 name = assets_map[name]; 10 } 11 return '<script src="' + name + '" ></script>'; 12 }); 13 14 module.exports = xtplApp;
然后在 index.js 里
let xtpl = require('./extensions/xtpl.ext');
注意引入 bundle 的时候要注意引入顺序:manifest > vendor > common > entry,否则可能会报 ReferenceError: webpackJsonp is not defined 错误,还要注意要有 .babelrc 文件:
{ "presets": ["es2015"] }
否则会报 SyntaxError: Unexpected token: name (xxxxxx) from Uglify plugin 之类的错误,无法识别语言新特性