Rollup is the next generation ES module bundler

1.1 background

  • Webpack packaging is very cumbersome, packaging volume is relatively large
  • Rollup is mainly used to package JS libraries
  • Vue/React/Angular all use rollup as a packaging tool

1.2 Installation Dependencies

cnpm i @babel/core @babel/preset-env  @rollup/plugin-commonjs @rollup/plugin-node-resolve @rollup/plugin-typescript lodash rollup rollup-plugin-babel postcss rollup-plugin-postcss rollup-plugin-terser tslib typescript rollup-plugin-serve rollup-plugin-livereload -D
Copy the code

1.3 Initial Use

1.3.1 rollup. Config. Js

  • Asynchronous Module Definition Asynchronous Module Definition
  • ES6 Module is a new modular solution proposed by ES6
  • IIFE(Immediately Invoked Function Expression) executes the Function Expression Immediately. By immediate execution, a Function is declared and Invoked Immediately
  • UMD stands for Universal Module Definition
  • CJS is the modularity standard adopted by NodeJS. Commonjs uses the require method to introduce modules, where require() takes the module name or the path to the module file

rollup.config.js

export default {
    input:'src/main.js'.output: {file:'dist/bundle.cjs.js'.// The path and name of the output file
        format:'cjs'.// Five output formats: AMD/ES6 / Iife/UMD/CJS
        name:'bundleName'// Must be provided when format is iIFE and umD, will be hung as a global variable under window}}Copy the code

1.3.2 SRC \ main js

console.log('hello');
Copy the code

1.3.3 package. Json

{
 "scripts": {
    "build": "rollup --config"}},Copy the code

1.3.4 dist \ index HTML

<! DOCTYPEhtml>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <title>rollup</title>
</head>
<body>
    <script src="bundle.cjs.js"></script>
</body>
</html>
Copy the code

1.4 support the Babel

To use the new syntax, you can use Babel to compile the output

1.4.1 Installing Dependencies

  • @babel/core is the core package of Babel
  • @ Babel/preset – env is the default
  • Rollup-plugin-babel is the Babel plug-in
cnpm install rollup-plugin-babel @babel/core @babel/preset-env --save-dev
Copy the code

1.4.2 SRC \ main js

let sum = (a,b) = >{
    return a+b;
}
let result = sum(1.2);
console.log(result);
Copy the code

1.4.3 .babelrc

{
    "presets": [["@babel/env",
        {
            "modules":false}}]]Copy the code

1.4.4 rollup. Config. Js

+import babel from 'rollup-plugin-babel';
export default {
    input:'src/main.js'.output: {file:'dist/bundle.cjs.js'.// The path and name of the output file
        format:'cjs'.// Five output formats: AMD/ES6 / Iife/UMD/CJS
        name:'bundleName'// Must be provided when format is iIFE and umD, will be hung as a global variable under window
    },
+   plugins:[
+       babel({
+           exclude:"node_modules/**"+}) +]}Copy the code

1.5 the tree – shaking

  • Tree-shaking is all about eliminating useless JS code
  • Rollup handles only functions and top-level import/export variables

1.5.1 SRC \ main js

src\main.js

import {name,age} from './msg';
console.log(name);
Copy the code

1.5.2 the SRC \ MSG. Js

export var name = 'beijing';
export var age = 12;
Copy the code

1.6 Using third-party Modules

By default, only ES6+ import/export is supported for modules in rollup.js compiled source code

1.6.1 Installing Dependencies

cnpm install @rollup/plugin-node-resolve @rollup/plugin-commonjs lodash  --save-dev
Copy the code

1.6.2 SRC \ main js

import _ from 'lodash';
console.log(_);
Copy the code

1.6.3 rollup. Config. Js

import babel from 'rollup-plugin-babel';
+import resolve from '@rollup/plugin-node-resolve';
+import commonjs from '@rollup/plugin-commonjs';
export default {
    input:'src/main.js'.output: {file:'dist/bundle.cjs.js'.// The path and name of the output file
        format:'cjs'.// Five output formats: AMD/ES6 / Iife/UMD/CJS
        name:'bundleName'// Must be provided when format is iIFE and umD, will be hung as a global variable under window
    },
    plugins:[
        babel({
            exclude:"node_modules/**"
        }),
+       resolve(),
+       commonjs()
    ]
}
Copy the code

1.7 use the CDN

1.7.1 the SRC \ main js

import _ from 'lodash';
import $ from 'jquery';
console.log(_.concat([1.2.3].4.5));
console.log($);
export default 'main';
Copy the code

1.7.2 dist \ index HTML

<! DOCTYPEhtml>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <title>rollup</title>
</head>
<body>
    <script src="https://cdn.jsdelivr.net/npm/lodash/lodash.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/jquery/jquery.min.js"></script>
    <script src="bundle.cjs.js"></script>
</body>
</html>
Copy the code

1.7.3 rollup. Config. Js

import babel from 'rollup-plugin-babel';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
export default {
    input:'src/main.js'.output: {file:'dist/bundle.cjs.js'.// The path and name of the output file
+       format:'iife'.// Five output formats: AMD/ES6 / Iife/UMD/CJS
+       name:'bundleName'.// Must be provided when format is iIFE and umD, will be hung as a global variable under window
+       globals:{
+           lodash:'_'.// tell rollup that the global variable _ is lodash
+           jquery:'$' // tell rollup that the global variable $is jquery+}},plugins:[
        babel({
            exclude:"node_modules/**"
        }),
        resolve(),
        commonjs()
    ],
+   external:['lodash'.'jquery']}Copy the code

1.8 use the typescript

, version 1.8.1 installation

cnpm install tslib typescript @rollup/plugin-typescript --save-dev
Copy the code

1.8.2 SRC \ main ts

let myName:string = 'beijing';
let age:number=12;
console.log(myName,age);
Copy the code

1.8.3 rollup. Config. Js

import babel from 'rollup-plugin-babel';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
+import typescript from '@rollup/plugin-typescript';
export default {
+   input:'src/main.ts'.output: {file:'dist/bundle.cjs.js'.// The path and name of the output file
        format:'iife'.// Five output formats: AMD/ES6 / Iife/UMD/CJS
        name:'bundleName'.// Must be provided when format is iIFE and umD, will be hung as a global variable under window
        globals: {lodash:'_'.// tell rollup that the global variable _ is lodash
            jquery:'$' // tell rollup that the global variable $is jquery}},plugins:[
        babel({
            exclude:"node_modules/**"
        }),
        resolve(),
        commonjs(),
+       typescript()
    ],
    external: ['lodash'.'jquery']}Copy the code

1.8.4 tsconfig. Json

{
  "compilerOptions": {  
    "target": "es5"."module": "ESNext"."strict": true."skipLibCheck": true."forceConsistentCasingInFileNames": true}}Copy the code

1.9 compressed JS

Terser is a JavaScript compressor toolkit that supports ES6 +

1.9.1 installation

cnpm install rollup-plugin-terser --save-dev
Copy the code

1.9.2 rollup. Config. Js

import babel from 'rollup-plugin-babel';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import typescript from '@rollup/plugin-typescript';
+import {terser} from 'rollup-plugin-terser';
export default {
    input:'src/main.ts'.output: {file:'dist/bundle.cjs.js'.// The path and name of the output file
        format:'iife'.// Five output formats: AMD/ES6 / Iife/UMD/CJS
        name:'bundleName'.// Must be provided when format is iIFE and umD, will be hung as a global variable under window
        globals: {lodash:'_'.// tell rollup that the global variable _ is lodash
            jquery:'$' // tell rollup that the global variable $is jquery}},plugins:[
        babel({
            exclude:"node_modules/**"
        }),
        resolve(),
        commonjs(),
        typescript(),
+       terser(),
    ],
    external: ['lodash'.'jquery']}Copy the code

1.10 compile CSS

Rollup-plugin-postcs plugin supports compiling CSS

1.10.1 installation

cnpm install  postcss rollup-plugin-postcss --save-dev
Copy the code

1.10.2 rollup. Config. Js

import babel from 'rollup-plugin-babel';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import typescript from '@rollup/plugin-typescript';
import {terser} from 'rollup-plugin-terser';
+import postcss from 'rollup-plugin-postcss';
export default {
    input:'src/main.js'.output: {file:'dist/bundle.cjs.js'.// The path and name of the output file
        format:'iife'.// Five output formats: AMD/ES6 / Iife/UMD/CJS
        name:'bundleName'.// Must be provided when format is iIFE and umD, will be hung as a global variable under window
        globals: {lodash:'_'.// tell rollup that the global variable _ is lodash
            jquery:'$' // tell rollup that the global variable $is jquery}},plugins:[
        babel({
            exclude:"node_modules/**"
        }),
        resolve(),
        commonjs(),
        typescript(),
        //terser(),
+       postcss()
    ],
    external: ['lodash'.'jquery']}Copy the code

1.10.3 SRC \ main js

import './main.css';
Copy the code

1.10.4 SRC \ main CSS

body{
    background-color: green;
}
Copy the code

1.11 Local Server

1.11.1 installation

cnpm install rollup-plugin-serve --save-dev
Copy the code

1.11.2 rollup. Config. Dev. Js

import babel from 'rollup-plugin-babel';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import typescript from '@rollup/plugin-typescript';
import postcss from 'rollup-plugin-postcss';
+import serve from 'rollup-plugin-serve';
export default {
    input:'src/main.js'.output: {file:'dist/bundle.cjs.js'.// The path and name of the output file
        format:'iife'.// Five output formats: AMD/ES6 / Iife/UMD/CJS
        name:'bundleName'.// Must be provided when format is iIFE and umD, will be hung as a global variable under window
        sourcemap:true.globals: {lodash:'_'.// tell rollup that the global variable _ is lodash
            jquery:'$' // tell rollup that the global variable $is jquery}},plugins:[
        babel({
            exclude:"node_modules/**"
        }),
        resolve(),
        commonjs(),
        typescript(),
        postcss(),
+       serve({
+           open:true,
+           port:8080,
+           contentBase:'./dist'+})],external: ['lodash'.'jquery']}Copy the code

1.11.3 package. Json

{
  "scripts": {
    "build": "rollup --config rollup.config.build.js"."dev": "rollup --config rollup.config.dev.js -w"}},Copy the code

2. Pre-knowledge

2.1. Initialize the project

cnpm install magic-string acorn --save
Copy the code

2.2. Magic – string

Magic-string is a tool for manipulating strings and generating source-map doc/1.magicString.js

var MagicString = require('magic-string');
var magicString = new MagicString('export var name = "beijing"');
// Similar to intercepting a string
console.log(magicString.snip(0.6).toString()); // export
// Delete string from start to finish (index is always based on original string, not changed)
console.log(magicString.remove(0.7).toString()); // var name = "beijing"
// Use magicString. Bundle to combine multiple source code
// Many modules, to package them in one file, requires merging the source code of many files together
let bundleString = new MagicString.Bundle();
bundleString.addSource({
    content:'var a = 1; '.separator:'\n'
});
bundleString.addSource({
    content:'var b = 2; '.separator:'\n'
});
/* let str = ''; str += 'var a = 1; \n' str += 'var b = 2; \n' console.log(str); * /
console.log(bundleString.toString());
// var a = 1;
//var b = 2;
Copy the code

2.3. The AST

JavaScript Parser can transform the code into an abstract syntax tree AST, which defines the structure of the code. By manipulating this tree, we can accurately locate declaration statements, assignment statements, operation statements, etc., and realize the code analysis, optimization, change and other operations

2.3.1 AST workflow

  • Parse converts source code into an abstract syntax tree with many ESTree nodes
  • Transform Transforms the abstract syntax tree
  • Generate generates new code from the transformed abstract syntax tree of the previous step

2.3.2 acorn

  • Astexplorer can turn code into a syntax tree

  • Acorn results comply with The Estree Spec

Import $from ‘jquery ast

2.3.2.1 walk. Js

doc/walk.js

/ * * * *@param {*} Ast Syntax tree to traverse *@param {*} Param1 Configures the object */
function walk(ast, { enter, leave }) {
    visit(ast, null, enter, leave);
}
/** * Access the node *@param {*} node 
 * @param {*} parent 
 * @param {*} enter 
 * @param {*} leave 
 */
function visit(node, parent, enter, leave) {
    if (enter) {// Execute the Enter method on this node first
        enter(node, parent);// If you don't care about this, you can write it like this
        //enter.call(null,node,parent); // If you want to specify this in Enter
    }
    // Iterate over the child nodes to find which are the child nodes of the object
    let keys = Object.keys(node).filter(key= > typeof node[key] === 'object');
    keys.forEach(key= > {//key=specifiers value=[]
        let value = node[key];
        if (Array.isArray(value)) {
            value.forEach((val) = > visit(val, node, enter, leave));
        } else if(value && value.type){
            visit(value, node, enter, leave)
        }
    });
    // Execute the exit method again
    if(leave) { leave(node, parent); }}module.exports = walk;
Copy the code

2.3.2.2 Acorn.js tests walk.js

doc/acorn.js

// Both webpack and Rollup use the Acorn module to convert source code into an abstract syntax tree AST
let acorn = require('acorn');
let walk = require('./walk');
The parse method converts source code to an abstract syntax tree
let astTree = acorn.parse(`import $ from 'jquery'; `, {locations:true.ranges:true.sourceType:'module'.ecmaVersion:8
});
let ident = 0;
const padding = () = >"".repeat(ident);
//console.log(astTree.body);
// Walk through each statement in the syntax tree
astTree.body.forEach(statement= >{
    // Each statement is passed to the walk method, which iterates through the sentence elements
    // Use the depth-first method for traversal
    walk(statement,{
        enter(node){
            if(node.type){
                console.log(padding()+node.type+'进入');
                ident+=2; }},leave(node){
            if(node.type){
                ident-=2;
                console.log(padding()+node.type+'leave'); }}}); });Copy the code

Depth traversal figurePrint the result

ImportDeclaration Enter ImportDefaultSpecifier enter Identifier Enter Identifier Leave ImportDefaultSpecifier leave Literal enter Literal leave ImportDeclaration leaveCopy the code

2.4 scope

Against 2.4.1 scope

In JS, scope is a rule that specifies the scope of access to a variable

function one() {
  var a = 1;
}
console.log(a);
Copy the code

2.4.2 Scope chain

The scope chain is composed of a series of variable objects of the current execution environment and the upper execution environment, which ensures the ordered access of the current execution environment to the variables and functions that meet the access permissions. 2.4.2.1 Scope.js doc/scope.js

class Scope{
  constructor(options = {}){
    this.name = options.name;// The scope name is not useful, just to help people know
    this.parent = options.parent;// Parent scope
    this.names = options.params||[];// What are the variables in this action
  }
  add(name){
    this.names.push(name);
  }
  findDefiningScope(name){
    if(this.names.includes(name)){
        return this;
    }
    if(this.parent){
        return this.parent.findDefiningScope(name);
    }
    return null; }}module.exports = Scope;
Copy the code

2.4.2.2 useScope. Js

doc/usescope.js

let Scope = require('./scope');
let a = 1;
function one(d){
    let b = 2;
    function two(d){
        let c = 3;
        console.log(a,b,c,d);
    }
    two();
}
one();
let globalScope = new Scope({
    name:'globalScope'.params: [].parent:null
});
globalScope.add('a');
let oneScope = new Scope({
    name:'oneScope'.params: [].parent:globalScope
});
oneScope.add('b');
oneScope.add('two');
let twoScope = new Scope({
    name:'twoScope'.params: ['d'].parent:oneScope
});
twoScope.add('c');

console.log(twoScope.findDefiningScope('a').name);//globalScope
console.log(twoScope.findDefiningScope('b').name);//oneScope
console.log(twoScope.findDefiningScope('c').name);//twoScope
console.log(twoScope.findDefiningScope('d').name);//twoScope

The core of the tree-shaking principle is based on such a scope chain
Copy the code

3. Realize the rollup

Rollup Warehouse address

3.0 Directory Structure

. ├ ─ ─ package. Json ├ ─ ─ the README. Md ├ ─ ─ lib ├ ─ ─ the ast │ ├ ─ ─ analyse. Js// Analyze the scope and dependencies of AST nodes│ ├ ─ ─ Scope. Js// Some statements create new scope instances│ └ ─ ─ walk. Js// Provides recursive traversal of the AST syntax tree├ ─ ─ bundle. Js// The package tool generates a Bundle instance during packaging, collects other modules, and finally packages all the code together for output├ ─ ─module.js// Each file is a module├ ─ ─ a rollup. Js// Packaged entry module└ ─ ─ utilsCopy the code

3.1 the SRC \ MSG. Js

export let name = 'xiaoming';
export let age = 12;
name+='hello';
Copy the code

3.2 the SRC \ index. Js

import {name,age} from './msg';
var name2 ='1'
function say(){
    console.log('hello',name);
}
say();

import {age1} from './age1';
import {age2} from './age2';
import {age3} from './age3';
console.log(age1,age2,age3);


if(true) {var age4 = 12;
}
console.log(age4);


var name5 = 'xiaoqiang';
var age5 = 12;
console.log(age5);
Copy the code

Pack the result

let name = 'xiaoming';
name+='hello';
function say(){
    console.log('hello',name);
}
say();
const age = 11;
const age1 = age + '1';
const age$1 = 12;
const age2 = age$1 + '2';
const age$2 = 13;
const age3 = age$2 + '3';
console.log(age1,age2,age3);
if(true) {var age4 = 12;
}
console.log(age4);
var age5 = 12;
console.log(age5);
Copy the code

3.3 the debug. Js

const path = require('path');
const rollup = require('./lib/rollup');
// The absolute path to the entry file
let entry = path.resolve(__dirname,'src/index.js');
rollup(entry,'bundle.js');
Copy the code

3.4 lib/rollup. Js

const Bundle = require('./bundle');
/ * * *@param {*} Entry Entry file *@param {*} Filename indicates the filename */
function rollup(entry,filename){
    debugger
    // Create the bundle based on entry
   let bundle = new Bundle({entry});
   // Compile the entry file and write the output to the file
   bundle.build(filename);
}

module.exports = rollup;
Copy the code

3.5 lib/bundle. Js

let fs = require('fs');
let MagicString = require('magic-string');
let path = require('path');
const Module = require('./module');
let {has,replaceIdentifiers} = require('./utils');
class Bundle{
    constructor(options){
        //entryPath must be absolute
        // Import file data
        this.entryPath = path.resolve(options.entry.replace(/\.js$/.' ') +'.js');// The absolute path to the entry file
        //this.modules = {}; // There are some modules in it
    }
    /** * Compiles the entry module and its dependent modules *@param {*} Filename Specifies the output file path */
    build(filename){
        // Each file is a module
       let entryModule = this.fetchModule(this.entryPath);
       // From the entry module, expand all statements and assign to bundle.statements
       this.statements = entryModule.expandAllStatements();
       this.deConflict();// Handle conflicting variable names
       const {code } = this.generate();// Generate the final source according to the this.statements statement
       fs.writeFileSync(filename,code);
    }
    deConflict(){
      const defines = {};// Variable definition
      const conflicts = {};// Name conflict
      this.statements.forEach(statement= >{
        // Iterate over all variables declared by all statements
        Object.keys(statement._defines).forEach(name= >{//age
          if(has(defines,name)){
            conflicts[name]=true;//conflict.age = true; // Multiple modules contain the age variable
          }else{
            defines[name]=[];// The first one was definitely not defined, so assign an empty array
          }
          Key is the name of the variable, and the value is an array of modules that define the variable
          defines[name].push(statement._module);// Put the corresponding module in the statement
        });
      });
      // [age1,age2,age3]
      Object.keys(conflicts).forEach(name= >{
        const modules = defines[name];
        modules.shift();
        modules.forEach((module,index) = >{
          const replaceName = name + '$'+(index+1);module.rename(name,replaceName);
        });
      });
    }
    /** * get the module instance corresponding to this path *@param {*} The importee file path can be either a phase path or an absolute path submodule *@param {*} Importer imports Importee module parent module */
    fetchModule(importee,importer){
      let route;
      if(! importer){// If you are an entry file, pass the absolute path
        route=importee;
      }else{
        if(path.isAbsolute(importee)){
          route=importee;
        }else{// If relative
          route = path.resolve(path.dirname(importer),importee.replace(/\.js$/.' ') +'.js'); }}if(route){
        let code = fs.readFileSync(route,'utf8');
        const module = new Module({
            code,/ / the source code
            path:importee,// File path
            bundle:this// Which bundle instance does it belong to
        });
        return module; }}generate(){
      let magicStringBundle = new MagicString.Bundle();
      this.statements.forEach(statement= >{
        // Before generating the code, we need to deal with variable substitutions
        let replacements = {};// Replace the name of the variable
        // Fetch all dependent variables and defined variables
        Object.keys(statement._dependsOn).concat(Object.keys(statement._defines))
        .forEach(name= >{
          Age => AGE $1 age=>age$2
          const canonicalName = statement._module.getCanonicalName(name);
          if(name ! == canonicalName)replacements[name]=canonicalName; });const source = statement._source.clone();
        if(/^Export/.test(statement.type)){
          source.remove(statement.start,statement.declaration.start);
        }
        // Replace old name with new name
        replaceIdentifiers(statement,source,replacements);
        magicStringBundle.addSource({
          content:source,
          separator:'\n'
        });
      });
      return {code:magicStringBundle.toString()}
    }
}
module.exports = Bundle;
Copy the code

3.6 lib/module. Js

const MagicString = require('magic-string');
const {parse} = require('acorn');
const analyse = require('./ast/analyse');
const path = require('path');
const {has} = require('./utils');
const SYSTEM_VARIABLES = ['console'.'log'];
class Module{
   constructor({code,path,bundle}){
      this.code = new MagicString(code,{filename:path});
      this.path = path;
      this.bundle = bundle;
      this.canonicalNames = {};
      this.ast = parse(code,{
        ecmaVersion:7.sourceType:'module'
      });
      this.analyse();
   }
   analyse(){
     Parse out which variables are imported or exported by this module
     this.imports = {};/ / import
     this.exports = {};/ / export
    
     this.ast.body.forEach(node= >{
       if(node.type === 'ImportDeclaration') {// this is an import statement
          let source = node.source.value;//"./ MSG "which module is imported
          node.specifiers.forEach(specifier= >{
            const localName = specifier.local.name;// The local variable name is age
            const name = specifier.imported.name;// The imported variable name is age
            // the current module has an imported variable called age, which is imported from the./ MSG module
            // From which module which variable is imported, what is the name of the local variable
            // this.imports.age = {source:"./msg",name: 'age',localName:'age'};
            this.imports[localName]={source,name,localName};
          });
       }else if(node.type === 'ExportNamedDeclaration') {const variableDeclaration = node.declaration;
          let name = variableDeclaration.declarations[0].id.name;//age
          // Record which variables are exported in this module and which node is exported
          // Which variable is exported, through which variable declaration, what is the name, and what is the exported node
          //this.exports.age = {node,localName:'age',expression:variableDeclaration};
          this.exports[name]={node,localName:name,expression:variableDeclaration}; }}); analyse(this.ast,this.code,this);// Pass the instance of the current module to the analyse
     // Define a variable definition in the current module that holds all the variable definition statements
     this.definitions = {};// A statement that defines a variable
     this.modifications = {};// Contains all modification statements
     this.ast.body.forEach(statement= >{
       Object.keys(statement._defines).forEach(name= >{
         // The name of the global variable defined in this module. The value is the statement that defines the global variable
         this.definitions[name]=statement;
       });
       Object.keys(statement._modifies).forEach(name= >{
        // The name of the variable modified by this statement
        if(! has(this.modifications,name)){
          this.modifications[name]=[];
        }
        this.modifications[name].push(statement);
      });
      // We have put all modifications into module. Modifications
     });

   }
   expandAllStatements(){
       let allStatements = [];// All statements after the current module is expanded
       this.ast.body.forEach(statement= >{
           if(statement.type === 'ImportDeclaration') return;// No longer need to place import statements in the result
           if(statement.type === 'VariableDeclaration') return;// If it is a variable declaration, it is not needed
           let statements = this.expendStatement(statement); allStatements.push(... statements); });return allStatements;
   }
   expendStatement(statement){
    statement._include = true;// Specify that the statement is included in the output
    let result = [];
    //1. Variable definitions that contain dependencies
    const dependencies = Object.keys(statement._dependsOn);//[age]
    dependencies.forEach(name= >{
      let definition = this.define(name);// Find the definition statement for the age variable, and returnresult.push(... definition); });//2. Add your own statement
    result.push(statement);
    //3. Get or add the modified statement
    // Get the variable defined by the current statement
    const defines = Object.keys(statement._defines);//['age']
    defines.forEach(name= >{
      The key variable is the number of modifications to be made and the modifications are modifications to the module
      const modifications = has(this.modifications,name)&&this.modifications[name];
      if(modifications){
        modifications.forEach(statement= >{
          if(! statement._include){// To prevent double inclusion, check whether statement is already included in the output
            let statements = this.expendStatement(statement); result.push(... statements); }}); }});return result;
   }
   // Find the definition statement for this variable and include it
   define(name){
     debugger
     // Check whether this variable is an imported variable
     //this.imports[localName]={source,name,localName};
    if(has(this.imports,name)){
        const importDeclaration = this.imports[name];
        // create the dependent module source. / MSG
        let module = this.bundle.fetchModule(importDeclaration.source,this.path);
        //module.exports.name = {node,localName:'name',expression:variableDeclaration};
        const exportDeclaration = module.exports[importDeclaration.name];
        return module.define(exportDeclaration.localName);
    }else{
      // Get the variables defined in the current module and the definition statement
      let statement = this.definitions[name];
      if(statement){// If there is a definition,
        if(statement._include){If yes, return an empty array
          return [];
        }else{
          return this.expendStatement(statement);// Expand the returned result}}else if(SYSTEM_VARIABLES.includes(name)){// is a system variable
        return [];
      }else{
        throw new Error(` variable${name}Neither imported from outside nor declared in the current module); }}}/ / renamed
   rename(localName,replaceName){
    this.canonicalNames[localName]=replaceName;
   }
   getCanonicalName(localName){
    //return localName;
    //this. canonnames holds all the corresponding names
    if(! has(this.canonicalNames,localName)){
      this.canonicalNames[localName]=localName;/ / the default value
    }
    return this.canonicalNames[localName]; }}module.exports = Module;
Copy the code

3.7 analyse. Js

lib\ast\analyse.js

let walk = require('./walk');
let Scope = require('./scope');
/** * Parse the module's code syntax tree ** what scopes are in the current module, what variables are in those fields, and then know which are imported variables and which are declared variables in the module *@param {*} Syntax tree * corresponding to the AST module@param {*} MagicString class */
function analyse(ast,code,module){
  let scope = new Scope();// The global scope object in the module
  ast.body.forEach(statement= >{
     // Add variable declarations to the current scope
      function addToScope(declarator,isBlockDeclaration=false){
        var name = declarator.id.name;// Declare the variable name age
        Age is a block-level scope, but age is a global variable
        scope.add(name,isBlockDeclaration);// Add the variable name to scope
        // Statement is a top-level node or tier 1 node
        if(! scope.parent || (! isBlockDeclaration)){// No parent scope // If there is no lower scope, it is the top-level scope within the module
          statement._defines[name]=true;// If it is a top-level scope, the variables it declares are top-level variables}}Object.defineProperties(statement,{
          _module: {value:module},
          _defines: {value: {}},// The current node declares the variable home
          _modifies: {value: {}},// Modify the statement
          _dependsOn: {value: {}},// What external variable name the current node depends on
          _included: {value:false.writable:true},// This statement is already included in the output statement
          _source: {value:code.snip(statement.start,statement.end)}
      })
      // Iterate over the statement to build scopeChain
      Collect the variables defined on each statement and create a chain of scopes
      walk(statement,{
        enter(node){
          let newScope;
          switch(node.type){
            case 'FunctionDeclaration':
              addToScope(node);
              // the function declaration creates a new scope object
              const params = node.params.map(item= >item.name);//['amount']
              newScope = new Scope({
                parent:scope,
                params,
                block:false
              });
              break;
            case 'BlockStatement':
              newScope = new Scope({
                parent:scope,
                block:true // This is a block-level scope
              });
              break;
            case 'VariableDeclaration':
              node.declarations.forEach((variableDeclarator) = >{
                if(node.kind === 'let' || node.kind === 'const'){
                  addToScope(variableDeclarator,true);// This is a block-level declaration
                }else{ addToScope(variableDeclarator); }});break;
          }
          if(newScope){// Indicates that this node creates a new scope
            Object.defineProperty(node,'_scope', {value:newScope});
            scope = newScope;// The current scope equals the new scope}},leave(node){
          if(node._scope){// The current node creates a new scope
            scope = scope.parent;// return to the parent scope}}})});// Find out which external variables the current module depends on
  ast.body.forEach(statement= >{
    // See which identifiers are read by the current statement
    function checkForReads(node){
      if(node.type === 'Identifier') {// If it is an identifier, the variable is used or read
        //let definingScope = scope.findDefiningScope(node.name);
        // if(! DefiningScope){// If the defined scope cannot be found
        // Contains all variables declared by the current module and externally dependent on it
        statement._dependsOn[node.name]=true;// Add identifiers depending on which variables they depend on
        // }}}function checkForWrites(node){
      function addNode(node){
        // The current statement modifies the age variable statement._modifies. Name =true
        // This is a place to identify which variables are being modified, but does not store statements
        statement._modifies[node.name]=true;
      }
      // If the current node is an assignment,
      if(node.type === 'AssignmentExpression'){
        addNode(node.left,true);
      }else if(node.type === 'UpdateExpression'){
        addNode(node.argument,true);
      }
    }
    walk(statement,{
      enter(node){
        if(node._scope) scope = node._scope;
        checkForReads(node);// View the read identifier
        checkForWrites(node);// See which identifiers to modify
       
      },
      leave(node){
        if(node._scope) scope = scope.parent; }}); })}module.exports = analyse;
Copy the code

3.7 scope. Js

lib\ast\scope.js

class Scope{
    constructor(options={}){
        this.name = options.name;
        this.parent = options.parent;// The parent scope of this scope
        this.names = options.params||[];// hold the declared variables in this scope
        this.isBlockScope = !! options.block;// Indicates whether the current scope is a block-level scope
    }
    / * * *@param {*} Name Added variable *@param {*} IsBlockDeclaration Whether this variable is a block-level declaration let const * var is not a block-level declaration */
    add(name,isBlockDeclaration){
        //{} var
        // The current scope is a block-level scope, and the currently declared variables are not block-level declared variables
        if(! isBlockDeclaration &&this.isBlockScope){
            // This is a var or function declaration, and this is a block-level scope, so we need to scale it up
            // add it to the parent scope
            this.parent.add(name,isBlockDeclaration);
        }else{
            this.names.push(name); }}// Find the scope that defines this variable
    findDefiningScope(name){
        if(this.names.includes(name)){// See if the variable exists in your scope
            return this;
        }else if(this.parent){// If the current scope has a parent scope, the parent scope will look for it
            return this.parent.findDefiningScope(name);
        }
        return null; }}module.exports = Scope;
Copy the code

3.8 walk. Js

lib\ast\walk.js

function walk(ast, { enter, leave }) {
    visit(ast, null, enter, leave);
}

function visit(node, parent, enter, leave) {
    if (enter) {// If the Enter method is provided, it takes the current node as an argument
        enter(node, parent);
    }
    let childKeys = Object.keys(node).filter(key= > typeof node[key] === 'object');
    childKeys.forEach(key= > {
        let value = node[key];
        if (Array.isArray(value)) {
            value.forEach(value= > {
                visit(value, node, enter, leave);
            });
        } else if(value && value.type) { visit(value, node, enter, leave); }});if(leave) { leave(node, parent); }}module.exports = walk;
Copy the code

The warehouse address