了不起的 Webpack 构建流程学习指南 上

简介: 了不起的 Webpack 构建流程学习指南 上


最近原创文章回顾:

Webpack 是前端很火的打包工具,它本质上是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 Webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有模块打包成一个或多个 bundle

其实就是:Webpack 是一个 JS 代码打包器。

至于图片、CSS、Less、TS等其他文件,就需要 Webpack 配合 loader 或者 plugin 功能来实现~

网络异常,图片无法展示
|

一、Webpack 构建流程分析

1. Webpack 构建过程

首先先简单了解下 Webpack 构建过程:

  1. 根据配置,识别入口文件;
  2. 逐层识别模块依赖(包括 Commonjs、AMD、或 ES6 的 import 等,都会被识别和分析);
  3. Webpack 主要工作内容就是分析代码,转换代码,编译代码,最后输出代码;
  4. 输出最后打包后的代码。

2. Webpack 构建原理

看完上面的构建流程的简单介绍,相信你已经简单了解了这个过程,那么接下来开始详细介绍 Webpack 构建原理,包括从启动构建到输出结果一系列过程:

(1)初始化参数

解析 Webpack 配置参数,合并 Shell 传入和 webpack.config.js 文件配置的参数,形成最后的配置结果。

(2)开始编译

上一步得到的参数初始化 compiler 对象,注册所有配置的插件,插件监听 Webpack 构建生命周期的事件节点,做出相应的反应,执行对象的 run 方法开始执行编译。

(3)确定入口

从配置文件( webpack.config.js )中指定的 entry 入口,开始解析文件构建 AST 语法树,找出依赖,递归下去。

(4)编译模块

递归中根据文件类型loader 配置,调用所有配置的 loader 对文件进行转换,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理。

(5)完成模块编译并输出

递归完后,得到每个文件结果,包含每个模块以及他们之间的依赖关系,根据 entry 配置生成代码块 chunk

(6)输出完成

输出所有的 chunk 到文件系统。

注意:在构建生命周期中有一系列插件在做合适的时机做合适事情,比如 UglifyPlugin 会在 loader 转换递归完对结果使用 UglifyJs 压缩覆盖之前的结果

二、手写 Webpack 构建工具

到这里,相信大家对 Webpack 构建流程已经有所了解,但是这还不够,我们再来试着手写 Webpack 构建工具,来将上面文字介绍的内容,应用于实际代码,那么开始吧~

1. 初始化项目

在手写构建工具前,我们先初始化一个项目:

$ yarn init -y

并安装下面四个依赖包:

  1. @babel/parser : 用于分析通过 fs.readFileSync  读取的文件内容,并返回 AST (抽象语法树) ;
  2. @babel/traverse : 用于遍历 AST, 获取必要的数据;
  3. @babel/core : babel 核心模块,提供 transformFromAst 方法,用于将 AST 转化为浏览器可运行的代码;
  4. @babel/preset-env : 将转换后代码转化成 ES5 代码;
$ yarn add @babel/parser @babel/traverse @babel/core @babel/preset-env

初始化项目目录及文件:

网络异常,图片无法展示
|

代码存放在仓库:github.com/pingan8787/…

由于本部分核心内容是实现 Webpack 构建工具,所以会从《2. Webpack 构建原理》的“(3)确定入口”步骤开始下面介绍。

大致代码实现流程如下:

网络异常,图片无法展示
|

从图中可以看出,手写 Webpack 的核心是实现以下三个方法:

  • createAssets : 收集和处理文件的代码;
  • createGraph :根据入口文件,返回所有文件依赖图;
  • bundle : 根据依赖图整个代码并输出;

2. 实现 createAssets 函数

2.1 读取通过入口文件,并转为 AST

首先在 ./src/index 文件中写点简单代码:

// src/index.js
import info from "./info.js";
console.log(info);

实现 createAssets 方法中的 文件读取AST转换 操作:

// leo_webpack.js
const fs = require("fs");
const path = require("path");
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
// 由于 traverse 采用的 ES Module 导出,我们通过 requier 引入的话就加个 .default
const babel = require("@babel/core");
let moduleId = 0;
const createAssets = filename => {
    const content = fs.readFileSync(filename, "utf-8"); // 根据文件名,同步读取文件流
    // 将读取文件流 buffer 转换为 AST
    const ast = parser.parse(content, {
        sourceType: "module" // 指定源码类型
    })
    console.log(ast);
}
createAssets('./src/index.js');

上面代码: 通过 fs.readFileSync() 方法,以同步方式读取指定路径下的文件流,并通过 parser 依赖包提供的 parse() 方法,将读取到的文件流 buffer 转换为浏览器可以认识的代码(AST),AST 输出如下:

网络异常,图片无法展示
|

另外需要注意,这里我们声明了一个 moduleId 变量,来区分当前操作的模块。 在这里,不仅将读取到的文件流 buffer 转换为 AST 的同时,也将 ES6 代码转换为 ES5 代码了。

2.2 收集每个模块的依赖

接下来声明 dependencies 变量来保存收集到的文件依赖路径,通过 traverse() 方法遍历 ast,获取每个节点依赖路径,并 pushdependencies 数组中。

// leo_webpack.js
function createAssets(filename){
    // ...
    const dependencies = []; // 用于收集文件依赖的路径
    // 通过 traverse 提供的操作 AST 的方法,获取每个节点的依赖路径
    traverse(ast, {
        ImportDeclaration: ({node}) => {
            dependencies.push(node.source.value);
        }
    });
}

2.3 将 AST 转换为浏览器可运行代码

在收集依赖的同时,我们可以将 AST 代码转换为浏览器可运行代码,这就需要使用到 babel ,这个万能的小家伙,为我们提供了非常好用的 transformFromAstSync() 方法,同步的将 AST 转换为浏览器可运行代码:

// leo_webpack.js
function createAssets(filename){
    // ...
    const { code } = babel.transformFromAstSync(ast,null, {
        presets: ["@babel/preset-env"]
    });
    let id = moduleId++; // 设置当前处理的模块ID
    return {
        id,
        filename,
        code,
        dependencies
    }
}

到这一步,我们在执行 node leo_webpack.js ,输出如下内容,包含了入口文件的路径 filename  、浏览器可执行代码 code 和文件依赖的路径 dependencies 数组:

$ node leo_webpack.js
{ 
  filename: './src/index.js',
  code: '"use strict";\n\nvar _info = _interopRequireDefault(require("./info.js"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }\n\nconsole.log(_info["default"]);', 
  dependencies: [ './info.js' ] 
}

2.4 代码小结

// leo_webpack.js
const fs = require("fs");
const path = require("path");
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
// 由于 traverse 采用的 ES Module 导出,我们通过 requier 引入的话就加个 .default
const babel = require("@babel/core");
let moduleId = 0;
function createAssets(filename){
    const content = fs.readFileSync(filename, "utf-8"); // 根据文件名,同步读取文件流
    // 将读取文件流 buffer 转换为 AST
    const ast = parser.parse(content, {
        sourceType: "module" // 指定源码类型
    })
    const dependencies = []; // 用于收集文件依赖的路径
    // 通过 traverse 提供的操作 AST 的方法,获取每个节点的依赖路径
    traverse(ast, {
        ImportDeclaration: ({node}) => {
            dependencies.push(node.source.value);
        }
    });
    // 通过 AST 将 ES6 代码转换成 ES5 代码
    const { code } = babel.transformFromAstSync(ast,null, {
        presets: ["@babel/preset-env"]
    });
    let id = moduleId++; // 设置当前处理的模块ID
    return {
        id,
        filename,
        code,
        dependencies
    }
}


目录
相关文章
|
1月前
|
JavaScript
webpack学习三:webpack初始化整合配置vue,一步一步的抽离代码块整合vue。
这篇文章是关于如何在webpack环境中配置Vue.js,包括安装Vue.js、解决报错、理解el与template的区别、使用SPA模式、抽离模板为对象、封装为单独的js文件、安装vue-loader时遇到的问题及解决方案,以及整个过程的总结。
71 2
webpack学习三:webpack初始化整合配置vue,一步一步的抽离代码块整合vue。
|
1月前
|
JavaScript
webpack学习五:webpack的配置文件webpack.config.js分离,分离成开发环境配置文件和生产环境配置文件
这篇文章介绍了如何将webpack的配置文件分离成开发环境和生产环境的配置文件,以提高打包效率。
46 1
webpack学习五:webpack的配置文件webpack.config.js分离,分离成开发环境配置文件和生产环境配置文件
|
1月前
|
JavaScript 前端开发 Java
webpack学习一:什么是模块化开发,什么是webpack,以及二者之间的关系。
这篇文章介绍了模块化开发的概念、历史和实现方式,以及webpack作为一个现代JavaScript应用的静态模块打包工具,它如何帮助我们将ES6等高级语法打包成浏览器可以识别的低级语法,并解释了npm在webpack安装和使用中的作用。
40 1
webpack学习一:什么是模块化开发,什么是webpack,以及二者之间的关系。
|
18天前
|
缓存 监控
webpack 提高构建速度的方式
【10月更文挑战第23天】需要根据项目的具体情况和需求,综合运用这些方法,不断进行优化和改进,以达到最佳的构建速度和效果。同时,随着项目的发展和变化,还需要持续关注和调整构建速度的相关措施,以适应不断变化的需求。
|
18天前
|
存储 缓存 前端开发
利用 Webpack 5 的持久化缓存来提高构建效率
【10月更文挑战第23天】利用 Webpack 5 的持久化缓存是提高构建效率的有效手段。通过合理的配置和管理,我们可以充分发挥缓存的优势,为项目的构建和开发带来更大的便利和效率提升。你可以根据项目的实际情况,结合以上步骤和方法,进一步优化和完善利用持久化缓存的策略,以达到最佳的构建效果。同时,不断探索和实践新的方法和技术,以适应不断变化的前端开发环境和需求。
|
1月前
|
移动开发 JavaScript 前端开发
webpack学习四:使用webpack配置plugin,来使用HtmlWebpackPlugin、uglifyjs-webpack-plugin、webpack-dev-server等插件简化开发
这篇文章主要介绍了如何通过配置Webpack的插件,如HtmlWebpackPlugin、uglifyjs-webpack-plugin和webpack-dev-server,来简化前端开发流程。
38 0
webpack学习四:使用webpack配置plugin,来使用HtmlWebpackPlugin、uglifyjs-webpack-plugin、webpack-dev-server等插件简化开发
|
3月前
|
前端开发 JavaScript C++
【绝技大公开】Webpack VS Rollup:一场前端工程化领域的巅峰对决,谁能笑到最后?——揭秘两大构建神器背后的秘密与奇迹!
【8月更文挑战第12天】随着前端技术的发展,模块化与自动化构建成为标准实践。Webpack与Rollup作为主流构建工具,各具特色。Webpack是一款全能型打包器,能处理多种静态资源,配置灵活,适合复杂项目;Rollup专注于ES6模块打包,利用Tree Shaking技术减少冗余,生成更精简的代码。Rollup构建速度快,配置简洁,而Webpack则拥有更丰富的插件生态系统。选择合适的工具需根据项目需求和个人偏好决定。两者都能有效提升前端工程化水平,助力高质量应用开发。
41 1
|
3月前
|
JavaScript 前端开发 API
解锁前端开发新境界:Vue.js携手Webpack,打造高效构建流程,你的项目值得拥有!
【8月更文挑战第30天】随着前端技术的发展,模块化与组件化趋势愈发显著。Vue.js 以其简洁的 API 和灵活的组件系统,深受开发者喜爱;Webpack 则凭借强大的模块打包能力成为前端工程化的基石。两者结合,不仅简化了组件编写与引用,还通过模块热替换、代码分割等功能大幅提升开发效率。本文将通过具体示例,展示如何利用 Vue.js 和 Webpack 构建高效、有序的前端开发环境。从安装配置到实际应用,逐步解析这一组合的优势所在。
49 0
|
5月前
|
缓存 前端开发 JavaScript
Webpack作为模块打包器,为前端项目提供了高度灵活和可配置的构建流程
【6月更文挑战第12天】本文探讨了优化TypeScript与Webpack构建性能的策略。理解Webpack的解析、构建和生成阶段是关键。优化包括:调整tsconfig.json(如关闭不必要的类型检查)和webpack.config.js选项,启用Webpack缓存,实现增量构建,代码拆分和懒加载。这些方法能提升构建速度,提高开发效率。
61 3
|
4月前
|
JavaScript 前端开发 应用服务中间件