前端工程化项目搭建六: 优化

54 阅读3分钟

这是我参与「掘金日新计划 · 8 月更文挑战」的第5天

前言

前面几篇文章,简单介绍了html,js,css,一些常用loader的加载和一些常用babel的加载,这一篇是项目搭建的最后内容,webpack的优化

babel的优化

babel的转译速度

在 options 里配置cacheDirectory 为true,开启缓存babel-loader执行的结果。

module.exports = {
    module: {
        rules: [
            {
                test: /\.js$/,
                include: path.resolve(__driname, 'src'),
                use: {
                    loader: 'babel-loader',
                    options: {
                        cacheDirectory: true
                    }
                }
            }
        ]
    }
}

抽离公共转换代码

babel在每个文件都插入了辅助代码,使代码体积过大。babel对一些公共方法使用了非常小的辅助代码,比如_extend,默认情况下会被添加到每一个需要它的文件中
为了抽离这些公共转换代码,需要安装插件@babel/plugin-transform-runtime,其功能是可重复使用babel注入的代码以节省代码量
需要将其作为开发依赖,同时还需要安装@babel/runtime作为生产依赖

{
    "presets": [
        [
            "@babel/env",
        ]
    ],
    "plugins": [
        "@babel/plugin-transform-runtime"
    ]
}

webpack优化

简单介绍一些优化点:打包编译时间和代码体积,特别是使用了大量的loader和第三方库时,那简直了。

mode模式选择

mode选择会影响到实际的打包体积,如:production模式下会自动开启tree-shaking(删除js无用代码)、TerserPlugin(压缩js代码)等等。根据不同的环境去选择合适的mode,将有利于优化打包体积(其实对构建速度也有一定的影响)。

plugin/loader合理使用

尽可能少的使用plugins/loader,或者使用test,exclude,include配置项来缩减其使用范围

rules: [
      {
        test: /\.css$/,
        include: path.resolve(__dirname, "./src"),
        use: ["style-loader", "css-loader"],
      },
      {
        test: /\.js$/,
        include: path.resolve(__dirname, "./src"),
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
        },
      },
    ],

同时,也根据mode去使用相匹配的loader,比如开发环境和生产环境下,生产环境下需要压缩代码,而在开发环境下不需要

代码压缩

之前的文章中,对html,js,css的压缩,也能加快编译速度和减少打包代码体积,production模式下默认是开启了js代码压缩的

代码分割code splitting

如果多个页面引用了一些公共模块,那么可以讲这些公共模块抽离出来,单独打包。公共代码只需要下载一次就缓存起来,避免重复下载

 optimization: {
   splitChunks: {
      chunks: "all", // 所有的 chunks 代码公共的部分分离出来成为⼀个单独的⽂件
    },
 },

优化resolve.modules

这个属性告诉webpack解析模块时应该搜索的目录,绝对路径和相对路径都能使用。使用绝对路径之后,将只在给定目录中搜索,从而减少模块的搜索层级

resolve: { 
    modules: [ 
        path.resolve(__dirname, 'node_modules'), // 指定当前目录下的 node_modules 优先查找 
        'node_modules', // 将默认写法放在后面 
    ]
}

使用别名resolve.alias

多线程构建

由于运行在Node.js之上的webpack是单线程模型的,所以webpack需要处理的事情需要一件一件的做,不能多件事一起做。
如果webpack能同一时间处理多个任务,发挥多核CPU电脑的威力,那么对其打包速度的提升肯定是有很大的作用的。

Thread-loader

使用Thread-loader,webpack官方提供的插件。用法上只有一个单一的loader,比较简单,但兼容性较差。
需要安装依赖:thread-loader,其次要将其配置在loader首位确保其优先执行:

module.exports = {
  module: {
    rules: [{
      test: /.js$/,
      use: [
        'thread-loader',
        'babel-loader',
        'eslint-loader'
      ],
    }, ],
  },
};

其缺点如下:

  • Loader 中不能调用 emitAsset 等接口,这会导致 style-loader 这一类 Loader 无法正常工作,解决方案是将这类组件放置在 thread-loader 之前,如 ['style-loader', 'thread-loader', 'css-loader']
  • Loader 中不能获取 compilationcompiler 等实例对象,也无法获取 Webpack 配置

使用HappyPack

HappyPack 是一个使用多进程方式运行文件加载器 —— Loader 序列,从而提升构建性能的 Webpack 组件库,算得上 Webpack 社区内最先流行的并发方案,不过作者已经明确表示不会继续维护
安装依赖:happypack

const HappyPack = require('happypack');

module.exports = {
    // ...
    module: {
        rules: [{
            test: /.js$/,
            use: 'happypack/loader',
            // use: [
            //  {
            //      loader: 'babel-loader',
            //      options: {
            //          presets: ['@babel/preset-env']
            //      }
            //  },
            //  'eslint-loader'
            // ]
        }]
    },
    plugins: [
        new HappyPack({
            loaders: [
                {
                    loader: 'babel-loader',
                    option: {
                        presets: ['@babel/preset-env']
                    }
                },
                'eslint-loader'
            ]
        })
    ]
};

为不同的文件配置多个相应的加载器数组:

const HappyPack = require('happypack');

module.exports = {
  // ...
  module: {
    rules: [{
        test: /.js?$/,
        use: 'happypack/loader?id=js'
      },
      {
        test: /.less$/,
        use: 'happypack/loader?id=styles'
      },
    ]
  },
  plugins: [
    new HappyPack({
      id: 'js',
      loaders: ['babel-loader', 'eslint-loader']
    }),
    new HappyPack({
      id: 'styles',
      loaders: ['style-loader', 'css-loader', 'less-loader']
    })
  ]
};

jsless 资源都使用 happypack/loader 作为唯一 loader,并分别赋予 id = 'js' | 'styles' 参数;其次,示例中创建了两个 HappyPack 插件实例并分别配置了用于处理 js 与 css 的 loaders 数组,happypack/loaderHappyPack 实例之间通过 id 值关联起来,以此实现多资源配置
默认情况下,HappyPack 插件实例各自管理自身所消费的进程,导致整体需要维护一个数量庞大的进程池,反而带来新的性能损耗。
为此,HappyPack 提供了一套简单易用的共享进程池功能,使用上只需创建 HappyPack.ThreadPool 实例并通过 size 参数限定进程总量,之后将该实例配置到各个 HappyPack 插件的 threadPool 属性上即可

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

module.exports = {
  // ...
  plugins: [
    new HappyPack({
      id: 'js',
      threadPool: happyThreadPool,
      loaders: ['babel-loader', 'eslint-loader']
    }),
    new HappyPack({
      id: 'styles',
      threadPool: happyThreadPool,
      loaders: ['style-loader', 'css-loader', 'less-loader']
    })
  ]
};

sourceMap的合理使用

source map 即是一种映射文件,我们打包出来的 js 文件里面的代码都是各种 laoder 转换出来的代码,不是源码(原来代码的样子),source map 的作用就是将源码映射出来,就是解决控制台打印出来的代码应对应源码哪里,方便程序员进行调试。
在development模式里,建议使用eval-source-map和eval-cheap-module-source-map,前者可以追踪到源码的行列,后者只能追踪到行,当然后者的构建速度会块一点。
在production模式里,使用nosources-source-map,用这个进行调试程序是看不到任何的源码信息,只能看到信息来源与源码的第几行。

module.exports = merge(common, {
    ...
    devtool: 'nosources-source-map',
    ...
}

按需加载路由

vue或者react项目中,首次加载时,由于存在很多的路由,这就导致了首屏加载时间的过长,一定程度上影响到了用户体验。实现路由的按需加载方式:

const B = () => import('@/pages/business/b.vue')

按需引入第三方库

比如在使用element-ui或者antd这种UI库时,只需要使用库中的某些组件,却要引入全部的组件及样式文件,这会造成打包体积巨大,这就需要在文件中去单独引用某个插件来解决了,UI库中也有提到按需引入的方式,可以在实际项目中使用。

静态图片资源压缩

总结

参考了很多资料,所以大家可能会发现很多相似的内容,因为有部分我懒得敲就直接复制了,文末附上了原文链接。至此,创建项目的基本结构便是搭建完成了。

参考资料:

babel官网

webpack5的使用

webpack

17项关于webpack的性能优化

前端性能优化篇一:webpack性能优化

前端性能优化篇二:图片的合理使用