• 以下的性能优化是基于webpack3.x的版本

  • 缩小文件的搜索范围
    webpack在启动后会从配置的entry出发,解析出文件中的导入语句,再递归解析。在遇到导入语句时,webpack会做以下的事情

    • 按照导入语句寻找对应的要导入的文件。(会根据文件的路径查找)
    • 根据需要找到的文件的后缀,使用配置中的Loader去处理文件,将其转换为webpack需要的形式。例如es6使用babel-loader去处理,而css会使用css-loader和style-loader去处理.
      上面是缩小文件范围的一个基本概念。下面会详细介绍如何才能缩小文件的搜索范围
    1. 优化Loader配置

      • 尽量减少文件被Loader处理,因为Loader对文件的转换是非常耗时的。
        可以通过使用include去命中只有哪些文件需要被处理,通过exclude配置来排除不需要处理的文件。适当的调整项目的目录,以方便在配置Loader时通过include缩小命中的范围
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        module.exports = {
        module: {
        rules: [
        {
        test: /\.j(s|xs)$/,
        use: ['babel-loader?cacheDirectory'],
        include: path.resolve(__dirname, 'src'),
        exclude: /node_modules/
        }
        ]
        }
        }
    2. 优化resolve.module配置
      resolve.module用于配置去哪些目录下寻找第三方模块。其默认值是[‘node_modules’],意思就是说先在当前目录的./node_modules目录下去找我们想要的模块,如果没有的话那么就去上一级目录../node_modules中找,依此类推。如果我们在项目中安装的第三方模块都是在根目录下面的话,那么就可以直接指定第三方模块的绝对路径,以此来减少模块的查找.

      1
      2
      3
      4
      5
      module.exports = {
      resolve: {
      modules: [path.resolve(__dirname, 'node_modules')]
      }
      }
    3. 优化resolve.maniFields配置
      resolve.maniFields的作用是配置第三方模块使用哪个入口文件。resolve.maniFields用于配置采用哪个字段作为入口文件的描述。可以同时存在多个字段描述入口文件的原因是,某些模块可以同时用于多个环境中,针对不同的环境需要使用不同的代码。所以在其package.json文件中就会有两个入口文件描述字段

      1
      2
      3
      4
      {
      "browser": "......browser.js",
      "main": "....npm-node.js"
      }

      resolve.maniFields的默认值和当前的target配置有关系,其对应的关系如下所示

      • 当target为web或者webworker时,值是[“browser”, “module”, “main”]。
      • 当target为其他情况时,值是[“module”, “main”].
        所以为了减少搜索步骤,当在明确知道第三方模块的入口文件描述时,可以将它设置得尽量少,由于大多数的第三方模块都采用main字段去描述入口文件的位置,所以可以按照以下的方式进行配置
        1
        2
        3
        4
        5
        module.exports = {
        resolve: {
        maniFields: ['main']
        }
        }
    4. 优化resolve.alias配置取别名
      resolve.alias配置项通过别名来将原导入路径映射成一个新的导入路径,例如在导入react时,webpack会从入口文件./node_modules/react/react.js开始递归解析所依赖的所有文件,这是非常耗时的行为,通过配置resolve.alias,可以让webpack在处理react库时,直接使用单独、完整的react.min.js文件,从而跳过递归解析等。

      1
      2
      3
      4
      5
      6
      7
      module.exports = {
      resolve: {
      alias: {
      'react': path.resolve(__dirname, './node_modules/react/dist/react.min.js')
      }
      }
      }

      但是使用该方法可能会影响到使用Tree-Sharking去除无效代码的优化。对于一些工具类的库如(lodash)可能就不适合采用别名的方式来进行优化,因为这样可能会包含很多无效的代码在打包过后。

    5. 优化resolve.extenstions配置
      导入文件没带后缀时,webpack会在自动带上后缀后去尝试询问文件是否存在。resolve.extensions配置用于在尝试匹配过程中的后缀列表。

      1
      2
      3
      module.exports = {
      extensions: ['.js', '.json']
      }

      在上面的配置中如果文件导入不带后缀名的是时候,首先会去查找.js的文件。如果不存在那么就会去查找.json后缀结尾的文件。如果都找不到的话,那么就抛出错误。如果extensions的列表越长,就会造成尝试的次数越多,所以resolve.extensions的配置也会影响到构建的性能。所以在配置resolve.extensions时应该遵循以下的内容

      • 后缀尝试列表要尽可能小。
      • 出现频率高的文件后缀优先放在最前面,以便为了更快的退出寻找内容。
      • 在源码导入文件时,尽量带上后缀名,避免寻找过程。
    6. 优化module.noParse配置
      module.noParse配置项可以让webpack忽略对部分没有采用模块化的文件递归解析处理,这样做的好处是能提高构建性能。
  • 使用ParallelUglifyPlugin
    在使用webpack构建出用于发布的代码时,会采用压缩这个流程。最常见的是采用UglifyJS,并且webpack也内置了该插件。在前端压缩代码时,会将代码解析成用Object抽象表示的AST语法树,再去应用各种规则分析和处理AST,所以导致这个过程的计算量巨大,耗时非常多。ParallelUglifyPlugin采用了多个子进程,将对多个文件的压缩工作分配给多个子进程去完成,每个子进程内部还是通过UglifyJS去压缩代码,但是确实并行执行的,所以会更快的完成对多个文件的压缩。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    module.exports = {
    plugins: [
    new ParallelUglifyPlugin({
    // 传递给UglifyJS的参数
    uglifyJS: {
    output: {
    beatuify: false, // 紧凑输出
    comments: false // 删除注释
    },
    compress: {
    warnings: false,
    drop_console: true, // 删除所有的console语句
    collapse_vars: trus, // 内嵌已定义但是只是用到一次的变量
    reduce_vars: true // 提取出现多次但是没有定义成变量去引用的静态值
    }
    }
    })
    ]
    }
  • 使用自动刷新
    利用自动化的手段,在监听到本地源码文件发生变化时,自动重新构建出可运行的代码后再控制浏览器刷新。

    1
    2
    3
    4
    5
    6
    7
    8
    module.exports = {
    watch: true, // 开启文件监听
    watchOptions: {
    ignored: '/node_modules/', // 忽略node_modules中的第三方模块监听
    aggregateTimeout: 400,// 截流,监听文件变化之后400ms再去执行更新动作
    poll: 1000 // 默认每秒询问1000次
    }
    }
    1. 文件监听
      在webpack中监听一个文件发生变化的原理,是定时获取这个文件的最后编辑时间,每次都存下最新的最后编辑时间,如果发现当前获取的和最后一次保存的最后编辑时间不一致,就认为该文件发生了变化。当某个文件发生变化时,并不会立刻告诉监听者,而是先缓存起来,收集一段时间的变化后,再一次性告诉监听者。
    2. 优化文件监听的性能
      • 排除node_modules中的文件监听,因为node_modules下的文件是不太可能会发生变化的。
      • watchOptions.aggregateTimeout的值越大性能越好,这样会降低构建的频率。
      • watchOptions.poll的值越小越好,这样会降低检查的频率。
    3. 自动刷新浏览器
      文件变化之后就需要刷新浏览器,webpack-dev-server模块负责刷新浏览器,
      浏览器自动刷新原理