自制前端脚手架

前言

新建项目是很繁琐的一项工作, 要考虑项目目录结构,基础库的配置等等。 前段时间因为需求使用了几款前端脚手架,包括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个命令

对模板的操作——struct

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)

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏腾讯移动品质中心TMQ的专栏

Unittest实现H5页面接口功能测试

一、背景 目前主流的H5页面动态获取内容的方式是采用ajax异步请求后台数据实现实时刷新,实际上就是用GET/POST的HTTP请求后台接口,再将返回的数据(一...

2417
来自专栏黑泽君的专栏

Java主函数解释、java/javac命令解释、classpath解释

任何一段程序必须要有一个执行的起始点,有一个入口,这个入口就是主函数,本质上这个主函数就被虚拟机所调用。 即:主函数是一个入口、它被虚拟机所调用、有了主函数就能...

721
来自专栏北京马哥教育

一周文章推送:

文章名称:nginx + php-fpm fastcgi防止跨站、跨目录的安全设置 我们知道apache php mod的方式可以很方便的配置 open_bas...

2937
来自专栏喵了个咪的博客空间

phalapi-入门篇2(把它玩起来)

#phalapi-入门篇1(把它玩起来)# ? ##前言## 先在这里感谢phalapi框架创始人@dogstar,为我们提供了这样一个优秀的开源框架. 这一小...

3777
来自专栏技术小黑屋

Ruby执行shell命令的六种方法

在Ruby中,执行shell命令是一件不奇怪的事情,Ruby提供了大概6种方法供开发者进行实现。这些方法都很简单,本文将具体介绍一下如何在Ruby脚本中进行调用...

922
来自专栏一“技”之长

Git命令集之二——配置命令 原

Git初始化的.git目录中会自动创建一个config文件,这个文件中是对Git仓库做一些个性化的配置。Git的用户配置默认会从全局文件中继承,项目的confi...

653
来自专栏LEo的网络日志

shell技巧分享(四)

3468
来自专栏Hongten

python开发_python文件操作

官方API:os-Miscellaneous operating system interfaces

912
来自专栏大内老A

[WCF 4.0新特性] 默认绑定和行为配置

对于传统的WCF配置系统,无论是绑定的配置还是行为(服务行为和终结点行为)都必须具有一个名称。而正是通过整个配置名称,它们才能被应用到目标对象(终结点或者服务)...

17810
来自专栏xingoo, 一个梦想做发明家的程序员

Log4j官方文档翻译(二、架构设计)

log4j遵循层次化架构,每个层都有不同的对象来执行不同的任务。这种层次话的结构灵活设计、易于未来的扩展。 log4j框架中有两种对象: 核心对象:框架的...

1765

扫码关注云+社区