新建项目是很繁琐的一项工作, 要考虑项目目录结构,基础库的配置等等。 前段时间因为需求使用了几款前端脚手架,包括yeoman、imweb-cli, 他们功能丰富 ,简单高效,其核心功能是根据用户选择的模板快速的新建一个完整的项目,也可以在其中做一些自定义的配置。 学会了使用架手架,便想了解脚手架到底是怎么工作的, 阅读了相关脚手架的源码后,对原理有了一点心得。
generators是yeoman生态系统的积木,通过yo命令运行而为终端用户生产文件,yeoman-generator本质上是一个有着完整项目结构的模板 , yo根据用户选择不同的generator生成不同的项目。
imweb-cli也是一个以模板为核心的脚手架, 通过定制不同的模板, 可以实现初始化项目以及为已有项目添加符合规范的文件片段,这一点在新建页面的时候特别有用。 想了解的同学可以访问(https://www.npmjs.com/package/imweb-cli)
了解了脚手架基本原理后,我们来尝试自己制作一款脚手架。首先要明白脚手架的核心是模板,什么是模板呢? 模板就是包含一个项目完整信息的目录结构。 脚手架提供了对模板增、删、改、查的功能及初始化项目必要的能力。
下面是自制脚手架的目录结构:
|__ bin
|__struct // 脚手架命令
|__struct-init
|__ lib
|__ add.js
|__ delete.js
|__ list.js
|__ node_modules
|__.gitignore
|__ package.json
|__ templates.json // 模板存放文件
|__ README.md
在bin目录下新建2个不带后缀名的文件, 命名为struct , 主要提供对模板操作的能力。
struct-init提供初始化项目的能力。
在package.json 中添加如下配置:
"bin": {
"struct": "bin/struct",
"struct-init": "bin/struct-init"
}
这样配置后, 便于在全局使用struct 和struct init 2个命令
bin目录下的struct文件, 提供了对模板的操作的功能。 下面是文件的初始化代码,
#!/usr/bin/env node //必须放到文件开头
const program = require('commander');
program
.version(require('../package').version) // 显示版本号
.usage('<command> [options]');
模板的的操作代码如下:
program
.command('list')
.description('显示所有模板')
.action(require('../lib/list'));
program
.command('add')
.description('添加模板')
.action(require('../lib/add'));
program
.command('delete')
// .alias('d')
.description('删除模板')
.action(require('../lib/delete'));
将具体的对模板添加、删除、显示的主题功能放在了lib下面。
运行 npm link 将脚手架挂在到全局 , 此时可以在命令行调试新建的脚手架框架了。
在任意目录下运行命令struct -h, 可以看到刚完成的脚手架命令可以在全局生效了。
E:\code>struct -h
Usage: struct [options]
Options:
-V, --version output the version number
-h, --help output usage information
Commands:
list 显示所有模板
add 添加模板
delete 删除模板
init 用一个模板生成项目
help [cmd] display help for [cmd]
在lib下新建 list.js , 在用户输入struct list命令时, 读取根目录下存储模板信息的文件template.json, 在终端显示,如下:
const fs = require('fs');
const chalk = require('chalk');
const tplJson = require(`${__dirname}/../template.json`);
module.exports = function() {
Object.keys(tplJson).forEach((item) => {
let tplData = tplJson[item];
console.log(' ' +
chalk.yellow('★') +
' ' + chalk.yellow(tplData.name) +
' - ' + tplData.description +
' - ' + chalk.red(`模板安装包${tplData.npm}`));
})
}
在lib下新建add.js,在用户输入struct add命令时 , 使用inquirer库向用户提问, 并获取输入,将新加的模板信息,写入template.json .
const fs = require('fs');
const path = require('path');
const { prompt } = require('inquirer');
const tplPath = path.resolve(__dirname, '../template.json');
const tplJson = require(tplPath);
const questions = [
{
type: 'input',
name: 'name',
message: '模板名称',
validate: function(val) {
if (!val) {
return '模板名称不为空'
} else {
return true;
}
}
},
{
type: 'input',
name: 'description',
message: '模板描述',
validate: function(val) {
if (!val) {
return '模板名称不为空'
} else {
return true;
}
}
},
{
type: 'input',
name: 'npm',
message: '模板包名称,(ps: 创建项目使用这个npm包)',
validate: function(val) {
if (!val) {
return '模板名称不为空'
} else {
return true;
}
}
}
];
module.exports = function() {
prompt(questions).then(function(data) {
tplJson[data.name] = {};
tplJson[data.name]['name'] = data.name;
tplJson[data.name]['description'] = data.description;
tplJson[data.name]['npm'] = data.npm;
fs.writeFile(tplPath, JSON.stringify(tplJson), 'utf-8', function(err, data) {
if (err) {
console.log('模板添加失败');
}
console.log('模板添加成功');
});
});
};
在lib下新建delete.js,在用户输入struct delete命令时 , 删掉template.json中的模板信息即可
module.exports = function() {
prompt(questions).then(function(data) {
delete tplJson[data.name];
fs.writeFile(tplPath, JSON.stringify(tplJson), 'utf-8', function(err, data) {
if (err) {
console.log('模板删除失败');
}
console.log('模板删除成功');
});
});
};
struct init 提供初始化项目的功能, 用法如下:
使用发布成npm包的制作好的模板,新建项目, 项目包括了模板的一切目录、结构等信息。
首先是struct-nint.js的初始化代码,就用户输入的一些判断
program
.usage('<template-name> [project-name]');
program.on('--help', function () {
console.log(' Examples:')
console.log()
console.log(chalk.yellow(' # 使用npm模板创建'))
console.log(' $ struct init template-name my-project')
console.log(' $ struct init webpack my-project')
console.log()
});
// init 命令的帮助文档
function help () {
program.parse(process.argv)
if (program.args.length < 1) return program.help()
}
help();
const template = program.args[0];
const dir = program.args[1];
const to = path.resolve(dir || '.');
// 模板不存在将不执行初始化项目的操作
if (!tplJson[template]) {
console.log(chalk.red(`template.json里没有${template}的模板信息,请添加!`));
return;
}
if (!dir || dir.indexOf('/') > -1) {
console.log(chalk.red('请输入项目名名称'));
return;
}
下面是新建项目的核心代码, 首先是下载模板到本地 , 然后根据模板信息, 生成新项目。
function run () {
console.log(chalk.yellow(`使用模板${template}创建项目`));
const spinner = ora('正在下载模板');
spinner.start();
// 下载模板到本地
exec(`tnpm i ${tplJson[template].npm}`, (err, data) => {
spinner.stop();
process.on('exit', () => {
rm(`${process.cwd()}/node_modules`)
})
if (err) {
console.log(chalk.red('模板下载失败 ', err.message));
}
const tplPath = `${process.cwd()}/node_modules/${tplJson[template].npm}`;
const opts = require(`${tplPath}/meta`);
const projectPath = `${process.cwd()}/${dir}`;
// 生成新项目
Metalsmith(`${tplPath}/template`)
.source('.')
.destination(`${projectPath}`)
.build(function(err) {
if (err) {
console.log(chalk.red('项目生成失败', err));
}
console.log(chalk.yellow(' \n 项目已创建'));
})
});
}
自此, 脚手架框架制作完成 。 可以根据自己的需要来定制模板,从而初始化不同的项目。
最后使用npm账号发布即可。(https://www.npmjs.com/package/struct-cli)