当前位置: 首页 > news >正文

rollup打包工具

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.jsobject.jspromise.jsreplaceIdentifiers.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 = bundlethis.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; //nameconst 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;//VariableDeclarationif (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) returnswitch (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;//找出外部依赖_dependsOnast.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. 写入目标文件

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • ArcGIS的智慧与情怀
  • 蚁剑编码器——高级
  • 骨头的诱惑
  • Directory Opus 13 专业版(Windows 增强型文件管理器)值得购买?
  • wps批量删除空白单元格
  • [AI 大模型] Meta LLaMA-2
  • MVC之 Controller 》》 ModelState ValidationMessageFor ValidationSummary
  • 小厂Java开发面经解析
  • 如何连接到公司的服务器?
  • 模板语法指令语法——02
  • 【Leetcode--旋转矩阵】
  • tkinter-TinUI-xml实战(12)pip可视化管理器
  • 新书速览|Vue.js 3.x+Express全栈开发:从0到1打造商城项目
  • 数据结构与算法(1):递归函数的设计技巧
  • PostgreSQl 物化视图
  • 收藏网友的 源程序下载网
  • Babel配置的不完全指南
  • es6--symbol
  • github指令
  • HomeBrew常规使用教程
  • Java精华积累:初学者都应该搞懂的问题
  • miniui datagrid 的客户端分页解决方案 - CS结合
  • open-falcon 开发笔记(一):从零开始搭建虚拟服务器和监测环境
  • thinkphp5.1 easywechat4 微信第三方开放平台
  • underscore源码剖析之整体架构
  • vue从创建到完整的饿了么(18)购物车详细信息的展示与删除
  • 得到一个数组中任意X个元素的所有组合 即C(n,m)
  • 前端每日实战:61# 视频演示如何用纯 CSS 创作一只咖啡壶
  • ​香农与信息论三大定律
  • ######## golang各章节终篇索引 ########
  • #在线报价接单​再坚持一下 明天是真的周六.出现货 实单来谈
  • $var=htmlencode(“‘);alert(‘2“); 的个人理解
  • (16)UiBot:智能化软件机器人(以头歌抓取课程数据为例)
  • (6)设计一个TimeMap
  • (免费领源码)python#django#mysql校园校园宿舍管理系统84831-计算机毕业设计项目选题推荐
  • (牛客腾讯思维编程题)编码编码分组打印下标题目分析
  • (收藏)Git和Repo扫盲——如何取得Android源代码
  • (一)utf8mb4_general_ci 和 utf8mb4_unicode_ci 适用排序和比较规则场景
  • (已解决)什么是vue导航守卫
  • (转载)(官方)UE4--图像编程----着色器开发
  • .NET CF命令行调试器MDbg入门(四) Attaching to Processes
  • .NET Core Web APi类库如何内嵌运行?
  • .net core 调用c dll_用C++生成一个简单的DLL文件VS2008
  • .net framework 4.0中如何 输出 form 的name属性。
  • .net MVC中使用angularJs刷新页面数据列表
  • .net SqlSugarHelper
  • .NetCore Flurl.Http 升级到4.0后 https 无法建立SSL连接
  • @autowired注解作用_Spring Boot进阶教程——注解大全(建议收藏!)
  • @Slf4j idea标红Cannot resolve symbol ‘log‘
  • @vueup/vue-quill使用quill-better-table报moduleClass is not a constructor
  • @zabbix数据库历史与趋势数据占用优化(mysql存储查询)
  • []常用AT命令解释()
  • [acwing周赛复盘] 第 69 场周赛20220917
  • [ASP.NET 控件实作 Day7] 设定工具箱的控件图标
  • [bzoj 3124][sdoi 2013 省选] 直径