跳至主要內容

03 【资源处理】

约 8577 字大约 29 分钟...

03 【资源处理】

在上一章,我们讲解了四种资源模块引入外部资源。除了资源模块,我们还可以通过 loader引入其他类型的文件。

什么是loader

webpack 只能理解 JavaScriptJSON文件,这是 webpack 开箱可用的自带能力。 loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效 模块,以供 应用程序使用,以及被添加到依赖图中。

在 webpack 的配置中,loader 有两个属性:

  1. test 属性,识别出哪些文件会被转换。
  2. use 属性,定义出在进行转换时,应该使用哪个 loader。

1.HTML资源

index.html中是采用手动修改路径,然后才引入的,这样并不方便。我们想要实现的是webpack打包完成后自动帮助我们引入相关的资源。 于是就可以使用社区插件中的HtmlWebpackPlugin插件。

自动引入资源

1、下载 html-webpack-plugin 插件

pnpm i html-webpack-plugin -D

2、在 webpack.config.js 文件中引入插件并调用

使用,当直接new HtmlWebpackPlugin()的时候,会在dist文件夹下生成一个index.html,但是它只有一些基础的配置。如果想要自行配置,则需要一个自己给定的模板index.html,然后增加一些配置,配置的参考文档:https://github.com/jantimon/html-webpack-plugin#optionsopen in new window

该插件将为你生成一个 HTML5 文件, 在 body 中使用 script 标签引入你所有 webpack 生成的 bundle。 只需添加该插件到你的 webpack 配置中

  • 不传参的情况:new HTMLWebpackPlugin():会在配置的output文件夹创建一个空的html, 自动引入打包输出的所有资源,包括js, css...;

  • 设置参数template:复制设置的'./src/index.html'文件到配置的output文件夹,并自动引入打包输出的所有资源。

const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  /* 
    html-webpack-plugin 没有任何配置会生成一个自动引入js文件的index.html
  */
  plugins: [
    new HtmlWebpackPlugin({
      // 可以用于html模板的title标签内容
      title: 'dselegent',
      template: './index.html', // 打包生成的文件的模板
      filename: 'app.html', // 打包生成的文件名称。默认为index.html
      // 设置所有资源文件注入模板的位置。可以设置的值
      // true|'head'|'body'|false,默认值为 true
      inject: 'body',
      
        压缩html代码 production环境使用
        minify: {
            // 移除空格
            collapseWhitespace: true,
            // 移除注释
            removeComment: true
        }
    }),
  ],
};

模板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><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body></body>
</html>

如果你有多个 webpack 入口,他们都会在已生成 HTML 文件中的 <script> 标签内引入。

如果在 webpack 的输出中有任何 CSS 资源(例如,使用 MiniCssExtractPluginopen in new window 提取的 CSS),那么这些资源也会在 HTML 文件 <head> 元素中的 <link> 标签内引入。

打包:

image-20220801203026571
image-20220801203026571

查看 app.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>我是dselegent</title>
  </head>
  <body>
    <script defer src="bundle.js"></script>
  </body>
</html>

这次打包应用到了我们的模板文件 index.html , 并且生成了新的文件 app.html , 文 件里自动引用的 bundle.js 也从 迁移到了里。

Webpack 会在输出目录中新创建一个 HTML 文件,在原始的 HTML 文件中无需引入 JS 文件,通过 Webpack 编译后的 HTML 文件会自动引入。

官方说明:https://webpack.docschina.org/plugins/html-webpack-plugin/open in new window

配置选项:https://github.com/jantimon/html-webpack-plugin#optionsopen in new window

2.样式资源

Webpack 本身是不能识别样式资源的,所以我们需要借助 Loader 来帮助 Webpack 解析样式资源

我们找 Loader 都应该去官方文档中找到对应的 Loader,然后使用

官方文档找不到的话,可以从社区 Github 中搜索查询

Webpack 官方 Loader 文档open in new window

.css文件:style-loader, css-loader

  • css-loader:将css文件转换成commonjs模块加载到js中,css代码被转换成了样式字符串。(转换后得到的commonjs模块可以理解为:用js给元素动态添加样式的那种代码);
  • style-loader:创建style标签,将css-loader生成的样式资源插入进去,添加到head中,使样式生效。

.less文件:style-loader, css-loader, less-loader

  • less-loader:将less文件编译成css文件;
  • 注:要想less-loader生效,还需要下载less (npm install less -D)。

.scss/.sass文件:style-loader, css-loader, sass-loader

  • sass-loader:将sass或scss文件编译成css文件;
  • 注:要想sass-loader生效,还需要下载node-sass (npm install node-sass -D)。

2.1 css

下载包

pnpm add css-loader style-loader -D

注意:需要下载两个 loader

功能介绍

  • css-loader:负责将 Css 文件编译成 Webpack 能识别的模块
  • style-loader:会动态创建一个 Style 标签,里面放置 Webpack 中 Css 模块内容

此时样式就会以 Style 标签的形式在页面上生效

配置

module.exports = {
  ...
  module: {
    rules: [
      {
        // 用来匹配 .css 结尾的文件
        test: /\.css$/i,
         // 写法一:
        // use 数组里面 Loader 执行顺序是从右到左
        use: [
          'style-loader', // 创建style标签,将'css-loader'整合到js中的样式字符串放到style标签中。
          'css-loader' // 将css文件转换成commonjs模块加载到js中,css代码被转换成了样式字符串。
        ],
        // 写法二:
        use: [
          { loader: 'style-loader' },
          { loader: 'css-loader' }
        ]
      }
    ]
  }
}

模块 loader 可以链式调用。链中的每个 loader 都将对资源进行转换。链会逆序执 行。第一个 loader 将其结果(被转换后的资源)传递给下一个 loader,依此类推。 最后,webpack 期望链中的最后的 loader 返回 JavaScript

应保证 loader 的先后顺序: 'style-loader' 在前,而 'css-loader' 在后。如果 不遵守此约定,webpack 可能会抛出错误。webpack 根据正则表达式,来确定应该 查找哪些文件,并将其提供给指定的 loader。在这个示例中,所有以 .css 结尾的 文件,都将被提供给 style-loader css-loader

这使你可以在依赖于此样式的 js 文件中 import './style.css' 。现在,在此模块执行过程中,含有 CSS 字符串的 <style>标签,将被插入到html文件的<head>中。

通过在项目中添加一个新的style.css文件,并将其import到我 们的 index.js 中:

image-20220802190906654
image-20220802190906654

07-manage-assets/src/style.css

hello {
color: #f9efd4;
}

在入口文件index.js里导入.css文件:

// 引入 Css 资源,Webpack才会对其打包
import './style.css'

document.body.classList.add('hello')
image-20220802191034036
image-20220802191034036

你应该看到页面背景颜色是浅黄色。要查看 webpack 做了什么,请检查页面(不要 查看页面源代码,它不会显示结果,因为<style> 标签是由 JavaScript 动态创建 的),并查看页面的 head 标签,包含 style 块元素,也就是我们在 index.jsimport 的 css 文件中的样式。

2.2 less&sass

下载样式处理解析器

pnpm i sass-loader sass -D
pnpm i less-loader less -D
  • sass-loader:负责将 Sass 文件编译成 css 文件
  • sasssass-loader 依赖 sass 进行编译

在配置文件中添加解析器

module.exports = {
  ...
  module: {
    rules: [
      {
        test: /\.s[ac]ss$/i,
        use: [
          // 在 head 中创建 style 标签
          'style-loader',
          // 将 css 文件整合到 js 文件中
          'css-loader',
          // 编译 sass 文件为 css 文件
          'sass-loader'
        ]
      },
        {
        test: /\.less$/,
        use: ["style-loader", "css-loader", "less-loader"],
      },
    ]
  }
}

在项目 src 目录下创建 style.less 文件:

@color: red;
body {
  color: @color;
}

index.js 文件中导入 less 文件

import './style.less'
document.body.classList.add('world')
image-20220802191858697
image-20220802191858697

由预览的效果可见,页面的文字都添加了“红色”的样式。

不同的样式文件会新建一个style标签

2.3 抽离CSS

Css 文件目前被打包到 js 文件中,当 js 文件加载时,会创建一个 style 标签来生成样式,这样对于网站来说,会出现闪屏现象,用户体验不好,我们应该是单独的 Css 文件,通过 link 标签加载性能才好。

在多数情况下,我们也可以进行压缩CSS,以便在生产环境中节省加载时间,同时还 可以将CSS文件抽离成一个单独的文件。实现这个功能,需要 mini-css-extractplugin 这个插件来帮忙。安装插件:

下载包

pnpm i mini-css-extract-plugin -D

本插件会将取代style-loader, 将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件,并且支持 CSS 和 SourceMaps 的按需加载。

本插件基于 webpack v5 的新特性构建,并且需要 webpack 5 才能正常工作。

之后将 loader plugin添加到你的 webpack 配置文件中:

配置

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

module.exports = {
  ...
  module: {
    rules: [
        {
        // 用来匹配 .css 结尾的文件
        test: /\.css$/,
        // use 数组里面 Loader 执行顺序是从右到左
        // 用这个loader取代style-loader,因为想将css单独输出为一个文件
        use: [MiniCssExtractPlugin.loader, "css-loader"],
      },
      {
        test: /\.less$/,
        use: [MiniCssExtractPlugin.loader, "css-loader", "less-loader"],
      },
      {
        test: /\.s[ac]ss$/,
        use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"],
      },
      {
        test: /\.styl$/,
        use: [MiniCssExtractPlugin.loader, "css-loader", "stylus-loader"],
      },
    ]
  },
  plugins: [
    // 提取css成单独文件
    new MiniCssExtractPlugin({
      // 定义输出文件名和目录
      filename: "static/css/main.css",
    }),
  ]
}

⚠️ 注意,如果你从 webpack 入口处导入 CSS 或者在 初始open in new window chunk 中引入 style, mini-css-extract-plugin 则不会将这些 CSS 加载到页面中。请使用 html-webpack-pluginopen in new window 自动生成 link 标签或者在创建 index.html 文件时使用 link 标签。

查看打包完成后的目录和文件:

image-20220802192721777
image-20220802192721777

07-manage-assets/dist/app.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, initialscale=1.0">
<title>dselegent</title>
<link href="styles/c8d1b95f617a81aa500c.css"
rel="stylesheet"></head>
<body>
<script defer src="bundle.js"></script></body>
</html> 

如果是生成模式,将自动压缩css文件,无需额外配置。

官方文档:https://webpack.docschina.org/plugins/mini-css-extract-pluginopen in new window

2.4 压缩css(生产模式)

下载包

pnpm i css-minimizer-webpack-plugin -D

在配置文件中进行配置

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin")

module.exports = {
  ...
  optimization: {
    minimizer: [
      // 使用插件优化 css 代码
      new CssMinimizerPlugin()
    ],
  },
  // 模式
  mode: 'production'
}

压缩 CSS 代码,仅在生产模式下有效

官方文档:https://webpack.docschina.org/plugins/css-minimizer-webpack-plugin/open in new window

3.图片资源

过去在 Webpack4 时,我们处理图片资源通过 file-loaderurl-loader 进行处理

现在 Webpack5 已经将两个 Loader 功能内置到 Webpack 里了,我们只需要简单配置即可处理图片资源

配置

const path = require("path");

module.exports = {
  ...
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif|webp)$/,
        type: "asset",
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024 // 小于10kb的图片会被base64处理
          }
        }
      }
    ],
  },
  plugins: [],
  mode: "development",
};

对图片资源进行优化

将小于某个大小的图片转化成 data URI 形式(Base64 格式)

  • 优点:减少请求数量
  • 缺点:体积变得更大

此时输出的图片文件就只有两张,有一张图片以 data URI 形式内置到 js 中了 (注意:需要将上次打包生成的文件清空,再重新打包才有效果)

添加图片资源

  • src/images/1.jpeg
  • src/images/2.png
  • src/images/3.gif

使用图片资源

  • src/less/index.less
block-bg {
background-image: url(./assets/webpack-logo.svg) ;
}

07-manage-assets/src/index.js

import './style.less'

const block = document.createElement('div')
block.style.cssText = 'width: 200px; height: 200px; background: aliceblue'
block.classList.add('block-bg')
block.textContent = exampleTxt
document.body.appendChild(block)

启动服务,打开浏览器:

image-20220802194923201
image-20220802194923201

我们看到,通过样式把背景图片加到了页面中。

4.字体资源

同样可以使用webpack内置的asset module模块来加载引入字体文件。 这里我使用阿里矢量图标库的字体图标来演示。

下载字体图标文件

  1. 打开阿里巴巴矢量图标库open in new windowopen in new window
  2. 选择想要的图标添加到购物车,统一下载到本地

解压之后,是这么一大坨东西,而我要修改的其实仅仅是这个文件:

image-20220804192044822
image-20220804192044822

具体的使用方法可以查看这个地址:https://www.iconfont.cn/help/detail?spm=a313x.7781069.1998910419.d8cf4382a&helptype=codeopen in new window

添加字体图标资源

  • src/fonts/iconfont.ttf

  • src/fonts/iconfont.woff

  • src/fonts/iconfont.woff2

  • src/css/iconfont.css

    • 注意字体文件路径需要修改
  • src/index.js

import "./css/iconfont.css";

const span = document.createElement('span')
span.classList.add('testIcon')
span.innerHTML='&#xede2;'//这个是下载时对应图标的unicode
document.body.appendChild(span)

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>webpack5</title>
  </head>
  <body>
    <!-- 使用字体图标 -->
    <i class="iconfont icon-arrow-down"></i>
    <i class="iconfont icon-ashbin"></i>
    <i class="iconfont icon-browse"></i>
    <script src="../dist/static/js/main.js"></script>
  </body>
</html>

配置

const path = require("path");

module.exports = {
  entry: "./src/main.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中
    clean: true, // 自动将上次打包目录资源清空
  },
  module: {
    rules: [
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i,
        type: "asset/resource",
        generator: {
          filename: "static/font/[hash:8][ext][query]",
        },
      },
    ],
  },
  plugins: [],
  mode: "development",
};

运行指令

npx webpack
image-20220804192249137
image-20220804192249137

5.数据资源

此外,可以加载的有用资源还有数据,如 JSON 文件,CSVTSV XML。类似于 NodeJS,JSON 支持实际上是内置的,也就是说 import Data from './data.json' 默认将正常运行。

要导入 CSVTSV XML,你可以使用 csv-loaderxml-loader。让我们处理加载这三类文件:

安装包

pnpm install --save-dev csv-loader xml-loader

添加配置:

module: {
    rules: [
        {
        	test: /\.(csv|tsv)$/i,
        	use: ['csv-loader'],
        },
        {
       		test: /\.xml$/i,
       		use: ['xml-loader'],
        },
    ]
}

现在,你可以 import 这四种类型的数据(JSON, CSV, TSV, XML)中的任何一种,所 导入的 Data 变量,将包含可直接使用的已解析 JSON:

创建两个文件:

07-manage-assets/src/assets/data.xml

<?xml version="1.0" encoding="UTF-8"?>
<note>
  <to>Mary</to>
  <from>John</from>
  <heading>Reminder</heading>
  <body>Call Cindy on Tuesday</body>
</note>

07-manage-assets/src/assets/data.csv

to,from,heading,body
Mary,John,Reminder,Call Cindy on Tuesday
Zoe,Bill,Reminder,Buy orange juice
Autumn,Lindsey,Letter,I miss you

在入口文件里加载数据模块,并在控制台上打印导入内容:

07-manage-assets/src/index.js

import Data from './assets/data.xml'
import Notes from './assets/data.csv'
console.log(Data)
console.log(Notes)
image-20220802200215027
image-20220802200215027

由此可见, data.xml 文件转化为一个JS对象, data.cvs 转化为一个数组。

6.自定义 JSON 模块 parser

通过使用 自定义 parser 替代特定的 webpack loader,可以将任何 toml yamljson5 文件作为 JSON 模块导入。

假设你在 src 文件夹下有一个 data.toml 、一个 data.yaml 以及一个 data.json5 文件

下载包

pnpm install toml yaml json5 --save-dev

配置

const toml = require('toml')
const yaml = require('yaml')
const json5 = require('json5')

module.exports = {

  devServer: {
    static: './dist'
  },

  module: {
    rules: [
      {
        test: /\.toml$/,
        type: 'json',
        parser: {
          parse: toml.parse
        }
      },

      {
        test: /\.yaml$/,
        type: 'json',
        parser: {
          parse: yaml.parse
        }
      },

      {
        test: /\.json5$/,
        type: 'json',
        parser: {
          parse: json5.parse
        }
      }
    ]
  },

}

在主文件中引入模块,并打印内容:

import toml from './data.toml';
import yaml from './data.yaml';
import json from './data.json5';

console.log(toml.title); // output `TOML Example`
console.log(toml.owner.name); // output `Tom Preston-Werner`
console.log(yaml.title); // output `YAML Example`
console.log(yaml.owner.name); // output `Tom Preston-Werner`
console.log(json.title); // output `JSON5 Example`
console.log(json.owner.name); // output `Tom Preston-Werner`

现在, toml 、 yaml 和 json5 几个类型的文件都正常输出了结果。

7.js规范化

可组装的 JavaScript 和 JSX 检查工具。

这句话意思就是:它是用来检测 js 和 jsx 语法的工具,可以配置各项功能

我们使用 Eslint,关键是写 Eslint 配置文件,里面写上各种 rules 规则,将来运行 Eslint 时就会以写的规则对代码进行检查

7.1 配置文件

配置文件由很多种写法:

  • .eslintrc.*:新建文件,位于项目根目录

    • .eslintrc

    • .eslintrc.js

    • .eslintrc.json

    • 区别在于配置格式不一样

  • package.jsoneslintConfig:不需要创建文件,在原有文件基础上写

ESLint 会查找和自动读取它们,所以以上配置文件只需要存在一个即可

7.2 具体配置

我们以 .eslintrc.js 配置文件为例:

module.exports = {
    "env": { // 指定脚本的运行环境。每种环境都有一组特定的预定义全局变量。
        "browser": true, // 运行在浏览器
        "es2021": true // 支持es2021
    },
    "extends": [ // 使用的外部代码格式化配置文件
        "airbnb-base"
    ],
    "parserOptions": {
        "ecmaVersion": 12, // ecmaVersion 用来指定支持的 ECMAScript 版本 。默认为 5,即仅支持es5
        "sourceType": "module"
    },
  	globals: {}, // 脚本在执行期间访问的额外的全局变量
    "rules": { // 启用的规则及其各自的错误级别。0(off)  1(warning)  2(error)
    }
};
  1. parserOptions 解析选项
parserOptions: {
  ecmaVersion: 6, // ES 语法版本
  sourceType: "module", // ES 模块化
  ecmaFeatures: { // ES 其他特性
    jsx: true // 如果是 React 项目,就需要开启 jsx 语法
  }
}
  1. rules 具体规则

    • "off"0 - 关闭规则
    • "warn"1 - 开启规则,使用警告级别的错误:warn (不会导致程序退出)
    • "error"2 - 开启规则,使用错误级别的错误:error (当被触发的时候,程序会退出)
    rules: {
      semi: "error", // 禁止使用分号
      '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 除了少数情况下不会有警告
      ],
    }
    

    更多规则详见:规则文档open in new windowopen in new window

  2. extends 继承

开发中一点点写 rules 规则太费劲了,所以有更好的办法,继承现有的规则。

现有以下较为有名的规则:

// 例如在React项目中,我们可以这样写配置
module.exports = {
  extends: ["react-app"],
  rules: {
    // 我们的规则会覆盖掉react-app的规则
    // 所以想要修改规则直接改就是了
    eqeqeq: ["warn", "smart"],
  },
};

7.3 在 Webpack 中使用

  1. 下载包

    pnpm i eslint-webpack-plugin eslint -D
    
  2. 定义 Eslint 配置文件 .eslintrc.js

    module.exports = {
      // 继承 Eslint 规则
      extends: ["eslint:recommended"],
      env: {
        node: true, // 启用node中全局变量
        browser: true, // 启用浏览器中全局变量
      },
      parserOptions: {
        ecmaVersion: 6,
        sourceType: "module",
      },
      rules: {
        "no-var": 2, // 不能使用 var 定义变量
      },
    };
    

也可以 npx eslint --init 生成

image-20220812184256448
image-20220812184256448
image-20220812184309770
image-20220812184309770
image-20220812184323978
image-20220812184323978
image-20220812184337162
image-20220812184337162
image-20220812184348443
image-20220812184348443
image-20220812184402059
image-20220812184402059
image-20220812184419732
image-20220812184419732

Airbnb: https://github.com/airbnb/javascriptopen in new window Standard: https://github.com/standard/standardopen in new window Google: https://github.com/google/eslint-config-googleopen in new window XO: https://github.com/xojs/eslint-config-xoopen in new window

vue 官方: eslint-config-vueopen in new window腾讯 AlloyTeam: eslint-config-alloyopen in new window

  1. 修改 js 文件代码 index.js

    import count from "./js/count";
    import sum from "./js/sum";
    // 引入资源,Webpack才会对其打包
    import "./css/iconfont.css";
    import "./css/index.css";
    import "./less/index.less";
    import "./sass/index.sass";
    import "./sass/index.scss";
    import "./styl/index.styl";
    
    var result1 = count(2, 1);
    console.log(result1);
    var result2 = sum(1, 2, 3, 4);
    console.log(result2);
    
  2. 配置

    执行npx eslint ./src/,可以看到会输出一些error或者waring

    image-20220812184101959
    image-20220812184101959

    我们期望eslint能够实时提示报错而不必等待执行命令。 这个功能可以通过给自己的IDE(代码编辑器)安装对应的eslint插件来实现。

    webpack.config.js

    module.exports = {
      plugins: [
        new ESLintWebpackPlugin({
          // 指定检查文件的根目录
          context: path.resolve(__dirname, "src"),
        }),
      ],
    };
    
  3. 运行指令

    npx webpack
    

7.4 VSCode Eslint 插件

打开 VSCode,下载 Eslint 插件,即可不用编译就能看到错误,可以提前解决

但是此时就会对项目所有文件默认进行 Eslint 检查了,我们 dist 目录下的打包后文件就会报错。但是我们只需要检查 src 下面的文件,不需要检查 dist 下面的文件。

所以可以使用 Eslint 忽略文件解决。在项目根目录新建下面文件:

.eslintignore


build/*.js
src/assets
public
// 忽略dist目录下所有文件
dist

vscode设置eslint保存文件时自动修复eslint错误_五虎战画戟的博客-CSDN博客open in new window

启动 vscode 打开项目,

windows: ctrl + shift + p 打开 搜索面板

MacBook: command + shift + p 打开 搜索面板

输入 settings.json,选择 .vscode 下的 settings.json

加入以下代码:

{
  // #每次保存的时候将代码按eslint格式进行修复
  "editor.codeActionsOnSave": {
      "source.fixAll.eslint": true
    }
}

这个规则是在保存时按照你的项目中 eslint 配置 fix

注意你~/Library/Application Support/Code/User/settings.json 中的规则尽量不要与项目中的规则产生冲突

7.5 overlay

devServer.client.overlay 控制当出现编译错误或警告时,在浏览器中是否显示全屏覆盖。默认显示boolean = true object: { errors boolean = true, warnings boolean = true }

关闭浏览器全屏覆盖提示

webpack.config.js

module.exports = {
  //...
  devServer: {
    client: {
      overlay: false,
    },
  },
};

如果你只想显示错误信息: webpack.config.js

module.exports = {
  //...
  devServer: {
    client: {
      overlay: {
        errors: true,
        warnings: false,
      },
    },
  },
};

8.js兼容处理

8.1 什么是JS兼容性处理?

其实就是把ES6及以上(后边简写为ES6+)新增的语法、API处理成ES5及以下的版本,解决某些浏览器(ie)上的兼容性报错问题。

8.2 babel的介绍

babel的出现,就是用于解决这样的问题,它是一个编译器,可以把不同标准书写的语言,编译为统一的、能被各种浏览器识别的语言

image-20220803151528080
image-20220803151528080

由于语言的转换工作灵活多样,babel的做法和postcss、webpack差不多,它本身仅提供一些分析功能,真正的转换需要依托于插件完成

image-20220803151550097
image-20220803151550097

8.3 babel的安装

babel可以和构建工具联合使用,也可以独立使用

如果要独立的使用babel,需要安装下面两个库:

  • babel-loader: 在 webpack 里应用 babel 解析 ES6 的桥梁
  • @babel/core: babel 核心模块
  • @babel/preset-env: 简单理解:就是一组 Babel 插件, 扩展 Babel 功能
    • @babel/preset-env: 一个智能预设,允许您使用最新的 JavaScript。
    • @babel/preset-react:一个用来编译 React jsx 语法的预设
    • @babel/preset-typescript:一个用来编译 TypeScript 语法的预设
pnpm i -D babel-loader @babel/core @babel/preset-env

regeneratorRuntime

这里有时还会有一些特殊的语法 例如 async await等,这样的语法就不好转换,因此还需要一个新的 库来处理,就需要 regenerator-runtime 来进行处理转换。

pnpm i @babel/runtime @babel/plugin-transform-runtime -D

8.4 webpack配置

配置文件

配置文件由很多种写法:

  • babel.config.*

    :新建文件,位于项目根目录

    • babel.config.js
    • babel.config.json
  • .babelrc.*

    :新建文件,位于项目根目录

    • .babelrc
    • .babelrc.js
    • .babelrc.json
  • package.jsonbabel:不需要创建文件,在原有文件基础上写

Babel 会查找和自动读取它们,所以以上配置文件只需要存在一个即可

babel.config.js

module.exports = {
  // 预设
  presets: ['@babel/preset-env'],
  plugins: [['@babel/plugin-transform-runtime']],
};

webpack.config.js

module: {
             /*
    js 兼容性处理:babel-loader @babel/core @babel/preset-env
        1. 基本js兼容性处理 --> @babel/preset-env
            问题:只能转换基本语法,如Promise不能转换
        2. 全部js兼容性处理 --> @babel/polyfill   使用时直接在js文件里面引入:import '@babel/polyfill'
            问题:我只要解决部分兼容性问题,但是将所有兼容性代码全部引入,体积太大了
        3. 需要做兼容性处理的就做:按需加载 --> core-js
            */
  rules: [{
    test: /\.js$/,  // 处理后缀名为js的文件
    exclude: /node_modules/,  //排除node_modules下的文件
    // 加载 loader
 	loader: 'babel-loader',
  }] 
}
image-20220803215034506
image-20220803215034506

然后运行就可以了,会把对应的es6语法转换成es5语法。

9.代码分离

我们的 js 都是打包输出到一个文件中的,当内容越来越多的时候,会导致单个文件体积十分巨大,所以我们就需要对代码进行分割,将一个巨大的文件,分割成多个中小型文件,然后可以按需加载或并行加载这些文件

代码分离是 webpack 中最引人注目的特性之一。此特性能够把代码分离到不同的 bundle 中,然后可以按需加载或并行加载这些文件。代码分离可以用于获取更小的bundle,以及控制资源加载优先级,如果使用合理,会极大缩减加载时间。

常用的代码分离方法有三种:

  • 入口起点:使用 entry 配置手动地分离代码。
  • 动态导入:通过模块的内联函数调用来分离代码
  • 防止重复:使用 SplitChunksPlugin 去重和分离 chunk。

9.1 入口起点( 不推荐 )

使用 entry 配置手动分离代码,如果多个入口共享的文件,会分别在每个包里重复打包。

module.exports = {
  entry: {
    main: './assets/js/main.js',
    other: './assets/js/add.js'
  },
  output: {
    filename: 'js/[name].bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
}

这种方式的确存在一些隐患:

  • 如果入口 chunk 之间包含一些重复的模块,那些重复模块都会被引入到各个 bundle 中。
  • 这种方法不够灵活,并且不能动态地将核心应用程序逻辑中的代码拆分出来。

9.2 防止重复

配置 entry 提取公用依赖

webpack.config.js

module.exports = {
  entry: {
    main: {
      import: './assets/js/main.js',// 启动时需加载的模块
      dependOn: 'shared'// 当前入口所依赖的入口
    },
    other: {
      import: './assets/js/add.js',
      dependOn: 'shared'
    },
    shared: 'lodash' // 当上面两个模块有lodash这个模块时,就提取出来并命名为shared chunk
  },
  output: {
    filename: 'js/[name].bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
}

执行webpack命令,可以看到打包结果

已经提取出来shared.bundle.js,即为提取打包了lodash公用模块

index.bundle.js other.bundle.js体积也变小

查看dist/index.html可以看到三个文件都被加载了

SplitChunksPlugin

SplitChunksPlugin 插件可以能自动的帮助我们做公共模块的抽离。让我们使用这个插件,将之前的示例中重 复的 lodash 模块去除:

webpack 中 splitChunks 的默认配置

module.exports = {
  entry: {
    main: './assets/js/main.js',
    other: './assets/js/add.js'
  },
  output: {
    filename: 'js/[name].bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
  optimization: {
  splitChunks: {
    chunks: 'all', // 动态导入的模块其依赖会根据规则分离
    minSize: 30000, // 文件体积要大于 30k
    minChunks: 1, // 文件至少被 1 个chunk 引用
    maxAsyncRequests: 5, // 动态导入文件最大并发请求数为 5
    maxInitialRequests: 3, // 入口文件最大并发请求数为 3
    automaticNameDelimiter: '~', // 文件名中的分隔符
    name: true, // 自动命名
    cacheGroups: {
      vendors: { // 分离第三方库
        test: /[\\/]node_modules[\\/]/,
        priority: -10 // 权重
      },
      default: { // 分离公共的文件
        minChunks: 2, // 文件至少被 2 个 chunk 引用
        priority: -20,
        reuseExistingChunk: true // 复用存在的 chunk
      }
    }
  }
}
}

chunks

该参数有四种取值

  • async:动态导入的文件其静态依赖会根据规则分离
  • initial:入口文件的静态依赖会根据规则分离
  • all:所有的都会根据规则分离
  • chunk => Boolean:返回 true 表示根据规则分离,false 则不分离

更多配置查看: SplitChunksPluginopen in new window

可以通过 import 方法对文件名进行自定义

import(/* webpackChunkName: '自定义文件名' */'文件路径')

9.3 动态导入

当涉及到动态代码拆分时,webpack 提供了两个类似的技术。

  • 第一种,也是推荐选择的方式是,使用符合 ECMAScript 提案 的 import() 语法 来实现动态导入。
  • 第二种,则是 webpack 的遗留功能,使用 webpack 特定的 require.ensure 。

这里让我们尝试使用第一种方式,使用import()来进行动态导入

我们分别在 index.html、other.js、index.js 中添加如下代码

// index.html body 中添加
<button id="btn">点我</button>

// other.js
console.log("我被加载了")

// index.js
let btn = document.querySelector("#btn");
btn.addEventListener("click", () => {
  // import() 返回的是一个 promise,在 then() 方法中执行导入后的操作,也可以使用 async/await
  import("./other").then(res => {
    console.log(res);
  });
});

image-20220803154417374
image-20220803154417374

可以看到,打包文件多生成了一个叫 0.js 的文件,并且 html 中并没有引入该文件,点击按钮再看看会发生什么

image-20220803154506870
image-20220803154506870

生成了一个 script 标签,并加载了 0.js 文件,这就是动态导入。对于这个文件名称,不是很直观,我们使用魔法注释修改一下

import(/* webpackChunkName: "other" */ "./other").then(res => {  console.log(res); });

最终打包生成的文件名就叫 other.js,如果希望加上 hash 值,可以在配置文件里添加一个参数

output: {
  filename: "[name].[chunkhash:10].js", // 入口文件打包生成的文件名
  chunkFilename: "[name].[chunkhash:10].js" // 动态模块打包生成的文件名,name 默认为数字,如果使用了魔法注释则为魔法注释的名字
}

9.4 懒加载

懒加载或者按需加载,是一种很好的优化网页或应用的方式。这种方式实际上是先把你的代码在一些逻辑断点处分离开,然后在一些代码块中完成某些操作后,立即引用或即将引用另外一些新的代码块。这样加快了应用的初始加载速度,减轻了它的总体体积,因为某些代码块可能永远不会被加载。

math.js

export function add (x, y) {
  return x + y
}

export function reduce (x, y) {
  return x - y
}

src/index.js

const button = document.createElement('button')
button.textContent = '点击执行加法运算'
button.addEventListener('click', () => {
 // 魔法注释 webpackChunkName 修改懒加载打包文件名
 // 即使不使用 webpackChunkName,webpack 5 也会自动在 development 模式下分配有意义的文件名。
 import(/* webpackChunkName: 'math' */ './math.js').then(({ add }) => {
  console.log(add(4, 5))
 })
}) 
document.body.appendChild(button)

效果:

点击按钮后才加载math.bundle.js并执行了函数打印输出结果

image-20220803154940332
image-20220803154940332

第一次加载完页面, math.bundle.js 不会加载,当点击按钮后,才加载 math.bundle.js 文件。

问题:

这样做可能会让用户的交互长时间没有响应的。原因就是待到交互时才进行模块的加载,可能时间会比较长。由此,我们引入prefetch,即预取。

9.5 预获取、预加载

Webpack v4.6.0+ 增加了对预获取和预加载的支持。

在声明 import 时,使用下面这些内置指令,可以让 webpack 输出 “resource hint(资源提示)”,来告知浏览器:

  • prefetch(预获取):将来某些导航下可能需要的资源(当页面所有内容都加载完毕后,在网络空闲的时候,加载资源)
  • preload(预加载):当前导航下可能需要资源

prefetch

src/index.js

const button = document.createElement('button')
button.textContent = '点击执行加法运算'
button.addEventListener('click', () => {
 // webpackPrefetch: true 在动态引入时开始预获取
 import(/* webpackChunkName: 'math', webpackPrefetch: true */ './math.js').then(({ add }) => {
  console.log(add(4, 5))
 })
})
document.body.appendChild(button)

添加第二句魔法注释:webpackPrefetch: true

告诉 webpack 执行预获取。这会生成 并追加到页面头部,指示着浏览器在闲置时间预取 math.js 文件。

效果 可以看到math.bundle.js已经预先获取了

加载完成之后,浏览器又会去自动加载

image-20220803155257733
image-20220803155257733

我们发现,在还没有点击按钮时, math.bundle.js 就已经下载下来了。同时,在 app.html 里webpack自动添加了一句:

image-20220803155320537
image-20220803155320537

点击按钮,会立即调用已经下载好的 math.bundle.js 文件中的 add 方法:

preload

preload和prefetch在用法上相差不大,效果上的差别如下(引自官方文档):

  • preload chunk 会在父 chunk 加载时,以并行方式开始加载。prefetch chunk 会在父 chunk 加载结束后开始加载。
  • preload chunk 具有中等优先级,并立即下载。prefetch chunk 在浏览器闲置时下载。
  • preload chunk 会在父 chunk 中立即请求,用于当下时刻。prefetch chunk 会用于未来的某个时刻。 浏览器支持程度不同。

10.缓存

以上,我们使用 webpack 来打包我们的模块化后的应用程序,webpack 会生成一个可部署的 /dist 目录,然后把打包后的内容放置在此目录中。只要 /dist 目录中的内容部署到服务器上,客户端(通常是浏览器)就能够访问网站此服务器的网站及其资源。而最后一步获取资源是比较耗费时间的,这就是为什么浏览器使用一种名为 缓存open in new window 的技术。可以通过命中缓存,以降低网络流量,使网站加载速度更快,然而,如果我们在部署新版本时不更改资源的文件名,浏览器可能会认为它没有被更新,就会使用它的缓存版本。由于缓存的存在,当你需要获取新的代码时,就会显得很棘手。

此指南的重点在于通过必要的配置,以确保 webpack 编译生成的文件能够被客户端缓存,而在文件内容变化后,能够请求到新的文件。

10.1 输出文件的文件名

通过使用 output.filename 进行文件名替换open in new window,可以确保浏览器获取到修改后的文件。[hash] 替换可以用于在文件名中包含一个构建相关(build-specific)的 hash,但是更好的方式是使用 [chunkhash] 替换,在文件名中包含一个 chunk 相关(chunk-specific)的哈希。

让我们使用起步open in new window 中的示例,以及管理输出open in new window 中的 plugins 来作为项目的基础,所以我们不必手动处理维护 index.html 文件:

project

webpack-demo
|- package.json
|- webpack.config.js
|- /dist
|- /src
  |- index.js
|- /node_modules

webpack.config.js

module.exports = {
//...
output: {
    filename: 'scripts/[name].[contenthash].js',

   //...
},

//...
}

执行打包编译:

image-20220803160420311
image-20220803160420311

可以看到,bundle 的名称是它内容(通过 hash)的映射。如果我们不做修改,然后再次运行构建,文件名会保持不变。

10.2 缓存第三方库

将第三方库(library)(例如 lodashreact)提取到单独的 vendor chunk 文件中,是比较推荐的做法,这是因为,它们很少像本地的源代码那样频繁修改。因此通过实现以上步骤,利用客户端的长效缓存机制,可以通过命中缓存来消除请求,并减少向服务器获取资源,同时还能保证客户端代码和服务器端代码版本一致。 我们在 optimization.splitChunks 添加如下 cacheGroups 参数并构建:

module.exports = {
//...

optimization: {
//...

splitChunks: {
 cacheGroups: {
   vendor: {
     test: /[\\/]node_modules[\\/]/,
     name: 'vendors',
     chunks: 'all',
   },
 },
}
},
}

执行编译:

image-20220803161022142
image-20220803161022142

截止目前,我们已经把 JS 文件、样式文件及图片等资源文件分别放到了 scriptsstylesimages三个文件夹中。

11.js压缩

安装插件 terser-webpack-plugin

pnpm i terser-webpack-plugin -D

配置

const TerserWebpackPlugin = require("terser-webpack-plugin")

module.exports = {
  ...
  optimization: {
    minimizer: [
      // 使用插件压缩 js 代码 (生产模式)
      new TerserWebpackPlugin({
          parallel: true, //多线程压缩
          extractComments: false //不要注释-因为默认会对每个压缩的文件生成一个txt的注释文本。没必要
        })
    ]
  }
}

这个插件,当mode是production的时候,执行这个配置打包成js压缩的形式,而开发环境时,则是打包成原始的js。 插件的使用文档:https://webpack.docschina.org/plugins/terser-webpack-plugin/open in new window

已到达文章底部,欢迎留言、表情互动~
  • 赞一个
    0
    赞一个
  • 支持下
    0
    支持下
  • 有点酷
    0
    有点酷
  • 啥玩意
    0
    啥玩意
  • 看不懂
    0
    看不懂
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.15.8