rollup - Принцип построения и простота реализации

внешний интерфейс rollup.js
rollup - Принцип построения и простота реализации

56.gif

Построить запись каталога серии столбцов

Хуан Дандан, группа медицинской поддержки отдела передовых технологий WeDoctor. Старший любитель кошек, мелкозернистая программа Юань, которая любит статичное и плавное переключение.

1. Обзор накопительного пакета

Адрес официального сайта:rollupjs.org/guide/en/

Что такое накопительный пакет

Давайте посмотрим, что сказал автор Rollup Рич Харрис?Rollup — модульный инструмент для упаковки. По сути, он объединяет файлы JavaScript. И вам не нужно вручную указывать их порядок или беспокоиться о конфликтах имен переменных между файлами. Его внутренняя реализация немного сложнее, чем написано, но это то, что он делает —сливаться.

Сравнить веб-пакет

webpackЭто очень знакомый инструмент для внешнего интерфейса.Он предоставляет мощные функции для создания внешних ресурсов, включая языковые скрипты, такие как html/js/ts/css/less/scss... и изображения/шрифты... и т.д. двоичные файлы. Именно потому, что webpack имеет такие мощные функции, когда webpack упаковывает ресурсы, он генерирует много избыточного кода (если вы проверили пакетный файл webpack, вы его найдете). ​

Для некоторых проектов (особенно библиотек классов), в которых есть только js и нет других статических файлов ресурсов, использование веб-пакета слишком велико для использования, потому что размер файла пакета веб-пакета немного больше, операция немного медленнее, а читабельность немного ниже. В настоящее время вы можете выбрать Rollup ​

RollupЭто сборщик модулей, который поддерживает модули ES6 и ветвление дерева, но не поддерживает разделение кода веб-пакета, горячее обновление модулей и т. д., что означает, что он больше подходит в качестве сборщика для проектов библиотеки классов, а не для проектов приложений. .

простое резюме

Для приложений подходит webpack, а для библиотек классов больше Rollup, используются React/vue/angularRollupкак упаковочный инструмент

2. Предварительные знания по объединению

Прежде чем объяснять принцип построения Rollup, нам нужно понять некоторыеПредварительное знание

magic-string

magic-string — это библиотека для работы со строками, написанная автором Rollup.Эта библиотека в основном инкапсулирует некоторые распространенные методы работы со строками.

var MagicString = require('magic-string')
var magicString = new MagicString('export var name = "zhangsan"')
// 以下所有操作都是基于原生字符串
// 类似于截取字符串
console.log(magicString.snip(0, 6).toString()) // export
// 从开始到结束删除
console.log(magicString.remove(0, 7).toString()) //  var name = "zhangsan"

// 多个模块,把他们打包在一个文件里,需要把很多文件的源代码合并在一起
let bundleString = new MagicString.Bundle();
bundleString.addSource({
    content: 'console.log(hello)',
    separator: '\n'
})
bundleString.addSource({
    content: 'console.log(world)',
    separator: '\n'
})
// // 原理类似
// let str = ''
// str += 'console.log(hello);\n'
// str += 'console.log(world);\n'
console.log(bundleString.toString()) 
// hello
// world

AST

С помощью синтаксического анализа javascript код может быть преобразован в абстрактное синтаксическое дерево AST. Это дерево определяет структуру кода. Манипулируя этим деревом, мы можем точно определить местонахождение операторов объявления, операторов присваивания, операторов и т. д. анализ, оптимизация, изменение , так далее. Исходный код: main.js

// main.js
import { a } from './a'
console.log(a)

Преобразование в AST выглядит следующим образом:

{
  "type": "Program", // 这个 AST 类型为 Program,表明是一个程序
  "start": 0,
  "end": 40,
  "body": [ // body 是一个数组,每一条语句都对应 body 下的一个语句
    {
      "type": "ImportDeclaration", // 导入声明类型
      "start": 0,
      "end": 23,
      "specifiers": [
        {
          "type": "ImportSpecifier",
          "start": 9,
          "end": 10,
          "imported": {
            "type": "Identifier",
            "start": 9,
            "end": 10,
            "name": "a" // 导入模块命名 name 'a'
          },
          "local": {
            "type": "Identifier",
            "start": 9,
            "end": 10,
            "name": "a" // 本地模块命名,同 imported.name
          }
        }
      ],
      "source": {
        "type": "Literal",
        "start": 18,
        "end": 23,
        "value": "./a", // 导入路径 './a'
        "raw": "'./a'"
      }
    },
    {
      "type": "ExpressionStatement", // 表达式类型
      "start": 24,
      "end": 38,
      "expression": {
        "type": "CallExpression", // 调用表达式类型
        "start": 24,
        "end": 38,
        "callee": {
          "type": "MemberExpression",
          "start": 24,
          "end": 35,
          "object": {
            "type": "Identifier",
            "start": 24,
            "end": 31,
            "name": "console"
          },
          "property": {
            "type": "Identifier",
            "start": 32,
            "end": 35,
            "name": "log"
          },
          "computed": false,
          "optional": false
        },
        "arguments": [
          {
            "type": "Identifier",
            "start": 36,
            "end": 37,
            "name": "a"
          }
        ],
        "optional": false
      }
    }
  ],
  "sourceType": "module"
}

Рабочий процесс АСТ

Parse (анализ) преобразует код в абстрактное синтаксическое дерево с множеством узлов estree на дереве. Transform (трансформация) для преобразования абстрактного синтаксического дерева Генерировать (генерация кода) Генерировать новый код из абстрактного синтаксического дерева, преобразованного на предыдущем шаге.

acorn

acorn — это синтаксический анализатор JavaScript, который анализирует строки JavaScript в синтаксическом абстрактном дереве AST. Если вы хотите узнать синтаксическое дерево AST, вы можете щелкнуть этот URL-адресastexplorer.net/

цепочка объемов/областей

В js область — это правило, используемое для указания области доступа к переменной. Цепочка областей действия состоит из ряда переменных объектов в текущей среде выполнения и среде выполнения верхнего уровня, что обеспечивает упорядоченный доступ текущей среды выполнения к переменным и функциям, соответствующим правам доступа.

3. Сведение

Как работает накопительный пакет?

Вы даете ему входной файл — обычно index.js. Rollup будет использовать Acorn для чтения проанализированного файла, который вернет нам нечто, называемое абстрактным синтаксическим деревом (AST). Когда у вас есть AST, вы можете многое узнать о своем коде, например, какие декларации импорта он содержит. ​

Предположим, что в заголовке файла index.js есть такая строка:

import foo from './foo.js';

Это означает, что Rollup должен загрузить, разобрать и проанализировать файл ./foo.js, импортированный в index.js. Анализ повторяется до тех пор, пока не перестанут загружаться модули. Более того, все эти операции являются подключаемыми, поэтому вы можете импортировать из node_modules или использовать исходную карту для компиляции кода ES2015 в ES5.

В Rollup файл представляет собой модуль, и каждый модуль создает абстрактное синтаксическое дерево AST на основе кода файла. ​

Анализ узла AST заключается в том, чтобы увидеть, вызвал ли узел метод функции, прочитал ли он переменную, и если да, то проверить, находится ли он в текущей области, если нет, искать до области верхнего уровня модуля. находится. Если этот модуль не найден, значит, эта функция и метод зависят от других модулей и должны быть импортированы из других модулей. Если вы обнаружите, что в других модулях есть методы, которые зависят от других модулей, он будет рекурсивно читать другие модули и так далее, пока не останется зависимых модулей. Найдите, где определены эти переменные или методы, и включите оператор определения Никакой другой нерелевантный код не требуется ​

Посмотрите на следующий код, давайте сначала поработаем с ним:

// index.js
import { foo } from "./foo";
foo()
var city = 'hangzhou'

function test() {
    console.log('test')
}

console.log(test())
// foo.js
import { bar } from "./bar";
export function foo() {
    console.log('foo')
}
// bar.js
export function bar() {
    console.log('bar')
}
// rollup.config.js
export default {
    input: './src/index.js',
    output: {
        file: './dist/bundle.js', // 打包后的存放文件
        format: 'cjs', //输出格式 amd es6 life umd cjs
        name: 'bundleName', //如果输出格式 life,umd 需要指定一个全局变量
    }
};

Выполните сборку npm run, вы получите следующие результаты:

'use strict';

function foo() {
    console.log('foo');
}

foo();

function test() {
    console.log('test');
}

console.log(test());

Выше мы можем видетьRollup просто объединяет ваш код — никаких отходов. Полученный пакет также можно лучше минифицировать. Некоторые называют это «подъемом прицела». Во-вторых, он удаляет неиспользуемый код из импортируемых модулей. Это называется «(деревотрясение) деревьятрясение». Короче говоря, Rollup — это модульный инструмент для упаковки. ​

Далее вводим исходный код, конкретно анализируем процесс сборки ROLLUP

Анализ процесса строительства Rollup

Структура исходного кода сводки


│  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

Процесс сборки накопительного пакета

Входим в файл с index.js, index зависит от foo.js, а foo зависит от bar.js

// index.js
import { foo } from "./foo";
foo()
var city = 'hangzhou'

function test() {
    console.log('test')
}

console.log(test())
// foo.js
import { bar } from "./bar";
export function foo() {
    console.log('foo')
}
// bar.js
export function bar() {
    console.log('bar')
}

отлаживать!!!

// debug.js
const path = require('path')
const rollup = require('./lib/rollup')
// 入口文件的绝对路径
let entry = path.resolve(__dirname, 'src/main.js')
// 和源码有所不同,这里使用的是同步,增加可读性
rollup(entry, 'bundle.js')
1.new Bundle(), build()

Сначала сгенерируйте экземпляр Bundle, который является упаковщиком. Затем выполните сборку для упаковки и компиляции

// rollup.js
let Bundle = require('./bundle')
function rollup(entry, outputFileName) {
    // Bundle 代表打包对象,里面包含所有的模块信息
    const bundle = new Bundle({ entry })
    // 调用 build 方法开始进行编译
    bundle.build(outputFileName)
}
module.exports = rollup

lib/bundle.jsНачиная с пути входа (в бандле мы сначала равномерно обработаем суффикс файла входа), чтобы найти его определение модуля, в fetchModule будет сгенерирован экземпляр модуляimage.pngОбращаем внимание на код в красном поле, и мы обнаружим, что возвращается модульimage.png

2.new Module()

Каждый файл является модулем, и каждый модуль будет иметь экземпляр модуля. В экземпляре модуля вызывается метод parse() из библиотеки acorn для анализа кода в AST.image.pngАнализ сгенерированного ASTanalyseДавайте сначала посмотрим на AST, сгенерированный файлом ввода index.js.image.pngimage.pngВы можете видеть, что ast.body — это массив, соответствующий пяти операторам index.js. Разверните это дерево ast следующим образом:

{
  "type": "Program",
  "start": 0,
  "end": 128,
  "body": [
    {
      "type": "ImportDeclaration", // 导入声明
      "start": 0,
      "end": 31,
      "specifiers": [
        {
          "type": "ImportSpecifier",
          "start": 9,
          "end": 12,
          "imported": {
            "type": "Identifier",
            "start": 9,
            "end": 12,
            "name": "foo"
          },
          "local": {
            "type": "Identifier",
            "start": 9,
            "end": 12,
            "name": "foo"
          }
        }
      ],
      "source": {
        "type": "Literal",
        "start": 20,
        "end": 30,
        "value": "./foo.js",
        "raw": "\"./foo.js\""
      }
    },
    {
      "type": "ExpressionStatement",
      "start": 32,
      "end": 37,
      "expression": {
        "type": "CallExpression",
        "start": 32,
        "end": 37,
        "callee": {
          "type": "Identifier",
          "start": 32,
          "end": 35,
          "name": "foo"
        },
        "arguments": [],
        "optional": false
      }
    },
    {
      "type": "VariableDeclaration",
      "start": 38,
      "end": 59,
      "declarations": [
        {
          "type": "VariableDeclarator",
          "start": 42,
          "end": 59,
          "id": {
            "type": "Identifier",
            "start": 42,
            "end": 46,
            "name": "city"
          },
          "init": {
            "type": "Literal",
            "start": 49,
            "end": 59,
            "value": "hangzhou",
            "raw": "'hangzhou'"
          }
        }
      ],
      "kind": "var"
    },
    {
      "type": "FunctionDeclaration",
      "start": 61,
      "end": 104,
      "id": {
        "type": "Identifier",
        "start": 70,
        "end": 74,
        "name": "test"
      },
      "expression": false,
      "generator": false,
      "async": false,
      "params": [],
      "body": {
        "type": "BlockStatement",
        "start": 77,
        "end": 104,
        "body": [
          {
            "type": "ExpressionStatement",
            "start": 83,
            "end": 102,
            "expression": {
              "type": "CallExpression",
              "start": 83,
              "end": 102,
              "callee": {
                "type": "MemberExpression",
                "start": 83,
                "end": 94,
                "object": {
                  "type": "Identifier",
                  "start": 83,
                  "end": 90,
                  "name": "console"
                },
                "property": {
                  "type": "Identifier",
                  "start": 91,
                  "end": 94,
                  "name": "log"
                },
                "computed": false,
                "optional": false
              },
              "arguments": [
                {
                  "type": "Literal",
                  "start": 95,
                  "end": 101,
                  "value": "test",
                  "raw": "'test'"
                }
              ],
              "optional": false
            }
          }
        ]
      }
    },
    {
      "type": "ExpressionStatement",
      "start": 106,
      "end": 125,
      "expression": {
        "type": "CallExpression",
        "start": 106,
        "end": 125,
        "callee": {
          "type": "MemberExpression",
          "start": 106,
          "end": 117,
          "object": {
            "type": "Identifier",
            "start": 106,
            "end": 113,
            "name": "console"
          },
          "property": {
            "type": "Identifier",
            "start": 114,
            "end": 117,
            "name": "log"
          },
          "computed": false,
          "optional": false
        },
        "arguments": [
          {
            "type": "CallExpression",
            "start": 118,
            "end": 124,
            "callee": {
              "type": "Identifier",
              "start": 118,
              "end": 122,
              "name": "test"
            },
            "arguments": [],
            "optional": false
          }
        ],
        "optional": false
      }
    }
  ],
  "sourceType": "module"
}

С помощью этого дерева AST мы анализируем, что конкретно сделал **analyse**??? ​

Первый шаг: проанализировать текущие модули импорта [импорта] и экспорта [экспорта] и сохранить импортированные модули и экспортированные модули.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

Из точки останова видно, что foo был сохранен в imports = "** import { foo } from "./foo"; ** exports:{} означает отсутствие оператора экспортаimage.png

Шаг 2: проанализировать(this.ast, this.code, this); // найти _defines и _dependsOn

Узнайте, какие переменные используются текущим модулем Отметьте, какие переменные объявлены текущим модулем и какие переменные импортированы в другие модули. Мы определяем следующие поля для хранения:_defines: { value: {} },//Сохраняем все глобальные переменные, определенные текущим модулем
_dependsOn: { value: {} },//Текущий модуль не определяет, а использует переменные, то есть зависимые внешние переменные_included: { value: false, writable: true },//Включен ли этот оператор в результат упаковки для предотвращения повторной упаковки_source:{ value: magicString.snip(statement.start, statement.end) } //magicString.snip возвращает клон экземпляра magicString ​

Проанализируйте область между каждым узлом AST, постройте дерево областей,

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;
                }

            }
        });
    });
}

Точка останова может видеть, что **_define и _dependsOn ** хранят текущую переменную и введенную переменную соответственно.image.pngimage.png Шаг 3: this.definitions = {}; Сохраните оператор определения глобальных переменных в определениях

// module.js
this.definitions = {};//存放着所有的全局变量的定义语句
    this.ast.body.forEach(statement => {
      Object.keys(statement._defines).forEach(name => {
        //key 是全局变量名,值是定义这个全局变量的语句
        this.definitions[name] = statement;
      });
    });

Шаг 4: Разверните оператор, разверните все утверждения текущего модуля и поместите операторы переменных, определенных в этих заявлениях, в результате

if (statement.type === 'ImportDeclaration') {return} Если это оператор объявления импорта, то есть import { foo } from "./foo"; нам не нужен этот оператор, верните его

//展开这个模块里的语句,把些语句中定义的变量的语句都放到结果里
  expandAllStatements() {
    let allStatements = [];
    this.ast.body.forEach(statement => {
      if (statement.type === 'ImportDeclaration') {return}
      let statements = this.expandStatement(statement);
      allStatements.push(...statements);
    });
    return allStatements;
  }

**expandStatement:** Найдите переменные, от которых зависит текущий узел, и найдите операторы объявления этих переменных. Эти операторы могут быть объявлены в текущем модуле или в импортированном модуле. затем введите его в результат


  expandStatement(statement) {
    let result = [];
    const dependencies = Object.keys(statement._dependsOn);//外部依赖 [name]
    dependencies.forEach(name => {
      //找到定义这个变量的声明节点,这个节点可以有在当前模块内,也可能在依赖的模块里
      let definition = this.define(name);
      result.push(...definition);
    });
    if (!statement._included) {
      statement._included = true;//表示这个节点已经确定被纳入结果里了,以后就不需要重复添加了
      result.push(statement);
    }
    return result;
  }
  

определить: найти узел объявления, который определяет эту переменную.Этот узел может быть в текущем модуле или в зависимом модуле const module = this.bundle.fetchModule(importData.source, this.path);Получить модуль, импортирующий переменную

   define(name) {
    //查找一下导入变量里有没有 name
    if (hasOwnProperty(this.imports, name)) {
      const importData = this.imports[name];
      // 获取导入变量的模块
      const module = this.bundle.fetchModule(importData.source, this.path);
      // 这个 module 模块也有导入导出
      const exportData = module.exports[importData.name];
      // 返回这个导入模块变量的声明语句
      return module.define(exportData.localName);
    } else {
      //definitions 是对象,key 当前模块的变量名,值是定义这个变量的语句
      let statement = this.definitions[name];
      if (statement && !statement._included) {
        return this.expandStatement(statement);
      } else {
        return [];
      }
    }
  }

image.png

This.statements — это все массивы, которые мы возвращаем после анализа теговimage.png

Приведенного выше анализа много, но резюме заключается в следующем:

  • Соберите переменные импорта и экспорта
  • Установите отношение сопоставления для облегчения последующего использования
  • Соберите все переменные, определенные оператором
  • Установите соответствующую связь между переменными и операторами объявления, чтобы облегчить последующее использование
  • Операторы импорта фильтра
  • удалить ключевые слова
  • При выводе оператора определите, является ли переменная импортируемой.
  • Если вам нужно снова рекурсивно собрать переменные зависимых файлов
  • В противном случае вывод напрямую
  • Создайте зависимости, создайте цепочку областей видимости и передайте ее в файл ./src/ast/analyse.js.
  • Mount _source (исходный код), _define (переменные, определенные текущим модулем), _dependsOn (переменные внешних зависимостей), _included (независимо от того, был ли он включен в оператор вывода) для каждого оператора абстрактного синтаксического дерева
  • Соберите переменные, определенные в каждом операторе, создав цепочку областей видимости.
3.сгенерировать()

Шаг 1. Удалите лишний код Например, код функции foo(), импортированный из foo.js, выглядит следующим образом: export function foo() {}. rollup удаляет экспорт и становится функцией foo() {}. Поскольку они собираются упаковываться вместе, необходимости в экспорте нет. ​

Шаг 2: Добавьте исходный код узла AST в magicString Эта операция по сути эквивалентна написанию строк.image.pngШаг 3: **возвратите magicString.toString()**. Вернуть объединенный исходный код

generate() {
    let magicString = new MagicString.Bundle();
    this.statements.forEach(statement => {
      const source = statement._source;
      if (statement.type === 'ExportNamedDeclaration') {
        source.remove(statement.start, statement.declaration.start);
      } 
      magicString.addSource({
        content: source,
        separator: '\n'
      });
    });
    return { code: magicString.toString() };
  }

Наконец, вывод в 'dist/bundle.js'image.png

резюме

Проще говоря, сборки Rollup делают следующие вещи:

  • Получите содержимое входного файла, упакуйте его в модуль и сгенерируйте абстрактное синтаксическое дерево.
  • Анализ зависимостей абстрактного синтаксического дерева входного файла
  • Сгенерировать окончательный код
  • записать в целевой файл

4. Резюме

Вышеупомянутый процесс реализации кода может помочь вам легко реализовать процесс упаковки накопительных пакетов, но это только абстракция исходного кода накопительных пакетов, которая удобна для всех, чтобы понять принцип упаковки накопительных пакетов.Многие детали не выписаны.Если вы интересно, вы можете подробно прочитать его исходный код.

5. Ссылки

zhuanlan.zhihu.com/p/372808332

GitHub.com/rare earth/gold-no…

blog.CSDN.net/пожалуйста 411020382/…