#

# 执行npm run dev

 "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"
1

TIP

npm run dev 命令做了以下几件事:

  1. npm run bootstrap 脚本命令,安装所有依赖
  2. 安装完所有依赖后,执行 npm run build:file 脚本命令
  3. 上述命令执行完毕后 cross-env 插件设置环境变量,然后 webpack 起服务
  4. 执行 node build/bin/template.js

# npm run build:file

"build:file": "node build/bin/iconInit.js & node build/bin/build-entry.js & node build/bin/i18n.js & node build/bin/version.js"
1

TIP

使用node运行了几个文件,下面我们一个个分析每个文件都做了啥

# node build/bin/iconInit.js

// build/bin/iconInit.js
'use strict';

var postcss = require('postcss');
var fs = require('fs');
var path = require('path');
var fontFile = fs.readFileSync(path.resolve(__dirname, '../../packages/theme-chalk/src/icon.scss'), 'utf8'); // 异步读取文件
var nodes = postcss.parse(fontFile).nodes;
var classList = [];

nodes.forEach((node) => {
  var selector = node.selector || ''; // 选择器
  var reg = new RegExp(/\.el-icon-([^:]+):before/);
  var arr = selector.match(reg);

  if (arr && arr[1]) {
    classList.push(arr[1]);
  }
});

classList.reverse(); // => ["platform-eleme","eleme","delete-solid","delete","s-tools","setting", ...]

fs.writeFile(path.resolve(__dirname, '../../examples/icon.json'), JSON.stringify(classList), () => {});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

TIP

以上方法通过解析 icon.scss 最终导出 icon.json 文件,该文件保存了各种图标。

# node build/bin/build-entry.js

















 
 





































































 
















 

 





 









 










// build/bin/build-entry.js

var Components = require('../../components.json'); //json文件 {组件名:组件路径} => {"pagination": "./packages/pagination/index.js"}
/* Components文件内容
  {
    "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",
    ...
  }
*/
var fs = require('fs');
var render = require('json-templater/string'); // 引入 json 模版生成插件
var uppercamelcase = require('uppercamelcase'); // 驼峰
var path = require('path');
var endOfLine = require('os').EOL; // node内置模块(操作系统)=> \r\n

// 导出路径
var OUTPUT_PATH = path.join(__dirname, '../../src/index.js');

// 导入template、安装组件template、主要template
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}}
};
`;

/** MAIN_TEMPLATE 主模版中:
 *    {{name}}    - 组件名(模块名)
 *    {{package}} - 文件名
 *    {{include}} - import语法模版
 *    {{install}} - 组件名(模块名)
 *    {{version}} - 版本号
 *    {{list}}    - 组件名(模块名)
 *  需要render函数去处理
 * */

delete Components.font;

// 组件名
var ComponentNames = Object.keys(Components);

// include 模版,用于替换 {{include}}
var includeComponentTemplate = [];
// install 模版,用于替换 {{install}}
var installTemplate = [];
// list 模版,用于替换 {{list}}
var listTemplate = [];

// 遍历组件名解析template
ComponentNames.forEach(name => {
  var componentName = uppercamelcase(name);// 转化为驼峰命名

  includeComponentTemplate.push(render(IMPORT_TEMPLATE, {
    name: componentName, // 组件名
    package: name // 包名(文件名)
  }));

  if (['Loading', 'MessageBox', 'Notification', 'Message', 'InfiniteScroll'].indexOf(componentName) === -1) {
    installTemplate.push(render(INSTALL_COMPONENT_TEMPLATE, {
      name: componentName,
      component: name
    }));
  }

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

// 主要template
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)
});

// 导出文件
fs.writeFileSync(OUTPUT_PATH, template);
console.log('[build entry] DONE:', OUTPUT_PATH);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132

TIP

为了增加可维护性,自动构建入口文件,未来如果需要新增组件,就不用频繁改动入口文件。

# node build/bin/i18n.js

'use strict';

var fs = require('fs');
var path = require('path');
var langConfig = require('../../examples/i18n/page.json');

langConfig.forEach(lang => {
  // 有目录读取目录信息,没有目录创建目录
  try {
    // 获取文件信息
    fs.statSync(path.resolve(__dirname, `../../examples/pages/${ lang.lang }`)); // 同步提供有关文件信息
  } catch (e) {
    // 创建文件夹
    fs.mkdirSync(path.resolve(__dirname, `../../examples/pages/${ lang.lang }`)); // 同步地创建目录
  }

  // 遍历写入文件
  Object.keys(lang.pages).forEach(page => {
    var templatePath = path.resolve(__dirname, `../../examples/pages/template/${ page }.tpl`);
    var outputPath = path.resolve(__dirname, `../../examples/pages/${ lang.lang }/${ page }.vue`);
    var content = fs.readFileSync(templatePath, 'utf8');
    var pairs = lang.pages[page];

    Object.keys(pairs).forEach(key => {
      // 例如:匹配 <%= 1 %> 替换成展示内容
      content = content.replace(new RegExp(`<%=\\s*${ key }\\s*>`, 'g'), pairs[key]);
    });
    // 将替换后的内容写入 outputPath 中
    fs.writeFileSync(outputPath, content);
  });
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

以上代码是国际化的过程,最终将会在 examples/pages/ 目录中生成不同语言的内容。国际化具体内容请参照 国际化

pages里的组件都是页面级组件

TIP

通过json数据,将 tpl 模版动态的编译为 .vue 文件。

优点:

只需写一套模版,就能实现国际化 API。

# cross-env

cross-env NODE_ENV=development
1

TIP

写入环境变量

在 webpack 配置文件中通过 process.env.NODE_ENV 拿到环境变量

# webpack-dev-server

TIP

由于 webpack 打包,本篇不做介绍,详见webpack打包过程