webpack文档高级配置
主要从下面几个方面讲解:
- webpack的生产配置环境
- webpack的优化机制
- 模块分离
- 按需加载
- 第三方模块利用缓存
- webpack的部分插件
webpack的生产配置构建
在开发一个项目的时候,我们往往需要几个环境的切换,例如:开发(dev),生产(production), 测试(test),这里主要讲解开发和生产中的环境配置,webpack
官方推荐我们将公共的基本配置放在一起,然后对开发和生产进行不同的配置文件,通过官方提供的插件webpack-merge
进行配置的合并,然后通过webpack编译生产。
webpack.common.js
// 通用文件
webpack.dev.js
// 开发文件
webpack.prod.js
// 生产文件
// 通用文件 包含生产和开发的一些基本的使用 webpack.common.js
module.exports = {
entry: {
app: './src/index.js',
vendor: ['lodash']
},
output: {
path: path.resolve(__dirname, './dist'),
filename: '[name][chunkhash:3].bundle.js',
chunkFilename: '[name].bundle.js',
// publicPath: '/'
},
plugins: [
new htmlWebpackPlugin({
title: 'prodution'
}),
new cleanWebpackPlugin(['dist'])
]
}
// 开发文件 在通用文件下面进行拓展,合并并替换个性化的配置 webpack.dev.js
let merge = require('webpack-merge');
let commonConfig = require('./webpack.common.js');
module.exports = merge(commonConfig, {
// devtool: 'inline-source-map',
devtool: 'source-map',
devServer: {
contentBase: './dist'
},
})
// 生产文件 在通用文件下面进行拓展,合并并替换个性化的配置 webpack.prod.js
let merge = require('webpack-merge');
let webpack = require('webpack')
let commonConfig = require('./webpack.common.js');
module.exports = merge(commonConfig, {
plugins: [
new webpack.optimize.CommonsChunkPlugin({
// 利用manifest来进行缓存
// names: ['vendor', 'manifest'],
name: 'vendor',
minChunks: function(module, count) {
console.log(module.resource)
console.log(count)
}
}),
]
})
生产环境使用环境变量
当然这些只是配置文件,如果我们想在配置文件中使用环境变量
(process.env.NODE_ENV
)进行不同情况下面的判定,我们可以通过npm script
进行环境变量的配置。windows下要获取环境变量需要使用一个包插件cross-env
// package.json
{
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "cross-env NODE_ENV=production node_modules/.bin/webpack --config webpack.prod.js",
"dev": "cross-env NODE_ENV=development webpack --config webpack.dev.js",
"dev": "webpack-dev-server --open --config webpack.dev.js NODE_ENV=production", // 不能获取环境变量
"server": "cross-env NODE_ENV=production node server.js"
},
}
// 在配置文件中,我们可以通过process.env.NODE_ENV来获取环境变量
// webpack.prod.js
if(process.env.node_ENV){
console.log(111)
}
console.log(process.env.NODE_ENV)
一般情况下,我们已经区分了不同的环境下面的不同的webpack配置文件,所以很少通过这样配置环境变量,但是更多的是,我们在源文件中通过不同的环境参数进行不同的判定。但是当开启的是webpack-dev-server --open
的时候,不能获取环境变量。
源文件中使用环境变量
webpack提供自带插件webpack.DefinePlugin()
来给源文件来配置环境变量。
module.exports = {
new webpack.DefinePlugin({
'process.env' : {
'NODE_ENV': JSON.stringify('development')
}
})
}
// src下的index.js
if (process.env.NODE_ENV !== 'production') {
console.log('Looks like we are in development mode!');
}
这样我可以在源文件中根据不同的环境变量来编写不同的代码,也可以通过环境变量来提示用户信息。
webpack的优化机制
由于JS模块化的的兴起,前端代码从以前的一个大文件变成了一块块的代码,对于每一个模块的引用和使用就是一个问题了,总结由于模块化需要注意的问题。
- 对于重复引用的问题,一个模块重复引用,加大文件大小。
- 自己内部代码和第三方代码的分离问题
- 对于很大的第三方模块代码,一般都不会有很大的改变的情况下,我们能不能利用浏览器缓存技术?
- 我能不能按需加载,就是我什么页面需要什么模块就加载什么模块,减轻首页加载的负担?
带着上面的四个问题,我们来探究一下webpack的优化机制。
1. 解决模块重复引用的问题
对于多个入口chunk
的重复应用一个模块的情况下。
现在我们有三个模块a.js, b.js index.js
//a.js
export default function() {
console.log(1)
}
// b.js
import a from 'a.js'
a()
// index.js
import a from 'a.js'
a()
console.log('This ia index.js')
对于模块a.js
, 我们需要在其他的2个模块中使用,webpack打包的时候,就会重复打包a.js
, 这样就加大了文件的大小和负担,有没有一种方法可以提前把a.js
存放起来,当我们在使用的时候,直接去调用就行了。webpack
官方也考虑了这种情况,提供了一个自带的插件webpack.optimize.CommonsChunkPlugin
来处理这种情况。注意的是,options.name
不要和entry的name重合,不然会被认为第三方模块被分离。
const webpack = require('webpack');
module.exports = {
entry: {
app: './index.js',
b: './b.js'
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'common',
minChunks: number|fun|Infinity,
})
]
}
// name是重复引用的模块集合输出的文件名
// minChunks: 对于模块被引用的次数,对于小于次数的模块不会被打包进入,虽然还是会生成common.js文件,但是由于小于次数,不会被打包,所有还是像之前的那样重复的引用,加大大小。
// 当minChunks = Infinity的时候,会马上生成 公共chunk,但里面没有模块。利用缓存的时候可以用到
2. 解决内部模块和第三方模块的分离
由于模块化的好处,我们可以很轻松的使用第三方别人写好的模块,加速我们的代码编写,但是也不方便我们进行维护和进一步的拓展,所有有必要把第三方模块和自己的模块进行分离。CommonsChunkPlugin()
提供了我们分离第三方模块的使用。通过entry
里面指定对于的name
,然后分离代码。
module.exports = {
entry: {
app: './index.js',
vendor: ['lodash', 'axios']
},
output: {
path: path.resolve(__dirname, './dist'),
filename: '[name][chunkhash:3].bundle.js',
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: Infinity
// 传入 `Infinity` 会马上生成 公共chunk,但里面没有模块。
})
]
}
3. 利用缓存技术处理第三方修改很少的模块
我们一般在写输出文件的时候,都会利用hash值,让客户端(浏览器)实时更新我们的修改代码,不会走缓存,但是当使用第三方修改很少的模块的时候,我们恰恰需要利用浏览器的缓存,来减少服务器请求,优化页面。webpack
一般使用chunkhash
来改变文件的hash
值
按照上面的写法,当我们npm run build
的时候,会出现这样的结果,浏览器请求这些js
文件的时候
我们想的是,可以通过某个技术然后让我们在修改内部代码的时候,vendor[hash].build.js
的值不改变,这样浏览器就可以利用缓存,不用重复请求了。
CommonsChunkPlugin
有一个较少有人知道的功能是,能够在每次修改后的构建结果中,将 webpack 的样板(boilerplate)和 manifest 提取出来。通过指定 entry
配置中未用到的名称,此插件会自动将我们需要的内容提取到单独的包中。然后就可以通过manifest内部机制,跳过vendor
的hash值的改变。
module.exports = {
plugins: [
new webpack.optimize.CommonsChunkPlugin({
//利用manifest来进行缓存
names: ['vendor', 'manifest']
})
]
}
// 也可以分开写,但是manifest的必须写在vendor的后面
module.exports = {
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor'
}),
new webpack.optimize.CommonsChunkPlugin({
// name只是一个打包后的文件名,这里实际提取的manifest
name: 'runtime'
})
]
}
这样配置了后,我们再次修改后,第三方模块的文件名是不会更新的,这样我们可以只修改我们内部代码,然后重新打包后,可以不改变第三方模块的打包文件名,很好的利用浏览器的缓存。
4. 按需加载,减少首页的文件请求压力
浏览器加载首页的时候,如果我们把js文件全部都已经打包好,并且全部放在首页加载的话,就会增大请求,从而首页加载的时间也会增加。可不可以我们点击某个按钮或者交互的时候,加载我们需要的部分,而不是在首页全部加载,这样就可以是实现按需加载,从而减少首页请求服务器和时间。
webpack提供2中方式实现按需加载
- ES6 的
import
加载 (官网推荐使用这种方式加载)
import()
加载模块后返回一个promise
,得到一个对象,其中default
属性会是加载模块导出的内容。注释的webpackChunkName:name
配合output.chunkFilename:[name].js
会变成打包后的文件名。
// index.js
function get() {
button.onclick = e => import(/* webpackChunkName: "print" */ './print').then((module) => {
var print = module.default;
print();
});
}
// webpack config
module.exports = {
output: {
path: path.resolve(__dirname, './dist'),
filename: '[name][chunkhash:3].bundle.js',
chunkFilename: '[name].bundle.js',
},
}
交互点击后:我们发现才加载已经打包好了的print.bundle.js
- webpack自带的
require.ensure(dependencies, callback,filename)
//print.js
export default () => {
console.log('This is a print')
}
// index.js
function get() {
// print.js里面没有依赖,有依赖的话需要写在[]中。
button.onclick = e => require.ensure([],() => {
let print = require('print');
print()
},'print');
}
总结:
import
方式和require.ensure
决定了什么时候加载打包后的模块,webpackChunkName:name
和filename
配合webpack
的输出配置output.chunkFilename
控制文件打包后的文件名。
webpack的部分插件
上面的部分我们已经使用了很多的插件,这里总结一下一些常见的插件plugins
webpack
通过options.plugins: []
来配置相应的插件特性。
webpack自带插件
CommonsChunksPlugin
插件
使用方法:
const webpack = require('webpack')
module.exports = {
plugins: [
new webpack.optimize.CommonsChunkPlugin({
.....
})
]
}
使用用途:
- 对于多入口chunk,打包重复被引用的模块。
- 分离第三方模块
DefinePlugin插件
在使用DefinePlugin之后,我们可以在源文件内部是用配置好的全局环境变量,为什么这样做呢?可以区分开发模式和发布模式。用过node的同学知道,process.env
可以获取环境变量。这里沿用了node的。注意区分构建文件webpack.config.js
和源文件index.js
使用方法:
const webpack = require('webpack')
module.exports = {
plugins: [
new webpack.DefinePlugin({
'process.env' : { 'NODE_ENV' : JSON.stringify('production') }
})
]
}
// 字符串必须使用'"xx"'这样的形式, 一般都是用JSON.stringify()来转换
使用用途:
- 区分开发模式和发布模式
- 显示一些版本和一些实用技术
providePlugin插件
提供全局变量,我们不用在全局的去引用这个全局变量。webpack会自动记载模块。
使用方法:
const webpack = require('webpack')
module.exports = {
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
_map: ['lodash', 'map'],
Vue: ['vue/dist/vue.esm.js', 'default']
})
]
}
使用说明:
- 任何时候,使用
$
的时候,webpack都会自动加载模块jquery,并且$会被jquery的输出的内容所赋值。 - 对于es6的
export default
必须指定模块的default
属性。例如:Vue
- 可以只赋值模块导出的一部分。 例如:
lodash
其他插件
extractTextWebpackPlugin插件
用于把css内部样式提取成css单独文件。对于复杂的样式很有用。
使用方法:
npm install --save-dev extract-text-webpack-plugin
const ExtractTextPlugin = require('extract-text-webpack-plugin')
module.exports = {
plugins: [
new ExtractTextPlugin('filename')
]
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: "css-loader"
})
}
]
}
}
详细查看webpack插件
html-webpack-plugin插件
用于生成单独的html文件,并自动引入打包后的文件。如果有多个入口文件,也会生成多个index.htm
l文件
使用方法:
npm install --save-dev html-webpack-plugin
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
plugins: [
new HtmlWebpackPlugin({
title: 'plugins'
})
]
}
// 内部可以配置文件标题,文件名,文件模板等。。。。
详细查看webpack插件
UglifyjsWebpackPlugin
可以在webpack打包后,压缩文件,减少文件的大小。
使用方法:
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
module.exports = {
plugins: [
new UglifyJSPlugin()
]
}
使用说明:
- 可以配置压缩的文件
include
exclude
test
-
source map
的配置,映射错误信息。