初识webpack四

92 阅读6分钟

一、oneOf

正常情况来说,所有的文件在执行的时候,都需要将loader的rules过一遍,如果符合,就被对应loader处理,不符合则直接过,这样对性能并不好,为了解决这个问题,使用oneOf。在module中配置oneOf即可

作用:优化打包构建速度,避免每个文件都被所有的loader过一遍,因为任何一个文件,构建过程中,在遇到与之对应的loader后,就不会再往下执行。

注意:不能有两个配置处理同一个类型的文件,当一个文件要被多个loader处理,那么需要指定loader执行的先后顺序。例如eslint和babel都是处理js文件,需要先执行eslint,再执行babel。

webpack.config.js配置如下:


const { resolve } = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: ["./src/js/index.js", "./src/index.html"],
  output: {
    filename: "js/built.js",
    path: resolve(__dirname, "build"),
  },
  module: {
    rules: [
      {
        // 以下loader只会匹配一个
        // 注意:不能有两个配置处理同一个类型的文件
        oneOf: [
          {
            test: /\.css$/,
            use: ["style-loader", "css-loader"],
          },
          // 注意:安装less-loader时要一起安装less
          {
            test: /\.less$/,
            use: ["style-loader", "css-loader", "less-loader"],
          },
          {
            test: /\.(jpg|png|gif)$/,
            loader: "url-loader",
            type: "javascript/auto",
            options: {
              limit: 8 * 1024,
              name: "[hash:10].[ext]",
              esModule: false,
              outputPath: "imgs",
            },
          },
          // 处理html中img资源
          {
            test: /\.html$/,
            loader: "html-loader",
          },
          // 打包其他资源(除了html/js/css资源以外的其他资源)
          {
            // 排除css/js/html资源
            exclude: /\.(css|js|html|less|jpg|png|gif)$/,
            loader: "file-loader",
            type: "javascript/auto",
            options: {
              name: "[hash:9].[ext]",
              esModule: false,
              outputPath: "media",
            },
          },
        ],
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "./src/index.html",
    }),
  ],
  mode: "development",
 
  devServer: {
    static: resolve(__dirname, "build"),
    // contentBase: resolve(__dirname, "build"),
    // 启动gzip压缩
    compress: true,
    //端口号
    port: 3000,
    // 自动打开浏览器
    open: true,
    // 开启HMR功能, 热替换
    // 当修改了webpack配置,新配置要想生效,必须重启webpack服务
    hot: true,
    // host: "localhost",
  },
  devtool: "eval-source-map",
 
};

二、缓存

babel缓存

需要下载babel-loader

npm i babel-loader -D

cacheDirectory:true -->让第二次打包构建速度更快 文件资源缓存

  • hash:每次webpack构建打包时会生成一个唯一的hash值
  •     问题:因为js和css同时使用一个hash值。
    
  •     如果重新打包,会导致所有缓存失效(可能我只改动了一个文件)
    
  • chunkhash:根据chunk生成的hash值,如果打包来源于同一个chunk,那么hash值就一样
  •     问题:js和css的hash值还是一样的
    
  • contenhash:根据文件的内容生成hash值,不同文件hash值一定不一样 -->让代码运行缓存更好使用
  • output中的filename处设置hash类型

webpack.config.js配置:

const { resolve } = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const OptimizeCssAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");
const ESLintPlugin = require("eslint-webpack-plugin");
const WorkboxWebpackPlugin = require("workbox-webpack-plugin");
// 定义nodejs环境变量,决定使用browserlist的哪个环境
process.env.NODE_ENV = "Production"; //development

// 复用loader
const commonCssLoader = [
  MiniCssExtractPlugin.loader,
  "css-loader",
  // CSS兼容处理
  {
    loader: "postcss-loader",
  },
];

module.exports = {
  entry: "./src/js/index.js",
  output: {
    filename: "js/built.[contenthash:10].js",
    path: resolve(__dirname, "build"),
  },
  module: {
    rules: [
      {
        oneOf: [
          {
            test: /\.css$/,
            use: [...commonCssLoader],
          },
          {
            test: /\.less$/,
            use: [...commonCssLoader, "less-loader"],
          },
          {
            // 在package.json中eslintConfig -->airbnb
            test: /\.js$/,
            exclude: /node_module/,
            // 优先执行
            // enforce: "pre",
            // loader: "eslint-loader",
          },
        
          {
            // 在package.json中eslintConfig -->airbnb
            test: /\.js$/,
            exclude: /node_module/,
            loader: "babel-loader",
            options: {
              presets: [
                [
                  "@babel/preset-env",
                  {
                    useBuiltIns: "usage",
                    corejs: { version: 3 },
                    targets: {
                      chrome: "60",
                      firefox: "50",
                    },
                  },
                ],
              ],
              // 开启babel缓存
              // 第二次构建时,会读取之前的缓存
              cacheDirectory: true,
            },
          },
          {
            test: /\.(jpg|png|gif)/,
            loader: "url-loader",
            type: "javascript/auto",
            options: {
              limit: 8 * 1024,
              name: "[hash:10].[ext]",
              esModule: false,
              outputPath: "imgs",
            },
          },
          {
            test: /\.html$/,
            loader: "html-loader",
          },
          {
            exclude: /\.(js|css|less|html|jpg|png|gif)/,
            loader: "file-loader",
            type: "javascript/auto",
            options: {
              outputPath: "media",
              name: "[hash:9].[ext]",
              esModule: false,
            },
          },
        ],
      },
    ],
  },
  plugins: [
  //该插件会将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件,并且支持 CSS 和 SourceMaps 的按需加载。
  //此处对单独生成的文件设置contenthash缓存
    new MiniCssExtractPlugin({
      filename: "css/built.[contenthash:10].css",
    }),
    new HtmlWebpackPlugin({
      template: "./src/index.html",
      minify: {
        collapseWhitespace: true,
        removeComments: true,
      },
    }),
    new OptimizeCssAssetsWebpackPlugin(),
    new ESLintPlugin({ fix: true }),
  ],
  mode: "production",
  devServer: {
    static: resolve(__dirname, "build"),
    // contentBase: resolve(__dirname, "build"),
    // 启动gzip压缩
    compress: true,
    //端口号
    port: 3000,
    // 自动打开浏览器
    open: true,
    hot: true,
    // host: "localhost",
  },
  devtool: "source-map",
};

三、tree shaking

tree shaking:去除无用代码

前提:

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

作用:减少代码体积

sideEffects用于告知webpack compiler哪些模块时有副作用的

在package.json中配置"sideEffects":false 所有代码都没有副作用(都可以进行tree shaking)

问题:可能会把css/@babel/polyfill(副作用)文件干掉

解决办法:

如果有一些我们希望保留,可以设置为数组

"sideEffects":["*.css","*.less"],

四、code split

1、通过入口文件进行代码分割

在项目文件src中创建两个文件(index.js,test.js),可以通过entry设置多个入口,来打包生成多个js文件

output修改输出文件名filename:'js/[name].[contenthash:10].js',

// 单入口
  // entry:'./src/js/index.js'
  entry: {
    // 多入口:有一个入口,最终输出就有一个bundle
    main: "./src/js/index.js",
    test: "./src/js/test.js",
  },
  output: {
    // [name]:取文件名
    filename: "js/[name].[contenthash:10].js",
    path: resolve(__dirname, "build"),
  },

打包后生成的文件中可以看到输出了两个js文件:

demo1.jpg

缺点:如果js文件名修改,或者是新增和减少了js文件,都需要修改配置,很不方便

2、通过splitchunks属性进行代码分割

在index.js和test.js文件中引入jquery

多入口分析,代码设置为多个入口

index.js文件

import $ from "jquery";

function sum(...args) {
  return args.reduce((p, c) => p + c);
}
console.log(sum(1, 2, 3, 4));
console.log($);

test.js文件

import $ from "jquery";

export function mull(x, y) {
  console.log(x * y);
  return x * y;
}

export function count(x, y) {
  return x - y;
}

console.log($);

在webpack.config.js中配置splitChunks

1、可以将node_modules中代码单独打包成一个chunk最终输出

2、自动分析多入口chunk中,有没有公共的文件。如果有会打包成一个单独的chunk

 optimization: {
    splitChunks: {
      chunks: "all",
    },
  },

执行打包命令:

demo2打包.jpg 可以看见splitChunks将node_modules中的代码单独打包成一个chunk输出,而且jquery文件只打包了一次,输出了三个文件(一个node_modules的文件,两个入口文件), 项目的输出文件会小很多 打包后的输出文件:

demo2.jpg

splitChunks 会自动分析多个入口chunk中有没有公共的文件,如果有则会打包成单独的一个chunk,而不会重复加载

三、通过js代码进行分割

通过js代码,让某个文件被单独打包成一个chunk,import动态导入语法,能将某个文件单独打包

webpackChunkName:设置打包输出的文件名

test.js文件

export function mull(x, y) {
  console.log(x * y);
  return x * y;
}

export function count(x, y) {
  return x - y;
}

index.js文件引入test.js

function sum(...args) {
  return args.reduce((p, c) => p + c);
}
console.log(sum(1, 2, 3, 4));
/*
  通过js代码,让某个文件被单独打包成一个chunk
  import动态导入语法,能将某个文件单独打包
*/

import(/* webpackChunkName: 'test1' */ "./test")
  .then(({ mull, count }) => {
    // 文件加载成功
    console.log(mull(2, 5));
  })
  .catch(() => {
    // 文件加载失败
    console.log("文件加载失败");
  });

webpack.config.js改为单入口

entry: "./src/js/index.js", 


optimization: {
    splitChunks: {
      chunks: "all",
    },
  },

这样就可以实现单入口也能单独打包某个js文件

执行打包命令:

demo3打包.jpg

打包后的输出文件:

demo3.jpg

打包出来的test文件就是我们指定的路径下的文件名test1

五、懒加载

  • 懒加载:当文件需要使用时才加载
  • 预加载:webpackPrefetch:true,会在使用之前,提取加载js文件
  • 正常加载可以认为是并行加载(同一时间加载多个文件)
  • 预加载prefetch:等其他资源加载完毕,浏览器空闲了,再偷偷加载资源(兼容性差,慎用)

例如: index.html文件:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>webpack</title>
</head>
<body>
  <div id="box">
    <button id="btn">按钮</button>
  </div>
</body>
</html>

index.js文件:

console.log("index.js加载");

document.getElementById("btn").onclick = function () {
 
  import(/* webpackChunkName: 'test1' ,webpackPrefetch:true*/ "./test")
    .then(({ mull }) => {
      console.log(mull(2, 5), "成功");
    })
    .catch();
};

test.js文件同上一个知识点的test文件一样

打包后在浏览器的运行结果如下:

懒加载1.jpg

点击按钮后

懒加载2.jpg

我们可以看到一开始浏览器并没有将test.js文件加载出来,而是在点击按钮后才去将test.js文件加载

六、PWA

PWA渐进式网络开发应用程序(离线可访问)

(1)安装和引入workbox-webpack-plugin插件

npm i workbox-webpack-plugin -D

webpack.config.js配置:

const { resolve } = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const OptimizeCssAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");
const ESLintPlugin = require("eslint-webpack-plugin");
const WorkboxWebpackPlugin = require("workbox-webpack-plugin");

// 定义nodejs环境变量,决定使用browserlist的哪个环境
process.env.NODE_ENV = "Production"; //development


module.exports = {
  entry: "./src/js/index.js",
  output: {
    filename: "js/built.[contenthash:10].js",
    path: resolve(__dirname, "build"),
  },
  module: {
    rules: [
      {
        oneOf: [         
          {
            exclude: /\.(js|css|less|html|jpg|png|gif)/,
            loader: "file-loader",
            type: "javascript/auto",
            options: {
              outputPath: "media",
              name: "[hash:9].[ext]",
              esModule: false,
            },
          },
        ],
      },
    ],
  },
  plugins: [
    new WorkboxWebpackPlugin.GenerateSW({
      /**
       * 1.帮助serviceworker快速启动
       * 2.删除旧的serviceworker
       *
       * 生成一个 serviceworker文件
       */
      clientsClaim: true,
      skipWaiting: true,
    }),
    new MiniCssExtractPlugin({
      filename: "css/built.[contenthash:10].css",
    }),
    new HtmlWebpackPlugin({
      template: "./src/index.html",
      minify: {
        collapseWhitespace: true,
        removeComments: true,
      },
    }),
    new OptimizeCssAssetsWebpackPlugin(),
    new ESLintPlugin({ fix: true }),
  ],
  mode: "production",
  devServer: {
    static: resolve(__dirname, "build"),
    // contentBase: resolve(__dirname, "build"),
    // 启动gzip压缩
    compress: true,
    //端口号
    port: 3000,
    // 自动打开浏览器
    open: true,
    hot: true,
    // host: "localhost",
  },
  devtool: "source-map",
};

(2)注册serviceworker

index.js文件中注册

// 处理兼容问题
if ('serviceWorker' in navigator) {
    window.addEventListener('load', () => {
        navigator.serviceWorker
            .register('/service-worker.js')
            .then(() => {
                console.log('sw注册成功了~');
            })
            .catch(() => {
                console.log('sw注册失败了~');
            });
    });
}

注意:

  • eslint不认识window 、navigator全局变量
  • 解决:需要修改.eslintrc.js中配置
env: {
       browser: true, //支持浏览器全局变量
 
   },
  • sw代码必须运行在服务器上
    -->nodejs
        -->npm i serve -g
            serve -s build 启动服务器,将build目录下所有资源作为静态资源暴露出去

执行打包命令:

pwa1.jpg 打包后输出文件有:

pwa6.png

执行serve -s build启动服务器:

pwa2.jpg

本地浏览器访问:

pwa3.jpg

可以在开发工具中Application查看我们注册的service-worker和workbox

pwa4.jpg 可以在 application 中看到使用 service-worker 和 workbox 缓存的文件

pwa5.jpg