Element-UI(2.11.1) 构建流程

文章较长,建议收藏方便再看css

  1. Element是如何设计目录?各目录职能都是什么?
  2. Element是如何利用webpack、nodejs搭建组件构建环境、构建工具?
  3. Element是如何打包样式的?
  4. Element是如何实现全局引入、与按需引入的这两个功能的?
  5. 主流的组件库说明都是用markDown写的,如何把md文档,编译成Vue组件?这方面E是如何配置的?
  6. Element如何完成国际化的?
  7. 若是是二次封装,怎么在Element的架构上,新建一个组件?

若是你对上面存在问题存在疑问,或者下面的内容,能够帮助到你一点点。html

目录分析


最外层目录

├─build // 构建相关的nodejs脚本和webapck配置
├─examples // 展现Element组件的官网项目
├─lib // 打包出来的文件,发布到npm包
├─packages // 组件代码
├─src // 组件代码公共方法
├─test // 测试代码
├─Makefile // 构建工具文件
├─components.json // 组件列表
└─package.json // 记录项目依赖包、script脚本
复制代码

components.json


先了解一下最简单的components.jsonvue

​ 这个文件记录了你全部组件的路径关系,最终以key,value的形式保存在这个文件中,方便后续导出,批量操做组件node

咱们能够在不少地方应用了这个文件去参与Webpack、nodejs脚本的自动化构建流程,好比自动生成文件、自动生成多入口等等。。linux

// 为了避免增长文章长度,只截取关键部分,完整请看源码
{
  "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",
  "submenu": "./packages/submenu/index.js",
  "menu-item": "./packages/menu-item/index.js",
  "menu-item-group": "./packages/menu-item-group/index.js",
  "input": "./packages/input/index.js",
  "input-number": "./packages/input-number/index.js",
  "radio": "./packages/radio/index.js",
  "radio-group": "./packages/radio-group/index.js",
  "radio-button": "./packages/radio-button/index.js",
  "checkbox": "./packages/checkbox/index.js",
}

复制代码

Packages目录、src目录


一个组件库,最重要的是组件库的源码。webpack

ElementUI的组件库源码主要存在于packagessrc两个目录下面。git

Packages文件夹


// packages文件夹 每一个组件都有本身的文件夹
// 为了避免增长文章长度,只截取关键部分,完整请看源码
├── alert
├── aside
├── button
├── button-group
├── calendar
├── checkbox
├── form
├── table
├── theme-chalk // 组件的样式都在这里 index.scss 里面包含了全部的样式文件
复制代码
Button文件夹

packages下,每一个组件都是用一个独立的文件夹去管理github

. // packages/button文件夹
├── index.js // 组件入口文件
└── src
    ├── button-group.vue // button-group源码
    └── button.vue // button源码
复制代码
// packages/button/index.js
// 引入组件button组件
import ElButtonfrom './src/button';

// 为组件提供install方法,让Vue能够经过Vue.use(Button)形式去使用
ElButton.install = function(Vue) {
  Vue.component(ElButton.name, ElButton);
};

export default ElButton;

复制代码
theme-chalk 文件夹

theme-chalk文件夹是用来管理全部组件的样式的,文件夹大体结构以下:web

// 为了避免增长文章长度,只截取关键部分,完整请看源码
├── packages // 组件入口文件
└── theme-chalk
    ├── alert.scss // alert组件的样式
    └── button.scss // button组件的样式
    └── aside.scss
    └── checkbox.scss
    └── index.scss // 该文件引入了全部样式,是总的样式入口文件
    └── gulpfile.js // 样式的编译、压缩、输出是经过gulp工做流来完成的
复制代码

这里比较有趣的是,能够看到Element对样式的打包、编译、处理、输入,是经过gulp来完成的,而不是Webpack。vue-router

另外还有一个比较流行的组件库IView,也是经过这种gulp工做流的形式去处理的样式

Src文件夹


packages是把每一个组件单独分开去管理的

Src的做用就是,把packages的全部公共方法、自定义指令、组件国际化都放在这里

每种不一样类型的公共方法的,都存在不一样的文件夹中

// src目录
├── directives // 项目封装好的Vue自定义指令
├── index.js // 组件的总体入口文件
├── locale // 组件的国际化内容
├── mixins // 一些组件的mixins
├── transitions // 动画的封装
└── utils // 工具函数集合
复制代码

这里,咱们重点关注一下index.js

// 为了避免增长文章长度,只截取关键部分,完整请看源码

/* Automatically generated by './build/bin/build-entry.js' */

// 一、导入了全部组件packages下的全部组件
import Pagination from '../packages/pagination/index.js';
import Dialog from '../packages/dialog/index.js';
import Autocomplete from '../packages/autocomplete/index.js';
import Dropdown from '../packages/dropdown/index.js';

const components = [
  Pagination,
  Dialog,
  Autocomplete,
  Dropdown,
  DropdownMenu,
  DropdownItem,
];

// 二、提供了install方法,帮咱们挂载了一些组件与变量
const install = function(Vue, opts = {}) {
  locale.use(opts.locale);
  locale.i18n(opts.i18n);
  // 把全部的组件注册到Vue上面
  components.forEach(component => {
    Vue.component(component.name, component);
  });

  Vue.use(InfiniteScroll);
  Vue.use(Loading.directive);

  Vue.prototype.$ELEMENT = {
    size: opts.size || '',
    zIndex: opts.zIndex || 2000
  };

  Vue.prototype.$loading = Loading.service;
  Vue.prototype.$msgbox = MessageBox;

};

// 导出install方法,能够理解为一个插件、以及一些功能好比国际化功能
export default {
  install,
  locale
};

复制代码

在这个文档中,咱们能够看到这个文件的最开头有这么一句话:

Automatically generated by './build/bin/build-entry.js'

能够知道这是经过build-entry脚本去生成的,后续咱们看项目的构建脚本的时候会简单分析如何实现,这里简单了解一下便可。

另外这个自动生成的index.js文件,主要的实现的功能是如下三点:

  1. 导入了全部组件packages下的全部组件
  2. 对外暴露了install方法,让使用者经过Vue.use去使用的时候,自动执行一些自定义操做,好比挂载$loading、​$Msgbox等变量
  3. 导出install方法、一些公共的变量

最后,由于这里由于引入了全部的组件源码,因此这也是一个Webpack的打包的入口文件,在看webpack配置的时候会具体说。

examples目录


Examples,至关于一个独立的Vue项目,主要的功能是展现文档。

Element的官网展现的内容,就是在这个examples目录。

这里文件夹,咱们介绍一下比较主要的文件

├── app.vue // 根组件
├── components // 项目公共组件
├── docs // 每个页面的组件都存在在这
├── entry.js  // 项目入口
├── route.config.js // 路由配置,经过脚本自动生成而来
复制代码

entry.js 项目入口文件

咱们能够看到这个文件中

经过这代码import Element from 'main/index.js',把以前全部写的组件js都引入了进来。

这里配置了Webpack的aliasmain/index.js,其实就是咱们上面提到的src/index.js文件

另外还引入了全局的css import 'packages/theme-chalk/src/index.scss';

import Vue from 'vue';
import entry from './app';
import VueRouter from 'vue-router';
import Element from 'main/index.js';
import hljs from 'highlight.js';
import routes from './route.config';
import demoBlock from './components/demo-block';
import MainFooter from './components/footer';
import MainHeader from './components/header';
import SideNav from './components/side-nav';
import FooterNav from './components/footer-nav';
import title from './i18n/title';

import 'packages/theme-chalk/src/index.scss';
import './demo-styles/index.scss';
import './assets/styles/common.css';
import './assets/styles/fonts/style.css';
import icon from './icon.json';

Vue.use(Element);
Vue.use(VueRouter);
Vue.component('demo-block', demoBlock);
Vue.component('main-footer', MainFooter);
Vue.component('main-header', MainHeader);
Vue.component('side-nav', SideNav);
Vue.component('footer-nav', FooterNav);

const globalEle = new Vue({
  data: { $isEle: false } // 是否 ele 用户
});

Vue.mixin({
  computed: {
    $isEle: {
      get: () => (globalEle.$data.$isEle),
      set: (data) => {globalEle.$data.$isEle = data;}
    }
  }
});

Vue.prototype.$icon = icon; // Icon 列表页用

console.log(routes);
const router = new VueRouter({
  mode: 'hash',
  base: __dirname,
  routes
});

router.afterEach(route => {
  // https://github.com/highlightjs/highlight.js/issues/909#issuecomment-131686186
  Vue.nextTick(() => {
    const blocks = document.querySelectorAll('pre code:not(.hljs)');
    Array.prototype.forEach.call(blocks, hljs.highlightBlock);
  });
  const data = title[route.meta.lang];
  for (let val in data) {
    if (new RegExp('^' + val, 'g').test(route.name)) {
      document.title = data[val];
      return;
    }
  }
  document.title = 'Element';
  ga('send', 'event', 'PageView', route.name);
});

new Vue({ // eslint-disable-line
  ...entry,
  router
}).$mount('#app');

复制代码

docs文件夹

Element官网支持4种语言,docs一共有4个文件夹,每一个文件夹里面的内容基本是同样的,只是根据语言不一样,翻译一下。

这里咱们重点关注zh-CN文件下的中文文档

咱们能够看到里面所有都是md文档,而每个md文档,分别对应着其官网的展现页面

用markdown文档编写展现文档,其实这是各大组件库写展现文档的一个主流方式

可是用这种文档展现,须要咱们去克服一个技术上的难题

如何把md文档上写的Vue代码,编译成Vue组件呢?

这里咱们先把问题抛出来,不做回答,让你们带着问题看下去。

咱们先看button.md,就是官网展现Button组件的时候显示的内容

该文件里其实就是一些普通的md语法。

咱们聚焦到上图中,红色圈圈里的代码

它是用了特定的标识符给包裹起来,好比:::demo:::就把一些Vue组件的代码给包裹起来

:::demo

\```html 这里面的内容有两个做用: 一、以Vue组件的形式展示出来 二、以md的形式展示出来 \``` ::: 复制代码

为何要同:::demo这样的标识符去给代码包裹起来呢?

实际上是由于,Element要把demo标识符里面的代码给单独提取出去,去给Vue-loader处理,从而生成Vue组件。

那么具体实施思路是怎样的呢?

在说这个以前,先复习一下Webpack中的loaders相关的三个知识

  • loaders的执行顺序是,从下到上、从右到左的
  • 前一个loaders执行的结果,会返回到下一个loaders中
  • Vue-loader能够帮咱们把SFC规范的语法,编译成Vue组件

这个功能的实现,底层就是Element本身编写了一个loader,把里面的内容抽出来,而后传给vue-loader去编译。

咱们先看看其webpack配置

module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          compilerOptions: {
            preserveWhitespace: false
          }
        }
      },
      {
        test: /\.md$/,
        use: [
          {
            loader: 'vue-loader',
            options: {
              compilerOptions: {
                preserveWhitespace: false
              }
            }
          },
          {
            loader: path.resolve(__dirname, './md-loader/index.js')
          }
        ]
      }
    ]
  }
复制代码

能够看到,当遇到*.md文档的时候

首先会用./md-loader/index.js,去处理。

而后把处理的结果,再传给vue-loader.

最终Vue-loader就帮咱们编译成Vue组件

那么./md-loader/index.js作了什么呢?

总结来讲,就是这个loader最终返回的是一段字符串

这段字符串就是符合Vue SFC 规范的字符串

那么这一串字符串,就能够被vue-loader去编译,为咱们生成Vue组件。

因此一个一个单独的md文档,经过路由+webpack的配置,其实跟一个个单独的Vue组件是差很少的,可是同时却提供了咱们写md文档的方便

当咱们跑起来项目的时候,修改这里的md文档,是会实时更新的。

咱们试试题目修改为 Ynet-button,那么在官方就能够看到

package.json


其实不少人都说,若是要理解一个项目的概要,首先要看的就是package.json

它记录了项目的依赖以及脚本,让你理解项目的一些主要功能点,咱们来看看它的大概样式

// 为了避免增长文章长度,只截取关键部分,完整请看源码
{
  "name": "element-ui",
  "version": "2.11.1",
  "description": "A Component Library for Vue.js.",
  // 发包上去后,项目的入口文件 
  // import Element from 'element-ui' 时候引入的就是main中的文件
  "main": "lib/element-ui.common.js",// element-ui.common.js是commonsjs规范 lib/index.js是umd规范
 // 当你npm publish 发包的时候,发什么到Npm上,由files决定,功能和.gitignore相似
  "script": {}, // webpack配置,nodejs脚本,这部分单独在下文中介绍
  "files": [
    "lib",
    "src",
    "packages",
    "types"
  ],
  // TypeScript 入口文件
  "typings": "types/index.d.ts",
  // 仓库信息
  "repository": {
    "type": "git",
    "url": "git@github.com:ElemeFE/element.git"
  },
  // 项目主页的URL地址
  "homepage": "http://element.eleme.io",
  "keywords": [
    "eleme",
    "vue",
    "components"
  ],
  // 使用MIT协议,具体见参考资料
  "license": "MIT",
  // 提issuse、bugs的地方
  "bugs": {
    "url": "https://github.com/ElemeFE/element/issues"
  },
  // unpkg.com是一个源自 npm 的全球快速 CDN
  // 若是经过unpkg.com这种CDN方式引入库的话,默认文件路径
  // 官方文档在CDN引入的时候有提 https://element.eleme.cn/#/zh-CN/component/installation
  // lib/index.js是umd规范,由webpack.conf.js生成
  "unpkg": "lib/index.js",
  // 声明当前模块包含 style 部分,并指定入口文件。
  "style": "lib/theme-chalk/index.css",
  "dependencies": {
  },
  // 解决模块之间相互依赖的问题,具体见参考资料
  "peerDependencies": {
    "vue": "^2.5.17"
  },
  "devDependencies": {
  }
}
复制代码
script脚本汇总

package.json中,script脚本的理解对我来讲难度是比较大的,因此这里单独拎出来去说。

{
"scripts": {
  // 安装依赖,官方推荐优先用yarn
    "bootstrap": "yarn || npm i",
  // build/bin/iconInit.js 解析icon.scss。把全部的icon的名字放在icon.json里。最后挂到Vue原型上的$icon的上,方便循环出来.具体使用见【补充资料】
  // build/bin/build-entry.js 根据components.json,生成src/index.js文件,核心就是json-templater/string插件的使用【插件使用见参考资料】
  // build/bin/i18n.js 根据examples/i18n/page.json和模板,生成不一样语言的demo
	//  build/bin/version.js 根据package.json中的versions,生成examples/versions.json
    "build:file": "node build/bin/iconInit.js & node build/bin/build-entry.js & node build/bin/i18n.js & node build/bin/version.js",
  // build/bin/gen-cssfile // 根据components.json,生成生成package/theme-chalk/index.scss
  // gulp build --gulpfile packages/theme-chalk/gulpfile.js 用gulp构建工具,编译scss、压缩、输出css
  // cp-cli 是一个跨平台的copy工具,和CopyWebpackPlugin相似、linux的cp差很少(不具备跨平台的功能),
    "build:theme": "node build/bin/gen-cssfile && gulp build --gulpfile packages/theme-chalk/gulpfile.js && cp-cli packages/theme-chalk/lib lib/theme-chalk",
  // 把src目录下的除了index.js入口文件外的其余文件经过babel转译,而后移动到lib文件夹下。
    "build:utils": "cross-env BABEL_ENV=utils babel src --out-dir lib --ignore src/index.js",
// 跑起来demo
    "dev": "npm run bootstrap && npm run build:file && cross-env NODE_ENV=development webpack-dev-server --config build/webpack.demo.js & node build/bin/template.js",
 // 总体构建,打包出lib库
    "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",      
  // 生成umd模块的语言包
    "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 bui
  // 对项目进行eslint检测
    "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",
复制代码
Script 脚本的进一步剖析

npm run bootstrap` 安装依赖,推荐优先用yarn
npm run build:file ` 用脚本自动化生成一些文件

该命令跑了4个任务

`node build/bin/iconInit.js`  // 解析icon.scss。把全部的icon的名字放在icon.json里。最后挂到Vue原型上的$icon的上
& `node build/bin/build-entry.js` // 根据components.json,生成src/index.js文件,核心就是json-templater/string插件的使用【插件使用见参考资料】
& `node build/bin/i18n.js` // 根据examples/i18n/page.json和模板,生成不一样语言的demo,当选择语言不同,会跑不一样语言的文件夹。
& `node build/bin/version.js` // 根据package.json中的versions,生成examples/versions.json,而后在header中获取这个versions.json来使用
复制代码

iconInit.js

这文件是为了把icon.scss里面全部的icon名字,抽离出来,而后存放在 examples/icon.json文件上。

但是Element为何要把icon的名字提取出来呢?

咱们看一下,最终展现在官网上的demo就知道了

由于Element中Icon有太多了,一个一个去写就太蠢了,因此才把全部icon都存在一个数组里,而后经过v-for循环出来

icon.json

这个文件,咱们能够看到最终抽离出来的效果

最后使用,咱们能够看到入口文件entry.js、与icon.md中看到做者这么作的最终使用状况

// entry.js
import Vue from 'vue';
import icon from './icon.json';
Vue.prototype.$icon = icon; // Icon 列表页用
------------------------------------------------------------------

// icon.md
### 图标集合
<ul class="icon-list">
  // 直接把全部的icon循环出来
  <li v-for="name in $icon" :key="name">
    <span>
      <i :class="'el-icon-' + name"></i>
      <span class="icon-name">{{'el-icon-' + name}}</span>
    </span>
  </li>
</ul>
复制代码

build-entry.js

在上面介绍src/index.js的时候,咱们知道index.js文件,是经过这个脚本去自动化构建生成的,它究竟是如何实现的呢?

这里咱们结合源码来看:

// 把全部组件的依赖关系,引入进来
var Components = require('../../components.json');
var fs = require('fs');

// https://www.npmjs.com/package/json-templater 可让string与变量结合,输出一些内容
var render = require('json-templater/string');

// https://github.com/SamVerschueren/uppercamelcase 转化为驼峰 foo-bar >> FooBar
var uppercamelcase = require('uppercamelcase');

var path = require('path');

var endOfLine = require('os').EOL;// os.EOL属性是一个常量,返回当前操做系统的换行符(Windows系统是\r\n,其余系统是\n)

// 生成文件的名字和路径
var OUTPUT_PATH = path.join(__dirname, '../../src/index.js');
var IMPORT_TEMPLATE = 'import {{name}} from \'../packages/{{package}}/index.js\';';
var INSTALL_COMPONENT_TEMPLATE = ' {{name}}';
var MAIN_TEMPLATE = `/* Automatically generated by './build/bin/build-entry.js' */ {{include}} import locale from 'element-ui/src/locale'; import CollapseTransition from 'element-ui/src/transitions/collapse-transition'; const components = [ {{install}}, CollapseTransition ]; const install = function(Vue, opts = {}) { locale.use(opts.locale); locale.i18n(opts.i18n); components.forEach(component => { Vue.component(component.name, component); }); Vue.use(InfiniteScroll); Vue.use(Loading.directive); Vue.prototype.$ELEMENT = { size: opts.size || '', zIndex: opts.zIndex || 2000 }; Vue.prototype.$loading = Loading.service; Vue.prototype.$msgbox = MessageBox; Vue.prototype.$alert = MessageBox.alert; Vue.prototype.$confirm = MessageBox.confirm; Vue.prototype.$prompt = MessageBox.prompt; Vue.prototype.$notify = Notification; Vue.prototype.$message = Message; }; /* istanbul ignore if */ if (typeof window !== 'undefined' && window.Vue) { install(window.Vue); } export default { version: '{{version}}', locale: locale.use, i18n: locale.i18n, install, CollapseTransition, Loading, {{list}} }; `;

delete Components.font;

// 获取全部组件的名字,存放在数组中
var ComponentNames = Object.keys(Components);

var includeComponentTemplate = [];
var installTemplate = [];
var listTemplate = [];

ComponentNames.forEach(name => {
  // menu-item 转化为 MenuItem
  var componentName = uppercamelcase(name);
  // IMPORT_TEMPLATE = 'import {{name}} from \'../packages/{{package}}/index.js\';';
  includeComponentTemplate.push(render(IMPORT_TEMPLATE, {
    name: componentName,
    package: name
  }));
  // 获得的数据结构是: includeComponentTemplate = ['import MenuItem from \'../packages/menu-item/index.js\';', ....]

  if (['Loading', 'MessageBox', 'Notification', 'Message', 'InfiniteScroll'].indexOf(componentName) === -1) {
    // INSTALL_COMPONENT_TEMPLATE = ' {{name}}';
    installTemplate.push(render(INSTALL_COMPONENT_TEMPLATE, {
      name: componentName,
      component: name
    }));
  }
  // 获得的数据结构是: installTemplate = ['Pagination', 'Dialog', ...]

  if (componentName !== 'Loading') listTemplate.push(` ${componentName}`);
});

// 这里若是理解include、install、version、list分别表明什么,下面的代码就容易理解了
// 其实就是用json-templater库,把 MAIN_TEMPLATE 中的字符串,和include、install、version、list这些变量结合出来,最终的样式就是src/index.js的样式
var template = render(MAIN_TEMPLATE, {
  include: includeComponentTemplate.join(endOfLine),
  install: installTemplate.join(',' + endOfLine),
  version: process.env.VERSION || require('../../package.json').version,
  list: listTemplate.join(',' + endOfLine)
});

// 结果输出到src/index.js中
fs.writeFileSync(OUTPUT_PATH, template);

复制代码

大概实现逻辑就是,根据components.json,生成src/index.js文件

核心就是json-templater/string插件的使用【插件使用见参考资料】

build/bin/i18n.js

Element项目的国际化,分为两个部分,分别用两种不一样的方式去处理。

第一部分是,官网的Demo展现国际化

第二部分是,组件源码国际化

build/bin/i18n.js是帮咱们完成第一部分的国际化工做的

第一部分:Element的展现的官网是如何实现国际化的

首先官网的主页(index)页面的国际化,是以examples/pages/template为公共的模板,再genuine不一样的语言,分别生成不一样的文件

咱们能够看看template里有什么

里面是一个个.tpl文件,每个文件都是一个模板

而每一个tpl文件,都是符合SFC规范的的Vue文件格式。

在tpl文件中,须要国际化的地方,会根据必定的格式,用数字标识出来

// guide.tpl
<script>
  export default {
    data() {
      return {
        lang: this.$route.meta.lang,
        navsData: [
          {
            path: '/design',
            name: '<%= 1 >' // 国际化标志位
          },
          {
            path: '/nav',
            name: '<%= 2 >'// 国际化标志位
          }
        ]
      };
    }
  };
</script>
复制代码

而后根据不一样的语言,把examples/i18n/page.json(这个文件包含首页全部的国际化字段)里,提早写好的字段,按照标志位填充进去

最终生成的这一套组件,组合起来就是就是Element展现的首页

这个首页是支持多语言的,每一种语言都有对应的一个vue文件。

那么它是如何作到不一样的语言,有不一样的vue文件呢?

build/bin/i18n.js,就是帮咱们完成这个事项。

大概的工做流程就是,引入examples/i18n/page.json,而后循环,根据不一样的数据结构

把tpl文件的标志标志位,经过正则匹配出来,并替换成本身预先设定好的字段

// 把标志位地方的字段,替换成page.json里预先写好的字段
content = content.replace(new RegExp(`<%=\\s*${ key }\\s*>`, 'g'), pairs[key]);
// 最后再输出
fs.writeFileSync(outputPath, content);
复制代码

最终看到的结果就是examples里面的内容

这样就完成了官网首页的国际化,那么markdown文档的国际化呢?

个人答案是,本身翻译的,这部分是没有用自动化流程去构建,只是用脚本为咱们生成一个空的md文档,内容由咱们本身去编写。(若是错了,请指正)

第二部分:组件源码的国际化

packages/color-picker/src/components/picker-dropdown.vue 中,咱们在模板部分能够看到这个语言包的使用:

<el-button
  size="mini"
  type="text"
  class="el-color-dropdown__link-btn"
  @click="$emit('clear')">
  {{ t('el.colorpicker.clear') }}
</el-button>
<el-button
  plain
  size="mini"
  class="el-color-dropdown__btn"
  @click="confirmValue">
  {{ t('el.colorpicker.confirm') }}
</el-button>

复制代码

这个t('el.colorpicker.clear')返回的是什么内容呢?

咱们再src/locale/lang下面能够看到

export default {
  el: {
    colorpicker: {
      confirm: '肯定',
      clear: '清空'
    },
  }
复制代码

因此t函数,实际上会根据你的语言,去获取不一样的语言包,而后再去加载去对于的字段出来,这一点跟普通项目的国际化没什么区别。

可是Element是如何取构建t函数的呢?是经过什么手段帮t绑定到Vue上面呢

这部分的操做是在src/locale/lang/index.js完成的

若是你没有设置语言包,在index.js里面就会默认使用中文

npm run build:theme

这个命令帮咱们跑了3个命令

`node build/bin/gen-cssfile` // 根据components.json,生成生成package/theme-chalk/index.scss,把全部组件的样式都导入到index.scss
&& `gulp build --gulpfile packages/theme-chalk/gulpfile.js` // 用gulp构建工具,编译scss、压缩、输出css
&& `cp-cli packages/theme-chalk/lib lib/theme-chalk` //cp-cli 是一个跨平台的copy工具,和CopyWebpackPlugin相似、linux的cp差很少(不具备跨平台的功能) 把文件复制到lib/theme-chalk下
复制代码

首先咱们知道ElemntUI在使用的时候,是提供2种方式的。

一种是全局引入、一种是按需引入。

因此在样式的处理上,也有要两种处理方法,分别用于全局引入、按需引入的

咱们先看看两种引入方式的具体使用

全局引入

import Vue from 'vue';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import App from './App.vue';

Vue.use(ElementUI);

new Vue({
  el: '#app',
  render: h => h(App)
});
复制代码

按需引入

import Vue from 'vue';
import { Button, Select } from 'element-ui';
// 上面这句会经过babel-plugin-component插件编译为
// var button = require('element-ui/lib/button')
// require('element-ui/lib/theme-chalk/button.css')
// 如何转义请看配置 https://element.eleme.cn/#/zh-CN/component/quickstart
import App from './App.vue';

Vue.use(Button)
Vue.use(Select)

new Vue({
  el: '#app',
  render: h => h(App)
});
复制代码

gulpfile.js 实现样式打包


既然有两种引入方式,那么Element在打包组件的样式,就必须打包两种方案。

还记得咱们上面介绍的packages/theme-chalk吗?

其实只要把packages/theme-chalk下的全部scss都编译成css就能够了

当你须要全局引入的时候,就去引入index.scss文件,这样全部组件的样式都齐全了。

若是你想按需引入,好比按需引入button组件,那么只要引入button.scss文件,就能够了。

那么如何把packages/theme-chalk下的全部scss都编译成css?(Element是如何打包样式的?)

这一点webpack不容易办到(鉴于我使用webpack的渣渣经验,我没想到该如何用webpack去实现会比较优雅,若是小伙伴知道,欢迎分享),可是gulp却很容易

因此ElementUI的样式打包,并非用webpack的,是用了gulp,基于工做流去处理样式。

咱们看看Element如何结合gulp去使用

'use strict';

const { series, src, dest } = require('gulp');
const sass = require('gulp-sass'); // 编译gulp工具
const autoprefixer = require('gulp-autoprefixer');// 添加厂商名字工具
const cssmin = require('gulp-cssmin'); // 压缩css工具

function compile() {
  return src('./src/*.scss') // 引入src下全部的scss样式
    .pipe(sass.sync()) // 把scss文件编译成css
    .pipe(autoprefixer({ // 基于目标浏览器版本,添加厂商前缀
      browsers: ['ie > 9', 'last 2 versions'],
      cascade: false
    }))
    .pipe(cssmin()) // 压缩css
    .pipe(dest('./lib')); // 输出到lib下
}

function copyfont() {
  return src('./src/fonts/**') // 读取src/fonts下的全部文件
    .pipe(cssmin()) // 压缩
    .pipe(dest('./lib/fonts')); // 输出到lib/fonts下
}

exports.build = series(compile, copyfont);

复制代码

最终就会帮咱们打包出了样式文件。

gen-cssfile.js

根据components.json,生成生成package/theme-chalk/index.scss,把全部组件的样式都导入到index.scss

这样每次新增组件,就不用手动去引入新增组建的样式了

cp-cli packages/theme-chalk/lib lib/theme-chalk

cp-cli 是一个跨平台的copy工具,和CopyWebpackPlugin相似、linux的cp差很少(不具备跨平台的功能) 把文件复制到lib/theme-chalk下

Webpack配置


咱们能够看到E的webpack配置主要要由下面5中配置去分工

webpack.common.js

生成commonjs2规范的入口文件 /lib/element-ui.common.js,用于import的方法引入

webpack.component.js

把各组件以commonjs规范单独打包出来,提供按需引入的功能

webpack.conf.js

生成umd规范的入口文件/lib/index.js,用于CDN方法引入

webpack.test.js

webpack.test.js 跑单元测试的,这里咱们忽略。

webpack.demo.js

用于跑Element官网的基础配置

makeFile


Element中还用了makefile为咱们编写了一些额外的脚本

当咱们在命令行中输入 make时,能够看到

这是已经帮咱们编写好的脚本

这里重点说一下 make new <component-name> [中文] 这个命令

这是用来建立一个新的组件的,而后这个命令会帮咱们基于新建好的组件,帮咱们进行一系列的自动化操做

当运行这个命令的时候,其实运行的是 node build/bin/new.js

new.js脚本主要作了下面几件事:

  1. 把你新建的组件添加到 components.json
  2. 添加到 index.scss
  3. 添加到 element-ui.d.ts
  4. 建立 package
  5. 添加到 nav.config.json

因此当咱们要在Elment架构上,新建一个elect-bill组件

只要执行 make new elect-bill 电子帐号,而后不用配置,就能够直接去开发了

补充资料


  • MIT协议
preview

参考连接