前言

这是我花了几个星期学习webpack4的学习笔记。内容不够细,因为一些相对比较简单的,就随意带过了。希望文章能给大家带来帮助。如有错误,希望及时指出。例子都在learn-webpack仓库上。如果你从中有所收获的话,希望你能给我的github点个star

tree shaking

一个模块里会导出很多东西。把一个模块里没有被用到的东西都给去掉。不会把他打包到入口文件里。tree shaking只支持es6的方式引入(import),使用require无法使用tree shaking

webpackdevelopment无法使用tree shaking功能。除非在打包的配置里加上

// 开发环境需要加如下代码optimization: {  usedExports: true}

当你需要import某个模块,又不想tree shaking把他给干掉,就需要在package.json里修改sideEffects参数。比如当你import './console.js' , import './index.css'等没有export(导出)模块的文件。又不想tree shaking把它干掉。

// package.jsonsideEffects: ['./console.js', './index.css']// 反之sideEffects: false

development环境即使你使用tree shaking,它也不会把其他多余的代码给干掉。他只会在打包的文件里注明某段代码是不被使用的。

developmentproduction 区别

development代码不压缩,production代码会压缩

省略…☺

webpack-merge

reactvue都会区分环境进行不同的webpack配置,但是它们一定会有相同的部分。这个时候需要通过使用webpack-merge进行抽离。

// webpack.base.config.jsconst path = require('path')const HtmlWebpackPlugin = require('html-webpack-plugin')module.exports = {    mode: 'production',    // mode: 'development',    entry: './index.js',    output: {        filename: 'bundle.js',        path: path.resolve(__dirname, './dist')    },    module: {        rules: [            {                test: /\.css$/,                use: ['style-loader', 'css-loader']            }        ]    },    optimization: {        usedExports: true    },    plugins: [        new HtmlWebpackPlugin({            template: './index.html'        })    ]}// webpack.dev.config.jsconst merge = require('webpack-merge')const baseConfig = require('./webpack.base.config')const devConfig = {    mode: 'development',}module.exports = merge(baseConfig, devConfig)

这里就不重复把production环境在配置出来了,主要介绍下webpack-merge用法。

  • 安装npm i webpack-merge -D
  • 新建一个公共的文件如:webpack.base.config.js
  • developmentproduction两个webpack配置相同的抽离到webpack.base.config.js文件中
  • 在环境配置文件中(具体代码如上)

    • const merge = require('webpack-merge')
    • const baseConfig = require('./webpack.base.config.js')
    • module.exports = merge(baseConfig, devConfig)

code splittingsplitChunks

当你把所有的代码都打包到一个文件的时候,每次改一个代码都需要重新打包。且用户都要重新加载下这个js文件。但是如果你把一些公共的代码或第三方库抽离并单独打包。通过缓存加载,会加快页面的加载速度。

  1. 异步加载的代码,webpack会单独打包到一个js文件中
  2. 同步加载的代码有两种方式

原始代码

import _ from 'lodash'console.log(666)

打包后的文件:

main.js 551 KiB main [emitted] main
可以看到,webpack将业务代码跟lodash库打包到一个main.js文件了

方法一:

创建一个新文件

import _ from 'lodash'window._ = _

将文件挂载到window对象上,这样其他地方就可以直接使用了。

然后在webpack配置文件中的entry增加一个入口为该文件。让该文件单独打包。

    Asset      Size  Chunks             Chunk Nameslodash.js   551 KiB  lodash  [emitted]  lodash  main.js  3.79 KiB    main  [emitted]  main

方法二:

通过添加optimization配置参数

optimization: 会将诸如lodash等库抽离成单独的chunk,还会将多个模块公用的模块抽离成单独的chunk

optimization: {  splitChunks: {    chunks: 'all'  }},

打包后文件:

          Asset      Size        Chunks             Chunk Names        main.js  6.78 KiB          main  [emitted]  mainvendors~main.js   547 KiB  vendors~main  [emitted]  vendors~main

可以看到,webpack将lodash抽成公共的chunk打包出来了。

splitChunks里面还可以在添加个参数cacheGroups

optimization: {    splitChunks: {        chunks: 'all',        cacheGroups: {              // 下面的意思是:将从node_modules中引入的模块统一打包到一个vendors.js文件中            vendors: {                test: /[\\/]node_modules[\\/]/,                priority: -10,                filename: 'vendors.js'            },            default: false        }    }}

cacheGroupsvendors配置表示将从node_modules中引入的模块统一打包到一个vendors.js文件中

splitChunksvendorsdefault参数:

根据上下文来解释,如上配置了vendors,打包node_modules文件夹中的模块,

那么default将会打包自己编写的公共方法。

当不使用default配置时。

     Asset     Size             Chunks             Chunk Names   main.js  315 KiB               main  [emitted]  main   test.js  315 KiB               test  [emitted]  test

添加如下配置之后:

optimization: {    splitChunks: {        chunks: 'all',        cacheGroups: {              // 下面的意思是:将从node_modules中引入的模块统一打包到一个vendors.js文件中            vendors: {                test: /[\\/]node_modules[\\/]/,                priority: -10,                filename: 'vendors.js'            },            // 打包除上面vendors以外的公共模块            default: {                priority: -20,                reuseExistingChunks: true, // 如果该chunk已经被打包进其他模块了,这里就复用了,不打包进common.js了                filename: 'common.js'            }        }    }}

打包后的文件体积为

     Asset      Size             Chunks             Chunk Names common.js   308 KiB  default~main~test  [emitted]  default~main~test   main.js  7.03 KiB               main  [emitted]  main   test.js  7.02 KiB               test  [emitted]  test

配置说明

splitChunks: {  chunk: 'all', // all(全部), async(异步的模块),initial(同步的模块)  minSize: 3000, // 表示文件大于3000k的时候才对他进行打包  maxSize: 0,  minChunks: 1, // 当某个模块满足minChunks引用次数时,才会被打包。例如,lodash只被一个文件import,那么lodash就不会被code splitting,lodash将会被打包进 被引入的那个文件中。如果满足minChunks引用次数,lodash会被单独抽离出来,打出一个chunk。  maxAsyncRequests: 5, // 在打包某个模块的时候,最多分成5个chunk,多余的会合到最后一个chunk中。这里分析下这个属性过大过小带来的问题。当设置的过大时,模块被拆的太细,造成并发请求太多。影响性能。当设置过小时,比如1,公共模块无法被抽离成公共的chunk。每个打包出来的模块都会有公共chunk  automaticNameDelimiter: '~', // 当vendors或者default中的filename不填时,打包出来的文件名就会带~  name: true,  cashGroups: {} // 如上}

maxAsyncRequests

Lazy Loading

异步import的包会被单独打成一个chunk

async function getComponent() {    const { default: _ } = await import(/* webpackChunkNanem:'lodash */ 'lodash')    const element = document.createElement('div')    element.innerHTML = _.join(['Dell', 'Lee'], '-')    return element}document.addEventListener('click', () => {    getComponent().then(element => {        document.body.appendChild(element)    })})

lazy loading

chunk

每一个js文件都是一个chunk

chunk是使用Webpack过程中最重要的几个概念之一。在Webpack打包机制中,编译的文件包括entry(入口,可以是一个或者多个资源合并而成,由html通过script标签引入)和chunk(被entry所依赖的额外的代码块,同样可以包含一个或者多个文件)。从页面加速的角度来讲,我们应该尽可能将所有的js打包到一个bundle.js之中,但是总会有一些功能是使用过程中才会用到的。出于性能优化的需要,对于这部分资源我们可以做成按需加载。

打包分析

打包分析:

安装:npm install --save-dev webpack-bundle-analyzer

// package.json => scripts"analyz": "NODE_ENV=production npm_config_report=true npm run build"
// webpack.config.jsconst BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;plugins: [  new BundleAnalyzerPlugin()]

执行命令npm run analyz

浏览器就会自动打开localhost:8888,分析图就会展现在你眼前

非常清晰直观的看出

CSS文件的代码分割

我们之前写的css文件都会被打包进js文件中,要想把css单独打包成一个css文件该怎么做呢?

这个时候就需要用到MiniCssExtractPlugin

开发环境用不到这个功能,一般都是用在生产环境中。

安装:npm install --save-dev mini-css-extract-plugin

// webpack.config.jsconst MiniCssExtractPlugin = require('mini-css-extract-plugin')module: {  rules: [    {      test: /\.css$/,      use: [{        loader: MiniCssExtractPlugin.loader,        options: {          // 可以在此处指定publicPath          // 默认情况下,它在webpackoptions.output中使用publicPath          publicPath: '../',          // hmr: process.env.NODE_ENV === 'development',        },      }, 'css-loader']    }  ]},plugins: [  new MiniCssExtractPlugin({    // 与webpackoptions.output中相同选项类似的选项    // 两个选项都是可选的    filename: '[name].css',    chunkFilename: '[id].css',  }),]// index.jsimport './index.css';console.log('haha')// index.cssbody {    background: green;}

这样打包之后,css会被单独打包成一个css文件。

缓存

目前为止,我们每次修改内容,打包出去后的文件名都不变。线上环境的文件是有缓存的。所以当你文件名不变的话,更新内容打包上线。有缓存的电脑就无法获取到最新的代码。

这个时候我们就会用到contenthash

我们先记录配置contenthash之前打包的文件名。

     Asset       Size  Chunks             Chunk Namesindex.html  180 bytes          [emitted]     main.js   3.81 KiB    main  [emitted]  main

接下来我们来配置下contenthash (就是根据你文件内容生成的hash值)

// webpack.config.jsoutput: {  path: path.resolve(__dirname, '../dist'),  filename: '[name][contenthash].js'},

打包完之后会在main后面接上hash值。

                      Asset       Size  Chunks             Chunk Names                 index.html  200 bytes          [emitted]  mainf5faa2d3d1e119256290.js   3.81 KiB    main  [emitted]  main

当你不更新内容重新打包后,contenthash还会维持不变。所以线上用户访问的时候就不会去服务器重新拿取代码,而是从缓存中取文件。

shimming (预置依赖)

jquery为例,代码如下

// index.jsimport $ from 'jquery'$('body').html('HHAHAH')import func from './test.js'func()// test.jsexport default function func() {    $('body').append('<h1>2</h1>')}

当你不在test.js中引入import $ from 'jquery'

那么浏览器访问的时候,会报

test.js:5 Uncaught ReferenceError: $ is not defined

这个时候就需要使用垫片功能

const webpack = require('webpack')plugins: [  new webpack.ProvidePlugin({    $: 'jquery'  })]

当你加上这段代码后,模块在打包的时候,发现你使用了$就会在你模块顶部自动加入import $ from 'jquery'

其他关于shimming的内容参考webpack官网 shimming