澳门新浦京娱乐场网站-www.146.net-新浦京娱乐场官网
做最好的网站

Vuejs项目的Webpack2构建优化,0打包优化策略整理小

简介

本文介绍了webpack4.0打包优化策略整理小结,分享给大家,具体如下:

最近在做的项目因为相对较大(打包有100多个chunk),在build构建的时候速度一直上不去,甚是烦恼。由于用的是vue-cli的webpack2模板,一开始并没有想着要对其进行优化,一直觉得是webpack本身慢 硬件慢(在开发机上开发,内存和CPU都不是很强力)的原因。后来慢到实在受不了了,转移到本地(i7 16G)开发的时候,发现生产构建居然需要90s,实在太长了。所以开始着手Webpack2构建优化。

在前文 Webpack 打包优化之体积篇中,对如何减小 Webpack 打包体积,做了些探讨;当然,那些法子对于打包速度的提升,也是大有裨益。然而,打包速度之于开发体验及时构建,相当重要;所以有必要对其做更为深入的研究,以便完善工作流,这就是本文存在的缘由。

webpack优化方案

读了《深入浅出webpack》总结一下常用的webpack的构建优化策略,可通过以下手段来提升项目构建时的速度

webapck4 新特性介绍-参考资料

  • 优化webpack构建速度,总的来说有几个思路:

Webpack Package optimization

1. 优化开发体验

更精准的loader规则

1.优化loader配置

  1. 优化本身项目结构,模块的引入、拆分、共用
  2. 优化结构路径、让webpack接管的文件能够快速定位
  3. 优化uglify编译速度
  4. 优化webpack本身编译速度
    有些是在开发的时候代码层面上的,有些则是需要在webpack配置层面上的。对于开发层面上来说,按需引入是很重要的一点。通常为了方便我们可以直接引入一个echarts,但是实际上并不需要echarts的所有功能。而按需引入则能最大程度上让

减小文件搜索范围

在使用实际项目开发中,为了提升开发效率,很明显你会使用很多成熟第三方库;即便自己写的代码,模块间相互引用,为了方便也会使用相对路劲,或者别名(alias);这中间如果能使得 Webpack 更快寻找到目标,将对打包速度产生很是积极的影响。于此,我们需要做的即:减小文件搜索范围,从而提升速度;实现这一点,可以有如下两法:

    1-1. 加快构建速度

     ① 缩小文件搜索范围

  • 由于 Loader 对文件的转换操作很耗时,需要让尽可能少的文件被 Loader 处理,用include和exclude去缩小;
  • resolve.modules用于配置 Webpack 去哪些目录下寻找第三方模块:[path.resolve(__dirname, 'node_modules')](根目录下);

  • resolve.mainFields用于配置第三方模块使用哪个入口文件:['mian'];

  • resolve.alias配置项通过别名来把原导入路径映射成一个新的导入路径:[path.resolve(__dirname, './node_modules/react/dist/react.min.js')];
  • resolve.extensions用于配置在尝试过程中用到的后缀列表:['js', 'json'];
  • module.noParse配置项可以让 Webpack 忽略对部分没采用模块化的文件的递归解析处理:[/react.min.js$/];

     ② HappyPack

  • HappyPack 的核心原理就是把loader转换任务分解到多个进程去并行处理,从而减少了总的构建时间;

     ③ ParallelUglifyPlugin 

  • 压缩js代码需要先把代码解析成用 Object 抽象表示的 AST 语法树,再去应用各种规则分析和处理 AST;
  • ParallelUglifyPlugin会开启多个子进程,把对多个文件的压缩工作分配给多个子进程去完成,每个子进程其实还是通过 UglifyJS 去压缩代码,但是变成了并行执行;       

将loader规则写清楚

1.1 缩小文件匹配范围

  • 总得来说作用最大的有这几个:

配置 resolve.modules

Webpack的resolve.modules配置模块库(即 node_modules)所在的位置,在 js 里出现 import 'vue' 这样不是相对、也不是绝对路径的写法时,会去 node_modules 目录下找。但是默认的配置,会采用向上递归搜索的方式去寻找,但通常项目目录里只有一个 node_modules,且是在项目根目录,为了减少搜索范围,可以直接写明 node_modules 的全路径;同样,对于别名(alias)的配置,亦当如此:

function resolve (dir) {
  return path.join(__dirname, '..', dir)
}

module.exports = {
  resolve: {
    extensions: ['.js', '.vue', '.json'],
    modules: [
      resolve('src'),
      resolve('node_modules')
    ],
    alias: {
      'vue$': 'vue/dist/vue.common.js',
      'src': resolve('src'),
      'assets': resolve('src/assets'),
      'components': resolve('src/components'),
      // ...
      'store': resolve('src/store')
    }
  },
  ...
}

需要额外补充一点的是,这是 Webpack2.* 以上的写法。在 1.* 版本中,使用的是 resolve.root,如今已经被弃用为 resolve.modules;同时被弃用的还有resolve.fallbackresolve.modulesDirectories

    1-2. 提升开发效率

     ① 自动刷新(webpack 模块负责监听文件,webpack-dev-server 模块则负责刷新浏览器)

  • 文件监听(--watch或watch: true),原理是定时的去获取这个文件的最后编辑时间,每次都存下最新的最后编辑时间,如果发现当前获取的和最后一次保存的最后编辑时间不一致,就认为该文件发生了变化;
  • 优化文件监听:watchOptions: {ignored: /node_modules/};
  • 自动刷新原理是往要开发的网页中注入代理客户端代码,通过代理客户端向DevServer 发起的 WebSocket 连接去刷新整个页面;
  • devServer.inline是用来控制是否往 Chunk 中注入代理客户端的,默认会注入;在你关闭了 inline 后,DevServer 会自动地提示你通过新网址 http://localhost:8080/webpack-dev-server/ 去访问;

     ② 模块热替换

  • 模块热替换的原理和自动刷新原理类似,都需要往要开发的网页中注入一个代理客户端用于连接 DevServer 和网页, 不同在于模块热替换独特的模块替换机制;

仅让需要处理的文件,进入loader处理环节,如下

通过排除node_modules下的文件 从而缩小了loader加载搜索范围 高概率命中文件

  1. 开启webpack的cache
  2. 开启babel-loader的cache
  3. 指定modules以及配置项目相关的alias
  4. 配置loader的include和exclude
  5. 用CommonsChunkPlugin提取公用模块
  6. 使用DllPlugin和DllReferencePlugin预编译
  7. 换用happypack多进程构建
  8. css-loader换成0.14.5版本。
  9. 换用webpack-uglify-parallel并行压缩代码

设置 test & include & exclude

Webpack 的装载机(loaders),允许每个子项都可以有以下属性:

test:必须满足的条件(正则表达式,不要加引号,匹配要处理的文件)
exclude:不能满足的条件(排除不处理的目录)
include:导入的文件将由加载程序转换的路径或文件数组(把要处理的目录包括进来)
loader:一串“!”分隔的装载机(2.0版本以上,"-loader"不可以省略)
loaders:作为字符串的装载器阵列

对于include,更精确指定要处理的目录,这可以减少不必要的遍历,从而减少性能损失。同样,对于已经明确知道的,不需要处理的目录,则应该予以排除,从而进一步提升性能。假设你有一个第三方组件的引用,它肯定位于node_modules,通常它将有一个 src 和一个 dist 目录。如果配置 Webpack 来排除 node_modules,那么它将从 dist 已经编译的目录中获取文件。否则会再次编译它们。故而,合理的设置 include & exclude,将会极大地提升 Webpack 打包优化速度,比如像这样:

module: {
  preLoaders: [
    {
      test: /.js$/,
      loader: 'eslint',
      include: [resolve('src')],
      exclude: /node_modules/
    },
    {
      test: /.svg$/,
      loader: 'svgo?'   JSON.stringify(svgoConfig),
      include: [resolve('src/assets/icons')],
      exclude: /node_modules/
    }
  ],
  loaders: [
    {
      test: /.vue$/,
      loader: 'vue-loader',
      include: [resolve('src')],
      exclude: /node_modules/(?!(autotrack|dom-utils))|vendor.dll.js/
    },
    {
      test: /.(png|jpe?g|gif|svg)(?.*)?$/,
      loader: 'url',
      exclude: /assets/icons/,
      query: {
        limit: 10000,
        name: utils.assetsPath('img/[name].[hash:7].[ext]')
      }
    }
  ]
}

2. 优化输出质量

 rules: [{ // 正则尽量准确 test: /.js$/, // 使用缓存,缓存后在文件未改变时编译会更快 use: ['babel-loader?cacheDirectory'], // 指定需要处理的目录 include: path.resolve // 理论上只有include就够了,但是某些情况需要排除文件的时候可以用这个,排除不需要处理文件 // exclude: [] }]
 module: { rules: [ { test: /.js$/, use: 'babel-loader', exclude: /node_modules/, // 排除不处理的目录 include: path.resolve // 精确指定要处理的目录 } ] }

以下的配置都是基于vue-cli的webpack模板进行的优化。

增强代码代码压缩工具

Webpack 默认提供的 UglifyJS 插件,由于采用单线程压缩,速度颇慢 ;推荐采用 webpack-parallel-uglify-plugin 插件,她可以并行运行 UglifyJS 插件,更加充分而合理的使用 CPU 资源,这可以大大减少的构建时间;当然,该插件应用于生产环境而非开发环境,其做法如下,

new webpack.optimize.UglifyJsPlugin({
  compress: {
    warnings: false
  },
  sourceMap: true
})

替换如上自带的 UglifyJsPlugin 写法为如下配置即可:

var ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
new ParallelUglifyPlugin({
  cacheDir: '.cache/',
  uglifyJS:{
    output: {
      comments: false
    },
    compress: {
      warnings: false
    }
  }
})

当然也有其他同类型的插件,比如:webpack-uglify-parallel,但根据自己实践效果来看,并没有 webpack-parallel-uglify-plugin 表现的那么卓越,有兴趣的朋友,可以更全面的做下对比,择优选用。需要额外说明的是,webpack-parallel-uglify-plugin 插件的运用,会相对 UglifyJsPlugin 打出的包,看起来略大那么一丢丢(其实可以忽略不计);如果在你使用时也是如此,那么在打包速度跟包体积之间,你应该有自己的抉择。

    2-1. 减少加载时间

     ① 区分环境

  • process.env.NODE_ENV

     ② 压缩代码

     ③ CDN加速

  • 针对 HTML 文件:不开启缓存,把 HTML 放到自己的服务器上,而不是 CDN 服务上,同时关闭自己服务器上的缓存。自己的服务器只提供 HTML 文件和数据接口;
  • 针对静态的 JavaScript、CSS、图片等文件:开启 CDN 和缓存,上传到 CDN 服务上去,同时给每个文件名带上由文件内容算出的 Hash 值;

     ④ tree shaking

  • 让 Tree Shaking 正常工作的前提是交给 Webpack 的js代码必须是采用 ES6 模块化语法的, 因为 ES6 模块化语法是静态的(导入导出语句中的路径必须是静态的字符串,而且不能放入其它代码块中),这让 Webpack 可以简单的分析出哪些export的被import过了

     ⑤ 提取公共代码(CommonsChunkPlugin)

     ⑥ 按需加载

更精准的查找目录

1.2 缓存loader的执行结果

  • 开启webpack的cache

用 Happypack 来加速代码构建

你知道,Webpack 中为了方便各种资源和类型的加载,设计了以 loader 加载器的形式读取资源,但是受限于 nodejs 的编程模型影响,所有的 loader 虽然以 async 的形式来并发调用,但是还是运行在单个 node 的进程,以及在同一个事件循环中,这就直接导致了些问题:当同时读取多个loader文件资源时,比如`babel-loader`需要 transform 各种jsx,es6的资源文件。在这种同步计算同时需要大量耗费 cpu 运算的过程中,node的单进程模型就无优势了,而 Happypack 就是针对解决此类问题而生的存在。

Webpack-Happypack

Happypack 的处理思路是:将原有的 webpackloader 的执行过程,从单一进程的形式扩展多进程模式,从而加速代码构建;原本的流程保持不变,这样可以在不修改原有配置的基础上,来完成对编译过程的优化,具体配置如下:

var HappyPack = require('happypack');
var happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });

module: {
  loaders: [
    {
      test: /.js[x]?$/,
      include: [resolve('src')],
      exclude: /node_modules/,
      loader: 'happypack/loader?id=happybabel'
    }
  ]
},
plugins: [
  new HappyPack({
    id: 'happybabel',
    loaders: ['babel-loader'],
    threadPool: happyThreadPool,
    cache: true,
    verbose: true
  })
]

可以研究看到,通过在 loader 中配置直接指向 happypack 提供的 loader,对于文件实际匹配的处理 loader,则是通过配置在 plugin 属性来传递说明,这里 happypack 提供的 loader 与 plugin 的衔接匹配,则是通过id=happybabel来完成。配置完成后,laoder的工作模式就转变成了如下所示:

Webpack-Happypack

Happypack 在编译过程中,除了利用多进程的模式加速编译,还同时开启了 cache 计算,能充分利用缓存读取构建文件,对构建的速度提升也是非常明显的;更多关于 happyoack 个中原理,可参见 @淘宝前端团队(FED) 的这篇:happypack 原理解析。如果你使用的 Vue.js 框架来开发,也可参考 vue-webpack-happypack 相关配置。

    2-2. 提升流畅度

     ① Prepack

  • 通过 Babel 把 JavaScript 源码解析成抽象语法树(AST),以方便更细粒度地分析源码;
  • Prepack 实现了一个 JavaScript 解释器,用于执行源码。借助这个解释器 Prepack 才能掌握源码具体是如何执行的,并把执行过程中的结果返回到输出中。

     ② Scope Hoisting

 

理论上我们项目的第三方依赖均应在自己的工程的node_modules下,所以我们可以设置查找目录,减少node的默认查找

cacheDirectory是loader的一个特定的选项,默认值是false。指定的目录(use: 'babel-loader?cacheDirectory=cacheLoader')将用来缓存loader的执行结果,减少webpack构建时Babel重新编译过程。如果设置一个空值(use: 'babel-loader?cacheDirectory') 或true(use: 'babel-loader?cacheDirectory=true') 将使用默认的缓存目录(node_modules/.cache/babel-loader),如果在任何根目录下都没有找到 node_modules 目录,将会降级回退到操作系统默认的临时文件目录。

打开webpack.base.conf.js,在module.exports里加上cache: true:

设置 babel 的 cacheDirectory 为true

babel-loader is slow! 所以不仅要使用excludeinclude,尽可能准确的指定要转化内容的范畴,而且要充分利用缓存,进一步提升性能。babel-loader 提供了 cacheDirectory特定选项(默认 false):设置时,给定的目录将用于缓存加载器的结果。

未来的 Webpack 构建将尝试从缓存中读取,以避免在每次运行时运行潜在昂贵的 Babel 重新编译过程。如果值为空(loader: 'babel-loader?cacheDirectory')或true(loader: babel-loader?cacheDirectory=true),node_modules/.cache/babel-loaderVuejs项目的Webpack2构建优化,0打包优化策略整理小结_javascript技巧_脚本之家。node_modules 在任何根目录中找不到任何文件夹时,加载程序将使用默认缓存目录或回退到默认的OS临时文件目录。实际使用中,效果显著;配置示例如下:

rules: [
  {
    test: /.js$/,
    loader: 'babel-loader?cacheDirectory=true',
    exclude: /node_modules/,
    include: [resolve('src'), resolve('test')]
  },
  ... ...
]

webpack构建流程

  1. 初始化(启动构建,读取并合并配置参数,加载plugin,实例化compiler)
  2. 编译(entry出发,针对每个module引用loader解析,依次找到module依赖,然后递归)
  3. 输出(对编译后的module组合成chunk,转换成文件,输出)
  4. 文件变化,转到step2;

    

module.exports = { resolve: { // 指定当前目录下的node_modules目录 modules: [path.resolve(__dirname, 'node_modules')] }}
module: { rules: [ { test: /.js$/, use: 'babel-loader?cacheDirectory', // 缓存loader执行结果 发现打包速度已经明显提升了 exclude: /node_modules/, include: path.resolve } ]}
module.exports = {
  cache: true,
  // ... 其他配置
}

设置 noParse

如果你确定一个模块中,没有其它新的依赖,就可以配置这项, Webpack 将不再扫描这个文件中的依赖,这对于比较大型类库,将能促进性能表现,具体可以参见以下配置:

module: {
  noParse: /node_modules/(element-ui.js)/,
  rules: [
    {
      ...
    }
}

更精准的扩展名

2.resolve优化配置

  • 开启babel-loader的cache
    开启了cache的babel-loader,在下次编译的时候,遇到不变的部分可以直接拿取cache里的内容,能够较为明显地提高构建速度。在loader选项里只需要对babel-loader开启cacheDirectory=true即可。
    配置cacheDirectory后,babel-loader可以缓存处理过的模块,对于没有修改过的文件不会再重新编译。

    // ... 其他配置
    module: {
      rules: [
    {
      test: /.js$/,
      loader: 'babel-loader?cacheDirectory=true'
    },
    // ... 其他loader
      ]
    }
    
  • 配置modules以及配置项目相关的alias

拷贝静态文件

在前文 Webpack 打包优化之体积篇中提到,引入 DllPluginDllReferencePlugin 来提前构建一些第三方库,来优化 Webpack 打包。而在生产环境时,就需要将提前构建好的包,同步到 dist 中;这里拷贝静态文件,你可以使用 copy-webpack-plugin 插件:把指定文件夹下的文件复制到指定的目录;其配置如下:

var CopyWebpackPlugin = require('copy-webpack-plugin')

plugins: [
  ...
  // copy custom static assets
  new CopyWebpackPlugin([
    {
      from: path.resolve(__dirname, '../static'),
      to: config.build.assetsSubDirectory,
      ignore: ['.*']
    }
  ])
]

当然,这种工作,实现的法子很多,比如可以借助 shelljs,可以参见这里的实现 vue-boilerplate-template。

于深圳.南山 @17-08-10 Last Modify: @17-08-12


数量更多类型的文件尽量放在前面

2.1 优化模块查找路径 resolve.modules

这个部分的配置实际上都是对webpack接管的文件路径的一些配置。通过这些配置,webpack可以不必自己遍历去搜索模块等,而可以通过我们定义的路径,快速定位。尤其是node_modules的位置,这个位置可以通过modules选项配置,节省webpack去查找的时间。

猜你喜欢(/对你有用)的文章

Webpack 打包优化之体积篇
『引』最全前端资源汇集
所历前端“姿势”更替记(其一)
Vuejs项目的Webpack2构建优化,0打包优化策略整理小结_javascript技巧_脚本之家。Vue ES6 Jade Scss Webpack Gulp
Vue Webpack 组件化开发实践

平时写代码,我们都习惯直接写文件名,而不去写扩展名,那么解析则按照下面属性进行解析

Webpack的resolve.modules配置模块库所在的位置,在 js 里出现 import 'vue' 这样不是相对、也不是绝对路径的写法时,会去 node_modules 目录下找。但是默认的配置,会采用向上递归搜索的方式去寻找,但通常项目目录里只有一个 node_modules,且是在项目根目录,为了减少搜索范围,可以直接写明 node_modules 的全路径;同样,对于别名的配置,亦当如此:

而alias是别名。通过编写alias,既能让webpack查找文件定位更快,在开发的时候,也能少些很多相对路径的../..,在引入模块的时候很方便。

module.exports = { extensions: ['.js', '.jsx', '.ts', '.tsx'],}

extensions: [".js", ".json"]
const path = require;function resolve { // 转换为绝对路径 return path.join;}resolve: { modules: [ // 优化模块查找路径 path.resolve, path.resolve // 指定node_modules所在位置 当你import 第三方模块时 直接从这个路径下搜索寻找 ]}

同样是打开webpack.base.conf.js,在module.exports的resolve属性里配置modules和alias。其中vue-cli会自动配置一些默认的alias。

使用动态链接库预编译大模块

配置好src目录所在位置后,由于util目录是在src里面 所以可以用下面方式引入util中的工具函数

resolve: {
  //... 其他配置
  modules: [path.resolve(__dirname, '../../node_modules')], // node_modules文件夹所在的位置取决于跟webpack.base.conf.js相对的路径
  alias: {
    //... 其他配置
    api: path.resolve(__dirname, '../../server/api') // api文件所在的位置取决于跟webpack.base.conf.js相对的路径,在项目中会自动转换跟项目文件的相对路径
    //... 其他配置
  }
}

使用动态链接库,提前编译大模块

// main.jsimport dep1 from 'util/dep1';import add from 'util/add';

如果配置了如上的alias,那么我们在项目里,要引用比如api.js这个模块,可以直接这样做了:

新建一个文件webpack_dll.config.js,内容如下

2.2 resolve.alias 配置路径别名

import * as api from 'api' // 'api'是个alias,webpack会直接去找`server/api`
const path = require;const webpack = require;// 复用的大模块放在这里,这样每次都不需要重新编译了const vendors = [ 'react', 'react-dom', 'lodash'];module.exports = { mode: 'development', output: { path: path.resolve, filename: '[name].js', library: '[name]', }, entry: { vendors, }, plugins: [ new webpack.DllPlugin({ path: path.resolve(__dirname, './dist/manifest.json'), name: '[name]', }), ],};

创建 import 或 require 的路径别名,来确保模块引入变得更简单。配置项通过别名来把原导入路径映射成一个新的导入路径 此优化方法会影响使用Tree-Shaking去除无效代码

而不用手动去根据项目文件和api所在路径的相对位置去书写import的路径了。

执行webpack --config webpack_dll.config.js进行首次编译

例如,一些位于 src/ 文件夹下的常用模块:

  • 配置loader的include和exclude

然后在你的webpack配置文件中引入manifest.json

alias: { Utilities: path.resolve(__dirname, 'src/utilities/'), Templates: path.resolve(__dirname, 'src/templates/')}

loader的include和exclude也就是需要loader接管编译的文件和不需要loader接管编译的文件。

 plugins: [ new webpack.DllReferencePlugin({ manifest: require('./dist/manifest.json') }) ],

现在,替换「在导入时使用相对路径」这种方式,就像这样:

这里我们举babel-loader为例。通常情况下,我们不需要loader去编译node_modules下的js文件,而我们只需要编译我们项目目录下的js就行了。这样可以通过配置这两个选项,能够最小范围的限制babel-loader需要编译的内容,能够有效提升构建速度。

多进程处理文件

import Utility from '../../utilities/utility';

import Utility from 'Utilities/utility';

resolve: { alias: { // 别名配置 通过别名配置 可以让我们引用变的简单 'vue$': 'vue/dist/vue.common.js', // $表示精确匹配 src: resolve // 当你在任何需要导入src下面的文件时可以 import moduleA from 'src/moduleA' src会被替换为resolve 返回的绝对路径 而不需要相对路径形式导入 }}

同样打开webpack.base.conf.js,在rules的babel-loader那块加上include和exclude。

使用HappyPack同时处理多个loader编译任务

也可以在给定对象的键后的末尾添加 $,以表示精准匹配:

// ... 其他配置
module: {
  rules: [
    {
      test: /.js$/,
      loader: ['babel-loader?cacheDirectory=true'],
      include: [resolve('src')], // src是项目开发的目录
      exclude: [path.resolve('../../node_modules')] // 不需要编译node_modules下的js 
    },
    // ... 其他loader
  ]
}

为了发挥多核CPU电脑的功能,利用HappyPack将任务分发给多个子进程并发执行

alias: { util$: resolve}

import Test1 from 'util'; // 精确匹配,所以 src/util/add.js 被解析和导入import Test2 from 'util/dep1.js'; // 精确匹配,触发普通解析 util/dep1.js
  • 使用CommonsChunkPlugin提取公用模块
const path = require;const HappyPack = require;// 共享5个进程池const happyThreadPool = HappyPack.ThreadPool;module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve, }, module: { // noParse: [/react.production.min.js$/], rules: [{ test: /.js$/, // 和下面插件id一直,happypack才可以找到 use: ['happypack/loader?id=babel'], include: path.resolve }] }, plugins: [ // 插件可以实例化多个 new HappyPack({ // 与上面对应 id: 'babel', // 实际要使用的loader loaders: ['babel-loader?cacheDirectory'], // 默认开启进程数 threads: 3, // 是否允许happyPack打印日志 verbose: true, // 共享进程数,如果你使用超过一个happyplugin,官方建议共享进程池 threadPool: happyThreadPool }) ],};

2.3resolve.extensions

我们经常会有这种场景:在a.vue组件里引入了a.js或者比如c.vue,在b.vue组件里也引入了a.js或者c.vue。这样,打包了之后将会把引入的模块重复打包。而CommonsChuncksPlugin就是把这样重复打包的模块给抽取出来单独打包的插件。这个能够显著降低最后打包的体积,也能提升一些打包速度。

多进程压缩文件

当引入模块时不带文件后缀 webpack会根据此配置自动解析确定的文件后缀

在webpack.base.conf.js里的plugins可以加上这段:

使用ParallelUglifyPlugin多进程同时压缩文件

后缀列表尽可能小 频率最高的往前放 导出语句尽可能带上后缀

plugins: [
  new webpack.optimize.CommonsChunkPlugin({
    async: 'shared-module',
    minChunks: (module, count) => (
      count >= 2    // 当一个模块被重复引用2次或以上的时候单独打包起来。 
    )
  }),
  //...
]

ParallelUglifyPlugin是在UglifyJS基础上,增加了多进出处理的能力,加快了压缩速度

resolve: { extensions: ['.js', '.vue']}
  • 使用DllPlugin和DllReferencePlugin预编译
import ParallelUglifyPlugin from 'webpack-parallel-uglify-plugin'; module.exports = { plugins: [ new ParallelUglifyPlugin({ test, include, exclude, cacheDir, workerCount, sourceMap, uglifyJS: { }, uglifyES: { } }), ],};

3.module.noParse

这个也是一个大杀器。将一些全局都要用到的依赖抽离出来,预编译一遍,然后引入项目中,作为依赖项。而webpack在正式编译的时候就可以放过他们了。能够很明显地提升webpack的构建速度。类似于Windows的dll文件的设计理念。dll资源能够有效的解决资源循环依赖的问题。能够大大减少项目里重复依赖的问题。

减少监听文件

用了noParse的模块将不会被loaders解析,所以当我们使用的库如果太大,并且其中不包含import require、define的调用,我们就可以使用这项配置来提升性能, 让 Webpack 忽略对部分没采用模块化的文件的递归解析处理。

在webpack.base.conf.js所在的文件夹里建立一个webpack.dll.conf.js,我们将一些常用的依赖打包成dll。

减少监听文件

// 忽略对jquery lodash的进行递归解析module: { // noParse: /jquery|lodash/ // 从 webpack 3.0.0 开始 noParse: function { return /jquery|lodash/.test }}

首先配置一下DllPlugin的资源列表。

当我们使用webpack的watch功能进行文件监听时,更好的方式是控制监听目录,如下,排除node_modules减少对该目录监听,减少编译所需要循环的文件,提高检查速度

4.HappyPack

const path = require('path');
const webpack = require('webpack');
module.exports = {
  entry: {
    vendor: ['vue/dist/vue.esm.js','vue-router','axios','vuex'] // 需要打包起来的依赖
  },
  output: {
    path: path.join(__dirname, '../../public/js'), // 输出的路径
    filename: '[name].dll.js', // 输出的文件,将会根据entry命名为vendor.dll.js
    library: '[name]_library' // 暴露出的全局变量名
  },
  plugins: [
    new webpack.DllPlugin({
      path: path.join(__dirname, '../../public/js/', '[name]-mainfest.json'), // 描述依赖对应关系的json文件
      name: '[name]_library', 
      context: __dirname // 执行的上下文环境,对之后DllReferencePlugin有用
    }),
    new webpack.optimize.UglifyJsPlugin({ // uglifjs压缩
      compress: {
        warnings: false
      }
    })
  ]
}
module.export = { watchOptions: { ignored: /node_modules/ }}

HappyPack是让webpack对loader的执行过程,从单一进程形式扩展为多进程模式,也就是将任务分解给多个子进程去并发的执行,子进程处理完后再把结果发送给主进程。从而加速代码构建 与 DLL动态链接库结合来使用更佳。

为了方便之后构建,可以在package.json里加上这么一句scripts:

其他没那么重要的优化

npm i happypack@next -D
scripts: {
  //... 其他scripts
  "build:dll": "webpack --config ./webpack/build/webpack.dll.conf.js" // 填写你项目中webpack.dll.conf.js的路径
}

更精准的mainFields

webpack.config.js

然后执行一下npm run build:dll,就可以在输出的目录里输出一个vendor.dll.js和vendor-mainfest.json两个文件。

默认的这个值查找方式见官网点击此处

const HappyPack = require;const os = require; // node 提供的系统操作模块 // 根据我的系统的内核数量 指定线程池个数 也可以其他数量const happyThreadPool = HappyPack.ThreadPool.lenght})module: { rules: [ { test: /.js$/, use: 'happypack/loader?id=babel', exclude: /node_modules/, include: path.resolve } ]},plugins: [ new HappyPack({ // 基础参数设置 id: 'babel', // 上面loader?后面指定的id loaders: ['babel-loader?cacheDirectory'], // 实际匹配处理的loader threadPool: happyThreadPool, // cache: true // 已被弃用 verbose: true });]

之后打开webpack.base.conf.js。在plugins一项里加上DllReferencePlugin。这个plugin是用于引入上一层里生成的json的。

看了下react和lodash,只有一个main,目前来看使用es6看来还不普遍,所以这个值目前可能不太重要

happypack提供的loader,是对文件实际匹配的处理loader。这里happypack提供的loader与plugin的衔接匹配,则是通过id=happypack来完成。

module.exports = {
  //... 其他配置

  plugins: [
    // ... 其他插件
    new webpack.DllReferencePlugin({
      context: __dirname,
      manifest: require('../../public/js/vendor-mainfest.json') // 指向这个json
    })
  ]
}
module.exports = { resolve: { mainFields: ['main'] }}
npm run dev

最后,在项目输出的index.html里,最先引入这个js:

为什么这个不重要,我发现react直接导出的index.js则是根据环境判断使用哪份代码,目测来看并不需要进行循环依赖的处理

5.DLL动态链接库

<script type="text/javascript" src="public/js/vendor.dll.js"></script>

通过依赖,则可以直接使用打包后代码,而不需webpack去循环依赖

在一个动态链接库中可以包含其他模块调用的函数和数据,动态链接库只需被编译一次,在之后的构建过程中被动态链接库包含的模块将不会被重新编译,而是直接使用动态链接库中的代码。

这样,webpack将不会再解析dll里的资源了。构建速度将会有质的提高。

 resolve: { mainFields: ["main"], alias: { 'react': path.resolve(__dirname, './node_modules/react/cjs/react.production.min.js') } }

将web应用依赖的基础模块抽离出来,打包到单独的动态链接库中。一个链接库可以包含多个模块。 当需要导入的模块存在于动态链接库,模块不会再次打包,而是去动态链接库中去获取。 页面依赖的所有动态链接库都需要被加载。

  • 换用happypack多进程构建

不使用inline模式的devServer

5.1 定义DLL配置

webpack的构建毕竟还是单进程的。采用happypack可以改为多进程构建。而对于小文件而言,happypack效果并不明显。而对于babel-loader编译的庞大的js文件群来说,则是一大利器。

默认情况下,应用程序启用内联模式。这意味着一段处理实时重载的脚本被插入到你的包中,并且构建消息将会出现在浏览器控制台。

依赖的两个内置插件:DllPlugin 和 DllReferencePlugin

首先安装:npm install happypack --save-dev或者yarn add happypack

当使用inline模式时,devServer会向每个Chunk中注入一段重载的脚本代码,但是其实一个页面只需要一次,所以当Chunk过多时,可以将inline设置为false

5.1.1 创建一个DLL配置文件webpack_dll.config.js

然后修改webpack.base.conf.js的配置如下:

module.export = { devServer: { inline: false }}
module.exports = { entry: { react: ['react', 'react-dom'] }, output: { filename: '[name].dll.js', // 动态链接库输出的文件名称 path: path.join, // 动态链接库输出路径 libraryTarget: 'var', // 链接库输出方式 默认'var'形式赋给变量 b library: '_dll_[name]_[hash]' // 全局变量名称 导出库将被以var的形式赋给这个全局变量 通过这个变量获取到里面模块 }, plugins: [ new webpack.DllPlugin({ // path 指定manifest文件的输出路径 path: path.join(__dirname, 'dist', '[name].manifest.json'), name: '_dll_[name]_[hash]', // 和library 一致,输出的manifest.json中的name值 }) ]}
const os = require('os');
const HappyPack  = require('happypack');
const happThreadPool = HappyPack.ThreadPool({size: os.cpus().length}); // 采用多进程,进程数由CPU核数决定
//...
module.exports = {
  plugins: [
    // ...
    new HappyPack({
      id: 'js',
      cache: true,
      loaders: ['babel-loader?cacheDirectory=true'],
      threadPool: happThreadPool
    })
  ],
  module: {
    rules: [
      {
        test: /.vue$/,
        loader: 'vue-loader',
        options: {
          loaders: {
            js: 'happypack/loader?id=js' // 将loader换成happypack
          }
        }
      },
      {
        test: /.js$/,
        loader: ['happypack/loader?id=js'], // 将loader换成happypack
        include: [resolve('src')], // src是项目开发的目录
        exclude: [path.resolve('../../node_modules')] // 不需要编译node_modules下的js 
      },
      //...
    ]
  }
}

补充

5.1.2 output.libraryTarget 规定了以哪一种导出你的库 默认以全局变量形式 浏览器支持的形式

提速还是比较明显的。

补充1-cacheDirectory原理

"var" - 以直接变量输出 var Library = xxx "this" - 通过设置this的属性输出 this["Library"] = xxx "commonjs" - 通过设置exports的属性输出 exports["Library"] = xxx "commonjs2" - 通过设置module.exports的属性输出 module.exports = xxx "amd" - 以amd方式输出 "umd" - 结合commonjs2/amd/root

  • css-loader换成0.14.5版本

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

5.1.3 打包生成动态链接库

可以查看这个issue,说是该版本之上的版本会拖慢webpack的构建速度。我自己实验了之后确实能快几秒钟。

补充2-node的默认查找方式

webpack --config webpack_dll.config.js --mode production
  • 换用webpack-uglify-parallel并行压缩代码

查找当前目录下的node_modules目录,看是否有匹配项,如有,命中文件 寻找父目录的下的node_modules,如有,命中文件 按照这个规则一直往父目录搜索直到到根目录下的node_modules

在dist目录下 多出react.dll.js 和 react.manifest.json

webpack自带的uglifyjs插件效果确实不错。只不过由于受限于单线程,所以压缩速度不够高。换成webpack-uglify-parallel这个插件之后能够有效减少压缩的时间。

补充3-动态链接库思想

react.dll.js 动态链接库 里面包含了 react和react-dom的内容 react.manifest.json 描述链接库中的信息

首先安装:npm install webpack-uglify-parallel --save-dev 或者 yarn add webpack-uglify-parallel

大量项目中可以复用的模块只需要被编译一次,在之后的构建过程中被动态链接库包含的模块将不会重新编译,而是直接使用动态链接库中的代码。(注:如果升级依赖的模块版本,需要重新编译动态链接库)

5.2 在主配置文件中使用动态链接库文件

找到webpack.prod.conf.js(由于开发模式不需要进行uglify压缩),将原本的:

补充4-HappyPack原理

// webpack.config.jsconst webpack = require;plugins: [ // 当我们需要使用动态链接库时 首先会找到manifest文件 得到name值记录的全局变量名称 然后找到动态链接库文件 进行加载 new webpack.DllReferencePlugin({ manifest: require('./dist/react.manifest.json') })]
plugins: [
  new webpack.optimize.UglifyJsPlugin({
    compress: {
      warnings: false
    },
    sourceMap: true
  })
  // ... 其他配置
]

webpack构建中,需要大量的loader转换操作,很耗时,由于nodejs是单线程的,如果想更好利用cpu的多核能力,可以开启多个进程,同时对文件进行处理;可以看到在配置文件中,我们每次将文件交给happypack-loader去处理,然后由happypack去调度来执行文件的处理(happypack采用哪个loaders进行处理,是通过id知道的)

5.3 将动态链接库文件加载到页面中

替换为:

补充5-文件监听原理

需要借助两个webpack插件

const UglifyJsparallelPlugin = require('webpack-uglify-parallel');
const os = require('os');
// ... 其他配置
plugins: [
    new UglifyJsparallelPlugin({
        workers: os.cpus().length,
        mangle: true,
        compressor: {
            warnings: false,
            drop_console: true,
            drop_debugger: true
        }
    })        
]

webpack会从入口触发,将所有的依赖项放到一个list里边,然后每次修改文件内容,回去遍历整个list里边的文件,看是否有编辑时间的变化,如果有的话则进行编译

html-webpack-plugin 产出html文件

  • 效果综述

补充6-自动刷新原理

html-webpack-include-assets-plugin 将js css资源添加到html中 扩展html插件的功能

经过以上优化,原本90 s的构建时间,优化到30 秒效果还是很明显的。原本HMR模式初始化需要50 秒,也优化到了20 秒的程度。不过,还是有一个至今我自己还无法解决的问题,那就是HMR模式,rebuild时间会越来越长,直到超过初始化的20 秒。此时只能重新开关一次HMR模式。这一点我还无法找到具体原因是什么。不过,至少生产的构建时间得到了60%的优化,效果还是挺好的。

向要开发的网页中注入代理客户端代码,通过代理客户端去刷新整个页面 将要开发的网页放进一个iframe,通过刷新iframe去看刷新效果

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

来源:

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

配置webpack.config.js

const webpack = require;const HtmlWebpackPlugin = require('html-webpack-plugin');const HtmlIncludeAssetsPlugin = require('html-webpack-include-assets-plugin');pluings: [ new webpack.DllReferencePlugin({ manifest: require('./dist/react.manifest.json') }), new HtmlWebpackPlugin({ template: path.join(__dirname, 'src/index.html') }), new HtmlIncludeAssetsPlugin({ assets: ['./react.dll.js'], // 添加的资源相对html的路径 append: false // false 在其他资源的之前添加 true 在其他资源之后添加 });]

此时react.dll.js和main.js被自动引入到页面中,并且dll文件在main.js之前加载

6.ParallelUglifyPlugin

这个插件可以帮助有很多入口点的项目加快构建速度。把对JS文件的串行压缩变为开启多个子进程并行进行uglify。

cnpm i webpack-parallel-uglify-plugin -D

// webpck.config.jsconst ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');plugins: [ new ParallelUglifyPlugin({ workerCount: 4, uglifyJS: { output: { beautify: false, // 不需要格式化 comments: false // 保留注释 }, compress: { // 压缩 warnings: false, // 删除无用代码时不输出警告 drop_console: true, // 删除console语句 collapse_vars: true, // 内嵌定义了但是只有用到一次的变量 reduce_vars: true // 提取出出现多次但是没有定义成变量去引用的静态值 } } });]

webpack --mode production

7.Tree Shaking

剔除JavaScript中用不上的代码。它依赖静态的ES6模块化语法,例如通过impot和export导入导出

commonJS模块 与 es6模块的区别

commonJS模块:

1.动态加载模块 commonJS 是运行时加载 能够轻松实现懒加载,优化用户体验

2.加载整个模块 commonJS模块中,导出的是整个模块

3.每个模块皆为对象 commonJS模块被视作一个对象

4.值拷贝 commonJS的模块输出和函数的值传递相似,都是值得拷贝

es6模块

1.静态解析 es6模块时 编译时加载 即在解析阶段就确定输出的模块的依赖关系,所以es6模块的import一般写在被引入文件的开头

2.模块不是对象 在es6里,每个模块并不会当做一个对象看待

3.加载的不是整个模块 在es6模块中 一个模块中有好几个export导出

4.模块的引用 es6模块中,导出的并不是模块的值得拷贝,而是这个模块的引用

7.1 保留ES6模块化语法

// .babelrc{ "presets": [ [ "env", { modules: false // 不要编译ES6模块 }, "react", "stage-0" ] ]}

7.2 执行生产编译 默认已开启Tree Shaking

webpack --mode production

什么是Tree Shaking?

有个funs.js 里面有两个函数

// funs.jsexport const sub = () => 'hello webpack!';export const mul = () => 'hello shaking!';

main.js 中依赖funs.js

// main.jsimport {sub} from './funs.js'sub();

在main.js只使用了里面的 sub函数 默认情况下也会将funs.js里面其他没有的函数也打包进来, 如果开启tree shaking 生产编译时

webpack --mode production //此时funs.js中没有被用到的代码并没打包进来 而被剔除出去了

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

本文由澳门新浦京娱乐场网站发布于服务器,转载请注明出处:Vuejs项目的Webpack2构建优化,0打包优化策略整理小