vue腳手架能生成vue的模板,把所有的都封裝好了。但是比如我們是一個專題項目,對于公司來說肯定有些自己的配置,每次用腳手架生成后,還要修改,那么我們能不能自己生成一個類似于腳手架,每次就生成封裝好的模板呢。
在之前,先介紹會用的的儲備知識
1. ora進度轉輪
用于node的控制臺進度美化
直接看栗子吧
const ora = require('ora');
const spinner = ora('進入loading狀態...')
spinner.start()
setTimeout(() => {
// spinner.stop()
// spinner.succeed()
// spinner.fail()
spinner.fail('失敗了')
}, 2000)
當我們創建模板過程中,更好的用戶體驗,顯示loading狀態, stop直接結束。 成功和失敗 還可以傳入不同的文字。
2. chalk 控制臺 以 彩色顯示 提示信息
const chalk = require('chalk');
console.log(chalk.blue('Hello world!'))
console.log(chalk.blue('Hello') + 'World' + chalk.red('!'));
console.log(chalk.blue.bgRed.bold('Hello world!'));
console.log(chalk.green('Hello', 'World!', 'Foo', 'bar', 'biz', 'baz'));
console.log(chalk.red('Hello', chalk.underline.bgBlue('world') + '!'));
3.log-symbols - 為各種日志級別提供著色的符號(控制臺 左邊的 ?,??,??,!等)
const symbols = require('log-symbols');
const chalk = require('chalk');
console.log(symbols.error, chalk.red('錯誤'));
console.log(symbols.warning, chalk.yellow('警告'));
console.log(symbols.info, chalk.blue('提示'));
console.log(symbols.success, chalk.green('成功'));
4.path 路徑
項目中用到的
path.sep
提供平臺特定的路徑片段分隔符:
Windows 上是 \。
POSIX 上是 /。
console.log('foo/bar/baz'.split(path.sep)); // [ 'foo', 'bar', 'baz' ]
path.resolve([...paths])
path.resolve() 方法會將路徑或路徑片段的序列解析為絕對路徑
path.resolve('/目錄1/目錄2', './目錄3');
// 返回: '/目錄1/目錄2/目錄3'
path.resolve('/目錄1/目錄2', '/目錄3/目錄4/');
// 返回: '/目錄3/目錄4'
path.join([...paths])
path.join() 方法會將所有給定的 path 片段連接到一起(使用平臺特定的分隔符作為定界符),然后規范化生成的路徑。
path.join('/目錄1', '目錄2', '目錄3/目錄4', '目錄5', '..');
// 返回: '/目錄1/目錄2/目錄3/目錄4'
path.resolve('/目錄1/目錄2', '/目錄3/目錄4/');
// 返回: '/目錄1/目錄2/目錄3/目錄4'
path.normalize(path)
path.normalize() 方法規范化給定的 path,解析 '..' 和 '.' 片段。
當找到多個連續的路徑段分隔字符時(例如 POSIX 上的 /、Windows 上的 \ 或 /),則它們將被替換為單個平臺特定的路徑段分隔符(POSIX 上的 /、Windows 上的 \)。 尾部的分隔符會保留。
如果 path 是零長度的字符串,則返回 '.',表示當前工作目錄。
console.log(path.normalize('')); // .
console.log(path.normalize('/a//b////c///////d////')); // /a/b/c/d/
console.log(path.normalize('/a/b/c/../../d')); // /a/d
5.fs-extra
fs-extra -- 文件操作相關工具庫
fs-extra模塊是系統fs模塊的擴展,提供了更多便利的 API,并繼承了fs模塊的 API
const fs = require('fs-extra');
fs.copy(src, dest, [option],callback)
復制文件或目錄
-
src
<String>
請注意,如果src
是目錄,它將復制此目錄中的所有內容,而不是整個目錄本身(請參閱問題#537)。 -
dest
<String>
請注意,如果src
是文件,dest
則不能是目錄(請參閱問題#323)。 -
options
<Object>
-
overwrite
<boolean>
:覆蓋現有文件或目錄,默認為true
。請注意,如果將其設置為false
并且目標存在,則復制操作將以靜默方式失敗。使用該errorOnExist
選項可以更改此行為。 -
errorOnExist
<boolean>
:當overwrite
isfalse
并且目的地存在時,引發錯誤。默認值為false
。 -
dereference
<boolean>
:取消引用符號鏈接,默認為false
。 -
preserveTimestamps
<boolean>
:為true時,將設置對原始源文件的修改和訪問時間。如果為false,則時間戳記行為取決于OS。默認值為false
。 -
filter
<Function>
:用于過濾復制的文件/目錄的功能。返回true
以復制該項目,false
忽略它。
-
emptyDir(dir[, callback])
確保目錄為空。如果目錄不為空,則刪除目錄內容。如果該目錄不存在,則會創建該目錄。目錄本身不會被刪除。
ensureFile(file[, callback])
確保文件存在。如果請求創建的文件位于不存在的目錄中,則會創建這些目錄
ensureDir(dir [,options] [,callback])
確保目錄存在。如果目錄結構不存在,則會創建它。
pathExistsSync
別名fs.existsSync()
,為與保持一致而創建pathExists()
。
如果路徑存在,則返回 true,否則返回 false。
readJsonSync(file[, options])
讀取JSON文件,然后將其解析為一個對象。
const fs = require('fs-extra')
const packageObj = fs.readJsonSync('./package.json')
console.log(packageObj.version) // => 2.0.0
6.inquirer NodeJs交互式命令行工具Inquirer.js
它是非常容易去處理以下幾種事情的:
- 提供錯誤回調
- 詢問操作者問題
- 獲取并解析用戶輸入
- 檢測用戶回答是否合法
- 管理多層級的提示
inquirer.prompt([ {
type: 'confirm',
name: 'test',
message: 'Are you handsome?',
default: true
}]).then((answers) => { console.log('結果為:'); console.log(answers)})
問題的標題和默認結果值都是可以預設的。而在回答完成后會返回一個Promise對象,在其then方法中可以獲取到用戶輸入的所有回答。其中傳遞給prompt方法的參數為一個question問題數組,數組中的每個元素都是一個問題對象。其包含的屬性共有以下幾種:
type: String, // 表示提問的類型,下文會單獨解釋 name: String, // 在最后獲取到的answers回答對象中,作為當前這個問題的鍵
message: String|Function, // 打印出來的問題標題,如果為函數的話
default: String|Number|Array|Function, // 用戶不輸入回答時,問題的默認值。或者使用函數來return一個默認值。假如為函數時,函數第一個參數為當前問題的輸入答案。
choices: Array|Function, // 給出一個選擇的列表,假如是一個函數的話,第一個參數為當前問題的輸入答案。為數組時,數組的每個元素可以為基本類型中的值。
validate: Function, // 接受用戶輸入,并且當值合法時,函數返回true。當函數返回false時,一個默認的錯誤信息會被提供給用戶。
filter: Function, // 接受用戶輸入并且將值轉化后返回填充入最后的answers對象內。
when: Function|Boolean, // 接受當前用戶輸入的answers對象,并且通過返回true或者false來決定是否當前的問題應該去問。也可以是簡單類型的值。
pageSize: Number, // 改變渲染list,rawlist,expand或者checkbox時的行數的長度。}
Prompt types —— 問題類型
- List
{type: 'list'}
問題對象中必須有type,name,message,choices等屬性,同時,default選項必須為默認值在choices數組中的位置索引(Boolean)
inquirer.prompt([ {
type: 'list',
name: 'test',
choices: ['a','b','c'],
message: '請選擇?',
default: 1
}]).then((answers) => { console.log(answers)})
加上pageSize
inquirer.prompt([ {
type: 'list',
name: 'test',
choices: ['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa','b','c'],
message: '請選擇?',
default: 1 ,
pageSize: 2
}]).then((answers) => { console.log(answers)})
此時就滯后兩個選擇了
- Raw list
{type: 'rawlist'}
與List類型類似,不同在于,list打印出來為無序列表,而rawlist打印為有序列表
inquirer.prompt([ {
type: 'rawlist',
name: 'test',
choices: ['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa','b','c'],
message: '請選擇?',
default: 1 ,
}]).then((answers) => { console.log(answers)})
- Expand
{type: 'expand'}
同樣是生成列表,但是在choices屬性中需要增加一個屬性:key,這個屬性用于快速選擇問題的答案。類似于alias或者shorthand的東西。同時這個屬性值必須為一個小寫字母
inquirer.prompt([ {
type: 'expand',
name: 'test',
choices: [
{
key: 'y',
name: 'Overwrite',
value: 'overwrite',
},
{
key: 'a',
name: 'Overwrite this one and all next',
value: 'overwrite_all',
},
{
key: 'd',
name: 'Show diff',
value: 'diff',
},
{
key: 'x',
name: 'Abort',
value: 'abort',
},
],
message: '請選擇?',
}]).then((answers) => { console.log(answers)})
直接輸入key 值就可以選擇了
- Checkbox
{type: 'checkbox'}
其余諸項與list類似,主要區別在于,是以一個checkbox的形式進行選擇。同時在choices數組中,帶有checked: true屬性的選項為默認值。
inquirer.prompt([ {
type: 'checkbox',
name: 'test',
choices:['a','b','c','d'],
message: '請選擇?',
default: ['b','d'] ,
}]).then((answers) => { console.log(answers)})
- Confirm
{type: 'confirm'}
提問,回答為Y/N。若有default屬性,則屬性值應為Boolean類型
inquirer.prompt([ {
type: 'confirm',
name: 'test',
message: '需要默認選擇嗎?',
default: false,
}]).then((answers) => { console.log(answers)})
默認是大寫字母
- Input
{type: 'input'}
獲取用戶輸入字符串
inquirer.prompt([ {
type: 'input',
name: 'test',
message: '輸入手機號',
default: '110120',
}]).then((answers) => { console.log(answers)})
- Password
{type: 'password'}與input類型類似,只是用戶輸入在命令行中呈現為XXXX
inquirer.prompt([ {
type: 'password',
name: 'test',
message: '輸入密碼',
default: '110120',
}]).then((answers) => { console.log(answers)})
- Editor
{type: 'editor'}
終端打開用戶默認編輯器,如vim,notepad。并將用戶輸入的文本傳回
inquirer.prompt([ {
type: 'editor',
name: 'test',
message: '輸入',
}]).then((answers) => { console.log(answers)})
enter鍵進入編輯頁面
7.handlebars
Handlebars 是一種簡單的 模板語言
類似于 vue 。 這里就是用來解析 默認 package.json中的值比如
{
"name": "{{name}}",
"year": "{{year}}",
"outputFolder": "{{outputFolder}}",
"outputFolderNeibu": "neibu-dist",
"version": "1.0.0",
"ztType": "{{ztType}}",
"author": "{{author}}",
"platform": "{{platform}}",
"description": "",
"main": "index.js",
....
創建模板是替換為上面輸入的 值,生成新的 package.json文件
8.process.platform 屬性會返回標識操作系統平臺(Node.js 進程運行其上的)的字符串。
當前可能的值有:
- 'aix'
- 'darwin'
- 'freebsd'
- 'linux'
- 'openbsd'
- 'sunos'
- 'win32'
console.log(此平臺是 ${process.platform}
);
9.child_process 子進程
child_process.spawn(command[, args][, options])
-
command
<string> 要運行的命令。 -
args
<string[]> 字符串參數的列表。 -
options
<Object>-
cwd
<string> 子進程的當前工作目錄。 -
env
<Object> 環境變量的鍵值對。 默認值:process.env
。 -
argv0
<string> 顯式地設置發送給子進程的argv[0]
的值。 如果沒有指定,則會被設置為command
的值。 -
stdio
<Array> | <string> 子進程的 stdio 配置,參見options.stdio
。 -
detached
<boolean> 使子進程獨立于其父進程運行。 具體行為取決于平臺,參見options.detached
。 -
uid
<number> 設置進程的用戶標識,參見setuid(2)
。 -
gid
<number> 設置進程的群組標識,參見setgid(2)
。 -
serialization
<string> 指定用于在進程之間發送消息的序列化類型。 可能的值為'json'
和'advanced'
。 詳見高級序列化。 默認值:'json'
。 -
shell
<boolean> | <string> 如果為true
,則在 shell 中運行command
。 在 Unix 上使用'/bin/sh'
,在 Windows 上使用process.env.ComSpec
。 可以將不同的 shell 指定為字符串。 參見 shell 的要求和默認的 Windows shell。 默認值:false
(沒有 shell)。 -
windowsVerbatimArguments
<boolean> 在 Windows 上不為參數加上引號或轉義。 在 Unix 上會被忽略。 如果指定了shell
并且是 CMD,則自動設為true
。 默認值:false
。 -
windowsHide
<boolean> 隱藏子進程的控制臺窗口(在 Windows 系統上通常會創建)。 默認值:false
。
-
- 返回: <ChildProcess>
child_process.spawn()
方法使用給定的 command
衍生新的進程,并傳入 args
中的命令行參數。 如果省略 args
,則其默認為空數組。
如果啟用了 shell
選項,則不要將未經過處理的用戶輸入傳給此函數。 包含 shell 元字符的任何輸入都可用于觸發任意命令的執行。
栗子
const { spawn } = require('child_process');
const ls = spawn('node', ['-v']);
ls.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
ls.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});
ls.on('close', (code) => {
console.log(`子進程退出,退出碼 ${code}`); // code 為 0 成功
});
ok 下面看代碼
create.js (#!/usr/bin/env node頂上要加上,這樣發布到npm后 才能按node執行)
#!/usr/bin/env node
const fs = require('fs-extra');
const inquirer = require('inquirer');
const handlebars = require('handlebars');
const ora = require('ora');
const chalk = require('chalk');
const symbols = require('log-symbols');
const { spawn } = require('child_process');
const path = require('path');
const now = new Date();
// 交互式獲取用戶 的需求
inquirer.prompt([
{
type: 'input',
name: 'year',
message: '請輸入項目年份,比如2019(插件請輸入"plugins")',
default: now.getFullYear(),
}, {
type: 'input',
name: 'name',
message: '請輸入項目名稱,比如0702projectname(注意不要使用過長的文件名,CMS會報錯)',
default: `${String(now.getMonth() + 1).padStart(2, '00')}${now.getDate()}-projectName`,
}, {
type: 'list',
name: 'platform',
message: 'PC端項目還是wap端項目?',
choices: ['pc', 'wap'],
default: 0,
}, {
type: 'input',
name: 'author',
message: '請輸入作者名稱',
default: 'author_name',
}, {
type: 'input',
name: 'ztType',
message: '請輸入專題類型(kaoyan):',
default: 'kaoyan',
}, {
type: 'confirm',
name: 'autoInitialize',
message: '是否自動安裝依賴?',
}, {
type: 'list',
name: 'packageManager',
message: '使用哪種包管理工具?',
choices: ['npm', 'yarn'],
default: 0,
},
]).then((answers) => {
const { year, name } = answers;
// 創建 專題 還是 插件
const projectBasePath = year == 'plugins' ? 'project/plugins' : `project/zt/${year}/${name}`;
answers.outputFolder = 'pro-dist';
// 項目已存在則退出
if (fs.existsSync(projectBasePath)) {
return console.log(symbols.error, chalk.red('項目已存在'));
}
// 拷貝模板
downloadTemplate(projectBasePath, answers);
return true;
});
// 拷貝模板
function downloadTemplate (projectBasePath, answers) {
let templatePath = path.join(__dirname, '../', 'template', 'base');
// 把 基礎模板 拷貝 到用戶 輸入后的新位置
fs.copySync(templatePath, projectBasePath);
// 處理wap的差異
if (answers.platform === 'wap') {
handlePlatform(projectBasePath);
}
// 處理package.json文件
updatePackageJson(answers, projectBasePath);
console.log(symbols.success, chalk.green('項目初始化成功'));
// 如果開始交互式 有選擇 默認安裝依賴,則安裝依賴
answers.autoInitialize && yarnInstall(projectBasePath, answers.packageManager);
}
// 處理pc 和 wap的差異
function handlePlatform (projectBasePath) {
// html模板引用文件
const indexHtmlFileName = `${projectBasePath}/src/index.ejs`;
// 讀取 頁面內容 把 引入的 pc轉換為 wap 的(wap會做rem處理)
const htmlFile = fs.readFileSync(indexHtmlFileName)
.toString()
.replace('./ejstpls/public-pc.ejs', './ejstpls/public-wap.ejs');
// 處理之后把 模板 頁面替換
fs.writeFileSync(indexHtmlFileName, htmlFile);
}
// 合并選項到package.json
function updatePackageJson (answers, projectBasePath) {
const meta = { ...answers };
console.log(symbols.success, chalk.green(`你的配置是:\n${JSON.stringify(meta)}`));
// 獲取創建后 新的package.json
const fileName = `${projectBasePath}/package.json`;
// 獲取里面 的內容
const content = fs.readFileSync(fileName).toString();
// 用模板引擎 修改 {{}} 的內容
const result = handlebars.compile(content)(meta);
// 重新 package.json
fs.writeFileSync(fileName, result);
}
// 安裝依賴
function yarnInstall (dir, pm) {
// 提示用戶安裝中
const spinner = ora('正在安裝依賴……').start();
// pm 是用選擇的 npm 或者yarn 開始安裝package.json 中的依賴
const sp = spawn(pm, ['install'], {
cwd: path.resolve(process.cwd(), `./${dir}`), // process.cwd() 當前命令執行的目錄
shell: /^win/.test(process.platform),
});
// 這里僅僅是輸出 查看,其實可以去掉
sp.on('message', (msg) => {
console.log(msg.toString());
});
sp.stdout.on('data', (data) => {
console.log(data.toString());
});
sp.on('close', (code) => {
// 安裝依賴失敗
if (code !== 0) {
spinner.fail();
return console.log(chalk.red(`安裝失敗,退出碼 ${code}`));
}
// 安裝依賴成功
spinner.succeed();
console.log(chalk.green(`下載依賴成功~請執行cd ${dir}`));
return true;
});
}
結構
當然, template/base中就是 你們公司 需要定制化的一套模板了
** 補充 **
如果要要使用類似于 vue-cli的腳手架,用命令行就能安裝的話,
新建一個bin目錄,把create.js放進去。當然 create.js中有些路徑的話要自己修改一下了。
需要去package.json中 增加一個bin命令
"bin": {
"my-create": "./bin/create.js"
},
然后本地測試的時候 ,要npm link 把my-create命令創建為全局命令,類似于cnpm ,yarn等等一樣。 此時 命令行輸入 my-create 其實就是 進入bin目錄運行 node create.js命令了。
如果沒問題,就可以把自己的 這個模板項目發布到npm上了。
以后自己下載到全局,運行 my-create就可以 執行按照模板了。
比如:我安裝好之后就是 npm i zxx-cli-test -g
安裝好之后直接運行my-create 就可以安裝我的模板了;
其他
同一個套代碼,不同的啟動方式,配置不同的項目
const inquirer = require('inquirer'); // 8.0.0
const fs = require('fs-extra');
const path = require('path');
const { spawn } = require('child_process');
const handlebars = require('handlebars');
const ora = require('ora'); // 5.4.0
const chalk = require('chalk'); // 4.1.2
const symbols = require('log-symbols'); // 4.1.0
const peizhiObj = {
test: {
'通用散客im': 'aaaaa',
'養老家醫': 'bbbbb',
'健康有約': 'cccc',
'私人牙醫': 'ddddd',
},
production: {
'通用散客im': 'eeee',
'養老家醫': 'ffff',
'健康有約': 'ggg',
'私人牙醫': 'dds',
}
}
const typeList = ['通用散客im', '養老家醫', '健康有約', '私人牙醫']
const routerObj = {
'通用散客im': 'commonImRouter.js',
'養老家醫': 'elderlyRouter.js',
'健康有約': 'healthAppointRouter.js',
'私人牙醫': 'privateDentist.js'
}
// 模板文件地址
const appConfig_base_path = path.resolve(process.cwd(), './build/appConfig.json')
const envBeat_base_path = path.resolve(process.cwd(), './build/.env.beta')
const envProduction_base_path = path.resolve(process.cwd(), './build/.env.production')
// 需要替換文件地址
const appConfig_dest_path = path.resolve(process.cwd(), './public/appConfig.json')
const envBeat_dest_path = path.resolve(process.cwd(), './.env.beta')
const envProduction_dest_path = path.resolve(process.cwd(), './.env.production')
// 臨時保存的文件地址
const appConfig_tem_path = path.resolve(process.cwd(), './build/tem/appConfig.json')
const envBeat_tem_path = path.resolve(process.cwd(), './build/tem/.env.beta')
const envProduction_tem_path = path.resolve(process.cwd(), './build/tem/.env.production')
// 交互式獲取用戶 的需求
inquirer.prompt([
{
type: 'list',
name: 'env',
message: '打測試包還是線上包',
choices: Object.keys(peizhiObj), // ['test', 'production']
default: 0,
},
{
type: 'list',
name: 'type',
message: '小應用類型',
choices: typeList,
default: 0,
}, {
type: 'confirm',
name: 'autoInitialize',
message: '是否自動安裝依賴?',
}
]).then((answers) => {
// 臨時保存以前的 appid
fs.copySync(appConfig_dest_path, appConfig_tem_path);
fs.copySync(envBeat_dest_path, envBeat_tem_path);
fs.copySync(envProduction_dest_path, envProduction_tem_path);
updateTemplate(answers)
updateRouter(answers);
return true;
})
function updateTemplate (answers) {
const { env, type, autoInitialize } = answers
const data = { appid: peizhiObj[env][type] }
// 獲取模板里面 的內容
const appConfigContent = fs.readFileSync(appConfig_base_path).toString();
const envBetaContent = fs.readFileSync(envBeat_base_path).toString();
const envProductionContent = fs.readFileSync(envProduction_base_path).toString();
// 用模板引擎 修改 {{}} 的內容
const result_appConfigContent = handlebars.compile(appConfigContent)(data);
const result_envBetaContent = handlebars.compile(envBetaContent)(data);
const result_envProductionContent = handlebars.compile(envProductionContent)(data);
fs.writeFileSync(appConfig_dest_path, result_appConfigContent);
fs.writeFileSync(envBeat_dest_path, result_envBetaContent);
fs.writeFileSync(envProduction_dest_path, result_envProductionContent);
if (!autoInitialize) {
fs.unlink(appConfig_tem_path)
console.log(symbols.success, chalk.green('配置修改完成, 請去手動打包'));
// 自己要打包,所有不會恢復public/appConfig.json
}
// 如果開始交互式 有選擇 默認安裝依賴,則安裝依賴
autoInitialize && yarnInstall(env);
}
// 更新路由
function updateRouter (answers) {
const { type } = answers;
// 配置的router
const nowRouterPath = path.resolve(process.cwd(), './src/router/routerConfig/' + routerObj[type]);
// 用的路由地址
const useRouterPath = path.resolve(process.cwd(), './src/router/router.js');
const nowRouter = fs.readFileSync(nowRouterPath).toString();
// 先刪除
fs.unlinkSync(useRouterPath);
fs.writeFileSync(useRouterPath, nowRouter);
}
function yarnInstall (env) {
// 提示用戶安裝中
const spinner = ora('正在安裝依賴……').start();
// pm 是用選擇的 npm 開始安裝package.json 中的依賴
const binObj = {
test: 'beta',
production: 'pre-build',
}
// 如果是生產環境
if (env === 'production') {
const packagePath = path.resolve(process.cwd(), './package.json')
const res = fs.readFileSync(packagePath, 'utf8');
const data = JSON.parse(res);
// 獲取健康依賴包
let jkChronic = data.dependencies['jk-chronic']
let jkFollowupplan = data.dependencies['jk-followupplan']
let jkHealthRecords = data.dependencies['jk-health-records']
let jkQuestionaire = data.dependencies['jk-questionaire']
let jkVideocomponent = data.dependencies['jk-videocomponent']
// 去除 ^ ~ 等符號 只留下具體版本號
jkChronic = jkChronic.replace(/[^0-9|\.]/g, '')
jkFollowupplan = jkFollowupplan.replace(/[^0-9|\.]/g, '')
jkHealthRecords = jkHealthRecords.replace(/[^0-9|\.]/g, '')
jkQuestionaire = jkQuestionaire.replace(/[^0-9|\.]/g, '')
jkVideocomponent = jkVideocomponent.replace(/[^0-9|\.]/g, '')
// 修改package.json中的 更新指定 健康包版本
data.scripts.updateWithVersion = `npm install jk-chronic@${jkChronic} jk-followupplan@${jkFollowupplan} jk-questionaire@${jkQuestionaire} jk-health-records@${jkHealthRecords} jk-videocomponent@${jkVideocomponent} --legacy-peer-deps`
// 重新package.json (自己手動去格式化一下該文件,樣式不好看)
fs.writeFileSync(packagePath, JSON.stringify(data, "", "\t"));
}
const sp = spawn(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['run', binObj[env]], {
cwd: path.resolve(process.cwd(), './'), // process.cwd() 當前命令執行的目錄
});
// 這里僅僅是輸出 查看,其實可以去掉
sp.on('message', (msg) => {
console.log(msg.toString());
});
sp.stdout.on('data', (data) => {
console.log(data.toString());
});
sp.on('close', (code) => {
// 安裝依賴失敗
if (code !== 0) {
spinner.fail();
return console.log(symbols.error, chalk.green(`安裝失敗,退出碼 ${code}`));
}
// 安裝依賴成功
spinner.succeed();
// 還原appid
fs.copySync(appConfig_tem_path, appConfig_dest_path)
fs.copySync(envBeat_tem_path, envBeat_dest_path)
fs.copySync(envProduction_tem_path, envProduction_dest_path)
// 刪除臨時保存的appid
fs.unlink(appConfig_tem_path)
fs.unlink(envBeat_tem_path)
fs.unlink(envProduction_tem_path)
console.log(symbols.success, chalk.green(`安裝依賴打包成功,去平臺發布吧`));
return true;
});
}
start.js 啟動不同的服務
const inquirer = require('inquirer'); // 8.0.0
const fs = require('fs-extra');
const path = require('path');
const handlebars = require('handlebars');
const peizhiObj = {
test: {
'通用散客im': 'aaa',
'養老家醫': 'bbbb',
'健康有約': 'cccc',
'私人牙醫': 'ddddd',
}
}
const routerObj = {
'通用散客im': 'commonImRouter.js',
'養老家醫': 'elderlyRouter.js',
'健康有約': 'healthAppointRouter.js',
'私人牙醫': 'privateDentist.js'
}
const typeList = ['通用散客im', '養老家醫', '健康有約', '私人牙醫']
// 模板文件地址
const envDevelopment_base_path = path.resolve(process.cwd(), './build/.env.development')
// 需要替換文件地址
const envDevelopment_dest_path = path.resolve(process.cwd(), './.env.development')
// 交互式獲取用戶 的需求
inquirer.prompt([
{
type: 'list',
name: 'type',
message: '啟動小應用類型',
choices: typeList,
default: 0,
}
]).then((answers) => {
updateTemplate(answers);
updateRouter(answers);
})
function updateTemplate(answers) {
const { type } = answers
const data = { appid: peizhiObj['test'][type] }
// 獲取模板里面 的內容
const envDevelopmentContent = fs.readFileSync(envDevelopment_base_path).toString();
// 用模板引擎 修改 {{}} 的內容
const result_envDevelopmentContent = handlebars.compile(envDevelopmentContent)(data);
fs.writeFileSync(envDevelopment_dest_path, result_envDevelopmentContent);
}
function updateRouter(answers) {
const { type } = answers;
// 配置的router
const nowRouterPath = path.resolve(process.cwd(), './src/router/routerConfig/' + routerObj[type]);
// 用的路由地址
const useRouterPath = path.resolve(process.cwd(), './src/router/router.js');
const nowRouter = fs.readFileSync(nowRouterPath).toString();
// 先刪除
fs.unlinkSync(useRouterPath);
fs.writeFileSync(useRouterPath, nowRouter);
}