龙空技术网

细聊图解webpack 指南手册

Echa攻城狮 391

前言:

现时大家对“ajax的反推”大约比较关怀,朋友们都需要分析一些“ajax的反推”的相关文章。那么小编同时在网络上搜集了一些关于“ajax的反推””的相关文章,希望朋友们能喜欢,小伙伴们快快来学习一下吧!

作者:蓝色的秋风

转发链接:

前言

提到 webpack,可以说是与我们的开发工程非常密切的工具,不管是日常开发、进行面试还是对于自我的提高,都离不开它,因为它给我们的开发带了极大的便利以及学习的价值。但是由于webpack是一个非常庞大的工程体系,使得我们望之却步。本文想以这种图解的形式能够将它慢慢地剥开一层一层复杂的面纱,最终露出它的真面目。以下是我列出的关于 webpack 相关的体系。

本文讲的是 打包 - CommonJS 模块,主要分为两个部分

webpack 的作用webpack 的模块化机制与实现webpack 的作用

在我们前端多样化的今天,很多工具为了满足我们日益增长的开发需求,都变得非常的庞大,例如 webpack 。在我们的印象中,它似乎集成了所有关于开发的功能,模块打包,代码降级,文件优化,代码校验等等。正是因为面对如此庞大的一个工具,所以才让我们望而却步,当然了还有一点就是,webpack 的频繁升级,周边的生态插件配套版本混乱,也加剧我们对它的恐惧。

那么我们是不是应该思考一下,webpack 的出现究竟给我们带来了什么?我们为啥需要用它?而上面所有的一些代码降级(babel转化)、编译SCSS 、代码规范检测都是得益于它的插件系统和loader机制,并不是完完全全属于它。

所以在我看来,它的功能核心是「打包」,而打包则是能够让模块化的规范得以在浏览器直接执行。因此我们来看看打包后所带来的功能:

模块隔离模块依赖加载模块隔离

如果我们不用打包的方式,我们所有的模块都是直接暴露在全局,也就是挂载在 window/global 这个对象。也许代码量少的时候还可以接受,不会有那么多的问题。特别是在代码增多,多人协作的情况下,给全局空间带来的影响是不可预估的,如果你的每一次开发都得去一遍一遍查找是否有他们使用当前的变量名。

举个例子(仅仅为例子说明,实际工程会比以下复杂许多),一开始我们的 user1 写了一下几个模块,跑起来非常的顺畅。

image-20200626231748187

├── bar.js    function bar(){}├── baz.js    function baz(){}└── foo.js function foo(){}

但是呢,随着业务迭代,工程的复杂性增加,来了一个 user2,这个时候 user2,需要开发一个 foo 业务,里面也有一个 baz 模块,代码也很快写好了,变成了下面这个样子。

├── bar.js    function bar(){}├── baz.js    function baz(){}├── foo│   └── baz.js function baz(){}└── foo.js function foo(){}

但是呢这个时候,老板来找 user2 了,为什么增加了新业务后,原来的业务出错了呢?这个时候发现原来是 user2 新的新模块覆盖了 user1 的模块,从而导致了这场事故。

image-20200626220806881

因此,当我们开发的时候将所有的模块都暴露在全局的时候,想要避免错误,一切都得非常的小心翼翼,我们很容易在不知情的偷偷覆盖我们以前定义的函数,从而酿成错误。

因此 webpack 带来的第一个核心作用就是隔离,将每个模块通过闭包的形式包裹成一个个新的模块,将其放于局部作用域,所有的函数声明都不会直接暴露在全局。

image-20200626220851909

原来我们调用的 是 foo 函数,但是 webpack 会帮我们生成独一无二的模块ID,完全不需要担心模块的冲突,现在可以愉快地书写代码啦。

baz.jsmodule.exports = function baz (){}foo/baz.jsmodule.exports = function baz (){}main.jsvar baz = require('./baz.js');var fooBaz = require('./foo/baz.js');baz();fooBaz();

可能你说会之前的方式也可以通过改变函数命名的方式,但是原来的作用范围是整个工程,你得保证,当前命名在整个工程中不冲突,现在,你只需要保证的是单个文件中命名不冲突。(对于顶层依赖也是非常容易发生冲突)

image-20200627140818771

模块依赖加载

还有一种重要的功能就是模块依赖加载。这种方式带来的好处是什么?我们同样先来看例子,看原来的方式会产生什么问题?

User1 现在写了3个模块,其中 baz 是依赖于 bar 的。

image-20200627000240836

写完后 user1 进行了上线,利用了顺序来指出了依赖关系。

<script src="./bar.js"></script><script src="./baz.js"></script><script src="./foo.js"></script>

可是过了不久 user2 又接手了这个业务。user 2 发现,他开发的 abc 模块,通过依赖 bar 模块,可以进行快速地开发。可是 粗心的 user2 不太明白依赖关系。竟然将 abc 的位置随意写了一下,这就导致 运行 abc 的时候,无法找到 bar 模块。

image-20200627000713100

<script src="./abc.js"></script><script src="./bar.js"></script><script src="./baz.js"></script><script src="./foo.js"></script>

因此这里 webpack 利用 CommonJS/ ES Modules 规范进行了处理。使得各个模块之间相互引用无需考虑最终实际呈现的顺序。最终会被打包为一个 bunlde 模块,无需按照顺序手动引入。

baz.jsconst bar = require('./bar.js');module.exports = function baz (){ ... bar(); ...}abc.jsconst bar = require('./bar.js');module.exports = function baz (){ ... bar(); ...}
<script src="./bundle.js"></script>

image-20200627003815071

webpack 的模块化机制与实现

基于以上两项特性,模块的隔离以及模块的依赖聚合。我们现在可以非常清晰的知道了webpack所起的核心作用。

为了尽可能降低编写的难度和理解成本,我没有使用 AST 的解析,(当然 AST 也不是什么很难的东西,以后的文章中我会讲解 AST是什么以及 AST 解析器的实现过程。仅实现了 CommonJS 的支持bundle工作原理

为了能够实现 webpack, 我们可以通过反推的方法,先看webpack 打包后 bundle 是如何工作的。

「源文件」

// index.jsconst b = require('./b');b();// b.jsmodule.exports = function () {    console.log(11);}

「build 后」(去除了一些干扰代码)

(function(modules) {  var installedModules = {};  function __webpack_require__(moduleId) {    if (installedModules[moduleId]) {      return installedModules[moduleId].exports;    }    var module = (installedModules[moduleId] = {      i: moduleId,      l: false,      exports: {},    });    modules[moduleId].call(      module.exports,      module,      module.exports,      __webpack_require__    );    module.l = true;    return module.exports;  }  return __webpack_require__((__webpack_require__.s = 0));})([  /* 0 */  function(module, exports, __webpack_require__) {    var b = __webpack_require__(1);    b();  },  /* 1 */  function(module, exports) {    module.exports = function() {      console.log(11);    };  },]);

image-20200627135324956

以上就是 bundle 的运作原理。通过上述的流程图我们可以看到,有四个关键点

已注册模块(存放已经注册的模块)模块列表(用来存放所有的包装模块)模块查找(从原来的树形的模块依赖,变成了扁平查找)模块的包装(原有的模块都进行了一次包装)webpack实现

通过 bundle 的分析,我们只需要做的就是 4 件事

遍历出所有的模块模块包装提供注册模块、模块列表变量和导入函数持久化导出模块的遍历

首先来介绍一下模块的结构,能使我们快速有所了解, 结构比较简单,由内容和模块id组成。

interface GraphStruct {    context: string;    moduleId: string;}
{ "context": `function(module, exports, require) {    const bar = require('./bar.js');  const foo = require('./foo.js');  console.log(bar());  foo();  }`,  "moduleId": "./example/index.js"}

接下来我们以拿到一个入口文件来进行讲解,当拿到一个入口文件时,我们需要对其依赖进行分析。说简单点就是拿到 require 中的值,以便我们去寻找下一个模块。由于在这一部分不想引入额外的知识,开头也说了,一般采用的是 AST 解析的方式,来获取 require 的模块,在这里我们使用正则。

用来匹配全局的 require const REQUIRE_REG_GLOBAL = /require\(("|')(.+)("|')\)/g;用来匹配 require 中的内容const REQUIRE_REG_SINGLE = /require\(("|')(.+)("|')\)/;
const context = `const bar = require('./bar.js');const foo = require('./foo.js');console.log(bar());foo();`;console.log(context.match(REQUIRE_REG_GLOBAL));// ["require('./bar.js')", "require('./foo.js')"]

image-20200627202427794

由于模块的遍历并不是只有单纯的一层结构,一般为树形结构,因此在这里我采用了深度遍历。主要通过正则去匹配出require 中的依赖项,然后不断递归去获取模块,最后将通过深度遍历到的模块以数组形式存储。(不理解深度遍历,可以理解为递归获取模块)

image-20200627142130902

以下是代码实现

...private entryPath: stringprivate graph: GraphStruct[]...createGraph(rootPath: string, relativePath: string) {    // 通过获取文件内容    const context = fs.readFileSync(rootPath, 'utf-8');    // 匹配出依赖关系    const childrens = context.match(REQUIRE_REG_GLOBAL);   // 将当前的模块存储下来    this.graph.push({        context,        moduleId: relativePath,    })    const dirname = path.dirname(rootPath);    if (childrens) {       // 如有有依赖,就进行递归        childrens.forEach(child => {            const childPath = child.match(REQUIRE_REG_SINGLE)[2];            this.createGraph(path.join(dirname, childPath), childPath);        });    }}
模块包装

为了能够使得模块隔离,我们在外部封装一层函数, 然后传入对应的模拟 require 和 module使得模块能进行正常的注册以及导入 。

function (module, exports, require){    ...},
提供注册模块、模块列表变量和导入函数

这一步比较简单,只要按照我们分析的流程图提供已注册模块变量、模块列表变量、导入函数。

/* modules = {  "./example/index.js": function (module, exports, require) {    const a = require("./a.js");    const b = require("./b.js");    console.log(a());    b();  },  ...};*/bundle(graph: GraphStruct[]) {    let modules = '';    graph.forEach(module => {        modules += `"${module.moduleId}":function (module, exports, require){        ${module.context}        },`;    });    const bundleOutput = `    (function(modules) {        var installedModules = {};        // 导入函数        function require(moduleId) {            // 检查是否已经注册该模块            if (installedModules[moduleId]) {                return installedModules[moduleId].exports;            }           // 没有注册则从模块列表获取模块进行注册            var module = (installedModules[moduleId] = {                i: moduleId,                l: false,                exports: {},            });           // 执行包装函数,执行后更新模块的内容            modules[moduleId].call(                module.exports,                module,                module.exports,                require            );            // 设置标记已经注册            module.l = true;            // 返回实际模块            return module.exports;        }        require("${graph[0].moduleId}");    })({${modules}})    `;    return bundleOutput;}
持久化导出

最后将生成的 bundle 持久写入到磁盘就大功告成。

fs.writeFileSync('bundle.js', this.bundle(this.graph))

完整代码100行 代码不到,详情可以查看以下完整示例。

github地址:

结尾

以上仅代表个人的理解,希望让你对webpack的理解有所帮助, 如有讲的不好的请多指出。

推荐JavaScript学习相关文章

《一文带你彻底搞懂 NPM 知识点「进阶篇」》

《细聊webpack性能优化面面观》

《JS实现各种日期操作方法汇总》

《「实践」细聊前端性能优化总结》

《「实践」浏览器中的画中画(Picture-in-Picture)模式及其 API》

《「多图」一文带你彻底搞懂 Web Workers (上)》

《「多图」一文带你彻底搞懂 Web Workers (中)》

《深入细聊前端下载总结「干货」》

《细品西瓜播放器功能分析(上)「实践」》

《细品西瓜播放器功能分析(下)「实践」》

《细聊50道JavaScript基础面试题「附答案」》

《webpack4主流程源码解说以及动手实现一个简单的webpack(上)》

《webpack4主流程源码解说以及动手实现一个简单的webpack(下)》

《细聊前端架构师的视野》

《细聊应用场景再谈防抖和节流「进阶篇」》

《前端埋点统一接入方案实践》

《细聊微内核架构在前端的应用「干货」》

《一种高性能的Tree组件实现方案「干货」》

《进击的JAMStack》

《前后端全部用 JS 开发是什么体验(Hybrid + Egg.js经验分享)上》

《前后端全部用 JS 开发是什么体验(Hybrid + Egg.js经验分享)中》

《前后端全部用 JS 开发是什么体验(Hybrid + Egg.js经验分享)下》

《一文带你搞懂 babel-plugin-import 插件(上)「源码解析」》

《一文带你搞懂 babel-plugin-import 插件(下)「源码解析」》

《JavaScript常用API合集汇总「值得收藏」》

《推荐10个常用的图片处理小帮手(上)「值得收藏」》

《推荐10个常用的图片处理小帮手(下)「值得收藏」》

《JavaScript 中ES6代理的实际用例》

《12 个实用的前端开发技巧总结》

《一文带你搞懂搭建企业级的 npm 私有仓库》

《教你如何使用内联框架元素 IFrames 的沙箱属性提高安全性?》

《细说前端开发UI公共组件的新认识「实践」》

《细说DOM API中append和appendChild的三个不同点》

《细品淘系大佬讲前端新人如何上王者「干货」》

《一文带你彻底解决背景跟随弹窗滚动问题「干货」》

《推荐常用的5款代码比较工具「值得收藏」》

《Node.js实现将文字与图片合成技巧》

《爱奇艺云剪辑Web端的技术实现》

《我再也不敢说我会写前端 Button组件「实践」》

《NodeX Component - 滴滴集团 Node.js 生态组件体系「实践」》

《Node Buffers 完整指南》

《推荐18个webpack精美插件「干货」》

《前端开发需要了解常用7种JavaScript设计模式》

《浅谈浏览器架构、单线程js、事件循环、消息队列、宏任务和微任务》

《了不起的 Webpack HMR 学习指南(上)「含源码讲解」》

《了不起的 Webpack HMR 学习指南(下)「含源码讲解」》

《10个打开了我新世界大门的 WebAPI(上)「实践」》

《10个打开了我新世界大门的 WebAPI(中)「实践」》

《10个打开了我新世界大门的 WebAPI(下)「实践」》

《「图文」ESLint 在中大型团队的应用实践》

《Deno是代码的浏览器,你认同吗?》

《前端存储除了 localStorage 还有啥?》

《Javascript 多线程编程​的前世今生》

《微前端方案 qiankun(实践及总结)》

《「图文」V8 垃圾回收原来这么简单?》

《Webpack 5模块联邦引发微前端的革命?》

《基于 Web 端的人脸识别身份验证「实践」》

《「前端进阶」高性能渲染十万条数据(时间分片)》

《「前端进阶」高性能渲染十万条数据(虚拟列表)》

《图解 Promise 实现原理(一):基础实现》

《图解 Promise 实现原理(二):Promise 链式调用》

《图解 Promise 实现原理(三):Promise 原型方法实现》

《图解 Promise 实现原理(四):Promise 静态方法实现》

《实践教你从零构建前端 Lint 工作流「干货」》

《高性能多级多选级联组件开发「JS篇」》

《深入浅出讲解Node.js CLI 工具最佳实战》

《延迟加载图像以提高Web网站性能的五种方法「实践」》

《比较 JavaScript 对象的四种方式「实践」》

《使用Service Worker让你的 Web 应用如虎添翼(上)「干货」》

《使用Service Worker让你的 Web 应用如虎添翼(中)「干货」》

《使用Service Worker让你的 Web 应用如虎添翼(下)「干货」》

《前端如何一次性处理10万条数据「进阶篇」》

《推荐三款正则可视化工具「JS篇」》

《如何让用户选择是否离开当前页面?「JS篇」》

《JavaScript开发人员更喜欢Deno的五大原因》

《仅用18行JavaScript实现一个倒数计时器》

《图文细说JavaScript 的运行机制》

《一个轻量级 JavaScript 全文搜索库,轻松实现站内离线搜索》

《推荐Web程序员常用的15个源代码编辑器》

《10个实用的JS技巧「值得收藏」》

《细品269个JavaScript小函数,让你少加班熬夜(一)「值得收藏」》

《细品269个JavaScript小函数,让你少加班熬夜(二)「值得收藏」》

《细品269个JavaScript小函数,让你少加班熬夜(三)「值得收藏」》

《细品269个JavaScript小函数,让你少加班熬夜(四)「值得收藏」》

《细品269个JavaScript小函数,让你少加班熬夜(五)「值得收藏」》

《细品269个JavaScript小函数,让你少加班熬夜(六)「值得收藏」》

《深入JavaScript教你内存泄漏如何防范》

《手把手教你7个有趣的JavaScript 项目-上「附源码」》

《手把手教你7个有趣的JavaScript 项目-下「附源码」》

《JavaScript 使用 mediaDevices API 访问摄像头自拍》

《手把手教你前端代码如何做错误上报「JS篇」》

《一文让你彻底搞懂移动前端和Web 前端区别在哪里》

《63个JavaScript 正则大礼包「值得收藏」》

《提高你的 JavaScript 技能10 个问答题》

《JavaScript图表库的5个首选》

《一文彻底搞懂JavaScript 中Object.freeze与Object.seal的用法》

《可视化的 JS:动态图演示 - 事件循环 Event Loop的过程》

《教你如何用动态规划和贪心算法实现前端瀑布流布局「实践」》

《可视化的 js:动态图演示 Promises & Async/Await 的过程》

《原生JS封装拖动验证滑块你会吗?「实践」》

《如何实现高性能的在线 PDF 预览》

《细说使用字体库加密数据-仿58同城》

《Node.js要完了吗?》

《Pug 3.0.0正式发布,不再支持 Node.js 6/8》

《纯JS手写轮播图(代码逻辑清晰,通俗易懂)》

《JavaScript 20 年 中文版之创立标准》

《值得收藏的前端常用60余种工具方法「JS篇」》

《箭头函数和常规函数之间的 5 个区别》

《通过发布/订阅的设计模式搞懂 Node.js 核心模块 Events》

《「前端篇」不再为正则烦恼》

《「速围」Node.js V14.3.0 发布支持顶级 Await 和 REPL 增强功能》

《深入细品浏览器原理「流程图」》

《JavaScript 已进入第三个时代,未来将何去何从?》

《前端上传前预览文件 image、text、json、video、audio「实践」》

《深入细品 EventLoop 和浏览器渲染、帧动画、空闲回调的关系》

《推荐13个有用的JavaScript数组技巧「值得收藏」》

《前端必备基础知识:window.location 详解》

《不要再依赖CommonJS了》

《犀牛书作者:最该忘记的JavaScript特性》

《36个工作中常用的JavaScript函数片段「值得收藏」》

《Node + H5 实现大文件分片上传、断点续传》

《一文了解文件上传全过程(1.8w字深度解析)「前端进阶必备」》

《【实践总结】关于小程序挣脱枷锁实现批量上传》

《手把手教你前端的各种文件上传攻略和大文件断点续传》

《字节跳动面试官:请你实现一个大文件上传和断点续传》

《谈谈前端关于文件上传下载那些事【实践】》

《手把手教你如何编写一个前端图片压缩、方向纠正、预览、上传插件》

《最全的 JavaScript 模块化方案和工具》

《「前端进阶」JS中的内存管理》

《JavaScript正则深入以及10个非常有意思的正则实战》

《前端面试者经常忽视的一道JavaScript 面试题》

《一行JS代码实现一个简单的模板字符串替换「实践」》

《JS代码是如何被压缩的「前端高级进阶」》

《前端开发规范:命名规范、html规范、css规范、js规范》

《【规范篇】前端团队代码规范最佳实践》

《100个原生JavaScript代码片段知识点详细汇总【实践】》

《关于前端174道 JavaScript知识点汇总(一)》

《关于前端174道 JavaScript知识点汇总(二)》

《关于前端174道 JavaScript知识点汇总(三)》

《几个非常有意思的javascript知识点总结【实践】》

《都2020年了,你还不会JavaScript 装饰器?》

《JavaScript实现图片合成下载》

《70个JavaScript知识点详细总结(上)【实践】》

《70个JavaScript知识点详细总结(下)【实践】》

《开源了一个 JavaScript 版敏感词过滤库》

《送你 43 道 JavaScript 面试题》

《3个很棒的小众JavaScript库,你值得拥有》

《手把手教你深入巩固JavaScript知识体系【思维导图】》

《推荐7个很棒的JavaScript产品步骤引导库》

《Echa哥教你彻底弄懂 JavaScript 执行机制》

《一个合格的中级前端工程师需要掌握的 28 个 JavaScript 技巧》

《深入解析高频项目中运用到的知识点汇总【JS篇】》

《JavaScript 工具函数大全【新】》

《从JavaScript中看设计模式(总结)》

《身份证号码的正则表达式及验证详解(JavaScript,Regex)》

《浏览器中实现JavaScript计时器的4种创新方式》

《Three.js 动效方案》

《手把手教你常用的59个JS类方法》

《127个常用的JS代码片段,每段代码花30秒就能看懂-【上】》

《深入浅出讲解 js 深拷贝 vs 浅拷贝》

《手把手教你JS开发H5游戏【消灭星星】》

《深入浅出讲解JS中this/apply/call/bind巧妙用法【实践】》

《手把手教你全方位解读JS中this真正含义【实践】》

《书到用时方恨少,一大波JS开发工具函数来了》

《干货满满!如何优雅简洁地实现时钟翻牌器(支持JS/Vue/React)》

《手把手教你JS 异步编程六种方案【实践】》

《让你减少加班的15条高效JS技巧知识点汇总【实践】》

《手把手教你JS开发H5游戏【黄金矿工】》

《手把手教你JS实现监控浏览器上下左右滚动》

《JS 经典实例知识点整理汇总【实践】》

《2.6万字JS干货分享,带你领略前端魅力【基础篇】》

《2.6万字JS干货分享,带你领略前端魅力【实践篇】》

《简单几步让你的 JS 写得更漂亮》

《恭喜你获得治疗JS this的详细药方》

《谈谈前端关于文件上传下载那些事【实践】》

《面试中教你绕过关于 JavaScript 作用域的 5 个坑》

《Jquery插件(常用的插件库)》

《【JS】如何防止重复发送ajax请求》

《JavaScript+Canvas实现自定义画板》

《Continuation 在 JS 中的应用「前端篇」》

作者:蓝色的秋风

转发链接:

标签: #ajax的反推