Last Surprise Lyn
  1. 1 Last Surprise Lyn
  2. 2 Time Bomb Veela
  3. 3 The Night We Stood Lyn
  4. 4 Warcry mpi
  5. 5 Life Will Change Lyn
  6. 6 Flower Of Life 发热巫女
  7. 7 Libertus Chen-U
  8. 8 One Last You Jen Bird
  9. 9 かかってこいよ NakamuraEmi
  10. 10 Hypocrite Nush
  11. 11 Quiet Storm Lyn
2019-02-05 19:20:32

ELEME组件库源码要点分析

最近有计划写一个自己的组件库,一是练习一些通用业务逻辑的写法,二是为自己以后的项目组件做个积累。于是大概阅读了下比较有名的两个组件库——element和Ant Design的源码。这篇文章就对element的一些值得学习的编码思路做个整理。

入口分析

看一个前端项目的基本结构,首先看package.json,其中的scripts字段又是重中之重,就先从这里开始。

 "scripts": {
    "bootstrap": "yarn || npm i",
    "build:file": "node build/bin/iconInit.js & node build/bin/build-entry.js & node build/bin/i18n.js & node build/bin/version.js",
    "build:theme": "node build/bin/gen-cssfile && gulp build --gulpfile packages/theme-chalk/gulpfile.js && cp-cli packages/theme-chalk/lib lib/theme-chalk",
    "build:utils": "cross-env BABEL_ENV=utils babel src --out-dir lib --ignore src/index.js",
    "build:umd": "node build/bin/build-locale.js",
    "clean": "rimraf lib && rimraf packages/*/lib && rimraf test/**/coverage",
    "deploy:build": "npm run build:file && cross-env NODE_ENV=production webpack --config buildack.demo.js && echo element.eleme.io>>examples/element-ui/CNAME",
    "dev": "npm run bootstrap && npm run build:file && cross-env NODE_ENV=development webpack-dev-server --config buildack.demo.js & node build/bin/template.js",
    "dev:play": "npm run build:file && cross-env NODE_ENV=development PLAY_ENV=true webpack-dev-server --config buildack.demo.js",
    "dist": "npm run clean && npm run build:file && npm run lint && webpack --config buildack.conf.js && webpack --config buildack.common.js && webpack --config buildack.component.js && npm run build:utils && npm run build:umd && npm run build:theme",
    "i18n": "node build/bin/i18n.js",
    "lint": "eslint src/**/* test/**/* packages/**/* build/**/* --quiet",
    "pub": "npm run bootstrap && sh build/git-release.sh && sh build/release.sh && node build/bin/gen-indices.js && sh build/deploy-faas.sh",
    "test": "npm run lint && npm run build:theme && cross-env CI_ENV=/dev/ BABEL_ENV=test karma start test/unit/karma.conf.js --single-run",
    "test:watch": "npm run build:theme && cross-env BABEL_ENV=test karma start test/unit/karma.conf.js"
  }

icon管理

通过build:file命令,会首先执行build/bin/iconInit.js,该脚本会获取packages/theme-chalk/src/icon.scss下的icon相关scss代码,通过正则匹配获取class中icon名统一生成icon.json文件,记录了项目中的所有icon。

进入到theme-chalk这个文件夹中也可以发现,这也是一个单独的小项目,由gulp和scss组成,目的在于提供给eleme单独管理样式管理能力。

入口文件生成自动化

build:file命令中进行的第二个操作就是build-entry,这个文件的作用就是根据组件库中的单个组件导出自动生成一个index.js集成所有导出,方便在组件库更新时实现入口文件自动化更新。实现上主要是通过json-templater这个库,将处理过后的字符串转化为JS代码。

国际化和版本管理

build:file集成的最后两个操作就是bin目录下i18n.jsversion.js的执行,前者根据国际化JSON配置生成不同语言版本的首页,后者生成版本记录JSON。

样式管理

先前也提到了组件默认样式都写在theme-chalk文件夹下,样式和组件的整合工作就靠build:theme实现。首先执行bin/gen-cssfile.js,根据components.json获取所有组件名,遍历组件名写入导入样式语句将theme-chalk内对应样式都整合到index.scss中,接着利用gulp对scss文件做一些css压缩等常规处理,最终样式文件生成到./lib下。

Webpack模式

开发模式下,分为devdev:play两种情况,使用的Webpack配置文件为webpack.demo.js,两者通过环境变量区分,具体配置上也只是入口文件不同。play模式下会启动一个空白页,我的理解是在开发时在这个空白页引入需要编辑的组件查看效果。非play模式下会启动一个集成所有组件的预览页,适合查看已开发完成组件的效果。

生产环境下,会通过webpack.conf.jswebpack.common.js打包umdcommonjs规范的代码各一份,并通过webpack.component.js对各个组件分开打包一份支持按需引用。

通用工具分析

项目组件主体部分位于packages文件夹下,其中各个组件通用工具函数被抽出到src/utilssrc/mixins下,这里选几个比较重要的谈谈。

popup

这个模块负责管理模态弹出框,在组件里通过mixin 的形式引用。核心思路的简单实现如下。

import PopupManager from 'element-ui/src/utils/popup/popup-manager';

{
    props: {
        visible: {
            type: Boolean,
            default: false
        }
    },
    beforeMount() {
        this._popupId = 'popup-' + idSeed++;
        PopupManager.register(this._popupId, this);
    },
    beforeDestroy() {
        PopupManager.deregister(this._popupId);
    },
    watch: {
        visible(val) {
            if (val) {
                if (!this.rendered) {
                    Vue.nextTick(() => {
                        this.open();
                    });
                } else {
                    this.open();
                }
            } else {
                this.close();
            }
        }   
    },
    methods: {
        open() {
            PopupManager.openModal(this._popupId);
        }
        close() {
            PopupManager.closeModal(this._popupId);
        }
    }
}

简单来说,就是通过watch一个标志变量决定是打开还是关闭弹窗,并将细节处理代理到PopupManager上,PopupManager进一步负责真实模态窗dom的生命周期,id记录等。

同理的,除了popup负责的模态窗管理外,还有vue-popper负责的定位弹出框管理,原理也是一样的,只是将PopupManger的实现替换为了三方库popper

emitter

这是eleme内部使用的跨组件通信机制,在由一系列小组件封装成的完整组件中很有用,避免了prop drilling。原理就是利用了vue组件父子关系明确的特点递归的处理事件。

function broadcast(componentName, eventName, params) {
  this.$children.forEach(child => {
    var name = child.$options.componentName;

    if (name === componentName) {
      child.$emit.apply(child, [eventName].concat(params));
    } else {
      broadcast.apply(child, [componentName, eventName].concat([params]));
    }
  });
}
export default {
  methods: {
    dispatch(componentName, eventName, params) {
      var parent = this.$parent || this.$root;
      var name = parent.$options.componentName;

      while (parent && (!name || name !== componentName)) {
        parent = parent.$parent;

        if (parent) {
          name = parent.$options.componentName;
        }
      }
      if (parent) {
        parent.$emit.apply(parent, [eventName].concat(params));
      }
    },
    broadcast(componentName, eventName, params) {
      broadcast.call(this, componentName, eventName, params);
    }
  }
};

小结

以上是我认为该组件库从宏观上看值得注意的点,当然各个组件实现细节肯定也有很多值得学习的地方就不在这里赘述了。

-- EOF --

添加在分类「 前端开发 」下,并被添加 「Vue」 标签。