前言
在上一篇文章中,我們實現了一個web工程通用腳手架工具,目的是快速搭建專案。在實際開發中,特別是後臺管理系統,有很多相似的程式碼實現。於是乎,我們可以繼續實現一個快速生成web程式碼模板的工具,告別複製/貼上。
基本流程
基本思路其實很簡單,就是通過命令調取定義好的模板,然後生成程式碼檔案:
專案結構
xman-tcli
├─ bin
│ └─ xmant.js
├─ command
│ ├─ createFile.js
│ └─ createManyFiles.js
├─ config
│ └─ fileList.js
├─ templates
│ ├─ index.js
│ ├─ js
│ │ ├─ reactClassJSX.js
│ │ └─ reactFuncJSX.js
│ └─ ts
│ ├─ reactClassTSX.js
│ ├─ reactFuncTS.js
│ └─ reactFuncTSX.js
├─ utils
│ └─ index.js
├─ .gitignore
├─ LICENSE
├─ package.json
└─ README.md
具體實現
很多依賴的用處上一篇文章已經提及,所以這篇文章就不會過多的介紹。
初始化專案
可以用 npm init 進行建立,也可以根據下面列出的 package.json 進行修改。
{
"name": "xman-tcli",
"version": "1.0.0",
"description": "web-cli工具,可以快速建立template",
"bin": {
"xmant": "bin/xmant.js"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "https://github.com/XmanLin/xman-tcli.git"
},
"keywords": [
"cli"
],
"author": "xmanlin",
"license": "MIT",
"dependencies": {
"chalk": "^4.1.2",
"clui": "^0.3.6",
"commander": "^8.2.0",
"figlet": "^1.5.2",
"handlebars": "^4.7.7",
"inquirer": "^8.1.5",
"update-notifier": "^5.1.0"
}
}
編寫bin/xman.js
#!/usr/bin/env node
const { program } = require('commander');
program
.version(require('../package').version, '-v, --version');
program.parse(process.argv); // 這裡是必要的
if (!program.args.length) {
program.help();
}
在當前xmant-cli目錄下,執行 npm link
後,就可以在本地對腳手架工具進行除錯了。
然後在當前目錄下執行:
xmant -v
說明工程初步搭建成功。
通過命令快速建立單個程式碼模板
編寫模板
程式碼模板可以根據實際專案進行抽離,這裡利用幾個簡單的模板作為例子。
templates/js/reactClassJSX.js
return `
import * as React from 'react';
export class ${className} extends React.Component{
constructor(props){
super(props);
this.state = {}
}
componentDidMount(){
}
render() {
return (
<div></div>
)
}
}
`
}
templates/js/reactFuncJSX.js
module.exports = function (funcName) {
return `
import React, {useEffect, useState} from 'react';
const ${funcName} = (props) => {
return (
<div></div>
)
}
export default ${funcName};
`
}
templates/ts/reactClassTSX.js
module.exports = function (className) {
return `
import * as React from 'react';
interface Props {}
interface State {}
export class ${className} extends React.Component<Props, State>{
constructor(props: Props){
super(props);
this.state = {}
}
componentDidMount(){
}
render() {
return (
<div></div>
)
}
}
`
}
templates/ts/reactFuncTS.js
module.exports = function (funcName) {
return `
export const ${funcName} = () => {
}
`
}
templates/ts/reactFuncTSX.js
module.exports = function (funcName) {
return `
import React, {useEffect, useState} from 'react';
const ${funcName} = (props: any) => {
useEffect(() => {
},[])
return (
<div></div>
)
}
export default ${funcName};
`
}
模板定義好之後,通過 index.js
統一匯出。
templates/index.js
const reactClassJSX = require('./js/reactClassJSX');
const reactFuncJSX = require('./js/reactFuncJSX');
const reactClassTSX = require('./ts/reactClassTSX');
const reactFuncTSX = require('./ts/reactFuncTSX');
const reactFuncTS = require('./ts/reactFuncTS');
// 命名規範:name由“-”連結,前面為模板名,後面為建立後檔案的字尾
module.exports = [
{
name: 'reactClass-jsx', src: reactClassJSX
},
{
name: 'reactFunc-jsx', src: reactFuncJSX
},
{
name: 'reactClass-tsx', src: reactClassTSX
},
{
name: 'reactFunc-tsx', src: reactFuncTSX
},
{
name: 'reactFunc-ts', src: reactFuncTS
}
]
這裡的“命名規範”,目的是為了後面建立檔案時得到相應的字尾。
建立工具函式 utils/index.js:
module.exports = {
getFileSuffix: (name) => {
if(typeof name === 'string') {
return name.split('-')[1]
}
}
}
編寫建立檔案邏輯
準備工作就緒,接下來就是檔案建立的邏輯 command/createFile.js:
// 建立單個檔案
const templates = require('../templates/index');
const chalk = require('chalk');
const inquirer = require('inquirer');
const fs = require("fs");
const utils = require('../utils/index');
module.exports = () => {
inquirer.prompt([
{
name: 'templateName',
type:'list',
message: '請選擇你想要生成的程式碼模板:',
choices: templates
},
{
name: 'filename',
type:'input',
message: '請輸入程式碼檔案中類名或方法名:',
validate: function (value) {
if (value.length) {
return true;
} else {
return '請輸入程式碼檔案中類名或方法名';
}
},
}
])
.then(answers => {
const templateName = answers.templateName;
const filename = answers.filename;
templates.forEach((item) => {
if(item.name === templateName) {
const suffix = utils.getFileSuffix(item.name)
const file = `./index.${suffix}`
// 檢驗當前資料夾下是否有同名檔案
fs.access(file, function(err) {
if(!err) {
console.log('建立失敗:', chalk.yellow('檔案已存在'))
} else {
fs.writeFile(file, item.src(filename), function(err) {
if(err) {
console.log('建立失敗:', chalk.red(err))
} else {
console.log(chalk.green(`建立檔案成功!${file}`));
}
})
}
})
}
})
})
}
這裡需要注意的是:如果不在檔案建立之前檢查當前資料夾下是否有同名檔案的話,原有的同名檔案將被覆蓋。
編寫命令
最後就是命令的編寫 bin/xman.js:
#!/usr/bin/env node
const { program } = require('commander');
...
program
.command('create')
.description("Create a file")
.alias('c')
.action(() => {
require('../command/createFile')()
});
...
除錯
在當前專案資料夾下執行 npm link --force
, 然後隨便找個檔案下執行 xmant c
:
開啟我們新建立的檔案看看:
也可以選擇其他模板建立試試。
通過命令快速批量建立程式碼模板
如果我們想一次性建立大量的程式碼模板呢?當然還是通過命令批量的建立檔案。
這裡的思路:通過讀取配置檔案,然後進行批量建立。
編寫配置檔案
// 說明:
// folder: 資料夾名,可以巢狀,用 “/”分隔
// fileName: 檔名
// funcName: 類名或函式名
// template: 用到的檔案模板
module.exports = [
{
folder: './home',
fileName: 'index',
funcName: 'Home',
template: 'reactFunc-tsx'
},
{
folder: './home/compnent',
fileName: 'index',
funcName: 'Compnent',
template: 'reactFunc-tsx'
},
{
folder: './home/service',
fileName: 'index',
funcName: 'service',
template: 'reactFunc-ts'
},
{
folder: './news',
fileName: 'index',
funcName: 'News',
template: 'reactFunc-tsx'
},
{
folder: './news/service',
fileName: 'index',
funcName: 'service',
template: 'reactFunc-ts'
}
]
這裡用到的檔案模板就是我們之前編寫好的模板。
編寫批量建立檔案邏輯
根據配置檔案進行資料夾和檔案的批量建立 command/createManyFiles.js:
// 批量建立檔案
const chalk = require('chalk');
const inquirer = require('inquirer');
const fs = require('fs');
const path = require('path');
const utils = require('../utils/index');
const fileList = require('../config/fileList');
const templates = require('../templates/index');
const clui = require('clui');
const Spinner = clui.Spinner;
const status = new Spinner('正在建立...');
// 遞迴建立目錄 同步方法
function mkdirsSync(dirname) {
if (fs.existsSync(dirname)) {
return true;
} else {
if (mkdirsSync(path.dirname(dirname))) {
fs.mkdirSync(dirname);
console.log(chalk.green(`建立目錄成功-${dirname}`));
}
}
}
module.exports = () => {
inquirer.prompt([
{
name: 'choices',
type:'list',
message: '請確認配置好模板批量生成列表',
choices: ['yes', 'no']
}
])
.then(answers => {
const choices = answers.choices
if(choices === 'yes') {
// 批量建立目錄
fileList.forEach(item => {
if(item.folder) {
mkdirsSync(`${item.folder}`)
}
})
// 批量建立檔案
fileList.forEach(item => {
templates.forEach(tpl => {
if(item.template === tpl.name) {
const suffix = utils.getFileSuffix(item.template)
const fileName = `${item.fileName}.${suffix}`
fs.writeFile(`${item.folder}/${fileName}`, tpl.src(item.funcName), function(err) {
if(err) {
console.log('建立失敗:', chalk.red(err))
} else{
console.log(chalk.green(`建立檔案成功!${fileName}`));
}
})
}
})
})
}
})
}
編寫命令
最後編寫 bin/xman.js:
#!/usr/bin/env node
const { program } = require('commander');
...
program
.command('create-many')
.description("Create many folders and files")
.alias('cm')
.action(() => {
require('../command/createManyFiles')()
});
...
除錯
在當前專案資料夾下執行 npm link --force
, 然後隨便找個檔案下執行 xmant cm
:
看一下我們批量建立的檔案和資料夾:
總結
關於快速建立程式碼模板的方法有很多,有VSCode外掛,也有CLI工具,更有做成 lowcode/nocode 平臺的方式等等。本文這種方式的好處在於足夠靈活,我們可以根據具體的需求進行靈活的改造,使得更適用。