likes
comments
collection
share

Webpack入门系列(三)

作者站长头像
站长
· 阅读数 42

本文主要是对之前配置的本地webpack再次进行优化。没有阅读过前面两篇文章的建议先阅读入门系列一和入门系列二

传送门: Webpack入门系列(一)Webpack入门系列(二)

一、Eslint

eslint想必大家很熟悉了,他可以格式化我们的代码,并且还可以在开发过程中对我们的代码进行检查,看是否有使用错的地方,大大减少了线上bug的产生。

首先安装eslint

npm i -D eslint,安装好依赖后我们要在根目录创建一个eslint配置文件名为.eslintrc.js,当然,其他名称也行如.eslintrc.json等,我习惯了用js来命名配置文件。

module.exports = {
  env: {
    browser: true,
    node: true,
    es2021: true,
  },
  // 解析选项
  parserOptions: {
    ecmaVersion: 6, // ES 语法版本
    sourceType: "module", // ES 模块化
    ecmaFeatures: {
      // ES 其他特性
      jsx: true, // 如果是 React 项目,就需要开启 jsx 语法
    },
  },
  // 具体检查规则
  extends: ["eslint:recommended"],
  // 具体检查规则
  rules: {},
};

配置好之后,我们要指定eslint检测的文件目录,这就要用到eslint-webpack-plugin

eslint-webpack-plugin

首先安装npm i -D eslint-webpack-plugin,然后使用插件,指定eslint需要检测的目录

new EslintWebpackPlugin({
      context: path.resolve(__dirname, "./src"),
    })

好了,我们可以build一下看一下效果。

Webpack入门系列(三)

可以看到eslint检测有效,成功检除了3个问题。 但是我们发现有的是引入的字体js代码,eslint也检测了。我们需要配置eslint忽略检测的目录。 可以在根目录下创建.eslintignore文件,配置上要忽略检测的目录,如下

dist
node_modules
public
src/static/*

再一次build,成功减少报错

Webpack入门系列(三)

那对于这eslint报错,我们怎么解决呢?

如果你觉得这个报错是eslint的问题,而不是你的问题,好,你可以在eslint配置文件的rules中将这条校验规则关闭,如下。

Webpack入门系列(三)

cache

在项目越大的时候,eslint的代码检测反而月越慢,这时,我们应该开启缓存,将每一次的eslint结果缓存到本地,然后提升构建速度。开启方法如下

new EslintWebpackPlugin({
      context: path.resolve(__dirname, "./src"),
      cache: true, // 开启缓
      // 缓存文件
       cacheLocation: path.resolve(__dirname, "./node_modules/.cache/.eslintcache"),
    }),

启动一下本地服务,发现生成缓存文件

Webpack入门系列(三)

二、 SourceMap

通常,在开发过程中,如果代码中有报错,我们如何去定位报错位置。我们需要借助一个工具,sourcemap。他可以生成一个map文件,并且将代码位置的映射记录在map文件中,当代码报错时可以精准的定位到报错位置。

但是sourcemap也有缺点,就是会生成多余的map文件,如果sourcemap设置的不合理,还有可能造成项目代码的泄漏,所以,我们得小心配置。

根据webpack官方文档来看,webpack推荐在生产模式下不启动sorucemap,在开发模式下可以选择启用sorucemap,不同的类型,对于项目的启动编译也有影响,如下图

Webpack入门系列(三)

也可以直接前往webpakc官网查看,传送门

我们配置一下devtool

  devtool: "eval-cheap-module-source-map"

三、HotModuleReplacement

对于css代码,如果我们想在改代码的时候就自动更新呢?只需要在devServer中开启热更新即可,如下

   devServer: {
    // 自动打开浏览器
    open: true,
    // 端口
    port: "3000",
    // 地址
    host: "127.0.0.1",
    // 热更新
    hot: true,
    // 静态目录
    static: {
      directory: path.resolve(__dirname, "./dist"),
    },
  },

但是对于js代码,默认是不支持热更新的。 我们可以手动增加热更新js文件

比如,我们新建一个sum方法,如下

const sum = (...args) => {
  return args.reduce((pre, current) => {
    return pre + current;
  }, 0);
};

export { sum };

在入口文件main.js中引入。

Webpack入门系列(三) 我们启动本地服务npm run dev,打开控制面板 然后保存sum.js文件,发现控制台刷新了,说明js是没有热更新的,需要我们手动配置,如下

import { sum } from "./utils/sum";
console.log(sum(1, 2, 3));

// 判断是否支持热更新
if (module.hot) {
  module.hot.accept("./utils/sum.js");
}

在启动服务后,修改sum.js文件后保存,发现控制台没有刷新,则热更新实现

Webpack入门系列(三)

那可能会有以下疑问,一个项目中有很多的js文件,每一个都需要手动引入吗?当然不是,vue和react都帮我们实现了。

四、oneOf

打包时每个文件都会经过所有 loader 处理,虽然因为 test 正则原因实际没有处理上,但是都要过一遍。比较慢。为了提高打包时的效率,我们只需要一种文件类型被一个loader处理即可,则需要配置oneOf,则需要在配置loader外层再包一层数组,如下

 module: {
    rules: [
      {
        oneOf: [
          {
            test: /\.css$/,
            use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader"],
          },
          {
            test: /\.less$/,
            use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader", "less-loader"],
          },
          {
            test: /\.s[ac]ss$/i,
            use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader", "sass-loader"],
          },
          {
            test: /\.(png|jpe?g|gif)$/i,
            type: "asset/resource",
            generator: {
              filename: "static/[hash][ext][query]",
            },
          },
          {
            test: /\.(ttf|woff|woff2)$/i,
            type: "asset/resource",
            generator: {
              filename: "static/[hash][ext][query]",
            },
          },
          {
            test: /\.m?js$/,
            exclude: /(node_modules|bower_components)/,
            use: [
              {
                loader: "babel-loader",
                options: {
                  cacheDirectory: true,
                },
              },
            ],
          },
        ],
      },
    ],
  },

五、include/exclude

在需要大量编译操作的loader时,我们可以配置指定loader的处理文件夹,或者是指定不处理哪些文件夹,提升编译速度。

babel-loader

对于js的编译,我们可以指定需要babel处理的文件路径,如下

{
            test: /\.m?js$/,
            exclude: /(node_modules|bower_components)/,
            use: [
              {
                loader: "babel-loader",
                
                options: {
                  cacheDirectory: true,
                  include: path.resolve(__dirname, "./src"),
                },
              },
            ],
          },

eslint

对于eslint需要检测的目录,我们可以排除需要代码检测的目录,如下

new EslintWebpackPlugin({
      context: path.resolve(__dirname, "./src"),
      exclude: "node_modules",
      cache: true, // 开启缓
      cacheLocation: path.resolve(__dirname, "./node_modules/.cache/.eslintcache"),
    }),

六、thread多线程

当项目越来越庞大时,打包速度越来越慢,甚至于需要一个下午才能打包出来代码。这个速度是比较慢的。

我们想要继续提升打包速度,其实就是要提升 js 的打包速度,因为其他文件都比较少。

而对 js 文件处理主要就是 eslint 、babel、Terser 三个工具,所以我们要提升它们的运行速度。

我们可以开启多进程同时处理 js 文件,这样速度就比之前的单进程打包更快了。

需要注意:请仅在特别耗时的操作中使用,因为每个进程启动就有大约为 600ms 左右开销。 下面介绍一下怎么开启多线程打包

首先安装npm install --save-dev thread-loader,然后我们在耗时的babel-loader中加入

{
            test: /\.m?js$/,
            exclude: /(node_modules|bower_components)/,
            use: [
              "thread-loader",
              {
                loader: "babel-loader",
                include: path.resolve(__dirname, "./src"),
                options: {
                  cacheDirectory: true,
                },
              },
            ],
          },

我们打包一下,发现确实变慢了,文件太少了,而开启进程的耗时太长了。需谨慎使用。

七、代码分割code split

目前我们在入口mian.js中引入的代码最终都被打包到一个js文件下了,这就造成了打包后main.js文件越来越大,在加载时就会出现白屏。我们想把模块都分开,我们可以在webpack.config.js中增加优化器配置

 // 优化
  optimization: {
    splitChunks: {
      chunks: "all", // 对所有模块都分割
    },
  },

然后,我们继续打包看一下,果然多出一个文件553.js

Webpack入门系列(三)

当然,我们也可以指定同类文件打包到一个js文件中,配置如下

 cacheGroups: {
        "vue":{
          test: /[\\/]node_modules[\\/]vue(.*)?/,
          name: 'vue-chunk.js',
          priority: 40
        },
        libs:{
          priority: 30,
          test: /[\\/]node_modules[\\/]$/,
          name: 'libs-chunk.js'
        }
      }

八、懒加载

通常在vue中,我们经常使用路由懒加载来优化加载速度,就是为了在首次加载的时候不一次性加载所有路由,在用户点击的时候才去请求相应的页面。 我们简单实现一下

首先在html文件中写一个按钮

<button class="btn">懒加载</button>

然后在main.js中写一些原生dom操作

const btn = document.querySelector(".btn");
btn.addEventListener("click", () => {
  import(/* webpackChunkName:"count" */ "./utils/count").then((fn) => {
    console.log(fn.default());
  });
});

此时发现eslint报错了

Webpack入门系列(三)

我们安装个插件解决这个报错npm i eslint-plugin-import -D然后在eslint配置文件中引入插件,如下

module.exports = {
  // 继承 Eslint 规则
  extends: ["eslint:recommended"],
  env: {
    node: true, // 启用node中全局变量
    browser: true, // 启用浏览器中全局变量
    es2020: true,
  },
  plugins: ["import"], // 解决动态导入import语法报错问题 --> 实际使用eslint-plugin-import的规则解决的
  parserOptions: {
    ecmaVersion: 11,
    sourceType: "module",
    allowImportExportEverywhere: true,
  },
  rules: {
    "no-var": 2, // 不能使用 var 定义变量
  },
};

然后重新启动项目,打开f12,点击按钮,发现有个js的网络请求

Webpack入门系列(三)

懒加载成功

九、 Preload / Prefetch

为了进一步提高用户体验,有时懒加载的文件太大了会导致白屏,我们可不可以在浏览器空闲的时候去下载这些js资源呢?这里我们就需要用上 Preload 或 Prefetch 技术。

首先Preload / Prefetch是什么?

  • Preload:告诉浏览器立即加载资源。
  • Prefetch:告诉浏览器在空闲时才开始加载资源。

他们的区别呢?

  • Preload加载优先级高,Prefetch加载优先级低。
  • Preload只能加载当前页面需要使用的资源,Prefetch可以加载当前页面资源,也可以加载下一个页面需要使用的资源。

他们存在的问题

  • 兼容性都不是好

如何使用?

安装插件 pnpm i -D @vue/preload-webpack-plugin

启动插件

 new PreloadWebpackPlugin({
      rel: "preload", // preload兼容性更好
      as: "script",
      // rel: 'prefetch' // prefetch兼容性更差
    }),

我们打包一下,发现之前的懒加载count.js文件已经被标上了preload标签了

Webpack入门系列(三)

在页面首次加载时也是直接下载了count.js文件

十、PWA

我们想在用户没有网路的时候也能操作网址,这个要怎么实现呢? 首先安装插件pnpm i -D workbox-webpack-plugin

使用plugin

new WorkboxPlugin.GenerateSW({
      // 这些选项帮助快速启用 ServiceWorkers
      // 不允许遗留任何“旧的” ServiceWorkers
      clientsClaim: true,
      skipWaiting: true,
    }),

然后在入口js文件中添加下面这一行代码

 if ('serviceWorker' in navigator) {
   window.addEventListener('load', () => {
     navigator.serviceWorker.register('/service-worker.js').then(registration => {
       console.log('SW registered: ', registration);
     }).catch(registrationError => {
      console.log('SW registration failed: ', registrationError);
     });
   });
 }

build一下,然后起一个本地的静态服务serve dist打开控制台发现

Webpack入门系列(三) 说明服务server work注册成功,接下来,我们关闭serve,然后重新刷新浏览器,发现页面还在。 如果刷新后页面不见了,需要把浏览器的停用缓存取消,如下。

Webpack入门系列(三)

十一、Network Cache

将来开发时我们对静态资源会使用缓存来优化,这样浏览器第二次请求资源就能读取缓存了,速度很快。

但是这样的话就会有一个问题, 因为前后输出的文件名是一样的,都叫 main.js,一旦将来发布新版本,因为文件名没有变化导致浏览器会直接读取缓存,不会加载新资源,项目也就没法更新了。

所以我们从文件名入手,确保更新前后文件名不一样,这样就可以做缓存了。

webpack5中有以下几种类型的hash值

  • fullhash(webpack4 是 hash)

每次修改任何一个文件,所有文件名的 hash 至都将改变。所以一旦修改了任何一个文件,整个项目的文件缓存都将失效。

  • chunkhash

根据不同的入口文件(Entry)进行依赖文件解析、构建对应的 chunk,生成对应的哈希值。我们 js 和 css 是同一个引入,会共享一个 hash 值。

  • contenthash

根据文件内容生成 hash 值,只有文件内容变化了,hash 值才会变化。所有文件 hash 值是独享且不同的。 这我们使用contenthash来命名文件

output: {
    filename: "js/[name].[contenthash:10].js",
    path: path.resolve(__dirname, "./dist"),
    assetModuleFilename: "static/[name][hash][ext][query]",
    // 动态加载路径
    chunkFilename: "js/[name].[contenthash:10].chunk.js",
    // 打包前清空dist目录
    clean: true,
  },

目前我们更新一下count里的代码,发现main.js和count.js都变了

Webpack入门系列(三)

这是因为count里的代码发生了变化,进而contenthash值也发生了变化,导致main文件也发生了变化。这样main.js的缓存就失效了。

可不可以把对应的hash值存在一个文件里呢?保证main.js在不修改时不变。

我们需要做以下步骤,在优化器中添加runtimeChunk配置

  // 优化
  optimization: {
    splitChunks: {
      chunks: "all", // 对所有模块都分割
    },
    runtimeChunk: {
      name: (entryPoint) => `runtime~${entryPoint.name}`,
    },
  },

我们再修改一下count.js文件,然后打包。这次发现只有count.js和runtime文件被修改了。

Webpack入门系列(三)

转载自:https://juejin.cn/post/7202891949379436600
评论
请登录