14.4 企业脚手架 ---雏形

46 阅读3分钟

企业脚手架-雏形

1. 基础修改

packages\cli\bin\wzmserve

import { runCLI } from '../dist/index.js'
runCLI()
console.log('bin执行目录文件')

packages\cli\src\cli.ts

/*

*/
const create = (...args) => {
  console.log("初始化项目create", args);
};
const build = (...args) => {
  console.log("构建项目build", args);
};
const serve = (...args) => {
  console.log("启动项目serve", args);
};
export const run = (args) => {
  const [, , ...runargs] = args;
  const [command, ...commandArgs] = runargs;
  console.log("command参数:", command);
  console.log("cli-获取参数:", runargs);
  switch (command) {
    case "create":
      console.log("create参数:", commandArgs);
      create(...commandArgs);
      break;
    case "build":
      build(...commandArgs);
      break;
    case "serve":
      serve(...commandArgs);
      break;
    default:
      console.log("请输入正确的命令", ...commandArgs);
      break;
  }
};


packages\cli\src\index.ts

import { run } from "./cli";

// 入口文件保持足够的整洁,方便后续扩展,给外部暴露内部的api
export const defineConfig = () => {};

export const runCLI = () => {
  // 获取配置信息
  run(process.argv);
};

执行命令 wzm-cli-demo create test -r "wzm"

command 参数: create
cli-获取参数: [ 'create', 'test', '-r', 'wzm' ]
create 参数: [ 'test', '-r', 'wzm' ]
初始化项目 create [ 'test', '-r', 'wzm' ]
bin 执行目录文件

2.增加commander

pnpm i commander -D

  1. tsup.config.t
import { defineConfig } from "tsup";

export default defineConfig({
  dts: true, // 在dist目录下生成dts文件
  clean: true,
  entry: ["src/index.ts"],
  format: ["cjs"], // 属性有esm, cjs, iife  默认是esm
  outDir: "dist",
  platform: "node",
  target: "node22",
  // 不要将 ESM-only 依赖打包进来,避免 import.meta 丢失
  external: [
    "commander",
    "events",
    "path",
    "fs",
    "util",
    "os",
    "stream",
    "child_process",
    "http",
    "https",
    "url",
    "net",
    "dns",
    "zlib",
    "crypto",
    "tls"
  ]
});

2.wzmserve

import { runCLI } from "../dist/index.cjs";
runCLI();
console.log("bin执行目录文件");

3.cli.ts

import { program } from "commander";
import { create } from "./commands/base/create";
import { build } from "./commands/base/build";
import { serve } from "./commands/base/serve";
// const create = () => {
//   console.log("初始化项目create");
// };
// const build = (...args) => {
//   console.log("构建项目build", args);
// };
// const serve = (...args) => {
//   console.log("启动项目serve", args);
// };

/**
export const run = (args) => {
    const [, , ...runargs] = args;
    const [command, commandArgs] = runargs;
  console.log("command参数:",command);
  console.log("cli-获取参数:",runargs);
  switch (command) {
    case "create":
      create(commandArgs);
      break;
    case "build":
      build(commandArgs);
      break;
    case "serve":
      serve(commandArgs);
      break;
    default:
      console.log("请输入正确的命令", commandArgs);
      break;
  }
}

**/
program.version("0.0.1").name("wzm-cli");

program.command("create").description("创建项目").action(create);
program.command("build").description("打包项目").action(build);
program.command("serve").description("构建项目").action(serve);
export const run = (args) => {
  const [, , ...runargs] = args;
  const [command, commandArgs] = runargs;
  console.log("cli-command参数:", command);
  console.log("cli-command-获取参数:", commandArgs);
  switch (command) {
    case "create":
      create(commandArgs);
      break;
    case "build":
      build(commandArgs);
      break;
    case "serve":
      serve(commandArgs);
      break;
    default:
      console.log(`${command}命令有误,请输入正确的命令, `);
      break;
  }
};
  1. build.ts,serve.ts, build.ts文件内容如下:
import { Command } from "commander";
// import { logger } from '../../utils/logger'
export const build = (program: Command) => {
  console.info("-----build-构建项目------", program);
  //   return program
  //     .createCommand("build")
  //     .description("构建项目")
  //     .action(() => {
  //       console.info("构建项目");
  //     });
};

3.创建packages\cli\src\commands\base\greet.ts,

  1. prompts
  2. boxen
  3. chalk
  4. gradient-string
  5. ora
  6. figlet
  7. cli-table
import { Command } from "commander";
import { logger } from "../../utils/logger";
import prompts from "prompts";
import boxen from "boxen";
import chalk from "chalk";
import gradient, { rainbow, pastel } from "gradient-string";
import ora from "ora";
import figlet from "figlet";
import Table from "cli-table";
import standard from "figlet/importable-fonts/Standard.js";
figlet.parseFont("Standard", standard);
export const greet = async (program: Command) => {
  logger.info("初始化项目greet", program);

  const nameRes = await prompts({
    type: "text",
    name: "name",
    message: "请输入你的名字"
  });
  const hobbyRes = await prompts({
    type: "select",
    name: "hobby",
    choices: [
      { title: "篮球", value: "football" },
      { title: "足球", value: "basketball" },
      { title: "游泳", value: "swimming" }
    ],
    message: "请输入你的爱好"
  });
  logger.info(`Hello ${nameRes.name},你爱好的是${hobbyRes.hobby}`);

  logger.info(
    boxen("boxen 盒子模型 恭喜发财,红包拿来", {
      borderColor: "blue", // 'black' 'red' 'green' 'yellow' 'blue' 'magenta' 'cyan' 'white' 'gray' or a hex value like '#ff0000'
      borderStyle: "double", // string| object // signal 'round' 'double' 'bold' 'singleDouble', 'doubleSingle', 'classic','arrow','none'
      /* {
                        topLeft: '+',
                        topRight: '+',
                        bottomLeft: '+',
                        bottomRight: '+',
                        top: '-',
                        bottom: '-',
                        left: '|',
                        right: '|'
                    }*/
      dimBorder: false, // 边框是否为 dim
      title: "文本描述展示", // 居中展示
      titleAlignment: "center", // 'left' 'center' 'right'
      width: 50,
      height: 10,
      // fullscreen: (width, height) => [width, height - 100], // 宽高
      padding: 1,
      margin: 1,
      float: "left", // 'left' 'center' 'right',
      backgroundColor: "yellow", // 'black' 'red' 'green' 'yellow' 'blue' 'magenta' 'cyan' 'white' 'gray' or a hex value like '#ff0000'
      textAlignment: "left" // Values: 'left' 'center' 'right'
    })
  );
  console.log(
    boxen("boxen 盒子模型", { title: "magical", titleAlignment: "center" })
  );
  console.log(
    boxen(chalk.blue("boxen 盒子模型+chalk"), {
      padding: 1,
      margin: 1,
      borderStyle: "double",
      borderColor: "red"
    })
  );

  // console.log(chalk.blue('Hello world!'))
  console.log(pastel("I love gradient-string!"));
  console.log(rainbow("It is so pretty! 🌈"));
  const duck = gradient(["green", "yellow"]).multiline(`
  __
<(o )___
 ( ._> /
   ---
`);
  console.log(duck);
  console.log(rainbow.multiline("多个颜色渐变\n变化不同"));
  console.log(
    gradient(["cyan", "pink"], { interpolation: "hsv" }).multiline(
      "Multi line\nstring"
    )
  );
  const spinner = ora("加载中").start();
  console.log("========");
  setTimeout(() => {
    spinner.color = "yellow";
    // spinner.text = 'Loading rainbows'
    spinner.prefixText = "前缀";
    spinner.suffixText = "后缀";
    spinner.text = chalk.red("Loading rainbows");
    spinner.succeed("加载成功完成");
  }, 1000);
  //
  // ASCII艺术字
  const asciiArt = figlet.textSync("My App", { font: "Standard" });

  // 渐变标题
  const title = gradient.rainbow("欢迎使用我的应用程序");

  // 信息表格
  const table = new Table({
    head: ["TH 1 label", "TH 2 label"],
    colWidths: [40, 50]
  });
  table.push({ 版本: "1.0.0" }, { 作者: "开发者" }, { 许可证: "MIT" });

  // 使用boxen包装
  const message = `${chalk.bold(asciiArt)}\n\n${title}\n\n${table.toString()}`;

  console.log(
    boxen(message, {
      titleAlignment: "center",
      padding: 1,
      borderStyle: "double",
      borderColor: "cyan",
      backgroundColor: "#222"
    })
  );
};

4. 丰富命令

  1. cli文件夹下执行命令 pnpm i fs-extra picocolors -D
  2. packages\cli\src\cli.ts
// packages\cli\src\cli.ts
import { program } from "commander";

import "./commands";

export const defineConfig = () => {
  //
};

export const runCLI = () => {
  program.parse(process.argv);
};
  1. packages\cli\src\index.ts
export * from "./cli";

  1. packages\cli\src\commands\registerCommand.ts
// packages\cli\src\commands\registerCommand.ts
import type { Command } from "commander";
import { program } from "commander";

type Fn = (p: Command) => Command;

/**
 * register new command
 * @param program
 * @param command
 */
// 负责插件的注册逻辑
export const registerCommand = (fn: Fn) => {
  program.addCommand(fn(program));
};
  1. packages\cli\src\utils\loadTemplate.ts
// packages\cli\src\utils\loadTemplate.ts

// 本地模板,fs操作
// import {readFile} from 'fs-extra'
import { copy, emptyDir } from "fs-extra";
import { join } from "node:path";

// readFile(join(__dirname,'../../cli/package.json')).then(data=>{
//     console.log('加载本地脚手架',data.toString())
// })
// wzm-cli-new>wzm-cli-demo2 create wzm-demo --template template-ssr-vue
export const loadTemplate = async (
  projectName: string,
  templateName: string
) => {
  // 1. 获取模板路径
  const templatePath = join(__dirname, `../templates/${templateName}`);
  // console.log('templatePath',templatePath)
  const targetPath = `${process.cwd()}/wzm-demo`;
  // 2. 清空目标路径
  await emptyDir(targetPath);
  // 3. 拷贝模板到目标路径
  await copy(templatePath, `${process.cwd()}/${projectName}`);
  console.log("模板加载完成");
};
  1. packages\cli\templates在此文件下增加模板文件,如 template-ssr-vue

  2. 执行命令 wzm-cli-demo create ssr-vue3 在同级目录下会创建一个ssr-vue3文件夹,完成了脚手架的创建

  3. 增加好看的颜色设置

    • 修改packages\cli\src\commands\base\create.ts文件
// packages\cli\src\commands\base\create.ts
import { Command } from "commander";
import { logger, figlet, pastel, gradient, rainbow } from "../../utils/logger";
import { loadTemplate } from "../../utils/loadTemplate";
import prompts from "prompts";

// ASCII艺术字
const asciiArt = figlet.textSync("Welcome", { font: "Standard" });
const welcomeMsg = `${asciiArt}`;

// wzm-cli-new>wzm-cli-demo2 create wzm-demo --template template-ssr-vue
export const create = (program: Command) => {
  return program
    .createCommand("create")
    .arguments("<project-name>")
    .option("-f, --template <template>", "template name")
    .description("初始化创建项目")
    .action(async (projectName, options) => {
      logger.log(welcomeMsg);
      logger.log(pastel(welcomeMsg));
      let { template } = options;
      // 如果未指定模板,则提示用户选择
      if (!template) {
        const response = await prompts({
          type: "select",
          choices: [
            {
              title: `${pastel("template-ssr-vue-模板")}?`,
              value: "template-ssr-vue"
            },
            {
              title: `${pastel("template-ssr-vue-ts-模板")}?`,
              value: "template-ssr-vue-ts"
            }
          ],
          name: "selectTemplate",
          message: `${pastel("选择你需要的脚手架")}?`
        });
        template = response.selectTemplate;
        const duck = gradient(["green", "yellow"]).multiline(`
__
<(o )___
( ._> /
 ---
`);
        logger.log(duck);
        console.log(rainbow.multiline(`手动选择了模板:${template}`));
      }
      // logger.info(pc.bgCyan("初始化创建项目"));
      // logger.info(
      //   pc.bgCyan(`name: ${projectName}, options: ${JSON.stringify(options)}`)
      // );
      // loadTemplate('template-ssr-vue')
      loadTemplate(projectName, template);
    });
};
  * `packages\cli\src\utils\logger.ts`
import { createConsola } from "consola";
import boxen from "boxen";
import chalk from "chalk";
import gradient, { rainbow, pastel } from "gradient-string";
import ora from "ora";
import figlet from "figlet";
import Table from "cli-table";
import standard from "figlet/importable-fonts/Standard.js";
figlet.parseFont("Standard", standard);

export const logger = createConsola({});

export {
  boxen,
  chalk,
  gradient,
  rainbow,
  pastel,
  ora,
  figlet,
  Table,
  standard
};
  1. 优化packages\cli\src\commands\base\create.ts
// packages\cli\src\commands\base\create.ts
import { Command } from "commander";
import {
  logger,
  figlet,
  pastel,
  gradient,
  rainbow,
  ora,
  loggerConsts,
  chalk
} from "../../utils/logger";
import { loadTemplate } from "../../utils/loadTemplate";
import prompts from "prompts";

// ASCII艺术字
const asciiArt = figlet.textSync("Welcome", { font: "Standard" });
const welcomeMsg = `${asciiArt}`;

// wzm-cli-new>wzm-cli-demo2 create wzm-demo --template template-ssr-vue
/**
 * 创建一个新项目
 *
 * @param program Commander实例
 * @returns 返回配置好的program实例
 */
export const create = (program: Command) => {
  return program
    .createCommand("create")
    .arguments("[project-name]")
    .option("-f, --template <template>", "template name")
    .description("初始化创建项目")
    .action(async (projectName, options) => {
      if (!projectName) {
        const response = await prompts({
          type: "text",
          name: "projectName",
          message: `${pastel("请输入创建的项目名称:")}?`,
          validate: (value) => {
            if (!value) return `${pastel("项目名称不能为空")}?`;
            return true;
          }
        });

        if (!response.projectName) {
          logger.error("项目名称是必需的");
          process.exit(1);
        }

        projectName = response.projectName;
      }
      logger.log(welcomeMsg);
      logger.log(pastel(welcomeMsg));
      let { template } = options;
      // 如果未指定模板,则提示用户选择
      if (!template) {
        const response = await prompts({
          type: "select",
          choices: [
            {
              title: `${pastel("template-ssr-vue-模板")}?`,
              value: "template-ssr-vue"
            },
            {
              title: `${pastel("template-ssr-vue-ts-模板")}?`,
              value: "template-ssr-vue-ts"
            }
          ],
          name: "selectTemplate",
          message: `${pastel("选择你需要的脚手架")}?`
        });
        template = response.selectTemplate;
        const spinner = ora("加载中").start();
        spinner.color = "yellow";
        // spinner.text = 'Loading rainbows'
        spinner.prefixText = "前缀";
        spinner.suffixText = "后缀";
        spinner.text = chalk.red("Loading rainbows");
        const duck = gradient(["green", "yellow"]).multiline(
          loggerConsts.l_duck
        );
        logger.log(duck);
        console.log(rainbow.multiline(`手动选择了模板:${template}`));
        setTimeout(() => {
          spinner.succeed("加载成功完成");
        }, 2000);
      }
      // logger.info(pc.bgCyan("初始化创建项目"));
      // logger.info(
      //   pc.bgCyan(`name: ${projectName}, options: ${JSON.stringify(options)}`)
      // );
      // loadTemplate('template-ssr-vue')

      loadTemplate(projectName, template);
    });
};

5.增量构建与构建缓存优化 turbo