zl程序教程

您现在的位置是:首页 >  前端

当前栏目

Webpack学习笔记

2023-09-27 14:28:02 时间

1.引言

    最近想来自己对于前端的学习还是有很多的不足,于是决定重新学习一下webpack,毕竟在疫情可视化代码上线的时候使用的是直接在生产环境部署开发环境的代码,代码根本没有压缩、混淆等处理,无论是安全还是性能都显得有待提高。于是在B站找来了尚硅谷的教程,学习笔记总结分享如下。

2.Webpack相关的基础性问题

(1)为什么要使用打包工具?
    开发时,我们会使用框架(React、Vue),ES6 模块化语法,Less/Sass 等 css 预处理器等语法进行开发。这样的代码要想在浏览器运行必须经过编译成浏览器能识别的 JS、Css 等语法才能运行,所以我们需要打包工具帮我们做完这些事。除此之外,打包工具还能压缩代码、做兼容性处理、提升代码性能等。选择webpack是因为功能齐全,相对于其他打包工具热度更高。
(2)webpack的用途?
    在开发模式下,webpack仅能编译 JS 中的 ES Module 语法,在生产模式下能编译 JS 中的 ES Module 语法,还能压缩 JS 代码。
(3)举个例子
    直接贴代码如下:

//src/count.js
export default function count(x,y){
    return x+y;
}
//src/sum.js
export default function sum(...args){
    return args.reduce((p,c)=>p+c,0)
}
//简单说说reduce:
//array.reduce(function(total,currentValue,currentIndex ?,arr ?),initValue ?);
//其中reduce第一个参数传入的是函数,用于累加,内部参数依次为(和,当前数组值,当前数组下标,数组),
//第二个参数是total的初始值,加了?都是可选参数。

//调用者,src/main.js
import count from "./js/count";
import sum from "./js/sum";
console.log(count(2,3));;
console.log(sum(1,2,3,4,5));

直接使用一个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>Document</title>
</head>
<body>
    <div>hello webpack!</div>
    <script src="../src/main.js"></script>
</body>
</html>

报错如下(不能使用模块导入声明):
在这里插入图片描述
下面webpack登场(首先npm init -y,方便查看安装的具体依赖):

npm i webpack webpack-cli -D #安装依赖
npx webpack ./src/main.js --mode=development #利用npx执行

为什么是npx?——npx 会自动查找当前依赖包中的可执行文件,如果找不到,就会去 PATH 里找。如果依然找不到,就会帮你安装!npm不能够直接执行依赖包里面的可执行文件。如果直接npm会报错。然后将导入的文件由src/main.js改成dist/main.js,就够执行成功了。

3.Webpack的使用

    webpack 的五大角色:
(1)entry:Webpack 从打包的入口文件;
(2)output: Webpack 打包完的文件输出位置等;
(3)loader:webpack 本身只能处理 js、json 等资源,其他资源需要借助 针对性的loader解析;
(4)plugins:扩展 Webpack 的插件,有一些模块webpack里面没有内置,需要使用插件的方式来引入;
(5)mode:打包的时候使用的模式(生产模式和开发模式)。
串联这些的是一个根目录下配置文件(webpack.config.js,当添加了webpack配置文件之后启动webpack打包的时候就只需要利用npx webpack即可),eg:

// Node.js的核心模块,专门用来处理文件路径
const path = require("path");
module.exports = {
  entry: "./src/main.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "main.js",
  },
  //loader
  module: {
    rules: [],
  },
  plugins: [],
  mode: "development", // 开发模式
};

下面详细说说针对指定资源的打包方式:
(1)处理css文件:

npm i css-loader style-loader -D

如果需要对css文件进行压缩处理,需要使用模块化语法导入css,也就是在src/main.js中使用import "./css/index.css";导入css,在根目录的webpack.config.js文件中的rules模块部分添加如下代码(利用正则语法来检测文件后缀名并启用loader处理):

 {
        // 用来匹配 .css 结尾的文件
        test: /\.css$/,
        // use 数组里面 Loader 执行顺序是从右到左
        use: ["style-loader", "css-loader"],
 },

(2)处理less文件:

npm i less-loader less -D #把less 也加进去

同样在webpack.config.js配置项的rules部分添加如下代码:

 {
        test: /\.less$/,
        use: ["style-loader", "css-loader", "less-loader"],
      },

(3)处理 Sass 和 Scss 资源:
这里简单说说Sass和Scss的关系,sass和scss其实是一样的css预处理语言,SCSS 是 Sass 3 引入新的语法,其后缀名是分别为 .sass和.scss两种。SASS版本3.0之前的后缀名为.sass,而版本3.0之后的后缀名.scss。两者的不在于,sass时代是有严格的缩进规范并且没有‘{}’和‘;’,继sass之后scss的编写规范基本和css一致,也就是scss更便于阅读。好,直接说怎么利用webpack来打包这两种资源:

npm i sass-loader sass -D

同样在webpack.config.js配置项的rules部分添加如下代码:

 {
        test: /\.s[ac]ss$/,
        use: ["style-loader", "css-loader", "sass-loader"],
      },

(4)处理 Styl 资源(和sass差不多,也可以省略{}:,等):

npm i stylus-loader stylus -D

同样在webpack.config.js配置项的rules部分添加如下代码:

 {
        test: /\.styl$/,
        use: ["style-loader", "css-loader", "stylus-loader"],
      },

(5)处理图片资源,同样在webpack.config.js配置项的rules部分添加如下代码:

{
        test: /\.(png|jpe?g|gif|webp)$/,
        type: "asset",
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024 // 小于10kb的图片会被转化成base64,其它的会被重命名仍采用引入的方式
            //使用maxSize可以减少文件资源的请求次数
          }
        }
      },

补充关于静态资源的处理方式(在静态资源的rules部分内部追加配置):

generator: {
  // 将图片文件(不能够转化成base64的)输出到 static/images 目录中
  // 将图片文件命名 [hash:8][ext][query]
  // [hash:8]: hash值取8位
  // [ext]: 使用之前的文件扩展名
  // [query]: 添加之前的query参数
  filename: "static/images/[hash:8][ext][query]",
},

(6)配置自动清空webpack上一次的生成文件,在output里面追加一个clean:true即可;
(7)处理字体资源(剩余其他类型的资源也一样),

{
        test: /\.(ttf|woff2?|map4|map3|avi)$/,
        type: "asset/resource",
        generator: {
          filename: "static/media/[hash:8][ext][query]",
        },
      },

说明一下这里的type属性,type: “asset/resource” 相当于file-loader, 将文件转化成 Webpack 能识别的资源,其他不做处理type: “asset” 相当于url-loader, 将文件转化成 Webpack 能识别的资源,同时小于某个大小的资源会处理成 data URI 形式。

4.Webpack与代码规范和兼容性

    为了提高代码的规范性可以使用eslint,为了提高代码的兼容性,可以使用babel来处理js文件,针对于eslint需要安装如下依赖(分别是适用于webpack的插件和eslint依赖):

npm i eslint-webpack-plugin eslint -D

下面给出简单的eslint配置(在根目录下创建.eslintrc.js文件),如需根据实际情况调整可以参考eslint官方说明

module.exports = {
    extends: ["recommend"],//使用官方推荐的eslint配置
    // 解析选项
    parserOptions: {
        ecmaVersion: 6, // ES 语法版本
        sourceType: "module", // ES 模块化
        ecmaFeatures: { // ES 其他特性
            jsx: true // 如果是 React 项目,就需要开启 jsx 语法
        }
    },
    rules: {
        semi: "error", // 禁止使用分号,"off"=0,"warn"=1,"error"=2
        'no-var': 2,
        'array-callback-return': 'warn', // 强制数组方法的回调函数中有 return 语句,否则警告
        'default-case': [
          'warn', // 要求 switch 语句中有 default 分支,否则警告
          { commentPattern: '^no default$' } // 允许在最后注释 no default, 就不会有警告了
        ],
        eqeqeq: [
          'warn', // 强制使用 === 和 !==,否则警告
          'smart' // https://eslint.bootcss.com/docs/rules/eqeqeq#smart 除了少数情况下不会有警告
        ],
    }
};

最后在webpack的配置文件中加上const ESLintWebpackPlugin = require("eslint-webpack-plugin");同时在的plugins部分添加如下代码激活插件:

new ESLintWebpackPlugin({
      // 指定检查文件的根目录
      context: path.resolve(__dirname, "src"),
    }),

当然有些时候可能需要主动忽略对于某些文件的检查,这个时候可以使用.eslintignore文件,使用方式和gitignore一样,eg:

# 忽略dist目录下所有文件
dist

    接下来说说兼容性处理babel的使用,创建babel的配置文件babel.config.js
下载相关的依赖项(babel依旧是采用loader的形式处理):

npm i babel-loader @babel/core @babel/preset-env -D

在babel的配置项中添加如下代码:

module.exports = {
  presets: ["@babel/preset-env"] //开启智能预设,能够使用最新版本的JavaScript语法
  	//@babel/preset-react:用来编译 React jsx 语法的预设
	//@babel/preset-typescript:用来编译 TypeScript 语法的预设
};

然后再回到webpack配置文件的rules部分添加如下内容:

  {
        test: /\.js$/,
        exclude: /node_modules/, // 排除node_modules代码不编译
        loader: "babel-loader",
      },

可以看到的一个比较明显的特点就是箭头函数消失了,转变成了普通函数,在sum函数里面的...args参数被修改成遍历模式了。

5.优化Html入口文件的处理

    当部署vue-cli生成的生产环境的文件时,我们会发现一般都是一个js文件和一个入口html文件,其实webpack也可以借助HtmlWebpackPlugin 插件实现这一点:

npm i html-webpack-plugin -D

跟前面提到的eslint插件一致,先定义并引入,然后在plugins部分启用:

const HtmlWebpackPlugin = require("html-webpack-plugin");
new HtmlWebpackPlugin({
      // 以 public/index.html 为模板创建文件
      // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源
      template: path.resolve(__dirname, "public/index.html"),
    }),

注意一点,此时需要撤销对main.js入口文件的引入,避免重复引入两次,而且之后生产环境是直接使用dist目录下的文件,两次导入会导致第二次外部资源引入失败。

6.实现webpack热更新

    在前面的基础上,每次修改代码之后需要重新打包,比较麻烦,解决方法是利用webpack-dev-server依赖:

npm i webpack-dev-server -D

下载完依赖之后在webpack配置文件的plugins的同级目录下添加如下服务器配置:

 devServer: {
    host: "localhost", //域名
    port: "3000", //端口
    open: true, // 是否自动打开浏览器
  },

然后利用npx webpack serve开启热更新服务,这样就能够下一次保存的时候自动重新打包了。

7.生产模式与开发模式分析

    为了将生产环境与开发环境分开,需要进行如下的调整:
(1)生产环境:不需要devServer
(2)开发环境:关闭自动删除dist目录下的文件clean:true配置;
为了方便调起两种模式,可以在package.json里面配置脚本:

"scripts": {
    "start": "npm run dev",
    "dev": "npx webpack serve --config .webpack.dev.js",
    "build": "npx webpack --config webpack.prod.js"
  }

8.优化css配置,避免闪频现象

    在webpack打包css的时候,首先是js来处理css,然后创建style节点插入到html中,一旦我们在入口文件中或者嵌套导入的样式文件过多,很可能出现某些标签一开始没有css布局,之后轮到它加载了才有布局,可能会因为css引入之后html的盒子大量调整出现所谓的闪屏现象,解决方法是利用mini-css-extract-plugin插件,将样式预先合并处理,再通过link标签导入到页面中:

npm i mini-css-extract-plugin -D
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
 new MiniCssExtractPlugin({
      filename: "static/css/main.css", // 输出目录以及文件名
    }),

然后还需要在每一个css样式文件,对应的处理rules的loader部分追加一个MiniCssExtractPlugin.loader
另一方面和js配置一样,由于不同的浏览器或者统一浏览器的不同版本,存在着css兼容问题,可以使用官方提供的postcss-loadercss兼容处理loader来优化:

npm i postcss-loader postcss postcss-preset-env -D

然后在每一个样式相关的rules配置项部分的loader里面加上如下的loader配置:

 {
            loader: "postcss-loader",
            options: {
              postcssOptions: {
                plugins: [
                  "postcss-preset-env", // 能解决大多数样式兼容性问题
                ],
              },
            },
          },

兼容程度的配置上,可以在package.json里面添加配置项:

{
  "browserslist": ["ie >= 8"]//兼容ie8以上
}

常用的是:

{
  "browserslist": ["last 2 version", "> 1%", "not dead"] 
  //最近的两个版本,全球超过1%人使用的浏览器,去除里面已经挂掉的版本
}

9.代码压缩

只要开启了生产模式,webpack会自动压缩html文件和js文件,对于css文件压缩,需要使用css-minimizer-webpack-plugin插件(配置方式与上述插件的配置方式一致):

npm i css-minimizer-webpack-plugin -D
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
 new CssMinimizerPlugin(),

最后为了方便大家配置参考,还是附上生产环境下的配置文件(生产环境和开发环境的区别就在于有无dist输出和devServer是否开启):

const path = require("path");
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
//打包时与eslint联动插件
const HtmlWebpackPlugin = require("html-webpack-plugin");
//通过html文件自动导入js文件插件
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
//css抽离成单文件插件
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
//css压缩插件,js与html在生产环境下默认压缩

const getStyleLoaders = (preProcessor) => {// loaders使用的函数封装
  return [
    MiniCssExtractPlugin.loader,//抽离成单文件
    "css-loader",// 能解决大多数样式兼容性问题
    {
      loader: "postcss-loader",
      options: {
        postcssOptions: {
          plugins: [
            "postcss-preset-env", 
          ],
        },
      },
    },
    preProcessor,
  ].filter(Boolean);//如果数组元素undefined,则直接给过滤掉
};

module.exports = {
  entry: "./src/main.js",
  output: {
    path: path.resolve(__dirname, "dist"), // 生产模式需要输出
    filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中
    clean: true,
  },
  module: {
    rules: [
      {
        // 用来匹配 .css 结尾的文件
        test: /\.css$/,
        // use 数组里面 Loader 执行顺序是从右到左
        use: getStyleLoaders(),
      },
      {
        test: /\.less$/,
        use: getStyleLoaders("less-loader"),
      },
      {
        test: /\.s[ac]ss$/,
        use: getStyleLoaders("sass-loader"),
      },
      {
        test: /\.styl$/,
        use: getStyleLoaders("stylus-loader"),
      },
      {
        test: /\.(png|jpe?g|gif|webp)$/,
        type: "asset",
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024, // 小于10kb的图片会被base64处理
          },
        },
        generator: {
          // 将图片文件输出到 static/imgs 目录中
          // 将图片文件命名 [hash:8][ext][query]
          // [hash:8]: hash值取8位
          // [ext]: 使用之前的文件扩展名
          // [query]: 添加之前的query参数
          filename: "static/imgs/[hash:8][ext][query]",
        },
      },
      {
        test: /\.(ttf|woff2?)$/,
        type: "asset/resource",
        generator: {
          filename: "static/media/[hash:8][ext][query]",
        },
      },
      {
        test: /\.js$/,
        exclude: /node_modules/, // 排除node_modules代码不编译
        loader: "babel-loader",
      },
    ],
  },
  plugins: [
    new ESLintWebpackPlugin({
      // 指定检查文件的根目录
      context: path.resolve(__dirname, "src"),
    }),
    new HtmlWebpackPlugin({
      // 以 public/index.html 为模板创建文件
      // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源
      template: path.resolve(__dirname, "public/index.html"),
    }),
    // 提取css成单独文件
    new MiniCssExtractPlugin({
      // 定义输出文件名和目录
      filename: "static/css/main.css",
    }),
    new CssMinimizerPlugin(),
  ],
  // devServer: {
  //   host: "localhost", // 启动服务器域名
  //   port: "3000", // 启动服务器端口号
  //   open: true, // 是否自动打开浏览器
  // },
  mode: "production",
};