最近在公司兼顾了三个项目的开发,跟不一样的人合做发现不少不统一的问题:javascript
等等这些问题,在从开发者的角度来讲,先要花时间去熟悉其余人搭的项目,清楚代码之间的依赖才能才能真正的去完成开发任务,或许这里面花的时间不会很长,可是每一个人都花了这样的时间,从公司角度来讲那应该是一个很大的成本了,咱们应该作的是去统一规范、统一架构。另外公司目前前端框架的选型统一是使用vue,当咱们每次用vue-cli去初始化项目的时候,都要从新写一些公用的代码或者去旧项目copy,这时有一个专属于公司的一个模版会不会更好?从这里出发,我就查看了一些解决方案和脚手架的实现方式。前端
说到脚手架应该就会想到vue-cli 、 create-react-app 等等,实际上脚手架的做用是什么呢?简单来讲就是在前端工做流中负责项目起始阶段建立初始文件。那这样是否能够把前言所述的问题,归类总结作成一个综合解决这些问题的项目模版,经过脚手架来初始化每一个项目的初始文件呢?事实是能够的。那下面就明确需求,写一个自定义脚手架初始化项目文件。如下谨记录简单实现过程,重点是解决工做中存在的问题。vue
初始化项目后运行安装java
npm install commander axios ora download-git-repo inquirer chalk metalsmith consolidate ncp
复制代码
新建文件夹“xxx-cli”,进入到该目录node
npm init -y # 初始化package.json
复制代码
文件结构react
├── bin
│ └── www // 全局命令执行的根文件
├── src
│ ├── main.js // 入口文件
│ └── create.js // create
│ └── constants.js // 存放常量
│── package.json
复制代码
www文件中使用main做为入口文件,而且以node环境执行此文件ios
#! /usr/bin/env node
require('../src/main.js');
复制代码
设置在命令下执行xxx-cli时调用bin目录下的www文件,package.json中加入; ('xxx-cli'这个名字能够任意起,但若是须要发包须要到官网上验证是否已被用)git
"bin": {
"xxx-cli": "./bin/www"
}
复制代码
连接包到全局下使用github
npm link
复制代码
咱们已经能够成功的在命令行中使用xxx-cli
命令,而且能够执行main.js文件!vuex
constants.js
const { version } = require('../package.json');
/ 存储模板的位置
const downloadDirectory = `${process.env[process.platform === 'darwin' ? 'HOME' : 'USERPROFILE']}/.template`;
module.exports = {
version,
downloadDirectory,
};
复制代码
main.js就是咱们的入口文件
const program = require('commander');
const { version } = require('./constants');
program
.command('create')
.alias('c')
.description('to create a new project')
.action(() => {
console.log('create');
});
program.version(version)
.parse(process.argv); // process.argv就是用户在命令行中传入的参数
复制代码
执行 xxx-cli --version
或 xxx-cli create
是否是已经有一提示了!以上就是commander做用的表现。
下面就重点来实现create的action的逻辑。为了后续的拓展,改造一下main.js,把create的逻辑写在create.js里
// main.js
const program = require('commander');
const path = require('path');
const { version } = require('./constants');
const mapAction = { // 须要生成的指令数据
create: {
alias: 'c',
description: 'create a project',
examples: [
'xxx-cli create <project-name>',
],
},
'*': {
alias: '',
description: 'command not found',
examples: [],
},
};
Reflect.ownKeys(mapAction).forEach((action) => {
program
.command(action) // 命令名
.alias(mapAction[action].alias) // 命令别名
.description(mapAction[action].description) // 命令描述
.action(() => { // 命令执行的操做
if (action === '*') { // 命令不存在
console.log(mapAction[action].description);
} else {
require(path.resolve(__dirname, action))(...process.argv.slice(3)); // 引入命令对应操做模块
}
});
});
program.on('--help', () => { // help命令打印帮助信息
console.log('\nExample');
Reflect.ownKeys(mapAction).forEach((action) => {
mapAction[action].examples.forEach((item) => {
console.log(item);
});
});
});
program
.version(version)
.parse(process.argv);
复制代码
create功能需求:在命令行输入“xxx-cli create project-name”创建一个“project-name”的项目,项目里的文件是咱们提早建好的template,能够选择不一样的template。下面来实先create的操做:
如今须要在git上拉取项目模版,这里用axios去获取
npm insatll axios
复制代码
create.js
// create.js
const fs = require('fs');
const path = require('path');
const axios = require('axios');
const ora = require('ora');
const Inquirer = require('inquirer');
const { promisify } = require('util');
const chalk = require('chalk');
const MetalSmith = require('metalsmith');
let { render } = require('consolidate').els;
let downloadGitRepo = require('download-git-repo');
let ncp = require('ncp');
render = promisify(render);
downloadGitRepo = promisify(downloadGitRepo);
const { downloadDirectory } = require('./constants');
ncp = promisify(ncp);
// 获取仓库列表
const fetchRepoList = async () => {
// 获取当前组织中的全部仓库信息,这个仓库中存放的都是项目模板
const { data } = await axios.get('https://api.github.com/orgs/xxx/repos');// xxx表明某个仓库
return data;
};
// 获取选中模版的tags列表
const fechTagList = async (repo) => {
const { data } = await axios.get(`https://api.github.com/repos/xxx/${repo}/tags`);
return data;
};
// 封装loading
const waitFnloading = (fn, message) => async (...args) => { // 高阶函数
const spinner = ora(message);
spinner.start();
const result = await fn(...args);
spinner.succeed();
return result;
};
// 下载模版
const download = async (repo, tag) => {
let api = `xxx/${repo}`;
if (tag) {
api += `#${tag}`;
}
// /user/xxxx/.template/repo
const dest = `${downloadDirectory}/${repo}`;
await downloadGitReop(api, dest); // 下载模版存放到指定路径
return dest; // 下载的最终目录路径
};
// 逻辑主体部分
module.exports = async (projectName = 'my-project') => {
if (fs.existsSync(projectName)) { // 判断 projectName 文件夹是否存在?
console.log(chalk.red('Folder already exists.'));
} else {
// 1.获取组织下的全部模版;
let repos = await waitLoading(fetchRepoList, 'fetching template...')();
repos = repos.map((item) => item.name);
const { repo } = await Inquirer.prompt({ // 选择模版
name: 'repo',
type: 'list',
message: 'please choise a template to create project',
choices: repos,
});
// 2.获取当前选择项目的对应版本号
let tags = await waitLoading(fetchTagList, 'fetching tags...')(repo);
let result;
if (tags.length > 0) {
tags = tags.map((item) => item.name);
const { tag } = await Inquirer.prompt({ // 选择模版的版本号
name: 'tag',
type: 'list',
message: 'please choise tags to create project',
choices: tags,
});
result = await waitLoading(download, 'download template...')(repo, tag); // 下载模版,拿到缓存模版的路径
} else {
result = await waitLoading(download, 'download template...')(repo); // 下载模版,拿到缓存模版的路径
}
if (!fs.existsSync(path.join(result, 'ask.js'))) { // 是否须要输入信息
try {
await ncp(result, path.resolve(projectName)); // 把模版复制到projectName
console.log('\r\n', chalk.green(`cd ${projectName}\r\n`), chalk.yellow('npm install\r\n')); // 信息提示
} catch (error) {
console.log(error);
}
} else {
await new Promise((resolve, reject) => { // 须要用户输入信息
MetalSmith(__dirname)
.source(result)
.destination(path.resolve(projectName))
.use(async (files, metal, done) => {
const args = require(path.join(result, 'ask.js')); // 获取填写选项
const select = await Inquirer.prompt(args);
const meta = metal.metadata(); // 用户填写的结果
Object.assign(meta, select);
delete files['ask.js'];
done();
})
.use((files, metal, done) => { // 根据用户输入编写模版
const obj = metal.metadata();
Reflect.ownKeys(files).forEach(async (file) => {
if (file.includes('js') || file.includes('json')) {
let content = files[file].contents.toString();
if (content.includes('<%')) {
content = await render(content, obj);
files[file].contents = Buffer.from(content);
}
}
});
done();
})
.build((err) => {
if (err) {
reject();
} else {
console.log('\r\n', chalk.green(`cd ${projectName}\r\n`), chalk.yellow('npm install\r\n'));
resolve();
}
});
});
}
}
};
复制代码
以上建立项目的代码基本完成。
nrm use npm
npm publish
复制代码
发布后,安装
npm install xxx-cli -g // 安装脚手架
xxx-cli create projext-xxx // 建立项目
复制代码
一个简单的脚手架就这样实现了,虽然简单,可是在公司推行起来做用和效果仍是蛮大的。
以上仅从我的遇到的问题的角度出发去实现,若有错误或不妥的地方请指出,感谢阅读。