Open LiangRongfu opened 4 years ago
最近在公司兼顾了三个项目的开发,跟不同的人合作发现很多不统一的问题:
等等这些问题,在从开发者的角度来说,先要花时间去熟悉其他人搭的项目,清楚代码之间的依赖才能才能真正的去完成开发任务,或许这里面花的时间不会很长,但是每个人都花了这样的时间,从公司角度来说那应该是一个很大的成本了,我们应该做的是去统一规范、统一架构。另外公司目前前端框架的选型统一是使用vue,当我们每次用vue-cli去初始化项目的时候,都要重新写一些公用的代码或者去旧项目copy,这时有一个专属于公司的一个模版会不会更好?从这里出发,我就查看了一些解决方案和脚手架的实现方式。
说到脚手架应该就会想到vue-cli 、 create-react-app 等等,实际上脚手架的作用是什么呢?简单来说就是在前端工作流中负责项目起始阶段创建初始文件。那这样是否可以把前言所述的问题,归类总结做成一个综合解决这些问题的项目模版,通过脚手架来初始化每个项目的初始文件呢?事实是可以的。那下面就明确需求,写一个自定义脚手架初始化项目文件。以下谨记录简单实现过程,重点是解决工作中存在的问题。
初始化项目后运行安装
npm install commander axios ora download-git-repo inquirer chalk metalsmith consolidate ncp
新建文件夹“xxx-cli”,进入到该目录
npm init -y # 初始化package.json
文件结构
├── bin │ └── www // 全局命令执行的根文件 ├── src │ ├── main.js // 入口文件 │ └── create.js // create │ └── constants.js // 存放常量 │── package.json
www文件中使用main作为入口文件,并且以node环境执行此文件
#! /usr/bin/env node require('../src/main.js');
设置在命令下执行xxx-cli时调用bin目录下的www文件,package.json中加入; ('xxx-cli'这个名字可以任意起,但如果需要发包需要到官网上验证是否已被用)
"bin": { "xxx-cli": "./bin/www" }
链接包到全局下使用
npm link
我们已经可以成功的在命令行中使用xxx-cli命令,并且可以执行main.js文件!
xxx-cli
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作用的表现。
xxx-cli --version
xxx-cli create
下面就重点来实现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 // 创建项目
一个简单的脚手架就这样实现了,虽然简单,但是在公司推行起来作用和效果还是蛮大的。
以上仅从个人遇到的问题的角度出发去实现,如有错误或不妥的地方请指出,感谢阅读。
在此之前vue-cli3已经有做了这个预设的功能,可以快速解决到我遇到的问题,脚手架是考虑到可以拉去vue以外的模版,同时建立公司的模版库
前言
最近在公司兼顾了三个项目的开发,跟不同的人合作发现很多不统一的问题:
等等这些问题,在从开发者的角度来说,先要花时间去熟悉其他人搭的项目,清楚代码之间的依赖才能才能真正的去完成开发任务,或许这里面花的时间不会很长,但是每个人都花了这样的时间,从公司角度来说那应该是一个很大的成本了,我们应该做的是去统一规范、统一架构。另外公司目前前端框架的选型统一是使用vue,当我们每次用vue-cli去初始化项目的时候,都要重新写一些公用的代码或者去旧项目copy,这时有一个专属于公司的一个模版会不会更好?从这里出发,我就查看了一些解决方案和脚手架的实现方式。
脚手架
说到脚手架应该就会想到vue-cli 、 create-react-app 等等,实际上脚手架的作用是什么呢?简单来说就是在前端工作流中负责项目起始阶段创建初始文件。那这样是否可以把前言所述的问题,归类总结做成一个综合解决这些问题的项目模版,通过脚手架来初始化每个项目的初始文件呢?事实是可以的。那下面就明确需求,写一个自定义脚手架初始化项目文件。以下谨记录简单实现过程,重点是解决工作中存在的问题。
第三方库(npm包)
初始化项目后运行安装
初始化项目
新建文件夹“xxx-cli”,进入到该目录
文件结构
www文件中使用main作为入口文件,并且以node环境执行此文件
设置在命令下执行xxx-cli时调用bin目录下的www文件,package.json中加入; ('xxx-cli'这个名字可以任意起,但如果需要发包需要到官网上验证是否已被用)
链接包到全局下使用
我们已经可以成功的在命令行中使用
xxx-cli
命令,并且可以执行main.js文件!了解commander 命令行工具
constants.js
main.js就是我们的入口文件
执行
xxx-cli --version
或xxx-cli create
是不是已经有一提示了!以上就是commander作用的表现。create命令
下面就重点来实现create的action的逻辑。为了后续的拓展,改造一下main.js,把create的逻辑写在create.js里
create功能需求:在命令行输入“xxx-cli create project-name”建立一个“project-name”的项目,项目里的文件是我们提前建好的template,可以选择不同的template。下面来实先create的操作:
现在需要在git上拉取项目模版,这里用axios去获取
create.js
以上创建项目的代码基本完成。
发布
发布后,安装
一个简单的脚手架就这样实现了,虽然简单,但是在公司推行起来作用和效果还是蛮大的。
以上仅从个人遇到的问题的角度出发去实现,如有错误或不妥的地方请指出,感谢阅读。