Principle analysis of on-demand loading

Posted by TheVoice on Wed, 09 Feb 2022 09:13:07 +0100

brief introduction

Understand the basic knowledge of Babel plug-in and the internal principle of on-demand loading. I'm no longer afraid of the interviewer asking me the implementation principle of on-demand loading.

import { Button } from 'element-ui'

How did it become

var Button = require('element-ui/lib/button.js')
require('element-ui/lib/theme-chalk/button.css')

In order to find the answer, it is divided into two steps, which is also the process of self-study:

  1. Getting started with babel plug-ins and writing babel plugin Lyn plug-ins
  2. Interpret the Babel plugin component source code and find the answer from the source code

Getting started with babel plug-in

In this step, we write a Babel plugin Lyn plug-in. The purpose of this step is:

  • Understand what the babel plug-in does
  • Learn to analyze AST syntax tree
  • Learn to use basic API s
  • Can write a simple plug-in to do basic code conversion

With the above foundation, we can try to read the Babel plugin component source code and find the answer we want from the source code

Brief introduction

Babel is a JavaScript compiler, which is a conversion compiler from source code to source code. You provide Babel with some JavaScript code. Babel changes these codes as required, and then returns them to your newly generated code.

The process of code conversion (change) is completed with the help of AST (abstract syntax tree). The purpose of code conversion is achieved by changing the ast node information. In fact, here we can simply answer how the code conversion mentioned in the goal is completed?, In fact, Babel reads our source code, converts it into ast, analyzes ast, changes some node information of AST, and then generates new code to complete the conversion process. Specifically, how to change the node information needs to find the answer in Babel plugin component source code

In Babel's world, when we want to change a node, we need to access (intercept) the node. The visitor mode is adopted here. The visitor is a cross language mode for AST traversal. In addition, it defines an object and a method for obtaining specific nodes in the tree structure. These nodes are actually AST nodes, which can be used in AST Explorer To view the AST information of the code, which we will use many times when writing the code

babel-plugin-lyn

Next, write your own plug-in

Initialize project directory

mkdir babel-plugin && cd babel-plugin && npm init -y

New plug-in directory

In the node of the project_ Create a new folder in the modules directory as your plug-in directory

mkdir -p node_modules/babel-plugin-lyn

Create a new index in the plug-in directory js

touch index.js

Create JS code that needs to be processed

Create index. In the root directory of the project JS, write the following code

let a = 1
let b = 1

Quite simply, we need to convert it into:

const aa = 1
const bb = 1

Next, write the plug-in

babel-plugin-lyn/index.js

Basic structure
// The function will have a babelTypes parameter, and we will structure the types inside
// Some of its methods need to be used in the code. Please refer to the specific meaning of the method 
// https://babeljs.io/docs/en/next/babel-types.html
module.exports = function ({ types: bts }) {
  // Return an object with a visitor, which is the rule. Then write the method to get each node in the visitor
  return {
    visitor: {
        ...
    }
  }
}
Analyze source code

After having the basic structure of the plug-in, we need to analyze our code and what it looks like in AST

AST Explorer

As shown in the figure below:

Click the place that needs to be changed with the mouse. For example, if we want to change the quantity name, after clicking, we will see that the AST tree on the right expands and highlights a part. The highlighted part is the AST node of variable a we want to change. We know that it is a node of Identifier type, so we write an Identifier method in visitor

module.exports = function ({ types: bts }) {
    return {
        visitor: {
            /**
             * Responsible for processing all AST nodes whose node type is Identifier
             * @param {*} path AST The path information of the node can be simply understood as containing various information of the AST node
             * @param {*} state There is a very important state Opts, yes Configuration items in babelrc
            */
            Identifier (path, state) {
                // Node information
                const node = path.node
                // Get the name attribute from the node information, that is, a and b
                const name = node.name
                // If there is a name attribute in the configuration item, path.path is used node. Replace the value of name with the value in the configuration item
                if (state.opts[name]) {
                    path.node.name = state.opts[name]
                }
            }
        }
    }
}

Here we use the configuration information of the plug-in. Next, we will Write the configuration information of the plug-in in babelrc

.babelrc
{
  "plugins": [
    [
      "lyn",
      {
        "a": "aa",
        "b": "bb"
      }
    ]
  ]
}

Is this configuration item familiar? Similar to that of babel plugin component, lyn indicates the name of the babel plugin, and the following object is our configuration item

Output results
First install Babel cli

One thing to note here is that before installing Babel cli, back up the plug-ins we wrote, otherwise our plug-in directory will be deleted when performing the following installation. The reason is not deep. It should be that our plug-in is not a valid npm package, so it will be cleared

npm i babel-cli -D
compile
npx babel index.js

The following output is obtained:

let aa = 1;
let bb = 1;

It shows that our plug-in has come into effect, and the idea just now is no problem. In fact, the translation code is just by changing the information of AST node

let -> const

We have just finished the translation of variables, and then change the let keyword into const

According to the method just now, we need to change the keyword let, move the cursor over the let, and find that the highlighted part of AST Tree has changed. We can see that the ast node type of let is VariableDeclaration, and what we need to change is the kind attribute. OK, start writing code

module.exports = function ({ types: bts }) {
    return {
        visitor: {
            Identifier (path, state) {
                ...
            },
            // Processing variable declaration keywords
            VariableDeclaration (path, state) {
                // I didn't read it from the configuration file this time. Let's make a simple change directly
                path.node.kind = 'const'
            }
        }
    }
}
compile
npx babel index.js

The following output is obtained:

const aa = 1;
const bb = 1;

Here, the first stage of our introduction is over. Does it feel very simple?? Yes, this introductory example is really simple, but really writing a Babel plug-in for business and the AST and compilation principles involved are very complex. However, this introductory example can support us to analyze the source code principle of Babel plugin component plug-in.

Complete code
// The function will have a babelTypes parameter, and we will structure the types inside
// Some of its methods need to be used in the code. Please refer to the specific meaning of the method 
// https://babeljs.io/docs/en/next/babel-types.html
module.exports = function ({ types: bts }) {
  // Return an object with a visitor, which is the rule. Then write the method to get each node in the visitor
  return {
    visitor: {
      /**
       * Responsible for processing all AST nodes with node type Identifier
       * @param {*} path AST The path information of the node can be simply understood as containing various information of the AST node
       * @param {*} state There is a very important state Opts, yes Configuration items in babelrc
       */
      Identifier (path, state) {
        // Node information
        const node = path.node
        // Get the name attribute from the node information, that is, a and b
        const name = node.name
        // If there is a name attribute in the configuration item, path.path is used node. Replace the value of name with the value in the configuration item
        if (state.opts[name]) {
          path.node.name = state.opts[name]
        }
      },
      // Processing variable declaration keywords
      VariableDeclaration (path, state) {
        // I didn't read it from the configuration file this time. Let's make a simple change directly
        path.node.kind = 'const'
      }
    }
  }
}

Babel plugin component source code analysis

Target analysis

Before reading the source code, let's first analyze our goal. If we read with the goal, the effect will be better

source code

// Global import
import ElementUI from 'element-ui'
Vue.use(ElementUI)
// On demand import
import { Button, Checkbox } from 'element-ui'
Vue.use(Button)
Vue.component(Checkbox.name, Checkbox)

The above are the two ways we use the element UI component library: global import and on-demand import

Object code

// Global import
var ElementUI = require('element-ui/lib')
require('element-ui/lib/theme-chalk/index.css')
Vue.use(ElementUI)
// On demand import
var Button = require('element-ui/lib/button.js')
require('element-ui/lib/theme-chalk/button.css')
var Checkbox = require('element-ui/lib/checkbox.js')
require('element-ui/lib/theme-chalk/checkbox.css')
Vue.use(Button)
Vue.component(Checkbox.name, Checkbox)

The above is the source code and the translated object code. We can copy them to AST Explorer View the information of AST Tree in and analyze it

Global import

As can be seen from the above figure, these two statements are composed of two types of nodes in total. The node of ImportDeclaration corresponding to import, Vue Use (elementui) corresponds to a node of type ExpressionStatement

You can see that the import elementui from 'element UI' corresponds to the AST, and the element UI after from corresponds to the source Value, and the node type is StringLiteral

The ElementUI in import ElementUI from 'element UI' corresponds to the node of ImportDefaultSpecifier type. It is a default import, and the variable corresponds to the name attribute of the identifier node

Vue. Use (element UI) is a declarative statement, corresponding to the node of ExpressionStatement. You can see that the parameter element UI is placed in the arguments section

On demand import

You can see that the body has three child nodes, one ImportDeclaration and two expressionstatements, which correspond to our code one by one

In the import statement, for the part after from, the above global is the same. They are all in the source, which is a Literal node

You can see that the content after import has changed. The above global import is an ImportDefaultDeclaration node. The on-demand load here is an ImportDeclaration node, and the imported content is placed in the specifiers object. Each component (Button and Checkbox) is an ImportSpecifier, which defines the Identifier of imported and local, Our variable names (Button, Checkbox) are placed on the name attribute

The remaining Vue Use (button) and Vue Component (checkbox. Name, checkbox) is similar to the global introduction above, with one difference being Vue The arguments of component (checkbox. Name, checkbox) has two elements

After the basic introduction and the above analysis of AST, we can guess what happened during the conversion from source code to object code. In fact, we set the response method (node type) on the visitor object, and then deal with the nodes that meet the requirements, Change the corresponding attribute on the node to the value of the response on the object code, and copy both the source code and the object code to AST Explorer You will find that the difference (change) between the corresponding nodes is what Babel plugin component does. Next, we go to the source code to find the answer.

Source code analysis

Directly in the project just now

npm i babel-plugin-component -D

Install the Babel plugin component. After the installation is completed, install it on the node_modules Directory Find Babel plugin component directory

Check the code and check AST Explorer and log at any time

.babelrc

{
  "plugins": [
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]
  ]
}

Entrance, index js

// The default is the on-demand plug-in for the element UI component library
module.exports = require('./core')('element-ui');

Core js

Source code reading tips

  • What is the purpose of reading the source code and what kind of problems to solve
  • Be sure to have relevant basic knowledge, such as the introduction to babel above, know the location of the entrance in the visitor, and find those methods to read in the visitor
  • In the process of reading, you must work hard, write notes and log, which will help improve your thinking
  • Read this source code, be sure to use AST Explorer to analyze and compare our source code and object code
  • Almost every line of the following source code is annotated. You can compare the next set of source code according to the steps. You can't understand it once, read it twice, and read the book three times. Its meaning is self-evident. Really, of course, there are places you don't understand in the process of reading, which need to be checked

/**
 * Judge the type of obj
 * @param {*} obj 
 */
function _typeof(obj) { 
  if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { 
    _typeof = function _typeof(obj) { 
      return typeof obj; 
    }; 
  } else { 
    _typeof = function _typeof(obj) { 
      return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; 
    }; 
  } 
  return _typeof(obj); 
}

// Provides methods for generating import nodes
var _require = require('@babel/helper-module-imports'),
  addSideEffect = _require.addSideEffect,
  addDefault = _require.addDefault;

// node.js built-in module, processing path information
var resolve = require('path').resolve;

// node.js built-in module to judge whether the file exists
var isExist = require('fs').existsSync;

// Cache variable, cache[libraryName] = 1 or 2
var cache = {};
// Style path of cache style library, cachePath[libraryName] = ""
var cachePath = {};
// Importall ['element UI / lib '] = true, indicating that there is a default import
var importAll = {};

module.exports = function core(defaultLibraryName) {
  return function (_ref) {
    // babelTypes provides a series of methods for use. Official website address: https://babeljs.io/docs/en/next/babel-types.html
    var types = _ref.types;
    // Store all importspecifiers, i.e. components imported on demand. Specified = {button: 'button', checkbox: 'checkbox'}
    var specified;
    // Global repository of 'obramui' {obramentui '}
    var libraryObjs;
    // Storing methods (components) that have been introduced (processed),
    // selectedMethods = {
    //   ElementUI: { type: 'Identifier', name: '_ElementUI' },
    //   Button: { type: 'Identifier', name: '_Button' },
    //   Checkbox: { type: 'Identifier', name: '_Checkbox' }
    // }
    var selectedMethods;
    // The corresponding relationship between the imported module and the library, modulearr = {button: 'element UI', checkbox: 'element UI'}
    var moduleArr;

    // Convert hump naming to hyphen naming
    function parseName(_str, camel2Dash) {
      if (!camel2Dash) {
        return _str;
      }

      var str = _str[0].toLowerCase() + _str.substr(1);

      return str.replace(/([A-Z])/g, function ($1) {
        return "-".concat($1.toLowerCase());
      });
    }

    /**
     * This method is responsible for generating some AST nodes. The information of these nodes is based on a pile of configuration items. This pair of configuration items are telling the path information of each component of the AST node,
     * For example, 'element UI / lib / button JS' and 'element UI / lib / theme talk / button css'
     * @param {*} methodName Button,element-ui
     * @param {*} file Drag the object information you don't want to see
     * @param {*} opts .babelrc Configuration item
     */
    function importMethod(methodName, file, opts) {
      // If there are no Butotn and element UI in selectedMethods, enter if, otherwise directly return selectedMethods[methodName], indicating that the method (component) has been processed
      if (!selectedMethods[methodName]) {
        var options;
        var path;

        // Never mind
        if (Array.isArray(opts)) {
          options = opts.find(function (option) {
            return moduleArr[methodName] === option.libraryName || libraryObjs[methodName] === option.libraryName;
          }); // eslint-disable-line
        }

        /**
         * Here are a bunch of configuration items
         */
        // Configuration passed in
        options = options || opts;
        var _options = options,
          // Configured libDir
          _options$libDir = _options.libDir,
          // If it is not configured, it defaults to lib, / element UI / lib / button This is how Lib in JS comes from
          libDir = _options$libDir === void 0 ? 'lib' : _options$libDir,
          // Component library, element UI
          _options$libraryName = _options.libraryName,
          // Component library name
          libraryName = _options$libraryName === void 0 ? defaultLibraryName : _options$libraryName,
          // Style, boolean type, here is undefined
          _options$style = _options.style,
          // style is true by default and can also be provided by the user. It works when the user does not provide the styleLibraryName option
          style = _options$style === void 0 ? true : _options$style,
          // undefiend
          styleLibrary = _options.styleLibrary,
          // undefined
          _options$root = _options.root,
          // ''
          root = _options$root === void 0 ? '' : _options$root,
          _options$camel2Dash = _options.camel2Dash,
          camel2Dash = _options$camel2Dash === void 0 ? true : _options$camel2Dash;
        // 'theme chalk 'in configuration item
        var styleLibraryName = options.styleLibraryName;
        // ''
        var _root = root;
        var isBaseStyle = true;
        var modulePathTpl;
        var styleRoot;
        var mixin = false;
        // Suffix XX css
        var ext = options.ext || '.css';

        if (root) {
          _root = "/".concat(root);
        }

        if (libraryObjs[methodName]) {
          // Import elementui by default, path = 'element UI / lib'
          path = "".concat(libraryName, "/").concat(libDir).concat(_root);

          if (!_root) {
            // By default, the record is marked with path as true in importAll
            importAll[path] = true;
          }
        } else {
          // On demand import, path = 'element UI / lib / button'
          path = "".concat(libraryName, "/").concat(libDir, "/").concat(parseName(methodName, camel2Dash));
        }

        // 'element-ui/lib/button'
        var _path = path;
        /**
         * selectedMethods['Button'] = { type: Identifier, name: '_Button' }
         * addDefault I'm responsible for adding the visitor The pile of things callexpreesion said,
         * It is mainly responsible for VAR button = require ('element UI / lib / button. JS'),
         * This is a guess, mainly because I didn't find the documentation in this regard
         */
        selectedMethods[methodName] = addDefault(file.path, path, {
          nameHint: methodName
        });

        /**
         * Next is processing styles
         */
        if (styleLibrary && _typeof(styleLibrary) === 'object') {
          styleLibraryName = styleLibrary.name;
          isBaseStyle = styleLibrary.base;
          modulePathTpl = styleLibrary.path;
          mixin = styleLibrary.mixin;
          styleRoot = styleLibrary.root;
        }

        // Stylelibraryname = 'theme chalk'. If this option is configured, the default method will be used to enter else to view
        if (styleLibraryName) {
          // Cache style library path
          if (!cachePath[libraryName]) {
            var themeName = styleLibraryName.replace(/^~/, '');
            // cachePath['element-ui'] = 'element-ui/lib/theme-chalk'
            cachePath[libraryName] = styleLibraryName.indexOf('~') === 0 ? resolve(process.cwd(), themeName) : "".concat(libraryName, "/").concat(libDir, "/").concat(themeName);
          }

          if (libraryObjs[methodName]) {
            // Default import
            /* istanbul ingore next */
            if (cache[libraryName] === 2) {
              // Prompt message, which means that if your project has both default import and on-demand loading, ensure that the default import precedes on-demand loading
              throw Error('[babel-plugin-component] If you are using both' + 'on-demand and importing all, make sure to invoke the' + ' importing all first.');
            }

            // Default exported style library path: path = 'element UI / lib / theme talk / index css'
            if (styleRoot) {
              path = "".concat(cachePath[libraryName]).concat(styleRoot).concat(ext);
            } else {
              path = "".concat(cachePath[libraryName]).concat(_root || '/index').concat(ext);
            }

            cache[libraryName] = 1;
          } else {
            // Import on demand, which is not equal to 1 here, is the case of default import + import on demand. Basically, no one will use it like this
            if (cache[libraryName] !== 1) {
              /* if set styleLibrary.path(format: [module]/module.css) */
              var parsedMethodName = parseName(methodName, camel2Dash);

              if (modulePathTpl) {
                var modulePath = modulePathTpl.replace(/\[module]/ig, parsedMethodName);
                path = "".concat(cachePath[libraryName], "/").concat(modulePath);
              } else {
                path = "".concat(cachePath[libraryName], "/").concat(parsedMethodName).concat(ext);
              }

              if (mixin && !isExist(path)) {
                path = style === true ? "".concat(_path, "/style").concat(ext) : "".concat(_path, "/").concat(style);
              }

              if (isBaseStyle) {
                addSideEffect(file.path, "".concat(cachePath[libraryName], "/base").concat(ext));
              }

              cache[libraryName] = 2;
            }
          }

          // Add style import, require ('elememt UI / lib / theme chalk / button. CSS'). Here is also a guess. To be honest, the addDefault method looks a little confused. If only there were documents
          addDefault(file.path, path, {
            nameHint: methodName
          });
        } else {
          if (style === true) {
            // '/element-ui/style.css, here is the default. ext can be provided by the user and is also the default
            addSideEffect(file.path, "".concat(path, "/style").concat(ext));
          } else if (style) {
            // 'element UI / xxx, where the style is provided by the user 
            addSideEffect(file.path, "".concat(path, "/").concat(style));
          }
        }
      }

      return selectedMethods[methodName];
    }

    function buildExpressionHandler(node, props, path, state) {
      var file = path && path.hub && path.hub.file || state && state.file;
      props.forEach(function (prop) {
        if (!types.isIdentifier(node[prop])) return;

        if (specified[node[prop].name]) {
          node[prop] = importMethod(node[prop].name, file, state.opts); // eslint-disable-line
        }
      });
    }

    function buildDeclaratorHandler(node, prop, path, state) {
      var file = path && path.hub && path.hub.file || state && state.file;
      if (!types.isIdentifier(node[prop])) return;

      if (specified[node[prop].name]) {
        node[prop] = importMethod(node[prop].name, file, state.opts); // eslint-disable-line
      }
    }

    return {
      // The whole entrance of the program, familiar visitor
      visitor: {
        // Responsible for processing Program type nodes in AST
        Program: function Program() {
          // Initialize several previously defined variables as objects without prototype chains
          specified = Object.create(null);
          libraryObjs = Object.create(null);
          selectedMethods = Object.create(null);
          moduleArr = Object.create(null);
        },
        // Processing ImportDeclaration nodes
        ImportDeclaration: function ImportDeclaration(path, _ref2) {
          // Plug in configuration item in. babelrc
          var opts = _ref2.opts;
          // import xx from 'xx', ImportDeclaration node
          var node = path.node;
          // Import XX from 'element UI', node here source. Value is the repository name
          var value = node.source.value;
          var result = {};

          // Don't worry. If the configuration item is an array, find the configuration item of the library from the array
          if (Array.isArray(opts)) {
            result = opts.find(function (option) {
              return option.libraryName === value;
            }) || {};
          }

          // Library name, such as element UI
          var libraryName = result.libraryName || opts.libraryName || defaultLibraryName;

          // If the currently import ed library is the one we need to process, enter
          if (value === libraryName) {
            // Traverse node Specifiers, in which there are several importspecifiers, each of which is the component (method) we want to introduce
            node.specifiers.forEach(function (spec) {
              // Importspecifier is imported on demand. There is another default import, ImportDefaultSpecifier, such as ElementUI
              if (types.isImportSpecifier(spec)) {
                // Set components imported on demand, such as specified ['button '] ='button'
                specified[spec.local.name] = spec.imported.name;
                // Record which library the current component is imported from, such as modulearr ['button '] ='element UI'
                moduleArr[spec.imported.name] = value;
              } else {
                // Default import, libraryobjs ['elementui '] ='element UI'
                libraryObjs[spec.local.name] = value;
              }
            });

            // This node is not deleted for global import, which means all on-demand imports are deleted. This will be set in the importMethod method method
            if (!importAll[value]) {
              path.remove();
            }
          }
        },
        /**
         * This is very important. We will find that when using on-demand loading, if you just import it but don't use it, such as Vue Use (button), it won't pack, so here's it
         * To deal with this situation, only when the package you imported is actually used can it be really imported. Otherwise, it will not be deleted just now, and then various arguments will not be added to the node, such as:
         * {
         *   type: 'CallExpression',
         *   callee: { type: 'Identifier', name: 'require' },
         *   arguments: [ { type: 'StringLiteral', value: 'element-ui/lib' } ]
         * }
         * {
         *   type: 'CallExpression',
         *   callee: { type: 'Identifier', name: 'require' },
         *   arguments: [
         *    {
         *      type: 'StringLiteral',
         *      value: 'element-ui/lib/chalk-theme/index.css'
         *    }
         *   ]
         * }
         * {
         *    type: 'CallExpression',
         *    callee: { type: 'Identifier', name: 'require' },
         *    arguments: [ { type: 'StringLiteral', value: 'element-ui/lib/button' } ]
         * }
         * You can check the above by log ging. This format is very important because with this part of data, we know:
         * import {Button} from 'element-ui' Why
         * Get var button = require ('element UI / lib / button. JS')
         * And require ('element UI / lib / theme talk / button. CSS')
         *
         * @param {*} path 
         * @param {*} state 
         */
        CallExpression: function CallExpression(path, state) {
          // Vue.use(Button), CallExpression node
          var node = path.node;
          // A large drag of objects, do not want to see (do not need to see, waste your hair)
          var file = path && path.hub && path.hub.file || state && state.file;
          // The name attribute of callee is not involved here. A syntax like ElementUI(ok) will have this attribute, node callee. Name is ElementUI
          var name = node.callee.name;

          console.log('import method Pre treatment node: ', node)
          // Judge node Whether callee belongs to Identifier is not here, but a MemberExpression
          if (types.isIdentifier(node.callee)) {
            if (specified[name]) {
              node.callee = importMethod(specified[name], file, state.opts);
            }
          } else {
            // Parse node Arguments array. Each element is an Identifier, Vue Use or Vue Parameters of component
            node.arguments = node.arguments.map(function (arg) {
              // Parameter name
              var argName = arg.name;

              // 1. Here, a new Identifier will be generated and the attribute value of AST node will be changed
              // 2. On demand import or default import is determined in ImportDeclaration
              if (specified[argName]) {
                // Import on demand, such as: {type: "Identifier", name: "_Button"}, which is the JSON object representation of AST structure
                return importMethod(specified[argName], file, state.opts);
              } else if (libraryObjs[argName]) {
                // Default import, {type: "Identifier", name: "_ElementUI"}
                return importMethod(argName, file, state.opts);
              }

              return arg;
            });
          }
          console.log('import method Treated node: ', node)
        },
        /**
         * Don't pay too much attention to the latter few. It's not involved here. You can see what you're doing by looking at the literal amount 
         */
        // Process MemberExpression and change node Object object
        MemberExpression: function MemberExpression(path, state) {
          var node = path.node;
          var file = path && path.hub && path.hub.file || state && state.file;

          if (libraryObjs[node.object.name] || specified[node.object.name]) {
            node.object = importMethod(node.object.name, file, state.opts);
          }
        },
        // Processing assignment expressions
        AssignmentExpression: function AssignmentExpression(path, _ref3) {
          var opts = _ref3.opts;

          if (!path.hub) {
            return;
          }

          var node = path.node;
          var file = path.hub.file;
          if (node.operator !== '=') return;

          if (libraryObjs[node.right.name] || specified[node.right.name]) {
            node.right = importMethod(node.right.name, file, opts);
          }
        },
        // Array expression
        ArrayExpression: function ArrayExpression(path, _ref4) {
          var opts = _ref4.opts;

          if (!path.hub) {
            return;
          }

          var elements = path.node.elements;
          var file = path.hub.file;
          elements.forEach(function (item, key) {
            if (item && (libraryObjs[item.name] || specified[item.name])) {
              elements[key] = importMethod(item.name, file, opts);
            }
          });
        },
        // attribute
        Property: function Property(path, state) {
          var node = path.node;
          buildDeclaratorHandler(node, 'value', path, state);
        },
        // Variable declaration
        VariableDeclarator: function VariableDeclarator(path, state) {
          var node = path.node;
          buildDeclaratorHandler(node, 'init', path, state);
        },
        // Logical expression
        LogicalExpression: function LogicalExpression(path, state) {
          var node = path.node;
          buildExpressionHandler(node, ['left', 'right'], path, state);
        },
        // Conditional expression
        ConditionalExpression: function ConditionalExpression(path, state) {
          var node = path.node;
          buildExpressionHandler(node, ['test', 'consequent', 'alternate'], path, state);
        },
        // if statement
        IfStatement: function IfStatement(path, state) {
          var node = path.node;
          buildExpressionHandler(node, ['test'], path, state);
          buildExpressionHandler(node.test, ['left', 'right'], path, state);
        }
      }
    };
  };
};

summary

By reading the source code and log ging, we get the following information:

{
    type: 'CallExpression',
    callee: { type: 'Identifier', name: 'require' },
    arguments: [ { type: 'StringLiteral', value: 'element-ui/lib' } ]
}
{
    type: 'CallExpression',
    callee: { type: 'Identifier', name: 'require' },
    arguments: [
        {
          type: 'StringLiteral',
          value: 'element-ui/lib/chalk-theme/index.css'
        }
    ]
}
{
    type: 'CallExpression',
    callee: { type: 'Identifier', name: 'require' },
    arguments: [ { type: 'StringLiteral', value: 'element-ui/lib/button' } ]
}

In fact, this is part of the information of the changed AST. By comparing the display of the object code in the AST Tree, it will be found that the results are consistent, that is, the object code we need can be generated through the above AST information

The require keyword in the object code is callee, and the parameter in the require function is the arguments array

The above is all about the on-demand loading principle analysis.

link

Thank you for your likes, collections and comments. I'll see you next time.

When learning becomes habit, knowledge becomes commonsense. Scanning code concerns WeChat official account, learning and progress together. The article has been included in github , welcome to Watch and Star.

Topics: Javascript Front-end ECMAScript