webpack 复用-彻底理解Webpack打包! 从脚本男孩到工程师!

2023-08-23 0 1,434 百度已收录

从脚本男孩到工程师!

了解Webpack5是如何实现模块打包的?

从脚本编写者到工程师

1995年webpack 复用,第一个JavaScript诞生了,用来在客户端做一些DOM的改变,减少用户的一些流量请求。 当时,HTML页面上的任何文本更改都需要从后台获取所有文件。 当然,最重要的是,在当时的带宽条件下,每一丝流量都极其罕见。

作为一种新生的脚本语言,它的地位较低,甚至连它的名字都是为了赶上Java的普及。 直到浏览器流行起来,成为你在线滑水的首选,JavaScript的地位也陡然上升。 随着网站量的不断增大,一个简单的JS文件可能无法满足大量复杂的需求。

无数开发者奋起,提出自己的新想法,开源自己的封装库,从原生JS到脚本库到模板句型再到框架,前端也从刀耕火种的耕种到了脚本语言到当今的组件化模块化工程。

一个前端开发人员从脚本师到工程师花了20年的时间。 在后端工程中,Rollup、Gulp、Webpack等打包工具是不可或缺的。 本系列将探讨最流行的后端打包解决方案 Webpack5。

Webpack5的模块化打包

模块化是前端工程最基本的部分。 源代码的划分和分层、组件的复用、项目模块的延迟加载都依赖于模块化的存在。

本文精心挑选Webpack最简单的模块化打包原理,带你第一步了解Webpack,帮助你从整个项目的角度理解后端工程!

PS:本文使用的Webpack配置如下:

const path = require('path')const HtmlWebpackPlugin = require('html-webpack-plugin')module.exports = {    mode: 'development',    devtool: false, // 不要sourcemap    entry: './src/index.js',    output: {        path: path.resolve(__dirname, 'dist'),        filename:'main.js',    },    plugins: [        new HtmlWebpackPlugin({            template:'./src/index.html'        })    ]}

CommonJS 规范

哪里有模块化,哪里就有模块化规范。 所谓规范就是大家约定好的代码句型,Webpack也会按照既定的句型来打包整个项目。 本质上就是对源代码进行正则匹配,然后替换,前端模块化规范有很多,这里只介绍最重要的两个:CommonJS规范和ESModule规范,其代表用户是NodeJS和ES6语法分别。

Webpack 对 CommonJS 规范的封装代码比较简单,其句型如该模拟文件所示。 模拟的文件结构如下:

// index.js 引用index2.jslet index2 = require('./index2.js')console.log(index2)// index2.js 导出一个字段module.exports = 'index2'

那么Webpack是如何实现这样一个由两个模块组成的简单项目的打包呢? 说多了也没用,都在源码里了! 只有35行,可以自己做,打包后的源码全部注释:

// 打包结束后的main.js,自执行函数,script标记加载完后立即执行(() => {     // key为路径,value为模块内容包裹成的函数    var __webpack_modules__ = ({        "./src/index2.js": ((module) => {            module.exports = 'index2'        })    });
    // 如果缓存里有该模块,就使用缓存中的模块    var __webpack_module_cache__ = {};    function __webpack_require__(moduleId) {        var cachedModule = __webpack_module_cache__[moduleId];        if (cachedModule !== undefined) {            return cachedModule.exports;        }        // 创建一个新模块并将其放入缓存        var module = __webpack_module_cache__[moduleId] = {            exports: {}        };        // 执行模块时 传入三个参数, module module.exports 以及 require,        // 以供嵌套递归引用模块        __webpack_modules__[moduleId](module, module.exports,                                      __webpack_require__);        // 最终返回该模块的exports        return module.exports;    }
   var __webpack_exports__ = {};    // 自执行函数,分割作用域,防止变量污染    (() => {        // 最终,入口文件的代码开始执行        let index2 = __webpack_require__("./src/index2.js")        console.log(index2)    })();})()

解析:

当文件中存在 require() module.exports 等关键字时,Webpack 会将其识别为 CommonJS 模块规范。

打包后的main.js会在Webpack创建的index.html文件中添加标签,引用(() => {})()自执行函数,立即执行,起到划分模块作用域的作用,Webpack模块object 存放所有加载的模块,供模块之间互相require(),键是src的相对路径,值是模块内容转换成的函数。

Webpack require 替换文件中的 require(),接收密钥,从 Webpack 模块中读取运行的模块,并返回其导出。 上述规划完成后,执行入口文件代码,启动项目。 浏览器本身不支持模块化,通过函数来​​模拟模块化的功效。 对于node来说也是如此,尽管像requireexports这样的全局变量是函数的参数。

注意为什么(模块函数)有最里面的括号,函数后面直接取出来执行,而箭头函数需要这样写(()=>{})()才能自执行。

热身完毕,我们来看看最重要、最常用的ESModule——Webpack是如何打包的。

ES模块规格

ESModule规范的语法如下,也是这个仿真包的工程文件。

模拟文件结构:

// 入口文件indeximport index2 from './index2'console.log(index2)// 被引用文件index2const index2 = 'index2'export default index2话不多说,直接上源码,心急的同学可以先看后面的分析,再看源码,方便理解。(为了Word排版,做了一定的折叠。)打包后的全部源码带注释(() => { // webpackBootstrap    "use strict";    var __webpack_modules__ = ({        "./src/index2.js":            ((__unused_webpack_module, __webpack_exports__,              __webpack_require__) => {                // 标记该模块为ESModule                __webpack_require__.r(__webpack_exports__);                // 通过d绑定要导出的数据到__webpack_exports__上                __webpack_require__.d(__webpack_exports__, {                    "default": () => (__WEBPACK_DEFAULT_EXPORT__),                    "test": () => (test)                });                // 模块内的代码执行                const __WEBPACK_DEFAULT_EXPORT__ = ('index2');                const test = 'test'            })    });
   var __webpack_module_cache__ = {};    function __webpack_require__(moduleId) {        var cachedModule = __webpack_module_cache__[moduleId];        if (cachedModule !== undefined) {            return cachedModule.exports;        }        // 创建一个新模块并将其放入缓存        var module = __webpack_module_cache__[moduleId] = {            exports: {}        };        // 根据moduleId拿到模块,传入exports供挂载导出数据,执行该module        __webpack_modules__[moduleId](module, module.exports,                                      __webpack_require__);        return module.exports;    }
   (() => {        // 绑定definition对象内的属性 到 exports上,即要导出的而数据        __webpack_require__.d = (exports, definition) => {            for (var key in definition) {                // definition: { "default": () => (...), "test": () => (test) }                // definition上有的属性,exports上没有的属性,就绑定上去                // 之后,外部模块使用如test属性时,实际上是调用`() => (test)`,                // 由于闭包原则,此时会拿到内部模块此test变量的最新值                // 这就是harmony exports                if (__webpack_require__.o(definition, key) &&                    !__webpack_require__.o(exports, key)) {                    Object.defineProperty(exports, key, {                        enumerable: true, get: definition[key]});                }            }        };    })();
   (() => {        // 判断是否有某属性        __webpack_require__.o = (obj, prop) => (            Object.prototype.hasOwnProperty.call(obj, prop)        )    })();
   (() => {        // 为该模块的export新增__esModule属性,        // 以供处理混合使用 ESModule 和 CommonJS 的情况        __webpack_require__.r = (exports) => {            // 如果浏览器支持 Symbol属性,就使用Symbol进行属性定义            // 注:Symbol可以在几乎所有框架内看到,用于作为独一无二的属性Key值            if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {                Object.defineProperty(exports, Symbol.toStringTag, {                    value: 'Module'                });            }            Object.defineProperty(exports, '__esModule', { value: true });        };    })();
   // 开始处理index模块,入口模块,整个程序开始运行    var __webpack_exports__ = {};    (() => {        // 标记其导出为ESModule        __webpack_require__.r(__webpack_exports__);        // 执行index2模块,拿到其exports        var _index2__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(            "./src/index2.js"        );        // 执行index主要代码        console.log(_index2__WEBPACK_IMPORTED_MODULE_0__["default"])        console.log(_index2__WEBPACK_IMPORTED_MODULE_0__.test)    })();})()可以看到与CommonJS规范 几乎相同 !!!实现了 三个重要部分:__webpack_modules__ 文件路径为key,包裹为函数的模块为value 的Map结构__webpack_require__ 函数类型,实现导入功能(import)__webpack_exports__ 对象类型,实现导出功能(export)__webpack_require__,通过 key(文件路径) 从 Map__webpack_modules__ 获取到 引用的 模块函数 并执行,执行时传入__webpack_exports__对象 供 被引用的模块函数 导出数据传入__webpack_require__函数 供 被引用的模块函数 递归调用 引用模块三个工具函数:__webpack_require__.o 判断某对象是否有某属性__webpack_require__.r 将exports对象标记为ESModule__webpack_require__.d 通过给exports对象设置getter属性,绑定要导出的数据

总结

不同的设置和不同的模块化规范会影响Webpack打包的代码,但这些都不会影响其模块化打包的核心逻辑。

实现导入:将Webpack的exports对象传递到模块函数中进行挂载,

实现导出:使用一个全局对象作为Map结构来存储所有模块,

Webpack的导入函数require手动检索Map下的模块并执行,

使用函数来模拟模块:只有当模块被引用时才执行模块代码,并隔离各个模块的作用域。

从入口模块开始执行,递归调用运行模块,启动整个项目。

最后,Webpack 将打包好的 main.js 打包为 Script 标签,并插入准备好的模板 HTML 文件。 试想webpack 复用,当用户访问网页时,nginx返回index.html,浏览器执行script标签时,向服务器请求main.js资源。 加载完成后,main.js开始执行,整个框架开始运行。 根据代码,在div中插入各种DOM结构,整个Web应用程序就这样运行了。

后记

Webpack为我们抹平了各种规范和书写风格的差异,让我们的编码更加简单,源码更加高贵。 本文讲解了比较简单、最基本、最可疑的模块化原理,而这只是 Webpack 学习帷幕的第一步。 很多源码都是这样的。 当你不明白的时候,你就会觉得深不可测。 经过搜索信息和研究,你会感受到通往简单的大道。

如果你特别喜欢我的Webpack系列,请点赞并支持。 未来可能会发布Webpack按需加载、热更新、AST、Tree-Shaking等原理的解释。

如果你更关心框架技术,欢迎在评论区留言,Vue和React的源码也可以拉下来和你聊聊。

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

悟空资源网 webpack webpack 复用-彻底理解Webpack打包! 从脚本男孩到工程师! https://www.wkzy.net/game/147591.html

常见问题

相关文章

官方客服团队

为您解决烦忧 - 24小时在线 专业服务