阅读 3581
让webpack5再飞一会儿,夯实webpack4吧(优化篇)

让webpack5再飞一会儿,夯实webpack4吧(优化篇)

海阔凭鱼跃,天高任鸟飞。Hey 你好!我是秦爱德。😄

上篇文章,介绍了webpack的入门基础配置(传送门🚀🚀🚀)。那现在我们就来讲一讲如何通过优化配置项来提高我们的项目构建性能吧。

应该优化那些内容?

从项目实战角度出发,我们更关心的还是从项目开发到发布上线。所以优化项可以分为以下几个维度考虑:

  • 开发环境
    • 优化代码运行的性能
    • 优化代码调试
  • 生产环境优化
    • 优化代码打包构建速度
    • 优化代码运行的性能

优化代码运行的性能(开发环境)

开发服务器devServer

在开发环境中,主要是为了代码能够友好的运行起来并且方便我们开发调试,所以我们需要借助devServer来启动一个本地服务,让代码run起来。

devServer用于项目自动化(自动编译,自动打开浏览器,自动刷新浏览器等等)。只会在内存中编译打包,不会对外输出build包,而是存在于内存中,关闭后会自动删除。

启动开发服务器devServer需要下载包

npm i -D webpack-dev-server
复制代码

写入基本服务器配置项

devServer: {
  // 项目构建后路径
  contentBase: resolve(__dirname, 'dist'),
  // 端口号
  port: 3000,
  // 自动打开浏览器
  open: true,
},
复制代码

配置完毕后即可启动项目了,我的webpack是项目维度安装的,所以使用npx运行

npx webpack 会输出打包结果在dist文件夹
npx webpack-dev-server 只会在内存中编译打包,没有输出
复制代码

HMR(模块热替换)

HMR: hot module replacement 热模块替换 / 模块热替换,感应文件变化及时响应到页面,解放F5手动刷新。

只需要在 devServer 中设置 hot 为 true,就会自动开启HMR功能

devServer: {
  // 开启HMR功能
  hot: true
}
复制代码

HMR支持文件:

  • 样式文件:可以使用HMR功能,style-loader 内部默认实现了热模块替换功能

  • js 文件:默认不能使用HMR功能

    修改一个 js 模块所有 js 模块都会刷新,显然这样做会导致热替换效率低下。我们需要修改入口js文件代码,让一个模块发生变化,只会重新构建这一个模块,而不是所有。

// print.js 为入口js之外的js
if (module.hot) {
  // 一旦 module.hot 为true,说明开启了HMR功能。 --> 让HMR功能代码生效
  module.hot.accept('./print.js', function() {
    // 方法会监听 print.js 文件的变化,一旦发生变化,只有这个模块会重新打包构建,其他模块不会。
    // 会执行后面的回调函数
    print();
  });
}
复制代码
  • html 文件:默认不能使用HMR功能

注意:使用 HMR 会导致html文件不能热替换,可修改 entry 入口,将 html 文件引入

entry: ['./src/index.js', './src/index.html']
复制代码

优化代码调试(开发环境)

提取 css 成单独文件

由于css-loader将css文件整合到js文件中,会造成如下影响:

1:js文件体积会很大

2:需要先加载js再动态创建style标签,样式渲染速度就慢

解决办法:用MiniCssExtractPlugin.loader替代style-loader,提取js中的css成单独文件

需要引入mini-css-extract-plugin

npm i -D mini-css-extract-plugin
复制代码
// webpack.config.js

const MiniCssExtractPlugin = require('mini-css-extract-plugin')

{
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
            options: {
              publicPath: '../'
            }
          },
        ],
      },
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'css/main.css'
    }),
  ]
}
复制代码

css兼容性处理

在开发中,我们在写一些高级样式时在老一代浏览器中并不支持,这时候我们需要引入一些loader来帮我们解决这个问题。

需要引入postcss-loader postcss-preset-env

npm i -D postcss-loader postcss-preset-env
复制代码
// webpack.config.js
{
  loader: 'postcss-loader',
  options: {
    ident: 'postcss',
    plugins: () => [
      require('postcss-preset-env')(),
    ],
  },
},
复制代码

引入loader后还需在package.json中定义browserslist

// package.json

"browserslist": {
  // 开发环境
  "development": [ // 这里可根据具体业务场景,匹配不同浏览器
    "last 1 chrome version",
    "last 1 firefox version",
    "last 1 safari version"
  ],
  // 生产环境
  "production": [ // 满足绝大多数浏览器的兼容
    ">0.2%",
    "not dead",
    "not op_mini all"
  ]
},
复制代码

js兼容性处理

为了兼容老一代浏览器,同样需要将js做一下兼容。

需要引入babel-loader @babel/preset-env core-js @babel/core

{
  test: /\.js$/,
  exclude: /node_modules/,
  use:[
    {
      loader: 'babel-loader',
      options: {
        presets: [
          [
            '@babel/preset-env',
            {
              //按需加载
              useBuiltIns: 'usage',
              // 指定core-js版本
              corejs:{
                version: 3
              },
              // 指定兼容到什么版本的浏览器
              targets:{
                chrome: '60',
                firefox: '60',
                ie: '9',
                safari: '10',
                edge: '17'
              }
            }
          ]
        ],
        cacheDirectory:true
      }
    }
  ],
},
复制代码

source-map

source-map:一种提供源代码到构建后代码的映射的技术 (如果构建后代码出错了,通过映射可以追踪源代码错误)

参数:[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map

 devtool: 'eval-source-map'
复制代码

以上所列的参数是可以任意组合的,每个组合各有不同。常见的组合可以有7种

这么多种模式,那什么组合才是比较合理的呢?

最终得出最好的两种方案

1:eval-source-map(完整度高,速度快)

2:eval-cheap-module-souce-map(错误提示忽略列但是包含其他信息,速度快)

配置eslint代码风格检查

团队开发,规范自然是必不可少的。个人觉得eslint太过于严谨,更推荐团队根据自身情况书写一套合适的eslint规则。

这里引入的是目前流行的一套js风格airbnb 传送门🚀🚀🚀

需要引入eslint-loader eslint

// webpack.config.js

{
  test: /\.js$/,
  exclude: /node_modules/, // 忽略node_modules
  loader: 'eslint-loader',
  options: {
    fix: true, // 自动修复
  },
}
复制代码

引入loader之后,还需要在package.jsoneslintConfig中写入配置

// package.json

"eslintConfig": {
  "extends": "airbnb-base", // 继承airbnb的风格规范
  "env": {
    "browser": true // 可以使用浏览器中的全局变量(使用window不会报错)
  }
}
复制代码

优化代码打包构建速度(生产环境)

oneOf

oneOf:匹配到 loader 后就不再向后进行匹配,优化生产环境的打包构建速度

babel 缓存

babel 缓存:将 babel 处理后的资源缓存起来,让第二次打包构建时只更新改变内容,其他不变的内容走缓存。故此提高构建速度

// 开启babel缓存
cacheDirectory:true
复制代码

这里存在一个问题:当文件名没有发生变化的时候,同名文件都是走缓存。会导致修改内容与实际展示内容不一致。

解决办法:使用hash命名,通过更换文件名来判断哪些文件需要更新。

hash值可分为(hashchunkhashcontenthash)这里使用contenthash较为合理

hash:每次 wepack 打包时会生成一个唯一的 hash 值。

chunkhash:根据 模块 生成的 hash 值。隶属于同一个 模块hash 值一样

contenthash:根据文件的内容生成 hash 值,可以保证不同文件 hash 值唯一性

多进程打包

thread-loader会对其后面的loader开启多进程打包。

需要引入thread-loader

启动thread-loader的开销比较昂贵,一般项目不建议使用

npm i -D thread-loader
复制代码
{
  loader: 'thread-loader',
  options: {
    workers: 2 // 开启2个进程
  }
},
复制代码

externals

externals 让某些第三方库不打包

externals: {
  jquery: 'jQuery'
}
复制代码

优化代码运行的性能(生产环境)

压缩文件

  • 压缩样式文件

    需要引入optimize-css-assets-webpack-plugin

      npm i -D optimize-css-assets-webpack-plugin
    复制代码
      const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
      plugins: [
        new OptimizeCssAssetsWebpackPlugin(),
      ]
    复制代码
  • 压缩html

    需要引入html-webpack-plugin

    html-webpack-plugin 会自动将单独打包的样式文件通过link标签引入

      npm i -D html-webpack-plugin
    复制代码
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    plugins: [
      new HtmlWebpackPlugin({
        template: './src/index.html',
        minify: {
          // 移除空格
          collapseWhitespace: true,
          // 移除注释
          removeComments: true
        }
      }),
    ]
    复制代码
  • js代码在生产环境会自动压缩

tree shaking(树摇)

如果把程序想象成一棵树的话,咱们正在使用的有用代码或第三方库代表绿色的树叶。那些废代码,无用代码代表枯萎的枯叶。 tree shaking就像一只无形的大手可劲儿的摇这棵树,把枯叶(无用代码)给摇下来。

tree shaking(树摇)前提

1:必须使用es6模块化
2:开启production环境

在上述二者前提条件下,webpack会自动将我们的无用代码去除掉

可在 package.json 中写入配置项,控制树摇范围

// 不会对css/less文件tree shaking处理
"sideEffects": ["*.css", "*.less"]
复制代码

代码分割

webpack将js代码打包输出到一个build.js文件里面,这对于大型项目来说,无疑是致命的。build.js文件过大,会导致页面加载时间过长、只改动一点代码而要下载整个大文件等等一系列问题。

代码分割 将打包输出的一个大的 bundle.js 文件拆分成多个小文件,这样可以并行加载多个文件,比加载一个文件更快。

实现代码分割有三种方式:

1:多入口拆分(entry 入口使用多个入口文件)

entry: {
  index: './src/js/index.js',
  test: './src/js/test.js'
},
output: {
  filename: 'js/[name].[contenthash:10].js',
  path: resolve(__dirname, 'build')
},
复制代码

2:配置optimization

optimization: {
  splitChunks: {
    chunks: 'all'
  }
},
复制代码

3:import 动态导入语法

import('./test').then(({ a, b }) => {
  // 文件加载成功~
}).catch(() => {
  // 文件加载失败~
});
复制代码

lazy loading(懒加载/预加载)

懒加载:当文件需要使用的时候才加载

预加载:在使用之前,提前加载

可通过import动态导入的方式实现懒加载和预加载

// 将import的内容放在异步回调函数中使用,需要用到的时候再进行加载
// webpackPrefetch: true表示开启预加载
import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test').then(() => {
  ...
});
复制代码

至此关于webpack优化内容差不多就是这些了。这里总结的可能并不全面,不过应对大部分项目还是足够了。一起共勉,好好学习,天天向上吧。

撒花、撒花 🌸🌸🌸🌸🌸🌸🌸🌸

点赞👍再看,已成习惯!该系列持续更新,你们的一键三连就是我持续写作的最大动力,如果对本篇博客有任何意见和建议,欢迎师兄们留言!欢迎来扰!😜😝

我是秦爱德,一个在互联网夹缝求生的程序员!

文章分类
前端
文章标签