构建配置
从本篇文档开始,我们将介绍 Lavas 构建、运行中使用的配置项。开发者可以在项目根目录下的 lavas.config.js 中定义这些配置项。配置对象的结构大致如下:
// lavas.config.js{build: {},router: {},middleware: {},// 省略其他配置项}
Lavas 内部使用 Webpack 进行构建,众所周知 Webpack 功能强大但是配置非常复杂,我们隐藏了大部分构建细节,将构建流程中部分常用特性以配置项的形式暴露给用户,便于快速上手。同时,对于高级开发者,也能通过特殊的配置项将自定义的 Loader 和 Plugin 加入构建流程。
这些在构建过程中使用的 Webpack 相关配置项将放在 build 下,下面我们将依次介绍这些配置项。
// lavas.config.jsbuild: {ssr: true,path: '',publicPath: '',// 省略其他配置项}
ssr
切换 SPA 单页应用和 SSR 服务端渲染两种编译模式。
ssr: true // SSR 模式
path
最终构建产物的输出地址,必须为绝对路径。如下配置将输出构建产物到 dist 文件夹下:
path: path.resolve(__dirname, 'dist')
等同于 Webpack 配置中的 output.path。
publicPath
在静态资源路径之前添加的前缀。默认值为 '/'。
publicPath: '/'
例如在使用 CDN 场景下,可以使用如下配置:
publicPath: '//cdn.example.com/assets/'
更多使用例子可参考 Webpack 配置中的 output.publicPath。
filenames
Webpack 可以指定输出静态资源(JS CSS FONT IMG)的文件名,其中可以使用例如 [hash] 这样的模板字符串。
Lavas 中使用的默认值如下:
filenames: {entry: 'js/[name].[chunkhash:8].js',vue: 'js/vue.[chunkhash:8].js',vendor: 'js/vendor.[chunkhash:8].js',chunk: 'js/[name].[chunkhash:8].js',css: 'css/[name].[contenthash:8].css',img: 'img/[name].[hash:8].[ext]',fonts: 'fonts/[name].[hash:8].[ext]'}
其中:
- entry entry chunk。将影响各个入口文件名。 可参考 Webpack 中的 output.filename。
- vue 我们将 Vue 相关的依赖合并成一个 chunk。包括 vue、vue-router、vuex 和 vue-meta。
- vendor 包含其他第三方依赖。
- chunk async chunk。将影响非入口文件名。可参考 Webpack 中的 output.chunkFilename。
- css 样式文件。由于使用了 ExtractTextWebpackPlugin从 JS 中提取样式,必须使用
[contenthash]而非[hash]或者[chunkhash]。 - img 图片。
- fonts 字体文件。
更多模板字符串示例及其使用场景可以参考 Webpack output.filename。
babel
Lavas 内部配置 Webpack 规则,使用 babel-loader 处理 JS 文件。通过 babel 这个配置项可以指定包括 babel preset 和 plugin 在内的很多属性,更多可配置属性可以参考 babel-loader options。
Lavas 默认使用 vue-app 这个 preset,其中已经包含了一系列 babel 插件,在大多数情况下已经能满足 Vue 项目的开发。 但有时我们也需要进行额外的配置,例如在下面使用 vuetify 的场景中,我们需要配置一系列 plugins 以支持组件的按需加载功能:
babel: {plugins: ["transform-runtime",["transform-imports",{"vuetify": {"transform": "vuetify/es5/components/${member}","preventFullImport": true}}]]}
使用 .babelrc
如果您更习惯使用根目录下的 .babelrc 来配置 babel-loader,您可以手动开启:
babel: {babelrc: true}
然后在 .babelrc 中进行配置,例如对于一个 SPA 项目:
{"presets": ["vue-app",{"targets": {"ie": 9, "uglify": true}}],"plugins": []}
cssExtract
在使用 ExtractTextWebpackPlugin从 JS 中提取样式时,需要设置 Loader 和 Plugin。由于分离样式会造成额外的编译开销,Lavas 默认在开发模式中关闭这一特性,在生产环境打开。
cssExtract: true
cssMinimize & cssSourceMap
是否需要对 CSS 文件进行压缩以及生成 source-map。
cssMinimize: true,cssSourceMap: true
这两个参数最终将传递给 css-loader。
loader: 'css-loader',options: {minimize: true,sourceMap: true}
jsSourceMap
是否需要对 JS 文件生成 source-map,便于开发模式调试以及生产环境排查错误,默认开启。
jsSourceMap: true
Webpack 支持通过 devtool 配置项生成多种 source-map。这些不同格式的 source-map 在生成速度,是否内联在源文件中等等方面都有显著差异,需要使用者根据具体场景选择合适的格式。
在开发模式中,由于代码经常发生变动,我们通常会选择生成速度快,可以接受内联从而增加源文件体积的代价。而在生产环境中,我们通常选择生成独立的 source-map 文件,此时生成速度就可以忽略了。
在 Lavas 中开启这个配置后将作用于以下两种场景:
- 开发模式中选择
cheap-module-eval-source-map。这种格式只显示行号不显示列号,重新生成速度很快。 - 生产环境中选择
nosources-source-map。这种格式下生成的独立 source-map 不会暴露源文件内容,只会显示错误堆栈信息。同时通过 UglifyJsPlugin 在压缩 JS 文件的同时生成 sourceMap,内部通过设置插件的 sourcemap 选项实现。
bundleAnalyzerReport
Webpack Bundle Analyzer 提供了可视化图表这样的直观方式,帮助开发者分析构建产物中可能出现的问题,例如重复引入、不必要的依赖。
// 默认配置,启动 localhost:8888 服务器展示网页bundleAnalyzerReport: true// 自定义配置bundleAnalyzerReport: {analyzerMode: 'server',analyzerHost: '127.0.0.1',analyzerPort: 8888,// 省略其他配置}
Lavas 默认关闭这一配置。开启后,运行 lavas build,将自动启动服务器并打开网页,以下是 Lavas 模板项目的分析结果:

defines
在构建时我们常常需要使用全局常量,在 Webpack 中可以通过 DefinePlugin 插件定义这些常量。Lavas 提供三组命名空间,分别是 SSR 中服务端使用的 server,客户端使用的 client 以及两者共用的 base。另外需要注意的是常量的值必须包含字符串本身内的实际引号,可以使用单引号包含双引号或者 JSON.stringify()。
defines: {base: {'MY_CONSTANT': '"VALUE"'},client: {},server: {}}
一个常见的使用场景是,需要根据开发环境和生产环境定义不同的 URL。由于在启动 Lavas 时已经设置了环境变量 process.env.NODE_ENV,在 lavas.config.js 中可以这样做:
// lavas.config.jsconst isProd = process.env.NODE_ENV === 'production';defines: {base: {PASSPORT_URL: isProd? JSON.stringify('https://wappass.example.com/passport'): JSON.stringify('https://wappass.qatest.example.com/passport')}}
另外,Lavas 已经内置了以下两组全局常量 process.env.VUE_ENV 和 process.env.NODE_ENV,可以直接在项目中使用,不需要开发者重复定义:
// 在同构应用当前处于 client 或者 server 端'process.env.VUE_ENV': '"client"',// 当前应用处于开发模式 development 或者生产模式 production'process.env.NODE_ENV': '"development"'
alias
在 Webpack 中解析模块时,我们常常使用 alias 定义路径的简写别名,便于更加简便地引用模块。
Lavas 提供了 alias 下三组命名空间,分别是 SSR 中服务端使用的 server,客户端使用的 client 以及两者共用的 base。
alias: {base: {},client: {},server: {}}
另外,Lavas 已经内置了两组别名,开发者不需要重复定义。例如如果想引用项目根目录下 components 文件夹中的组件,只需要使用 import MyComponent from '@/components/MyComponent':
'@': '' // 指向项目根目录,'$': '' // 指向 .lavas 目录
plugins
在使用 Webpack 构建时,各种插件是必不可少的,对于开发者自定义的插件,Lavas 提供了 plugins 下三组命名空间,分别是 SSR 中服务端使用的 server,客户端使用的 client 以及两者共用的 base。有一点需要注意,自定义插件将添加到 Lavas 已有插件列表之后,如果对于插件添加顺序有要求,可以参考 extend 配置项,进行更精确的添加。
plugins: {base: [],client: [],server: []}
extend
为了给予开发者更大的灵活度,能自由修改 Webpack 配置对象,Lavas 提供了 extend 方法。
该方法参数说明如下:
configWebpack 配置对象。options.typeWebpack 配置对象类型,一共有三种:client供客户端使用,server供服务端使用,base使两者同时生效。options.env当前构建环境变量,取值有两种:development|production。
例如我们想增加 vue-style-variables-loader 来处理 .vue 文件,可以这么做:
extend(config, {type, env}) {// 在客户端和服务端同时生效,等同于 type === 'client' || type === 'server'if (type === 'base') {let vueRule = config.module.rules[0];vueRule.use.push({loader: 'vue-style-variables-loader',options: {variablesFiles: [path.join(__dirname, 'assets/styles/variables.styl')]}});}}
info
extend方法适合对于 Webpack 配置对象进行简单扩展的场景,例如添加插件。如果需要对 Lavas 内置的规则和插件进行修改,可以参考下面的extendByWebpackChain方法。
extendWithWebpackChain
info
该方法在 lavas-core-vue@1.0.8 版本引入,同时由于使用了 webpack-chain 依赖,要求 Node 版本 > 6.9.0。
使用上面提到的 extend 方法直接修改 Webpack 配置对象,对于熟悉 Webpack 文档的开发者会比较直观。但是在以下场景下存在局限性:
- 修改 Lavas 已有规则。由于 Webpack 配置对象层次很深,尤其是访问数组类型的配置项时只能以索引方式。例如 Lavas 内置了 vue-loader 处理
.vue文件,如果使用extend方法试图访问并修改,只能通过config.module.rules[0].use[0].options.loader这样的方法,十分繁琐。 - 控制自定义插件的顺序。开发者对于 Lavas 内部使用的插件缺少便捷的引用方式,因此无法插入新插件到某个特定插件前后。另外,如果想删除 Lavas 内置的某个插件,也只能通过索引方式访问插件数组。
- 干预 Lavas 内置插件的初始化创建。对于 Lavas 内置的插件,一旦用户希望修改传入这个插件构造函数的参数,使用
extend是无法做到的,因为调用extend时插件已经由 Lavas 初始化完成。
而使用 extendWithWebpackChain 可以解决上述三个问题,方法参数和 extend 相同。
扩展 Lavas 内置规则及 Loader
首先,我们给 Lavas 内置的所有规则设置了名称,某条规则应用的任何一个 Loader 都可以通过 config.rule(规则名称).use(Loader 名称) 方式引用,并使用 tap 方法对传入 Loader 的参数进行扩展:
extendWithWebpackChain: (config, {type, env}) => {// 扩展 babel-loader,添加一个 babel 插件config.module.rule('js').use('babel').tap(options => merge(options, { plugins: ['babel-plugin-syntax-object-rest-spread'] }));}
Lavas 内置的全部规则及对应 Loader 如下:
| 规则 | Loader 名称 | 说明 |
|---|---|---|
| vue | vue | 匹配 /\.vue$/ 规则 |
| js | babel | 匹配 /\.js$/ 规则,默认使用 vue-app preset |
| img | url | 处理 .png .jpe?g .gif .svg |
| font | url | 处理 .woff2 .eot .ttf .otf |
| style-css | css | 匹配 /\.css$/ 规则 |
| style-postcss | css | —- |
| style-less | css less vue-style | 匹配 /\.less$/ 规则,依次通过 css less 和 vue-style 这三个 Loader 处理,下同 |
| style-sass | css sass vue-style | 匹配 /\.sass$/ 规则 |
| style-scss | css sass vue-style | 匹配 /\.scss$/ 规则 |
| style-stylus | css stylus vue-style | 匹配 /\.stylus$/ 规则 |
| style-styl | css stylus vue-style | 匹配 /\.styl$/ 规则 |
扩展 Lavas 内置插件
其次,我们给 Lavas 内部使用的所有插件也设置了名称,可以通过 config.plugin(name) 方式访问,通过以下方法可以对插件进行便捷地修改:
- init 扩展已有插件初始化参数
- use 添加新插件
- after/before 控制新插件添加到已有插件前后
extendWithWebpackChain: (config, {type, env}) => {// 在 friendly-error 插件创建时扩展自定义参数config.plugin('friendly-error').init((Plugin, args) => {let customParams = {}; // 扩展传入插件构造函数的参数return new Plugin(...args, customParams)});// 添加第三方插件到指定 Lavas 内置 html 插件之后config.plugin('my-plugin').after('html').use(MyPlugin);}
以下是 Lavas 内置的部分插件列表:
| 插件名称 | client/server | 开发/生产环境 | 说明 |
|---|---|---|---|
| define | client & server | 开发 & 生产 | DefinePlugin,可以通过 build.defines 进行扩展。 |
| html | client | 开发 & 生产 | HtmlWebpackPlugin,用于在 SPA 模式下生成 HTML。 |
| skeleton | client | 开发 & 生产 | VueSkeletonWebpackPlugin,用于在 SPA 模式下向 HTML 中注入 Skeleton。 |
| chunk-vendor | client | 开发 & 生产 | CommonsChunkPlugin,创建包含第三方依赖的 chunk |
| chunk-vue | client | 开发 & 生产 | 包含 vue,vuex,vue-router 和 vue-meta |
| chunk-manifest | client | 开发 & 生产 | 仅包含 Webpack 运行时代码 |
| hot-module-replacement | client | 开发 | 代码热更新相关 |
| no-emit-on-errors | client | 开发 | 出错时终止编译流程 |
| progress-bar | client | 开发 | ProgressBarWebpackPlugin,展示构建进度条 |
| friendly-error | client | 开发 | FriendlyErrorsWebpackPlugin,友好地展示错误信息 |
| extract-css | client & server | 生产 | ExtractTextWebpackPlugin,默认在开发模式关闭,生产环境开启。可以通过build.cssExtract配置。 |
| module-concatenation | client & server | 生产 | ModuleConcatenationWebpackPlugin,实现预编译功能 |
| hashed-module-ids | client & server | 生产 | HashedModuleIdsWebpackPlugin,根据模块的相对路径生成模块 id |
| optimize-css | client & server | 生产 | OptimizeCSSPlugin,样式去重压缩 |
| uglify-js | client & server | 生产 | UglifyjsWebpackPlugin,JS 压缩 |
| workbox | client | 生产 | WorkboxWebpackPlugin,使用 2.x 版本,用于生成 ServiceWorker 文件 |
| sw-register | client | 生产 | SWRegisterWebpackPlugin,用于向 HTML 中注入 ServiceWorker 注册代码 |
更多示例
上面列出了最常用的对于 Webpack 的扩展,即修改 Loader 和 插件,其余配置项的使用方法,可以参考 webpack-chain API。
另外,我们编写了一个简单的 Codelab,使用 extendWithWebpackChain 方法配合 ESlint 为 Lavas 项目增加代码检查。
compress
info
该配置项只有 SSR 模式下生效,SPA 模式下可以忽略。
在 SSR 模式下是否启用 gzip,通过内置的 compress 中间件实现。Lavas 默认在开发模式中关闭这一特性,在生产环境打开。
compress: false
nodeExternalsWhitelist
info
该配置项只有 SSR 模式下生效,SPA 模式下可以忽略。
在 SSR 模式下,通常我们不希望将 node_modules 中的依赖打包进 server bundle 中,因此需要使用 Webpack externals 配置项。Lavas 已经通过 Webpack node modules externals 将 node_modules 全部排除。但是在某些场景下,我们还是需要将部分特定的依赖打包进来,这时就需要使用白名单了:
nodeExternalsWhitelist: []
例如在服务端渲染场景下常常遇到的一个问题是,某些第三方依赖使用了 document, window 这样在 Node.js 环境中不存在的对象。为了保证服务端渲染正常运行,通常使用 resolve.alias 引导 Webpack 使用空的 stub 对象,此时一定要同时在 nodeExternalsWhitelist 中加入该依赖。
ssrCopy
info
该配置项只有 SSR 模式下生效,SPA 模式下可以忽略。
在 SSR 模式下,Lavas 除了将构建产物输出到例如 dist 文件夹中,还可以将例如 node_modules,线上脚本等文件拷贝到里面。这样 dist 文件夹可以作为一个可单独运行的包,移动到任意位置:
ssrCopy: isDev ? [] : [{src: 'server.prod.js'},{src: 'node_modules'},{src: 'package.json'}]
虽然从功能上看都是拷贝文件,但是这些文件并不会经过 CopyWebpackPlugin 处理,这一点不同于 /static 文件夹。
watch
Lavas 在开发模式下使用了 webpack-dev-middleware。得益于自带的热加载功能,很多源文件的修改会自动触发 Webpack 重新编译,不需要开发者重启开发服务器。Lavas 扩充了这个功能,修改以下文件,也会触发重新编译:
/pages下增加删除修改路由组件。lavas.config.js配置内容发生修改。- SSR 模式下模板内容发生修改。
整个重新编译过程中不需要开发者关闭重启服务,也就是说从 MPA 模式切换到 SSR 模式也只需要修改配置后等待编译完成。另外,如果想监控自定义文件、文件夹,在它们发生修改时也触发重新编译,可以通过 watch 配置项传入文件列表:
watch: ['/foo/bar' // 自定义文件]
development & production
对于以上配置项,如果需要在开发模式和生产环境启用不同的值,可以使用两个特殊的配置项 development 和 production。
例如想在开发模式关闭 cssExtract 分离样式而在生产环境开启,可以这么做:
build: {// 省略其他配置项},development: {build: {cssExtract: false}},production: {build: {cssExtract: true}}
