ElementUi按需加载浅析

97 阅读3分钟

项目使用

import Button from 'element-ui'
import {Dialog,Select} from 'element-ui'
Vue.use(Dialog)
Vue.component(Button.name, Button)
Vue.component(Select.name, Select)

通过在线网站(AST explorer)转化成ast如下:

image.png

elementui正是通过babel-plugin-component这个插件处理ImportDeclaration和``` CallExpression

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

var _require = require('@babel/helper-module-imports'), addSideEffect = _require.addSideEffect, addDefault = _require.addDefault;

var resolve = require('path').resolve;

var isExist = require('fs').existsSync;

var cache = {}; var cachePath = {}; var importAll = {};

function core(defaultLibraryName) { return function (_ref) { var types = _ref.types; var specified; var libraryObjs; var selectedMethods; var moduleArr;

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

    function importMethod(methodName, file, opts) {
        if (!selectedMethods[methodName]) {
            var options;
            var path;

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

            options = options || opts; // 配置文件中的参数 {libraryName: "element-ui", styleLibraryName: "theme-chalk"}
            var _options = options,
                _options$libDir = _options.libDir,
                libDir = _options$libDir === void 0 ? 'lib' : _options$libDir,//没有设置libDir默认是lib
                _options$libraryName = _options.libraryName,
                libraryName = _options$libraryName === void 0 ? defaultLibraryName : _options$libraryName,//element-ui
                _options$style = _options.style,
                style = _options$style === void 0 ? true : _options$style,
                styleLibrary = _options.styleLibrary,
                _options$root = _options.root,
                root = _options$root === void 0 ? '' : _options$root,
                _options$camel2Dash = _options.camel2Dash,
                camel2Dash = _options$camel2Dash === void 0 ? true : _options$camel2Dash;
            var styleLibraryName = options.styleLibraryName;
            var _root = root;
            var isBaseStyle = true;
            var modulePathTpl;
            var styleRoot;
            var mixin = false;
            var ext = options.ext || '.css'; // css默认扩展名是css

            if (root) {
                _root = "/".concat(root);
            }
            //methodName是Button if成立
            if (libraryObjs[methodName]) {
                path = "".concat(libraryName, "/").concat(libDir).concat(_root); //elementui/lib

                if (!_root) {
                    importAll[path] = true;
                }
            } else {
                path = "".concat(libraryName, "/").concat(libDir, "/").concat(parseName(methodName, camel2Dash));
            }

            var _path = path;
            selectedMethods[methodName] = addDefault(file.path, path, {
                nameHint: methodName
            }); // addDefault是用来创建 ImportDeclaration 节点的

            if (styleLibrary && _typeof(styleLibrary) === 'object') {
                styleLibraryName = styleLibrary.name;
                isBaseStyle = styleLibrary.base;
                modulePathTpl = styleLibrary.path;
                mixin = styleLibrary.mixin;
                styleRoot = styleLibrary.root;
            }
            // styleLibraryName这里我们指定的是theme-chalk
            if (styleLibraryName) {
                if (!cachePath[libraryName]) {
                    var themeName = styleLibraryName.replace(/^~/, '');
                    cachePath[libraryName] = styleLibraryName.indexOf('~') === 0 ? resolve(process.cwd(), themeName) : "".concat(libraryName, "/").concat(libDir, "/").concat(themeName);
                }

                if (libraryObjs[methodName]) {
                    /* istanbul ingore next */
                    if (cache[libraryName] === 2) {
                        throw Error('[babel-plugin-component] If you are using both' + 'on-demand and importing all, make sure to invoke the' + ' importing all first.');
                    }

                    if (styleRoot) {
                        path = "".concat(cachePath[libraryName]).concat(styleRoot).concat(ext);
                    } else {
                        //element-ui/lib/theme-chalk/index.css
                        path = "".concat(cachePath[libraryName]).concat(_root || '/index').concat(ext);
                    }

                    cache[libraryName] = 1;
                } else {
                    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;
                    }
                }

                addDefault(file.path, path, {
                    nameHint: methodName
                });
            } else {
                if (style === true) {
                    addSideEffect(file.path, "".concat(path, "/style").concat(ext));
                } else if (style) {
                    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 {
        visitor: {
            Program: function Program() { // 在这里做一些初始化操作,用来保存信息
                specified = Object.create(null);
                libraryObjs = Object.create(null);
                selectedMethods = Object.create(null);
                moduleArr = Object.create(null);
            },
            // ImportDeclaration -> import { Button,Select } from 'element-ui'
            ImportDeclaration: function ImportDeclaration(path, _ref2) {
                var opts = _ref2.opts;
                var node = path.node;
                var value = node.source.value; // 这里是 "element-ui" 如果 import xxx from 'vue' 那么就是value值是vue
                var result = {};

                if (Array.isArray(opts)) {
                    result = opts.find(function (option) {
                        return option.libraryName === value;
                    }) || {};
                }

                var libraryName = result.libraryName || opts.libraryName || defaultLibraryName;

                if (value === libraryName) {
                    // node.specifiers -> 这里就代表了我们导入的Button和Select
                    // ImportSpecifier 和 ImportDefaultSpecifier
                    node.specifiers.forEach(function (spec) {
                        if (types.isImportSpecifier(spec)) { // ImportDefaultSpecifier 有local和imported
                            specified[spec.local.name] = spec.imported.name; // {'Button':'Button'}
                            moduleArr[spec.imported.name] = value;  // {'Button':'elementui'}
                        } else { // ImportSpecifier只有local
                            libraryObjs[spec.local.name] = value; // {'Button':'elementui'}
                        }
                    });

                    if (!importAll[value]) {
                        path.remove();  // 移除节点
                    }
                }
            },
            // Vue.use()  Vue.component(Button.name, Button)
            // use()
            CallExpression: function CallExpression(path, state) {
                var node = path.node;
                var file = path && path.hub && path.hub.file || state && state.file;
                var name = node.callee.name; // 如果是Vue.use()的话 name就是undefined  use() 的话name才有值

                if (types.isIdentifier(node.callee)) {
                    if (specified[name]) {
                        node.callee = importMethod(specified[name], file, state.opts);
                    }
                } else {
                    node.arguments = node.arguments.map(function (arg) {
                        var argName = arg.name;

                        if (specified[argName]) { // 找到之前存起来的 Button
                            return importMethod(specified[argName], file, state.opts);
                        } else if (libraryObjs[argName]) {
                            return importMethod(argName, file, state.opts);
                        }

                        return arg;
                    });
                }
            },
            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);
                }
            },
            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);
                }
            },
            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);
                    }
                });
            },
            Property: function Property(path, state) {
                var node = path.node;
                buildDeclaratorHandler(node, 'value', path, state);
            },
            VariableDeclarator: function VariableDeclarator(path, state) {
                var node = path.node;
                buildDeclaratorHandler(node, 'init', path, state);
            },
            LogicalExpression: function LogicalExpression(path, state) {
                var node = path.node;
                buildExpressionHandler(node, ['left', 'right'], path, state);
            },
            ConditionalExpression: function ConditionalExpression(path, state) {
                var node = path.node;
                buildExpressionHandler(node, ['test', 'consequent', 'alternate'], path, state);
            },
            IfStatement: function IfStatement(path, state) {
                var node = path.node;
                buildExpressionHandler(node, ['test'], path, state);
                buildExpressionHandler(node.test, ['left', 'right'], path, state);
            }
        }
    };
};

};

module.exports = core("element-ui")

1 找到引入 element-ui 的类型为 ImportDeclaration 节点,并将其缓存下来。同时删除ast中对应的节点。 在遍历到 CallExpression 类型节点的时候,之后创建新的 ImportDeclaration 节点, 用于之后加载对应的 js 与 css 文件。