教你用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)信息,也可以直接默認 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 # 導出所有命令文件
│ ├── index.js # 導出command命令方法
├── inquirers # 交互式命令文件
│ ├── options # 交互式命令所有選項
│ │ ├── common.js # 公共交互式命令
│ │ ├── react.js # react相關(guān)命令
│ │ ├── index.js # 導出所有交互式命令文件
│ ├── index.js # 導出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"; // 導入 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; }
思路分析:
上圖所示,主要分兩種情況,當用戶完整輸入和部分輸入?yún)?shù)判。
關(guān)于program
詳細分析
.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
可查看其用法
當用戶沒有完整輸入項目框架時,我們會提供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”
,效果圖如下
(六).導出command命令
文件目錄:bin/commands/option/index.js
/** * @description 導出所有命令 * @author hu-snail 1217437592@qq.com */ import create from "./create.js"; import help from "./help.js"; export { create, help };
(七).導出inquirer交互式命令
文件目錄:bin/inquirers/option/index.js
/** * @description 導出用戶輸入選擇項 * @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命令中使用到,補充?。?!
八.本地測試
準備完以上代碼后,我們開始本地測試,測試目的是檢測是否能夠正確的返回我們預(yù)設(shè)的結(jié)果,可根據(jù)自己的需求定義和完善腳手架。本文只是講解demo流程?。?!
(一).完整輸入
完成輸入測試包含正確輸入、構(gòu)建方式有誤輸入、項目名稱不合規(guī)輸入
js版本完成輸入、默認為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)建方式不輸入會直接導致報錯,重點是名稱不輸入、項目框架方式不輸入,這時會給用戶提供選擇
項目名稱不輸入
yarn start create vite # or npm npm run start create vite
效果圖
項目名稱不輸入會提供默認值和輸入給用戶,同時完成項目框架選擇、和是否支持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ù)這個思路完善自己的腳手架,接下來開始模板生成
九.導入Vite + react18模板
當我們能獲取到用戶輸入選擇的值后,就可根據(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.json
ts才存在,所以在js需要忽略??砂凑沾朔绞絽^(qū)分文件之間的差異化。
第三步:react生成模板
準備完以上內(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'
代碼分析:
代碼中導入了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ù)制文件,比如圖片/圖標靜態(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 // 對模板文件進行操作 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
進行動態(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"
,需要注意的<%_ -%>
:語法會刪除多余空行,如果使用<% %>
:會導致留白,可以自行測試,"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)計組件屬性的使用情況解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪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)境中解釋運行,需要的朋友可以參考下2016-01-01node實現(xiàn)批量上傳本地圖片轉(zhuǎn)為圖片CDN的項目實踐
本文主要介紹了node實現(xiàn)批量上傳本地圖片轉(zhuǎn)為圖片CDN的項目實踐,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-07-07