timg.jpg

从技术手段上提高打包速度

跟上技术的迭代(升级node,npm或者yarn),因为webpack是基于node的,node的升级对webpack性能也会有提升,npm中很多的依赖包存在升级,那么对打包的速度也是有提高的
在尽可能少的模块上应用loader,loader的使用会对更多的文件去解析,所以在配置文件中,比如要把node_modules中的文件进行排除,因为可能依赖项已经被打包过,再打包一遍会更浪费事件
关于plugins的使用,尽量使用社区/官方推荐或者非常流行的插件,因为通常这类插件的稳定性和易用性得到了大多数人的认可,而第三方公司或者私人的插件可能会造成对项目优化不友好的地方
所以尽量避免使用plugins

通过resolve配置来提升打包速度

webpack中的resolve可以帮助我们引入第三方模块或者组件的时候,可以能够更方便的引入,比如通常在写vue的时候

import commonHeader from "../components/commonHeader"

不用指定其组件的后缀“vue”就可以找到,那么vue-cli是如何做到的呢,webpack配置就是如下:

resolve: {
    extensions: ["vue", "js", "jsx"]
}

可以通过这样简单配置,如果在目录下,没有找到对应的文件,那么就会再次遍历去寻找配置项指定后缀文件,但是这样的配置如果我们配置的过多的话,比如像这样

["jpg", "png", "json"]

一股脑地全部配上,这样就会在webpack打包中增加压力,延缓打包速度(因为要多次遍历查询)

所以一般的解决方案是:“只配置逻辑文件,如jsx,js,vue”等

如果我们引入配置的时候,引入的路径是这样的

import common from "./common/"

会发现,当组件的名称如果叫index.**时,这句代码是正确的,如果是其他名称,就‘

会报错,是因为webpack的默认项会把index当作主文件,那么我们也可以通过这样的配置进行更改:

mainFiles: ["index", "common"]

resolve中也还有其他常用的配置

比如alias(别名),比如我要这样引入

import common from "shenhao"

我们可以设置一个别名

alias: {
    shenhao: path.resolve(__dirname, ../src/common)
}

然后我们就可以使用alias进行引入,感觉通过这样的描述会感觉这个属性非常鸡肋,其实并不然,比如非常复杂的目录结构,要引入一个包要写很长的路径

如果写过vue的开发者来讲,@符号就表示src下的根目录,那么这个@就是一个别名

但是为了webpack的打包性能,尽量不要使用过多的配置alias和上面的extensions,配置项目中最常用的即可哟

使用DllPlugin提高打包速度

我们尝试使用一个babel打包一个react的程序,如图:

import React, { Component } from 'react';
import ReactDom from 'react-dom';
import _ from 'lodash';
class App extends Component {
    render() {
        return (
            <div>
                <div>{_.join([1,2,3,4,5], "...")}</div>
            </div>
        )
    }
}
let haha = document.createElement("div");
document.body.appendChild(haha);
ReactDom.render(<App />, haha);

经过多次打包,速度平均稳定在

Image.png

那么问题来了,我们每一次打包都会分析react,react-dom等库,我们可以尝试把这些库只分析一次,之后的打包读这些分析过的文件即可

首先我们需要额外的分析这些第三方库

新建webpack.dll.js

const path = require("path");
module.exports = {
    mode: "production",
    entry: {
        vendors: ["react", "react-dom", "lodash"]
    },
    output: {
        path: path.resolve(__dirname, 'dll'),
        filename: '[name].dll.js',
        library: "[name]"
    }
}

我们向外暴漏一下全局变量,然后我们则要让打包后的index.html去引入这个第三方库编译后的js
ps: 要让打包后的index.html自动引入,则需要add-asset-html-webpack-plugin这个插件,做一个简单配置即可(必须)

但是我们在main.js中的引入

根本引入的不是我们编译好的js文件,还是引入的是node_modules中的

  1. 让main.js引入的文件映射到编译过的第三方库文件中

首先配置一下,webpack.dll.js中我们增加一句代码

plugins: [new webpack.DllPlugin({
    name: "[name]",
    path: path.resolve(__dirname, "dll/[name].manifest.json")
})]

引入了webpack之后,使用了webpack中的DllPlugin模块,生成的分析文件的name就是暴漏的全局变量,path就是分析文件的地址

我们将使用全局变量+分析文件来做映射,让代码中的import这样的语句,映射到我们分析的js文件中,而不是引入node_modules中

所以在webpack.config.js中加入

new webpack.DllReferencePlugin({
   manifest: path.resolve(__dirname, "dll/vendors.manifest.json")
})

这边我们传递参数的内容就是生成出来的映射文件,这个时候我们重新打包会发现,打包速度会少200-300毫秒(根据电脑性能不同)

流程梳理:

将不会变更的库进行单独打包,并且暴漏一个全局变量(核心)
打包之后使用DllPlugin进行分析,生成一个映射文件,条件是必须有一个全局变量
在打包main.js中,webpack会对引入的模块进行查询,而common.js使用了DllReferencePlugin这个插件,会在对应的映射文件中查询如果对应的单独库js文件存在引入的模块,那么main.js将直接把单独库js文件中的对应的内容为自己所用,就不会在去node_modules中去寻找模块,通过这一系列,将减少打包时间

那么在大型项目中,需要拆分很多很多的dll.js和manifest.json,那么如何做到“智能的”让webpack使用DllReferencePlugin和add-asset-html-webpack-plugin呢,在看这个问题之前,我们来尝试一下传统方法

不智能的做法:

entry: {
        vendors: ["lodash"],
        react: ["react", "react-dom"]
    },

我们假设要生成2个分析文件

那么我们在webpack.config.js需要这样做

plugins: [new HtmlWebpackPlugin({
        filename: 'index.html'
    }), new AddAssetsHtmlPlugin({
        filepath: path.resolve(__dirname, "dll/vendors.dll.js")
    }),new AddAssetsHtmlPlugin({
        filepath: path.resolve(__dirname, "dll/react.dll.js")
    }), new webpack.DllReferencePlugin({
        manifest: path.resolve(__dirname, "dll/vendors.manifest.json")
    }),new webpack.DllReferencePlugin({
        manifest: path.resolve(__dirname, "dll/react.manifest.json")
    })]

居然要配置2遍插件

每个被分析的js都要暴漏一个全局变量,这个之前也说过,需要把每一个文件添加到html中,有了全局变量之后,才能让webpack去用json去查询模块

那么我们假象一下,一个项目如果需要规定上10个,上100个的入口模块,那么就要配置200次(100次添加js到html,100次查询json依赖文件)

这显然是不可取的,所以我们可以用相对智能的方式去做哟,需要用到node一点东西哟;

智能的做法:

思路1: 在运行之前,肯定是解析过了dll.js的,所以运行webpack.config.js之前,项目中应该会有100个.dll.js , 100个.manifest.json
我们抽象一下webpack.config.js中的plugins,定义一个数组

const fs = require("fs");
const plugins = []; // 抽象plugins数组
// 读取dll目录下的数据
let readFileList = fs.readdirSync(path.resolve(__dirname, "dll"));
console.log(readFileList);

// 打印结果:
[ 'react.dll.js',
  'react.manifest.json',
  'vendors.dll.js',
  'vendors.manifest.json' ]

循环这个数组

const files = fs.readdirSync(path.resolve(__dirname, '../dll'));
files.forEach(file => {
    if(/.*\.dll.js/.test(file)) {
        plugins.push(new AddAssetHtmlWebpackPlugin({
            filepath: path.resolve(__dirname, '../dll', file)
        }) )
    }
    if(/.*\.manifest.json/.test(file)) {
        new webpack.DllReferencePlugin({
            manifest: path.resolve(__dirname, "dll", file)
        }) )
    }
})

判断循环的当前file名称是不是dll.js,如果是dll.js,就在数组中添加对应的AddAssetHtmlWebpackPlugin 插件,反之就DllReferencePlugin 添加这个插件

大功告成,所以每当有一个新的入口时候,只需要

entry: {
        vendors: ["lodash"],
        react: ["react", "react-dom"]
    },

在webpack.dll.js随意配置即可, 但是每次打包之前,必须要先生成dll.js和manifest.json文件

控制包文件大小

在日常写代码的时候,一些引入的模块,没有用到,那么可以借助treeshaking和codesplit一些代码分割的插件,来控制包大小

多线程打包

webpack基于node的,是单线程进行打包,我们可以在单页应用使用thread-loader来对单页应用进行多线程进行打包,多页应用则使用parallel-webpack进行打包,还有一个是happypack

合理使用soucemap

合理选用适合项目的soucemap模式,过细的soucemap会增大打包压力,所以根据调整soucemap也可以调优

结合stats

一些第三方的网站可以分析webpack的打包文件,分析出打包的文件,哪个文件大,哪个文件慢,从而达到目的性的调优

开发环境内存编译

在开发环境时,使用的webpack-dev-server就是通过内存存储打包后的文件,所以内存的读写一定比硬盘读写快,所以使用内存编译的方式,打包速度也会加快

开发环境无用插件剔除

开发环境下,不需要过多的插件进行压缩等等,所以剔除无用插件也可以达到调优目的;

标签: none

添加新评论