一款快速创建简单koa项目的脚手架工具---koa-setup

1,873 阅读7分钟

介绍

工作中常会使用一些类似vue-cli的脚手架工具,快速生成所需要的项目结构、文件以及一些基本的功能等。本文所介绍的koa-setup可以帮助我们快速生成一个简单的Node-koa的项目。

基本思想

回顾一下正常使用npm创建项目的步骤:

  1. 使用npm init命令初始化项目,此时会要求我们输入项目名称、版本号等内容;
  2. 完成后,会生成项目必需的文件package.json,里面包含项目的基本信息、配置以及项目所需要的第三方依赖包等;
  3. 再创建一个项目入口文件,index.js,即可完成一个简单项目的搭建;
  4. 如需搭建vue项目或node项目,可在当前基础上,添加相关对应配置文件、第三包依赖包等。

因此,我们要搭建一个koa项目快速创建的脚手架工具,这个脚手架工具所必须的功能就是帮我们创建package.json文件以及入口文件index.js,其次就是帮我们创建koa相关内容,以及下载koa所需要的第三方依赖包。

注:package.json详细内容可查看Node官方相关介绍

详细内容

koa-setup项目文件结构 image.png

1. koa-setup主入口js文件

该文件为本项目的主要文件,用于生成项目文件夹、index.js、package.json文件、以及第三方依赖的安装。

#! /usr/bin/env node
// 上面这句话非常重要
import fs from 'fs';
import chalk from 'chalk';// 用于日志打印的插件
import { execa } from 'execa';// 用于执行shell命令的插件

import createIndexTemplate from './createIndexTemplate.js';// 创建index.js
import createPackageTemplate from './createPackageTemplate.js';// 创建package.json

import question from './question/index.js';// 用户交互问题
import { createConfig } from './config.js';// 生成项目配置

const answer = await question(); // 获取用户交互的答案
const config = createConfig(answer); // 根据答案生成最终的配置文件

// 1.创建项目文件夹 =》 根据用户输入的项目名称
console.log(chalk.blue(`创建文件夹 -> ${config.packageName}`));
fs.mkdirSync(getRootPath());

// 2.创建入口文件 =》 index.js
console.log(chalk.blue('创建入口文件'));
fs.writeFileSync(`${getRootPath()}/index.js`, createIndexTemplate(config));

// 3.创建package.json
console.log(chalk.blue('创建package.json'));
fs.writeFileSync(`${getRootPath()}/package.json`, createPackageTemplate(config));

// 4.安装依赖
console.log(chalk.blue('安装依赖'));
execa('yarn', {
    cwd: getRootPath(),
    stdio: [2, 2, 2],
});

// 获取项目路径
function getRootPath() {
    return `./${config.packageName}`;
}

2. koa-setup项目配置文件

{
    "name": "koa-setup",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "bin": {
        "mykoa": "./bin/index.js"
    },
    "type": "module",
    "files": [
        "bin",
        "package.json"
    ],
    "scripts": {
        "test": "rm -rf hei  && node index.js",
        "test-g": "rm -rf hei  && koa-setup"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "dependencies": {
        "chalk": "^5.0.0",
        "ejs": "^3.1.6",
        "execa": "^6.0.0",
        "inquirer": "^8.2.0",
        "prettier": "^2.5.1"
    },
    "devDependencies": {
        "@types/node": "^17.0.8"
    }
}

3. 用户交互配置question文件夹

使用第三方库inquirer进行用户交互操作,要求用户输入项目名称、输入项目端口、选择项目中间件等操作。该插件提供了文本输入、数字输入、选择框选择、密码输入、记事本输入等功能,根据需要进行使用。

以下举例文本输入使用:

// bin/question/packageName.js
export default () => {
    return {
        type: 'input',
        name: 'packageName',
        message: 'set package name',
        validate(val) {
            if (val) return true;
            return 'Please enter package name';
        },
    };
};

最终bin/question/index.js中会抛出用户给出的选项供后续使用。

import inquirer from 'inquirer';
import packageName from './packageName.js';
import port from './port.js';
import middleware from './middleware.js';

export default () => {
    return inquirer.prompt([packageName(), port(), middleware()]);
};

由于inquirer插件最终给出的用户选项与所使用的的数据结构不同,通过config.js对用户选项进行重新组装抛出,得到用户的选项,并可用于创建内容使用。

// bin/config.js
export function createConfig(answer) {
    function haveMiddle(name) {
        return answer.middleware.indexOf(name) !== -1;
    }

    return {
        packageName: answer.packageName,
        port: answer.port,
        middleware: {
            static: haveMiddle('koaStatic'),
            router: haveMiddle('koaRouter'),
        },
    };
}

注:inquirer详细配置及其他使用方法可点击此处

4. 根据用户选项创建入口文件index.js和配置文件package.json

上面已经获取到用户交互过程中所输入或选择的内容,接下来就根据用户的答案来创建相关文件,主要通过createIndexTemplate.jscreatePackageTemplate.js,以及两个对应的模板,bin/template/index.ejs,bin/template/package.ejs来进行。

createIndexTemplate.js中,调用模板index.ejs,根据项目配置内容来创建index.js

// bin/createIndexTemplate.js

import ejs from 'ejs';
import fs from 'fs';
import prettier from 'prettier';
import path from 'path';
import { fileURLToPath } from 'url';

export default config => {
    const __dirname = fileURLToPath(import.meta.url);
    const indexTemplate = fs.readFileSync(path.resolve(__dirname, '../template/index.ejs'));

    const code = ejs.render(indexTemplate.toString(), {
        middleware: config.middleware,
        port: config.port,
    });
    return prettier.format(code, { parser: 'babel' });
};

使用ejs模板,根据传入的参数对中间件的需要进行控制,并且实现自行配置端口号的操作。

// bin/template/index.ejs
    const Koa = require('koa');
<% if (middleware.static) { %>
    const serve = require('koa-static');
<% } %>

<% if (middleware.router) { %>
    const Router = require('koa-router');
<% } %>
    const app = new Koa();

<% if (middleware.static) { %>
    app.use(serve(__dirname + '/static'));
<% } %>

<% if (middleware.router) { %>
    const router = new Router();
    router.get('/', ctx => {
        ctx.body = 'hello koa-setup';
    });
    app.use(router.routes());
<% } %>

    app.listen(<%= port %> , () => {
        console.log('open server localhost:<%= port %> ');
    });

同理,调用createPackageTemplate.js,来创建package.json,根据传入的参数对中间件进行控制,name字段使用用户输入的项目名称。

// bin/createPackageTemplate.js
import ejs from 'ejs';
import fs from 'fs';
import prettier from 'prettier';
import path from 'path';
import { fileURLToPath } from 'url';

export default config => {
    const __dirname = fileURLToPath(import.meta.url);
    const indexTemplate = fs.readFileSync(path.resolve(__dirname, '../template/package.ejs'));

    const code = ejs.render(indexTemplate.toString(), {
        packageName: config.packageName,
        middleware: config.middleware,
    });
    return prettier.format(code, { parser: 'json' });
};

// bin/template/package.ejs
{
    "name": "<%= packageName %>",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
    },
    "keywords": [],
    "author": "",
    "license": "ICS",
    "dependencies": {
        "koa": "^2.13.1"
    <% if (middleware.router) { %>
        ,"koa-router": "^10.0.0"
    <% } %>

    <% if (middleware.static) { %>
        ,"koa-static": "^5.0.0"
    <% } %>

    }
}

5. 应用

到此,整个项目基本已完成,核心内容就是为了生成index.jspackage.json,再安装好依赖,一个简单的koa项目就可以生成好了,接下来看一下如何应用:

本地测试:

开发完成后,可以在本地先进行测试,使用npm link命令将当前项目链接到npm全局后,即可在命令行窗口中使用全局命令koa-setup开始创建新的koa项目了。

注意:

  1. 这里使用的命令koa-setup,即为创建koa-setup项目中,package.json文件中的name字段。当然也可以在package.json文件中,通过bin字段进行命令指定,"bin":{"mykoa": "./bin/index.js"},重新执行npm link后,即可使用mykoa命令来创建项目。命令默认为name字段时,bin字段仍需指定执行的入口文件,"bin": "./bin/index.js"

  2. 在执行项目创建命令的时候,其实就是在执行./bin/index.js文件,正常情况下电脑无法直接js文件,所以我们必须告诉电脑以什么环境执行js文件,因此在index.js首行必须添加#! /usr/bin/env node,用来告诉电脑需要在node环境中去执行js文件。

  3. npm unlink moduleName,通过此命令可以解除项目到npm全局的链接。

npm包发布:

在本地测试无误后,也可以将最终的包发布到npm上,别人也可以通过npm下载并使用我们开发的工具了。

  1. 首先需要有npm的账号,可去npm官网进行注册;
  2. 其次在命令行内使用npm login命令,登录自己的npm账号;
  3. package.json文件中指定需要发布的文件;
"files": [
    "bin",
    "package.json"
],
  1. npm上不允许有同名的包存在,可以在发布前,去npm网站上,查看是否有同名的包;
  2. 使用npm publish命令即可将包发布到npm官网上。

总结

  1. 使用inquirer实现用户交互;

  2. 根据inquirer获取用户的输入,使用ejs模板,自动创建入口文件index.jspackage.json

  3. 使用execa插件执行yarn命令,完成依赖包安装。

项目代码:GitHub

扩展

  1. inquirer

    一款交互式的命令行工具,inquirer 会简化询问终端用户问题,解析,验证答案,提供错误反馈等等功能 。通过交互式的方式获取用户的相关信息。

  2. chalk

    一款用于修改控制台中打印信息样式的工具,可以修改字符的颜色等。

  3. execa

    execa是可以调用shell和本地外部程序的javascript封装。会启动子进程执行。支持多操作系统,包括windows。如果父进程退出,则生成的全部子进程都被杀死。

    本文中使用该工具,执行yarn命令实现项目依赖的安装。

  4. npm

    可在npm官网中查看详细内容,本文主要用的了npm linknpm publish等命令。

  5. esm模块化

    崔大的视频中详解讲解了关于esm模块化相关内容,有兴趣的可以看看。

  6. ora

    实现node.js命令行环境的loading效果,和显示各种状态的图标等。

  7. commander

    commander 的核心功能是解析命令行参数,例如:命令npm init -y中,对于-y的处理,就可以通过该工具来进行解析。脚手架工具中,通常也会使用类似的操作,来解析命令行中的参数,从而实现更加方便的功能。

以上是在开发一款脚手架工具的过程中,经常会用到的几款工具。使用相关工具,可以简化我们的开发,丰富脚手架的功能,从而比较便捷的输出一款方便、美观的脚手架工具。