webpack5 的使用(一):起步

6,551 阅读7分钟

这是我参与更文挑战的第2天,活动详情查看: 更文挑战

前言

本系列将分多篇文章,逐一介绍 webpack5 的使用。
本系列比较新手向,适合从来没有使用过 webpack 或略知一二的人看,不适合希望深入 webpack 的人看。

起步

安装 webpack

webpack 依赖 node 环境,若没有安装 node,请先移步安装 node。

先创建一个空白文件夹 test,输入以下命令,初始化项目。

npm init

输入命令后,会提示你输入一些信息,直接 enter 下一步即可。

test 文件夹里将会自动创建一个 package.json 文件,该文件是这个项目的描述文件,用来存储项目的一些信息,如:项目名称、版本号、运行脚本等。

安装 webpack 和 webpack-cli(如果是 webpack4+,需要安装 webpack-cli)

npm install --save-dev webpack webpack-cli

在 package.json 文件中,可以看到新增了 devDependencies,并且里面含有 webpack 和 webpack-cli 及对应的版本号,说明已经成功安装好 webpack。

image.png

基本配置

在根目录下创建一个 src 目录,src 目录下创建一个 js 目录,里面创建一个 index.js 入口文件。

index.js 文件写上测试代码。

console.log('这是一个入口文件')

在根目录下创建一个 build 目录,创建一个 webpack.config.js 文件,里面写上如下代码。

const path = require('path')

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

entry 是用来写入口配置,output 是用来写出口配置。
path.resolve 方法用于生成绝对路径,这个方法有些难懂,可以理解为一个 cd 操作,先 cd 到第一个参数路径,再配合第二个参数生成绝对路径,可以看下此文章,当初,我也是看了这篇文章才恍然大悟。

这里的配置的用意就是:index.js 作为入口文件,构建后输出到 dist 目录下的 main.js 文件。

在 package.json 文件里的 script 添加一条 build 构建命令,如下:

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

运行命令 npm run build,我们可以发现根目录多了一个 dist 目录,里面有一个 main.js 文件。

image.png

因为现在没有 html 载体,js 将不能在浏览器运行的,下面将进行添加 html。

html-webpack-plugin

安装 html-webpack-plugin,该插件用于管理 html,利用好此插件可以开发单页面应用或多页面应用。

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

用法一:单页面

在 webpack.config.js,引入 html-webpack-plugin,并进行对应配置。

const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
    ...
    // 新增 plugins 属性
    plugins: [
        new HtmlWebpackPlugin({
          title: '首页'
        })
    ]
}

运行构建命令:npm run build。 我们可以发现,dist 目录自动生成了一个 title 为“首页”的 index.html,并且引入了 main.js。

image.png

用法二:多页面

但有时候我们的页面并不是一个页面,可能还有其他页面。
假设还需要开发其他两个页面:header.html 和 footer.html。

我们在 src 目录创建 html 目录,里面创建三个文件 index.html、header.html、footer.html,注意这里也需要创建 index.html 文件,我们不再需要 webpack 为我们自动生成页面,而是以这几个文件为模板生成。

修改 webpack 配置,如下:

module.exports = {
  entry: path.resolve(__dirname, '../src/js/index.js'),
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, '../dist')
  },
  plugins: [
    // new HtmlWebpackPlugin({
    //   title: '首页'
    // }),
    // 配置多个 HtmlWebpackPlugin,有多少个页面就配置多少个
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../src/html/index.html'),
      filename: 'index.html',
      chunks: ['main'] // 与入口文件对应的模块名(entry 配置),这里可以理解为引入 main.js
    }),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../src/html/header.html'),
      filename: 'header.html',
    }),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../src/html/footer.html'),
      filename: 'footer.html',
    }),
  ]
}

我们再 build 一下,可以发现 dist 目录下多了 header.html 和 footer.html。
但是如果 header.html 和 footer.html 也有自己的 js,应该怎么办?
我们还需要修改一下 entry 、output 和 HtmlWebpackPlugin 的配置。

先在 src/js 目录下创建 header.js 和 footer.js。
修改 webpack.config.js 里的 entry。

module.exports = {
    // entry: path.resolve(__dirname, '../src/js/index.js'),
    entry: {
        main: path.resolve(__dirname, '../src/js/index.js'),
        header: path.resolve(__dirname, '../src/js/header.js'),
        footer: path.resolve(__dirname, '../src/js/footer.js'),
    },
    output: {
        // filename: 'main.js',
        filename: '[name].[fullhash].js', // 不再指定文件名,用 [name] 来输出原文件名
        path: path.resolve(__dirname, '../dist')
    },
    plugins: [
    // new HtmlWebpackPlugin({
    //   title: '首页'
    // }),
    // 配置多个 HtmlWebpackPlugin,有多少个页面就配置多少个
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../src/html/index.html'),
      filename: 'index.html',
      chunks: ['main'] // 与入口文件对应的模块名(entry 配置),这里可以理解为引入 main.js
    }),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../src/html/header.html'),
      filename: 'header.html',
      chunks: ['header'] // 添加 chunks
    }),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../src/html/footer.html'),
      filename: 'footer.html',
      chunks: ['footer']
    }),
}

build 一下,可以发现 dist 目录包含了 main、header、footer 等相关 js。
你可能会发现 js 文件名会有一串码,这串码是哈希码,是上面 output 里面的“[fullhash]”起作用,为什么要有加这个代码?
我们都知道浏览器是有缓存的,每次打包部署一个项目,如果 js 文件名或引入 js 链接没有改变,浏览器将会使用上一次缓存下来的 js 文件,这显然不是我们想要的,因此每次打包时我们都给资源文件(js、css、图片等)添加一串不同的哈希码,防止浏览器使用缓存文件。

image.png

自动清理 dist 目录

细心的人可能会发现之前生成 main.js 依然还在,如果我们每次在构建时都要手动清理一下 dist 目录,会很麻烦。

image.png

新版本做法

现在 webpack 5.20.0+,已经自带清理功能,只要配置一下 output 的 clean 即可。

output: {
    // ...
    clean: true, // 在生成文件之前清空 output 目录
},

旧版本做法

旧版本,可以进行安装 clean-webpack-plugin 解决这个问题。

npm i -D clean-webpack-plugin

webpack.config.js

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

module.exports = {
    ...
    plugins: [
        ...
        new CleanWebpackPlugin(),
    ]
}

可以发现我们每次构建时,dist 目录都会被删除,然后再构建出新的 dist 目录和对应的内容。

注意:这个项目将采用旧版本做法。

webpack-dev-server

webpack-dev-server 是一个开发服务器,可以实现 HMR(模块热替换),所谓的 MHR 就是它把构建出来的文件保存到运行内存中,确保运行速度,每当你的开发代码发生改变时,立刻重新构建一遍到内存中,且通知浏览器更新页面内容。

利用 webpack-dev-server,我们可以大大提高开发效率。

安装 webpack-dev-server

npm i -D webpack-dev-server

webpack.config.js

module.exports = {
    ...
      devServer: {
        port: 3000,
        hot: true,
        contentBase: '../dist' // 如果出错,请将 contentBase 替换为 static
      },
}

如果上面的 contentBase 导致了报错,请改为 static。contentBase 是旧版本的写法,static 是新版本(大概是 webpack-dev-server 4+ )的写法。

package.json 添加脚本 dev

{
    ...
    "scripts": {
        "dev": "webpack server --config build/webpack.config.js --open",
        ...
    },
    ...
}

运行命令 npm run dev

我们可以发现浏览器自动打开并加载我们的页面了,如果不想要浏览器自动打开,删掉 dev 脚本命令里的 --open 即可。

当然,我们也可以自己输入 http://localhost:3000 (端口要看实际部署的) 进行加载页面。

注意:有些技术博客,在脚本命令那里可能是这样写的。

"scripts": {
    "dev": "webpack-dev-server --config build/webpack.config.js --open",
    ...
},

在 webpack-cli4(对应 webpack5,webpack-cli3 才是对应 webpack4)里,webpack-dev-server 命令一运行,会报无法找到模块 webpack-cli/bin/config-yargs 错误,因为 webpack-cli4 已经去除了模块 webpack-cli/bin/config-yargs,如果想用 webpack-dev-server 命令,则需要降级为 webpack-cli3,否则使用 webpack server 命令代替。

当时 webpack4 刚升级 webpack5 那会,我就发现了这个问题,但 webpack 文档也明显写着是使用 webpack-dev-server(估计没有及时更改),在百度也找不到解决方法,后面在 github 的 webpack 的 issue 发现了这个问题的解决方法。所以,大家如果发现问题,可以多点查或提 github 的 issue,这是个很不错的方法。

  • 2021.9.18 补充 现在最新的版本,好像已经没有这个问题了,webpack-dev-server 和 webpack server 命令均可以用,可能是官方为了兼容,改回来了。不过建议在新版本里使用 webpack server 命令。

这个问题的相关讨论,具体可以看这两个网站:

  1. Cannot find module 'webpack/bin/config-yargs' - Stack Overflow
  2. Error: Cannot find module 'webpack-cli/bin/config-yargs' · Issue #2759 · webpack/webpack-dev-server (github.com)

完整代码

目录

image.png

webpack.config.js

const path = require('path')

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

module.exports = {
  // entry: path.resolve(__dirname, '../src/js/index.js'),
  entry: {
    main: path.resolve(__dirname, '../src/js/index.js'),
    header: path.resolve(__dirname, '../src/js/header.js'),
    footer: path.resolve(__dirname, '../src/js/footer.js'),
  },
  output: {
    // filename: 'main.js',
    filename: '[name].[fullhash].js',
    path: path.resolve(__dirname, '../dist')
  },
  devServer: {
    port: 3000,
    hot: true,
    contentBase: '../dist'
  },
  plugins: [
    // new HtmlWebpackPlugin({
    //   title: '首页'
    // }),
    // 配置多个 HtmlWebpackPlugin,有多少个页面就配置多少个
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../src/html/index.html'),
      filename: 'index.html',
      chunks: ['main'] // 与入口文件对应的模块名(entry 配置),这里可以理解为引入 main.js
    }),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../src/html/header.html'),
      filename: 'header.html',
      chunks: ['header']
    }),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../src/html/footer.html'),
      filename: 'footer.html',
      chunks: ['footer']
    }),
    new CleanWebpackPlugin(),
  ]
}

package.json

{
  "name": "test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "webpack server --config build/webpack.config.js --open",
    "build": "webpack --config ./build/webpack.config.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "clean-webpack-plugin": "^4.0.0-alpha.0",
    "html-webpack-plugin": "^5.3.1",
    "webpack": "^5.38.1",
    "webpack-cli": "^4.7.0",
    "webpack-dev-server": "^3.11.2",
  }
}

系列文章

webpack5 的使用(零):概念
webpack5 的使用(一):起步
webpack5 的使用(二):多个环境配置
webpack5 的使用(三):加载 css
webpack5 的使用(四):加载资源文件
webpack5 的使用(五):babel 转译 js 代码
webpack5 的使用(六):优化