我們組維護的管理后臺會接到很多開發需求,每次新開頁面,就會到處復制黏貼相關代碼。
  并且還會經常性的翻閱文檔,先在書簽或地址欄輸入WIKI地址,然后找到那一份說明文檔,再定位到要看的組件位置。
  雖然單人損耗的時間并不是非常多,但還是會打斷思路,影響開發的流暢性,當把所有人的時間累加起來,那損耗的時間也很可觀。
  為了能提升團隊成員的開發效率,就開始構思一套可視化搭建系統。理想狀態下,拖動組件,配置交互和樣式,頁面生成,直接可用。
  但是要完成這套功能,開發成本比較大,現在我想先解決當前的痛點,減少代碼復制的頻率和快速讀取組件文檔。
  為此,在構思了好多天后,打算搞一個半吊子的可視化搭建系統。
  所謂半吊子是指搭建完后,點擊生成,會在后臺創建視圖和數據兩個腳本文件、自動添加權限、新增菜單欄,不過后續我們還得繼續做開發,完善頁面功能。
## 一、界面
  界面分成左右兩部分,左邊是配置區域,右邊空白處是組件的預覽區域。
:-: 
**1)組件區域**
  組件區域的第一個下拉框可以選擇Ant Design和部分模板組件,選中后,會替換組件地址的鏈接,點擊就能跳轉到組件的說明文檔。
  第二個下拉框能選擇頁面中需要的組件,例如圖中的提示組件,點擊添加后會在右邊顯示,并且還會提供一個刪除圖標,目前暫不支持拖動效果。
:-: 
**2)配置區域**
  在配置區域中,可以輸入菜單名稱、路由、文件目錄和權限等信息。
  原先的話,還得手動的在路由和權限兩個文件中新增配置項,現在都能自動化了。
  原理就是先用Node分別讀取這兩份文件,得到一個數組,然后將配置內容塞到此數組中,再將數組序列化寫入文件內。
  注意,需求在引入模塊(調用require()函數)前刪除模塊緩存,否則讀到的將是之前的文件內容。
~~~
//權限文件的絕對路徑
const absAuthorityPath = pathObj.resolve(__dirname, 'src/utils/authority.ts');
delete require.cache[absAuthorityPath]; //刪除模塊緩存
const authorities = require(absAuthorityPath);
const obj = {
id: authority,
pid: parent,
name: menu,
desc: '',
routers: currentPath,
};
authorities.push(obj); //添加權限
//寫入文件
fs.writeFileSync(absAuthorityPath, `module.exports = ${JSON.stringify(authorities, null, 2)}`);
~~~
  fs.writeFileSync()用于同步寫入文件。module.exports是Node的模塊語法,而export default是ES6語法,Node原生并不支持,好在webpack對于這些模塊化語法都支持。
  一旦點擊生成文件按鈕,在項目重新構建后,左邊菜單列表就能出現剛剛配置的菜單名稱(例如名稱叫菜單測試),并且能夠跳轉,權限也加好了。
:-: 
  視圖和數據文件也是用Node創建的,在Node項目中寫好一份模板字符串(下面是生成視圖模板的函數),將可變部分作為參數傳入。
~~~typescript
export function setPageTemplate({name, antd, namespace, code='', props, component}) {
return `import { connect, Dispatch, ${namespace}ModelState } from "umi";
import { setColumn } from '@/utils/tools';
import { TEMPLATE_MODEL } from '@/utils/constants';
${antd}
// 頁面參數類型
interface ${name}Props {
dispatch: Dispatch;
state: ${namespace}ModelState;
}
// 全局聲明
${code}
const ${name} = ({ dispatch, state }: ${name}Props) => {
// dispatch({ type: "xx/xx", payload: {} });
// 狀態
// const { } = state;
// 通用組件配置
${props}
return <>
${component}
</>;
};
export default connect((data: {${namespace}: ${namespace}ModelState}) => ({ state: data.${namespace} }))(${name});`;
}
~~~
## 二、配置
  配置是本系統的核心,構思了很久,原先考慮了系統的靈活性,就想直接提供腳本編輯框,自定義邏輯。
  不過出現個問題,那就是我這邊目前是用TypeScript語言開發的,那么我在自定義腳本邏輯時,也需要使用TypeScript語法。
  瀏覽器提供的[eval()](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/eval)函數并不支持TypeScript語法,需要先做轉譯,網上搜索后,得到了解決方案,下載了TypeScript庫后。
  但是卻一直報錯,在網上也查到了些解決方案([方案一](https://github.com/microsoft/TypeScript/issues/39436),[方案二](https://stackoverflow.com/questions/45153848/evaluate-typescript-from-string)),不過并不適用于我目前的項目環境。
~~~
./node_modules/typescript/lib/typescript.js
Critical dependency: the request of a dependency is an expression
./node_modules/typescript/lib/typescript.js
Critical dependency: the request of a dependency is an expression
./node_modules/typescript/lib/typescript.js
Module not found: Can't resolve 'perf_hooks' in 'C:\Users\User\node_modules\typescript\lib'
~~~
  最終決定暫時放棄自定義腳本邏輯,先解決當前痛點,盡快將系統上線。
  期間還遇到個比較隱蔽的bug,如下所示,數組會先調用 toString() 轉換成字符串,最終變為 eval("(1, 2)"),所以得到的值是 2。
~~~
eval(`(${[1,2]})`); //2
~~~
  還遇到個問題,那就是在用?[JSON.stringify()](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify)?序列化對象時,若參數是函數,那么就會被過濾掉。
~~~
JSON.stringify({func:() => {}}); //"{}"
~~~
**1)物料庫**
  物料庫中的組件分為兩種,一種是自定義的[后臺模板組件](https://github.com/pwstrick/shin-admin/blob/main/docs/template.md),另一種是第三方的[Ant Design 3.X](https://3x.ant.design/docs/react/introduce-cn)組件。
  為了快速搭建頁面,選擇的組件是前者。這次順便用TypeScript,再次完善了組件代碼的類型聲明。
:-: 
  后者只是用來文檔查詢和在模板字符串中拼接引入語句,如下所示。
~~~typescript
`import { ${antds.join(',')} } from 'antd';`
~~~
**2)自定義組件**
  自定義組件的聲明采用JSON格式,TypeScript聲明的類型如下所示。
~~~typescript
interface OptionsType {
value: string;
label: string;
children: Array<{
value: string;
label: string;
link: string; //鏈接地址
readonlyProps?: ObjectType; //會影響組件的呈現,并且不能配置的屬性
readonlyStrProps?: string; //待拼接的字符串屬性
handleProps?: (values:ObjectType) => ObjectType; //在格式化表單數據后,再處理特定的組件屬性
handleStrProps?: (values:ObjectType) => string; //拼接無法轉換成字符串的屬性
props: Array<{
label: string;
name: string;
params?: ObjectType;
control: JSX.Element | ((index: number) => JSX.Element);
type?: string;
initControl?: (props:any) => JSX.Element;
}>
}>;
}
~~~
  鏈接地址就是說明文檔的地址,在組件的屬性中,有一部分是回調函數,而目前已經舍棄了自定義的回調邏輯。
  所以這部分屬性要特殊處理(聲明在?readonlyProps),不能在界面中輸入。
~~~
readonlyProps: {
initPanes: (record: ObjectType): TabPaneType[] => [
{
name: "示例",
key: "demo",
controls: [
{ label: '測試組件', control: <>內容</> }
]
},
],
},
~~~
  readonlyStrProps 就是?readonlyProps 對應的字符串格式,該屬性還會增加一些其它屬性,配上注釋,也相當于是份組件文檔了。
~~~typescript
readonlyStrProps: `,
// 標簽欄內容回調函數,參數為 record,當標簽欄只有一項時,將不顯示菜單
"initPanes": (record: ObjectType): TabPaneType[] => [
{
name: "示例",
key: "demo",
controls: [
{ label: '測試組件', control: <>內容</> }
]
},
],
// useEffect鉤子中的回調函數,參數是 record
"effectCallback": (record: ObjectType) => {}`,
~~~
  handleProps() 是一個回調函數,在表單接收到數據后,有些組件需要再做一次特殊的處理。
  例如加些特定屬性、數組元素合并成字符串等,從而才能順利的在預覽界面呈現。
~~~
handleProps: (values:ObjectType) => { //對表單中的值做處理
// 對接口數組做特殊處理,從['api', 'get']轉換成api.get
values.url && (values.url = values.url.join('.'));// 初始化表單需要的組件
if(values.controls.length === 0) {
values.controls = [
{
label: "示例",
name: "demo",
control: <>測試組件</>
},
];
}else {
values.originControls = values.controls; //備份組件名稱數組
values.controls = values.controls.map((item:string) => getControls(item));
}
delete values.controlskeys; //刪除冗余屬性
return values;
},
~~~
  handleStrProps() 是在輸出模板時使用,將那些特殊屬性寫成字符串形式。
~~~
handleStrProps: (values:ObjectType):string => {
if(values.controls.length === 0) {
delete values.originControls; //刪除備份數組
delete values.controls; //刪除原始屬性
return `,"controls": [
{
label: "示例",
name: "demo",
control: <>測試組件</>
},
]`;
}
// 組件名稱數組處理
const newControls = values.originControls.map((item:string) => getStrControls(item));
delete values.originControls;
delete values.controls;
return `,"controls": [${newControls.join(',')}]`;
},
~~~
  在經過一系列的處理后,將一些字符串代碼傳遞給接口,接口最后拼接成兩個文件,輸出到指定目錄中。
  不過生成的代碼,排版有點混亂,每次都還需要手動格式化一下。
*****
> 原文出處:
[博客園-Node.js躬行記](https://www.cnblogs.com/strick/category/1688575.html)
[知乎專欄-Node.js躬行記](https://zhuanlan.zhihu.com/pwnode)
已建立一個微信前端交流群,如要進群,請先加微信號freedom20180706或掃描下面的二維碼,請求中需注明“看云加群”,在通過請求后就會把你拉進來。還搜集整理了一套[面試資料](https://github.com/pwstrick/daily),歡迎閱讀。

推薦一款前端監控腳本:[shin-monitor](https://github.com/pwstrick/shin-monitor),不僅能監控前端的錯誤、通信、打印等行為,還能計算各類性能參數,包括 FMP、LCP、FP 等。
- ES6
- 1、let和const
- 2、擴展運算符和剩余參數
- 3、解構
- 4、模板字面量
- 5、對象字面量的擴展
- 6、Symbol
- 7、代碼模塊化
- 8、數字
- 9、字符串
- 10、正則表達式
- 11、對象
- 12、數組
- 13、類型化數組
- 14、函數
- 15、箭頭函數和尾調用優化
- 16、Set
- 17、Map
- 18、迭代器
- 19、生成器
- 20、類
- 21、類的繼承
- 22、Promise
- 23、Promise的靜態方法和應用
- 24、代理和反射
- HTML
- 1、SVG
- 2、WebRTC基礎實踐
- 3、WebRTC視頻通話
- 4、Web音視頻基礎
- CSS進階
- 1、CSS基礎拾遺
- 2、偽類和偽元素
- 3、CSS屬性拾遺
- 4、浮動形狀
- 5、漸變
- 6、濾鏡
- 7、合成
- 8、裁剪和遮罩
- 9、網格布局
- 10、CSS方法論
- 11、管理后臺響應式改造
- React
- 1、函數式編程
- 2、JSX
- 3、組件
- 4、生命周期
- 5、React和DOM
- 6、事件
- 7、表單
- 8、樣式
- 9、組件通信
- 10、高階組件
- 11、Redux基礎
- 12、Redux中間件
- 13、React Router
- 14、測試框架
- 15、React Hooks
- 16、React源碼分析
- 利器
- 1、npm
- 2、Babel
- 3、webpack基礎
- 4、webpack進階
- 5、Git
- 6、Fiddler
- 7、自制腳手架
- 8、VSCode插件研發
- 9、WebView中的頁面調試方法
- Vue.js
- 1、數據綁定
- 2、指令
- 3、樣式和表單
- 4、組件
- 5、組件通信
- 6、內容分發
- 7、渲染函數和JSX
- 8、Vue Router
- 9、Vuex
- TypeScript
- 1、數據類型
- 2、接口
- 3、類
- 4、泛型
- 5、類型兼容性
- 6、高級類型
- 7、命名空間
- 8、裝飾器
- Node.js
- 1、Buffer、流和EventEmitter
- 2、文件系統和網絡
- 3、命令行工具
- 4、自建前端監控系統
- 5、定時任務的調試
- 6、自制短鏈系統
- 7、定時任務的進化史
- 8、通用接口
- 9、微前端實踐
- 10、接口日志查詢
- 11、E2E測試
- 12、BFF
- 13、MySQL歸檔
- 14、壓力測試
- 15、活動規則引擎
- 16、活動配置化
- 17、UmiJS版本升級
- 18、半吊子的可視化搭建系統
- 19、KOA源碼分析(上)
- 20、KOA源碼分析(下)
- 21、花10分鐘入門Node.js
- 22、Node環境升級日志
- 23、Worker threads
- 24、低代碼
- 25、Web自動化測試
- 26、接口攔截和頁面回放實驗
- 27、接口管理
- 28、Cypress自動化測試實踐
- 29、基于Electron的開播助手
- Node.js精進
- 1、模塊化
- 2、異步編程
- 3、流
- 4、事件觸發器
- 5、HTTP
- 6、文件
- 7、日志
- 8、錯誤處理
- 9、性能監控(上)
- 10、性能監控(下)
- 11、Socket.IO
- 12、ElasticSearch
- 監控系統
- 1、SDK
- 2、存儲和分析
- 3、性能監控
- 4、內存泄漏
- 5、小程序
- 6、較長的白屏時間
- 7、頁面奔潰
- 8、shin-monitor源碼分析
- 前端性能精進
- 1、優化方法論之測量
- 2、優化方法論之分析
- 3、瀏覽器之圖像
- 4、瀏覽器之呈現
- 5、瀏覽器之JavaScript
- 6、網絡
- 7、構建
- 前端體驗優化
- 1、概述
- 2、基建
- 3、后端
- 4、數據
- 5、后臺
- Web優化
- 1、CSS優化
- 2、JavaScript優化
- 3、圖像和網絡
- 4、用戶體驗和工具
- 5、網站優化
- 6、優化閉環實踐
- 數據結構與算法
- 1、鏈表
- 2、棧、隊列、散列表和位運算
- 3、二叉樹
- 4、二分查找
- 5、回溯算法
- 6、貪心算法
- 7、分治算法
- 8、動態規劃
- 程序員之路
- 大學
- 2011年
- 2012年
- 2013年
- 2014年
- 項目反思
- 前端基礎學習分享
- 2015年
- 再一次項目反思
- 然并卵
- PC網站CSS分享
- 2016年
- 制造自己的榫卯
- PrimusUI
- 2017年
- 工匠精神
- 2018年
- 2019年
- 前端學習之路分享
- 2020年
- 2021年
- 2022年
- 2023年
- 2024年
- 日志
- 2020