如何实现一个类似create-vite的脚手架并发布npm

前言

最近在做一个electron生态相关的项目,由于要做一些项目初始化的功能,所以就写了一个脚手架来做这件事情,然后详细了解和实践了一番脚手架相关的功能,最后成功做出来我想要的脚手架,在这里把相关的经验分享出来。

我们先来看下vite的官网。

我们要实现的目标也是这样,yarn create electron-prokit myapp 直接快速搭建一个electron的项目。

npm create是什么

命令行运行一下就知道了

bash 复制代码
npm create --help

也就是说npm create其实就是npm init的别名

在node版本>=6.10时可以使用该方法构建app

npm 将在你提供的初始项前拼接 create- 然后使用npx工具下载并执行该方法,也就是说

csharp 复制代码
npm create vite
// 等同于
npm init vite
// 等同于
npx create-vite
// 等同于
npm install create-vite -g && create-vite

所以npm create vite也就是使用create-vite脚手架创建vite项目。

yarn也是一样: classic.yarnpkg.com/en/docs/cli...

搞清楚这个了可以开始设计脚手架了。

脚手架功能

我们的脚手架起名为 create-electron-prokit,顾名思义,这是一个 electron-prokit系列项目的生成器,主要功能是生产 electron-prokit 相关项目,拆分细节,我们的功能点有以下这些。

  • 接收用户输入的项目名称、描述等,用于确定目录名称和修改 package.json文件。
  • 接收用户的输入,定制项目内容(比如对框架的选择)。
  • 下载 electron-prokit 模板代码到本地。
  • 对创建进度和创建结果,给出反馈。

技术选型

知道功能了,我们需要做一下技术选型,读了create-vite的源码,我们也可以借鉴相关的技术工具。

  • 开发语言工具:typescriptts-node
  • 处理命令:commander
  • 处理交互:inquirer
  • 下载git仓库模版:git-clone
  • 语义化模板:handlebars
  • 命令行美化:ora
  • 文件相关插件:fs-extra

开发步骤

下面我们就开始具体说说如何开发,一共分为下面6个步骤

初始化项目

命令行运行

csharp 复制代码
npm i -g pnpm
pnpm init

然后补充必要的信息,其中 main 是入口文件,bin 用于引入一个全局的命令,映射到 dist/index.js,有了 bin 字段后,我们就可以直接运行 create-electron-prokit命令,而不需要 node dist/index.js了。

json 复制代码
{
  "name": "create-electron-prokit",
  "version": "0.0.1",
  "description": "A cli to create a electron prokit project",
  "main": "dist/index.js",
  "type": "module",
  "bin": {
    "create-electron-prokit": "dist/index.js"
  },
  "keywords": [
    "Electron",
    "electron",
    "electron-prokit",
    "electron prokit",
    "Electron Prokit",
    "Prokit",
    "prokit",
    "create-electron-prokit"
  ],
  "author": "Xutaotaotao",
  "license": "MIT",
}

让项目支持TS 安装 typescript@types/node

sql 复制代码
pnpm add typescript @types/node -D

初始化 tsconfig.json

csharp 复制代码
tsc --init
json 复制代码
{
  "compilerOptions": {
    "target": "es2016",
    "module": "ESNext",
    "moduleResolution": "node",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "sourceMap": true,
    "outDir": "./dist",
    "importHelpers": true 
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist/**/*"],
}

npm link本地调试

我们在 src/index.ts 写个 hello world,测试下 ts 编译是否正常。

  • src/index.ts
ts 复制代码
#!/usr/bin/env node --experimental-specifier-resolution=node
const msg: string = 'Hello World'
console.log(msg)

package.json文件的scripts中加上dev选项

json 复制代码
"dev": "node --experimental-specifier-resolution=node --loader ts-node/esm src/index.ts"

运行npm run dev可看到Hello World就成功一半了,有了上面的准备,我们就可以本地调试了。但是要达到命令行一样的效果还需npm link

记得我们前面在 package.json中有个 bin 配置吗,我们如果在项目里面执行了npm link的命令,你就可以运行create-electron-prokit这个命令了。但是这个命令是指向dist/index.js这个文件的,这个明显是编译之后的文件,所以我们需要在package.json中加一些scripts选项,让我开发起来更加的丝滑!

json 复制代码
"scripts": {
    "dev": "node --experimental-specifier-resolution=node --loader ts-node/esm src/index.ts",
    "build": "tsc",
    "start": "node --experimental-specifier-resolution=node dist/index.js"
  },

npm run build 之后你再运行create-electron-prokit这个命令,你就可以看到hello world啦!是不是很开心,这个项目你已经完成一半了,万事开头难,后面的就是一些逻辑功能的开发。

命令处理功能开发

我们先从最简单的开始,接收命令行参数。

  • src/index.ts
ts 复制代码
#!/usr/bin/env node --experimental-specifier-resolution=node

const name = process.argv[2];
if (!name) {
  log.warn("The project name cannot be empty!");
  process.exit(1);
} else {
  init(name);
}

function init(name: string) {
    console.log(name)
}

就这么简单,我们第一个功能开发完了,下面就是对init函数进行扩充

交互处理功能开发

到这一步我们就需要打印日志,然后询问用户相应的意见,然后获得用户的输入和选择项。 安装inquirer,ora,fs-extra

csharp 复制代码
pnpm add inquirer ora fs-extra

添加项目的描述和作者输入询问以及框架的选择

ts 复制代码
#!/usr/bin/env node --experimental-specifier-resolution=node
import type { QuestionCollection } from "inquirer";
import inquirer from "inquirer";
import ora from "ora";
import fs from "fs-extra";

const log = ora("modify");

async function init(name: string) {
  const InitPrompts: QuestionCollection = [
    {
      name: "description",
      message: "please input description",
      default: "",
    },
    {
      name: "author",
      message: "please input author",
      default: "",
    },
  ];

  const FrameworkOptions: QuestionCollection = {
    type: "list",
    name: "framework",
    message: "Select a framework",
    choices: [
      {
        name: "React",
        value: "React",
      },
      {
        name: "Vue",
        value: "Vue",
      },
    ],
  };
  if (fs.existsSync(name)) {
    log.warn(`Has the same name project,please create another project!`);
    return;
  }
  log.info(`Start init create-electron-prokit project: ${name}`);
  const initOptions = await inquirer.prompt(InitPrompts);
  const frameworkOptions = await inquirer.prompt(FrameworkOptions);
}

function main() {
  const name = process.argv[2];
  if (!name) {
    log.warn("The project name cannot be empty!");
    process.exit(1);
  } else {
    init(name);
  }
}

main()

这里我们把代码优化和整合了一下,更加清晰了。我们用ora来美化控制台的输出,fs-extra检测文件夹是否存在,用inquirer来接收用户的输入和选择。这一步我们把最基本的用户的input获取到了,后面就是通过用户的输入来下载相应的模版,然后更改一些模版信息。

下载模版功能开发

安装git-clone

csharp 复制代码
pnpm add git-clone

在src/download.ts实现下载逻辑

  • src/download.ts
ts 复制代码
import path from "path"
import gitclone from "git-clone"
import fs from "fs-extra"
import ora from "ora"

export const downloadTemplate = (
  templateGitUrl: string,
  downloadPath: string
):Promise<any> =>  {
  const loading = ora("Downloadimg template")
  return new Promise((resolve, reject) => {
    loading.start("Start download template")
    gitclone(templateGitUrl, downloadPath, {
      checkout: "master",
      shallow: true,
    },(error:any) => {
      if (error) {
        loading.stop()
        loading.fail("Download fail")
        reject(error)
      } else {
        fs.removeSync(path.join(downloadPath, ".git"))
        loading.succeed("Download success")
        loading.stop()
        resolve("Download success")
      }
    })
  })
}

很简单,实现了。我们在init方法引用一下,并定义好相应的模版地址

ts 复制代码
#!/usr/bin/env node --experimental-specifier-resolution=node
import * as tslib from "tslib";
import type { QuestionCollection } from "inquirer";
import inquirer from "inquirer";
import ora from "ora";
import fs from "fs-extra";
import { downloadTemplate } from "./download";

const log = ora("modify");

async function init(name: string) {
  const ReactTemplateGitUrl =
    "https://github.com/Xutaotaotao/ep-vite-react-electron-template";

  const VueTemplateGitUrl =
    "https://github.com/Xutaotaotao/ep-vite-vue3-electron-template";

  const InitPrompts: QuestionCollection = [
    {
      name: "description",
      message: "please input description",
      default: "",
    },
    {
      name: "author",
      message: "please input author",
      default: "",
    },
  ];

  const FrameworkOptions: QuestionCollection = {
    type: "list",
    name: "framework",
    message: "Select a framework",
    choices: [
      {
        name: "React",
        value: "React",
      },
      {
        name: "Vue",
        value: "Vue",
      },
    ],
  };
  if (fs.existsSync(name)) {
    log.warn(`Has the same name project,please create another project!`);
    return;
  }
  log.info(`Start init create-electron-prokit project: ${name}`);
  const initOptions = await inquirer.prompt(InitPrompts);
  const frameworkOptions = await inquirer.prompt(FrameworkOptions);
  const templateGitUrl =
    frameworkOptions.framework === "React"
      ? ReactTemplateGitUrl
      : VueTemplateGitUrl;
  try {
    const downloadPath = `./${name}`;
    // 下载
    await downloadTemplate(templateGitUrl, downloadPath);
  } catch (error) {
    console.error(error);
  }
}

function main() {
  const name = process.argv[2];
  if (!name) {
    log.warn("The project name cannot be empty!");
    process.exit(1);
  } else {
    init(name);
  }
}

main()

哇哦!我们离成功只剩一步了,就是修改package.json了。

修改package.json功能开发

在替换前,我们需要修改模板的 package.json,添加一些插槽,方便后面替换。

json 复制代码
{
  "name": "{{name}}",
  "version": "1.0.0",
  "description": "{{description}}",
  "author": "{{author}}"
}

安装handlebars

csharp 复制代码
pnpm add handlebars

在src/modify.ts实现修改逻辑

  • src/modify.ts
ts 复制代码
import path from "path"
import fs from "fs-extra"
import handlebars from "handlebars"
import ora from "ora"

const log = ora("modify")

export const modifyPackageJson = function (downloadPath: string, options: any):void {
  const packagePath = path.join(downloadPath, "package.json")
  log.start("start modifying package.json")
  if (fs.existsSync(packagePath)) {
    const content = fs.readFileSync(packagePath).toString()
    const template = handlebars.compile(content)

    const param = {
      name: options.name,
      description: options.description,
      author: options.author,
    }

    const result = template(param)
    fs.writeFileSync(packagePath, result)
    log.stop()
    log.succeed("This project has been successfully created! ")
    log.info(`Install dependencies:

      cd ${downloadPath} && yarn install
    `)
    log.info(`Run project:

      yarn run dev
    `)
  } else {
    log.stop()
    log.fail("modify package.json fail")
    throw new Error("no package.json")
  }
}

这里我们就完成了修改逻辑的函数,然后在init函数里面导入并使用。

javascript 复制代码
 try {
    const downloadPath = `./${name}`;
    await downloadTemplate(templateGitUrl, downloadPath);
    modifyPackageJson(downloadPath, { name, ...initOptions } as Options);
  } catch (error) {
    console.error(error);
  }

OK,到这里我们就大功告成了!接下来发布NPM

发布NPM

本地发布NPM很简单,分三步,构建,登录npm, 然后publish

构建

构建直接运行

arduino 复制代码
npm run build

登录&publish

先在npm官网注册一个账号。

在项目根目录下,登录npm账号,输入用户名、密码、邮箱。

npm login

登录成功之后直接执行npm publish即可。

可以看我这篇文章,从零构建一个Vue UI组件库(三)------发布第一个npm包

验证

我们发布成功了之后就可以去本地验证了,我们直接运行

lua 复制代码
yarn create electron-prokit my-app

或者

lua 复制代码
npm create electron-prokit my-app

就可以看到效果了!

结束语

本篇文章用最简单的方式了一个脚手架,中间的功能其实还可以丰富,但是核心流程实现了,后面的功能扩展也只是逻辑上的补充和变更,主要是让大家快速上手!!!

项目源码:github.com/Xutaotaotao...

本篇文章如果对你有帮助,欢迎给我的项目给个小小的star✨

相关推荐
y先森2 小时前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy2 小时前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu10830189113 小时前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
IT女孩儿4 小时前
CSS查缺补漏(补充上一条)
前端·css
吃杠碰小鸡5 小时前
commitlint校验git提交信息
前端
虾球xz5 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇5 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒5 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员6 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐6 小时前
前端图像处理(一)
前端