ElementUI源码系列五 - 学习build-entry.js文件之自动生成总入口文件index.js内容

「这是我参与2022首次更文挑战的第5天,活动详情查看:2022首次更文挑战」。

写在开头

在这一篇文章中 ElementUI源码系列三 - 学习gen-cssfile.js文件之自动创建组件的.scss文件与生成index.scss文件内容 我们讲过添加一个新组件要经历以下三个步骤:

  • 第一步 - 创建组件目录结构
  • 第二步 - 创建组件样式文件
  • 第三步 - 总入口文件引入组件

经过前两篇文章的学习,我们已经实现了第一、第二步骤的自动化处理,只需由一行命令(npm run new xxx)即可完成操作。这一章节我们来搞定第三步骤的自动化处理,下面接着来开启愉快的旅途吧。

0047ED6B.jpg

课前预备

build-entry.js

首先,我们先把 build-entry.js 文件创建处理,在 build/bin 目录下:

image.png

老样子,给这个脚本文件配置命令:

"scripts": {
  "dev": "webpack-dev-server --config build/webpack.common.js",
  "build": "webpack --config build/webpack.common.js",
  "build:theme": "gulp build --gulpfile packages/theme-chalk/gulpfile.js && cp-cli packages/theme-chalk/lib lib/theme-chalk",
  "gen": "node build/bin/gen-cssfile.js",
  "new": "node build/bin/new.js",
  "build:entry": "node build/bin/build-entry.js"
},
复制代码

不知不觉,我们已经拥有了六条命令,希望你能记住它们各自的作用,后面我们会把它们进行一些合并。不要觉得多,这才哪到哪,element-ui 源码中的命令直接是一整坨,干懵你。(ㄒoㄒ)

json-templater模块

json-templater 模块是一个字符串模板生成器,它能帮助我们更加快速、便捷的生成好字符串模板。

下载该包:npm install [email protected] -D

在项目根目录下创建 test.js 文件,基本使用:

// test.js
var render = require('json-templater/string');
var strs = `
姓名:{{name}}
年龄:{{age}}
爱好:{{hobby.coding}}、{{hobby.writing}}
其他:{{others}}
`;
var template = render(strs, {
  name: '橙某人',
  age: 18,
  hobby: {
    coding: '写代码',
    writing: '写文章',
  },
  others: ['var a = 1;', 'var b = 2;', 'var c = 3;'].join('\n') // 数组变字符串并加换行符
});
console.log(template)
复制代码

image.png

生成总入口index.js文件

推导index.js文件字符串模板

在编写 build-entry.js 内容之前,我们先来观察一下我们现有的项目总入口 src/index.js 文件:

import Button from '../packages/button/index.js';
import Divider from '../packages/divider/index.js';
const components = [
  Button, Divider
];
const install = (Vue) => {
  components.forEach(component => {
    Vue.component(component.name, component)
  })
}
export default {
  install,
  Button,
  Divider
}
复制代码

因为要自动生成该文件内容,那么我们需要先推导出该文件的字符串模块,后面才好去动态生成文件内容。

我们根据 json-templater 模块的内容来做第一次推导:

// build-entry.js
var render = require('json-templater/string');

var strs = `
{{include}}
const components = [
  {{list}}
];
const install = function(Vue, opts = {}) {
  components.forEach(component => {
    Vue.component(component.name, component);
  });
};
export default {
  install,
  {{list}}
};
`;

var template = render(strs, {
  include: [
    'import Button from \'../packages/button/index.js\';';
    'import Divider from \'../packages/divider/index.js\';';  
  ].join('\n'),
  list: ['Button', 'Divider'].join(',' + '\n'),
});
复制代码

是不是很简单,应该不难吧?第二次推导:

// build-entry.js
var render = require('json-templater/string');
var uppercamelcase = require('uppercamelcase');

var strs = `
{{include}}
const components = [
  {{list}}
];
const install = function(Vue, opts = {}) {
  components.forEach(component => {
    Vue.component(component.name, component);
  });
};
export default {
  install,
  {{list}}
};
`;

// 定义相关变量
var ComponentNames = ['button', 'divider'];
var IMPORT_TEMPLATE = 'import {{name}} from \'../packages/{{package}}/index.js\';';
var includeComponentTemplate = [];
var listTemplate = [];

// 循环收集动态数据
ComponentNames.forEach(name => {
  var componentName = uppercamelcase(name); // ['Button', 'Divider']
  includeComponentTemplate.push(render(IMPORT_TEMPLATE, {
    name: componentName,
    package: name
  }));
  listTemplate.push(`  ${componentName}`); // ['  Button', '  Divider']
})

var template = render(strs, {
  include: includeComponentTemplate.join('\n'),
  list: listTemplate.join(',' + '\n'),
});
复制代码

第二次推导结果应该也不算难,我们做了一些变量的定义,用循环的形式来收集相应的动态数据。现在生成的模板还不是最终想要的,再优化优化:

// build-entry.js
var render = require('json-templater/string');
var uppercamelcase = require('uppercamelcase');
var endOfLine = require('os').EOL;

var strs = `
{{include}}
const components = [
  {{list}}
];
const install = function(Vue, opts = {}) {
  components.forEach(component => {
    Vue.component(component.name, component);
  });
};
export default {
  install,
  {{list}}
};
`;

// 引入 `components.json`
var Components = require('../../components.json')
var ComponentNames = Object.keys(Components);
var IMPORT_TEMPLATE = 'import {{name}} from \'../packages/{{package}}/index.js\';';
var includeComponentTemplate = [];
var listTemplate = [];

ComponentNames.forEach(name => {
  var componentName = uppercamelcase(name); 
  includeComponentTemplate.push(render(IMPORT_TEMPLATE, {
    name: componentName,
    package: name
  }));
  listTemplate.push(`  ${componentName}`);
})

var template = render(strs, {
  include: includeComponentTemplate.join(endOfLine), // 替换换行符
  list: listTemplate.join(',' + endOfLine),
});
复制代码

也没做多少改动,就是引入了 var endOfLine = require('os').EOL;,替换了所有的换行符。因为换行符不同系统需要兼容,os.EOL 属性是一个常量,返回当前操作系统的换行符,Windows系统是\r\n,其他系统是\n

另一个改动就是引入 components.json 文件,根据这个文件来生成 index.js 文件内容。

os 模块是 Node 环境的内置模块,和 pathfs 等模块一样,并不需要单独下载,更多详情可以点我

生成index.js文件内容

上面推导出最终的模板,也就是 index.js 的内容了,最后我们把它写入文件即可。

// build-entry.js
var render = require('json-templater/string');
var uppercamelcase = require('uppercamelcase');
var endOfLine = require('os').EOL;

var strs = `
{{include}}
const components = [
  {{list}}
];
const install = function(Vue, opts = {}) {
  components.forEach(component => {
    Vue.component(component.name, component);
  });
};
export default {
  install,
  {{list}}
};
`;

var Components = require('../../components.json')
var ComponentNames = Object.keys(Components);
var IMPORT_TEMPLATE = 'import {{name}} from \'../packages/{{package}}/index.js\';';
var includeComponentTemplate = [];
var listTemplate = [];

ComponentNames.forEach(name => {
  var componentName = uppercamelcase(name);
  includeComponentTemplate.push(render(IMPORT_TEMPLATE, {
    name: componentName,
    package: name
  }));
  listTemplate.push(`  ${componentName}`);
})

var template = render(strs, {
  include: includeComponentTemplate.join(endOfLine),
  list: listTemplate.join(',' + endOfLine),
});

// 写入文件
var path = require('path');
var fs = require('fs');
var OUTPUT_PATH = path.join(__dirname, '../../src/index.js');
fs.writeFileSync(OUTPUT_PATH, template);
复制代码

最终我们执行 npm run build:entry 命令,index.js 文件内容就自动生成,是不是很棒!

000ADE8B.gif

到这里,我们就基本是实现了自动化创建组件的过程了,只需要两步:

  • 执行 npm run new xxx 命令创建组件
  • 执行 npm run build:entry 引入组件

就能搞定了。(✪ω✪)

build-entry.js 完整源码

下面我们丢一下 ElementUI 源码中 build-entry.js 文件的完整内容,虽然有所出入,但是大体本质是一样的,感兴趣的小伙伴可以观摩观摩。

var Components = require('../../components.json');
var fs = require('fs');
var render = require('json-templater/string');
var uppercamelcase = require('uppercamelcase');
var path = require('path');
var endOfLine = require('os').EOL;

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 => {
  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}`);
});

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);
复制代码




往期内容




至此,本篇文章就写完啦,撒花撒花。

image.png

希望本文对你有所帮助,如有任何疑问,期待你的留言哦。
老样子,点赞+评论=你会了,收藏=你精通了。

Guess you like

Origin juejin.im/post/7062138564917592071