Vue项目Webpack优化实践,构建效率提升50%

       公司的前端项目使用Vue框架,Vue框架使用Webpack进行构建,随着项目不断迭代,项目逐渐变得庞大,然而项目的构建速度随之变得缓慢,因而对Webpack构建进行优化变得刻不容缓。通过不断的摸索和实践,经过如下方法优化后,项目的构建速度提升了50%。现将相关优化方法进行总结分享。javascript

github地址为:github.com/fengshi123/…,若是喜欢或者有所启发,请帮忙给个 Star ~,对做者也是一种鼓励。
前端

一、缩小文件的搜索范围

1.一、优化Loader配置

       因为Loader对文件的转换操做很耗时,因此须要让尽量少的文件被Loader处理。咱们能够经过如下3方面优化Loader配置:(1)优化正则匹配(2)经过cacheDirectory选项开启缓存(3)经过include、exclude来减小被处理的文件。实践以下:

项目原配置:

{
  test: /\.js$/,
  loader: 'babel-loader',
  include: [resolve('src'), resolve('test')]
},复制代码

优化后配置:

{
  // 一、若是项目源码中只有js文件,就不要写成/\.jsx?$/,以提高正则表达式的性能
  test: /\.js$/,
  // 二、babel-loader支持缓存转换出的结果,经过cacheDirectory选项开启
  loader: 'babel-loader?cacheDirectory',
  // 三、只对项目根目录下的src 目录中的文件采用 babel-loader
  include: [resolve('src')]
},
复制代码

1.二、优化resolve.modules配置

       resolve.modules 用于配置Webpack去哪些目录下寻找第三方模块。resolve.modules的默认值是[node modules],含义是先去当前目录的/node modules目录下去找咱们想找的模块,若是没找到,就去上一级目录../node modules中找,再没有就去../ .. /node modules中找,以此类推,这和Node.js的模块寻找机制很类似。当安装的第三方模块都放在项目根目录的./node modules目录下时,就没有必要按照默认的方式去一层层地寻找,能够指明存放第三方模块的绝对路径,以减小寻找。vue

优化后配置:

resolve: {
// 使用绝对路径指明第三方模块存放的位置,以减小搜索步骤
modules: [path.resolve(__dirname,'node_modules')]
},
复制代码

1.三、优化resolve.alias配置

       resolve.alias配置项经过别名来将原导入路径映射成一个新的导入路径。java

如项目中的配置使用:

alias: {
  '@': resolve('src'),
},
// 经过以上的配置,引用src底下的common.js文件,就能够直接这么写
import common from '@/common.js';
复制代码

1.四、优化resolve.extensions配置 

       在导入语句没带文件后缀时,Webpack 会在自动带上后缀后去尝试询问文件是否存在。默认是:extensions :[‘. js ‘,’. json ’] 。也就是说,当遇到require ( '. /data ’)这样的导入语句时,Webpack会先去寻找./data .js 文件,若是该文件不存在,就去寻找./data.json 文件,若是仍是找不到就报错。若是这个列表越长,或者正确的后缀越日后,就会形成尝试的次数越多,因此 resolve .extensions 的配置也会影响到构建的性能。 node

 优化措施:

 • 后缀尝试列表要尽量小,不要将项目中不可能存在的状况写到后缀尝试列表中。jquery

 • 频率出现最高的文件后缀要优先放在最前面,以作到尽快退出寻找过程。webpack

 • 在源码中写导入语句时,要尽量带上后缀,从而能够避免寻找过程。例如在肯定的状况下将 require(’. /data ’)写成require(’. /data.json ’),能够结合enforceExtension 和 enforceModuleExtension开启使用来强制开发者遵照这条优化git

1.五、优化resolve.noParse配置

       noParse配置项可让Webpack忽略对部分没采用模块化的文件的递归解析和处理,这 样作的好处是能提升构建性能。缘由是一些库如jQuery、ChartJS 庞大又没有采用模块化标准,让Webpack去解析这些文件既耗时又没有意义。 noParse是可选的配置项,类型须要是RegExp 、[RegExp]、function中的一种。例如,若想要忽略jQuery 、ChartJS ,则优化配置以下:
github

// 使用正则表达式 
noParse: /jquerylchartjs/ 
// 使用函数,从 Webpack3.0.0开始支持 
noParse: (content)=> { 
// 返回truefalse 
return /jquery|chartjs/.test(content); 
}
复制代码

二、减小冗余代码

        babel-plugin-transform-runtime 是Babel官方提供的一个插件,做用是减小冗余的代码 。 Babel在将ES6代码转换成ES5代码时,一般须要一些由ES5编写的辅助函数来完成新语法的实现,例如在转换 class extent 语法时会在转换后的 ES5 代码里注入 extent 辅助函数用于实现继承。babel-plugin-transform-runtime会将相关辅助函数进行替换成导入语句,从而减少babel编译出来的代码的文件大小。
web

三、使用HappyPack多进程解析和处理文件

       因为有大量文件须要解析和处理,因此构建是文件读写和计算密集型的操做,特别是当文件数量变多后,Webpack构建慢的问题会显得更为严重。运行在 Node.之上的Webpack是单线程模型的,也就是说Webpack须要一个一个地处理任务,不能同时处理多个任务。Happy Pack ( https://github.com/amireh/happypack )就能让Webpack作到这一点,它将任务分解给多个子进程去并发执行,子进程处理完后再将结果发送给主进程。

项目中HappyPack使用配置:

(1)HappyPack插件安装:
    $ npm i -D happypack
(2)webpack.base.conf.js 文件对module.rules进行配置
    module: {
     rules: [
      {
        test: /\.js$/,
        // 将对.js 文件的处理转交给 id 为 babel 的HappyPack实例
          use:['happypack/loader?id=babel'],
          include: [resolve('src'), resolve('test'),   
            resolve('node_modules/webpack-dev-server/client')],
        // 排除第三方插件
          exclude:path.resolve(__dirname,'node_modules'),
        },
        {
          test: /\.vue$/,
          use: ['happypack/loader?id=vue'],
        },
      ]
    },
(3)webpack.prod.conf.js 文件进行配置    const HappyPack = require('happypack');
    // 构造出共享进程池,在进程池中包含5个子进程
    const HappyPackThreadPool = HappyPack.ThreadPool({size:5});
    plugins: [
       new HappyPack({
         // 用惟一的标识符id,来表明当前的HappyPack是用来处理一类特定的文件
         id:'vue',
         loaders:[
           {
             loader:'vue-loader',
             options: vueLoaderConfig
           }
         ],
         threadPool: HappyPackThreadPool,
       }),

       new HappyPack({
         // 用惟一的标识符id,来表明当前的HappyPack是用来处理一类特定的文件
         id:'babel',
         // 如何处理.js文件,用法和Loader配置中同样
         loaders:['babel-loader?cacheDirectory'],
         threadPool: HappyPackThreadPool,
       }),
    ]
复制代码

 四、使用ParallelUglifyPlugin多进程压缩代码文件

       因为压缩JavaScript 代码时,须要先将代码解析成用 Object 抽象表示的 AST 语法树,再去应用各类规则分析和处理AST ,因此致使这个过程的计算量巨大,耗时很是多。当Webpack有多个JavaScript 文件须要输出和压缩时,本来会使用UglifyJS去一个一个压缩再输出,可是ParallelUglifyPlugin会开启多个子进程,将对多个文件的压缩工做分配给多个子进程去完成,每一个子进程其实仍是经过UglifyJS去压缩代码,可是变成了并行执行。因此 ParallelUglify Plugin能更快地完成对多个文件的压缩工做。

 项目中ParallelUglifyPlugin使用配置:

(1)ParallelUglifyPlugin插件安装:
     $ npm i -D webpack-parallel-uglify-plugin
(2)webpack.prod.conf.js 文件进行配置
    const ParallelUglifyPlugin =require('webpack-parallel-uglify-plugin');
    plugins: [
    new ParallelUglifyPlugin({
      cacheDir: '.cache/',
      uglifyJs:{
        compress: {
          warnings: false
        },
        sourceMap: true
      }
     }),
    ]
复制代码

五、使用自动刷新 

       借助自动化的手段,在监听到本地源码文件发生变化时,自动从新构建出可运行的代码后再控制浏览器刷新。Webpack将这些功能都内置了,而且提供了多种方案供咱们选择。

 项目中自动刷新的配置:

devServer: {
  watchOptions: {
    // 不监听的文件或文件夹,支持正则匹配
    ignored: /node_modules/,
    // 监听到变化后等300ms再去执行动做
    aggregateTimeout: 300,
    // 默认每秒询问1000次
    poll: 1000
  }
},
复制代码

相关优化措施: 

(1)配置忽略一些不监听的一些文件,如:node_modules。 

(2)watchOptions.aggregateTirneout 的值越大性能越好,由于这能下降从新构建的频率。

(3) watchOptions.poll 的值越小越好,由于这能下降检查的频率

六、开启模块热替换 

       DevServer 还支持一种叫作模块热替换( Hot Module Replacement )的技术可在不刷新整个网页的状况下作到超灵敏实时预览。原理是在一个源码发生变化时,只需从新编译发生变化的模块,再用新输出的模块替换掉浏览器中对应的老模块 。模块热替换技术在很大程度上提高了开发效率和体验 。 

项目中模块热替换的配置:

devServer: {
  hot: true,
},
plugins: [
  new webpack.HotModuleReplacementPlugin(),
// 显示被替换模块的名称
  new webpack.NamedModulesPlugin(), // HMR shows correct file names
]
复制代码

七、提取公共代码 

        若是每一个页面的代码都将这些公共的部分包含进去,则会形成如下问题 : 

 • 相同的资源被重复加载,浪费用户的流量和服务器的成本。

 • 每一个页面须要加载的资源太大,致使网页首屏加载缓慢,影响用户体验。 

       若是将多个页面的公共代码抽离成单独的文件,就能优化以上问题 。Webpack内置了专门用于提取多个Chunk中的公共部分的插件CommonsChunkPlugin。 

项目中CommonsChunkPlugin的配置:

// 全部在 package.json 里面依赖的包,都会被打包进 vendor.js 这个文件中。
new webpack.optimize.CommonsChunkPlugin({
  name: 'vendor',
  minChunks: function(module, count) {
    return (
      module.resource &&
      /\.js$/.test(module.resource) &&
      module.resource.indexOf(
        path.join(__dirname, '../node_modules')
      ) === 0
    );
  }
}),
// 抽取出代码模块的映射关系
new webpack.optimize.CommonsChunkPlugin({
  name: 'manifest',
  chunks: ['vendor']
}),
复制代码

八、按需加载代码 

       经过vue写的单页应用时,可能会有不少的路由引入。当打包构建的时候,javascript包会变得很是大,影响加载。若是咱们能把不一样路由对应的组件分割成不一样的代码块,而后当路由被访问的时候才加载对应的组件,这样就更加高效了。这样会大大提升首屏显示的速度,可是可能其余的页面的速度就会降下来。 

项目中路由按需加载(懒加载)的配置:

const Foo = () => import('./Foo.vue')
const router = new VueRouter({
  routes: [
    { path: '/foo', component: Foo }
  ]
})复制代码

九、优化SourceMap 

       咱们在项目进行打包后,会将开发中的多个文件代码打包到一个文件中,而且通过压缩,去掉多余的空格,且babel编译化后,最终会用于线上环境,那么这样处理后的代码和源代码会有很大的差异,当有bug的时候,咱们只能定位到压缩处理后的代码位置,没法定位到开发环境中的代码,对于开发很差调式,所以sourceMap出现了,它就是为了解决很差调式代码问题的。

 SourceMap的可选值以下:


开发环境推荐: cheap-module-eval-source-map 

生产环境推荐: cheap-module-source-map 

缘由以下: 

1. 源代码中的列信息是没有任何做用,所以咱们打包后的文件不但愿包含列相关信息,只有行信息能创建打包先后的依赖关系。所以不论是开发环境或生产环境,咱们都但愿添加cheap的基本类型来忽略打包先后的列信息。 

2. 不论是开发环境仍是正式环境,咱们都但愿能定位到bug的源代码具体的位置,好比说某个vue文件报错了,咱们但愿能定位到具体的vue文件,所以咱们也须要module配置。 

3. 咱们须要生成map文件的形式,所以咱们须要增长source-map属性。 

4. 咱们介绍了eval打包代码的时候,知道eval打包后的速度很是快,由于它不生成map文件,可是能够对eval组合使用 eval-source-map使用会将map文件以DataURL的形式存在打包后的js文件中。在正式环境中不要使用 eval-source-map, 由于它会增长文件的大小,可是在开发环境中,能够试用下,由于他们打包的速度很快。

十、构建结果输出分析 

       Webpack输出的代码可读性很是差并且文件很是大,让咱们很是头疼。为了更简单、直观地分析输出结果,社区中出现了许多可视化分析工具。这些工具以图形的方式将结果更直观地展现出来,让咱们快速了解问题所在。接下来说解vue项目中用到的分析工具:webpack-bundle-analyzer 。

项目中在webpack.prod.conf.js进行配置:

if (config.build.bundleAnalyzerReport) {
  var BundleAnalyzerPlugin =   require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
  webpackConfig.plugins.push(new BundleAnalyzerPlugin());
}
执行 $ npm run build --report 后生成分析报告以下:复制代码


github地址为:github.com/fengshi123/…,若是喜欢或者有所启发,请帮忙给个 Star ~,对做者也是一种鼓励。

相关文章
相关标签/搜索