webpack5通关

321 阅读17分钟

「这是我参与2022首次更文挑战的第2天,活动详情查看:2022首次更文挑战」。

入门webpack

webpack是一个静态的模块化打包工具。从入口文件开始,解析代码,生成一个依赖关系图。然后遍历关系图,不同的类型的文件使用相应的loader进行解析,最后打包成一个个模块。

安装

npm install webpack webpack-cli -g // 全局安装
npm install webpack webpack-cli -d // 局部安装

基本配置

// 默认入口:
根路径下的src/index.js

// 默认出口:
根路径下的dist文件夹

// 指定入口,出口:
npx webpack --entry ./src/main,js --output-path ./build

// 默认配置文件路径:
根路径下的webpack.config.js

// 自定义配置文件路径
npx webpack --config ./cyj.config.js

module.exports = {
    entry: "./src/main.js",
    output: {
        filename: "bundle.js",
        path: path.resolve(__dirname, "./dist")
    }
}

// 在文档- api - 命令行接口(cli) 中查看更多命令

执行

  • npx命令会执行当前路径下的node_module/.bin路径下的命令。也可以在package.json里配置script命令简化命令。
  • 如果不使用npx,使用的是全局的webpack。
npm init -y
npm install webpack webpack-cli -d // 局部安装
npx webpack // 开始打包

常见loader

loader用于解析不同类型文件的工具,webpack默认只能解析js文件,解析css文件需要css-loader,解析图片需要file-loader。

css-loader

npm install -d css-loader // 解析css文件
npm install -d style-loader // 将解析的css生成sryle标签添加到html页面中
npm install -d less
npm install -d less-loader
let path = require("path")

module.exports = {
  entry: "./src/entry",
  output: {
    filename: "my-bundle.js",
    path: path.resolve(__dirname, "./my-dist")
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"], // 倒序执行
      },
      {
        test: /\.less$/,
        use: ["style-loader", "css-loader", "less-loader"],
      },
    ],
  },
}

borwserslist

  • 官方说明:在不同前端工具之间共享目标浏览器和 Node.js 版本的配置。其实是:根据配置文件,查询包含的浏览器列表,为其他插件提供需要兼容的浏览器列表。
  • 安装webpack时自动安装了,无需手动安装
// 新建.browserslistrc文件:

>1%
last 2 version
not dead

PostCSS

  • 获取browserslist提供的浏览器列表,配合插件完成一系列工作
  • PostCSS是一个平台,通过其生态里的插件,可以实现兼容css等问题
  • 例如使用autoprefixer插件,可以为css添加浏览器前缀,实现浏览器兼容

postcss-loader

  • 下面案例中style-loader的importLoaders属性表示:当style-loader处理的css文件中含有@import "a.less"时,会将该css交给前面第几个loader来处理。案例中为2,所以会将该less文件交给less-loader处理,并按顺序执行。
// 安装依赖
npm install postcss-loader -d
npm install autoprefixer -d

// 新建:postcss.config.js
// postcss-loader执行时会加载该配置文件
module.exports = {
  plugins: [
    require('autoprefixer')
  ]
}

// webpack.config.js
module: {
    rules: [
      {
        test: /\.less$/,
        use: [
          "style-loader",
          {
            loader: "css-loader",
            options: {
              importLoaders: 2
            }
          },
          "postcss-loader", // here
          "less-loader"
        ],
      },
    ],
  },

postcss-preset-env

  • 属于PostCSS的插件,是多个插件的集合,包含autoprefixer。
  • 具有:处理兼容css,根据浏览器列表添加所需的polyfill,将16进制颜色转换为浏览器兼容的rgba等特点
// install
npm install postcss-preset-env -d

// postcss.config.js
module.exports = {
  plugins: [
    "postcss-preset-env"
  ]
}

// webpack.config.js
添加loader: "postcss-loader"

file-loader

  • 将通过import、require方式引入的jpg,png等格式的图片输出到目标文件夹中
  • 也可以加载字体文件,例如eot,ttf等格式
  • css中的的background-img不生效
// install
npm install file-loader -d

// webpack.config.js
{
  test: /\.(png|jpe?g|gif|svg)$/i,
  use: [
    {
      loader: "file-loader",
      options: {
        name: "[name][hash:8].[ext]",
        outputPath: "images"
      },
    },
  ]
}

url-loader

  • 也是处理图片资源的,它会将limit大小内的图片转换为base64格式
  • 如果图片超过大小,那就只做file-loader相同的操作
  • 也是只处理导入的图片,而不会处理css里的背景图片
{
  test: /\.(png|jpe?g|gif|svg)$/i,
    use: [
      {
        // loader: "file-loader",
        loader: "url-loader",
        options: {
          name: "[name][hash:8].[ext]",
          outputPath: "images",
          limit: 10 * 1024 // 10kb以内的图片转换为base64格式
        },
      },
    ]
}

asset module type

  • webpack5资源文件处理方式,用于替代:file-loader, url-loader, raw-loader
  • 具有四种模块类型
    • asset/resource // 替代file-loader
    • asset/inline // 替代url-loader,但是无limit选项,会将资源转换为dataUrl,嵌入到html中
    • asset/source // 导出资源的源代码,替代raw-loader
      • 使用场景:导入一个txt文本模块,获取文本内容
    • asset // 根据配置的文件体积大小限制,在单独导出文件和转换为base64格式之间选择
// asset/resource
{
    test: /\.(png|jpe?g|gif|svg)$/i,
    type: "asset/resource",
    generator: {
        filename: "img/[name].[hash].[ext]"
    }
}
// 打包字体文件
{
    test: /\.(woff2?|eot|ttf)$/i,
    type: "asset/resource",
    generator: {
        filename: "font/[name].[hash:6].[ext]"
    }
}

// 特点:可以实现css里的背景图片的加载
// asset
{
    test: /\.(png|jpe?g|gif|svg)$/i,
    type: "asset",
    generator: {
        filename: "img/[name].[hash:6].[ext]"
    },
    parser: {
        dataUrlCondition: {
            maxSize: 100 * 1024 // 100k以内b的图片会被压缩为base64格式
        }
    }
}

// 特点:可以实现css里的背景图片的加载
// asset/source
{
    test: /\.txt/,
    type: "asset/source"
}

// main.js
import exampleText from './assets/example.txt'

console.log("文本内容:", exampleText);

常见plugin

  • loader是用于特定模块类型进行转换
  • plugin有更广泛的任务,比如打包优化,资源管理,环境变量注入等

clean-webpack-plugin

  • 打包前删除dist文件夹
// install
npm install clean-webpack-plugin -d

// webpack.config.js
const { CleanWebpackPlugin } = require("clean-webpack-plugin")

plugins: [
    new CleanWebpackPlugin()
]

html-webpack-plugin

  • 用于在dist文件夹中生成html模板
npm install html-webpack-plugin -d

// webpack.config.js
const HtmlWebpackPlugin = require("html-webpack-plugin")

plugins: [
    new HtmlWebpackPlugin()
]
  • 自定义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>
  <p>
    <%= htmlWebpackPlugin.options.abc %>
  </p>
  <div>这是一段<span>文字</span></div>
</body>
<script src="./my-dist/my-bundle.js"></script>

</html>
    
plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
        title: "自定义模板",
        template: "./index.html",
        abc: "ddd"
    	// 在这里定义的变量会挂载到htmlWebpackPlugin.options上
        // 可以在自定义模板中使用自定义的变量
    })
]

DefinePlugin

  • 在编译时,创建全局变量
  • webpack内置的插件,不需要单独安装
// 观察其他脚手架创建的自定义模板发现:
<link rel="icon" href="<%= BASE_URL %>favicon.ico">

// webpack.config.js
const { DefinePlugin } = require("webpack");
plugins: [
    new DefinePlugin({
        BASE_URL: '"./"'
        // 这里定义的变量也可以在自定义模板中使用
    })
]

copy-webpack-plugin

  • 复制文件
  • 将指定文件夹里的文件复制到dist文件夹中
npm install copy-webpack-plugin -d

const copyWebpackPlugin = require("copy-webpack-plugin")
plugins: [
  new CopyWebpackPlugin({
    patterns: [
      {
        from: "./public",
        globOptions: {
          ignore: [
            '**/index.html',
            '**/.DS_Store',
            '**/test02.js',
          ]
        }
      }
    ]
  })
]

babel

  • 一个工具链,主要用于将es6+的代码转换为es5格式的代码
  • 包含语法转换、源代码转换、polyfill实现目标浏览器缺少的功能

命令行使用

// 安装babel核心库和babel命令行工具
npm install @babel/core -d
npm install @babel/cli -d

// 安装插件
// 安装箭头函数转换插件
npm install @babel/plugin-transform-arrow-functions -d

// 安装const,let变量转换工具(块作用域插件)
npm install @babel/plugin-transform-block-scoping -d

// 使用:
npx babel 入口文件/文件夹 --out-dir 输出文件夹 --plugins=插件名,插件名

npx babel src/entry/btest1.js --out-dir my-dist --plugins=@babel/plugin-transform-block-scoping,@babel/plugin-transform-arrow-functions

使用预设

  • 如果需要安装的插件非常多,一个个安装是非常慢的。使用该预设,可以根据需要兼容的浏览器列表,进行安装相应的插件。
npm install @babel/preset-env -d

npx babel 入口文件/文件夹 --out-dir 输出文件夹 --presets=@babel/preset-env

babel-loader

npm install @babel/core -d
npm install babel-loader -d
npm install @babel/plugin-transform-block-scoping -d
npm install @babel/plugin-transform-arrow-functions -d

// webpack.config.js
{
    test: /\.m?js$/,
        use: {
            loader: "babel-loader",
                options: {
                    plugins: [
                        "babel/plugin-transform-babel-scoping",
                        "babel/plugin-transform-form-arrow-function"
                    ]
                }
        }
}

使用预设

  • 预设:解决某个问题,所需插件的集合
npm install @babel/core -d
npm install babel-loader -d
npm install @babel/preset-env -d

// webpack.config.js
{
    test: /\.m?js$/,
    // 第三方库都是已经编译好了的,避免重复编译,提高编译速度
    exclude: /node_modules/,
    use: {
        loader: "babel-loader",
    }
}

// babel.config.js
module.exports = {
  presets: [
    ["@babel/preset-env"]
  ]
}

polyfill

  • 当我们使用了一些新的语法特性(如promise,generator,symbol等),而浏览器不支持这些功能,就会报错。使用polfill来为浏览器打个补丁,浏览器就会支持该特性了。
  • polyfill默认是将所有的特性添加到全局 使用
npm install core-js regenerator-runtime --save

{
    test: /\.m?js$/,
    // 第三方库都是已经编译好了的,避免重复编译,提高编译速度
    exclude: /node_modules/,
    use: "babel-loader"
}

// babel.config.js
module.exports = {
  presets: [
    ["@babel/preset-env", {
        // false(默认): 打包后的文件不使用polfill
        // usage: 根据源代码中使用到的新特性,自动引入对应的polfill
        // entry:导入所有的polfill,需要在入口文件添加import 'core-js/stable'和import 'regenerator-runtime/runtime'
        useBuiltIns: "usage",
        corejs: 3.8 // 根据安装decore-js依赖版本决定
    }]
  ]
}

@babel/plugin-transform-runtime

在局部作用域中添加新特性(polyfill),防止污染全局。常用于开发第三方库。

打包react

  • 使用@babel/preset-react预设
  • webpack加载入口文件,发现是js格式,交给babel-loader处理,根据react的预设,添加相关的插件(解析jsx代码等),最后将react代码进行打包。

使用

npm install ract react-dom -s
npm install @babel-core -d
npm install babel-loader -d
npm install @babel/preset-react -d

// webpack.config.js
{
    test: /\.m?js$/,
    // 第三方库都是已经编译好了的,避免重复编译,提高编译速度
    exclude: /node_modules/,
    use: {
        loader: "babel-loader",
    }
}
    
// babel.config.js
module.exports = {
  presets: [
    ["@babel/preset-env"],
    ["@babel/preset-react"]
  ]
}
    
// react-test.js
import React from "react";
import ReactDom from "react-dom";

const app = (props) => {
  return (
    <div>{props.children}</div>
  )
}
ReactDom.render(<app><h1>hello react</h1></app>, document.getElementById("app"))

打包ts

  • 使用typescript包自带的compile
npm install typesctipt -g
cmd: tsc index.ts
  • ts-loader tsconfig.js文件包含转换后的js文件使用es几语法的配置,使用哪种模块化方式等。 这时你可能会问,这些事不是应该由babel来处理嘛?但是要知道babel是处理js文件的,需要先将ts转为js才能交给bebel处理。
npm install ts-loader -d
npm install typescript -d
// 项目根目录下创建tsconfig.js
tsc --init
npx webpack
  • babel-loader

除了可以使用typescript compiler来编译typescript之外,还可以使用babel

npm install @babel/preset-typescript -d
  • ts-loader与babel-loader如何选择
    • ts-loader
      • 只能将ts转换为js,如果这个过程中需要polyfill,ts-loader无能为力,需要借助babel
      • 对错误类型进行检测,即遇到类型错误会报错
    • babel-loader
      • 可以实现polyfill
      • 无法在babel-loader编译过程中,不会对错误类型进行检测,即遇到类型错误不会报错
    • 最佳实践
      • ts官网推荐使用babel来进行代码转换,使用tsc来进行类型检查
      • npx tsc --noEmit --watch // 在打包前先进行类型检测,watch会实时监听(可选)

打包vue

npm install vue vue-loader vue-template-compiler -d

// webpack.config.js
import { VueLoaderPlugin } from "vue-loader";
{
    test: /\.vue$/,
    use: "vue-loader",
}
plugins: [
    new VueLoaderPlugin()
]
// test.vue

// index.js
import Vue from "vue";
import myComponent from "./test.vue";
new Vue({
  render: h => h(myComponent)
}).$mount("#app")
    
// 具体配置可以查看vue-loader官方文档

vscode插件:eslint和prettier

  • eslint插件会读取根目录的.eslintrc文件的里的规则,进行语法错误提示
  • prettier插件会根据自定义的.prettierrc文件,通过快捷键,可以格式化代码为所需格式

webpack-dev-server

前面开发存在的问题:每次修改完代码都需要重新打包

解决方案:

  • npx webpack --watch ,或者在在配置文件里添加watch:true
    • 依赖图中的任何一个文件改变了,都会重新打包。
    • 会在磁盘中生成新的文件
    • 使用vscode的live server实现热更新
  • webpack-dev-server
    • 打包后的文件都存放在内存中
    • 不会输出文件到dist文件夹中
npm install webpack-dev-server -s
    
npx webpack serve

具体使用参考官方文档指南部分

// 开启模块热替换(hmr)
{
    devServer: {
        hot: true, 
    }
}
// 当在模块a里导入b模块时,需要在a文件的最后添加:
// 指定该模块在更新时,进行 HMR
import component from "./b.js";
let demoComponent = component();

document.body.appendChild(demoComponent);
// HMR interface
if (module.hot) {
  // Capture hot update
  module.hot.accept("./b.js", () => {
    const nextComponent = component();
    // Replace old content with the hot loaded one
    document.body.replaceChild(nextComponent, demoComponent);
    demoComponent = nextComponent;
  });
}

配置react热更新

  • 如果需要对每个文件都进行module.hot.accept,会非常麻烦
  • vue、react脚手架内置了hmr,不用手动去添加accept。vue使用vue-loader来支持hmr,react使用react-refresh来支持hmr
npm install @pmmmwh/react-refresh-webpack-plugin react-refresh -d

// webpack.config.js
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
new ReactRefreshWebpackPlugin()

// 注意这里没有设置mode的话,报错信息显示不完整
devServer: {mode:"development", hot:true}

// babel.config.js
plugins: [
    ['react-refresh/babel']
]

配置vue热更新

vue-loader默认就配置了

HMR原理图

hmr原理图.png

  • webpack-dev-server插件开启了两个服务(http服务,socket服务)。
  • http服务用于存放打包后的静态资源,供浏览器访问。
  • 另一个是socket服务,当源代码发生修改时,HMR Server会生成两个文件(json文件和js文件),json文件记录着修改的内容,js文件为被修改的源文件(不是重新打包后的bundle.js)。将这两个文件发送给浏览器HMR runtime,浏览器这边的socket服务根据这两个文件更新浏览器中的代码。

webpack.config.js配置

source-map

  • 本质是一个信息文件,存储着代码转换前后的对应位置信息,因为代码经过打包后,生产环境与开发环境的代码有差异,source-map文件解决了难以定位报错位置的问题
  • 通过设置devtools的属性,决定webpack打包生成source-map的格式,一共有26个选项。大概分为:
    • 1,source-map与bundle.js文件合并
    • 2,分离
    • 3,不生成source-map文件
  • 最佳实践:
    • 开发阶段:source-map或者cheap-module-source-map,这里分别是vue,react脚手架的配置
    • 测试阶段:同开发阶段
    • 生产环境:false,不写

publicPath

  • 用于对打包后dist文件夹下的文件的路径的拼接
  • output中有两个很重要的属性:pathpublicPath
  • path:用于指定文件的输出路径(比如打包的html、css、js等),是一个绝对路径,通常使用path.resolve()进行拼接。
  • publicPath:默认是一个空字符串,它是我们项目资源指定的一个公共的路径,其实就是为我们打包的资源添加一个路径:资源的路径 = output.publicPath + 打包资源的路径(比如"js/[name].bundle.js")
  • 比较常设置的是两个值:
    • "./":本地环境下可以使用这个相对路径;
    • "/":服务器部署时使用,服务器地址 + /js/[name].bundle.js;
    • webpack默认为: "",vue脚手架默认设置为: "/"
    • 当在本地使用file协议打开时,需要改为:"./"

优化

模块解析

用于寻找模块的绝对路径

// webpack.config.js
resolve: {
    // 常用的两个配置
    alias: // 别名
    extensiobs: [] // 用于省略文件后缀名
}
// 在文档的 配置-> 解析 里查看

环境分离

npx webpack --config webpack.dev.js
npx webpack --config webpack.prod.js
或者:npx webpack --config webpack.config.js --env product

module.exports = function(env) {
    const isProduction = env.profuction;
}

代码分离

  • 多入口文件
    • 每个入口文件都会生成一个js文件,每个js文件都会生成script:src标签添加到html中
  • 防止重复
    • 每个入口文件都会将自己依赖的包,打包到一个bundle文件中。如果多个入口文件都依赖某个库,会造成重复文件。
    • 解决:
      • 1,可以通过配置entry解决
      • 2,使用SplitCnunksPlugin插件
// entry
module.exports = {
  mode: "development",
  entry: {
    main: { import: "./src/main.js", dependOn: 'utils' },
    index: { import: "./src/index.js", dependOn: 'utils' },
    utils: ["./src/components/common.js"],
  },
  output: {
    filename: "[name].[hash:8].js",
    chunkFilename: "[name].[hash:8].js"
  },
}
// 最终dist文件夹下会有三个文件:main,index,utils.js
optimization: {
 splitChunks: {
   chunks: 'all', // 不管是异步还是啥情况导入的包,都进行拆分
 },
},
// 更多配置查看Plugin -> SplitCnunksPlugin    

模块懒加载

  • 当点击按钮时,才加载该模块
  • 以下案例中魔法注释的意思
    • webpackChunkName:打包后的文件名: cus-b-module.bundle.js
    • webpackPrefetch:是否开启预加载:会在所有js加载完后加载该文件,等点击按钮时,会从缓存中提取出来执行,可以观察chrom network
document.querySelector("#btn").addEventListener("click", function () {
  import(
    /* webpackChunkName: "cus-b-module" */
    /* webpackPrefetch: true */
    "./b").then(module => {
      console.log(module)
    })
})

打包运行时加载的模块

配置optimization.runtimeChunk属性,是否将运行时动态导入的模块打包到单独的文件(例如以上使用import()函数导入的文件),可以设置为true或者single

  • true:为每一个入口文件创建一个运行时bundle
  • single:将所有入口的的动态导入的包打包到一个bundle
  • 也可以使用对象

配置CDN

webpack打包默认会将依赖图中的所有库都打包,如何将第三方库配置cdn呢?

// 1,webpack.config.js
externals: {
	// 排除使用cdn的包,包名: 全局对象
    jquery: "jQuery",
    lodash: "_"
}

// 2,模板html的底部添加scrpit:src标签
  • 环境分离,开发环境不使用cdn,生产环境使用cdn

    • 1,将externals移到webpack.dev.js中
  • 2,使用ejs语法,因为DefinePlugin插件提供功能的环境变量

cdn配置.png

拆分css

MiniCssExtractPlugin插件

// webpack.config.js
import MiniCssExtractPlugin from "mini-css-extract-plugin"
plugins: [
    new MiniCssExtractPlugin({
        filename: "css/[name].[contenthash:8].css",
        chunkFilename: "css/[name].[contenthash:8].css"
    })
]
// 将style-babel替换为:MiniCssExtractPlugin.loader

hash,contentHash,ChunkHash

压缩js

  • terserWebpackPlugin插件
  • webpack5内置了该插件,无需手动安装
  • 特点:用于压缩代码,丑化代码,将变量名改短
  • 具体配置查看webpack官网->Plugin -> TerserWebpackPlugin
  • 也可以看terser的github首页

压缩css

css-minimizer-webpack-plugin插件,用于去除多余空格

npm install css-minimizer-webpack-plugin

// webpack.config.js
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin")
plugins: [
    new CssMinimizerPlugin()
]

scoping hoisting

  • webpack打包后的文件是包含在自执行函数里面或者闭包中,当某个包需要调用另一个包里的函数,就需要先执行该闭包才能拿到变量或者函数。很繁琐。使用该插件将提升作用域,即去除闭包和自执行函数。

  • 在mode:production模式下已经添加了该插件。

  • webpack官网->Plugin->ModuleConcatenationPlugin

DLL

某个包不想每次都进行打包,可以先将该包打包,然后在新项目中直接引用,而不需要重新打包。

Tree Shaking

  • 消除项目中未使用的代码
  • webpack实现tree shaking的两种方式:usedExportssideEffects

usedExports

  • 设置为true后,会将导入的文件中没有被使用的函数、变量通过魔法注释进行标记:/* unused hormony export sum */
  • 再使用terser插件,打包时会将标记的内容进行删除。
optimzation: {
    usedExports: true, // 使用usedExports
    minimize: true, // 开启优化
    minimizer: [
        // 使用terser
        new TerserPlugin({})
    ]
}

sideEffects

  • 用于告知webpack compiler哪些文件有副作用
  • 副作用是指会影响倒其他模块的内容,例如一个函数里面会修改其他对象/变量的值。
package.json根路径下添加:
sideEffects: true; // 代表所有文件都是有副作用的,告知webpack不可以删除没有使用到的代码
sideEffects: false; // 代表所有文件都是没有副作用的,告知webpack可以安全删除没有使用到的代码
如果有某个文件是有副作用的怎么办?
sideEffects: [
    "./src/util/Format.js" // 表示该文件有副作用,会删除没有使用的代码,保留有副作用的代码
]
如果在js中导入css,会被当做没有使用到的代码,怎么处理?
方法一
sideEffects: [
    "**.css"
]
方法二
在css、less的rule里面添加sideEffects: true,

设置为false// index.js
import {sum} from "math.js"
如果没有对math.js中的sum进行使用,那么math.js会在打包时被删除,无论该math.js中是否有副作用的代码。
如果设置上terser,也不会保留有副作用的代码

设置为true后
无论有没有使用math.js的sum,都会保留math代码中所有内容,
如果设置上terser,将会保留该被导入文件中含有副作用的代码

实现css的tree shaking

删除没有用到过的css,webpack项目里的所有代码html、css等代码都会由document.createElement,和dom.style.xxx来实现,所以webpack可以在打包的过程中判断哪些css没有用到

npm install purgecss-webpack-plugin -d

// webpack.config.js
const glob = require("glob")
const PurgecssPlugin = require("purgecss-webpack-plugin")
new PurgecssPlugin({
    path:glob.sync(`${resolveApp('./src')}/**/*`,{nodir: true}),
    safelist: function(){
        return {
            standard: ["html","body"] // 保留css里的html。body选择器
        }
    }
})

const path = require('path');

// node中的api
const appDir = process.cwd();
const resolveApp = (relativePath) => path.resolve(appDir, relativePath);

module.exports = resolveApp;

http压缩

是一种内置在浏览器和服务器端的,以改进传输速率利用率的方式

流程,当请求头包含:accept-Encoding: gzip时,表明客户端可以接受gizp压缩后的格式文件。服务器端会将请求的资源通过gzip格式压缩文件,并返回。

webpack可以实现压缩功能,节约服务器端压缩的时间。

npm install compression-webpack-plugin -d

// webpack.config.js
new CpmpressionPlugin({
    // 更多属性查看文档
    test: /\.(css|js)$/,
    algorithm: "gizp",
})

优化html模板中的代码

前面使用了HtmlWebpackPlugin插件来生成html模板,它还有一些其他的配置:

	
template: "./index.html",
// inject: "body"
cache: true, // 当文件没有发生任何改变时, 直接使用之前的缓存
minify: isProduction ? {
removeComments: false, // 是否要移除注释
removeRedundantAttributes: false, // 是否移除多余的属性
removeEmptyAttributes: true, // 是否移除一些空属性
collapseWhitespace: false, // 折叠空格
removeStyleLinkTypeAttributes: true, // 比如link中的type="text/css"
minifyCSS: true, // 是否压缩style里的css
minifyJS: {
        mangle: {
                toplevel: true
        }
    }
}: false

优化runtime代码

runtime代码量不大,但是必须加载,可以内联到html中。可以使用react-dev-utils插件

npm install react-dev-utils -d

// webpack.config.js
const InlineChunkHtmlPlugin = require("react-dev-utils/InlineChunkHtmlPlugin")
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
    plugins: [
        new InlineChunkHtmlPlugin(htmlWepackPlugin, [/runtime.+\.js/])
    ]
}

打包自定义的库文件

我们自定义的一个库文件,提供多个方法,为了想在nodejs/commjs/浏览器等环境下都可以使用,就需要做如下配置:

// webpack.config.js
module.exports = {
    output: {
        // 需要支持的环境,umd:amd/commonks/浏览器
        libraryTarget: "umd",
        // 该库暴露的全局变量名,myUtils.sum(1, 2)
        library: "myUtil",
        // 默认this就行
        globalObject: "this"
    }
}
// 经过这样打包后的文件,在头部会进行当前运行环境的判断,然后将变量暴露出去。
// lib/format.js
export function dateFormat() {
    return "2020-10-20";
}
// lib/math.js
export function sum(num1, num2) {
    return num1 + num2;
}
export function mul(num1, num2) {
    return num1 * num2;
}
// index.js
import * as math from "./lib/math.js"
import * as format from "./lib/format.js"
export {
    math,
    format
}

npx webpack流程

1, 执行: npx webpack
2, 来到:node_modules/bin/webpack shell文件
3, 来到:node_modules/webpack/bin/webpack.js
	1, 找到cli对象
	2,执行if(!cli.installed),如果webpack-cli未安装,则进行一些报错。如果已经安装,执行runCli()
	3, 最后require("webpack-cli/bin/cli.js"),即执行该文件
4,来到node_module/webpack-cli/bin/cli.js
	如果webpack已安装,执行:runCLI(process.argv, orinimalModuleComiple)
5,来到node_module/webpack-cli/lib/bootstrap.js
	1, const cli = new WebpackCLI(); // 创建webpackCli的实例对象
	观察webpack-cli的构造函数
	1.1, this.webpack = require("webpack");
	1.2, 在后面会进行compiler = this.webpack(config, callback)
		config是合并后的配置文件,包含webpack.config.js以及npx webpck命令后面跟的参数
		webpack本质是一个函数
        callback如果传递了,webpack会自动调用run方法,如果没有则需要使用webpack实例对象手动调用run(callback)
	2await cli.run(args); // 
	2.1,来到run方法里面,将配置配置文件合并成option对象
    2.2,执行this.makeCommand(),
	2.2,再执行buildCommand(),创建webpack对象,开始打包
总结:
webpack-cli做的工作其实就是合并配置文件

// 简易版webpack-cli
const config = require("./webapck.config.js")
const compiler = webpack(config);
compiler.run((err, stats) => {
    if(err) console.error(err);
    	else
        console.log(stats)
})

plugin在哪个阶段执行

在webpack生命周期的任意阶段都可能执行,具体取决于该plugin监听的是哪个hook,当hook执行时,会调用注册在该hook上的监听的回调函数。

webpack配置文件里的很多属性都是一个plugin来实现的,比如入口属性就是通过EntryPlugin来实现。

webpack会有很多hook,每一个hook相当于一个生命周期,在入口插件执行的时候会将各个插件进行注册,注册其实是对某个hook进行某个动作的监听,例如:compiler.hooks.make.tapAsync("EntryPlugin", callback),对make hook监听entryplugin事件

自定义loader

loader本质是一个函数,参数为匹配的文件的内容的字符串,返回值是处理后的字符串

配置loader
1,默认loader是在node_module文件夹下的寻找的,自定义的话需要使用路径:"./myloader/yj-loader01",并配置context路径:"."
2,如何只写yj-loader01,配置resolveLoader: {modules: ["node_modules", "./yj-loaders"]}

loader执行顺序:
loader文件中除了默认导出的函数,还可以导出一个名为pitch的函数,当一个rule需要使用多个loader时,会先从前往后依次执行loader的pitch函数,然后从后往前执行默认函数

// 异步loader
// 当我们需要在异步操作之后返回结果,则需要:
module.exports = function(content) {
    const callback = this.async();
    setTimeout(() => {
        console.log("loader01", content);
        callback(null, content)
    }, 1000)
}

// 传递参数
// webpack.config.js
{
    test: /\.js$/i,
    use: {
        loader: "yj-loader01",
        options: {
            name: "why",
            age: 18
        }
    }
}

// yj-loader01.js
const { getOptions } = require("loader-utils"); // npm install loader-utils
module.exports = function(content) {
    // 设置为异步loader
    const callback = this.async();
    // 获取参数
    const options = getOptions(this);
    setTimeout(() => {
        console.log("loader01", content, options);
        callback(null, content)
    }, 1000)
}

// 校验参数
npm install schema-utils -d

// loader01_schema.json
{
    "type": "object",
    "properties": {
        "name": {
            "type": "string",
            "descriptioon": "name属性接收string类型"
        },
        "age": {
            "type": "number",
            "description": "age 属性为number类型"
        }
    }
}
// yj-loader.js
在上方案例的settimeout函数上方添加:
const {validate} = require("schema-utils")
const loader01Schema = require("./schema/loader01_schema.json")
validate(options) // 检验

自定义md-loader

加载markdown文件

md-loader.js
使用marked库加载md文件,得到html字符串,拼接字符串:
module.export function() {
    return `var code = ${htmlStr}; export default code`;
}

main.js
import md from "./webpack-note.md"
document.body.innerHTML = md;