10分钟教你手搓一个bun-cli

251 阅读2分钟

image.png

前言

通常情况下,我们为了提高效率,通常会使用npm init < initializer > 来快速生成项目基础结构或添加额外的功能,例如create-react-app、@eslint/config,本文采用bun来实现类似的操作,让你快速拥有自己的增效工具(仅做创建模板示例)

环境搭建

1. 第一步我们需要翻一下npm官方的文档

image.png 可以看到npm init会对名为 create-xxx、@usr/create、@usr/create-xxx的包做额外的识别操作 (其他命名不能做npm init操作)

2. 知道前置条件后,我们直接登录github,创建一个create-bun-cli的空白仓库,随后clone进vscode并初始化一下

git clone https://github.com/1596944197/create-bun-cli.git

bun init -y

3. 既然我们是用bun来做cli,那肯定是需要检测是否具备bun环境,创建lib/test.ts文件

import { execSync } from "child_process";

function checkInstallation(command: string) {
  try {
    execSync(`${command} -v`, { stdio: "ignore" });
    return true;
  } catch {
    return false;
  }
}

const isBunInstalled = checkInstallation("bun");

if (!isBunInstalled) {
  throw new Error("Bun is not installed");
}

当前项目结构

.
├── README.md
├── bun.lockb
├── index.ts
├── lib
│   └── test.ts
├── package.json
└── tsconfig.json

在index.ts导入lib/test.ts文件 import "./lib/test.ts";

4. 既然是cli,那少不了交互式命令行工具,我们安装inquirer来实现

bun i inquirer && bun i -D @types/inquirer

5. 创建lib/inquirer文件

import inquirer from "inquirer";

/**@description 案例只做简单演示,详细请访问https://www.npmjs.com/package/create-bun-cli */
export default {
  ask: () => {
    return inquirer.prompt([
      // 是否初始化eslint
      {
        name: "init_eslint",
        type: "confirm",
        message: "是否初始化eslint",
      },
    ]);
  },
};

6. 再创建一个接受参数执行命令的文件lib/exec.ts

import { execSync } from "child_process";
import { writeFile } from "node:fs/promises";
import type inquirer from "./inquirer";

const eslintPath = "eslint.config.js";
export async function handleCommand<
  T extends Awaited<ReturnType<typeof inquirer.ask>>
>(params: T) {
  execSync("bun init -y", { stdio: "inherit" });

  if (params.init_eslint) {
    await writeFile(eslintPath, getEslintJson());

    /** @description 安装固定版本避免冲突 */
    await execSync(
      "bun i -D typescript-eslint@8 globals@15 eslint@9 @eslint/js@9",
      {
        stdio: "inherit",
      }
    );
  }
}

/** @description 模板文件 */
function getEslintJson() {
  return `
import pluginJs from "@eslint/js";
import globals from "globals";
import tsEslint from "typescript-eslint";

export default [
  { files: ["**/*.{js,mjs,cjs,ts}"] },
  { languageOptions: { globals: { ...globals.browser, ...globals.node } } },
  pluginJs.configs.recommended,
  ...tsEslint.configs.recommended,
];
`;
}

7. index.ts导入文件

import { handleCommand } from "./lib/exec";
import inquirer from "./lib/inquirer";
import "./lib/test";

const run = async () => {
  const credentials = await inquirer.ask();
  handleCommand(credentials);
};

run();

8. 我们创建一个打包文件用来将ts打包成js build.ts

import { execSync } from "child_process";

await Bun.build({
  target: "node",
  entrypoints: ["index.ts"],
  outdir: "dist",
});

// 此处必须添加 #! /usr/bin env xxx ,不然npm init时不会执行文件
execSync(
  `sleep 2 && echo #!/usr/bin/env node | cat - dist/index.js > temp && mv temp dist/index.js`,
);

9. package.json 增加对应的命令

// scripts打包用
// bin 是npm init 时会执行的操作
// repository 是发布npm包时需要
  "scripts": {
    "build": "bun run build.ts"
  },
  "bin": {
    "bun/cli": "./dist/index.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/1596944197/create-bun-cli.git"
  }

当前目录结构

├── README.md      
├── build.ts       
├── bun.lockb      
├── dist
│   └── index.js   
├── index.ts       
├── lib
│   ├── exec.ts    
│   ├── inquirer.ts
│   └── test.ts    
├── package.json
└── tsconfig.json

10. 到此基本完成了,我们把仓库publish到npm后,即可在任意位置进行npm init bun-cli

生产验证

我们直接开个空文件夹进行验证,可以看到正常生效

image.png

image.png

总结

  1. npm init 非常适合快速启动项目,只需要以create-xxx发布一个包就可以利用上
  2. 不仅仅是生成模板文件,也可以把npm init当成一个云脚本库

非简版demo仓库地址