Webpack--打包优化

一、HappyPack

HappyPack是一个通过多线程来提升webpack打包速度的工具。

  • 工作原理

(1)在打包过程中有一项非常耗时的工作,就是使用loader将各种资源进行转译处理。最常见的包括使用babel-loader转移ES6+语法和ts-loader转移TS。

1、从配置中获取打包入口

2、匹配loader规则,并对入口模块进行转译。

3、对转译后的模块进行依赖查找。

4、对新找到的模块重复2和3步骤,知道没有新的依赖模块。 

(2)webpack是单线程的。虽然这些转译任务彼此之间没有任何依赖关系,却必须串行地执行。HappyPack恰恰以此为切入点,它的核心特性是可以开启多个线程,并行地对不同模块进行转译,这样就可以充分利用本地地计算资源来提升打包速度。

(3)HappyPack适用于那些转译任务比较重地工程,如babel-loader和ts-loader。

  • 单个loader的优化

 要用HappyPack提供的loader来替换原有的loader,并将原有的哪个通过HappyPack插件传进去

const HappyPack = require('happypack')
module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        loader: 'happypack/loader',
        exclude: /node_modules/
      }
    ]
  },
  plugins: [
    new HappyPack({
      // 配置loader
      loaders: [{
        loader: 'babel-loader',
        options: {
          presets: ['react']
        }
      }],
    })
  ]
}
  • 多个loader的优化 

需要为每个loader配置一个id,否则 HappyPack 无法知道rules与plugins如何一一对应。

const HappyPack = require('happypack')
module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        loader: 'happypack/loader?id=js',
        exclude: /node_modules/
      },
      {
        test: /\.ts$/,
        loader: 'happypack/loader?id=ts',
        exclude: /node_modules/
      }
    ]
  },
  plugins: [
    new HappyPack({
      id: 'js',
      // 配置loader
      loaders: [{
        loader: 'babel-loader',
        options: {
          presets: ['react']
        }
      }],
    }),
    new HappyPack({
      id: 'ts',
      // 配置loader
      loaders: [{
        loader: 'ts-loader',
        options: {
        }
      }],
    }),
  ]
}

二、缩小打包作用域 

(1)exclude和include

(2)noParse

有些库希望webpack完全不要去解析,即不希望应用任何loader规则,库的内部也不会有对其他模块的依赖,此时可以用noParse对其进行忽略。

module.exports = {
  //..
  module: {
    //忽略所有文件名中包含loadsh的模块
    //这些模块仍然会被打包进资源文件,只不过webpack不会对其进行任何解析
    noParse: /loadsh/,
  }
}

webpack3后还支持完整的路径匹配 

module.exports = {
  //..
  module: {
    noParse: function (fullPath) {
      //fullPath是绝对路径
      return /lib/.test(fullPath)
    }
  }
}

 (3)IgnorePlugin

(1)它可以完全排除一些模块,被排除的模块即便被引用了也不会被打包进资源文件中

(2)这对于排除一些库相关文件非常有用。一些库产生的额外资源我们用不到但又无法去掉,因为引用的语句处于库文件的内部。(如Mommen.js,为了做本地化它会加载很多语言包,对于我们来说一般用不到其他地区的语言包,但他会占很多体积,这时就可以用IgnorePlugin来去掉)

plugins:[
  new webpack.IgnorePlugin({
    //匹配资源文件
    resourceRegExp:/^\.\/locale$/,
    //匹配检索目录
    contextRegExp:/moment$/
  })
]

(4)Cache

1、有些loader会有一个cache配置项,用来在编译代码后同时保存一份缓存,在执行下一次编译前会先检查源码文件是否有变化,如果没有就直接采用缓存,也就是上次编译的结果。这样相当于实际编译的只有变化了的文件,整体速度上会有一定提升。

 2、webpack5中添加了一个新的配置项“cache:{type : "filesSystem" }”,它会在全局启用一个文件缓存(实验阶段,并且无法自动检测到缓存已过期,目前解决方法:更改后手动修改cache.version来让缓存过期)

 三、动态链接库与DllPlugin

(1)动态链接库是早期windows系统由于受限于当时计算机内存空间较小的问题而出现的一种内存优化方法。当一段时间的子流程被多个程序调用时,为了减少内存消耗,可以将这段子程序存储为一个可执行文件,当被多个程序调用时只在内存中生成和使用同一个实例。

(2)DllPlugin借鉴了动态链接库的这种思路,对于第三方模块或者一些不常变化的模块,可以将它们预先编译和打包,然后再项目实际构建过程中直接取用即可。

(3)再打包vendor的时候还会附加生成一份vendor的模块清单,这份清单将会在工程业务模块打包时起到链接和索引的作用。

(4)DllPlugin和代码分片有点类似,都可以用来提取公共模块,但本质上有一些区别。

1、代码分片的思路是设置一些特定的规则并再打包的过程中根据这些规则提取模块;DllPlugin则是将vendor完全拆出来,有自己一整套webpack配置并独立打包,在实际工程构建时就不用再对它进行任何处理,直接取用即可。

2、因此DllPlugin比代码分片在打包速度上更胜一筹,但也相应地增加了配置,以及资源管理地复杂度。

  • vendor配置 

(1)需要单独创建一个webpack配置文件,如webpack.vendor.config.js。

const path = require('path');
const webpack = require('webpack');
const dllAssetPath = path.join(_dirname, 'dll')
const dllLibraryName = 'dllExample'
module.exports = {
  entry: {
    vendor: ['react']
  },
  output: {
    path: dllAssetPath,
    filename: 'vendor.js',
    library: dllLibraryName
  },
  plugins: [
    new webpack.DllPlugin({
      //资源清单地绝对路径,业务代码打包时将会使用这个清单进行模块索引
      path: path.join(dllAssetPath, 'manifest.json'),
      //导出的 dll library 的名字,它需要与output.library的值对应
      name: dllLibraryName
    })
  ]
};

 entry:指定了把那些模块打包为vendor

  • vendor打包

(1)在package.js中配置一条npm script

{
  "scripts": {
    "dll": "webpack --config webpack.vendor.config.js"
  }
}

 (2)运行npm ruqianzn dll 后会生成一个dll目录,里面有vendor.js和manifest.json,前者包含了库的代码,后者是资源清单。

//vendor.js
//dllExample 在配置文件中指定的dllLibraryName
var dllExample = (function (params) {
  //...
}(params))
//manifest.json
{
  //通过DllPlugin中的name指定的
  "name": 'dllExample',
    "content": {
    "./node_modules/fbjs/lib/invariant.js": {
      "id": 0,
      "buildMeta": {
        "providedExports": true
      }
    },
    //.....
  }
}
  • 链接到业务代码

(1)DllReferencePlugin

它起到一个索引和链接的作用。

//webpack.config.js
module.exports = {
  //...
  plugins: [
    new webpack.DllReferencePlugin({
      manifest: require(path.join(_dirname, 'dll/manifest.json')),
    })
  ]
};
//index.html
<script src="dll/vendor.js" > </script>
<script src="dist/app.js" > </script>

1、当页面执行到vendor.js时,会声明DllExample全局变量。

2、manifest相当于我们注入app.js的资源地图,app.js会先通过name字段找到名为DllExample的library,再进一步获取其内部模块。

  • 潜在问题

1、manifest.json中每个模块都有一个id,其值是按数字顺序递增的。加入一个模块可能会导致id变化。id变化会导致hash变化,导致缓存失效。

2、解决:配置 HashedModuleIdsPlugin

//webpack.vendor.config.js
module.exports = {
  //...
  plugins: [
    new webpack.HashedModuleIdsPlugin()
  ]
};

四、tree-shaking

(1)作用

1、可以再打包过程中帮助我们检测工程中没有被引用过的模块,这部分代码将永远无法被执行到,因此成为“死代码”。

2、webpack会对这部分代码进行标记,并在资源压缩时将它们从最终的bundle中去掉。

 (2)前提条件

1、只能对ES6 Module生效。

2、如果我们在工程中使用了babel-loader,那么一定要通过配置来禁用它的模块依赖解析。因为如果有babel-loader来做依赖解析,webpack接收到的就都是转化过的CommonJS形式的模块,无法进行tree-shaking。

{
  test: /\.js$/,
    use: [{
      loader: 'babel-loader',
      options: {
        presets: [
          [@babel/preset-env,{modules:false}]
        ]
      }
    }
    ]
}

3、使用压缩工具去除死代码

tree-shaking本身只是为死代码加上标记,真正去除死代码的是通过压缩工具来进行的。

4、原理

利用ES6模块的特点:

1、只能作为模块顶层的语句出现

2、import的模块名只能是字符串常量

3、import binding 是 immutable 的

代码擦除:

uglify阶段删除无用代码 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值