1 Star 5 Fork 2

nicefree / webpack-study

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README

avatar

参考:

注意:当前 node 版本要尽量用最新版本,目前 16.13.0 是没有问题的

初始化

创建项目文件夹 webpack-study

npm init -y

npm i -D webpack webpack-cli

  • npm i -D 为 npm install --save-dev 的缩写
  • npm i -S 为 npm install --save 的缩写

新建一个文件夹 src ,然后新建一个文件 main.js,写一点代码测试一下 console.log('Hello World')

配置 package.json 命令

"scripts": {
  "build": "webpack src/main.js"
}

执行 npm run build

此时如果生成了一个 dist 文件夹,并且内部含有 main.js 说明已经打包成功了

自定义配置

上面一个简单的例子只是 webpack 自己默认的配置,下面我们要实现更加丰富的自定义配置 新建一个 build 文件夹,里面新建一个 webpack.config.js

// webpack.config.js

const path = require("path");
module.exports = {
  mode: "development", // 开发模式
  entry: path.resolve(__dirname, "../src/main.js"), // 入口文件
  output: {
    filename: "output.js", // 打包后的文件名称
    path: path.resolve(__dirname, "../dist"), // 打包后的目录
  },
};

更改我们的打包命令

"scripts": {
  "build": "webpack --config build/webpack.config.js"
}

执行 npm run build 会发现生成了以下目录 其中 dist 文件夹中的 main.js 就是我们需要在浏览器中实际运行的文件 当然实际运用中不会仅仅如此,下面让我们通过实际案例带你快速入手 webpack

配置 html 模板

js 文件打包好了,但是我们不可能每次在 html 文件中手动引入打包好的 js

这里可能有的朋友会认为我们打包 js 文件名称不是一直是固定的嘛(output.js)?这样每次就不用改动引入文件名称了呀?实际上我们日常开发中往往会这样配置:

module.exports = {
  // 省略其他配置
  output: {
    filename: "[name].[hash:8].js", // 打包后的文件名称, hash已被启用,改fullhash,
    path: path.resolve(__dirname, "../dist"), // 打包后的目录
  },
};

这时候生成的 dist 目录文件如下

main.8626b326.js

为了缓存,你会发现打包好的 js 文件的名称每次都不一样。webpack 打包出来的 js 文件我们需要引入到 html 中,但是每次我们都手动修改 js 文件名显得很麻烦,因此我们需要一个插件来帮我们完成这件事情

npm i -D html-webpack-plugin

新建一个 build 同级的文件夹 public,里面新建一个 index.html 具体配置文件如下

// webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
  mode: "development", // 开发模式
  entry: path.resolve(__dirname, "../src/main.js"), // 入口文件
  output: {
    filename: "[name].[fullhash:8].js", // 打包后的文件名称
    path: path.resolve(__dirname, "../dist"), // 打包后的目录
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "../public/index.html"),
    }),
  ],
};

可以发现打包生成的 js 文件已经被自动引入 html 文件中

<script defer src="main.df19b0e4.js"></script>

思考?为什么多了个 defer,查看博客文章https://blog.csdn.net/qq_43238599/article/details/115273893

查看官方插件配置选项https://github.com/jantimon/html-webpack-plugin#options

重新调整插件配置,再打包

plugins: [
  new HtmlWebpackPlugin({
    template: path.resolve(__dirname, "../public/index.html"),
    scriptLoading: "blocking",
  }),
];

打包结果的 html 内容:

<script src="main.df19b0e4.js"></script> js 打包进入到了 body,如果不配置 scriptLoading 却在 head,配置了却在 body,再加个配置项
plugins: [
  new HtmlWebpackPlugin({
    template: path.resolve(__dirname, "../public/index.html"),
    inject: "head",
    scriptLoading: "blocking",
  }),
];

再打包就 ok

多入口文件如何开发

生成多个 html-webpack-plugin 实例来解决这个问题

public 目录下新建 header.html,新建 webpack.config.multi.js

// webpack.config.multi.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
  mode: "development", // 开发模式
  entry: {
    main: path.resolve(__dirname, "../src/main.js"),
    header: path.resolve(__dirname, "../src/header.js"),
  },
  output: {
    filename: "[name].[fullhash:8].js", // 打包后的文件名称
    path: path.resolve(__dirname, "../dist"), // 打包后的目录
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "../public/index.html"),
      filename: "index.html",
      inject: "head",
      scriptLoading: "blocking",
      chunks: ["main"], // 与入口文件对应的模块名
    }),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "../public/header.html"),
      filename: "header.html",
      inject: "head",
      scriptLoading: "blocking",
      chunks: ["header"], // 与入口文件对应的模块名
    }),
  ],
};

package.json 调整

"scripts": {
  "build": "webpack --config build/webpack.config.js",
  "buildmul": "webpack --config build/webpack.config.multi.js"
},

执行 npm run buildmul

clean-webpack-plugin

每次执行 npm run build 会发现 dist 文件夹里会残留上次打包的文件,这里我们推荐一个 plugin 来帮我们在打包输出前清空文件夹 clean-webpack-plugin

npm i -D clean-webpack-plugin

const { CleanWebpackPlugin } = require("clean-webpack-plugin");
module.exports = {
  // ...省略其他配置
  plugins: [new CleanWebpackPlugin()],
};

引用 CSS

loader

loader 用于对模块的源代码进行转换

loader 都在 module 下的 rules 中配置

loader 配置项包括:

  • test 正则校验(必须)
  • loader 调用 loader 的名称 / use 链式调用 loader (二选一)
  • include/exclude 手动添加必修处理的文件/文件夹或屏蔽不需要处理的文件/文件夹(可选)
  • options 为 loaders 提供额外的设置选项(可选)

tip:use 链式调用,都是从右向左解析,需注意调用 loader 的顺序。loader 要记住,面试经常被问到有哪些 loader 以及其作用

我们的入口文件是 js,所以我们在入口 js 中引入我们的 css 文件

/* index.css */
* {
  margin: 0;
  padding: 0;
}
/* index.less */
.red {
  color: red;
}
/* index.scss */
.green {
  color: green;
}
import "./assets/index.css";
import "./assets/index.less";
import "./assets/index.scss";
console.log("Hello World");

同时我们也需要一些 loader 来解析我们的 css 文件

npm i -D style-loader css-loader

如果我们使用 less 来构建样式,则需要多安装两个

npm i -D less less-loader

如果我们使用 scss 来构建样式,则需要安装

npm i -D sass-loader

配置文件如下

// webpack.config.js
module.exports = {
  // ...省略其他配置
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"], // 从右向左解析原则
      },
      {
        test: /\.less$/,
        use: ["style-loader", "css-loader", "less-loader"], // 从右向左解析原则
      },
      {
        test: /\.scss$/,
        use: ["style-loader", "css-loader", "sass-loader"], // 从右向左解析原则
      },
    ],
  },
};

执行编译的时候会报 scss 相关错误,还需安装 node-sass,但没有看到相关使用 node-sass 的字眼!!! npm i -D node-sass sass-loader

全局安装 live-server, npm i -g live-server

cmd cd dist

cmd live-server

打开浏览器,观察 html 内容 为了直观可以修改 public/index.html 的内容

<body>
  <div class="red">Hello World</div>
  <div class="green">Hello World</div>
</body>

可以看到 css 都已经注入

为 css 添加浏览器前缀

npm i -D postcss-loader autoprefixer

给 index.css 添加内容

.noselect {
  user-select: none;
}

模板 html

<body>
  <div class="red">Hello World</div>
  <div class="green noselect">Hello World</div>
</body>

调整 webpack.config.js 的 rules 配置如下:

rules: [
  {
    test: /\.css$/,
    use: ["style-loader", "css-loader", "postcss-loader"], // 从右向左解析原则
  },
  {
    test: /\.less$/,
    use: ["style-loader", "css-loader", "postcss-loader", "less-loader"], // 从右向左解析原则
  },
  {
    test: /\.scss$/,
    use: ["style-loader", "css-loader", "postcss-loader", "sass-loader"], // 从右向左解析原则
  },
];

接下来,我们还需要引入 autoprefixer 使其生效,这里有两种方式 1,在项目根目录下创建一个 postcss.config.js 文件,配置如下:

module.exports = {
  plugins: [require("autoprefixer")], // 引用该插件即可了
};

在 index.css 修改为

.noselect {
  user-select: none;
  transform: rotate(3deg);
  display: flex;
}

重新 npm run build,浏览器发现 transfrom 只有-webkit-前缀,如果兼容性更强有两种方法:

  1. 在 package.json 追加
"browserslist": [
  "defaults",
  "not ie <= 8",
  "last 2 versions",
  "> 1%",
  "Safari >= 6",
  "Firefox > 3",
  "iOS >= 7",
  "Android >= 4.0"
]

或者

  1. 根目录下新建.browserslistrc
ie > 8
last 2 versions
> 1%
Safari >= 6
Firefox > 3
iOS >= 7
Android >= 4.0

cmd npx browserslist 可以查看浏览器厂商前缀兼容的范围

2,直接在 webpack.config.js 里配置,由于高版本的 webpack 不兼容了,这里暂不研究

这时候我们发现 css 通过 style 标签的方式添加到了 html 文件中,但是如果样式文件很多,全部添加到 html 中,难免显得混乱。这时候我们想用把 css 拆分出来用外链的形式引入 css 文件怎么做呢?这时候我们就需要借助插件来帮助我们

合并 css

npm i -D mini-css-extract-plugin

webpack 4.0 以前,我们通过 extract-text-webpack-plugin 插件,把 css 样式从 js 文件中提取到单独的 css 文件中。webpack4.0 以后,官方推荐使用 mini-css-extract-plugin 插件来打包 css 文件

配置文件如下 webpack.config.jminicss.js

const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
  //...省略其他配置
  module: {
    rules: [
      {
        test: /\.less$/,
        use: [MiniCssExtractPlugin.loader, "css-loader", "less-loader"],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "[name].[hash].css",
      chunkFilename: "[id].css",
    }),
  ],
};

执行 npm run buildminicss

刚开始的的时候 rules 中使用了 style-loader,发现和 MiniCssExtractPlugin.loader 一起使用会出现 warning

style-loader 的作用是头部用<style></style>嵌入式引入, MiniCssExtractPlugin 插件的作用是提取 JS 中的 CSS 样式,用 link 外部引入,减少 JS 文件的大小,简称 CSS 样式分离

如果使用了 MiniCssExtractPlugin.loader 又要使用 webpack5 的内置资源打包静态资源 需加入

// webpack.config.minicss.js
rules: [
  {
    test: /\.(jpe?g|png|gif)$/i, // 图片文件
    type: "asset",
    // 解析
    parser: {
      // 超过10kb将转成base64,优点:减少请求 缺点:文件体积变大
      dataUrlCondition: {
        maxSize: 10 * 1024,
      },
    },
    generator: {
      // 与output.assetModuleFilename是相同的,这个写法引入的时候也会添加好这个路径
      filename: "images/[name].[hash:6][ext]",
      // 打包后对资源的引入
      // publicPath: '../'
    },
  },
];

一定不要使用 publicPath !!!不兼容,会报错

拆分多个 css

这里需要说的细一点,上面我们所用到的 mini-css-extract-plugin 会将所有的 css 样式合并为一个 css 文件。如果你想拆分为一一对应的多个 css 文件,我们需要使用到 extract-text-webpack-plugin,而目前 mini-css-extract-plugin 还不支持此功能。我们需要安装@next 版本的 extract-text-webpack-plugin

npm i -D extract-text-webpack-plugin

// webpack.config.js

const path = require("path");
const ExtractTextWebpackPlugin = require("extract-text-webpack-plugin");
let indexLess = new ExtractTextWebpackPlugin("index.less");
let indexCss = new ExtractTextWebpackPlugin("index.css");
module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: indexCss.extract({
          use: ["css-loader"],
        }),
      },
      {
        test: /\.less$/,
        use: indexLess.extract({
          use: ["css-loader", "less-loader"],
        }),
      },
    ],
  },
  plugins: [indexLess, indexCss],
};

运行不成功,暂不研究 webpack4 以上版本不支持该插件,废弃 https://github.com/webpack-contrib/extract-text-webpack-plugin Since webpack v4 the extract-text-webpack-plugin should not be used for css. Use mini-css-extract-plugin instead.

打包 图片、字体、媒体、等文件

file-loader 就是将文件在进行一些处理后(主要是处理文件名和路径、解析文件 url),并将文件移动到输出的目录中 url-loader 一般与 file-loader 搭配使用,功能与 file-loader 类似,如果文件小于限制的大小。则会返回 base64 编码,否则使用 file-loader 将文件移动到输出的目录中

// webpack.config.js
module.exports = {
  // 省略其它配置 ...
  module: {
    rules: [
      // ...
      {
        test: /\.(jpe?g|png|gif)$/i, //图片文件
        use: [
          {
            loader: "url-loader",
            options: {
              limit: 10240,
              fallback: {
                loader: "file-loader",
                options: {
                  name: "img/[name].[hash:8].[ext]",
                },
              },
            },
          },
        ],
      },
      {
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, //媒体文件
        use: [
          {
            loader: "url-loader",
            options: {
              limit: 10240,
              fallback: {
                loader: "file-loader",
                options: {
                  name: "media/[name].[hash:8].[ext]",
                },
              },
            },
          },
        ],
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i, // 字体
        use: [
          {
            loader: "url-loader",
            options: {
              limit: 10240,
              fallback: {
                loader: "file-loader",
                options: {
                  name: "fonts/[name].[hash:8].[ext]",
                },
              },
            },
          },
        ],
      },
    ],
  },
};

webpack5 内置资源模块 asset module 来替换 raw-loader、url-loader、file-loader, 上述配置仅提供参考

博客文章: https://www.jianshu.com/p/36e972b19b28 https://blog.csdn.net/QQ1443003435/article/details/121108377

官方文档: https://webpack.docschina.org/guides/asset-modules/

一定要使用 url-loader 和 file-loader 须使用如下,这里也仅验证图片资源

rules: [
  // ...其他配置
  {
    test: /\.(jpe?g|png|gif)$/i,
    loader: "url-loader",
    type: "javascript/auto",
    options: {
      limit: 10 * 1024,
      esModule: false,
      fallback: {
        loader: "file-loader",
        options: {
          esModule: false,
          name: "[name].[hash:8].[ext]",
        },
      },
    },
  },
];

这里 type 和 esModule 很重要!!!

在 src/assets/images 下存放 三张图片资源,其中一个小于 10kb

index.css 添加

#box1 {
  width: 200px;
  height: 100px;
  background-image: url("./images/logo.jpg");
  background-repeat: no-repeat;
  background-size: 100% 100%;
}

#box2 {
  width: 200px;
  height: 100px;
  background-image: url("./images/web.jpeg");
  background-repeat: no-repeat;
  background-size: 100% 100%;
}

#box3 {
  width: 400px;
  height: 600px;
  background-image: url("./images/iu.jpg");
  background-repeat: no-repeat;
  background-size: 100% 100%;
}

在 index.html 中代码调整修改

<body>
  <div class="red">Hello World</div>
  <div class="green noselect">Hello World</div>
  <div id="box1"></div>
  <div id="box2"></div>
  <div id="box3"></div>
</body>

npm run build 在 dist 目录下旧可以看到只有两张图片资源,其中有个小于 10kb 的图片被转成了 base64,校验此配置选项成功

由于 webpack5 已经使用 asset 来打包资源,现在使用如下最新配置选项

rules: [
  // ...其他配置
  {
    test: /\.(jpe?g|png|gif)$/i, // 图片文件
    type: "asset",
    // 解析
    parser: {
      // 超过10kb将转成base64,优点:减少请求 缺点:文件体积变大
      dataUrlCondition: {
        maxSize: 10 * 1024,
      },
    },
    generator: {
      // 与output.assetModuleFilename是相同的,这个写法引入的时候也会添加好这个路径
      filename: "images/[name].[hash:6][ext]",
      // 打包后对资源的引入
      publicPath: "./",
    },
  },
];

这里 filename 多生成了一个 images 目录

html 中图片资源处理

npm i html-loader -D

在 index.html 的内容调整

<body>
  <div class="red">Hello World</div>
  <div class="green noselect">Hello World</div>
  <img src="../src/assets/images/logo.jpg" />
  <img src="../src/assets/images/iu.jpg" />
  <div id="box1"></div>
  <div id="box2"></div>
  <div id="box3"></div>
</body>

rules 的配置

rules: [
  {
    test: /\.html$/,
    loader: "html-loader",
  },
];

npm run build 查看 html 的内容,其中一张是转成了 base64 一张是文件路径形式

处理媒体文件

免费视频素材网站

下载个视频放到 media 目录下 video.mp4

index.html 内容调整

<body>
  <div class="red">Hello World</div>
  <div class="green noselect">Hello World</div>
  <video width="320" height="240" controls>
    <source src="../src/assets/media/video.mp4" type="video/mp4" />
    <object
      data="../src/assets/media/video.mp4"
      width="320"
      height="240"
    ></object>
  </video>
  <img src="../src/assets/images/logo.jpg" />
  <img src="../src/assets/images/iu.jpg" />
  <div id="box1"></div>
  <div id="box2"></div>
  <div id="box3"></div>
</body>
// webpack.config.js
rules: [
  {
    test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, // 媒体文件
    type: "asset",
    parser: {
      dataUrlCondition: {
        maxSize: 10 * 1024,
      },
    },
    generator: {
      filename: "media/[name].[hash:6][ext]",
      publicPath: "./",
    },
  },
];

cmd npm run build

查看 html 引入的视频路径和 dist/media 下的视频

处理字体文件

添加字体 src/assets/fonts/Poppins-Regular.woff

// index.css追加如下
.font {
  font-family: "Poppins-Regular";
}

@font-face {
  font-family: "Poppins-Regular";
  src: url("./fonts/Poppins-Regular.woff") format("woff");
}
// webpack.config.js
rules: [
  {
    test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i, // 字体
    type: "asset/inline", // inline 的时候不需要指定文件名
  },
];

inde.html 调整添加 font

<div class="red font">Hello World</div>

npm run build 查看 index.html,动态去除 font 类能看到字体有变化,或者通过浏览器也能看到字体的加载说明成功

用 babel 转义 js 文件

为了使我们的 js 代码兼容更多的环境我们需要安装依赖

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

注意 babel-loader 与 babel-core 的版本对应关系

  1. babel-loader 8.x 对应 babel-core 7.x
  2. babel-loader 7.x 对应 babel-core 6.x

最新安装的都是高版本

配置如下

// webpack.config.js
rules = [
  // ... 其他配置
  {
    test: /\.js$/,
    use: {
      loader: "babel-loader",
      options: {
        presets: ["@babel/preset-env"],
      },
    },
    exclude: /node_modules/,
  },
];

在 main.js 中添加如下 ES6 代码

let arr = [1, 2, , 3, 4, 5, 6, 7, 8, 9];

arr.map((item) => {
  console.log(item);
});

class Animal {
  constructor() {
    this.type = "animal";
  }
  says(say) {
    console.log(this.type + " says " + say);
  }
}
let animal = new Animal();
animal.says("hello");

npm run build 以后观察打包出来的 js 代码,可以看到相关转化成 ES5 的代码,不够明显,可以把转换的配置规则去掉,再打包可以看到打包出来的 js 是没有转化的 ES6 代码

如果 options 去掉 在项目的根目录下新建.babelrc 内容如下:

{
    "presets": ["@babel/preset-env"],
    "plugins": ["@babel/plugin-transform-runtime"]
}
// webpack.config.js
rules: [
  {
    test: /\.js$/,
    use: {
      loader: "babel-loader",
    },
    exclude: /node_modules/,
  },
];

还需安装一个

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

打包观察 js 的是否转换成 es5

Babel 其实是几个模块化的包:

  • @babel/core:babel 核心库
  • babel-loader:webpack 的 babel 插件,让我们可以在 webpack 中运行 babel
  • @babel/preset-env:将 ES6 转换为向后兼容的 JavaScript
  • @babel/plugin-transform-runtime:处理 async,await、import()等语法关键字的帮助函数

上面的 babel-loader 只会将 ES6/7/8 语法转换为 ES5 语法,但是对新 api 并不会转换 例如(promise、Generator、Set、Maps、Proxy 等) 此时我们需要借助 babel-polyfill 来帮助我们转换 如下未实验,仅供参考

npm i @babel/polyfill

// webpack.config.js
const path = require("path");
module.exports = {
  entry: ["@babel/polyfill", path.resolve(__dirname, "../src/index.js")], // 入口文件
};

增加编译进度条

进度条插件有两种,取其中一个使用就行

  1. npm i progress-bar-webpack-plugin -D
const chalk = require("chalk");
const ProgressBarPlugin = require("progress-bar-webpack-plugin");
// ...
plugins: [
  // 进度条
  new ProgressBarPlugin({
    format: `  :msg [:bar] ${chalk.green.bold(":percent")} (:elapsed s)`,
  }),
];
  1. npm i webpackbar
const WebpackBar = require("webpackbar");
// ...
plugin: [new WebpackBar()];

保留前面的 webpack.config.js 配置到 webpack.config.bak.js

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

const chalk = require("chalk");
const ProgressBarPlugin = require("progress-bar-webpack-plugin");
const WebpackBar = require("webpackbar");

module.exports = {
  mode: "development", // 开发模式
  entry: path.resolve(__dirname, "../src/main.js"), // 入口文件
  output: {
    filename: "[name].[fullhash:8].js", // 打包后的文件名称
    path: path.resolve(__dirname, "../dist"), // 打包后的目录
  },
  plugins: [
    // 进度条
    new WebpackBar(),
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "../public/index.html"),
      inject: "head",
      scriptLoading: "blocking",
    }),
  ],
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader", "postcss-loader"], // 从右向左解析原则
      },
      {
        test: /\.less$/,
        use: ["style-loader", "css-loader", "postcss-loader", "less-loader"], // 从右向左解析原则
      },
      {
        test: /\.scss$/,
        use: ["style-loader", "css-loader", "postcss-loader", "sass-loader"], // 从右向左解析原则
      },
      {
        test: /\.(jpe?g|png|gif)$/i, // 图片文件
        type: "asset",
        // 解析
        parser: {
          // 超过10kb将转成base64,优点:减少请求 缺点:文件体积变大
          dataUrlCondition: {
            maxSize: 10 * 1024,
          },
        },
        generator: {
          // 与output.assetModuleFilename是相同的,这个写法引入的时候也会添加好这个路径
          filename: "images/[name].[hash:6][ext]",
          // 打包后对资源的引入
          publicPath: "./",
        },
      },
      {
        test: /\.html$/,
        loader: "html-loader",
      },
      {
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, // 媒体文件
        type: "asset/resource",
        generator: {
          filename: "media/[name].[hash:6][ext]",
          publicPath: "./",
        },
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i, // 字体
        type: "asset/inline", // inline 的时候不需要指定文件名
      },
      {
        test: /\.js$/,
        use: {
          loader: "babel-loader",
          /* options: {
                    presets: ["@babel/preset-env"]
                  } */
        },
        exclude: /node_modules/,
      },
    ],
  },
};

搭建本地服务器

npm i webpack-dev-server -D

将 webpack.config.js 复制一份,改名称 webpack.common.js 添加选项:

devServer: {
    hot: true, // 热更新
    open: false, // 编译完自动打开浏览器
    compress: true,// 开启gzip压缩
    port: 8088, // 开启端口号
    client: { //在浏览器端打印编译进度
      progress: true,
    },
}

在 package.json 添加命令

"dev": "webpack serve --config build/webpack.common.js"

npm run dev

浏览器输入 http://localhost:8088/

使用了本地服务将不会打包到 dist 目录

生产环境与开发环境

npm i webpack-merge -D

build 下新建 webpack.dev.js,webpack.prod.js

webpack.dev.js 内容如下:

const { merge } = require("webpack-merge");
const common = require("./webpack.common.js");

module.exports = merge(common, {
  mode: "development",
  devServer: {
    hot: true, // 热更新
    open: false, // 编译完自动打开浏览器
    compress: true, // 开启gzip压缩
    port: 8088, // 开启端口号
    client: {
      //在浏览器端打印编译进度
      progress: true,
    },
  },
});

webpack.prod.js 内容如下:

const { merge } = require("webpack-merge");
const common = require("./webpack.common.js");

module.exports = merge(common, {
  mode: "production",
});

webpack.common.js 内容如下:

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

const chalk = require("chalk");
const ProgressBarPlugin = require("progress-bar-webpack-plugin");
const WebpackBar = require("webpackbar");

module.exports = {
  entry: path.resolve(__dirname, "../src/main.js"), // 入口文件
  output: {
    filename: "[name].[fullhash:8].js", // 打包后的文件名称
    path: path.resolve(__dirname, "../dist"), // 打包后的目录
  },
  plugins: [
    new WebpackBar(),
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "../public/index.html"),
      inject: "head",
      scriptLoading: "blocking",
    }),
  ],
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader", "postcss-loader"], // 从右向左解析原则
      },
      {
        test: /\.less$/,
        use: ["style-loader", "css-loader", "postcss-loader", "less-loader"], // 从右向左解析原则
      },
      {
        test: /\.scss$/,
        use: ["style-loader", "css-loader", "postcss-loader", "sass-loader"], // 从右向左解析原则
      },
      {
        test: /\.(jpe?g|png|gif)$/i, // 图片文件
        type: "asset",
        // 解析
        parser: {
          // 超过10kb将转成base64,优点:减少请求 缺点:文件体积变大
          dataUrlCondition: {
            maxSize: 10 * 1024,
          },
        },
        generator: {
          // 与output.assetModuleFilename是相同的,这个写法引入的时候也会添加好这个路径
          filename: "images/[name].[hash:6][ext]",
          // 打包后对资源的引入
          publicPath: "./",
        },
      },
      {
        test: /\.html$/,
        loader: "html-loader",
      },
      {
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, // 媒体文件
        type: "asset/resource",
        generator: {
          filename: "media/[name].[hash:6][ext]",
          publicPath: "./",
        },
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i, // 字体
        type: "asset/inline", // inline 的时候不需要指定文件名
      },
      {
        test: /\.js$/,
        use: {
          loader: "babel-loader",
          /* options: {
                    presets: ["@babel/preset-env"]
                  } */
        },
        exclude: /node_modules/,
      },
    ],
  },
};

package.json 命令

{
  "scripts": {
    "dev": "webpack serve --config build/webpack.dev.js",
    "prod": "webpack --config build/webpack.prod.js"
  }
}

npm run prod 和 npm run dev 可以正常执行

配置别名

在 index.html 添加内容

<img src="~@/assets/images/iu.jpg" />

注意:~@

在 webpack.common.js 中添加配置选项 resolve 和 entry 同级

resolve: {
    alias: { // 配置别名
      "@": path.resolve(__dirname, "../src"),
    },
},

npm run dev 编译通过,并且浏览器正常访问,如果把配置去掉,编译不通过会报错!

代码依赖分析

npm i webpack-bundle-analyzer -D

只有打包才会分析所以在 webpack.prod.js 添加如下配置:

const BundleAnalyzerPlugin =
  require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
// ... 其他配置
plugins: [
  new BundleAnalyzerPlugin({
    //  可以是`server`,`static`或`disabled`。
    //  在`server`模式下,分析器将启动HTTP服务器来显示软件包报告。
    //  在“静态”模式下,会生成带有报告的单个HTML文件。
    //  在`disabled`模式下,你可以使用这个插件来将`generateStatsFile`设置为`true`来生成Webpack Stats JSON文件。
    analyzerMode: "static",
    //  将在“服务器”模式下使用的主机启动HTTP服务器。
    analyzerHost: "127.0.0.1",
    //  将在“服务器”模式下使用的端口启动HTTP服务器。
    analyzerPort: 8888,
    //  路径捆绑,将在`static`模式下生成的报告文件。
    //  相对于捆绑输出目录。
    reportFilename: "report.html",
    //  模块大小默认显示在报告中。
    //  应该是`stat`,`parsed`或者`gzip`中的一个。
    //  有关更多信息,请参见“定义”一节。
    defaultSizes: "parsed",
    //  在默认浏览器中自动打开报告
    openAnalyzer: false,
    //  如果为true,则Webpack Stats JSON文件将在bundle输出目录中生成
    generateStatsFile: false,
    //  如果`generateStatsFile`为`true`,将会生成Webpack Stats JSON文件的名字。
    //  相对于捆绑输出目录。
    statsFilename: "stats.json",
    //  stats.toJson()方法的选项。
    //  例如,您可以使用`source:false`选项排除统计文件中模块的来源。
    //  在这里查看更多选项:https:  //github.com/webpack/webpack/blob/webpack-1/lib/Stats.js#L21
    statsOptions: null,
    logLevel: "info", // 日志级别。可以是'信息','警告','错误'或'沉默'。
  }),
];

npm run prod

splitChunks(分离 chunks)

webpack.prod.js 也只有打包才需要分离

  //webpack.prod.js
  //与plugins同级
  optimization: {
    splitChunks: {
      chunks: "all",
      name: "vendor",
      cacheGroups: {
        "echarts.vendor": {
          name: "echarts.vendor",
          priority: 40,
          test: /[\\/]node_modules[\\/](echarts|zrender)[\\/]/,
          chunks: "all",
        },
        lodash: {
          name: "lodash",
          chunks: "async",
          test: /[\\/]node_modules[\\/]lodash[\\/]/,
          priority: 40,
        },
        "async-common": {
          chunks: "async",
          minChunks: 2,
          name: "async-commons",
          priority: 30,
        },
        commons: {
          name: "commons",
          chunks: "all",
          minChunks: 2,
          priority: 20,
        },
      },
    },
  },

安装 echarts

npm i echarts -S

新建 src/echart.js

import * as echarts from "echarts";
var myChart = echarts.init(document.getElementById("main"));

var option = {
  title: {
    text: "ECharts 入门示例",
  },
  tooltip: {},
  legend: {
    data: ["销量"],
  },
  xAxis: {
    data: ["衬衫", "羊毛衫", "雪纺衫", "裤子", "高跟鞋", "袜子"],
  },
  yAxis: {},
  series: [
    {
      name: "销量",
      type: "bar",
      data: [5, 20, 36, 10, 10, 20],
    },
  ],
};
myChart.setOption(option);

在 main.js 中引入

import './echart'

修改 public/index.html

<div id="main" style="width: 600px; height: 400px"></div>

这个时候还需调整 HtmlWebpackPlugin 配置的 nject: 'body',思考为什么?

运行 npm run prod,在 dist 下会发现新增了 echarts.vendor.xxxx.js 这就是通过 splitChunks 分离出来 echarts 包

动态导入(按需加载 chunks)

按需下载资源,如路由懒加载。可以提升首屏加载速度

npm i lodash -S

通过 import()语法实现动态导入

//在main.js添加
function getComponent() {
  // Lodash, now imported by this script
  return import("lodash")
    .then(({ default: _ }) => {
      const element = document.createElement("div");

      element.innerHTML = _.join(["Hello", "webpack"], " ");

      return element;
    })
    .catch((error) => "An error occurred while loading the component");
}

const button = document.createElement("button");

button.innerHTML = "Click me ";

button.onclick = () => {
  getComponent().then((component) => {
    document.body.appendChild(component);
  });
};

document.body.appendChild(button);

在 webpack.prod.js 中 cacheGroups 下添加(在上面 splitChunks 中已经加过了)

lodash: {
  name: "lodash",
  chunks: "async",
  test: /[\\/]node_modules[\\/]lodash[\\/]/,
  priority: 40,
},

npm run prod 可以看到有生成 lodash.xxx.js

cd dist

cmd live-server 启动服务,点击底部的 Click me 按钮,可以看到动态加载的 js

定义编译时全局变量

npm i cross-env -D

package.json 添加两个命令

"scripts": {
  "cdev": "cross-env NODE_ENV=development webpack serve --config build/webpack.dev.js",
  "cbuild": "cross-env NODE_ENV=production webpack --config build/webpack.prod.js",
}

在 webpack.common.js 添加输出

console.log('process.env.NODE_ENV',process.env.NODE_ENV)

cmd npm run cdev 或者 npm run cbuild 可以看到輸出的变量

定义编译后全局变量

通过 DefinePlugin 实现

根目录下新建 config/dev.env.js

module.exports = {
  NODE_ENV: '"development"',
  APP_API: '"wwww.testapi.com"',
};
// webpck.dev.js
const env = require("../config/dev.env")
const webpack =require("webpack")

module.exports = merge(common,{
  plugins: [
    new webpack.DefinePlugin({
      "process.env": env,
    }),
  ],
})

// main.js try { console.log(process.env) } catch (error) {

}

externals

防止将外部资源包打包到自己的 bundle 中

示例:从 cdn 引入 jQuery,而不是把它打包

  1. index.html 中添加
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>

必须是</ body>前,如果放</ body>后就不行,思考?

  1. main.js 添加
import $ from "jquery";
console.log($("#main"));
  1. npm i jquery -S 这里主要满足 dev 环境使用, html 引入了 jq,main.js 中又引入,会出现引入两次的情况,后面再考虑如何 dev 只引入一次

  2. webpack.prod.js 添加配置

externals: {
    jquery: 'jQuery',
,

npm run dev 和 npm run prod 查看结果

打包入口是 CSS

思考后续是否可以打包组件相关的样式,只打包样式文件

新建 src\assets\main.css

* {
  margin: 0;
  padding: 0;
}
.red {
  color: red;
}
.green {
  color: green;
}

新建 webpack.config.css.js

const path = require('path')

module.exports = {
  mode: 'development', // 模式
  entry: path.resolve(**dirname, '../src/assets/main.css'), // 打包入口地址
  output: {
    filename: 'bundle.css', // 输出文件名
    path: path.join(**dirname, '../dist') // 输出文件目录
  }
}

package.json 添加命令

"buildcss": "webpack --config build/webpack.config.css.js"

npm run buildcss 编译错误

处理 css 文件需要使用 css-loader 调整配置

// webpack.config.css.js
const path = require("path");

module.exports = {
  mode: "development", // 模式
  entry: path.resolve(__dirname, "../src/assets/main.css"), // 打包入口地址
  output: {
    filename: "bundle.css", // 输出文件名
    path: path.join(__dirname, "../dist"), // 输出文件目录
  },
  module: {
    rules: [
      // 转换规则
      {
        test: /\.css$/, //匹配所有的 css 文件
        use: "css-loader", // use: 对应的 Loader 名称
      },
    ],
  },
};

npm run buildcss

常用 loader

常用的 loader:

  • style-loader、css-loader、postcss-loader、less-loader,
  • 编译 sass 须使用 sass-loader node-sass 或 dart-sass
  • style-loader 和 MiniCssExtractPlugin.loader 不能共用
  • file-loader:解决资源引入问题,并将资源 copy 到指定目录,默认为 dist
  • url-loader:解依赖 file-loader,当图片小于 limit 值的时候,会将图片转为 base64 编码,大于 limit 值的时候依然是使用 file-loader 进行拷贝
  • img-loader:压缩图片
  • html-loader: 处理 html 文件资源
  • webpack5 新增资源模块(asset module),允许使用资源文件(字体,图标等)而无需配置额外的 loader。
  • asset/resource 将资源分割为单独的文件,并导出 url,类似之前的 file-loader 的功能.
  • asset/inline 将资源导出为 dataUrl 的形式,类似之前的 url-loader 的小于 limit 参数时功能.
  • asset/source 将资源导出为源码(source code). 类似的 raw-loader 功能.
  • asset 会根据文件大小来选择使用哪种类型,当文件小于 8 KB(默认) 的时候会使用 asset/inline,否则会使用 asset/resource

缩小文件的搜索范围(配置 include exclude alias noParse extensions)

  • alias: 当我们代码中出现 import 'vue'时, webpack 会采用向上递归搜索的方式去 node_modules 目录下找。为了减少搜索范围我们可以直接告诉 webpack 去哪个路径下查找。也就是别名(alias)的配置。
  • include exclude 同样配置 include exclude 也可以减少 webpack loader 的搜索转换时间。
  • noParse 当我们代码中使用到 import jq from 'jquery'时,webpack 会去解析 jq 这个库是否有依赖其他的包。但是我们对类似 jquery 这类依赖库,一般会认为不会引用其他的包(特殊除外,自行判断)。增加 noParse 属性,告诉 webpack 不必解析,以此增加打包速度。
  • extensions webpack 会根据 extensions 定义的后缀查找文件(频率较高的文件类型优先写在前面)
module.exports = {
  module: {
    noParse: /jquery/,
    rules: [
      {
        test: /\.vue$/,
        loader: "vue-loader",
        include: [path.resolve(__dirname, "src")],
        exclude: /node_modules/,
      },
      {
        test: '/\\.(jep?g|png|gif)$/,
        use: {
          loader: 'url-loader',
          include: [path.resolve(__dirname, 'src/assets/icons)],
          exclude: /node_modules/
        }
      }
    ],
  },
  resolve: {
    alias: {
      'vue$', 'vue/dist/vue.runtime.esm.js',
      '@': path.resolve(__dirname, '../src'),
      'assets': resolve('src/assets'),
      'components': resolve('src/components')
    },
    extensions: ['*', '.js', '.json', '.vue']
  }
}

parallelUglifyPlugin 多进程压缩 js

适用生产环境

npm i webpack-parallel-uglify-plugin -D

// webpack.prod.js
const ParallelUglifyPlugin = require("webpack-parallel-uglify-plugin");

plugins: [
  new ParallelUglifyPlugin({
    // 压缩js的一些配置
    uglifyJS: {
      output: {
        beautify: false, // 不需要格式化,以最紧凑的方式输出
        comments: false, // 删除注释
      },
      warnings: false, // 删除未使用一些代码时引起的警告
      compress: {
        drop_console: true, // 删除所有console.log
        // 是否内嵌虽定义,但只使用了一次的变量
        // 比如var x = 2, y = 10, z = x + y 变成 z = 12
        collapse_vars: true,
        // 提出多次出现但没定义的变量,将其变成静态值;
        // 比如x = 'xx', y = 'xx' 变成 var a = 'xx', x = a, y = a
        reduce_vars: true,
      },
    },
  }),
];

npm run prod 后查看打包出来的 js 已经没有相关 console.log 等

happyPack 多进程打包

因为 js 是单线程的,如果引用的模块很多,且模块间引用的层级很深,那么 webpack 在递归解析依赖时,速度就会很慢。而使用 happyPack 可以开启多进程打包,会提高构建速度 它在开发或者生产环境都可以使用,不过对于小项目,使用这个优化空间不大,且开启进程可能消耗性能会更多;在大项目时,才会有较多的优化空间

npm i happypack -D

// wepback.common.js 或webpack.prod.js
const HappyPack = require('happypack')
// 将原来babel的配置改下,改为使用happypack多进程打包
module: {
    rules: [
      {
        test: /\.js$/,
        // use: ['babel-loader?cacheDirectory'],
        // 改为使用 happypack打包
        use: ['happypack/loader?id=babel'], // 这个id是自定义命名的,要跟插件中id对应
        // 排除 node_modules 目录下的文件
        exclude: /node_modules/
      }
  ]
},
plugins: [
  new HappyPack({
    id: 'babel', // 唯一标识符
    // 使用的loader配置改写到happypack的配置项中
    use: ['babel-loader']
  })
]

npm run prod

HappyPack 添加多个 loader,如下参考, 未实验:

// ...
// 引入 happypack
const HappyPack = require("happypack");

// 创建 happypack 共享进程池,其中包含 6 个子进程
const happyThreadPool = HappyPack.ThreadPool({ size: 6 });

module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.js$/,
        // use: ['babel-loader?cacheDirectory'] 之前是使用这种方式直接使用 loader
        // 现在用下面的方式替换成 happypack/loader,并使用 id 指定创建的 HappyPack 插件
        use: ["happypack/loader?id=babel"],
        include: path.resolve(__dirname, "src"),
      },
      {
        test: /\\.(css|less)$/,
        // 之前是使用这种方式直接使用 loader
        // use: ['style-loader',
        // {
        //     loader: 'css-loader',
        //     options: {
        //         sourceMap: true
        //     }
        // },
        // {
        //     loader: 'postcss-loader',
        //     options: {
        //         plugins: () => [autoprefixer()]
        //     }
        // },
        // {
        //     loader: 'less-loader',
        //     options: {
        //         javascriptEnabled: true,
        //     }
        // }]
        // 现在用下面的方式替换成 happypack/loader,并使用 id 指定创建的 HappyPack 插件
        use: ["happypack/loader?id=styles"],
        include: path.resolve(__dirname, "src"),
      },
    ],
  },
  plugins: [
    // ...
    new HappyPack({
      /*
       * 必须配置项
       */
      // id 标识符,要和 rules 中指定的 id 对应起来
      id: "babel",
      // 需要使用的 loader,用法和 rules 中 Loader 配置一样
      // 可以直接是字符串,也可以是对象形式
      loaders: ["babel-loader?cacheDirectory"],
      // 使用共享进程池中的进程处理任务
      threadPool: happyThreadPool,
    }),
    new HappyPack({
      /*
       * 必须配置
       */
      // id 标识符,要和 rules 中指定的 id 对应起来
      id: "styles",
      // 需要使用的 loader,用法和 rules 中 Loader 配置一样
      // 可以直接是字符串,也可以是对象形式
      loaders: [
        "style-loader",
        {
          loader: "css-loader",
          options: {
            sourceMap: true,
          },
        },
        {
          loader: "postcss-loader",
          options: {
            plugins: () => [autoprefixer()],
          },
        },
        {
          loader: "less-loader",
          options: {
            javascriptEnabled: true,
          },
        },
      ],
      // 使用共享进程池中的进程处理任务
      threadPool: happyThreadPool,
    }),
  ],
};

css 压缩 css-minimizer-webpack-plugin

CSS 压缩之前会使用 optimize-css-assets-webpack-plugin 这个插件,在 webpack v5 之后推荐使用 css-minimizer-webpack-plugin 这个插件。

npm i css-minimizer-webpack-plugin -D 修改 webpack.config.minicss.js

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
module.exports = {
  optimization: {
    minimizer: [new CssMinimizerPlugin()],
  },
};

忘了 mode: 'development', // 开发模式 改为 productions,相关 hash 改为 fullhash

npm run buildminicss 观察添加配置前后的 css 变化

拷贝静态资源 copy-webpack-plugin

npm i -D copy-webpack-plugin

webpack.common.js 添加配置选项

const CopyPlugin = require("copy-webpack-plugin");

module.exports = {
  plugins: [
    new CopyPlugin({
      patterns: [
        {
          from: path.resolve(__dirname, "../public"),
          to: "./",
          globOptions: {
            ignore: ["**/*.html"],
          },
        },
      ],
    }),
  ],
};

public 下面必须需要有可拷贝的资源(除了 html),这里放了个 logo,否则会有警告信息

npm run dev / npm run prod

空文件

简介

2022年了,再不会webpack5敲得代码就不香了,持续更新中...... 展开 收起
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
1
https://gitee.com/nicefree/webpack-study.git
git@gitee.com:nicefree/webpack-study.git
nicefree
webpack-study
webpack-study
master

搜索帮助