A, start

This Rollup packaging principle analysis is v0.3.1 version, because of its simple code, high legibility, so it is used to explore the working principle of the packer.

In Rollup, a file is a Module, and each Module generates an AST syntax abstraction tree, and Rollup analyzes each AST node.

Second, the AST

The core of Rollup packaging is the parsing and processing of the AST, and to understand how packaging works you must understand the AST.

The ACorn library is used for AST generation. Acorn generates AST results according to ESTREE specification.

Here is an example of an AST generated by Acorn to get a feel for it.

const { parse } = require('acorn');
const code = `import { a } from './a.js'`

console.log(parse(code, {
  ecmaVersion: 7.sourceType: 'module',}))Copy the code

Generated AST results:

{
  type: 'Program'.start: 0.end: 26.body: [{type: 'ImportDeclaration'.start: 1.end: 26.specifiers: [{type: 'ImportSpecifier'.start: 9.end: 10.imported: {
            type: 'Identifier'.start: 9.end: 10.name: 'a',},local: {
            type: 'Identifier'.start: 9.end: 10.name: 'a',}},],source: {
        type: 'Literal'.start: 18.end: 26.value: './a.js'.raw: '\'./a.js\'',}},],sourceType: 'module'};Copy the code

The basic structure of an AST is a Node, which must contain Type and Start /end (LOC) information. A Node whose type is Program indicates that this is a Program, usually the outermost layer of the script, with the body attribute.

There are several sites where you can view the AST structure online: AST Explorer, Esprima.

Three, V0.3.1 packaging principle

The entry file is SRC /rollup.js, which instantiates a Bundle and returns an object containing the generate and write methods after calling its build method.

export function rollup(entry, options = {}) {
  const bundle = new Bundle({
    entry,
    esolvePath: options.resolvePath
  }) 
  return bundle.build().then(() = > {
    return {
      generate: options= > bundle.generate(options),
      write: (dest, options = {}) = > {
        let { code, map } = bundle.generate({
					dest,
					format: options.format,
					globalName: options.globalName
				});

				return Promise.all([
					writeFile( dest, code ),
					writeFile( dest + '.map', map.toString() ) ]); }}})}Copy the code

Note that this version of Rollup can be used as an example:

rollup('entry.js').then((res) = > {
  res.wirte('bundle.js')})Copy the code

It can be seen that it is mainly divided into two steps: the first step is to build through build, and the second step is to generate or write for output. Genenrate is to return code, write is to write code to a file.

1. build

In the bundle.build method, we call this.fetchModule, which basically reads the contents of the file (Module) and instantiates a Module.

class Bundle {
  build() {
    return this.fetchModule(this.entryPath)
      .then(entryPath= > {
        this.entryModule = entryModule;
        return entryModule.expandAllStatements(true);
      })
      .then((statements) = > {
        this.statements = statements;
        this.deconflict();
      });
  }

  fetchModule() {
    // ...
    
    const module = new Module({
      code,
      path: route,
      bundle: this});// ...}}Copy the code

In the Module constructor, the incoming code(file content) is parsed through Acorn.parse, the AST is retrieved, and the this.analyse is called.

class Module {
  constructor({ code, path, bundle }) {
    this.code = new MagicString(code, {
      filename: path
    })
    this.ast = acorn.parse(code, {
      ecmaVersion: 6.sourceType: 'module'})},this.analyse();
}
Copy the code

The Module’s analyse method walks through the body of the AST and places import or export statements in this.imports and this.exports if they exist.

Then the ANALYSE method in the AST directory is used to add variables such as _scope, _defines variables, _modifies, _dependsOn for each node.

Back to bundle. The build method, the call fetchModule get entryModule (after entry module), and then call the entryModule. ExpandAllStatements method. It iterates through ast.body, calling expandStatement to collect all statements.

ExpandStatement finds the node’s _dependsOn attribute and calls define in turn. In the define method, fetchModule is called according to the path of the imported module, which implements recursive introduction of dependencies and builds a large module tree.

This way all statements are collected into bundle.statements, and then call this.deconflict to resolve naming conflicts.

The whole logic of bundle.build is that there are a lot of decisions about the AST to distinguish between different types of nodes for different processing.

2. generate

The bundle.generate method is relatively independent and its core idea is the splicing of this.statements. Since all statements have already been collected in the statement, you can simply concatenate them and return them.

The export statements are filtered during the concatenation, some import statements are processed, and code is returned.

Iv. Flow chart

Draw a flow chart to deepen understanding:

Five, the summary

The basic packaging principle of Rollup is summarized as follows: Build the AST from the entry file, collect dependencies, and finally concatenate all statements to export bundles.

The latest version, V2.59.0, has more features: Watch, plugin, command line parameters, etc.

Rollup’s plug-in system is simply something extra to do at different stages of the build and Generate lifecycle, and the English documentation is very detailed, so it is recommended to read the documentation directly.

Vi. Relevant materials

  1. A Rollup document
  2. Examples from the article
  3. estree
  4. Use Acorn to parse JavaScript
  5. Learn packaging principles from the rollup source code
  6. Rollup rollup rollup rollup