前端如何搭建脚手架并在本地运行

准备工作

在开始搭建前,确保本机安装了node,为避免奇奇怪怪的问题 建议node版本16以上

搭建脚手架

使用过vue ,react,angular的同学都知道 ,应该对脚手架有一定的理解,比如vue-cli的 vue create myApp ,其中vue 就是vue-cli声明的一个命令,下来我们创建一个项目并声明自己的命令。

创建项目

创建一个空的文件夹 demo,并在demo下初始化npm, 其中 -y 表示都采用默认值,执行成功后会在该目录下 生成package.json文件

 npm init -y 

声明命令

1.在package.json中 添加 “bin” 字段,来声明命令,其中 create-demo 就是我们本次声明的命令,./bin/index.js 是我们运行 create 后执行的文件路径。

	"bin" :{
		"create-demo" : "./bin/index.js"	
	}
  1. 创建 bin/index.js , 在根目录下 创建bin 目录 ,并且创建index.js, 创建后的内容如下
	#!/usr/bin/env node
	console.log('简易脚手架demo')

#!/usr/bin/env node 其中 #! 代表标注文件可以当做脚本运行 /usr/bin/env 代表绝对地址 node 代表需要使用的执行器类型

搭建本地调式环境

创建完成后,我们不能直接执行create-demo命令,需要搭建一个本地调式的环境,这部分最简单的就是 npm link, 这个命令就是将本地的项目链接到全局,等同于 npm i -g xxx.

	npm link 

执行后如下

在这里插入图片描述

npm link 后不生效问题
这个问题是一般都是因为环境变量没有配置正确导致的 ,我们先检查下 当前npm 的配置 npm config list --global 找到 prefix 的配置路径,将其添加在本机的 环境变量中

这时候代表我们已经初始化成功,这个时候再去调用 create-demo,这个时候就可以看到之前在 bin 目录下打印的内容

在这里插入图片描述

校验node版本

刚开始我们说了,为避免不必要的麻烦,尽量要求用户nodejs版本16以上,这里我们可以用 semver 这个来做个校验。
创建util文件夹,这里文件夹主要放我们封装的一些工具函数, 创建 index.js

	const semver = require('semver');
	/**
	 * 检测nodejs版本 是否大于16
 	*/
	function checkNodeVersion() {
    	const   unSupportedVer = semver.lt(process.version, 'v16.0.0');
    	console.log(`当前node版本${process.version}`);
    	if(unSupportedVer){
        	throw new Error('Node.js 版本过低,推荐升级 Node.js 至 v16.0.0+')
    	}
	}
	
	module.exports = {
    	checkNodeVersion
	}

然后在 /bin/index.js 中添加

const { checkNodeVersion } = require('../util/index');

console.log('简易脚手架demo');
checkNodeVersion();
	

到这里我们一个简易的脚手架就搭建成功了,当然一个脚手架不止这些内容,比如与用户的交互,下载依赖等等,下面将一一讲述。

与用户的交互

命令参数的获取

Node.js 中的 process 模块提供了当前 Node.js 进程相关的全局环境信息。其中process.argv 表示用户输入的 命令行信息,我们可以简单打印下。

	console.log(process.argv)

打印结果如下
在这里插入图片描述
可以看到 其中 myApp 就是本次输入的内容,当然参数还有多种情况输入 比如下面这两种

	create-demo --name=myApp
	create-demo --name myApp

这个时候我们打印出来的信息也不同,意味这我们需要处理参数的多种情况,这里推荐一个插件minimist,来处理。

	npm  install minimist

通过上面的打印我们注意到,用户输入的从数组的第二个开始,这里我们处理下

	#!/usr/bin/env node
	const minimist = require('minimist');
	const { checkNodeVersion } = require('../util/index');

	console.log('简易脚手架demo');
	checkNodeVersion();
	console.log(minimist(process.argv.slice(2)))

使用结果如下,这样我们就处理了命令参数这块。
在这里插入图片描述
这里添加几个简单的命令 --h,--v 对应help和version,以及init,添加后的代码如下

在 bin/index.js 中

#!/usr/bin/env node
const minimist = require('minimist');
const { checkNodeVersion , getPkgVersion  } = require('../util/index');

function run (){
    checkNodeVersion();
    parseArgs();
}

// 处理用户命令行输入信息
function parseArgs(){
    const args = minimist(process.argv.slice(2),{
        alias :{
            version: "v",
            help: ["h"],
        }
    });
    // 这里指的是 用户输入的第二个命令  比如 vue create-app 中的 create-app
    const command = args._[0];
    if(command){
        switch (command) {
            case 'init':
                console.log('创建简易脚手架demo');
                break;
        
            default:
                break;
        }
    } else {
        if (args.h) {
            console.log('Usage: create-demo <command> [options]')
            console.log()
            console.log('Options:')
            console.log('  -v, --version       查看当前版本')
            console.log('  -h, --help          查看帮助')
            console.log()
            console.log('Commands:')
            console.log('  init                创建一个新的项目')
        } else if (args.v) {
          console.log(getPkgVersion());
        } 
    
    }
}

run();

在 util/index.js 中

const semver = require("semver");
const path = require('path');
/**
 * 检测nodejs版本 是否大于16
 */
function checkNodeVersion() {
  const unSupportedVer = semver.lt(process.version, "v16.0.0");
  console.log(`当前node版本${process.version}`);
  if (unSupportedVer) {
    throw new Error("Node.js 版本过低,推荐升级 Node.js 至 v16.0.0+");
  }
}

/**
 * 获取根目录
 */
function getRootPath() {
  return path.resolve(__dirname, "../");
}

/**
 * 查看当前版本信息
 */
function getPkgVersion(){
  return require(path.join(getRootPath(), 'package.json')).version
}

module.exports = {
    checkNodeVersion,
    getPkgVersion
}

来看下 输出结果

在这里插入图片描述
此时整个目录结构如下

  • demo
    • bin
      • index.js
    • util
      • index.js
    • package.json

询问式交互

一个好的脚手架,与用户的交互是少不了的,这里我们采用询问式交互, 一问一答的来收集用户选择的信息。比如npm init 通过询问式的交互来完成package.json 内容。

这里推荐使用 inquirer库来完成,这个相对来说功能比较齐全,比如对值的一些校验,过滤不必要的值等等。

npm i inquirer

我们这里 主要是通过 inquirer.prompt 来完成,prompt接收一个数组,其中每一项都是一个问题对象,每一项下面简单描述下问题对象参数的含义

参数名称值的含义类型
type问题的类型input | confirm | list | checkbox
name问题答案的变量string
message问题描述string
default默认值any
choices列表选项any[]
validate对当前值的校验Function
filter过滤所选值Fucnton

下来我们直接在刚刚的init里面,添加让用户输入项目名称的问题。

	        case 'init':
                console.log('创建简易脚手架demo');
                inquirer.prompt([{
                    type: "input",
                    name: "projectName",
                    message: "请输入项目名称!",
                }])
            break;

这个时候我们再去终端查看
在这里插入图片描述
当然一般创建项目肯定不止一个问题 ,下面我们完善下代码

把 问题部分抽离出成一个ask.js, 并添加其余情况的处理,该文件位于 bin 目录下
bin/ask.js

const inquirer = require("inquirer");
const fs = require("fs");

async function init(cb) {
  try {
    const answer = await ask();
    console.log(answer);
    cb(answer);
  } catch (err) {
    console.log(chalk.red("项目创建失败:", err));
  }
}
/**
 * 询问式收集信息
 * @returns 用户的答案信息
 */
async function ask() {
  let prompts = [];
  let ans = {};
  // 每一个问题一个函数
  askProjectName(ans, prompts);

  const answer = await inquirer.prompt(prompts);
  return answer;
}

/**
 * 创建的项目名称
 * @param {*} ans  答案信息
 * @param {*} prompts 问题对象
 */
function askProjectName(ans, prompts) {
  if (typeof ans.projectName !== "string") {
    prompts.push({
      type: "input",
      name: "projectName",
      message: "请输入项目名称!",
      validate(input) {
        if (!input) {
          return "项目名不能为空!";
        }
        if (fs.existsSync(input)) {
          return "当前目录已经存在同名项目,请换一个项目名!";
        }
        return true;
      },
    });
  } else if (fs.existsSync(ans.projectName)) {
    prompts.push({
      type: "input",
      name: "projectName",
      message: "当前目录已经存在同名项目,请换一个项目名!",
      validate(input) {
        if (!input) {
          return "项目名不能为空!";
        }
        if (fs.existsSync(input)) {
          return "项目名依然重复!";
        }
        return true;
      },
    });
  }
}

module.exports = {
	askInit
};

同时创建init.js,预留后续会有新的命令出现,这里把之前校验node版本的也放进来

bin/init.js

const { askInit  } = require('./ask');
const { checkNodeVersion  } = require('../util/index');

// 执行 init 命令后的处理
async function init(){
  	checkNodeVersion();
    await askInit();
}

module.exports = {
    init
}

bin/index.js中,引入init 在用户输入init后执行

	///...
	const { init } = require('./init');
	///...
	case 'init':
       init();
     break;

弄完我们试验下
在这里插入图片描述
到这里我们与用户交互大致就完成了。下来我们来创建项目。

创建项目

创建项目这里大部分都是复制文件去生成,但是因为我们搭建的是一个简易的脚手架,没有要生成的目录,所以这里我们直接去git上去 克隆一个项目来生成自己的项目。

这里我们直接 在终端输入 git clone 你的项目地址来完成克隆。这里 我们先用 child_process中的 exec 来实现

child_process 为node 内置的模块 ,无需额外下载 。 具体使用参考官网 https://nodejs.org/api/child_process.html

bin/init.js

const {askInit} = require("./ask");
const {checkNodeVersion} = require("../util/index");
const {exec} = require("child_process");
const chalk = require('chalk');

// 执行 init 命令后的处理
async function init() {
  checkNodeVersion();
  await askInit(createProject);
}

/**
 * 创建项目
 */
function createProject(answer) {
  const {projectName} = answer;
  exec(`git clone 你的项目地址 ${projectName}`, (err) => {
    if (err) {
      console.log(chalk.red(err));
    } else {
      console.log(chalk.green("项目创建成功,即将安装依赖"));
      // 安装依赖

    }
  });
}

module.exports = {
  init,
};

此时我们去一个新的目录打开终端,并输入 create-demo init,结果如下
在这里插入图片描述
这个时候项目已经创建成功了。下面将安装依赖

chalk : 命令行输出美化工具 这里我使用的是 3.0.0版本的

安装依赖

基于克隆项目,安装依赖这里就相对比较简单了,也是直接去掉终端。

/**
 * 创建项目
 */
function createProject(answer) {
  const {projectName} = answer;
  exec(`git clone git@gitee.com:leadersir/resume.git ${projectName}`, (err) => {
    if (err) {
      console.log(chalk.red(err));
    } else {
      console.log(chalk.green("项目创建成功,即将安装依赖"));
      // 安装依赖
      const command = `cd ${process.cwd()}/${projectName} && yarn`;
      install(command);
    }
  });
}

/**
 * 下载依赖
 */
function install(command) {
      const child = exec(command,(err)=>{
        if(err){
            console.log(chalk.red('安装项目依赖失败,请自行重新安装!'))
        } else {
            console.log(chalk.gray('安装成功'))
        }
    });
     // 输出安装的信息
    child.stdout.on('data',(data)=>{
        console.log(data);
    })
    // 输出安装的信息
    chalk.stderr.on('data',(data)=>{
        console.log(data);
    })
}

此时我们运行,就可以正常安装依赖了。当然安装依赖这里我们可以加个loading,让用户更直观的感受到,这里推荐使用开源库ora来实现加载动画。

	npm i ora@5.0.0

优化后代码如下

function install(command) {
    const installSpinner = ora(`正在安装依赖, 请耐心等待...`).start()
    const child = exec(command,(err)=>{
        if(err){
            installSpinner.fail(chalk.red('安装项目依赖失败,请自行重新安装!'))
        } else {
            installSpinner.succeed(chalk.gray('安装成功'));
        }
    });
    child.stdout.on('data',(data)=>{
        installSpinner.stop()
        console.log(data.replace(/\n$/, ''))
        installSpinner.start()
    })
    child.stderr.on('data',(data)=>{
        console.log(data.replace(/\n$/, ''))
        installSpinner.start()
    })
}

在这里插入图片描述
最后我们再给用户一个提示 说明项目已经创建成功了 ,可以开发了。

/**
 * 创建项目成功回调
 */
function callSuccess() {
  console.log(chalk.green(`创建项目成功!`));
  console.log(chalk.green(` 开始工作吧!😝`));
}

添加到依赖下载成功这里

	installSpinner.succeed(chalk.gray("安装成功"));
    callSuccess();

此时执行完成后 结果如下
在这里插入图片描述
到这里我们的脚手架就搭建成功了,最后我们发布到github 上

	npm publish

项目目录&依赖版本信息

在这里插入图片描述

常用依赖包

  • chalk : 命令行美化工具
  • inquirer : 命令行交互工具
  • minimist : 命令行处理工具
  • ora : 命令行加载美化工具
  • semver : 版本比较工具
  • request : 处理请求

结语

上面主要是讲述如何搭建一个脚手架,比较简单,但这些方法都比较常用,是搭建脚手架的基本功,希望对你有所帮助。以上代码已上传至 gitee中 ,大家可以下载实践。

  • 15
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值