前置知识
lerna
命令 | 功能 |
---|---|
lerna bootstrap | 安装依赖 |
lerna clean | 删除各个包下的node_modules |
lerna init | 创建新的lerna库 |
lerna list | 查看本地包列表 |
lerna changed | 显示自上次release tag以来有修改的包, 选项通 list |
lerna diff | 显示自上次release tag以来有修改的包的差异, 执行 git diff |
lerna exec | 在每个包目录下执行任意命令 |
lerna run | 执行每个包package.json中的脚本命令 |
lerna add | 添加一个包的版本为各个包的依赖 |
lerna import | 引入package |
lerna link | 链接互相引用的库 |
lerna create | 新建package |
lerna publish | 发布 |
yarn
命令 | 说明 |
---|---|
yarn -v | 查看yarn版本 |
yarn config list | 查看yarn的所有配置 |
yarn config set registry https://registry.npm.taobao.org/ | 修改yarn的源镜像为淘宝源 |
yarn config set global-folder “D:\RTE\Yarn\global” | 修改全局安装目录, 先创建好目录(global), 我放在了Yarn安装目录下(D:\RTE\Yarn\global) |
yarn config set prefix “D:\RTE\Yarn\global” | 修改全局安装目录的bin目录位置 |
yarn config set cache-folder “D:\RTE\Yarn\cache” | 修改全局缓存目录, 先创建好目录(cache), 和global放在同一层目录下 |
yarn config list | 查看所有配置 |
yarn global bin | 查看当前yarn的bin的位置 |
yarn global dir | 查看当前yarn的全局安装位置 |
yarn workspace
- yarn官网
- yarn add <package…> [–ignore-workspace-root-check/-W]
- yarn add <package…> [–dev/-D]
作用 | 命令 |
---|---|
查看工作空间信息 | yarn workspaces info |
给所有的空间添加依赖 | yarn workspaces run add lodash |
给根空间添加依赖 | yarn add -W -D typescript jest |
给某个项目添加依赖 | yarn workspace create-react-app3 add commander |
删除所有的 node_modules | lerna clean 等于 yarn workspaces run clean |
安装和link所有的名 | yarn install 等于 lerna bootstrap --npm-client yarn --use-workspaces |
重新获取所有的 node_modules | yarn install --force |
查看缓存目录 | yarn cache dir |
清除本地缓存 | yarn cache clean |
在所有package中运行指定的命令 | yarn workspaces run |
yargs
- yargs帮助你构建交互命令行工具,可以解析参数生成优雅的用户界面
const yargs = require("yargs/yargs");
const cli = yargs();
cli
.usage(`Usage: vite2 <command> [options]`)
.demandCommand(1, "至少需要一个命令")
.strict()
.recommendCommands()
.command({
command: "create <name>",
describe: "创建项目",
builder: (yargs) => {
yargs.positional("name", {
type: "string",
describe: "项目名称",
});
},
handler: async function (argv) {
console.log(argv);
//{ _: [ 'create' ], '$0': 'doc\\1.yargs.js', name: 'p1' }
}
})
.parse(process.argv.slice(2));
node -e
- node -e可以直接执行一段js脚本并输入
- -e, --eval “script”
- 设置
stdion: 'inherit'
,当执行代码时,子进程将会继承主进程的stdin、stdout和stderr
node -e "console.log(process.argv)" -- a b
node -e "console.log(JSON.parse(process.argv[1]))" -- "{\"name\":\"hs\"}"
node -e "console.log(process.cwd())"
const spawn = require("cross-spawn");
async function executeNodeScript({ cwd }, source, args) {
return new Promise((resolve) => {
const childProcess = spawn(
process.execPath,
["-e", source, "--", JSON.stringify(args)],
{ cwd, stdio: "inherit" }
);
childProcess.on("close", resolve);
});
}
module.exports = executeNodeScript;
clone
const clone = require('clone-git-repo');
let repository = 'gitee:hstemplate/template-react#v1.0';
clone(repository,'./output', {clone:true},function (err) {
console.log(err);
})
create-vite介绍
最新版本为5.2.3
create-vite简介
使用
npm init vite
Need to install the following packages:
create-vite
Ok to proceed? (y) y
√ Project name: ... vite-project
√ Select a framework: » react
√ Select a variant: » react
Scaffolding project in C:\aprepare\t1\vite-project...
Done. Now run:
cd vite-project
npm install
npm run dev
create-vite源码调试
git clone https://github.com/vitejs/vite.git
cd vite
yarn install
packages\create-vite\index.js
.vscode\launch.json
{
"version": "0.2.0",
"configurations": [
{
"type": "pwa-node",
"request": "launch",
"name": "Launch Program",
"skipFiles": [
"<node_internals>/**"
],
"program": "${workspaceFolder}\\packages\\create-vite\\index.js",
"args": ["create","vite-project"]
}
]
}
create-vite功能
- [√] 支持参数解析
- [√] 支持自定义项目名
- [√] 支持空目录检查
- [√] 支持静态项目模板
- [×] 不支持lerna lerna
- [×] 不支持文件异步写入 create-react-app
- [×] 不支持多进程执行命令 create-react-app
- [×] 不支持执行动态
node
命令 create-react-app - [×] 不支持自动安装依赖 create-react-app
- [×] 不支持自动启动服务 create-react-app
- [×] 不支持参数配置 yarn
- [×] 不支持
gitub
和gitee
仓库动态读取 - [×] 不支持模板标签选择
- [×] 不支持动态模板渲染
- [×] 不支持插件化配置技术栈 vue-cli
npm create命令
用npm命令
npm create vite
创建工程npm create
是npm init
的alias,详情可查阅npm文档
npm create vite@lastest
相当于 => npx create-vite@lastest
,latest
是版本号,目前最新版本可以通过以下命令查看。
npm dist-tag ls create-vite
beta: 5.0.0-beta.1
latest: 5.2.3
简单来说,不管是 npm create
还是 yarn create
、pnpm create
,这三个命令的大致流程都是类似的:
- 解析命令行参数,得到 初始化器名称
initerName
和项目名称projectName
- 解析
initerName
,确定对应的依赖包名称和版本号 - 根据依赖包名称和版本号下载安装初始化器
- 执行初始化器的
create
方法 - 成功后打印提示信息,退出进程
核心
-
根据
package.json
中的type
和bin
,type类型指定为
module说明是
ES Module。bin 可执行命令为create-vite
或 别名cva
,主文件index.js
-
查看
index.js
-
import './dist/index.mjs'
,说明它引入的是一个打包好的模块,根据package.json
中的打包命令,可以看出使用了unbuild
工具,在目录可以查找到相关的配置文件build.config.ts
。unbuild
是一个统一的构建系统
-
src\index.ts
init初始化函数
- 通过
minimist
解析命令行参数 - 通过
prompts
命令行交互 - 通过
kolorist
命令行输出彩色文本 - 通过
spawn
跨平台开启子进程 - 通过
fileURLToPath
把url 转文件路径模块
处理项目名称和项目创建的目标路径
-
通过
formatTargetDir
函数,替换反斜杠/
为空字符串
通过prompts终端交互,进行参数确认
通过
prompts
询问用户并确认所用项目名称、所用框架等。
let result: prompts.Answers<
'projectName' | 'overwrite' | 'packageName' | 'framework' | 'variant'
>
prompts.override({
overwrite: argv.overwrite,
})
try {
result = await prompts(
[
{
type: argTargetDir ? null : 'text',
name: 'projectName',
message: reset('Project name:'),
initial: defaultTargetDir,
onState: (state) => {
targetDir = formatTargetDir(state.value) || defaultTargetDir
},
},
{
type: () =>
!fs.existsSync(targetDir) || isEmpty(targetDir) ? null : 'select',
name: 'overwrite',
message: () =>
(targetDir === '.'
? 'Current directory'
: `Target directory "${targetDir}"`) +
` is not empty. Please choose how to proceed:`,
initial: 0,
choices: [
{
title: 'Remove existing files and continue',
value: 'yes',
},
{
title: 'Cancel operation',
value: 'no',
},
{
title: 'Ignore files and continue',
value: 'ignore',
},
],
},
{
type: (_, { overwrite }: { overwrite?: string }) => {
if (overwrite === 'no') {
throw new Error(red('✖') + ' Operation cancelled')
}
return null
},
name: 'overwriteChecker',
},
{
type: () => (isValidPackageName(getProjectName()) ? null : 'text'),
name: 'packageName',
message: reset('Package name:'),
initial: () => toValidPackageName(getProjectName()),
validate: (dir) =>
isValidPackageName(dir) || 'Invalid package.json name',
},
{
type:
argTemplate && TEMPLATES.includes(argTemplate) ? null : 'select',
name: 'framework',
message:
typeof argTemplate === 'string' && !TEMPLATES.includes(argTemplate)
? reset(
`"${argTemplate}" isn't a valid template. Please choose from below: `,
)
: reset('Select a framework:'),
initial: 0,
choices: FRAMEWORKS.map((framework) => {
const frameworkColor = framework.color
return {
title: frameworkColor(framework.display || framework.name),
value: framework,
}
}),
},
{
type: (framework: Framework) =>
framework && framework.variants ? 'select' : null,
name: 'variant',
message: reset('Select a variant:'),
choices: (framework: Framework) =>
framework.variants.map((variant) => {
const variantColor = variant.color
return {
title: variantColor(variant.display || variant.name),
value: variant.name,
}
}),
},
],
{
onCancel: () => {
throw new Error(red('✖') + ' Operation cancelled')
},
},
)
} catch (cancelled: any) {
console.log(cancelled.message)
return
}
// user choice associated with prompts
// framework 框架
// overwrite 已有目录,是否重写
// packageName 输入的项目名
// variant 变体, 比如 react => react-ts
const { framework, overwrite, packageName, variant } = result
清空已有目录或创建不存在的新目录
-
通过
emptyDir
递归删除文件夹
确定template
// determine template
let template: string = variant || framework?.name || argTemplate
let isReactSwc = false
if (template.includes('-swc')) {
isReactSwc = true
template = template.replace('-swc', '')
}
注意这里在 template
确定之后,还有一个 swc
的处理: isReactSwc
关于 swc(Speedy Web Compiler)
,官方释意是:“SWC is an extensible Rust-based platform for the next generation of fast developer tools. SWC can be used for both compilation and bundling. For compilation, it takes JavaScript / TypeScript files using modern JavaScript features and outputs valid code that is supported by all major browsers”
翻译过来也就是:SWC 是基于 Rust 的下一代快速开发工具,可以用于代码编译和绑定;在代码编译上面,它可以使用现代 JavaScript 功能来处理 JavaScript/TypeScript 代码文件并输出适用于所有主流浏览器都支持的有效代码。
在代码编译上面,我们可以把它当成与 Babel 同等的代码处理插件,但是由于其基于 Rust 开发,所以在速度上比 Babel
快很多倍:
SWC在单线程上比 Babel 快 20 倍,在四核上 快 70 倍。
如果用户选择的模板具有 swc
配置,则会将 isReactSwc
变量置为 true
,并从 template
中移除 -swc
部分。
处理pkgManager
处理
node packages manager
包管理工具
首先会通过 pkgFromUserAgent
来读取 用户代理字符串 中的相关信息,并返回 name
包管理工具名称、version
包管理工具版本。
然后通过 const pkgManager = pkgInfo ? pkgInfo.name : 'npm'
处理,如果上面的 pkgFromUserAgent
返回为空则会默认使用 npm
。
对于 yarn
的话,由于 yarn@1.x
版本不支持在 create
阶段指定版本,所以需要一个变量来保存它的版本信息以供后面进行初始化命令的拼接:
const isYarn1 = pkgManager === 'yarn' && pkgInfo?.version.startsWith('1.')
此时,我们已经确定了用户当前的一个大致的工作环境了,包括工作目录、包管理工具等
customCommand
确认了用户的工作环境之后,就会在用户选择的 模板和变种 中提取出对应的
customCommand
自定义命令,然后根据上面确定的环境来 重新处理命令语句
- 替换默认语句中的包管理工具,指定当前的工作目录,
- 通过
spawn.sync
启动一个 子进程 执行脚本,并且设置stdio: 'inherit'
让子进程继承当前进程的输入输出,将输出信息同步输出在当前进程上
package.json单独处理(修改工程名)
package.json
文件单独处理。读取模板中的package.json
最为一个json
对象,修改其name
属性为用户输入的项目名称projectName
如果上面新增的
isReactSwc
为true
的话,还会通过editFile
方法在vite.config.js/ts
和package.json
中插入swc
的相关插件,但是目前 仅支持react
项目。
输出安装完成后的信息
实现
1.初始化项目
1.1 lerna初始化
mkdir vite2
cd vite2
lerna init
1.2 使用yarn workspace
- 开发多个互相依赖的package时,workspace会自动对package的引用设置软链接(symlink),比yarn link更加方便,且链接仅局限在当前workspace中,不会对整个系统造成影响
- 所有package的依赖会安装在根目录的
node_modules
下,节省磁盘空间,且给了yarn更大的依赖优化空间 - Yarn workspace只会在根目录安装一个node_modules,这有利于提升依赖的安装效率和不同package间的版本复用。而Lerna默认会进到每一个package中运行yarn/npm install,并在每个package中创建一个node_modules
- yarn官方推荐的方案,是集成yarn workspace和lerna,使用yarn workspace来管理依赖,使用lerna来管理npm包的版本发布
1.2.1 lerna.json
{
"packages": [
"packages/*"
],
"version": "0.0.0",
+ "npmClient": "yarn",
+ "useWorkspaces": true
}
1.2.2 package.json
{
"name": "root",
"private": true,
"devDependencies": {
"lerna": "^4.0.0"
},
+ "workspaces": [
+ "packages/*"
+ ]
}
1.3 创建子包
lerna create @vite2/config -y //配置项
lerna create @vite2/create -y //创建项目
lerna create vite2 -y //核心命令
lerna create @vite2/settings -y //常量定义
lerna create @vite2/utils -y //工具方法
1.4 安装依赖
- axios 请求接口
- cross-spawn 开启子进程
- userhome 获取用户主目录
- chalk 控制台打印彩色文字
- ejs 模板渲染
- execa 通过子进程执行命令
- glob 按模式匹配文件
- inquirer 交互式命令行选择
- vite2 核心命令
- @vite2/settings 常量配置
- @vite2/utils 帮助方法
- @vite2/config 配置参数
- @vite2/create 创建项目
1.4.1 config\package.json
packages\config\package.json
{
"dependencies": {
"@vite2/settings": "^0.0.0",
"@vite2/utils": "^0.0.0",
"fs-extra": "^10.0.0",
"userhome": "^1.0.0"
}
}
1.4.2 create\package.json
packages\create\package.json
{
"dependencies": {
"@vite2/settings": "^0.0.0",
"@vite2/utils": "^0.0.0",
"chalk": "^4.1.2",
"clone-git-repo": "^0.0.2",
"ejs": "^3.1.6",
"execa": "^5.1.1",
"fs-extra": "^10.0.0",
"glob": "^7.1.7",
"inquirer": "^8.1.2"
},
}
1.4.3 utils\package.json
packages\utils\package.json
{
"dependencies": {
"@vite2/settings": "^0.0.0",
"axios": "^0.21.2",
"cross-spawn": "^7.0.3",
"userhome": "^1.0.0"
},
}
1.4.4 vite2\package.json
packages\vite2\package.json
{
"dependencies": {
"@vite2/config": "^0.0.0",
"@vite2/create": "^0.0.0"
}
}
1.4.5 publishConfig
{
"publishConfig": {
"access": "public",
"registry": "http://registry.npmjs.org"
}
}
1.5 配置命令
1.5.1 package.json
packages\vite2\package.json
{
"name": "vite2",
"version": "0.0.0",
"dependencies": {
"@vite2/config":"^0.0.0",
"@vite2/create":"^0.0.0"
},
+ "bin":{
+ "vite2": "index.js"
+ }
}
1.5.2 index.js
packages\vite2\index.js
#!/usr/bin/env node
async function main() {
let argv = process.argv.slice(2);
console.log(argv);
}
main().catch((err) => {
console.error(err);
});
1.5.3 link
- 一定要先添加
#!/usr/bin/env node
再link,否则会用文本编辑器打开 - 这种情况可以
vite2\packages\vite2
目录中执行yarn unlink
,再重新link
yarn link
yarn global bin
C:\Users\admin\AppData\Local\Yarn\bin
C:\Users\admin\AppData\Local\Yarn\Data\link\vite2
npm bin -g
C:\Users\admin\AppData\Roaming\npm
vite2 create vite-project
2.实现配置命令
2.1 安装依赖
yarn workspace @vite2/config add userhome fs-extra
yarn workspace @vite2/utils add cross-spawn userhome fs-extra
2.2 settings\index.js
packages\settings\index.js
//执行命令脚本
exports.COMMAND_SOURCE = `
const args = JSON.parse(process.argv[1]);
const factory = require('.');
factory(args);
`
//配置文件名称
exports.RC_NAME = ".vite3rc";
2.3 config.js
packages\utils\config.js
const userhome = require("userhome");
const fs = require("fs-extra");
const { RC_NAME } = require("@vite5/settings");
const configPath = userhome(RC_NAME);
let config = {};
if (fs.existsSync(configPath)) {
config = fs.readJSONSync(configPath);
}
config.configPath=configPath;
module.exports = config;
2.4 executeNodeScript.js
packages\utils\executeNodeScript.js
const spawn = require("cross-spawn");
async function executeNodeScript({ cwd }, source, args) {
return new Promise((resolve) => {
const childProcess = spawn(
process.execPath,
["-e", source, "--", JSON.stringify(args)],
{ cwd, stdio: "inherit" }
);
childProcess.on("close", resolve);
});
}
module.exports = executeNodeScript;
2.5 log.js
packages\utils\log.js
const log = require('npmlog');
log.heading = 'vite2';
module.exports = log;
2.6 utils\index.js
packages\utils\index.js
exports.log = require('./log');
exports.executeNodeScript = require('./executeNodeScript');
exports.config = require('./config');
2.7 packages\config\command.js
packages\config\command.js
const {executeNodeScript} = require('@vite2/utils');
const {COMMAND_SOURCE} = require('@vite2/settings');
const command = {
command: "config [key] [value]",
describe: "设置或查看配置项,比如GIT_TYPE设置仓库类型,ORG_NAME设置组织名",
builder: (yargs) => {},
handler:async function(argv){
await executeNodeScript({ cwd: __dirname }, COMMAND_SOURCE,argv);
},
};
module.exports = command;
2.8 config\index.js
packages\config\index.js
const fs = require("fs-extra");
const { log ,config} = require("@vite2/utils");
async function factory(argv) {
const { key, value } = argv;
console.log('config',config);
if (key && value) {
config[key] = value;
await fs.writeJSON(config.configPath, config, { spaces: 2 });
log.info("vite3","(%s=%s)配置成功保存至%s", key, value, config.configPath);
}else if(key){
console.log('%s=%s',key, config[key]);
}else{
console.log(config);
}
}
module.exports = factory;
2.9 vite2\index.js
packages\vite2\index.js
#!/usr/bin/env node
const yargs = require("yargs/yargs");
const configCmd = require("@vite2/config/command");
async function main() {
const cli = yargs();
cli
.usage(`Usage: vite2 <command> [options]`)
.demandCommand(1, "至少需要一个命令")
.strict()
.recommendCommands()
+ .command(configCmd)
.parse(process.argv.slice(2));
}
main().catch((err) => {
console.error(err);
});
3.实现创建命令
3.1 create\command.js
packages\create\command.js
const {COMMAND_SOURCE} = require('@vite2/settings');
const {executeNodeScript} = require('@vite2/utils');
const command = {
command: "create <name>",
describe: "创建项目",
builder: (yargs) => {
yargs.positional("name", {
type: "string",
describe: "项目名称",
});
},
handler:async function(argv){
let args = {name:argv.name,cwd:process.cwd()};
await executeNodeScript({ cwd: __dirname }, COMMAND_SOURCE,args);
}
};
module.exports = command;
3.2 create\index.js
packages\create\index.js
async function factory(argv) {
console.log('create',argv);
}
module.exports = factory;
3.3 vite2\index.js
packages\vite2\index.js
#!/usr/bin/env node
const yargs = require("yargs/yargs");
const configCmd = require("@vite2/config/command");
+const createCmd = require("@vite2/create/command");
async function main() {
const cli = yargs();
cli
.usage(`Usage: vite2 <command> [options]`)
.demandCommand(1, "至少需要一个命令")
.strict()
.recommendCommands()
.command(configCmd)
+ .command(createCmd)
.parse(process.argv.slice(2));
}
main().catch((err) => {
console.error(err);
});
4.创建项目目录
4.1 安装
yarn workspace @vite2/create add chalk fs-extra inquirer
4.2 create\index.js
packages\create\index.js
+const path = require("path");
+const { red } = require("chalk");
+const { prompt } = require("inquirer");
+const fs = require("fs-extra");
+const {config, log } = require("@vite2/utils");
async function factory(argv) {
+ const { cwd, name } = argv;
+ process.chdir(cwd);//切换为当前命令执行的工作目录
+ const { ORG_NAME } = config;
+ if (!ORG_NAME) {
+ throw new Error(red("X") + " 尚未配置组织名称!");
+ }
+ const targetDir = path.join(process.cwd(), name);
+ log.info("vite2", "创建的项目目录为%s", targetDir);
+ try {
+ await fs.access(targetDir);
+ const files = await fs.readdir(targetDir);
+ if (files.length > 0) {
+ const { overwrite } = await prompt({
+ type: "confirm",
+ name: "overwrite",
+ message: `目标目录非空,是否要移除存在的文件并继续?`,
+ });
+ if (overwrite) {
+ await fs.emptyDir(targetDir);
+ } else {
+ throw new Error(red("X") + " 操作被取消");
+ }
+ }
+ } catch (error) {
+ await fs.mkdirp(targetDir);
+ }
+ log.info("vite3", "%s目录已经准备就绪", targetDir);
}
module.exports = factory;
5.下载模板
5.1 安装
-
clone-git-repo
下载仓库(GitHub, GitLab, Bitbucket,Gitee)
yarn workspace @vite2/create add clone-git-repo yarn workspace @vite2/utils add axios
5.2 settings\index.js
packages\settings\index.js
//执行命令脚本
exports.COMMAND_SOURCE = `
const args = JSON.parse(process.argv[1]);
const factory = require('.');
factory(args);
`
+//配置文件名称
+exports.RC_NAME = ".vite3rc";
+//组织的名称
+exports.ORG_NAME = "ORG_NAME";
+//git仓库类型
+exports.GIT_TYPE = "GIT_TYPE";
+//模板存放名称
+exports.TEMPLATES = ".vite3_templates";
5.3 utils\request.js
packages\utils\request.js
const axios = require("axios");
const { GIT_TYPE } = require("./config");
const GITEE = "https://gitee.com/api/v5";
const GITHUB = "https://api.github.com";
const BASE_URL = GIT_TYPE === "gitee" ? GITEE : GITHUB;
const request = axios.create({
baseURL: BASE_URL,
timeout: 5000,
});
request.interceptors.response.use(
(response) => {
return response.data;
},
(error) => {
return Promise.reject(error);
}
);
module.exports = request;
5.4 withLoading.js
packages\utils\withLoading.js
async function withLoading(message, fn, ...args) {
const ora = await import("ora");
const spinner = ora.default(message);
spinner.start();
const result = await fn(...args);
spinner.succeed();
return result;
}
module.exports = withLoading;
5.5 packages\utils\index.js
packages\utils\index.js
exports.log = require('./log');
exports.executeNodeScript = require('./executeNodeScript');
exports.config = require('./config');
+exports.withLoading = require('./withLoading');
+exports.request = require('./request');
5.6 create\index.js
packages\create\index.js
const path = require("path");
+const { promisify } = require("util");
const { red } = require("chalk");
const fs = require("fs-extra");
+const { prompt } = require("inquirer");
+const userhome = require("userhome");
+const clone = promisify(require('clone-git-repo'));
+const { TEMPLATES } = require("@vite2/settings");
+const { config, log,withLoading, request} = require("@vite2/utils");
async function factory(argv) {
const { cwd, name } = argv;
process.chdir(cwd);//切换为当前命令执行的工作目录
+ const { GIT_TYPE,ORG_NAME } = config;
if (!ORG_NAME) {
throw new Error(red("X") + " 尚未配置组织名称!");
}
const targetDir = path.join(process.cwd(), name);
log.info("vite2", "创建的项目目录为%s", targetDir);
try {
await fs.access(targetDir);
const files = await fs.readdir(targetDir);
if (files.length > 0) {
const { overwrite } = await prompt({
type: "confirm",
name: "overwrite",
message: `目标目录非空,是否要移除存在的文件并继续?`,
});
if (overwrite) {
await fs.emptyDir(targetDir);
} else {
throw new Error(red("X") + " 操作被取消");
}
}
} catch (error) {
await fs.mkdirp(targetDir);
}
log.info("vite3", "%s目录已经准备就绪", targetDir);
+ let repos = await withLoading("读取模板列表", async () =>
+ request.get(`/orgs/${ORG_NAME}/repos`)
+ );
+ let { repo } = await prompt({
+ name: "repo",
+ type: "list",
+ message: "请选择模板",
+ choices: repos.map((repo) => repo.name)
+ });
+ let tags = await withLoading("读取标签列表", async () =>
+ request.get(`/repos/${ORG_NAME}/${repo}/tags`)
+ );
+ let { tag } = await prompt({
+ name: "tag",
+ type: "list",
+ message: "请选择版本",
+ choices: tags,
+ });
+ let repository = GIT_TYPE + `:${ORG_NAME}/${repo}`;
+ if(tag)repository+=`#${tag}`;
+ const downloadDirectory = userhome(TEMPLATES);
+ const repoDirectory = `${downloadDirectory}/${repo}/${tag}`;
+ log.info("vite3", "准备下载模板到%s", repoDirectory);
+ try {
+ await fs.access(repoDirectory);
+ } catch (error) {
+ log.info("vite2", "从仓库下载%s", repository);
+ await clone(repository,repoDirectory, {clone:true});
+ }
}
module.exports = factory;
6.渲染模板
6.1 安装
yarn workspace @vite2/create add ejs glob execa
ask.json
[
{
"name": "projectName",
"type":"text",
"message":"请输入项目名称"
},
{
"name": "projectVersion",
"type": "text",
"message": "请输入项目版本"
}
]
6.2 packages\settings\index.js
packages\settings\index.js
//执行命令脚本
exports.COMMAND_SOURCE = `
const args = JSON.parse(process.argv[1]);
const factory = require('.');
factory(args);
`
//配置文件名称
exports.RC_NAME = ".vite3rc";
//组织的名称
exports.ORG_NAME = "ORG_NAME";
//git仓库类型
exports.GIT_TYPE = "GIT_TYPE";
//模板存放名称
exports.TEMPLATES = ".vite3_templates";
+//重命名文件配置
+exports.RENAME_FILES = {
+ _gitignore: '.gitignore'
+}
6.3 packages\create\index.js
packages\create\index.js
const path = require("path");
const { promisify } = require("util");
const { red } = require("chalk");
const fs = require("fs-extra");
const { prompt } = require("inquirer");
const userhome = require("userhome");
const clone = promisify(require('clone-git-repo'));
+const glob = promisify(require("glob"));
+const { render } = require("ejs");
+const execa = require("execa");
+const { TEMPLATES,RENAME_FILES } = require("@vite2/settings");
const { config, log, withLoading, request } = require("@vite2/utils");
async function factory(argv) {
const { cwd, name } = argv;
process.chdir(cwd);//切换为当前命令执行的工作目录
const { GIT_TYPE, ORG_NAME } = config;
if (!ORG_NAME) {
throw new Error(red("X") + " 尚未配置组织名称!");
}
const targetDir = path.join(process.cwd(), name);
log.info("vite2", "创建的项目目录为%s", targetDir);
try {
await fs.access(targetDir);
const files = await fs.readdir(targetDir);
if (files.length > 0) {
const { overwrite } = await prompt({
type: "confirm",
name: "overwrite",
message: `目标目录非空,是否要移除存在的文件并继续?`,
});
if (overwrite) {
await fs.emptyDir(targetDir);
} else {
throw new Error(red("X") + " 操作被取消");
}
}
} catch (error) {
await fs.mkdirp(targetDir);
}
log.info("vite3", "%s目录已经准备就绪", targetDir);
let repos = await withLoading("读取模板列表", async () =>
request.get(`/orgs/${ORG_NAME}/repos`)
);
let { repo } = await prompt({
name: "repo",
type: "list",
message: "请选择模板",
choices: repos.map((repo) => repo.name)
});
let tags = await withLoading("读取标签列表", async () =>
request.get(`/repos/${ORG_NAME}/${repo}/tags`)
);
let { tag } = await prompt({
name: "tag",
type: "list",
message: "请选择版本",
choices: tags,
});
let repository = GIT_TYPE + `:${ORG_NAME}/${repo}`;
if (tag) repository += `#${tag}`;
const downloadDirectory = userhome(TEMPLATES);
const repoDirectory = `${downloadDirectory}/${repo}/${tag}`;
log.info("vite3", "准备下载模板到%s", repoDirectory);
try {
await fs.access(repoDirectory);
} catch (error) {
log.info("vite2", "从仓库下载%s", repository);
await clone(repository, repoDirectory, { clone: true });
}
+ let ask = path.join(repoDirectory, "ask.json");
+ if (fs.existsSync(ask)) {
+ const askOptions = await fs.readJSON(ask);
+ const result = await prompt(askOptions);
+ const files = await glob(`**/*`, {
+ cwd: repoDirectory,
+ ignore: ['ask.json'],
+ nodir: true
+ });
+ await Promise.all(files.map(file => new Promise(async function (resolve) {
+ let content = await fs.readFile(path.join(repoDirectory, file), 'utf8');
+ let renderContent = await render(content, result);
+ let targetName = RENAME_FILES[file] || file;
+ let targetFile = path.join(targetDir, targetName);
+ await fs.ensureDir(path.dirname(targetFile));
+ await fs.writeFile(targetFile, renderContent, 'utf8');
+ resolve();
+ })));
+ } else {
+ await fs.copy(repoDirectory, targetDir);
+ }
+ process.chdir(targetDir);
+ log.info("vite3", "在%s初始化 Git 仓库", targetDir);
+ await execa("git", ["init"], { stdio: "inherit" });
+ log.info("vite3", "在%s安装依赖", targetDir);
+ await execa("npm", ["install"], { stdio: "inherit" });
+ log.info("vite3", "启动服务");
+ await execa("node", ["./node_modules/esbuild/install.js"], {
+ stdio: "inherit",
+ });
+ await execa("npm", ["run", "dev"], { stdio: "inherit" });
}
module.exports = factory;
7.发布
发布
7.1 创建组织
7.2 发布
package.json
{
"publishConfig": {
"access": "public",
"registry": "http://registry.npmjs.org"
}
}
npm whoami
npm login
hscoder
lerna publish