教你用NodeJs構(gòu)建屬于自己的前端腳手工具
一.前言
在日常開發(fā)中,我們經(jīng)常會用到各種腳手架工具,如常用的vue和react腳手架工具:vue-cli、Create React App。只需要執(zhí)行內(nèi)置命令和選擇內(nèi)置條件就可以生成對應(yīng)的項目模板。極大的提高了我們開發(fā)開發(fā)效率,所以我們能不能根據(jù)自己的日常業(yè)務(wù)構(gòu)建屬于自己的一套腳手架工具呢,答案是可以的。接下來步入本文章的主題
二.技術(shù)棧
- commander:^9.2.0 :完整的 node.js命令行解決方案
- execa: ^6.1.0:執(zhí)行shel命令
- inquirer: ^8.2.4:交互式命令行用戶界面,在命令工具中可提供交互
- ejs: ^3.1.8:嵌入式JavaScript模板
- chalk: ^5.0.1:設(shè)置終端字符串樣式
- mkdirp:^1.0.4:遞歸mkdir
三.特別說明
在開始之前,需要了解nodejs基礎(chǔ)知識,常用shell執(zhí)行命令,和javascript基礎(chǔ)知識?。。?本文將從零到一完整講解腳手架開發(fā)過程,以Vite + React18.0為模板案例,不會完整開發(fā)類似vue-cli 的所有功能!你可按照此文優(yōu)化完善自己的腳手架代碼。
四.構(gòu)建項目
現(xiàn)在正式開始構(gòu)建腳手架項目,可根據(jù)自己熟悉方式創(chuàng)建,直接創(chuàng)建項目文件夾
# 創(chuàng)建項目文件夾 名稱為 my-cli 可自定義 mkdir my-cli # 初始化 npm 根據(jù)自己的需求填寫對應(yīng)信息,也可以直接默認(rèn) npm init
五.安裝依賴
yarn add commander execa inquirer chalk mkdirp # or npm 安裝 npm install commander execa inquirer chalk mkdirp
六.目錄說明
完整目錄,如下所示,可根據(jù)目錄設(shè)計添加對應(yīng)文件。
my-cli # 項目名稱
└── bin # 主目錄
├── commands # 命令文件
│ ├── options # 命令所有選項
│ │ ├── create.js # 創(chuàng)建命令
│ │ ├── help.js # 幫助命令
│ │ ├── help.js # 導(dǎo)出所有命令文件
│ ├── index.js # 導(dǎo)出command命令方法
├── inquirers # 交互式命令文件
│ ├── options # 交互式命令所有選項
│ │ ├── common.js # 公共交互式命令
│ │ ├── react.js # react相關(guān)命令
│ │ ├── index.js # 導(dǎo)出所有交互式命令文件
│ ├── index.js # 導(dǎo)出command命令方法
├── templates # 所有模板文件
│ ├── react # react相關(guān)模板
│ ├── vue # vue相關(guān)模板
├── utils # 工具類
│ ├── index.js # 工具類處理函數(shù)
├── index.js # 入口文件
完整代碼:my-cli
七.實戰(zhàn)
在開始前,我們需要修改package.json文件,增加"bin": "./bin/index.js":發(fā)布到npm設(shè)置執(zhí)行入口文件; "type": "module":表示允許執(zhí)行export、import操作;"start": "node ./bin/index.js":做本地測試使用,表示執(zhí)行腳手架入口文件
{
+ "bin": "./bin/index.js",
+ "type": "module",
"scripts": {
- "test": "echo \"Error: no test specified\" && exit 1",
+ "start": "node ./bin/index.js"
}
}(一).入口文件
首先從入口文件開始分析,需要的模塊逐步完善。目錄路徑:bin/index.js
#! /usr/bin/env node
// 引入 fs模 塊
import fs, { mkdirSync } from "fs";
// 引入 path 模塊
import path from "path";
// 引入 commands 相關(guān)命令方法
import commands from "./commands/index.js";
// 獲取用戶輸入、選擇項
let config = await command();需要注意的是#! /usr/bin/env node 表示執(zhí)行環(huán)境為node,引入fs文件系統(tǒng)模塊,創(chuàng)建文件項目會涉及到文件相關(guān)讀取寫能力;引入path讀取文件路徑。commands執(zhí)行命令;通過let config = await command();獲取用戶輸入、選擇項;這一步涉及到commands所以接下來開始分析:bin/commands/index.js用戶輸入命令模塊
(二).命令文件
目錄路徑:bin/commands/index.js
// 引入 commander 命令
import { program } from "commander";
import chalk from "chalk";
// 導(dǎo)入 create創(chuàng)建命令 help幫助命令
import { create, help } from "./options/index.js";
export default async (call) => {
return new Promise((resolve, reject) => {
program
.name(chalk.blue("my-cli"))
.usage("[global options] command");
// 版本信息
program.version("1.0.0");
// 幫助信息
program.option("-F, --framework", "surport vue,react");
// 創(chuàng)建項目命令
create(program, (item) => {
resolve(item);
});
// 幫助信息
help(program);
// 解析指令
program.parse(process.argv);
});
};我們需要關(guān)注的是create、help分別是創(chuàng)建命令、幫助命令
(三).創(chuàng)建命令
create創(chuàng)建命令 ,文件路徑:bin/commands/options/create.js
/**
* @description 創(chuàng)建項目指令
* @author hu-snail 1217437592@qq.com
*/
import inquirer from "inquirer";
import { changeTemplate, changeVariant, inputProjectName } from "../../inquirers/options/index.js";
import {InvalidArgumentError} from "commander";
import chalk from "chalk";
import { hasTemplate, getSupportTs } from '../../utils/index.js'
export default (program, call) => {
program
.command("create")
.argument("<build-method>", "build tools", validatBuildMethod)
.argument("[app-name]", "app name", validatAppName)
.description("create a new project powered by my-cli")
.option("-t, --template <value>", "create a new project by template", validatTemplate)
.action(async (method, projectName, option) => {
let item = {};
// 判斷用戶是否輸入 projectName
if (!projectName) {
item = await inquirer.prompt([inputProjectName(), changeTemplate(), changeVariant()]);
// string轉(zhuǎn)為boolean
item.supportTs = item.supportTs === 'true' ? true : false
return call && call({ method, ...item });
}
// 如果用戶沒有輸入 模板參數(shù),則提供項目類型選擇 react/vue
if (!option.template) {
item = await getFramework(option);
item.supportTs = item.supportTs === 'true' ? true : false
} else {
item = option;
item.supportTs = getSupportTs(item.template)
}
call && call({ method, projectName, ...item });
});
};
/**
* @description 校驗構(gòu)建方式
* @param {String} appName 項目名稱
* @returns appName
*/
function validatBuildMethod(val) {
if (val === "vite") return val;
else
throw new InvalidArgumentError(
chalk.red(
`
"<build-method>構(gòu)建方式,只支持值為:${chalk.green(
"vite"
)}!請重新輸入`
)
);
}
/**
* @description 校驗項目名稱
* @param {String} appName 項目名稱
* @returns appName
*/
function validatAppName(appName) {
var reg = /^[a-zA-Z][-_a-zA-Z0-9]/;
if (!reg.test(appName)) {
throw new InvalidArgumentError(
chalk.red(`
<app-name>項目名稱必須以字母開頭且長度大于2,請重新輸入!`)
);
}
return appName;
}
/**
* @description 校驗?zāi)0?
* @param {String} template 模板名稱
* @returns template
*/
function validatTemplate(template) {
if (hasTemplate(template)) return template
else {
console.log(chalk.white(`error: option '-t, --template <value>' argument '${template}' is invalid`))
}
}
async function getFramework() {
let answer = await inquirer.prompt([changeTemplate(), changeVariant()]);
return answer;
}思路分析:

上圖所示,主要分兩種情況,當(dāng)用戶完整輸入和部分輸入?yún)?shù)判。
關(guān)于program詳細(xì)分析
.command("create"):表示創(chuàng)建一個名為create的命令;
.argument("<build-method>", "build tools", validatBuildMethod):其中argument:表示參數(shù)、<build-method>:表示構(gòu)建方式,必填參數(shù)、build tools:表示參數(shù)描述、validatBuildMethod:表示自定義校驗方法;
[app-name]:表示可選項,項目名稱。<>:表示必填參數(shù),[]:表示可選項
.description("create a new project powered by my-project-cli"):表示命令描述
.option("-t, --template <value>", "create a new project by template"):表示命令參數(shù)、 "-t, --template <value>":表示用戶可輸入-t 和 --template:表示template參數(shù)、"create a new project by template":表示參數(shù)描述;
.action(async (method, projectName, option) => {}):表示命令行為事件,其中method, projectName:分別代表<build-method>構(gòu)建方式、<app-name>項目名稱,和順序關(guān)聯(lián),可根據(jù)自己需求增刪參數(shù)、option:表示參數(shù)選項、也就是"-t, --template <value>"可根據(jù)自己需求定義。
hasTemplate、getSupportTs:分別表示是否存在模板,獲取是否支持ts,在bin/utils/index.js可查看其用法
當(dāng)用戶沒有完整輸入項目框架時,我們會提供changeTemplate、changeVariant、inputProjectName這個時候,就需要用到inquirer交互式命令提供選擇項。
(四).公共處理交互式命令
文件路徑:bin/inquirers/options/common.js
import chalk from "chalk";
export const changeVariant = () => {
return {
type: "list",
name: "supportTs",
choices: [
{
name: 'true',
},
{
name: 'false',
}
],
message:
"Support TS(default by javascript)",
};
};
export const inputProjectName = () => {
return {
type: "input",
name: "projectName",
default: "vite-app-project",
validate: function (appName) {
var done = this.async();
var reg = /^[a-zA-Z][-_a-zA-Z0-9]/;
if (!reg.test(appName)) {
done(chalk.red(`<app-name>項目名稱必須以字母開頭且長度大于2,請重新輸入!`));
}
done(null, true);
},
message:
"Project name",
};
};參數(shù)解釋,完整參數(shù)文檔inpuirer文檔
- type:表示參數(shù)類型,提供
input、number、confirm、list、rawlist、expand、checkbox、password、editor - name:表示參數(shù)屬性
- choices:選擇項
- message: 操作提示語
- de'fa
其中changeVariant:選擇是否支持ts;inputProjectName:輸入項目名稱
(五).選擇項目框架交互式命令
文件路徑:bin/inquirers/options/template.js
/**
* @description 創(chuàng)建項目類型選擇
* @author hu-snail 1217437592@qq.com
*/
export const changeTemplate = () => {
return {
type: "list",
name: "template",
choices: [
{
name: "react",
},
{
name: "vue",
},
],
message: "Select a framework",
};
};創(chuàng)建命令,到此結(jié)束,接下來分析help命令
文件路徑:bin/commands/options/help.js
/**
* @description 幫助信息
* @author hu-snail 1217437592@qq.com
*/
import chalk from "chalk";
export default (program) => {
program.addHelpText(
"after",
`
Run ${chalk.green(
"my-cli <command> --help"
)} for detailed usage of given command.`
);
};addHelpText:表示添加幫助文字,"after":表示在之后,可選項“before”,效果圖如下

(六).導(dǎo)出command命令
文件目錄:bin/commands/option/index.js
/**
* @description 導(dǎo)出所有命令
* @author hu-snail 1217437592@qq.com
*/
import create from "./create.js";
import help from "./help.js";
export { create, help };(七).導(dǎo)出inquirer交互式命令
文件目錄:bin/inquirers/option/index.js
/**
* @description 導(dǎo)出用戶輸入選擇項
* @author hu-snail 1217437592@qq.com
*/
import { changeTemplate } from "./template.js";
import { changeVariant, inputProjectName } from './common.js'
export { changeTemplate, changeVariant, inputProjectName };(八).工具類代碼
export function hasTemplate(template) {
return ['vue', 'vue-ts', 'react', 'react-ts'].includes(template)
}
export function getSupportTs(template) {
return ['vue-ts', 'react-ts'].includes(template)
}這兩個方法在react命令中使用到,補(bǔ)充?。?!
八.本地測試
準(zhǔn)備完以上代碼后,我們開始本地測試,測試目的是檢測是否能夠正確的返回我們預(yù)設(shè)的結(jié)果,可根據(jù)自己的需求定義和完善腳手架。本文只是講解demo流程?。?!
(一).完整輸入
完成輸入測試包含正確輸入、構(gòu)建方式有誤輸入、項目名稱不合規(guī)輸入
js版本完成輸入、默認(rèn)為js版本
# react 版本 yarn start create vite app-test --template react # or npm npm run start create vite app-test--template react
效果圖:

ts版本正確輸入
# react-ts 版本 yarn start create vite app-test --template react-ts # or npm npm run start create vite app-test --template react-ts
效果圖:

以上結(jié)果也就是我們?nèi)肟谖募?code>bin/index.js中let config = await command();獲取的值
構(gòu)建方法輸入有誤vite1:為錯誤輸入? 只支持vite方式構(gòu)建,可根據(jù)自己的需求適配多種構(gòu)建方式
yarn start create vite1 app-test # or npm npm run start create vite1 app-test
效果圖:

項目名稱不合規(guī)輸入 1my-test-project:為不符合規(guī)范名稱
yarn start create vite 1my-test-project --template react # or npm npm run start create vite 1my-test-project --template react
效果圖:

(二).不完整輸入
包含構(gòu)建方式不輸入、名稱不輸入、項目框架方式不輸入
構(gòu)建方式不輸入會直接導(dǎo)致報錯,重點是名稱不輸入、項目框架方式不輸入,這時會給用戶提供選擇
項目名稱不輸入
yarn start create vite # or npm npm run start create vite
效果圖

項目名稱不輸入會提供默認(rèn)值和輸入給用戶,同時完成項目框架選擇、和是否支持ts選擇。完整截圖如下:

這個結(jié)果也就是我們?nèi)肟谖募?code>bin/index.js中let config = await command();獲取的值
項目框架方式不輸入
yarn start create vite app-test # or npm npm run start create vite app-test
效果圖:

不輸入項目框架會提供選擇,同時選擇是否支持ts,完整截圖如下:

到此結(jié)束測試,你可以根據(jù)這個思路完善自己的腳手架,接下來開始模板生成
九.導(dǎo)入Vite + react18模板
當(dāng)我們能獲取到用戶輸入選擇的值后,就可根據(jù)用戶輸入的參數(shù)生成對應(yīng)的項目模板,本文以vite+reat18為例
在bin/templates目錄下創(chuàng)建React模板文件,完整目錄如下:
bin
├── templates # 所有模板文件
│ ├── react # react相關(guān)模板
│ │ ├── src # react主要文件
│ │ │ ├── App.css # App 樣式文件
│ │ │ ├── App.jsx.ejs # App 模板文件
│ │ │ ├── index.css # 首頁樣式文件
│ │ │ ├── main.js.ejs # main 模板文件
│ │ │ ├── logo.svg # react logo文件
│ │ ├── .gitignore # git忽略配置文件
│ │ ├── index.html.ejs # react首頁ejs模板
│ │ ├── package.json.ejs # package.json ejs模板
│ │ ├── tsconfig.json # ts 配置文件
│ │ ├── tsconfig.node.json # tsconfig node配置文件
│ │ ├── vite.config.js.ejs # vite.config ejs模板
具體代碼,查看完整代碼:github.com/hu-snail/my…
如果實現(xiàn)vite + vue3可參考此方法生成對應(yīng)模板
模板技巧:不需要動態(tài)改變文件,直接使用原后綴名,例如:靜態(tài)資源(圖、視頻、音等資源)、需要動態(tài)改變的文件,保留原來的后綴名,在其基礎(chǔ)上添加.ejs:例如(package.json → package.json.ejs)這樣的好處是,知道原來的文件類型,方便更快的處理
十.創(chuàng)建生成模板代碼
完成模板之后,開始重點環(huán)節(jié),根據(jù)用戶輸入選擇參數(shù)動態(tài)生成項目模板,跟著我的步驟完成生成模板語法
第一步:創(chuàng)建build文件夾
在bin/創(chuàng)建build文件,用于存放生成模板文件和生成配置文件,目錄如下:
bin
├── build # 生成模板文件
├── config.js # 模板配置文件
├── react.js # 生成react模板文件
第二步: config配置
因為腳手架內(nèi)置了支持ts和js,所有存在差異化,具體配置如下:
/**
* @description js需要忽略的文件
*/
export const jsignoreFile = [
'tsconfig.json',
'tsconfig.node.json'
]其中模板中的tsconfig.json、 tsconfig.node.jsonts才存在,所以在js需要忽略??砂凑沾朔绞絽^(qū)分文件之間的差異化。
第三步:react生成模板
準(zhǔn)備完以上內(nèi)容后,接下正式開始生成模板,文件路徑:bin/build/react.js
/**
* @description 生成react模板
* @author hu-snail 1217437592@qq.com
*/
import fs from 'fs'
import mkdirp from "mkdirp";
import { getFiles, copyFile, getCode } from '../utils/index.js'
import { jsignoreFile } from './config.js'代碼分析:
代碼中導(dǎo)入了getFiles、copyFile、getCode三個方法,分別代表獲取文件、拷貝文件、獲取代碼。首先從這三個方法開始分析,文件路徑:bin/utils/index.js 。
1).getFiles 獲取文件
import fs from "fs";
let files = []
let dirs = []
export function getFiles(template, dir) {
const templatePath = `./bin/templates/${template}/`
const rootFiles = fs.readdirSync(templatePath, 'utf-8')
rootFiles.map(item => {
const stat = fs.lstatSync(templatePath + item)
const isDir = stat.isDirectory()
if (isDir) {
const itemDir = `${template}/${item}/`.replace(/react\//g, '')
dirs.push(itemDir)
getFiles(`${template}/${item}`, itemDir)
} else files.push((dir ? dir : '') + item)
})
return {files, dirs}
}代碼分析:
該函數(shù)提供兩個參數(shù),分別是template、dir,表示模板、目錄,同時在方法前定義了兩個變量files、dirs分別表示文件、目錄,主要作用區(qū)分文件和目錄,這樣方便用于生成目錄和創(chuàng)建文件。
- templatePath:表示模板地址,根據(jù)傳入
template動態(tài)配置 - rootFiles:表示所有模板文件,通過
fs.readdirSync讀取 - stat:表示文件信息,通過
fs.lstatSync獲取 - isDir: 表示是否目錄,通過
stat.isDirectory()判斷,如果是則遞歸該目錄下的所有文件 - itemDir:表示層級目錄,會把上一層接口也接入
2).copyFile拷貝文件
import fs from "fs";
import { fileURLToPath } from "url";
const __dirname = fileURLToPath(import.meta.url);
/**
* @description 復(fù)制文件,比如圖片/圖標(biāo)靜態(tài)資源
* @param {*} rootPath 根目錄
* @param {*} template 模板
* @param {*} item 靜態(tài)模板文件
*/
export function copyFile(rootPath, template, item) {
const fromFileName = path.resolve(
__dirname,
`../../templates/${template}/${item}`
);
const toFileName = `${rootPath}/${item}`;
const rs = fs.createReadStream(fromFileName, {
autoClose: true,
encoding: "utf-8",
highWaterMark: 64 * 1024 * 1024,
flags: "r",
});
const ws = fs.createWriteStream(toFileName, {
encoding: "utf-8",
flags: "a",
highWaterMark: 16 * 1024 * 1024,
autoClose: true,
});
rs.on("data", (chunk) => {
const wsFlag = ws.write(chunk, "utf-8");
if (!wsFlag) {
rs.pause();
}
});
ws.on("drain", () => {
// 繼續(xù)讀取
rs.resume();
});
rs.on("end", () => {
ws.end();
});
}代碼分析:
該函數(shù)提供三個參數(shù)rootPath 、template、item,分別表示根目錄,模板名稱、靜態(tài)模板路徑。
- fromFileName:表示讀取文件地址
- toFileName:表示拷貝至地址
- rs:表示讀取文件流,通過
fs.createWriteStream創(chuàng)建 - ws:表示寫入文件流,通過
fs.createWriteStream創(chuàng)建
3).getCode獲取代碼
import ejs from "ejs";
import fs from "fs";
/**
* @description 解析ejs模板
* @param {Object} config
* @param {String} templateName
* @param {String} templatePath
* @returns code
*/
export function getCode(config, templateName, templatePath) {
const template = fs.readFileSync(
path.resolve(__dirname, `../../templates/${templateName}/${templatePath}`)
);
const code = ejs.render(template.toString(), {
...config,
});
return code;
}代碼分析:
該函數(shù)接受三個參數(shù)config、templateName、 templatePath:分別表示配置信息、模板名稱、模板地址
- template:模板文件,通過
fs.readFileSync讀取 - code: 模板代碼,通過
ejs.render提供生成 - ...config: 用戶輸入選擇的參數(shù)值,通過此參數(shù)動態(tài)生成對應(yīng)模板
第四步 分析createReact函數(shù)
分析完工具函數(shù)代碼之后,我們正式分析主函數(shù)createReact
/**
* @description 生成react模板
* @author hu-snail 1217437592@qq.com
*/
import fs from 'fs'
import mkdirp from "mkdirp";
import { getFiles, copyFile, getCode } from '../utils/index.js'
import { jsignoreFile } from './config.js'
export const createReact = (config, rootPath) => {
// 創(chuàng)建項目
mkdirp.sync(rootPath)
const { template, supportTs } = config
const reactTemplate = (template === 'react' || template === 'react-ts') ? 'react' : ''
const { files, dirs } = getFiles(reactTemplate)
// 創(chuàng)建文件夾
dirs.map(item => {
mkdirp.sync(`${rootPath}/${item}`)
})
files.map(item => {
const isEjs = item.indexOf('.ejs') !== -1
// 對模板文件進(jìn)行操作
if (isEjs) {
const fileTyep = supportTs ? 'ts' : 'js'
// 去掉ejs后綴
const newItem = item.replace(/.ejs/g, '')
// json后綴名不需要處理
const isJson = newItem.indexOf('.json') !== -1
// 替換后綴名
const newFilePath = isJson ? newItem : newItem.replace(/js/g, fileTyep)
// 寫入相關(guān)模板文件
fs.writeFileSync(
`${rootPath}/${newFilePath}`,
getCode(config, reactTemplate, item)
)
} else {
// 如果不是ejs模板,直接復(fù)制文件
if (supportTs) copyFile(rootPath, reactTemplate, item)
else {
// 判斷是否存在js需要忽略的文件,即存在ts文件
const hasTsFile = jsignoreFile.includes(item)
if (!hasTsFile) copyFile(rootPath, reactTemplate, item)
}
}
})
return ''
}代碼分析:
該函數(shù)接受兩個參數(shù)config、 rootPath:分別表示用戶輸入選擇參數(shù)值配置、項目根目錄。
- mkdirp.sync(rootPath):通過mkdirp創(chuàng)建項目目錄
- reactTemplate:通過
template傳入的值react、react-ts統(tǒng)一react傳入,因為模板目錄只有react,可根據(jù)自己需求調(diào)整
其他項有代碼注釋,不一一贅述
第五步 模板動態(tài)配置
作為測試,我們對package.json.ejs和index.html.ejs進(jìn)行動態(tài)配置,分別對應(yīng)的目錄bin/templates/react/package.json.ejs、bin/templates/react/index.html.ejs
package.json.ejs:代碼
{
"name": "<%= projectName %>",
"private": true,
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"devDependencies": {
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@vitejs/plugin-react": "^1.3.0",
<%_ if (supportTs) { -%>
"typescript": "^4.6.3",
<%_ } -%>
"vite": "^2.9.9"
}
}根據(jù)supportTs:判斷是否需要"typescript",需要注意的<%_ -%>:語法會刪除多余空行,如果使用<% %>:會導(dǎo)致留白,可以自行測試,"name": "<%= projectName %>":根據(jù)創(chuàng)建的項目名稱生成
index.html.ejs:代碼
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" rel="external nofollow" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title><%= projectName %></title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>動態(tài)生成 <title><%= projectName %></title>
以上模板只是測試,可根據(jù)ejs模板語法,動態(tài)生成你需要的值
第五步 入口文件執(zhí)行模板生成
接下來將在入口文件,執(zhí)行我們的createReact函數(shù)
#! /usr/bin/env node
/**
* @description 腳手架入口文件
* @author hu-snail 1217437592@qq.com
*/
import fs, { mkdirSync } from "fs";
import path from "path";
import command from "./commands/index.js";
import { createReact } from './build/react.js'
let config = await command();
var currentPath = path.resolve("./");
createReact(config, getRootPath())
function getRootPath() {
return `${currentPath}/${config.projectName}`;
}生成模板演示
現(xiàn)在可以看看具體效果
(一).js版本
yarn start create vite app-test -t react
效果圖

生成目錄

package.json文件

index.hml文件

(二).ts版本
yarn start create vite app-test -t react-ts
效果圖

生成目錄

package.json文件

index.hml文件相同
總結(jié)
到此這篇關(guān)于用NodeJs構(gòu)建屬于自己的前端腳手工具的文章就介紹到這了,更多相關(guān)NodeJs構(gòu)建前端腳手工具內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Node.js?連接?MySql?統(tǒng)計組件屬性的使用情況解析
這篇文章主要為大家介紹了Node.js?連接?MySql?統(tǒng)計組件屬性的使用情況解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10
在Linux系統(tǒng)中搭建Node.js開發(fā)環(huán)境的簡單步驟講解
這篇文章主要介紹了在Linux系統(tǒng)中搭建Node.js開發(fā)環(huán)境的步驟,Node使得JavaScript程序可以在本地操作系統(tǒng)環(huán)境中解釋運(yùn)行,需要的朋友可以參考下2016-01-01
node實現(xiàn)批量上傳本地圖片轉(zhuǎn)為圖片CDN的項目實踐
本文主要介紹了node實現(xiàn)批量上傳本地圖片轉(zhuǎn)為圖片CDN的項目實踐,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07

