• 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 目录,将会降级回退到操作系统默认的临时文件目录。

  • 相关阅读:
    HttpSession
    查看端口被哪个进程占用了
    变体类型 Variant VARIANT
    BDE View not exists
    c++builder 解压缩
    nginx的allow和deny配置
    linux下如何启动nginx?
    java如何发起一次http的post请求?
    mysql如何用sql添加字段如何设置字符集和排序规则
    设置Tomcat的UTF-8编码
  • 原文地址:https://www.cnblogs.com/laneyfu/p/12269947.html
Copyright © 2020-2023  润新知