您的位置:首页 > 经济 >

Webpack 学习笔记

2023-05-30 13:02:12 来源:博客园
Webpack 学习笔记

这篇学习笔记将用于记录本人在学习 Webpack 打包工具过程中所编写的心得体会与代码示例。为此,我会在https://github.com/owlman/study_note项目的Programming/Client-Server/Frameworks目录下创建一个名为的webpackjs目录,并在该目录下设置以下两个子目录:


(资料图片仅供参考)

note目录用于存放markdown格式的笔记。code目录则用于存放笔记中所记录的代码示例。学习规划学习基础:掌握 HTML、CSS、JavaScript 相关的基础知识。掌握 Node.js 运行平台的基础知识。掌握 npm 包管理器的基本用法。了解 B/S 应用程序架构的基本原理。学习资料:视频资料:webpack 前端配置线上文档:webpack 官方文档为何需要打包

程序员们在构建应用程序的前端部分时往往会出于可重用性方面的考虑将用户界面划分成不同的组件来编写,我们将这种编程思路称为模块化编程。在模块化编程中,每个模块通常都会涉及到一段用于描述界面元素的 HTML 代码,这些 HTML 代码又会去分别加载一系列 JavaScript 代码、CSS 样式以及其他静态资源(包括图片、字体、视频等)。并且在许多情况下,这些代码、样式和资源还都分别被存储在不同类型的文件中,这些文件之间是存在着一定依赖关系的。这就带来了一个潜在的问题:即当 Web 浏览器或其他客户端在加载某个模块时,如果该模块中文件的加载顺序和速度因各种不同的客观条件而产生一些不可预测的状况,那么这些状况中的大部分都会给应用程序带来一些负面影响。如果想避免这些状况,程序员们就应该考虑先将这些模块压缩并打包成更便于加载的文件单元。

除了模块加载带来的隐患之外,Web 浏览器或其他客户端对 JavaScript 语言标准的支持程度也是一个不容忽视的问题。毕竟,如今依然还存在着大量的用户仍在使用比 IE9 更老旧的浏览器,这些浏览器是完全不支持 ES6 标准规范的。如果希望应用程序被更多的用户使用,我们也需要将使用 ES6 标准编写的 JavaScript 代码转译成符合更早期标准的、具有同等效果的代码。

在如今的 Vue.js 项目实践中,上面所讨论的模块打包和代码转译工作大多数时候是通过 Webpack 这个工具来完成的。Webpack 是一个基于 JavaScript 语言的现代化前端打包工具,它会尝试着在前端项目中各类型文件之间构建起一个依赖关系图,这个关系图很大程度上就体现了应用程序中各模块之间、以及模块内部文件之间存在的依赖关系。然后,Webpack 会负责将这些模块按页面加载的具体需求压缩并打包成一个或多个经过压缩过的文件,整个过程如下图所示[1]

基本打包选项

接下来,就让我们以下面这个简单的webpack.config.js文件为例来说明一下使用 Webpack 打包一个 Vue.js 前端项目需要进行的基本配置吧。这个配置文件的内容如下:

const path = require("path");const VueLoaderPlugin = require("vue-loader/lib/plugin");const HtmlWebpackPlugin = require("html-webpack-plugin");const config = {    entry: {        main: path.join(__dirname,"src/main.js")    },    output: {        path: path.join(__dirname,"./public/"),        filename:"js/[name]-bundle.js"    },    plugins:[        new VueLoaderPlugin(),        new HtmlWebpackPlugin({            template: path.join(__dirname, "src/index.htm")        })    ],    module: {        rules: [            {                test: /\.vue$/,                loader: "vue-loader"            },            {                test: /\.js$/,                loader: "babel-loader"            },            {                test: /\.css/,                use: [                    "style-loader",                    "css-loader"                ]            }        ]    },    resolve: {        alias: {            "vue$": "vue/dist/vue.esm.js"        }    },    mode: "development"};module.exports = config;

正如读者所见,Webpack 的配置文件实际上是一个遵守 CommonJS 规范 JavaScript 文件,其中所有的配置工作都是通过定义一个名为config的 JSON 格式的数据对象来完成的。下面来详细介绍一下这个对象中定义的成员。

entry成员:配置入口模块

config对象中,entry成员通常是我们第一个要定义的配置选项,该选项主要用于指定对当前项目进行打包时的入口模块。换句话说,entry成员所配置的就是 Webpack 的输入选项,后者就是以该选项指定的模块为起点开始构建依赖关系图的。在该关系图的构建过程中,Webpack 会搜寻到项目中存在的所有模块,并确认这些模块内外存在的、直接或间接的依赖关系。另外,由于在许多情况下,项目的入口模块未必只有一个,所以entry成员可以有四种定义形式。首先是字符串形式,当我们确定项目自始至终只会存在单一入口模块时,entry成员就可以被直接被定义成一个字符串类型的值,其具体示例代码如下:

const path = require("path");const config = {    entry: path.join(__dirname,"src/main.js")    // 其他配置};module.export = config;

当然,以上这种配置方式只能应付一些极为简单的项目打包工作,我们在项目实践中并没有多少机会能用到它。下面再来看数组形式,当项目中存在多个入口模块,或者我们不确定项目今后会不会增加入口模块时,更好的选择是将entry成员定义成一个字符串数组,因为这样做不仅可以一次指定多个入口模块,也可以为今后增加入口模块预留接口,其具体示例代码如下:

const path = require("path");const config = {    entry:  [        path.join(__dirname,"src/main.js"),        path.join(__dirname,"liba/index.js")        // 其他入口模块    ],    // 其他配置};module.export = config;

entry成员的第三种定义形式是将它定义成一个 JSON 格式的数据对象。由于 Webpack 打包时是以 chunk 为单位来进行源码分割的,该单位在默认情况下是按照它读取到的 JavaScript 文件来进行划分的,这在我们从外部引入第三方源码时会带来一些没有必要的重复打包。如果想按照指定的业务逻辑对项目进行分 chunk 打包,也可以使用对象定义的语法来定义entry成员,其具体定义方式如下:

const path = require("path");const config = {    entry:  {        main: path.join(__dirname,"src/main.js"),        liba: path.join(__dirname,"liba/index.js"),        vendor: "vue"         // 其他入口模块    },    // 其他配置};module.export = config;

在上述配置中,我们为src/main.jsliba/index.js这两个入口模块,以及 Vue.js 这个第三方框架源文件指定了分别相应的 chunk 名称。这样一来,我们就可以在后面搭配optimization选项的配置将自己开发的业务代码和从外部引入的第三方源码分离开来。毕竟第三方框架在被安装之后,源码基本就不再会发生变化了,因此如果能将它们独立打包成一个 chunk,这一部分的源码就不用再重复打包了,这有助于提高项目的整体打包速度。

最后,如果我们在配置入口模块时需要设计一些在运行时才能获得路径的动态逻辑,也可以将entry成员定义成函数形式。其具体示例代码如下:

const path = require("path");const config = {    entry: function() {        return new Promise(function(resolve) {            // 在此处模拟一个异步调用            setTimeout(function() {                resolve(path.join(__dirname,"src/main.js"));            }, 1000);        });    }    // 其他配置};module.export = config;

需要特别说明的是,Webpack 在 4.0 之后的版本中新增了默认配置的机制,所以在使用最新版本的 Webpack 进行项目打包时,如果读者忘记了定义config对象的entry成员,Webpack 的输入选项会被配置为./src这个默认值。

output成员:配置输出选项

在配置完 Webpack 的输入选项之后,接下来要配置的自然是输出选项了。在config对象中,Webpack 的输出选项是通过定义其output成员来配置的,主要用于指定 Webpack 在完成打包工作之后以何种方式产生输出文件。在 Webpack 4.0 发布之后,该选项的默认值为./dist。在通常情况下,output成员通常会被定义成一个 JSON 格式的数据对象,该对象主要包含了以下两个最基本的属性:

filename成员:用于指定输出文件的名称;path成员:用于指定输出文件的存放路径。

下面来看一下output成员的基本定义形式,其具体示例代码如下:

const path = require("path");const config = {    entry:  [ // 配置入口模块        path.join(__dirname,"src/main.js"),        path.join(__dirname,"liba/index.js")    ],    output: { // 配置输出文件        filename: "bundle.js",        path: path.join(__dirname,"./public/")    },    // 其他配置};module.export = config;

需要注意的是,在配置输出选项时,path属性的值必须是一个绝对路径。另外,无论我们在entry成员中定义了几个入口模块,Webpack 根据上述配置产生的输出结果都是一个名为bundle.js的文件。如果想让 Webpack 根据指定的 chunk 名来产生不同文件名的输出结果,那就需要先在定义entry成员时为其指定 chunk 名称,然后再在定义output成员时将filename属性的值定义为[name].js。其具体示例代码如下:

const path = require("path");const config = {    entry:  {        main: path.join(__dirname,"src/main.js"),        liba: path.join(__dirname,"liba/index.js")    },    output: {        filename: "[name].js",        path: path.join(__dirname,"./public/")    },    // 其他配置};module.export = config;

在上述配置中,[name]是一种作用类似于模板变量一样的占位符,它在打包过程中会被自动替换成我们在配置入口模块选项时指定的 chunk 名称。这样一来,Webpack 就会在./public/目录下分别产生出main.jsliba.js这两个输出文件。除了[name]之外,我们还可以使用[id][chunkhash]等其他占位符来更详细地定义输出文件的名称。

module成员:配置预处理器

正如我们在 6.1.1 节中所说,Webpack 的工作除了对项目中的源码文件进行压缩打包之外,另一个作用就是将这些源码文件中的部分代码进行转译。这部分工作要针对的模板既包含了之前提到的、使用 ES6 标准来编写的 JavaScript 文件,也包含了 CSS、XML、HTML、PNG 等其他各种类型的文件。 在 Webpack 中,代码的转译工作是通过一个名叫预处理器(loader)的机制来完成的。在这里,预处理器可以被视为 Webpack 从外部引入的转译器组件,我们可以利用这种转译器组件处理一些非 JavaScript 类型的文件,以便可以在 JavaScript 代码中使用import语句导入一些非 JavaScript 模块。简而言之就是,在使用预处理器之前,项目中只有 JavaScript 文件才会被视为是模块,而在使用了预处理器之后,项目中的所有文件都可被视为模块。当然,为了让 Webpack 识别不同类型的模块,我们需要从外部引入相应类型的预处理器组件。在一个 Vue.js 项目中,我们通常需要引入以下最基本的预处理器组件:

css-loader:用于将 CSS 文件中的代码转译成符合 CommonJS 规范的 JavaScript 代码。style-loader:用于将css-loader产生的转译结果进一步转译成 HTML 中的