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:
- Getting started with babel plug-ins and writing babel plugin Lyn plug-ins
- 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
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.