从ele源码学习到的小知识点

去年的时候,有尝试看过一会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!');
复制代码

可以看到,这里接受了我们传递进来的两个参数 componentnamechineseName, 然后将我们创建的组件添加到components.json, 定义不同模板,用componentname填充模板并自动新建文件到packages,examples,test/unit/specs等目录下,一行命令,帮我们解决了新建各种文件,内容填充,规范化代码等许多问题,剩下的模板中的留白,比如单测模板中的留白,我们点击新建好的文件,直接继续编辑即可,是不是很方便。

所以整个核心思想就是, 用js脚本自动新建文件的可重复部分,在进行二次编辑。

既然要动态创建文件,那就需要有对应的配置文件,比如,关于组件的配置,可以放置在components.json ,关于router的配置,可以根据components.json 生成router.config.js,

以vue项目为例,举个?

a.png

安装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

查看生成的目录

image.png

好了,整个就可以运行了

image.png

demo是简单的,思想是相通的,项目中有大量重复内容的,想自动生成配置文件的,以及一些脚手架等等,都有借助自动生成文件这种思想,抓出规律,让机器去执行,毕竟机器比人脑出错的概率要小些。

觉得有用的话,一键三连,DDDD

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享