likes
comments
collection
share

[译] webpack 教程:如何从零开始设置 webpack 5

作者站长头像
站长
· 阅读数 14

原文链接:webpack Tutorial: How to Set Up webpack 5 From Scratch,2020.10.15,by Tania Rascia。

webpack 曾经对我来说是一个令人沮丧和压抑的怪物。如果有可能,我会尽量避免使用 webpack,因为它看起来很复杂、令人困惑,使用像 create-react-app 这样的工具来设置项目感觉会很安全。

如果你不知道如何从头开始设置 webpack 以便与 Babel、TypeScript、Sass、React 或 Vue 一起使用,或者不知道为什么要用 webpack,那么这篇文章很适合你。像所有事情一样,一旦你深入学习并了解它,会发现它并不可怕,并且只需要学习几个主要概念就能完成设置。

除了本文之外,我还创建了一个稳定版本的 webpack 5 模板仓库,可以帮助你启动任何项目。如果你熟悉 webpack 4 但想查看 webpack 5 的设置,也建议查看这个仓库。

前提要求

目标

  • 学习什么是 webpack 以及为什么你会想要使用它

  • 使用 webpack 设置开发服务器

  • 使用 webpack 设置生产构建流程

如果你正在从 webpack 4 升级到 webpack 5,这里有几个注意事项:

  • webpack-dev-server 命令现在变为 webpack-serve

  • 无需安装 file-loaderraw-loaderurl-loader,webpack 已内置的 asset 模块支持这类操作

  • Node polyfills 不再可用,因此如果出现 stream 错误,需要将 stream-browserify 包添加为依赖项,并在 webpack 配置文件 alias 属性中添加 { stream: 'stream-browserify' }

什么是 webpack?

大多数情况下,网站不再只是使用少量 JavaScript 、编写简单的 HTML 这么简单。现代站点通常完全由 JavaScript 构建。因此,我们必须将代码打包、压缩并转换为所有浏览器都能理解的代码,这就是 webpack 能够发挥作用的地方。

webpack 是一个模块打包工具(module bundler)。webpack 可以整齐地打包项目代码供浏览器使用;允许使用 Babel 编写最新版本的 JavaScript 或使用 TypeScript,将源代码编译成跨浏览器兼容的、被干净压缩的代码;还能在 JavaScript 中导入静态资源。

对开发而言,webpack 还提供了一个开发服务器,保存修改时可以实时更新模块和样式。vue createcreate-react-app 背后都依赖 webpack,但你也可以轻松设置自己的 webpack 配置文件。

webpack 还有很多其他功能,本文不会涵盖全部内容,但会帮助你熟悉相关概念并学习设置一些基础内容。

安装

首先,创建一个目录 webpack-tutorial 来存放项目,启动一个 Node 项目 。

mkdir webpack-tutorial
cd webpack-tutorial
npm init -y # creates a default package.json

还要安装 webpack 依赖:webpack 和 webpack-cli。

npm i -D webpack webpack-cli

我们创建一个名为 src 的文件夹包含所有源文件,并从创建一个简单的 index.js 文件开始。

// src/index.js
console.log('Interesting!')

好的,现在就有一个安装了基本包并具备启动入口文件的 Node 项目。接下来开始创建配置文件。

基本配置

现在,我们开始编写 webpack 打包配置。在项目的根目录中创建一个 webpack.config.js 文件。

入口

设置 webpack 配置的第一步是定义入口文件(entry point),即 webpack 将编译哪个文件或哪些文件。本例中,我们将把入口文件设置为 src/index.js

// webpack.config.js
const path = require('path')

module.exports = {
  entry: {
    main: path.resolve(__dirname, './src/index.js'),
  },
}

输出

output 用于指定打包文件的输出位置,我们指定输出到 dist 文件夹。输出中的 [name] 是个占位符,反映到本例中即是 main,对应 entry 对象中的属性名(key)。

// webpack.config.js
module.exports = {
  /* ... */

  output: {
    path: path.resolve(__dirname, './dist'),
    filename: '[name].bundle.js',
  },
}

现在我们已经有了构建 bundle 所需的最小配置。在 package.json 中,我们可以创建一个运行 webpack 命令的构建脚本 build

// package.json
"scripts": {
  "build": "webpack"
}

执行:

npm run build
asset main.bundle.js 19 bytes [emitted] [minimized] (name: main)
./src/index.js 18 bytes [built] [code generated]
webpack 5.1.0 compiled successfully in 152 mss

会看到创建了一个 dist 文件夹,其中包含 main.bundle.js。目前该文件还没有发生任何变化,但我们现在已经成功地使用 webpack 进行构建了。

插件

webpack 有一个插件接口,使其非常灵活。内部 webpack 代码和第三方扩展使用插件。这里介绍一些主要的、几乎每个 webpack 项目都会使用到的插件。

HTML模板文件

我们有一个打包文件,但对我们来说还不是很有用。如果我们正在构建一个 Web 应用程序,需要一个 HTML 页面,该页面将 JavaScript 打包文件作为脚本加载。我们希望 HTML 文件自动引入我们的打包出来的脚本,使用 html-webpack-plugin 即可做到,它会创建一个 HTML 模板。

安装插件:

npm i -D html-webpack-plugin

src 文件夹中创建一个 template.html 文件,我们还可以在模板中包含变量和其他自定义信息。对于本例,我们会添加一个自定义 title,除此之外的其他地方看起来就像是一个带有 root div 的普通 HTML 文件。

<!-- src/template.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>

  <body>
    <div id="root"></div>
  </body>
</html>

在配置中创建一个 plugins 属性然后添加插件,指定模板文件地址以及输出文件名(index.html),输出文件的内容是基于模板文件创建的。

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

module.exports = {
  /* ... */

  plugins: [
    new HtmlWebpackPlugin({
      title: 'webpack Boilerplate',
      template: path.resolve(__dirname, './src/template.html'), // template file
      filename: 'index.html', // output file
    }),
  ],
}

再次运行构建,将看到 dist 文件夹下会多出来一个加载了打包文件的 index.html。成功!如果你在浏览器中加载这个 HTML,会在控制台中看到 Interesting! 的输出结果。

我们更新下 index.js 入口文件,尝试向网页中注入一些内容,然后再次运行构建命令。

// Create heading node
const heading = document.createElement('h1')
heading.textContent = 'Interesting!'

// Append heading node to the DOM
const app = document.querySelector('#root')
app.append(heading)

现在为了测试,你可以进入 dist 文件夹启动服务器查看。(全局安装 http-server)。

http-server

译注:不想全局安装的,直接执行 npx http-server 也可以。

你会看到 JavaScript 代码注入到 DOM 中了,显示 Interesting!。还能看到打包文件代码被压缩了。

Clean

你可能还需要设置 clean-webpack-plugin,它会在每次重新构建后清除 dist 文件夹中的所有内容。这很重要,以确保不会留下旧数据。

安装插件:

npm i -D clean-webpack-plugin 
// webpack.config.js
const path = require('path')

const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

module.exports = {
  /* ... */

  plugins: [
    /* ... */
    new CleanWebpackPlugin(),
  ],
}

模块和加载器

webpack 使用 加载器(Loader) 来预处理通过 模块(modules) 加载的文件。它可以是 JavaScript 文件、或是像图片和 CSS 这样的静态资源,或是像 TypeScript 和 Babel 这样的编译器。webpack 5 还具有一些用于资源的内置加载器。

到目前为止,我们的项目有了一个 HTML 文件,加载并引入了一些 JavaScript,但实际上仍然没有做任何事情。那么,我们还希望 webpack 配置能帮忙完成哪些任务?

  • 将最新版本的的 JavaScript 编译成浏览器可以理解的版本

  • 导入样式并将 SCSS 编译为 CSS

  • 导入图片和字体文件

  • (可选)设置 React 或 Vue

我们要做的第一件事是设置 Babel 来编译 JavaScript。

Babel(JavaScript)

Babel 是一个工具,允许我们现在就能使用未来版本的 JavaScript 语法。

我们将检查项目中任何(node_modules 之外的)的 .js 文件,并使用 babel-loader 进行转译。还有安装一些额外的与 Babel 相关的依赖项。

译注:@babel/plugin-proposal-class-properties 已作为 ES2022 新功能发布,已包含在 @babel/preset-env 中了。由于原文发布时间在 2020 年,所以作者才会以此举例。我们可以忽略这个事实,跟随安装即可。

npm i -D babel-loader @babel/core @babel/preset-env @babel/preset-env @babel/plugin-proposal-class-properties
// webpack.config.js
module.exports = {
  /* ... */

  module: {
    rules: [
      // JavaScript
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: ['babel-loader'],
      },
    ],
  },
}

TIP:如果您正在设置一个 TypeScript 项目,那么需要使用 ts-loader 而不是 babel-loader 来满足所有 JavaScript 转译需求。使用 ts-loader 检查所有 .ts 文件。

现在 Babel 已经安装好了,但还没有应用 Babel 插件。所以直接在 index.js 中 @babel/plugin-proposal-class-properties 插件特性无法工作。

// src/index.js

// Create a class property without a constructor
class Game {
  name = 'Violin Charades'
}
const myGame = new Game()
// Create paragraph node
const p = document.createElement('p')
p.textContent = `I like ${myGame.name}.`

// Create heading node
const heading = document.createElement('h1')
heading.textContent = 'Interesting!'

// Append SVG and heading nodes to the DOM
const app = document.querySelector('#root')
app.append(heading, p)
ERROR in ./src/index.js
Module build failed (from ./node_modules/babel-loader/lib/index.js):
SyntaxError: /Users/you/webpack-tutorial/src/index.js: Support for the experimental syntax 'classProperties' isn't currently enabled (3:8):

  1 | // Create a class property without a constructor
  2 | class Game {
> 3 |   name = 'Violin Charades'
    |        ^
  4 | }

要解决这个问题,只需在项目的根目录中创建一个 .babelrc 文件。将使用 preset-env 中的许多默认设置,并添加插件 plugin-proposal-class-properties

// .babelrc

{
  "presets": ["@babel/preset-env"],
  "plugins": ["@babel/plugin-proposal-class-properties"]
}

现在再运行 npm run build,就能看到成功编译后的结果了。

图片

你需要能够直接将图片导入到 JavaScript 文件中,但这不是 JavaScript 默认可以做的。我们举个例子,先创建 src/images 目录并在其中添加一张图片,然后在 index.js 文件中导入这张图片。

// src/index.js

import example from './images/example.png'

/* ... */

运行构建时,会看到报错:

ERROR in ./src/images/example.png 1:0
Module parse failed: Unexpected character '�' (1:0)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders

webpack 内置了的 资源模块(assets modules),用来处理静态资源。对于图片类型,我们要使用 asset/resource。请注意,这是一种类型 type 而不是加载器 loader

// webpack.config.js
module.exports = {
  /* ... */
  module: {
    rules: [
      // Images
      {
        test: /\.(?:ico|gif|png|jpg|jpeg)$/i,
        type: 'asset/resource',
      },
    ],
  },
}

构建后,你会看到文件被输出到 dist 文件夹中。

字体和内联

webpack 的资源模块还支持使用 asset/inline 类型来内联一些数据,比如 SVG 和字体。

// src/index.js 

import example from './images/example.svg'

/* ... */
// webpack.config.js
module.exports = {
  /* ... */
  module: {
    rules: [
      // Fonts and SVGs
      {
        test: /\.(woff(2)?|eot|ttf|otf|svg|)$/,
        type: 'asset/inline',
      },
    ],
  },
}

样式

使用样式加载器是必要的,这样才能像在脚本中使用类似 import 'file.css' 方式加载样式。

现在有很多人使用 CSS-in-JSstyled-components 和其他工具引入样式到 JavaScript 应用程序中。也许你想使用 PostCSS,在任何浏览器中都可以使用最新的 CSS 特性。或者你想使用 Sass,一种 CSS 预处理器。

我想同时使用这三种技术——用 Sass 编写、用 PostCSS 处理并编译成 CSS,这需要引入一些加载器和依赖项。

npm i -D sass-loader postcss-loader css-loader style-loader postcss-preset-env sass postcss

就像使用 Babel 一样,PostCSS 也需要一个配置文件,请创建并将其添加到根目录。

// postcss.config.js
module.exports = {
  plugins: {
    'postcss-preset-env': {
      browsers: 'last 2 versions',
    },
  },
}

为了测试 Sass 和 PostCSS 是否正常工作,我们创建 src/styles/main.scss 文件使用 SCSS 变量和 PostCSS 能力(lch 单位)

// src/styles/main.scss

$font-size: 1rem;
$font-color: lch(53 105 40);

html {
  font-size: $font-size;
  color: $font-color;
}

现在在 index.js 中导入文件并增加加载器配置。加载器是按照从后往前的顺序进行编译的,所以列表中最后一个是sass-loader,因为需要进行编译,然后是 PostCSS、CSS 和最后的 style-loader,它将把 CSS 通过 <style> 标签注入到 DOM 中。

// src/index.js

import './styles/main.scss'

/* ... */
// webpack.config.js

module.exports = {
  /* ... */
  module: {
    rules: [
      // CSS, PostCSS, and Sass
      {
        test: /\.(scss|css)$/,
        use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader'],
      },
    ],
  },
}

现在当你重新构建时,你会注意到 Sass 和 PostCSS 已经被应用了。

TIP:以上皆是开发设置。对于生产环境,你可能要使用 MiniCssExtractPlugin 而不是 style-loader,它将导出CSS 压缩文件。你可以在 webpack 5 模板仓库 中找到这个设置。

开发

每次更新时都运行 npm run build 很繁琐。特性是网站越大,这样构建所需的时间就越长。所以,需要为 webpack 设置两个配置:

  • 生产配置,它会缩小、优化并删除所有源映射(source maps);

  • 开发配置,它在服务器上运行 webpack,每次更改时更新,并具有源映射

开发模式不会将文件构建到 dist 文件中,而是直接在内存中运行。

要进行开发设置,需要安装 webpack-dev-server

npm i -D webpack-dev-server

为了演示目的,我们可以将开发配置添加到当前正在构建和测试的 webpack.config.js 文件中。但是,需要创建两个配置文件:一个带有 mode: production,另一个带有 mode: development。在 webpack 5 模板仓库 中,我演示了如何使用 webpack-merge 将所有基本的 webpack 配置放入一个文件中,并将任何特殊的开发或生产配置放入webpack.prod.jswebpack.dev.js 文件中。

// webpack.config.js

module.exports =  {
  /* ... */
  mode: 'development',
  devServer: {
    historyApiFallback: true,
    static: {
      directory: path.join(__dirname, './dist'),
    },
    open: true,
    compress: true,
    hot: true,
    port: 8080,
  },
})

我们添加了开发模式(mode: development),并创建一个 devServer 属性。设置了一些默认值 :端口 8080,启动时自动打开浏览器窗口,并使用 热模块替换(hot-module-replacement),这里是用到webpack.HotModuleReplacementPlugin 插件,能够在不重新加载页面的情况下更新模块。因此,如果你更新了某些样式,则只有那些样式会更改,而无需重新加载整个JavaScript 代码库,从而大大加快了开发速度。

现在你可以使用 webpackserve 命令来启动服务器了。

// package.json

"scripts": {
  "start": "webpack serve"
}
npm start

当你运行这个命令时,浏览器会自动打开 localhost:8080 地址链接。现在你可以修改 Sass 和 JavaScript 文件,能够实时查看到更新。

总结

这应该能帮助你开始使用 webpack。再次提醒,我创建了一个生产就绪的 webpack 5 模板仓库,包括 Babel、Sass、PostCSS、生产优化和开发服务器等,包含本文中的所有内容但更加详细。使用该仓库,你可以轻松地设置React、Vue、Typescript 或其他任何你想要的东西。

克隆下来后,尝试操作一下吧。Enjoy!