去年的时候,有尝试看过一会ele源码,很多细节记不清楚了,但是它的工程化构建却给我留下了深刻的印象,整体全面清楚,一行命令能完成很多事情,后来我将ele新建组件的核心思想稍作修改就在实际开发中应用多次,效果还不戳,简单又好用,既然如此,不如分享一下
打开element的源码,在项目的根目录,有一个Makefile
文件,打开瞧瞧
.PHONY: dist test
default: help
# build all theme
build-theme:
npm run build:theme
install:
npm install
install-cn:
npm install --registry=http://registry.npm.taobao.org
dev:
npm run dev
play:
npm run dev:play
new:
node build/bin/new.js $(filter-out $@,$(MAKECMDGOALS))
new-lang:
node build/bin/new-lang.js $(filter-out $@,$(MAKECMDGOALS))
dist: install
npm run dist
deploy:
@npm run deploy
pub:
npm run pub
test:
npm run test:watch
help:
@echo " \033[35mmake\033[0m \033[1m命令使用说明\033[0m"
@echo " \033[35mmake install\033[0m\t\033[0m\t\033[0m\t\033[0m\t--- 安装依赖"
@echo " \033[35mmake new <component-name> [中文名]\033[0m\t--- 创建新组件 package. 例如 'make new button 按钮'"
@echo " \033[35mmake dev\033[0m\t\033[0m\t\033[0m\t\033[0m\t--- 开发模式"
@echo " \033[35mmake dist\033[0m\t\033[0m\t\033[0m\t\033[0m\t--- 编译项目,生成目标文件"
@echo " \033[35mmake deploy\033[0m\t\033[0m\t\033[0m\t\033[0m\t--- 部署 demo"
@echo " \033[35mmake pub\033[0m\t\033[0m\t\033[0m\t\033[0m\t--- 发布到 npm 上"
@echo " \033[35mmake new-lang <lang>\033[0m\t\033[0m\t\033[0m\t--- 为网站添加新语言. 例如 'make new-lang fr'"
复制代码
从文件的内容上有点开发经验的朋友都能看出来,它肯定是跟项目命令管理相关的文件,当然Makefile
本身的功能也很强大,本人也是孤陋寡闻第一次了解?,这里也只针对make
命令,通俗来说,就是执行对应的命令,运行对应的脚本,感兴趣的大佬们可以深入了解下。
今天主要用的是下面这个命令
new:
node build/bin/new.js $(filter-out $@,$(MAKECMDGOALS))
复制代码
根据make help
的提示,可以知道这行命令的含义就是根据输入的component-name
和 中文 来新建对应的组件。
make new <component-name> [中文名] --- 创建新组件 package. 例如 'make new button 按钮'
复制代码
在命令行输入 make new button 按钮
就会执行对应的node命令 node build/bin/new.js
,即就会运行build/bin/new.js
运行这个脚本,那可以打开看看这里有啥。
'use strict';
console.log();
process.on('exit', () => {
console.log();
});
if (!process.argv[2]) {
console.error('[组件名]必填 - Please enter new component name');
process.exit(1);
}
const path = require('path');
const fs = require('fs');
const fileSave = require('file-save');
const uppercamelcase = require('uppercamelcase');
const componentname = process.argv[2];
const chineseName = process.argv[3] || componentname;
const ComponentName = uppercamelcase(componentname);
const PackagePath = path.resolve(__dirname, '../../packages', componentname);
const Files = [
{
filename: 'index.js',
content: `import ${ComponentName} from './src/main';
/* istanbul ignore next */
${ComponentName}.install = function(Vue) {
Vue.component(${ComponentName}.name, ${ComponentName});
};
export default ${ComponentName};`
},
{
filename: 'src/main.vue',
content: `<template>
<div class="el-${componentname}"></div>
</template>
<script>
export default {
name: 'El${ComponentName}'
};
</script>`
},
{
filename: path.join('../../examples/docs/zh-CN', `${componentname}.md`),
content: `## ${ComponentName} ${chineseName}`
},
{
filename: path.join('../../examples/docs/en-US', `${componentname}.md`),
content: `## ${ComponentName}`
},
{
filename: path.join('../../examples/docs/es', `${componentname}.md`),
content: `## ${ComponentName}`
},
{
filename: path.join('../../examples/docs/fr-FR', `${componentname}.md`),
content: `## ${ComponentName}`
},
{
filename: path.join('../../test/unit/specs', `${componentname}.spec.js`),
content: `import { createTest, destroyVM } from '../util';
import ${ComponentName} from 'packages/${componentname}';
describe('${ComponentName}', () => {
let vm;
afterEach(() => {
destroyVM(vm);
});
it('create', () => {
vm = createTest(${ComponentName}, true);
expect(vm.$el).to.exist;
});
});
`
},
{
filename: path.join('../../packages/theme-chalk/src', `${componentname}.scss`),
content: `@import "mixins/mixins";
@import "common/var";
@include b(${componentname}) {
}`
},
{
filename: path.join('../../types', `${componentname}.d.ts`),
content: `import { ElementUIComponent } from './component'
/** ${ComponentName} Component */
export declare class El${ComponentName} extends ElementUIComponent {
}`
}
];
// 添加到 components.json
const componentsFile = require('../../components.json');
if (componentsFile[componentname]) {
console.error(`${componentname} 已存在.`);
process.exit(1);
}
componentsFile[componentname] = `./packages/${componentname}/index.js`;
fileSave(path.join(__dirname, '../../components.json'))
.write(JSON.stringify(componentsFile, null, ' '), 'utf8')
.end('\n');
// 添加到 index.scss
const sassPath = path.join(__dirname, '../../packages/theme-chalk/src/index.scss');
const sassImportText = `${fs.readFileSync(sassPath)}@import "./${componentname}.scss";`;
fileSave(sassPath)
.write(sassImportText, 'utf8')
.end('\n');
// 添加到 element-ui.d.ts
const elementTsPath = path.join(__dirname, '../../types/element-ui.d.ts');
let elementTsText = `${fs.readFileSync(elementTsPath)}
/** ${ComponentName} Component */
export class ${ComponentName} extends El${ComponentName} {}`;
const index = elementTsText.indexOf('export') - 1;
const importString = `import { El${ComponentName} } from './${componentname}'`;
elementTsText = elementTsText.slice(0, index) + importString + '\n' + elementTsText.slice(index);
fileSave(elementTsPath)
.write(elementTsText, 'utf8')
.end('\n');
// 创建 package
Files.forEach(file => {
fileSave(path.join(PackagePath, file.filename))
.write(file.content, 'utf8')
.end('\n');
});
// 添加到 nav.config.json
const navConfigFile = require('../../examples/nav.config.json');
Object.keys(navConfigFile).forEach(lang => {
let groups = navConfigFile[lang][4].groups;
groups[groups.length - 1].list.push({
path: `/${componentname}`,
title: lang === 'zh-CN' && componentname !== chineseName
? `${ComponentName} ${chineseName}`
: ComponentName
});
});
fileSave(path.join(__dirname, '../../examples/nav.config.json'))
.write(JSON.stringify(navConfigFile, null, ' '), 'utf8')
.end('\n');
console.log('DONE!');
复制代码
可以看到,这里接受了我们传递进来的两个参数 componentname
和 chineseName
, 然后将我们创建的组件添加到components.json
, 定义不同模板,用componentname
填充模板并自动新建文件到packages
,examples
,test/unit/specs
等目录下,一行命令,帮我们解决了新建各种文件,内容填充,规范化代码等许多问题,剩下的模板中的留白,比如单测模板中的留白,我们点击新建好的文件,直接继续编辑即可,是不是很方便。
所以整个核心思想就是, 用js脚本自动新建文件的可重复部分,在进行二次编辑。
既然要动态创建文件,那就需要有对应的配置文件,比如,关于组件的配置,可以放置在components.json
,关于router的配置,可以根据components.json
生成router.config.js
,
以vue项目为例,举个?
安装vue脚手架
sudo npm install -g @vue/cli
根据提示快速新建一个view项目
vue create hello-world
运行项目
npm run serve
删掉没有用处的文件
项目的根目录建一个Makefile
文件,
.PHONY: dist test
new:
node build/new.js $(filter-out $@,$(MAKECMDGOALS))
复制代码
编写new.js
'use strict';
const componentname = process.argv[2];
const chineseName = process.argv[3] || componentname;
const path = require('path');
const fs = require('fs');
const fileSave = require('file-save');
const render = require('json-templater/string');
const endOfLine = require('os').EOL;
const PackagePath = path.resolve(__dirname, './../src/views', `${componentname}.vue`);
const componentsFile = require('./config/components.json');
if (componentsFile[componentname]) {
console.error(`${componentname} 已存在.`);
process.exit(1);
}
const cleanAndWriteFile = (path, template) => {
fs.truncate(path, () => {
fs.writeFileSync(path, template);
});
}
// 添加到 components.json
componentsFile[componentname] = `./../views/${componentname}.vue`
fileSave(path.join(__dirname, './config/components.json'))
.write(JSON.stringify(componentsFile, null, ' '), 'utf8')
.end('\n');
// 添加到 router.config
let routes = []
let routerPath = path.join(__dirname, '../', 'src/router/router.config.js');
let routerItem = `
{
path: '/{{name}}',
name: '{{name}}',
component: () => import('{{path}}')
},`
// routerLink的数组
let routerLinkArr = [];
let routerLinkItem = `<router-link to="/{{routerName}}">{{routerName}}</router-link>`
let temp
Object.keys(componentsFile).forEach((key) => {
routes.push(render(routerItem,{
name: key,
path: componentsFile[key]
}))
temp = `
const routes = [${routes.join(endOfLine)}]
export default routes`
routerLinkArr.push(render(routerLinkItem,{
routerName: key,
}))
});
cleanAndWriteFile(routerPath,temp)
// 添加到views
let viewPath = path.join(__dirname, '../', 'src/router/router.config.js');
let viewItem = `
<template>
<div class=${componentname}>
<h1>${componentname}</h1>
</div>
</template>
`
fileSave(path.join(PackagePath))
.write(viewItem, 'utf8')
.end('\n');
// 添加APP.vue
let appPath = path.join(__dirname, '../', 'src/App.vue');
let AppTemp = `
<template>
<div id="app">
<nav>
${routerLinkArr.join(endOfLine)}
</nav>
<router-view/>
</div>
</template>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
nav {
padding: 30px;
}
nav a {
margin-left:20px;
font-weight: bold;
color: #2c3e50;
}
nav a.router-link-exact-active {
color: #42b983;
}
</style>
`
fileSave(appPath)
.write(AppTemp, 'utf8')
.end('\n');
复制代码
运行命令
make new abort
make new home
make new me
查看生成的目录
好了,整个就可以运行了
demo是简单的,思想是相通的,项目中有大量重复内容的,想自动生成配置文件的,以及一些脚手架等等,都有借助自动生成文件这种思想,抓出规律,让机器去执行,毕竟机器比人脑出错的概率要小些。
觉得有用的话,一键三连,DDDD