前言
最近在做一个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的源码,我们也可以借鉴相关的技术工具。
- 开发语言工具:
typescript
、ts-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✨