ElementUI源码阅读——前言: 准备工作

  最近看到一句话 我觉得说的非常正确:“感觉现在的业务开发,如果不是很特殊的需求,基本都能在对应的组件库内找到组件使用,这样编写代码就成了调用组件,但是却隐藏了组件内的思想,因此弱化了编程能力,所以我想写这么个分析系列来鞭策自己深入分析组件的原理,提高代码阅读理解能力,我觉得一定要记下点什么来,如果只是看不动笔感觉很快就忘了,因此准备持续写这么个分析。”

所以看了这篇博主超级索尼对Element源码的系列分析【还没有看完,理解了Layout(布局)和Button(按钮)】

一、插入篇:element-pro 组件库介绍

定制element项目生成element-pro业务组件库

  • 项目构建基于[elementUI]源码结构,简化了项目,保留原始组件库构建功能。
  • 在原有Vue UI组件的基础上进行封装或者完全独立开发,更加贴近实际业务逻辑。
  • 默认在ElementUI组件上进行封装,同时支持引入其他Vue UI组件库。
  • 它现在用于快速搭建后台CRUD功能页面,适用restful API场景页面,包括增加、删除、修改、更新操作。同时支持自定义操作,比如弹出对话框、展示错误提示等。
  • 传送门:http://intranet.cn-beijing-6.element-pro.rjxaavlly3.elb.xiaomi.com/#/zh-CN/component/introduction
  • 组件内部所有form表单,都基于@femessage/el-form-renderer渲染。所以使用前需要全局引入并注册组件
import ElFormRenderer from '@femessage/el-form-renderer';

Vue.component('el-form-renderer', ElFormRenderer);

  因为最近把需要把参与到的两个项目中可复用的组件,抽出来放在element-pro这个库里,也是为了巩固我们的组件化开发以及封装组件库的思想。所以就入手了ElementUI源码阅读,我也觉得一定要记下点什么来,如果只是看不动笔感觉很快就忘啦呢,同时鞭策自己深入分析组件的原理,提高代码阅读理解能力。

二、正文:项目结构篇

在这里插入图片描述

众所周知,组件都在packages文件夹里面,这个文件夹下面,每一个文件夹对应一个组件,可以时不时的看个小组件。

  • build:放置webpack的配置文件。
  • examples:放置element api的页面文档。
  • packages:放置element的组件(css样式放置在这个目录下的theme-chalk下)。
  • src/directives:放置自定义指令。
  • src/locale:放置语言的配置文件。
  • src/mixins:放置组件用的混合文件。
  • src/transitions:放置动画配置文件。
  • src/utils:放置用到工具函数文件。
  • src/index.js:组件注册的入口文件。
  • test:测试文件。
  • types:这个文件里放了typescript的数据类,还没找到哪里用了这里的类

├── build    
│   ├── bin             # js指向文件,大量文件操作
│   ├── md-loader       # 自定义实现的md-loader,markdown语法解析loader
│   ├── config.js       # 功能的webpack选项配置,`alias``externals`等
│   ├── webpack.xxx.js  # webpack 配置文件
├── examples 
│   ├── components      # 文档站点搭建组件
│   ├── extension       # element chrome插件
│   ├── i18n            # 文档站点中用到国际化语言包
│   ├── pages           # 文档站点中不同页面对应的vue组件,不同语言生成不同vue组件
│   ├── entry.js        # 文档站点vue编译入口文件
│   ├── index.tpl       # 文档站点的index.html 模板
│   ├── route.config.js # 文档站点的路由生成
│   ├── nav.config.json # 文档站点的路由配置文件
├── lib                 # 组件打包后的产物目录
├── packages            # 每个组件对应一个文件,组件的具体实现(不包括样式)
│   ├── theme-chalk     # 默认主题样式文件
│   ├── alert           # alert组件源码
│   ├── ...             # 其它组件源码
├── src                 # 一些基础的dom操作库,工具库,本地化,指令, mixins等实现
│   ├── index.js        # 组件注册入口
│   ├── directives      # 自定义指令
│   ├── locale          # 内置国际化语言包及使用api
├── tests               # 测试用例
├── Makefile            # makefile
├── components.json     # 所有组件列表
...

三、项目样式篇

packages:放置element的组件(css样式放置在这个目录theme-chalk下)。

也众所周知,css样式放置packages目录theme-chalk
在这里插入图片描述
在这里插入图片描述
其中,mixins目录下的文件

config.scss

这个文件是源头,定义了一些命名空间等原始的值:

$namespace: 'el'; // 命名空间
$element-separator: '__'; // 元素之间的分隔符
$modifier-separator: '--'; // 修饰符分隔符
$state-prefix: 'is-'; // 状态前缀

mixin.scss

/* BEM
 -------------------------- */
@mixin b($block) {
  $B: $namespace+'-'+$block !global;
  .#{$B} {
    @content;
  }
}
@mixin e($element) {
  $E: $element !global;
  $selector: &;
  $currentSelector: "";
  @each $unit in $element {
    $currentSelector: #{$currentSelector + "." + $B + $element-separator + $unit + ","};
  }
  @if hitAllSpecialNestRule($selector) {
    @at-root {
      #{$selector} {
        #{$currentSelector} {
          @content;
        }
      }
    }
  } @else {
    @at-root {
      #{$currentSelector} {
        @content;
      }
    }
  }
}
@mixin m($modifier) {
  $selector: &;
  $currentSelector: "";
  @each $unit in $modifier {
    $currentSelector: #{$currentSelector + & + $modifier-separator + $unit + ","};
  }
  @at-root {
    #{$currentSelector} {
      @content;
    }
  }
}
  • b方法为例
@mixin b($block) {
  $B: $namespace+'-'+$block !global;

  .#{$B} {
    @content;
  }
}
  • !global 变量提升 将局部变量转换为全局变量,在其他函数体内也能访问到此变量

如果传入的 $blockalert,那么解析后就是:

@mixin b($block) {
  .el-alert {
    @content;
  }
}

也就是说b的作用就是把传进来的组件名拼接成 el命名空间的选择符。

#{$B} 是一种插值写法,最终解析出来的就是el-alert

  • #{}插值,可以通过 #{} 插值语法在选择器和属性名中使用 SassScript 变量
  • when
@mixin when($state) {
  @at-root {
    &.#{$state-prefix + $state} {
      @content;
    }
  }
}

这里是 @at-root 官网File: SASS_REFERENCE — Documentation by YARD 0.9.12 的解释: @at-root 指令可以将一条或者多条规则注册在文档的根目录,而不用限制在父选择器内。

  • 所以:@at-root将父级选择器直接暴力的改成根选择器

那么这段翻译过来就是:

&.is-center {
      @content;
    }

/* 以 alert 举例:*/
@include when(center) {
    justify-content: center;
  }
/* 最终:*/
.el-alert.is-center {
    justify-content: center;
}

所以总结一下,这个 whenmixin 就是给传入的类名字段加一个 is-前缀


还有类似me

四、支持typescript

声明文件在types/*.d.ts 文件中,每个组件对应一个文件。

比如alert.d.ts 文件:

  • 组件声明类都继承了ElementUIComponent类,
  • ElementUIComponent 类继承了Vue, 所以每个组件都能通过ts编译时检查。
import { ElementUIComponent } from './component'

export type AlertType = 'success' | 'warning' | 'info' | 'error'
export type AlertEffect = 'dark' | 'light'

/** Alert Component */
export declare class ElAlert extends ElementUIComponent {
  /** Title */
  title: string

  /** Component type */
  type: AlertType

  /** Descriptive text. Can also be passed with the default slot */
  description: string

  /** If closable or not */
  closable: boolean

  /** whether to center the text */
  center: boolean

  /** Customized close button text */
  closeText: string

  /** If a type icon is displayed */
  showIcon: boolean

  /** Choose effect */
  effect: AlertEffect
}

五、组件单独打包

对应package.json里面的打包脚本

"dist": "npm run clean && npm run build:file && npm run lint && webpack --config build/webpack.conf.js && webpack --config build/webpack.common.js && webpack --config build/webpack.component.js && npm run build:utils && npm run build:umd && npm run build:theme",
  • 一长串的命令就是编译项目,生成目标文件。
  • 只用关注里面打包组件的命令是: webpack --config build/webpack.component.js
  • 再看build/webpack.component.js配置文件:
    在这里插入图片描述
const path = require('path');
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin');

const Components = require('../components.json');
const config = require('./config');

const webpackConfig = {
  mode: 'production',
  entry: Components,
  output: {
    path: path.resolve(process.cwd(), './lib'),
    publicPath: '/dist/',
    filename: '[name].js',
    chunkFilename: '[id].js',
    libraryTarget: 'commonjs2'
  },
   ...
};

module.exports = webpackConfig;

省略了配置中的loaderplugins,主要看这里的入口entry,对应最外层的components.json文件(这个文件很多地方都会用到)

// components.json
{
  "pagination": "./packages/pagination/index.js",
  "dialog": "./packages/dialog/index.js",
  "autocomplete": "./packages/autocomplete/index.js",
  "dropdown": "./packages/dropdown/index.js",
  "dropdown-menu": "./packages/dropdown-menu/index.js",
  "dropdown-item": "./packages/dropdown-item/index.js",
  "menu": "./packages/menu/index.js", 
  ...
 }
  • 里面的内容就是{key: packageName, value: packageDir},列出了所有的组件及路径。
  • 再看webpack.component.js里面的output.filename: [name].js,保留原组件名,实现了每个组件单独打包。

碎碎念

package.json里面的打包脚本里面多多深入会发现很多东西,script脚本意义,暂且分析了element的项目架构,没有分析每个组件的源码实现

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值