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

简介: 本文介绍了如何实现一个类似 Vite 的脚手架工具。通过详细解析和实践,文章分享了从零开始构建脚手架的过程,包括技术选型、开发步骤及发布 NPM 包的完整流程。最终目标是让用户能够通过 `yarn create electron-prokit myapp` 快速搭建 Electron 项目。项目源码可在 GitHub 上获取。

本文首发微信公众号:前端徐徐。

大家好,我是徐徐。今天我们来看看如何实现一个像 vite 一样的脚手架。

前言

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

我们先来看下 vite 的官网。

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

npm create 是什么

命令行运行一下就知道了

npm create --help


** 也就是说 npm create 其实就是 npm init 的别名 **
在 node 版本>=6.10 时可以使用该方法构建 app
npm 将在你提供的初始项前拼接 create- 然后使用 npx 工具下载并执行该方法,也就是说

npm create vite
// 等同于
npm init vite
// 等同于
npx create-vite
// 等同于
npm install create-vite -g && create-vite

所以 npm create vite 也就是使用 create-vite 脚手架创建 vite 项目。yarn 也是一样。
具体参考:https://classic.yarnpkg.com/en/docs/cli/create
搞清楚这个了可以开始设计脚手架了。

脚手架功能

我们的脚手架起名为 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 个步骤

初始化项目

命令行运行

npm i -g pnpm
pnpm init

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

{
  "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

pnpm add typescript @types/node -D

初始化 tsconfig.json

tsc --init
{
  "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
#!/usr/bin/env node --experimental-specifier-resolution=node
const msg: string = 'Hello World'
console.log(msg)

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

"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 选项,让我开发起来更加的丝滑!

"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
#!/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

pnpm add inquirer ora fs-extra

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

#!/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

pnpm add git-clone

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

  • src/download.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 方法引用一下,并定义好相应的模版地址

#!/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,添加一些插槽,方便后面替换。

{"name": "{{name}}",
  "version": "1.0.0",
  "description": "{{description}}",
  "author": "{{author}}"
}

安装 handlebars

pnpm add handlebars

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

  • src/modify.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 函数里面导入并使用。

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

  • 构建:构建直接运行
npm run build

验证

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

yarn create electron-prokit my-app

或者

npm create electron-prokit my-app

就可以看到效果了!

结语

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

项目源码

https://github.com/Xutaotaotao/electron-prokit

相关文章
|
2月前
|
资源调度 JavaScript API
Vue3+TS+Vite开发组件库并发布到npm
这篇文章介绍了如何使用Vue 3、TypeScript和Vite开发一个包含35个常用UI组件和8个API功能函数的组件库`vue-amazing-ui`,并将其发布到npm,同时提供了组件库的安装使用说明和在线预览。
Vue3+TS+Vite开发组件库并发布到npm
|
2月前
|
缓存 资源调度 JavaScript
Vue3+TS+Vite开发组件库并发布到npm
**vue-amazing-ui 组件库** 是一个基于 Vue 3 的高质量 UI 组件库,提供了丰富的组件和工具函数。该库已发布至 npm,可通过 `pnpm i vue-amazing-ui`、`yarn add vue-amazing-ui` 或 `npm install vue-amazing-ui` 安装使用。组件包括按钮、面包屑、卡片、日期选择器等,同时提供了日期格式化、节流、防抖等实用工具函数。项目结构清晰,支持按需加载,并提供了详细的文档与在线预览。
Vue3+TS+Vite开发组件库并发布到npm
|
5月前
|
JavaScript 前端开发
🚀自定义属于你的脚手架并发布到NPM仓库
🚀自定义属于你的脚手架并发布到NPM仓库
|
4月前
npm构建vite项目
npm构建vite项目
|
4月前
使用npm构建vite+vue+ts项目的两种方式
使用npm构建vite+vue+ts项目的两种方式
使用npm构建vite+vue+ts项目的两种方式
|
5月前
|
JavaScript
无法安装Vue脚手架 npm install @vue/cli -g
无法安装Vue脚手架 npm install @vue/cli -g
192 0
|
5月前
|
前端开发 开发者
NPM包脚手架:开启前端开发新纪元
NPM包脚手架:开启前端开发新纪元
123 0
|
存储 缓存 资源调度
Vite 是如何发布 npm 包的?
Vite 是如何发布 npm 包的?
381 1
|
JSON JavaScript 前端开发
学习Vue3 第三章(Vite目录 & Vue单文件组件 & npm run dev 详解)
index.html 非常重要的入口文件 (webpack,rollup 他们的入口文件都是enrty input 是一个js文件 而Vite 的入口文件是一个html文件,他刚开始不会编译这些js文件 只有当你用到的时候 如script src="xxxxx.js" 会发起一个请求被vite拦截这时候才会解析js文件)
365 0
学习Vue3 第三章(Vite目录 & Vue单文件组件 & npm run dev 详解)
|
存储 缓存
pnpm新建vite+vue3项目 以及pnpm和npm的区别
pnpm新建vite+vue3项目 以及pnpm和npm的区别
641 0

推荐镜像

更多