从 0 配置 webpack(一)

写在前面

webpack 是一个用于 JavaScript 应用程序的静态模块打包工具。

首先需要搞清楚,为什么需要打包工具?为什么直接编辑的代码不能直接发布?非要经过打包工具打包后才能在浏览器正常运行?

在我在学校上课初学 html、css、js时,根本就没进行过什么打包,所以在一开始深入学习时面对项目必须打包也是一脸疑惑。

原来,很早之前用 html、css、js 写的小项目都是用的 ES5 语法和 <script><style>引入 js 和 css 文件。这些前端基础语法都是浏览器本身就可以解析的,用浏览器打开时当然可以直接渲染出页面显示出来。

但是这些页面在一些旧版本的浏览器下也会出现各种报错,原因就是旧版本浏览器开发时只能解析更旧的语法,比如只能解析 html的语法,不能解析html5的语法等等。这就是常说的浏览器兼容问题。

浏览器版本的更新换代远比 html、css、js 等语法的更新和扩展要慢得多,随着前端的发展,html、css、js 语言在逐渐更新。比如 js 都到了 ES11(ES2020) 版本了,但大部分浏览器还不支持 ES6(ES2015) 语法。

而且,前端开发者根据实际需求,为了更好地开发,也在这三种语言的基础上进行了扩展,比如,在 css 语言基础上又开发了更方便的 sass 语法,浏览器更不认识 sass 语法了。

前端语言语法的新升级和各种前端功能扩展库的出现,都是极大地方便了开发者进行网页的开发,岂能因为你浏览器更新速度慢就不使用了呢?

因此前端开发就走上了开发时编辑的代码和最终运行时的代码完全不同的道路,走这条路的关键就是通过各种打包工具实现。

新的前端开发模式允许开发者在开发过程中只注重开发本身,怎么方便快捷就怎么来,比如使用更高效的 ES6 语法和 sass 文件等等。项目发布时通过各种打包工具将在开发代码中使用的浏览器不认识的高级语法和语言编译成最原始的 html、css、js 文件,发布到浏览器就可以了。

好了,以上就是 webpack 打包工具起到的作用了,那么开始使用 webpack 吧。

1. 安装 webpack

在安装 webpack 前,我们要新建一个文件夹,用 npm 或 yarn 来初始化生成一个 package.json 文件,其中,-y 是在初始化过程中各种询问都选 yes 的意思,详细可见 npm 或 yarn 官网。

npm init -y
//yarn init -y

webpack 是一个工具,是需要额外安装的,安装命令如下:

npm install webpack webpack-cli --save-dev
//yarn add webpack webpack-cli --dev

补充:
webpack4+ 以上的版本的安装必须同时安装 webpack-cli 工具。

安装好了 webpack 之后,我们就可以使用 webpack 工具来打包我们的项目了,但其实,webpack 更像是一个只有一个入口,有无数个出口的调度器。webpack 本身不具备转译各种文件的功能,每种语法或者文件的转译要依赖其对应的编译器。webpack 将这些不同的编译器集成到一起,通过一定的规则来调度从入口进来的文件送到对应的编译器处进行编译后输出。

因此 webpack 打包工具只负责 接收 ——> 调度分配出口 ——> 输出。它并不知道该让什么文件走什么出口,因此需要开发者配置 webpack 的调度规则,告诉它该怎么调度。

2. 用 webpack 转译 js

js 文件打包自然就是要用到将 js 文件的高级语法转化为所有浏览器都支持的低级语法了,那么能实现这种功能的编译器是啥呢?有很多,但常用在 webpack 中的就是 babel。babal 编译器就可以实现 js 文件的打包。

接下来要打包 js 文件是不是应该安装 babel 并在 webpack 中进行 js 打包规则的配置了呢?

并不需要!因为 webpack 内置了 babel。webpack 默认就支持打包 js 文件,让其走 babel 编译器进行输出。那就编写一个 js 文件打包下试试吧!

webpack 默认的打包路径调度规则就是:将 src 目录下的文件打包输出到 dist 目录,因此 src 目录是编辑文件目录,dist 目录是运行文件目录

因此,就在该项目中新建 src 文件夹,在 src 文件夹下新建一个 index.js 文件,先写一个简单的输出。
index.js

console.log('hi')

然后运行 webpack 的打包命令进行 js 文件的打包吧。因为 webpack 工具是局部安装的,因此不能直接运行 webpack,可以使用相对路径进行运行,或者使用 npx 工具。(npx 工具会先去 node_modules 目录下找,找不到再去系统环境变量中找)

npx webpak

//最原始的局部命令的使用,当 npx 运行出现问题时可使用最原始的方法
./node_modules/.bin/webpack

运行完 npx webpack 命令后,webpack 就帮我们打包好了,可以看到在项目中生成了一个 dist 目录。这个 dist 目录就是可以发布到浏览器直接运行的静态网页。

可以看到,经过 webpack 的打包,将 src 目录下的 index.js 映射成了 dist 目录下的 main.js。而且 main.js 还加了好多内容,这些内容就是用来兼容浏览器的各种语法。

好了,js 文件的打包就正式完成了。

2.1 配置 webpack 消除警告

但此时我们发现,在运行 npx webpack 打包时总是有一个警告:

从 0 配置 webpack(一)

它说我们没有设置模式,因此就额外创建一个 webpack.config.js 文件来配置 webpack 的相关信息,从而消除警告。

webpack.config.js

var path = require('path');

module.exports = {
    mode: 'development'
};

mode 是用来切换模式的,可以切换至 development 或 production 模式,开发者模式下生成的 dist 目录中的文件中有很多注释和代码格式,方便开发者查看。产品模式下的 dist 目录中的文件是尽可能地压缩,用于发布时

2.2 配置 entry 和 output

上述方式直接打包 js 文件就是直接使用 webpack 默认的配置,默认打包 js 文件的流程是,将 src/index.js 打包成 dist/main.js

我们也可以通过配置 webpack 来自定义 js 文件打包的入口和出口。

webpack.config.js

var path = require('path');

module.exports = {
    mode: 'production',
    entry: './src/index.js',    //配置入口,默认就是 './src/index.js'
    output: {
        filename: 'index.js'   //index.js 打包后输出的文件名
        //path: path.resolve(__dirname, 'dist'),   //配置打包后放到的目录,默认就是 'dist',该句可不写
    }
};

2.3 配置 hash 缓存

因为 http 有缓存,因此我们在发布的项目的文件名一般用 hash 的方式,便于以后的更新,因此,如何配置让 webpack 打包成有 hash 缓存的文件名呢?如下:

var path = require('path');

module.exports = {
    mode: 'production',
    entry: './src/index.js',
    output: {
        filename: '[name].[contenthash].js'   //修改了这句,文件名不是固定的,在配置多入口时必须为不固定的
    }
};

默认 hash 缓存的位数是 20 位,可以执行位数,如 [contenthash:8] 表示指定 hash 缓存位数为 8 位。

其实 webpack 在打包时很智能,如果源文件内容没有任何改变时,重新打包的 hash 名是不变的

2.4 使用自定义 script

在上述的方式中,每次运行 npx webpack 打包脚本都会在 dist 目录中添加东西,需要我们手动删除先前的 dist 目录:rm -rf dist

为了简化操作流程,可以在 package.json 中添加 scripts 脚本,如下:

package.json

"scripts": {
   "build": "rm -rf dist && webpack",
   "test": "echo "Error: no test specified" && exit 1"
}

在 scripts 脚本中的命令会先在本地找,因此就不用加 npx 就可以运行。直接是 webpack

设置了自定脚本后,我们就可以直接运行 build 命令了,如下:

yarn build

//npm run build

当脚本运行环境不是 bash 时,例如像 window 的 cmd 命令行,无法执行 rm -rf 命令,因此使用相同功能的 npm 模块 rimraf 代替

3. 用 webpack 插件自动生成 html

上述方式打包好了 js 文件,生成的 dist 目录无法预览,因为没有 html 文件,浏览器的预览入口就是 html 文件。可以临时在 dist 目录下添加一个 index.html 文件,使用 http-server 临时预览下。但这样是没用的,永远不要在 dist 目录下添加东西,因为在重新打包后就丢失了。

dist 目录中一般存放的是打包好的文件,不能修改。因此使用 webpack 提供的插件自动生成 html 文件。这个插件就是:html-webpack-plugin

yarn add html-webpack-plugin --dev

webpack.json.js 中进行相关配置

var HtmlWebpackPlugin = require('html-webpack-plugin');  //添加引入
var path = require('path');

module.exports = {
    mode: 'production',
    entry: './src/index.js',
    output: {
        filename: '[name].[contenthash].js'
    },
    plugins: [new HtmlWebpackPlugin()]  // 添加插件信息
};

此时再次运行 yarn build 打包生成的文件中就有了 html 文件。还可以详细设置生成 html 文件的选项,详看 html-webpack-plugin

这里使用模板对生成的 html 进行配置,在 src 目录下新建 assets 目录,在 assets 目录中新建 index.html ,是要生成的 html 文件的模板。

src/assets/index.html

<!doctype html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
    <div ></div>
</body>
</html>

其中 <%= htmlWebpackPlugin.options.title %> 就是使用 webpack.config.js 中传入的 title 的意思。

webpack.config.js

plugins: [new HtmlWebpackPlugin({
    title: 'My APP',
    template: 'src/assets/index.html'
})]

在 webpack 配置多入口时,自动生成的 html 文件也会自动创建多个 script 标签

4. 用 webpack 引入 css

我们新建立一个 css 文件,在 js 文件中使用 import 引入,进行打包时发现会出错,那是因为我们没有设置 css 文件的打包出口,在 js 文件中引入后会被当作 js 语法处理,css 语法在 js 中就会报错。

因此,想引入 css 文件,我们就需要配置 css 的编译出口。也是 loader。

4.1 使用 style 标签引入 css

一种方式是使用 css-loader + style-loader 的方式将 css 文件以 <style> 标签的形式存放到打包后的 index.html 中。详看 css-loader

安装 css-loader 和 style-loader

yarn add css-loader --dev

yarn add style-loader --dev

在 webpack.config.js 中配置

module.exports = {
  module: {
    rules: [
      {
        test: /.css$/i,
        use: ['style-loader', 'css-loader'],
      },
    ],
  },
};

用 webpack 打包后可以看到从 js import 进去的 css 文件变成了 style 标签样式

4.2 将 css 抽离成一个文件

上述如果样式很多时全部以 style 标签的形式放到页面中会很不好管理,因此,可以抽离成一个个独立的 css 文件。

这里介绍一种 MiniCssExtractPlugin

安装 mini-css-extract-plugin

yarn add mini-css-extract-plugin --dev

配置信息

const MiniCssExtractPlugin = require('mini-css-extract-plugin');
plugins: [
    new MiniCssExtractPlugin({
        filename: '[name].[contenthash].css',
        chunkFilename: '[id].[contenthash].css',
        ignoreOrder: false,
    })
],
module: {
    rules: [
    {
        test: /.css$/i,
        //use: ['style-loader', 'css-loader'],
        use: [
            {
                loader: MiniCssExtractPlugin.loader,
                options: {
                    publicPath: '../',
                    hmr: process.env.NODE_ENV === 'development'
                }
            },
            'css-loader'
        ]
    },
],

补充:
还有一个常用的 css 文件抽离插件叫做 ExtractTextWebpackPlugin,一个字:超级难用,各种 bug ,版本问题等。详情可见: webpack-issue,所以,不推荐使用 ExtractTextWebpackPlugin,上述介绍的 mini-css-extract-plugin 很好用。

5. 使用 webpack-dev-server

按照上述的配置过程,如果我们想要查看编辑代码在浏览器的运行效果,需要 yarn build 打包成 dist 文件,然后使用网页预览工具,如 http server 进入 dist 目录进行预览查看。

这样每次都要重新 build 才能预览效果实在是太麻烦了,webpack 提供了周边工具,webpack-dev-server 来进行预览,不依赖于 dist 文件,不需要每次 build。

安装 webpack-dev-server

yarn add webpack-dev-server --dev

配置方法详看官网的 webpack-dev-server

--open //表示启动并自动打开浏览器
--hot  //表示开启模块热替换技术自动刷新页面
--devtool source-map  //表示启用 source-map 模式