前言

这是我花了几个星期学习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.json
sideEffects: ['./console.js', './index.css']
// 反之
sideEffects: false

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

developmentproduction 区别

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

省略…☺

webpack-merge

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

// webpack.base.config.js
const 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.js
const 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 Names
lodash.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]  main
vendors~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.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

plugins: [
  new BundleAnalyzerPlugin()
]

执行命令npm run analyz

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

非常清晰直观的看出

image-20190421142354243

CSS文件的代码分割

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

这个时候就需要用到MiniCssExtractPlugin

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

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

// webpack.config.js
const 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.js
import './index.css';
console.log('haha')

// index.css
body {
    background: green;
}

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

缓存

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

这个时候我们就会用到contenthash

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

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

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

// webpack.config.js
output: {
  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.js
import $ from 'jquery'
$('body').html('HHAHAH')
import func from './test.js'
func()


// test.js
export 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


我是南方大汉
762 声望54 粉丝

爱技术,爱分享


引用和评论

0 条评论