vue-cli, create-react-app、react-native-cli 等都是非常优秀的脚手架,通过脚手架,我们可以快速初始化一个项目,无需自己从零开始一步步配置,有效提升开发体验。尽管这些脚手架非常优秀,但是未必是符合我们的实际应用的,我们可以定制一个属于自己的脚手架(或公司通用脚手架),来提升自己的开发效率。
脚手架的作用
在开始之前,我们需要明确自己的脚手架需要哪些功能。vue init template-name project-name 、create-react-app project-name。我们这次编写的脚手架(eos-cli)具备以下能力(脚手架的名字爱叫啥叫啥,我选用了Eos黎明女神):
大家可以自行扩展其它的 commander,本篇文章旨在教大家如何实现一个脚手架。
关于这些第三方库的说明,可以直接npm上查看相应的说明,此处不一一展开。
创建一个空项目(eos-cli),使用 npm init 进行初始化。
npm install babel-cli babel-env chalk commander download-git-repo ini inquirer log-symbols ora
├── bin
│ └── www //可执行文件
├── dist
├── ... //生成文件
└── src
├── config.js //管理eos配置文件
├── index.js //主流程入口文件
├── init.js //init command
├── main.js //入口文件
└── utils
├── constants.js //定义常量
├── get.js //获取模板
└── rc.js //配置文件
├── .babelrc //babel配置文件
├── package.json
├── README.md
开发使用了ES6语法,使用 babel 进行转义
.bablerc
{
"presets": [
[
"env",
{
"targets": {
"node": "current"
}
}
]
]
}
node.js 内置了对命令行操作的支持,package.json 中的 bin 字段可以定义命令名和关联的执行文件。在 package.json中添加 bin 字段
package.json
{
"name": "eos-cli",
"version": "1.0.0",
"description": "脚手架",
"main": "index.js",
"bin": {
"eos": "./bin/www"
},
"scripts": {
"compile": "babel src -d dist",
"watch": "npm run compile -- --watch"
}
}
www 文件
行首加入一行 #!/usr/bin/env node 指定当前脚本由node.js进行解析
#! /usr/bin/env node
require('../dist/main.js');
开发过程中为了方便调试,在当前的 eos-cli 目录下执行 npm link,将 eos 命令链接到全局环境。
npm run watch
利用 commander 来处理命令行。
main
import program from 'commander';
import { VERSION } from './utils/constants';
import apply from './index';
import chalk from 'chalk';
/**
* eos commands
* - config
* - init
*/
let actionMap = {
init: {
description: 'generate a new project from a template',
usages: [
'eos init templateName projectName'
]
},
config: {
alias: 'cfg',
description: 'config .eosrc',
usages: [
'eos config set <k> <v>',
'eos config get <k>',
'eos config remove <k>'
]
},
//other commands
}
// 添加 init / config 命令
Object.keys(actionMap).forEach((action) => {
program.command(action)
.description(actionMap[action].description)
.alias(actionMap[action].alias) //别名
.action(() => {
switch (action) {
case 'config':
//配置
apply(action, ...process.argv.slice(3));
break;
case 'init':
apply(action, ...process.argv.slice(3));
break;
default:
break;
}
});
});
function help() {
console.log('\r\nUsage:');
Object.keys(actionMap).forEach((action) => {
actionMap[action].usages.forEach(usage => {
console.log(' - ' + usage);
});
});
console.log('\r');
}
program.usage('<command> [options]');
// eos -h
program.on('-h', help);
program.on('--help', help);
// eos -V VERSION 为 package.json 中的版本号
program.version(VERSION, '-V --version').parse(process.argv);
// eos 不带参数时
if (!process.argv.slice(2).length) {
program.outputHelp(make_green);
}
function make_green(txt) {
return chalk.green(txt);
}
download-git-repo 支持从 Github、Gitlab 下载远程仓库到本地。
get.js
import { getAll } from './rc';
import downloadGit from 'download-git-repo';
export const downloadLocal = async (templateName, projectName) => {
let config = await getAll();
let api = `${config.registry}/${templateName}`;
return new Promise((resolve, reject) => {
//projectName 为下载到的本地目录
downloadGit(api, projectName, (err) => {
if (err) {
reject(err);
}
resolve();
});
});
}
在用户执行 init 命令后,向用户提出问题,接收用户的输入并作出相应的处理。命令行交互利用 inquirer 来实现:
inquirer.prompt([
{
name: 'description',
message: 'Please enter the project description: '
},
{
name: 'author',
message: 'Please enter the author name: '
}
]).then((answer) => {
//...
});
在用户输入之后,开始下载模板,这时候使用 ora 来提示用户正在下载模板,下载结束之后,也给出提示。
import ora from 'ora';
let loading = ora('downloading template ...');
loading.start();
//download
loading.succeed(); //或 loading.fail();
index.js
import { downloadLocal } from './utils/get';
import ora from 'ora';
import inquirer from 'inquirer';
import fs from 'fs';
import chalk from 'chalk';
import symbol from 'log-symbols';
let init = async (templateName, projectName) => {
//项目不存在
if (!fs.existsSync(projectName)) {
//命令行交互
inquirer.prompt([
{
name: 'description',
message: 'Please enter the project description: '
},
{
name: 'author',
message: 'Please enter the author name: '
}
]).then(async (answer) => {
//下载模板 选择模板
//通过配置文件,获取模板信息
let loading = ora('downloading template ...');
loading.start();
downloadLocal(templateName, projectName).then(() => {
loading.succeed();
const fileName = `${projectName}/package.json`;
if(fs.existsSync(fileName)){
const data = fs.readFileSync(fileName).toString();
let json = JSON.parse(data);
json.name = projectName;
json.author = answer.author;
json.description = answer.description;
//修改项目文件夹中 package.json 文件
fs.writeFileSync(fileName, JSON.stringify(json, null, '\t'), 'utf-8');
console.log(symbol.success, chalk.green('Project initialization finished!'));
}
}, () => {
loading.fail();
});
});
}else {
//项目已经存在
console.log(symbol.error, chalk.red('The project already exists'));
}
}
module.exports = init;
eos config set registry vuejs-templates
config 配置,支持我们使用其它仓库的模板,例如,我们可以使用 vuejs-templates 中的仓库作为模板。这样有一个好处:更新模板无需重新发布脚手架,使用者无需重新安装,并且可以自由选择下载目标。
config.js
// 管理 .eosrc 文件 (当前用户目录下)
import { get, set, getAll, remove } from './utils/rc';
let config = async (action, key, value) => {
switch (action) {
case 'get':
if (key) {
let result = await get(key);
console.log(result);
} else {
let obj = await getAll();
Object.keys(obj).forEach(key => {
console.log(`${key}=${obj[key]}`);
})
}
break;
case 'set':
set(key, value);
break;
case 'remove':
remove(key);
break;
default:
break;
}
}
module.exports = config;
rc.js
.eosrc 文件的增删改查
import { RC, DEFAULTS } from './constants';
import { decode, encode } from 'ini';
import { promisify } from 'util';
import chalk from 'chalk';
import fs from 'fs';
const exits = promisify(fs.exists);
const readFile = promisify(fs.readFile);
const writeFile = promisify(fs.writeFile);
//RC 是配置文件
//DEFAULTS 是默认的配置
export const get = async (key) => {
const exit = await exits(RC);
let opts;
if (exit) {
opts = await readFile(RC, 'utf8');
opts = decode(opts);
return opts[key];
}
return '';
}
export const getAll = async () => {
const exit = await exits(RC);
let opts;
if (exit) {
opts = await readFile(RC, 'utf8');
opts = decode(opts);
return opts;
}
return {};
}
export const set = async (key, value) => {
const exit = await exits(RC);
let opts;
if (exit) {
opts = await readFile(RC, 'utf8');
opts = decode(opts);
if(!key) {
console.log(chalk.red(chalk.bold('Error:')), chalk.red('key is required'));
return;
}
if(!value) {
console.log(chalk.red(chalk.bold('Error:')), chalk.red('value is required'));
return;
}
Object.assign(opts, { [key]: value });
} else {
opts = Object.assign(DEFAULTS, { [key]: value });
}
await writeFile(RC, encode(opts), 'utf8');
}
export const remove = async (key) => {
const exit = await exits(RC);
let opts;
if (exit) {
opts = await readFile(RC, 'utf8');
opts = decode(opts);
delete opts[key];
await writeFile(RC, encode(opts), 'utf8');
}
}
npm publish 将本脚手架发布至npm上。其它用户可以通过 npm install eos-cli -g 全局安装。
即可使用 eos 命令。
本项目完整代码请戳: https://github.com/YvetteLau/
像我们熟悉的 vue-cli,react-native-cli 等脚手架,只需要输入简单的命令 vue init webpack project,即可快速帮我们生成一个初始项目。在实际工作中,我们可以定制一个属于自己的脚手架,来提高自己的工作效率。
安装生成器npm install express-generator -g,创建名称为APP的应用,在浏览器中使用 localhost:3000访问。注:该模板默认用的是 .jade 文件作为模板渲染 若要使用 ejs 可按照一下方法配置
本文通过一个简单的例子来告诉大家如何使用 Yeoman 快速创建脚手架。要了解更多 yeoman-generator 的开发与使用,可以参考社区里大家写的各类 generator。目前在 npm 上有超过 8000 个 yeoman-generator,也许就会有你的菜。
在前端工程化过程中,为了解决多项目中,相似度高的工作,便诞生许多前端脚手架,这里记录下自己实现一个简易前端脚手架过程的实践。主要是解决多个页面相似内容的复制粘贴问题
commander在Vue-cli、creat-app(react)中都起到了很大的作用,这种创建脚手架的方式与vue-cli的方式不同,vue-cli则是使用git远程拉取项目再完成初始化,这样一来要比这种更加的方便灵活,每次模板变更不需要再次上传包
NodeJs的出现,让前端工程化的理念不断深入,正在向正规军靠近。先是带来了Gulp、Webpack等强大的构建工具,随后又出现了vue-cli和create-react-app等完善的脚手架,提供了完整的项目架构
一直想建一个自己公司自用的脚手架,可以方便的快速开发。于是开始看vue-cli的源码和一些网上的教程。发现,一款脚手架其实很简单,主要原理就是从远程下载一个模板来新建一个项目。同时提供了一系列的交互来动态的更改模板。
脚手架在前端工作流中负责项目起始阶段创建初始文件。与其他功能模块不同的是,脚手架是一个完全“启下”的模块,它没有任何前置依赖。创建完成项目初始文件之后,脚手架就再无用武之地了。
前端开发都少不了对后台的调用,后台地址配置在哪里,是一个很纠结的问题 为此大家开动脑筋,想了不少办法:
构建库的常见方法有两种:一种是自己手动构建webpack库打包,设置output为 library; 另一种是基于vue-cli3输出库资源包。我们采用第二种vue脚手架的方式构建库。
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!