rollup打包工具

news2024/9/21 4:40:47

rollup打包工具

在学习vite和vue3源码的时候,接触到了rollup,所以过来学习一下

什么是rollup

rollup是一个模块化的打包工具,会将javascript文件进行合并。比起webpack,webpack在打包的时候会进行代码注入(保障兼容性),如果对于一些项目,特别是类库,没有其他的静态资源文件,就可以使用rollup。rollup支持es6模块,支持tree-shaking,不支持code-splitting,模块热更新

  • tree shaking优化: tree shaking是一种优化技术,用于剔除未使用的代码,减少最终的文件大小
  • ES6模块支持: rollup专注es6模块的打包,有助于避免commonjs模块的一些问题,比如命名空间
  • 代码拆分与懒加载:Rollup 支持代码拆分和懒加载,允许将代码拆分成多个文件,只在需要时加载。这有助于减少初始加载时间,并提供更好的性能。
  • 可插拔的插件系统:允许使用现有插件和编写自定义插件
  • 输出格式多样性:支持多种输出格式,包含ES6模块,commonjs,umd等

rollup的工作流程

acorn: JavaScript的此法解析器,可以将JavaScript字符串解析成为语法抽象树AST。
提供一个入口文件,rollup通过acorn读取解析文件,返回一种ast的抽象语法树。一个文件就是一个模块,每个模块都会根据文件的代码生成一个ast抽象语法树
分析AST节点,就是看这个节点有没有调用函数的方法,有没有读到变量,有,就查看是否在当前的作用域,不在就往上找,直到找到模块顶层作用域为止,如果本模块没有找到,则说明依赖于别的模块,需要从其他模块中去导出。直到没有依赖的模块为止。

│  bundle.js // Bundle 打包器,在打包过程中会生成一个 bundle 实例,用于收集其他模块的代码,最后再将收集的代码打包到一起。
│  external-module.js // ExternalModule 外部模块,例如引入了 'path' 模块,就会生成一个 ExternalModule 实例。
│  module.js // Module 模块,module 实例。
│  rollup.js // rollup 函数,一切的开始,调用它进行打包。
│
├─ast // ast 目录,包含了和 AST 相关的类和函数
│      analyse.js // 主要用于分析 AST 节点的作用域和依赖项。
│      Scope.js // 在分析 AST 节点时为每一个节点生成对应的 Scope 实例,主要是记录每个 AST 节点对应的作用域。
│      walk.js // walk 就是递归调用 AST 节点进行分析。
│
├─finalisers
│      cjs.js
│      index.js
│
└─utils // 一些帮助函数
        map-helpers.js
        object.js
        promise.js
        replaceIdentifiers.js
生成一个new Bundle(),然后执行build()打包
```javascript
let Bundle = require('./bundle')
function rollup(entry, outputFileName) {
    const bundle = new Bundle({ entry })
    bundle.build(outputFileName)
}
module.export = rollup
```
```javascript
class Bundle {
    constructor() {}
    build(outputFileName) {}
    fetchModule(importee, importer) {
       ...
        if(route) {
            let code = fs.readFileSync(route, 'utf8'),
            let module = new Module({
                code, // 模块的源代码
                path: route, // 模块的绝对路径
                bundle: this // 属于那个bundle
            })
            return module;
        }     
    }
}
```
new Module()

每个文件都是一个模块,每个模块都会有一个module实例,在module实例中,会调用acorn库的parse()方法将代码解析称为AST

class Module {
    constructor({code, path, bundle}) {
        this.code = new MagicString(code, {filename: path})
        this.path = path 
        this.bundle = bundle
        this.ast = parse(code, { // 把源代码转换为抽象语法树
            ecmaVersion: 7,
            sourceType: 'module'
        })
        this.analyse()
    }
}
  1. 词法分析 this.analyse()
    • 分析当前模块导入import和导出exports模块,将引入的模块和导出的模块存储起来的this.imports={} // 存放当前模块所有的导入
    • this.exports = {} 存放着当前模块所有的导出
    this.imports = {};//存放着当前模块所有的导入
    this.exports = {};//存放着当前模块所有的导出
    this.ast.body.forEach(node => {
      if (node.type === 'ImportDeclaration') {// 说明这是一个 import 语句
        let source = node.source.value; // 从哪个模块导入的
        let specifiers = node.specifiers; // 导入标识符
        specifiers.forEach(specifier => {
          const name = specifier.imported.name; //name
          const localName = specifier.local.name; //name
          //本地的哪个变量,是从哪个模块的的哪个变量导出的
          this.imports[localName] = { name, localName, source }
        });
        //}else if(/^Export/.test(node.type)){ // 导出方法有很多
      } else if (node.type === 'ExportNamedDeclaration') { // 说明这是一个 exports 语句
        let declaration = node.declaration;//VariableDeclaration
        if (declaration.type === 'VariableDeclaration') {
          let name = declaration.declarations[0].id.name;
          this.exports[name] = {
            node, localName: name, expression: declaration
          }
        }
      }
    });
    analyse(this.ast, this.code, this);//找到了_defines 和 _dependsOn
    
  2. analyse(this.ast, this.code, this)
    • _defines: { value: {} },//存放当前模块定义的所有的全局变量
    • _dependsOn: { value: {} },//当前模块没有定义但是使用到的变量,也就是依赖的外部变量
    • _included: { value: false, writable: true },//此语句是否已经被包含到打包结果中,防止重复打包
    • _source: { value: magicString.snip(statement.start, statement.end) } //magicString.snip 返回的还是 magicString 实例 clone
    function analyse(ast, magicString, module) {
        let scope = new Scope();//先创建一个模块内的全局作用域
        //遍历当前的所有的语法树的所有的顶级节点
        ast.body.forEach(statement => {
        
            //给作用域添加变量 var function const let 变量声明
            function addToScope(declaration) {
                var name = declaration.id.name;//获得这个声明的变量
                scope.add(name);
                if (!scope.parent) {//如果当前是全局作用域的话
                    statement._defines[name] = true;
                }
            }
    
            Object.defineProperties(statement, {
                _defines: { value: {} },//存放当前模块定义的所有的全局变量
                _dependsOn: { value: {} },//当前模块没有定义但是使用到的变量,也就是依赖的外部变量
                _included: { value: false, writable: true },//此语句是否已经 被包含到打包结果中了
                //start 指的是此节点在源代码中的起始索引,end 就是结束索引
                //magicString.snip 返回的还是 magicString 实例 clone
                _source: { value: magicString.snip(statement.start, statement.end) }
            });
            
            //这一步在构建我们的作用域链
            walk(statement, {
                enter(node) {
                    let newScope;
                    if (!node) return
                    switch (node.type) {
                        case 'FunctionDeclaration':
                            const params = node.params.map(x => x.name);
                            if (node.type === 'FunctionDeclaration') {
                                addToScope(node);
                            }
                            //如果遍历到的是一个函数声明,我会创建一个新的作用域对象
                            newScope = new Scope({
                                parent: scope,//父作用域就是当前的作用域
                                params
                            });
                            break;
                        case 'VariableDeclaration': //并不会生成一个新的作用域
                            node.declarations.forEach(addToScope);
                            break;
                    }
                    if (newScope) {//当前节点声明一个新的作用域
                        //如果此节点生成一个新的作用域,那么会在这个节点放一个_scope,指向新的作用域
                        Object.defineProperty(node, '_scope', { value: newScope });
                        scope = newScope;
                    }
                },
                leave(node) {
                    if (node._scope) {//如果此节点产出了一个新的作用域,那等离开这个节点,scope 回到父作用法域
                        scope = scope.parent;
                    }
                }
            });
        });
        ast._scope = scope;
        //找出外部依赖_dependsOn
        ast.body.forEach(statement => {
            walk(statement, {
                enter(node) {
                    if (node._scope) {
                        scope = node._scope;
                    } //如果这个节点放有一个 scope 属性,说明这个节点产生了一个新的作用域
                    if (node.type === 'Identifier') {
                        //从当前的作用域向上递归,找这个变量在哪个作用域中定义
                        const definingScope = scope.findDefiningScope(node.name);
                        if (!definingScope) {
                            statement._dependsOn[node.name] = true;//表示这是一个外部依赖的变量
                        }
                    }
    
                },
                leave(node) {
                    if (node._scope) {
                        scope = scope.parent;
                    }
    
                }
            });
        });
    }
    
  3. this.definitions = {} 全局变量的定义语句存放到definitions里
    // module.js
    this.definitions = {};//存放着所有的全局变量的定义语句
    this.ast.body.forEach(statement => {
        Object.keys(statement._defines).forEach(name => {
        //key 是全局变量名,值是定义这个全局变量的语句
        this.definitions[name] = statement;
        });
    });
    
  4. 展开语法,展开当前模块的所有语法,把这些语句中定义的变量的语句放到结果里
generate()
  • 移除额外代码
  • 处理ast节点上的源码,拼接字符串
  • 返回合并后的源代码
  • 输出到dist/bundle.js中
总结

在这里插入图片描述

  1. 获取入口文件的内容,包装成 module,生成抽象语法树
  2. 对入口文件抽象语法树进行依赖解析
  3. 生成最终代码
  4. 写入目标文件

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1925714.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

fortran简单排序算法,对一维、二维矩阵进行正序或倒序排序

fortran简单排序算法,对一维、二维矩阵进行正序或倒序排序 0. 引言1. 算法实现1.1 一维数组排序1.2 二维数组排序1.2 module文件 2. 结语 0. 引言 排序算法是计算机科学中的一项重要技术,它将一组数据按照特定的顺序排列起来。排序算法有很多种&#xff…

【云原生】Prometheus整合Alertmanager告警规则使用详解

目录 一、前言 二、Altermanager概述 2.1 什么是Altermanager 2.2 Altermanager使用场景 三、Altermanager架构与原理 3.1 Altermanager使用步骤 3.2 Altermanager工作机制 3.3 Altermanager在Prometheus中的位置 四、Altermanager部署与接入Prometheus 4.1 Altermana…

Codeforces Round 797 (Div. 3) F. Shifting String

感觉这种题写多了,第一眼就感觉是个图论,很经典的排列置换问题,首先连边,然后观察样例可以知道,大概是多个环的大小取lcm,但容易发现,环内部的循环节也对答案有影响,比如一个长度为4…

threadx 线程以及优先级调整案例

以正点原子stm32 f407板子为例子,如何创建工程 CubeMXCubeIDE在我之前的文章里提到了,这里我就不多重复。 有关线程优先级的两个参数 priority和preemption,我们来看看在官网是如何定义的 在main.c里面添加一个printf转串口的代码&#xff0…

Binder解析精炼

Binder原理解析精炼 1 注册服务 Server进程向binder驱动向Binder驱动发起服务注册请求 向Binder驱动申请创建一个XXXService的Binder的实体,Binder驱动为这个XXXService创建位于内核中的Binder实体节点以及Binder的引用 Binder驱动将注册请求转发给ServiceManager进…

Manim学习笔记05:实现向量的加法动画

以同一点 O𝑂 为起点的两个已知向量 →a𝑎→, →b𝑏→,以 OA𝑂𝐴,OB𝑂𝐵 为邻边作 □OACB◻𝑂𝐴𝐶𝐵&#xff…

Yolov8 姿态估计

原文:Yolov8 姿态估计 - 知乎 (zhihu.com) YOLOv8论文还没有,官方默默又加了新模型:姿态估计。 现在你可以用YOLOv8做目标检测、实例分割、图像分类、目标跟踪、姿态估计,未完待续。。。。。。 一、Yolov8姿态估计 Yolov8的姿态估计模型是在COCO数据集训练的,目前支持…

python+Selenium自动化之免登录(cookie及token)

目录 cookie免登录 通过接口获取cookie 启用浏览器绕过登录 添加token 使用登录可以减去每次登录的重复操作,直接操作系统登录后的菜单页面,也可以减少安全验证登录,如图像验证登录的操作。注意:cookie和token都有有效期。 c…

前端Vue组件化实践:打造自定义等宽tabs标签组件

在前端开发的世界里,随着业务复杂度的提升和需求的多样化,传统的整体式开发方式已经难以满足快速迭代和高效维护的需求。组件化开发作为一种重要的解决方案,正逐渐受到广大开发者的青睐。本文将结合Vue框架,探讨如何通过组件化开发…

如何在Linux上如何配置虚拟主机

在Linux上配置虚拟主机可以通过使用Apache HTTP服务器来实现。Apache是一个开源的跨平台的Web服务器软件,可以在多种操作系统上运行并支持虚拟主机的配置。 以下是在Linux上配置虚拟主机的步骤: 安装Apache HTTP服务器 在终端中运行以下命令来安装Apache…

CANoe:为什么两个VLAN接口不能设置同一个网络的IP地址呢?

经常玩CANoe的人应该配置过TCP/IP Stack中网络节点的网卡信息,基本的信息包含:MAC地址、IP地址、子网掩码、默认网关、MTU值、IPv6地址。 如果你想让发送出去的报文携带VLAN tag,可以在网卡上添加VLAN tag信息。 此时你就能得到两个新的网卡V…

加速数字化转型,信创自主可控:TapData 为银行业数据管理能力建设提供新思路

使用 TapData,化繁为简,摆脱手动搭建、维护数据管道的诸多烦扰,轻量代替 OGG、DSG 等同步工具,「CDC 流处理 数据集成」组合拳,加速仓内数据流转,帮助企业将真正具有业务价值的数据作用到实处,…

防火墙nat与智能选路

这里写目录标题 此实验是基于上个实验的基础上添加功能拓扑1办公区设备可以通过电信链路和移动链路上网(多对多的NAT,并且需要保留一个公网IP不能用来转换)首先在fw1防火墙创建电信和移动两个安全区域,并且将对应的接口划分进去配置nat测试 分公司设备可…

Java核心篇之JVM探秘:内存模型与管理初探

系列文章目录 第一章 Java核心篇之JVM探秘:内存模型与管理初探 第二章 Java核心篇之JVM探秘:对象创建与内存分配机制 第三章 Java核心篇之JVM探秘:垃圾回收算法与垃圾收集器 第四章 Java核心篇之JVM调优实战:Arthas工具使用及…

[web]-sql注入-白云搜索引擎

ctrlu查看源代码&#xff0c;发现前端有js过滤 <script>function myFunction(){var xdocument.getElementById("number").value;var adocument.getElementById("word").value;var ba.replace(/[\ |\~|\|\!|\|\#|\$|\%|\^|\&|\*|\(|\)|\-|\_|\|\…

linux之find指令基础

目录 前言一、find .二、find xxx -name "*.c"三、组合查找文件名四、find . -type f五、find . -maxdepth 2 -type f六、find . -type f -perm 777七、find . -type f -name "*.txt" ! -perm 777八、借助-exec命令参考链接 前言 testfind下 check1.c ch…

【HTML入门】第十二课 - iframe框架

在早期没有出现Vue和React之前呢&#xff0c;做管理系统&#xff0c;iframe是非常普遍的技术。比如管理系统左侧有非常多的菜单&#xff0c;然后点击菜单后&#xff0c;右边就要展现不同的页面。 又或者呢&#xff0c;我们看一些网站&#xff0c;他们侧边展示着五彩绚烂的广告&…

在 PostgreSQL 里如何实现数据的实时监控和性能瓶颈的快速定位?

&#x1f345;关注博主&#x1f397;️ 带你畅游技术世界&#xff0c;不错过每一次成长机会&#xff01;&#x1f4da;领书&#xff1a;PostgreSQL 入门到精通.pdf 文章目录 在 PostgreSQL 里如何实现数据的实时监控和性能瓶颈的快速定位一、数据实时监控的重要性二、PostgreSQ…

MySQL学习记录 —— 이십 常用工具包

文章目录 1、总览2、mysqlcheck - 表维护程序1、作用2、注意事项3、语法4、命令选项下面每块都大致有这四个部分 3、Mysqldump - 数据库备份程序4、mysqladmin - MySQL 服务器管理程序5、mysqlshow - 显示数据库、表和列信息6、mysqldumpslow - 总结慢查询日志文件7、mysqlbinl…

福利:领取生育津贴汇总

大家注意了&#xff0c;最近多地区发文&#xff0c;生育津贴有了新变化。为了国家的未来&#xff0c;各位大佬记得全力以赴三胎。 01北京--不用缴费也能领取生育津贴 7月1日&#xff0c;北京市人社局、医保局、财政局、税务局等多部门联合印发了《关于领取失业保险金人员参加生…