做了一夜动画,让大家十分钟搞懂Webpack

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 做了一夜动画,让大家十分钟搞懂Webpack

一、什么是webpack


webpack是一个打包工具,他的宗旨是一切静态资源皆可打包。


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


二、原理分析


首先我们通过一个制作一个打包文件的原型。


假设有两个js模块,这里我们先假设这两个模块是复合commomjs标准的es5模块。

语法和模块化规范转换的事我们先放一放,后面说。


我们的目的是将这两个模块打包为一个能在浏览器端运行的文件,这个文件其实叫bundle.js。


比如


// index.js
var add = require('add.js').default
console.log(add(1 , 2))
// add.js
exports.default = function(a,b) {return a + b}


假设在浏览器中直接执行这个程序肯定会有问题 最主要的问题是浏览器中没有exports对象与require方法所以一定会报错。


我们需要通过模拟exports对象和require方法


1. 模拟exports对象


首先我们知道如果在nodejs打包的时候我们会使用fs.readfileSync()来读取js文件。这样的话js文件会是一个字符串。而如果需要将字符串中的代码运行会有两个方法分别是new Function与Eval。


在这里面我们选用执行效率较高的eval。


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


exports = {}
eval('exports.default = function(a,b) {return a + b}') // node文件读取后的代码字符串
exports.default(1,3)


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


上面这段代码的运行结果可以将模块中的方法绑定在exports对象中。由于子模块中会声明变量,为了不污染全局我们使用一个自运行函数来封装一下。


var exports = {}
(function (exports, code) {
  eval(code)
})(exports, 'exports.default = function(a,b){return a + b}')


2. 模拟require函数


require函数的功能比较简单,就是根据提供的file名称加载对应的模块。


首先我们先看看如果只有一个固定模块应该怎么写。


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


function require(file) {
  var exports = {};
  (function (exports, code) {
    eval(code)
  })(exports, 'exports.default = function(a,b){return a + b}')
  return exports
}
var add = require('add.js').default
console.log(add(1 , 2))


完成了固定模块,我们下面只需要稍加改动,将所有模块的文件名和代码字符串整理为一张key-value表就可以根据传入的文件名加载不同的模块了。


(function (list) {
  function require(file) {
    var exports = {};
    (function (exports, code) {
      eval(code);
    })(exports, list[file]);
    return exports;
  }
  require("index.js");
})({
  "index.js": `
    var add = require('add.js').default
    console.log(add(1 , 2))
        `,
  "add.js": `exports.default = function(a,b){return a + b}`,
});


当然要说明的一点是真正webpack生成的bundle.js文件中还需要增加模块间的依赖关系。


叫做依赖图(Dependency Graph)


类似下面的情况。


{
  "./src/index.js": {
    "deps": { "./add.js": "./src/add.js" },
    "code": "....."
  },
  "./src/add.js": {
    "deps": {},
    "code": "......"
  }
}


另外,由于大多数前端程序都习惯使用es6语法所以还需要预先将es6语法转换为es5语法。


总结一下思路,webpack打包可以分为以下三个步骤:


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


  1. 分析依赖


  1. ES6转ES5


  1. 替换exports和require


下面进入功能实现阶段。


三、功能实现


我们的目标是将以下两个个互相依赖的ES6Module打包为一个可以在浏览器中运行的一个JS文件(bundle.js)


  • 处理模块化


  • 多模块合并打包 - 优化网络请求


/src/add.js


export default (a, b) => a + b


/src/index.js


import add from "./add.js";
console.log(add(1 , 2))


1. 分析模块


分析模块分为以下三个步骤:


模块的分析相当于对读取的文件代码字符串进行解析。这一步其实和高级语言的编译过程一致。需要将模块解析为抽象语法树AST。我们借助babel/parser来完成。


AST (Abstract Syntax Tree)抽象语法树 在计算机科学中,或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。(astexplorer.net/)


yarn add @babel/parser
yarn add @babel/traverse
yarn add @babel/core
yarn add @babel/preset-env


  • 读取文件


  • 收集依赖


  • 编译与AST解析


const fs = require("fs");
const path = require("path");
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const babel = require("@babel/core");
function getModuleInfo(file) {
  // 读取文件
  const body = fs.readFileSync(file, "utf-8");
  // 转化AST语法树
  const ast = parser.parse(body, {
    sourceType: "module", //表示我们要解析的是ES模块
  });
  // 依赖收集
  const deps = {};
  traverse(ast, {
    ImportDeclaration({ node }) {
      const dirname = path.dirname(file);
      const abspath = "./" + path.join(dirname, node.source.value);
      deps[node.source.value] = abspath;
    },
  });
  // ES6转成ES5
  const { code } = babel.transformFromAst(ast, null, {
    presets: ["@babel/preset-env"],
  });
  const moduleInfo = { file, deps, code };
  return moduleInfo;
}
const info = getModuleInfo("./src/index.js");
console.log("info:", info);


运行结果如下:


![image-20210507152817221](/Users/xiaran/Library/Application Support/typora-user-images/image-20210507152817221.png)


2. 收集依赖


上一步开发的函数可以单独解析某一个模块,这一步我们需要开发一个函数从入口模块开始根据依赖关系进行递归解析。最后将依赖关系构成为依赖图(Dependency Graph)


/**
 * 模块解析
 * @param {*} file 
 * @returns 
 */
function parseModules(file) {
  const entry = getModuleInfo(file);
  const temp = [entry];
  const depsGraph = {};
  getDeps(temp, entry);
  temp.forEach((moduleInfo) => {
    depsGraph[moduleInfo.file] = {
      deps: moduleInfo.deps,
      code: moduleInfo.code,
    };
  });
  return depsGraph;
}
/**
 * 获取依赖
 * @param {*} temp 
 * @param {*} param1 
 */
function getDeps(temp, { deps }) {
  Object.keys(deps).forEach((key) => {
    const child = getModuleInfo(deps[key]);
    temp.push(child);
    getDeps(temp, child);
  });
}


3. 生成bundle文件


这一步我们需要将刚才编写的执行函数和依赖图合成起来输出最后的打包文件。


function bundle(file) {
  const depsGraph = JSON.stringify(parseModules(file));
  return `(function (graph) {
        function require(file) {
            function absRequire(relPath) {
                return require(graph[file].deps[relPath])
            }
            var exports = {};
            (function (require,exports,code) {
                eval(code)
            })(absRequire,exports,graph[file].code)
            return exports
        }
        require('${file}')
    })(${depsGraph})`;
}
!fs.existsSync("./dist") && fs.mkdirSync("./dist");
fs.writeFileSync("./dist/bundle.js", content);


最后可以编写一个简单的测试程序测试一下结果。


<script src="./dist/bundle.js"></script>


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


ok 学费了。


后面有兴趣的话大家可以在考虑一下如何加载css文件或者图片base64 Vue SFC .vue。


相关文章
|
存储 移动开发 安全
开心档之C++ Web 编程 2
开心档之C++ Web 编程
38 0
|
3月前
|
API 数据库 开发者
解锁Web2py新境界!揭秘如何利用神秘插件系统,让你的Web项目瞬间起飞?
【8月更文挑战第31天】Web2py是一款轻量级且功能全面的Python Web框架,其插件系统允许开发者在不修改核心代码的情况下扩展框架功能,提升项目灵活性和可扩展性。本文介绍如何利用Web2py插件系统增强Web项目,包括插件的优势、选择与安装方法,并通过集成身份认证插件的具体示例展示其应用过程。通过合理利用插件,可以显著提高开发效率和用户体验。
52 1
|
6月前
|
JSON JavaScript 前端开发
用打王者荣耀的方式学习 webpack 没有不会的
用打王者荣耀的方式学习 webpack 没有不会的
|
6月前
《吉师作业》(1)之我是web手为啥让我学C
《吉师作业》(1)之我是web手为啥让我学C
63 0
|
移动开发 Shell C++
开心档之C++ Web 编程 1
开心档之C++ Web 编程
34 0
|
存储 移动开发 Shell
开心档之C++ Web 编程
开心档之C++ Web 编程
|
缓存 JavaScript
webpack4 之 cacheGroups 分包【究极奥义】
近来遇项目打包之事,撰文记之。以期分享,皆有所获。
|
JSON 前端开发 JavaScript
webpack深入浅出(三)| 小册免费学
之前我们了解了webpack中的配置方式,下面我们来说一下实际生产中经常使用的loader以及plugin 以下说到的所有依赖都将省略安装过程,除非特别指明,否则都是直接npm安装依赖名
69 0
|
JSON 前端开发 JavaScript
webpack深入浅出(二)| 小册免费学
这一节内容主要介绍详细的webpack配置信息,毕竟“webpack高级配置工程师”是每个前端er的目标
94 0
|
JSON JavaScript 前端开发
看了涡流大佬的面试文章的总结(Webpack)
看了涡流大佬的面试文章的总结(Webpack)