手把手教你搭建属于自己的前端脚手架工具

前面的话

以前使用vue-clivue init webpack project-name一行命令就可以初始化一个我们自己的项目,小柒觉得好神奇,之后研究啦一下vue-cli,其实就是一个高级版的克隆。然后就决定自己实现一套属于自己的脚手架(xq-cli),目前xq-cli脚手架已经实现了有一段时间了,决定拿出来分享。

xq-cli脚手架功能

创建项目

  • 询问用户:询问用户新建项目的基本信息
  • 下载模板:根据用户的选择下载对应的模板
  • 生成配置文件:根据用户的选择生成对应的配置文件
  • 生成新项目:在目录中生成新项目

初始化项目

项目创建好后,我们要对它进行初始化操作:

  • 自动创建一个GitHub仓库,将我们的项目推送至仓库
  • 安装依赖

启动项目

  • 本地启动浏览
  • 支持热更新

打包项目

  • 打包我们的项目

前置知识

在开始实现时,我们必须要先了解一些工具:

commander:
  • 作用:命令行工具,用来编写指令和处理命令行的。

  • 用法:

    const program = require('commander')
    
    program
        .command('init')
        .alias('i')
        .description('初始化')
        .option('-x, --xxx', 'xxx') // 有参数时使用
        .action( () => {
         // 回调函数
        }
        })
        
    program.parse(process.argv)
    

vue init 就是这么来滴。

  • commander API 介绍
    • command - 自定义执行的命令,后面跟上自己定义的命令名称
    • alias - 定义更短的命令行(配置别名)(懒人必备)
    • description - 描述,它会在 --help里面显示
    • option - 定义参数,接受四个参数
    • action - 注册一个callback函数(执行命令之后,就会执行这个回调)
    • usage:用户使用提示
    • parse - 解析命令行,注意这个方法一定要放到最后调用
inquirer
  • 作用:这是一个强大的交互式命令行工具,询问用户就靠它啦。
  • 用法:
    onst inquirer = require('inquirer');
    inquirer
      .prompt([
        // 一些交互式的问题
      ])
      .then(answers => {
        // 回调函数,answers 就是用户输入的内容,是个对象
      });
    
    想一下,使用vue init webpack project-name时,是不是都要问你几个问题,项目名称、作者、描述等。
  • inquirer 功能简介
    • input - 输入
    • validate - 验证
    • list - 列表选项
    • comfirm - 提示
    • checkbox - 复选框
chalk
  • 作用:美化我们滴命令行,一个颜色插件,用来修改命令行输出样式,通过颜色区分 info、error 日志,清晰直观
  • 用法:
    const chalk = require('chalk');
    console.log(chalk.green('success')); 
    console.log(chalk.red('error'));
    
ora
  • 作用:用于显示加载效果,转圈圈滴那种

  • 用法:

    const ora = require('ora')
    let loading = ora('downloading template ...')
    loading.start()
    
log-symbols
  • 作用:控制台彩色日志符号,用来显示√ 或者 × 等图标。(搭配chalk使用)
  • 使用:
import symbol from 'log-symbols';
console.log(symbol.error, chalk.red('请输入项目名'));
console.log(symbol.success, chalk.green('配置文件更新完成'));
download-git-repo
  • 作用:用来下载远程模板的

  • 用法:

    	import downloadGit from 'download-git-repo';
         downloadGit(repository , ProjectName, options ,callback);
        // repository : 为远程仓库地址 
        // ProjectName:是存放下载的文件路径,可以直接是文件名,默认是当前目录
        // options: 一些选项,比如`{clone: boolean}`表示用http download
        // 还是 git clone 的形式下载
        // callback: 回调函数
    

准备工作

1、先创建一个文件夹,小柒这里叫xq-cli
2、创建.babelrc文件,支持es6语法。
3、在该目录下执行npm init,生成package.json文件,然后下载上面的依赖

"dependencies": {
	"babel-cli": "^6.26.0",
    "babel-env": "^2.4.1",
    "chalk": "^3.0.0",
    "commander": "^5.0.0",
    "download-git-repo": "^1.1.0",
    "inquirer": "^7.1.0",
    "ora": "^4.0.3",
    "log-symbols": "^3.0.0",
    "download-git-repo": "^3.0.2",
}

(有了package.josn文件,这样我们就可以发布我们的npm包了)。

4、新建一个bin文件夹,并在bin目录下新建一个无后缀名的cmd文件:

#!/usr/bin/env node
console.log('hello'); // 测试用

这个文件是我们整个脚手架的入口文件

再去package.json文件中配置bin参数,专门放置用户的自定义命令:
在这里插入图片描述
使用npm link命令将xq-clixqc软连接至全局,这样就可以全局使用xq-cli或者xqc这个命令来启动了。
在这里插入图片描述
(这里只是简单的测试一下)。

实际代码为:

#!/usr/bin/env node
require('../dist/main.js'); // 编译后的main.js文件作为入口文件

5、修改 package.json 中的 scripts 参数,指定可执行命令,实时编译脚本,让 node 能够识别并执行,开启watch实时监控。
在这里插入图片描述

6、补全目录结构
在这里插入图片描述

入口文件main.js

在整个脚手架的入口文件bin/cmd中,我们引入了require('../dist/main.js');这个编译后的文件,那我们就从src/main.js文件开始:

这个文件就是使用commander工具来定义我们的命令,主要代码:

  program
        .command(action)
        .description(actionMap[action].description)
        .alias(actionMap[action].alias)
        .action(()=>{
            switch (action) {
                case 'create':
                    create(...process.argv.slice(3));
                    break;
                case 'init':
                    init(program.username, program.token);
                    break;
                case 'dev': 
                    dev(program.port);
                    break;
                case 'build': 
                    build();
                    break;
                default:
                    break;
            }
        })
 });
// 项目版本
program
    .version(require('../package.json').version, '-v --version')
    .parse(process.argv);

action就是我们的命令,这里用四个命令,也就是对应脚手架的4个功能:create、init、dev 、build它们对应的回调函数分别在create.jsinit.jsdev.jsbuild.js。下面分别来实现这个几个功能。

创建项目(xq-cli create)

项目创建思路:

  • 项目创建命令必须输入新建项目名称(必须做一层判断,避免创建的项目名称覆盖已有项目)

    // 文件是否存在
    let isExist = async(name) => {
        return new Promise((resolve) => {
            if(fs.existsSync(name)) {
                console.log(symbol.error, chalk.red('文件夹名已被占用,请更换名字重新创建'))
            }else{
                resolve();
            }
        });
    }
    
    
  • 询问用户,引导用户输入配置信息

    // 询问用户
    let promptList = [
        {
            type: 'list',
            name: 'frame',
            message: 'please choose this project template',
            choices: ['vue', 'react']
        },
        {
            type: 'input',
            name: 'description',
            message: 'Please enter the project description:'
        },
        {
            type: 'input',
            name: 'author',
            message: 'Please enter the author name:'
        }
    ];
    
    let prompt = ()=>{
        return new Promise((resolve)=>{
            // inquirer提供prompt函数来实现询问,其参数为数组,询问将按数组的顺序来
            inquirer.prompt(promptList)
                .then(answer => {
                    resolve(answer);
                })
        });
    }
    
  • 下载模板,下载模板比较耗时,可以通过ora提示用户 正在下载模板,下载完毕之后给出提示。

    // 项目模块远程下载
    let downLoadTemplate = async (ProjectName ,api) => {
        return new Promise((resolve, reject) => {
            downloadGit(api, ProjectName, {clone: true}, (err) => {
                if(err) {
                    reject(err);
                }else{
                    resolve();
                }
            });
        });
    };
    
  • 项目下载完毕后,更新配置文件

    // 更新json配置文件
    let updateJsonFile = (fileName, obj) => {
        return new Promise((resolve) => {
            if(fs.existsSync(fileName)) {
                // 读出模板下的package.json文件
                const data = fs.readFileSync(fileName).toString();
                // 转为json对象
                let json = JSON.parse(data);
                // 将用户输入的更新到模板package.json文件
                Object.keys(obj).forEach(key => {
                    json[key] = obj[key];
                });
                // 重写模板下的package.json文件
                fs.writeFileSync(fileName, JSON.stringify(json, null, '\t'), 'utf-8');
                resolve();
            }
        });
    }
    
    

    上面的工具方法都在unit.js代码中,具体调用都在create.js中,可以到create.js文件中查看源码。

项目初始化(xq-cli init )
curl -u username:token https://api.github.com/user/repos -d "{\"name\": \"project name \"}"

使用这条命令就可以自动生成仓库,不用手动再去创建了。这里的username 就是你Github的用户名,token在Github上生成:(这里我的是windows系统,不支持单引号,所以要用转义符)
在这里插入图片描述
然后会有一个一长串数字字符串就是token了。

初始化项目的思路:

  • Git初始化
  • 创建Github仓库
  • 关联Github仓库
  • 更新package.json的repository配置
  • 提交代码到GitHub仓库
  • 安装依赖
let init = async (username, token)=>{
    
    try {
        await loadCmd(`git init`,'git初始化');
        if(username === '' || token === '') {
            console.log(symbol.warning, chalk.yellow('缺少参数无法创建远程仓库'));
        }else {
            const projectName = process.cwd().split('\\').slice(-1)[0];
            await loadCmd(`curl -u qjk-xiaoqi:a97fc56cbefe4bdc092490067bb1a9727615a583 https://api.github.com/user/repos -d "{\"name\": \"kkk\"}"`, 'Github仓库创建');
            // curl -u qjk-xiaoqi:a97fc56cbefe4bdc092490067bb1a9727615a583 https://api.github.com/user/repos -d "{\"name\": \"auto\"}"
            await loadCmd(`git remote add origin https://github.com/${username}/${projectName}.git`, '关联远端仓库')
            let loading = ora();
            loading.start(`package.json更新repository:命令执行中...`);
            await updateJsonFile('package.json', {
                "repository": {
                    "type": "git",
                    "url": `https://github.com/${username}/${projectName}.git`
                }
            }).then(() => {
                loading.succeed(`package.json更新repository: 命令执行完成`);
            });
            await loadCmd(`git add .`,"执行 git add");
            await loadCmd(`git commit -a -m "init"`, '执行git commit')
            await loadCmd(`git push --set-upstream origin master`, '执行git push')           
        }
        await loadCmd(`npm install`,"安装依赖");
    }catch(err) {
        console.log(symbol.error, chalk.red('初始化失败'));
        console.log(symbol.error, chalk.red(err));
        process.exit(1);
    }

}

let loadCmd = async(cmd, text) =>{
    let loading = ora();
    loading.start(`${text}: 命令执行中...`);
    await child.exec(cmd);
    loading.succeed(`${text}: 命令执行完成`)
}

相关代码在:init.js文件中。

运行命令:xq-cli init -u xxx -t xxx就可以了:
在这里插入图片描述

项目启动 (xq-cli dev )

项目启动就要用我们的Webpack啦,将项目的webpack.config.js 配置好。使用open-browser-webpack-plugin插件来打开了页面, 并且支持热更新。

const config = require('./webpack.config.js');

let dev = (port) => {
    // 启动项目后自动打开浏览器
    config.plugins.push(new OpenBrowserPlugin({ url: `http://localhost:${port}` }))
    // 创建一个小型服务器
    new WebpackDevServer(webpack(config), {
        contentBase: './public',// 配置http服务器的文件目录
        hot: true, // 开启模块热替换
        historyApiFallback: true // 开启html5 History API网页
    }).listen(port, 'localhost', function (err, result) {
        if (err) {
            console.log(err);
        }
    });
}

在这里插入图片描述

项目打包(xq-cli build)

这个就比较简单了,直接只用webpack

let build = () => {
    // 打包
    webpack(config, function(err, stats) {
        process.stdout.write(stats.toString({
            colors: true,
            modules: false,
            children: false,
            chunks: false,
            chunkModules: false
        }))
        console.log( symbol.success, chalk.green(' 打包完成'));
        process.exit(1);
    })
}

执行命令:xq-cli build
在这里插入图片描述

发布npm包

最后就可以发布我们自己的npm包啦,删掉创建的文件,去官网创建一个账号,就可以发布啦。

npm login登录,npm publish发布,之后就可以去官网上查看了:
(xq-cli已经被使用了,所以该为xqc)
在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章