• 作者:老汪软件技巧
  • 发表时间:2024-09-08 07:02
  • 浏览量:

最近 Rspack 发布了 1.0 版本,这是由字节 web infra 团队打造的一款 Web 打包器。其创立时最主要的目标之一就是要兼容 webpack API 和生态系统,这样能帮助字节内部大量基于 webpack 打包的项目丝滑迁移。

相比较于 webpack 这样一个基于 JS 编写的打包器,Rspack 基于 Rust 编写,其打包速度直接碾压 webpack。当然,这并不表示现在学习 webapck 没有价值了。

之所以启动这个 webpack 入门系列教程,主要有 2 点考虑:

目前企业中还广泛存在的基于 webpack 打包的项目,学习 webpack 有利于我们理解目前的手头项目。webpack 可以说是 Web 界元老级别的打包器了,虽然 webpack 的打包机制和打包速度常受诟病,但其完善的文档和强大的生效仍然它的优势虽然 Rspack 有其速度独优势,但其 API 架构和生态很大一方面还是基于 webpack 的。学好 webpack 也是在间接巩固 Rspack 的基础

作为 webpack 系列的第一篇,我们的目标是快速创建一个 webpack 项目并进行第一个应用程序的打包。

废话不多说,开始吧。

项目初始化

mkdir webpack-demo 
cd .\webpack-demo\
npm init -y
# 使用 VS Code 打开
code .

安装 webpack

为了使用 webpack 我们需要安装 2 个开发依赖包:

npm install -D webpack webpack-cli

截至目前 webpack 的最新版本如下:

cat .\package.json
{
  "name": "webpack-demo",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "devDependencies": {
    "webpack": "^5.94.0",
    "webpack-cli": "^5.1.4"
  }
}

webpack 5 的最低 Node 版本要求是 10.13.0 (LTS),可以说非常低了。

你肯定会有疑问:webpack、webpack-cli 的作用分别是什么?

import webpack from 'webpack';
const compiler = webpack({
  // ...
});

npx webpack
assets by status 0 bytes [cached] 1 asset
WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value.
Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/
ERROR in main
Module not found: Error: Can't resolve './src' in 'D:\solo\webpack-demo'
resolve './src' in 'D:\solo\webpack-demo'

其实在 webpack 3 及之前的版本,只有一个 webpack 依赖包,在 webpack 4 中,将原本包里的 CLI 能力单独拆分出来成为 webpack-cli,保证在单独使用 webpack API 时,依赖的精简。

创建配置文件指定打包入口

以上,我们在执行 npx webpack 的时候报错了。原因很简单,就是我们没有为 webpack 指定。

关于打包入口等这类 webpack 配置,我们通过在项目根目录创建 webpack.config.js 文件来指定。

webpack.config.js 是 webpack 指令执行时默认查找并执行的配置。

// webpack.config.js
module.exports = {
  entry: './src/index.js'
}

src/index.js 很简单:

console.log('Hello, webpack!')

再次执行 npx webpack:

 npx webpack
asset main.js 31 bytes [emitted] [minimized] (name: main)
./src/index.js 31 bytes [built] [code generated]
WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value.
Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/
webpack 5.94.0 compiled with 1 warning in 159 ms

打包成功,发现项目根目录下创建了一个 dist/main.js 文件:

文件内容如下:

指定打包输出

当然,我们也可以指定打包输出文件(Output):

// webpack.config.js
module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js'
  }
}

再次执行 npx webpack,发现 dist 目录下多了一个 bundle.js 文件,内容与 main.js 一样。

当然,你也可以通过 ouput.path 指定输出目录:

const path = require('path');
module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
+   path: path.resolve(__dirname, 'dist/assets'),
  }
}

再次执行 npx webpack,发现打包文件成功输出到了 dist/assets 目录里。

指定打包模式

webpack 从 v4 版本开始,引入了的概念,有 3 个可能取值:'none'、'development' 和 'production'。

项目打包时,必须要显式声明打包模式,否则打包会有告警出现。

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value.
Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/

当 mode 为 'development' 是,webpack 会启用针对开发阶段的一些优化配置;当 mode 为 'production' 是,webpack 会启用针对生产环境的一些优化配置。

实际项目中,通常会为开发和生产环境单独设置配置文件。比如:webpack.dev.config、webpack.prod.config.js。

// webpack.dev.config.js
module.exports = {
  entry: './src/index.js',
  mode: 'development',
  output: {
    filename: 'bundle.js',
  }
}
// webpack.prod.config.js
module.exports = {
  entry: './src/index.js',
  mode: 'production',
  output: {
    filename: 'bundle.js',
  }
}

再在 package.json 中添加 script。

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

webpack 指令后的 --config 参数用于指定配置文件(默认是 webpack.config.js,其他可用指令可以参考 npx webpack -h 输出)。

这样一来,我们就能通过 npm run dev、npm run build 分别进行开发环境和生产环境的打包了。

虽然我们可以通过 webpack --mode development/production 在命令行指定开发模式,但实际项目里还是倾向于把配置都写入到配置文件中去

引入开发服务器为什么要引入?

现在执行完 npm run dev 之后,我们就得到了 dist/bundle.js 文件了。现在,我们再在项目根目录下创建一个 index.html 去引用这个文件。


"en">
<head>
  "UTF-8">
  "viewport" content="width=device-width, initial-scale=1.0">
  Document


  


右键使用 Live Server (或直接)打开:

可以看到打包 JS 执行之后在控制台输出了。

接下来,修改 src/index.js 的内容。

// src/index.js
import { say } from './say'
document.getElementById('app').innerHTML = say('webpack')
// src/say.js
export function say(text) {
  return `Hello, ${text}!`
}

src/index.js 里还引入 1 个外部依赖文件,这样还能测试下依赖打包。



+ 

执行 npm run dev,打开 index.html 文件:

_webpack与vite_webpack面试题

打包成功!

不过目前的开发效率还不是很高,每一次修改文件后,我们都要手动执行 npm run dev,并刷新页面查看效果。

--watch + Live Server

有一个比较土的办法,可以保证我们修改源代码后页面自动刷新。即通过 webpack --watch + Live Server 的方式——是这么做的:

webpack 增加 --watch 参数,监听文件修改并自动打包

{
  "scripts": {
-   "dev": "webpack--config webpack.dev.config.js",
+   "dev": "webpack --watch --config webpack.dev.config.js",
    "build": "webpack --config webpack.prod.config.js"
  },
}

使用 Live Server 打开页面,这个 VS Code 插件会自动监听页面内文件修改并会自动刷新

不过,还是有些低消——每一次页面刷新都要依赖新的文件输出,另外还意外额外的 Live Server 启动的服务。

这个时候就可以通过引用 webpack-dev-server 来解决问题。

使用 webpack-dev-server

只在开发阶段才会用到,会帮助我们自动打包文件并启动一个静态资源服务器。

下面就来安装和使用它:

npm install -D webpack-dev-server

修改 dev script:

{
  "scripts": {
-   "dev": "webpack --config webpack.dev.config.js",
+   "dev": "webpack-dev-server --config webpack.dev.config.js",
    "build": "webpack --config webpack.prod.config.js"
  },
}

发现,只是把 webpack 换成 webpack-dev-server 就行了。当然,你也可以使用 webpack serve,webpack 底层会自动代理到 webpack-dev-server。

重新执行 npm run dev:

可以看到 webpack-dev-server 默认在 8080 端口启动了一个服务,这个服务默认会把当前项目 dist 目录开放出去,当然,开放目录等配置可以通过 webpack 配置文件中的 devServer 属性进行定义:

// webpack.dev.config.js
module.exports = {
  entry: './src/index.js',
  mode: 'development',
  output: {
    filename: 'bundle.js',
  },
  devServer: {
    static: path.join(__dirname, 'dist'),
    port: 9000,
  }
}

修改 index.html:

  
- +

并把 index.html 拖入到 dist/ 目录中去。再次执行 npm run dev:

服务启动在 9000 端口了。浏览器访问 :9000/:

现在随意修改源代码或是 dist/ 目录下的 index.html,发现浏览器都会自动刷新了。

这里有一点需要注意的是,使用 webpack-dev-server 打包跟直接使用 webpack 开发的区别是,后者每次都会生成新的 bundle.js 文件,而 webpack-dev-server 只是将打包结果存放在内存之中,并不会执行到硬盘的写入操作——webpack-dev-server 收到请求时只会将内存中的打包结果返回,不信可以删除 dist/bundle.js 文件看看,网页还是能正常工作的。

当 webpack-dev-server 不能在内存找到资源时,就会去 static(静态服务目录,此处是 **/dist** 目录)的配置目录中去寻找,这也是为什么我们在访问 :9000/(:9000/index.html) 地址时,会返回 dist/index.html 文件内容的原因。

当然,webpack-dev-server 还提供了一个很便捷特性——live-reloading(自动刷新)——这也是为什么我们在修改文件时,网页自动刷新的原因。不过,自动刷新对简单页面效率还能接收,而对于具有复杂交互的大型页面就很低效,后面还会介绍更先进的模块热替换(Hot Module Replacement),这里暂先不表。

自动化 HTML 创建

webapck 通常用来打包网站应用。我们的 webpack 入口是一个 bundle js,需要一个网页 HTML 来引用它,到目前为止 dist/index.html 都还是我们自己创建的,这有一个坏处。

这要从配置文件的 clean-webpack-plugin 说起:

clean-webpack-plugin 是一个用来清理旧打包产物的 webpack 插件(webpack 通过插件来扩展能力边界的)。

当你在项目中启用 clean-webpack-plugin 后:

npm install --save-dev clean-webpack-plugin

// webpack.prod.config.js
const path = require('node:path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
  entry: './src/index.js',
  mode: 'production',
  output: {
    filename: 'bundle.js',
    // 这个必须要设置,否则 clean-webpack-plugin 不起作用
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [
    new CleanWebpackPlugin(),
  ]
}

每次执行 npm run build 都会先清理 output.path 指定的目录(此处即 dist/ 目录),这样我们的 index.html 就随之删除了。

除此之外,如果我们的打包文件名改动或是打包文件位置变动之后,都要我们手动补充或修改 index.html 文件,会很麻烦,这个时候就可以使用 html-webpack-pluign 帮助我们自动创建 HTML。

使用 html-webpack-plugin

html-webpack-plugin 的使用也很简单,首先安装:

  npm install --save-dev html-webpack-plugin

当然也是作为开发依赖。

然后,在 webpack.prod.config.js 中引入:

// webpack.prod.config.js
const path = require('node:path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
+ const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  entry: './src/index.js',
  mode: 'production',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [
    new CleanWebpackPlugin(),
+   new HtmlWebpackPlugin()
  ]
}

执行 npm run build,会发现在 dist/ 创建的产物里,会多出一个 index.html 文件。文件内容:

html><html><head><meta charset="utf-8"><title>Webpack Apptitle><meta name="viewport" content="width=device-width,initial-scale=1"><script defer="defer" src="bundle.js">script>head><body>body>html>

不过,默认使用的模板里不包括 #app 元素,这个时候,我们可以通过自定义模板文件来处理。在项目根目录创建模板文件 index.html:

html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Webpack Apptitle>
    <meta name="viewport" content="width=device-width,initial-scale=1">
  head>
  <body>
    <div id="app">div>
  body>
html>

再在创建 HtmlWebpackPlugin 的时候指定一下:

module.exports = {
  plugins: [
    new CleanWebpackPlugin(),
-   new HtmlWebpackPlugin()
+   new HtmlWebpackPlugin({ template: './index.html' })
  ]
}

再次执行 npm run build,会发现在 dist/index.html 文件就是基于我们的模板产出的了。

html><html><head><meta charset="utf-8"><title>Webpack Apptitle><meta name="viewport" content="width=device-width,initial-scale=1"><script defer="defer" src="bundle.js">script>head><body><div id="app">div>body>html>

最后,我们打开 webpack.dev.config.js 也引入 HtmlWebpackPlugin:

// webpack.dev.config.js
const path = require('node:path');
+ const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  entry: './src/index.js',
  mode: 'development',
  output: {
    filename: 'bundle.js',
  },
  devServer: {
    static: path.join(__dirname, 'dist'),
    port: 9000,
    open: true
  },
  plugins: [
+    new HtmlWebpackPlugin({ template: './index.html' })
  ]
}

这样,在执行 npm run dev 时,就能顺利查看我们网页了,早也不同担心手动维护 index.html 的烦恼啦!

总结

本文是 webapck 入门教程系列的第一篇,教你如何是通用 webpack 打包你的第一个应用。

在打包第一个应用的过程当中,我们接触了 webpack 中的 3 个核心概念:入口、输出以及模式;接着,我们引入了 webpack-dev-server,解决了开发环境下的自动打包和启动静态服务的问题;最后,我们又引入了 html-webpack-plugin 解决了手动维护入口文件 index.html 的烦恼。

希望本文的讲解,能够帮助你加深对 webpack 的了解和使用,感谢你的阅读,再见。