Webpack构建项目进一步优化

Step14.Webpack构建项目进一步优化

webpack dll VS external

webpack在打包后,生成的文件主要分为三种类型:

* 业务代码

* 外部依赖库

* webpack runtime

webpack中的dll和external在本质上其实是解决的同一个问题:避免将某些外部依赖库打包进我们的业务代码,而是在运行时提供这些依赖。

一方面实现了代码拆分,以及依赖的复用,另一方面提升构建速度.

这两种方案应该是各有各的优劣,分别适用于不同的环境。

(1) dllexternals的区别

dll 符合前端模块化的要求

webpack配置上稍微复杂一些,需要预打包所需的dll资源,并在构建时配置相应的plugin,

使用dll的前提是,这些外部依赖一般不需要发生变更。所以,如果某天发生了变更,那就需要将项目重新构建,违背了dll的使用前提,必然要作出相应的牺牲。

external不太符合前端的模块化思想,所需要的外部库需要在浏览器全局环境下可访问

外部库升级的话,如果兼容之前的API,不需要项目重新构建

webpack配置上稍微简单些,但是同样需要将所需的外部库打包为所需要的格式,并在运行态下引用

相比较而言的话,dll比external应该更加智能一些,主要体现在模块的引用和打包上。比如说如下方式去引用了react中的一个方法:

import AA from 'react/lib/createClass'

如果采用dll的方式,是不会造成重复打包的,他会将引用直接指向dll。但是如果使用external的话,则会react中的部分代码打包进来。

(2) externals

防止将某些 import 的包(package)打包 bundle 中,而是在运行时(runtime)再去从外部获取这些扩展依赖(external dependencies)

例如,从 CDN 引入 jQuery,而不是把它打包

<script src="https://code.jquery.com/jquery-3.1.0.js""></script>

module.exports = {

  //...

  externals: {

    jquery: 'jQuery'

  }};

这样就剥离了那些不需要改动的依赖模块,换句话,下面展示的代码还可以正常运行:

import $ from 'jquery';

$('.my-element').animate(/* ... */);

DLLPlugin 和 DLLReferencePlugin的使用

DLLPlugin为什么会出现?

 

在使用webpack进行打包时候,对于依赖的第三方库,比如vuevuex等这些不会修改的依赖,我们可以让它和我们自己编写的代码分开打包,这样做的好处是每次更改我本地代码的文件的时候,webpack只需要打包我项目本身的文件代码,而不会再去编译第三方库,那么第三方库在第一次打包的时候只打包一次,以后只要我们不升级第三方包的时候,那么webpack就不会对这些库去打包,这样的可以快速的提高打包的速度。因此为了解决这个问题,DllPlugin DllReferencePlugin插件就产生了。

https://www.webpackjs.com/plugins/dll-plugin/#dllplugin

DLLPlugin

 

DLLPlugin 它能把第三方库代码分离开,并且每次文件更改的时候,它只会打包该项目自身的代码。所以打包速度会更快。

DLLPlugin 这个插件是在一个额外独立的webpack设置中创建一个只有dll的bundle,也就是说我们在项目根目录下除了有webpack.config.js,还会新建一个webpack.dll.config.js文件。webpack.dll.config.js作用是把所有的第三方库依赖打包到一个bundle的dll文件里面,还会生成一个名为 manifest.json文件。
manifest.json的作用是用来让 DllReferencePlugin 映射到相关的依赖上去的。

DllReferencePlugin 这个插件是在webpack.config.js中使用的,该插件的作用是把刚刚在webpack.dll.config.js中打包生成的dll文件引用到需要的预编译的依赖上来。什么意思呢?就是说在webpack.dll.config.js中打包后比如会生成 vendor.dll.js文件和vendor-manifest.json文件,vendor.dll.js文件包含所有的第三方库文件,vendor-manifest.json文件会包含所有库代码的一个索引,当在使用webpack.config.js文件打包DllReferencePlugin插件的时候,会使用DllReferencePlugin插件读取vendor-manifest.json文件,看看是否有该第三方库。vendor-manifest.json文件就是有一个第三方库的一个映射而已。

所以说 第一次使用 webpack.dll.config.js 文件会对第三方库打包,打包完成后就不会再打包它了,然后每次运行 webpack.config.js文件的时候,都会打包项目中本身的文件代码,当需要使用第三方依赖的时候,会使用 DllReferencePlugin插件去读取第三方依赖库。所以说它的打包速度会得到一个很大的提升。

DLLPlugin 和 DLLReferencePlugin 用某种方法实现了拆分 bundles,同时还大大提升了构建的速度。

(1) 配置webpack.dll.config.js:

var path = require("path");

var webpack = require("webpack");

var SRC_PATH = path.resolve(__dirname,'./src');

module.exports = {

  // 要打包的模块的数组

  entry: {

    vendor: ['vue/dist/vue.esm.js','vue-router']

  },

  output: {

    path: path.join(__dirname, './static/dll), // 打包后文件输出的位置

    filename: '[name].dll.js',// vendor.dll.js中暴露出的全局变量名。

    library: '[name]_library' // webpack.DllPlugin中的`name: '[name]_library',`保持一致。

  },

  plugins: [

    new webpack.DllPlugin({

      path: path.join(SRC_PATH,'./static/dll/[name]-manifest.json'),

      name: '[name]_library', 

      context: __dirname

    }),

  ]

};

DllPlugin 插件有三个配置项参数如下:
context(可选): manifest文件中请求的上下文,默认为该webpack文件上下文。
name: 公开的dll函数的名称,和 output.library保持一致。
path: manifest.json 生成文件的位置和文件名称。

上面就是webpack.dll.conf.js的主要配置。执行之后会在static文件夹(在vue-cli生成的项目中用于存放不需要webpack构建的静态文件【@vue/cli中的目录名为Public】)下生成两个文件夹(lib文件夹和mainfest文件夹)。其中lib下的文件为我们已经打包好的组件库,mainfest下的文件在引入项目时有用(是一个JSON文件)。

package.jsonscripts里加上:

"dll": "webpack --config  webpack.dll.config.js",

运行npm run dll static下生成dll文件夹,dll文件夹里包括js文件和json文件

(2) webpack.base.conf.js里加上:

// 添加DllReferencePlugin插件

  plugins: [

    new webpack.DllReferencePlugin({

      context: __dirname,

      manifest: require('./static/dll/vendor-manifest.json')

    })

  ],

DllReferencePlugin项的参数有如下:

context: manifest文件中请求的上下文。
manifest: 编译时的一个用于加载的JSON的manifest的绝对路径。
mainfest: 请求到模块id的映射(默认值为 manifest.content)
name: dll暴露的地方的名称(默认值为manifest.name)
scope: dll中内容的前缀。
sourceType: dll是如何暴露的libraryTarget。

(3) index.html中引入vendor.dll.js:

由于动态链接库我们一般只编译一次,除非依赖的三方库更新,之后就不用编译,因此入口的 index.js 文件中不包含这些模块,所以要在 index.html 中单独引入。

① 第一种方式, 一种是根据打包的路径手动添加

<div id="app"></div><script src="./static/js/vendor.dll.js"></script>

② 第二种方式,用add-asset-html-webpack-plugin或者html-webpack-include-assets-plugin插入到html中,简单自动化

下面着重讲下add-asset-html-webpack-plugin与html-webpack-include-assets-plugin插件的使用,项目中使用add-asset-html-webpack-plugin

安装大同小异

npm安装:

npm install add-asset-html-webpack-plugin -D

npm install html-webpack-include-assets-plugin -D

Yarn安装:

yarn add add-asset-html-webpack-plugin -D

yarn add html-webpack-include-assets-plugin -D

add-asset-html-webpack-plugin的使用

const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');
 
module.exports = {
    ...,
    plugins: [
        ...,
        # 给定的 JS  CSS 文件添加到 webpack 配置的文件中,并将其放入资源列表 html webpack插件注入到生成的 html 中。
        new AddAssetHtmlPlugin([
            {
                # 要添加到编译中的文件的绝对路径
                filepath: path.resolve(__dirname,'./static/dll/dll_vendor.js'),
                outputPath: 'dll',
                publicPath: 'dll',
                includeSourcemap: false
            }
        ])
    ]
}

html-webpack-include-assets-plugin的使用

const HtmlWebpackIncludeAssetsPlugin = require('html-webpack-include-assets-plugin');

module.exports = {

    ...,

    plugins: [

        ...,

        // 给定的 JS CSS 文件添加到 webpack 配置的文件中,并将其放入资源列表 html webpack插件注入到生成的 html 中。

    new HtmlWebpackIncludeAssetsPlugin(

          {

              assets: ['dll/asset.dll.js','dll/vendor.dll.js'],

              append: false

          }

       )

]

至此,配置之后的:
可以看到npm run build后的时间大幅度减少,在dist打包体积上也比之前的小。在项目优化中,可以很大程度上加快项目的构建速度和减少项目的打包体积。

使用 happypack 提升 Webpack 项目构建速度

提示:由于HappyPack file-loaderurl-loader 支持的不友好,所以不建议对该loader使用。

webpack打包哪一步最耗时?可能要数loader对文件的转换操作了,我们前面说过,我们使用loader将文件转换为我们需要的类型,文件数量巨大,webpack执行又是单线程的,转换的操作只能一个一个的处理,不能多件事一起做。
我们需要Webpack 能同一时间处理多个任务,发挥多核 CPU 电脑的威力,HappyPack 就能让 Webpack 做到这点,我们将需要通过loader处理的文件先交给happypack去处理,happypack 在收集到这些文件的处理权限后,统一分配CPU资源.

happypack工作原理

happypack 通过new HappyPack(),去实例化一个HappyPack对象,其实就是告诉Happypack核心调度器如何通过一系列loader去转换一类文件,并且可以指定如何为这类转换器作分配子进程。
核心调度器的逻辑代码在主进程里,也就是运行webpack的进程中,核心调度器会将一个个任务分配给当前空闲的子进程,子进程处理完后会将结果发送给核心调度器,它们之间的数据交换是通过进程间的通讯API实现的。
核心调度器收到来自子进程处理完毕的结果后,会通知webpack该文件已经处理完毕

参考:https://www.qdtalk.com/2018/11/16/webpack4plugin-2/

安装:

yarn add happypack --dev

使用:

// 引入 happypack

const HappyPack = require('happypack');

// 创建 happypack 共享进程池,其中包含 6 个子进程

const happyThreadPool = HappyPack.ThreadPool({ size: 6 });

module.exports = {

//省略部分配置
  module: {
    rules: [
      {
        test: /.js$/,
        //把对.js 的文件处理交给id为happyBabel 的HappyPack 的实例执行
         use: 'happypack/loader?id=happyBabel',
        //排除node_modules 目录下的文件,合理的使用排除可以事半功倍
        exclude: path.resolve(__dirname,'node_modules')
      },
    ]
  },
plugins: [
    new HappyPack({
        //用id来标识 happypack处理那里类文件
      id: 'happyBabel',
      //如何处理js文件  用法和loader 的配置一样
      loaders: [{
        loader: 'babel-loader?cacheDirectory=true',
      }],
      //使用共享进程池中的自进程去处理任务

      threadPool: happyThreadPool,
      //允许 HappyPack 输出日志,默认为true
      verbose: true,
    })
  ]
}

Loader 配置中,所有文件的处理都交给了 happypack/loader 去处理,使用紧跟其后的 querystring ?id=babel 去告诉 happypack/loader 去选择哪个 HappyPack 实例去处理文件。

Plugin 配置中,新增了两个 HappyPack 实例分别用于告诉 happypack/loader 去如何处理 .js 和 .css 文件。选项中的 id 属性的值和上面 querystring 中的 ?id=babel 相对应,选项中的 loaders 属性和 Loader 配置中一样。

对应的参数

id:String   

用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件.

loaders: Array   

用法和 webpack Loader 配置中一样.

threads: Number  

代表开启几个子进程去处理这一类型的文件,默认是3个,类型必须是整数。

verbose: Boolean

是否允许 HappyPack 输出日志,默认是 true。

threadPool: HappyThreadPool

代表共享进程池,即多个 HappyPack 实例都使用同一个共享进程池中的子进程去处理任务,以防止资源占用过多。

verboseWhenProfiling: Boolean

开启webpack --profile ,仍然希望HappyPack产生输出。

debug: Boolean

启用debug 用于故障排查。默认 false。

https://blog.csdn.net/zgd826237710/article/details/88172290#_HappyPack_36

注意

注意,webpack4中的happypack要使用5.0.0版本,如果你是从webpack3升级到webpack4,记得升级happypack

上面的loader中出现一个陌生词cacheDirectory:
cacheDirectory默认值为 false。
当有设置时,指定的目录将用来缓存 loader 的执行结果。之后的 webpack 构建,将会尝试读取缓存,来避免在每次执行时,可能产生的、高性能消耗的 Babel 重新编译过程(recompilation process)。如果设置了一个空值 (loader: ‘babel-loader?cacheDirectory’) 或者 true (loader: babel-loader?cacheDirectory=true),loader 将使用默认的缓存目录 node_modules/.cache/babel-loader,如果在任何根目录下都没有找到 node_modules 目录,将会降级回退到操作系统默认的临时文件目录。

原文地址:https://www.cnblogs.com/laneyfu/p/12269947.html