webpack原理解析(一)实现一个简单的webpack

简介: Webpack 是当下最热门的前端资源模块化管理和打包工具。它可以将许多松散的模块按照依赖和规则打包成符合生产环境部署的前端资源。还可以将按需加载的模块进行代码分隔,等到实际需要的时候再异步加载。webpack如此强大,其内部机制到底是怎样的,今天我们来一探究竟。

准备工作


新建一个目录,执行npm init -y 初始化一个package.json文件


新建src下index.js,作为入口文件,export.js作为依赖的模块


新建webpack.config.js,这里就按照webpack4的基础配置写一下


新建bundle.js作为入口进行webpack构建 创建lib下webpack.js


根据配置读取入口文件


bundle.js:


//读取到webpackd 的配置文件
const options = require("./webpack.config.js")
const Webpack = require("./lib/webpack.js")
new Webpack(options).run()


webpack.js 这里写了一个Webpack类,bundle.js实例化这个类,将参数(引入的基础配置的内容)传进来并执行run方法


module.exports = class Webpack {
    constructor(options){
        console.log(options)
    }
    run(){
        console.log("hello")
    }
}


此时执行node bundle.js :



已经可以根据基础配置读取到入口模块了


分析出依赖模块


我这里写的index.js是这样:


import { sayHi } from './export.js'
sayHi("hfj")
console.log("hello webpack index")


此时需要借助node的核心模块-- filesystem来读到内容


const conts = fs.readFileSync(entryFile,'utf-8')


接下来借助@babel/parser(记得自己安装和引入。。)把内容抽象成语法树


const ast = parser.parse(conts, {
            sourceType: "module"
          });


打印下ast.program.body



看type,哪里是依赖模块,哪里是表达式,已经很明显了,接下来通过@babel/traverse来提取依赖模块:


const dependencies = {}
          traverse(ast,{
            ImportDeclaration({node}){
                const newPath = "./" + path.join(
                    path.dirname(entryFile),
                    node.source.value
                    )
                dependencies[node.source.value] = newPath
                console.log(dependencies)
            }
        })


注意下,这里做了一个处理,将依赖模块以对象的形式放到了dependencies里。


编译内容


接下来,通过@babel/core将ast处理成内容


const {code} = transformFromAst(ast,null,{
            presets: ["@babel/preset-env"]
        })
        console.log(code)


打印的code如下:



内容成功拿到了!


遍历依赖模块


以上是对入口文件进行的编译,接下来还要看入口模块的依赖模块有哪些,分别按照刚才的方法进行内容分析,最后做一个汇总(根据dependence来判断的是否还有依赖模块):


run(){
       const info = this.analysis(this.entry)
        this.modulesArr.push(info)
        for(let i=0;i<this.modulesArr.length;i++) {
            const item = this.modulesArr[i]
            const { dependencies } = item;
            if(dependencies) {
                for(let j in dependencies){
                    this.modulesArr.push(this.analysis(dependencies[j]))
                }
            }
        }
        // console.log(this.modules)
        //数组结构转换
        const obj = {}
        this.modulesArr.forEach((item) => {
            obj[item.entryFile] = {
                dependencies:item.dependencies,
                code:item.code
            }
        })
    }


最终是将modulesArr转化成了对象,方便后续处理


输出浏览器可执行的js代码


此处划重点! webpack最终是将模块化的js打包成一个chunk,打包后的js到dist(webpack基础配置里设置的output)


首先用了fs.writeFileSync将生成的bundle写入filePath(filePath之前有在this.output保存) 接下来就是处理obj(上一步骤中的汇总后的模块)了:


核心是用eval()来执行obj中的code,同时需要处理requrie和export require和export通过形参传入,之后执行自己写的reqire和export require处理了模块路径,exports是一个对象,看源码--


const bundle = `(function(graph){
            function require(moduleId){
                function localRequire(relativePath){
                   return require(graph[moduleId].dependencies[relativePath]) 
                }
                var exports = {};
                (function(require,exports,code){
                    eval(code)
                })(localRequire,exports,graph[moduleId].code)
                return exports;
            }
            require('${this.entry}')
        })(${newCode})`


执行node bundle.js,在dist文件夹下输出如下js:


(function(graph){
            function require(moduleId){
                function localRequire(relativePath){
                   return require(graph[moduleId].dependencies[relativePath]) 
                }
                var exports = {};
                (function(require,exports,code){
                    eval(code)
                })(localRequire,exports,graph[moduleId].code)
                return exports;
            }
            require('./src/index.js')
        })({"./src/index.js":{"dependencies":{"./export.js":"./src\\export.js"},"code":"\"use strict\";\n\nvar _export = require(\"./export.js\");\n\n(0, _export.sayHi)(\"hfj\");\nconsole.log(\"hello webpack index\");"},"./src\\export.js":{"dependencies":{},"code":"\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n  value: true\n});\nexports.sayHi = void 0;\n\nvar sayHi = function sayHi(params) {\n  console.log(\"hello ~\" + params);\n};\n\nexports.sayHi = sayHi;"}})


在浏览器执行:输出:



完成~


总结


webpack通过入口文件逐层遍历到模块依赖,进行代码分析、代码转换,最终生成可在浏览器运行的打包后的代码


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


以上实现了一个简易版本的webpack


下一篇:webpack原理解析(二)loader和plugin机制


如有兴趣,欢迎进一步交流探讨~


附github地址:github.com/manli-tongh…


参考:Webpack官网



相关文章
|
3月前
|
安全 算法 网络协议
解析:HTTPS通过SSL/TLS证书加密的原理与逻辑
HTTPS通过SSL/TLS证书加密,结合对称与非对称加密及数字证书验证实现安全通信。首先,服务器发送含公钥的数字证书,客户端验证其合法性后生成随机数并用公钥加密发送给服务器,双方据此生成相同的对称密钥。后续通信使用对称加密确保高效性和安全性。同时,数字证书验证服务器身份,防止中间人攻击;哈希算法和数字签名确保数据完整性,防止篡改。整个流程保障了身份认证、数据加密和完整性保护。
|
5月前
|
存储 缓存 算法
HashMap深度解析:从原理到实战
HashMap,作为Java集合框架中的一个核心组件,以其高效的键值对存储和检索机制,在软件开发中扮演着举足轻重的角色。作为一名资深的AI工程师,深入理解HashMap的原理、历史、业务场景以及实战应用,对于提升数据处理和算法实现的效率至关重要。本文将通过手绘结构图、流程图,结合Java代码示例,全方位解析HashMap,帮助读者从理论到实践全面掌握这一关键技术。
164 14
|
6月前
|
运维 持续交付 云计算
深入解析云计算中的微服务架构:原理、优势与实践
深入解析云计算中的微服务架构:原理、优势与实践
240 3
|
2月前
|
机器学习/深度学习 数据可视化 PyTorch
深入解析图神经网络注意力机制:数学原理与可视化实现
本文深入解析了图神经网络(GNNs)中自注意力机制的内部运作原理,通过可视化和数学推导揭示其工作机制。文章采用“位置-转移图”概念框架,并使用NumPy实现代码示例,逐步拆解自注意力层的计算过程。文中详细展示了从节点特征矩阵、邻接矩阵到生成注意力权重的具体步骤,并通过四个类(GAL1至GAL4)模拟了整个计算流程。最终,结合实际PyTorch Geometric库中的代码,对比分析了核心逻辑,为理解GNN自注意力机制提供了清晰的学习路径。
293 7
深入解析图神经网络注意力机制:数学原理与可视化实现
|
2月前
|
机器学习/深度学习 缓存 自然语言处理
深入解析Tiktokenizer:大语言模型中核心分词技术的原理与架构
Tiktokenizer 是一款现代分词工具,旨在高效、智能地将文本转换为机器可处理的离散单元(token)。它不仅超越了传统的空格分割和正则表达式匹配方法,还结合了上下文感知能力,适应复杂语言结构。Tiktokenizer 的核心特性包括自适应 token 分割、高效编码能力和出色的可扩展性,使其适用于从聊天机器人到大规模文本分析等多种应用场景。通过模块化设计,Tiktokenizer 确保了代码的可重用性和维护性,并在分词精度、处理效率和灵活性方面表现出色。此外,它支持多语言处理、表情符号识别和领域特定文本处理,能够应对各种复杂的文本输入需求。
282 6
深入解析Tiktokenizer:大语言模型中核心分词技术的原理与架构
|
2月前
|
传感器 人工智能 监控
反向寻车系统怎么做?基本原理与系统组成解析
本文通过反向寻车系统的核心组成部分与技术分析,阐述反向寻车系统的工作原理,适用于适用于商场停车场、医院停车场及火车站停车场等。如需获取智慧停车场反向寻车技术方案前往文章最下方获取,如有项目合作及技术交流欢迎私信作者。
154 2
|
3月前
|
机器学习/深度学习 算法 数据挖掘
解析静态代理IP改善游戏体验的原理
静态代理IP通过提高网络稳定性和降低延迟,优化游戏体验。具体表现在加快游戏网络速度、实时玩家数据分析、优化游戏设计、简化更新流程、维护网络稳定性、提高连接可靠性、支持地区特性及提升访问速度等方面,确保更流畅、高效的游戏体验。
118 22
解析静态代理IP改善游戏体验的原理
|
3月前
|
编解码 缓存 Prometheus
「ximagine」业余爱好者的非专业显示器测试流程规范,同时也是本账号输出内容的数据来源!如何测试显示器?荒岛整理总结出多种测试方法和注意事项,以及粗浅的原理解析!
本期内容为「ximagine」频道《显示器测试流程》的规范及标准,我们主要使用Calman、DisplayCAL、i1Profiler等软件及CA410、Spyder X、i1Pro 2等设备,是我们目前制作内容数据的重要来源,我们深知所做的仍是比较表面的活儿,和工程师、科研人员相比有着不小的差距,测试并不复杂,但是相当繁琐,收集整理测试无不花费大量时间精力,内容不完善或者有错误的地方,希望大佬指出我们好改进!
239 16
「ximagine」业余爱好者的非专业显示器测试流程规范,同时也是本账号输出内容的数据来源!如何测试显示器?荒岛整理总结出多种测试方法和注意事项,以及粗浅的原理解析!
|
3月前
|
Java 数据库 开发者
详细介绍SpringBoot启动流程及配置类解析原理
通过对 Spring Boot 启动流程及配置类解析原理的深入分析,我们可以看到 Spring Boot 在启动时的灵活性和可扩展性。理解这些机制不仅有助于开发者更好地使用 Spring Boot 进行应用开发,还能够在面对问题时,迅速定位和解决问题。希望本文能为您在 Spring Boot 开发过程中提供有效的指导和帮助。
151 12
|
3月前
|
开发框架 监控 JavaScript
解锁鸿蒙装饰器:应用、原理与优势全解析
ArkTS提供了多维度的状态管理机制。在UI开发框架中,与UI相关联的数据可以在组件内使用,也可以在不同组件层级间传递,比如父子组件之间、爷孙组件之间,还可以在应用全局范围内传递或跨设备传递。
96 2

推荐镜像

更多