摘要:前段時(shí)間有寫(xiě)過(guò)一個(gè)在項(xiàng)目中的實(shí)踐。在里邊有解釋了為什么要使用,以及在中的一個(gè)項(xiàng)目結(jié)構(gòu)是怎樣的。關(guān)于的配置文件,在本項(xiàng)目中存在兩份。一個(gè)需要注意的小細(xì)節(jié)因?yàn)槲覀兊呐c實(shí)現(xiàn)版本中都用到了。
前段時(shí)間有寫(xiě)過(guò)一個(gè)TypeScript在node項(xiàng)目中的實(shí)踐。
在里邊有解釋了為什么要使用TS,以及在Node中的一個(gè)項(xiàng)目結(jié)構(gòu)是怎樣的。
但是那僅僅是一個(gè)純接口項(xiàng)目,碰巧趕上近期的另一個(gè)項(xiàng)目重構(gòu)也由我來(lái)主持,經(jīng)過(guò)上次的實(shí)踐以后,嘗到了TS所帶來(lái)的甜頭,毫不猶豫的選擇用TS+React來(lái)重構(gòu)這個(gè)項(xiàng)目。
這次的重構(gòu)不僅包括Node的重構(gòu)(之前是Express的項(xiàng)目),同時(shí)還包括前端的重構(gòu)(之前是由jQuery驅(qū)動(dòng)的多頁(yè)應(yīng)用)。
因?yàn)槟壳绊?xiàng)目是沒(méi)有做前后分離的打算的(一個(gè)內(nèi)部工具平臺(tái)類的項(xiàng)目),所以大致結(jié)構(gòu)就是基于上次Node項(xiàng)目的結(jié)構(gòu),在其之上添加了一些FrontEnd的目錄結(jié)構(gòu):
. ├── README.md ├── copy-static-assets.ts ├── nodemon.json ├── package.json + ├── client-dist + │?? ├── bundle.js + │?? ├── bundle.js.map + │?? ├── logo.png + │?? └── vendors.dll.js ├── dist ├── src │?? ├── config │?? ├── controllers │?? ├── entity │?? ├── models │?? ├── middleware │?? ├── public │?? ├── app.ts │?? ├── server.ts │?? ├── types + │?? ├── common │?? └── utils + ├── client-src + │?? ├── components + │ │ └── Header.tsx + │?? ├── conf + │ │ └── host.ts + │?? ├── dist + │?? ├── utils + │?? ├── index.ejs + │?? ├── index.tsx + │?? ├── webpack + │?? ├── package.json + │?? └── tsconfig.json + ├── views + │ └── index.ejs ├── tsconfig.json └── tslint.json
其中標(biāo)綠(也可能是一個(gè)+號(hào)顯示)的文件為本次新增的。
其中client-dist與views都是通過(guò)webpack生成的,實(shí)際的源碼文件都在client-src下。_就這個(gè)結(jié)構(gòu)拆分前后分離其實(shí)沒(méi)有什么成本_
在下邊分了大概這樣的一些文件夾:
dir/file | desc |
---|---|
index.ejs | 項(xiàng)目的入口html文件,采用ejs作為渲染引擎 |
index.tsx | 項(xiàng)目的入口js文件,后綴使用tsx,原因有二: 1. 我們會(huì)使用ts進(jìn)行React程序的開(kāi)發(fā) 2. .tsx文件在vs code上的icon比較好看 :p |
tsconfig.json | 是用于tsc編譯執(zhí)行的一些配置文件 |
components | 組件存放的目錄 |
config | 各種配置項(xiàng)存放的位置,類似請(qǐng)求接口的host或者各種狀態(tài)的map映射之類的(可以理解為枚舉對(duì)象們都在這里) |
utils | 一些公共函數(shù)存放的位置,各種可復(fù)用的代碼都應(yīng)該放在這里 |
dist | 各種靜態(tài)資源的存放位置,圖片之類文件 |
webpack | 里邊存放了各種環(huán)境的webpack腳本命令以及dll的生成 |
實(shí)際上邊還漏掉了一個(gè)新增的文件夾,我們?cè)?b>src目錄下新增了一個(gè)common目錄,這個(gè)目錄是存放一些公共的函數(shù)和公共的config,不同于utils或者config的是,這里的代碼是前后端共享的,所以這里邊的函數(shù)一定要是完全的不包含任何環(huán)境依賴,不包含任何業(yè)務(wù)邏輯的。
類似的數(shù)字千分位,日期格式化,抑或是服務(wù)監(jiān)聽(tīng)的端口號(hào),這些不包含任何邏輯,也對(duì)環(huán)境沒(méi)有強(qiáng)依賴的代碼,我們都可以放在這里。
這也是沒(méi)有做前后分離帶來(lái)的一個(gè)小甜頭吧,前后可以共享一部分代碼。
要實(shí)現(xiàn)這樣的配置,基于上述項(xiàng)目需要修改如下幾處:
1 src下的utils和config部分代碼遷移到common文件夾下,主要是用于區(qū)分是否可前后通用
2 為了將對(duì)之前node結(jié)構(gòu)方面的影響降至最低,我們需要在common文件夾下新增一個(gè)index.ts索引文件,并在utils/index.ts下引用它,這樣對(duì)于node方面使用來(lái)講,并不需要關(guān)心這個(gè)文件是來(lái)自utils還是common
// src/common/utils/comma.ts export default (num: number): string => String(num).replace(/B(?=(d{3})+$)/g, ",") // src/common/utils/index.ts export { default as comma } from "./comma" // src/utils.index.ts export * from "../common/utils" // src/app.ts import { comma } from "./utils" // 并不需要關(guān)心是來(lái)自common還是來(lái)自u(píng)tils console.log(comma(1234567)) // 1,234,567
3 然后是配置webpack的alias屬性,用于webpack能夠正確的找到其路徑
// client-src/webpack/base.js module.exports = { resolve: { alias: { "@Common": path.resolve(__dirname, "../../src/common"), } } }
4 同時(shí)我們還需要配置tsconfig.json用于vs code可以找到對(duì)應(yīng)的目錄,不然會(huì)在編輯器中提示can"t find module XXX
// client-src/tsconfig.json { "compilerOptions": { "paths": { // 用于引入某個(gè)`module` "@Common/*": [ "../src/common/*" ] } } }
5 最后在client-src/utils/index.ts寫(xiě)上類似server端的處理就可以了
// client-src/utils/index.ts export * from "@Common/utils" // client-src/index.tsx import { comma } from "./utils" console.log(comma(1234567)) // 1,234,567環(huán)境的搭建
如果使用vs code進(jìn)行開(kāi)發(fā),而且使用了ESLint的話,需要修改TS語(yǔ)法支持的后綴,添加typescriptreact的一些處理,這樣才會(huì)自動(dòng)修復(fù)一些ESLint的規(guī)則:
"eslint.validate": [ "javascript", "javascriptreact", { "language": "typescript", "autoFix": true }, { "language": "typescriptreact", "autoFix": true } ]webpack的配置
因?yàn)樵谇岸耸褂昧?b>React,按照目前的主流,webpack肯定是必不可少的。
并沒(méi)有選擇成熟的cra(create-react-app)來(lái)進(jìn)行環(huán)境搭建,原因有下:
webpack更新到4以后并沒(méi)有嘗試過(guò),想自己耍一耍
結(jié)合著TS以及公司內(nèi)部的東西,會(huì)有一些自定義配置情況的出現(xiàn),擔(dān)心二次開(kāi)發(fā)太繁瑣
但是其實(shí)也沒(méi)有太多的配置,本次重構(gòu)選用的UI框架為Google Material的實(shí)現(xiàn):material-ui
而他們采用的是jss 來(lái)進(jìn)行樣式的編寫(xiě),所以也不會(huì)涉及到之前慣用的scss的那一套loader了。
webpack分了大概如下幾個(gè)文件:
file | desc |
---|---|
common.js | 公共的webpack配置,類似env之類的選項(xiàng) |
dll.js | 用于將一些不會(huì)修改的第三方庫(kù)進(jìn)行提前打包,加快開(kāi)發(fā)時(shí)編譯效率 |
base.js | 可以理解為是webpack的基礎(chǔ)配置文件,通用的loader以及plugins在這里 |
pro.js | 生產(chǎn)環(huán)境的特殊配置(代碼壓縮、資源上傳) |
dev.js | 開(kāi)發(fā)環(huán)境的特殊配置(source-map) |
dll是一個(gè)很早之前的套路了,大概需要修改這么幾處:
創(chuàng)建一個(gè)多帶帶的webpack文件,用于生成dll文件
在普通的webpack文件中進(jìn)行引用生成的dll文件
// dll.js { entry: { // 需要提前打包的庫(kù) vendors: [ "react", "react-dom", "react-router-dom", "babel-polyfill", ], }, output: { filename: "vendors.dll.js", path: path.resolve(__dirname, "../../client-dist"), // 輸出時(shí)不要少了這個(gè)option library: "vendors_lib", }, plugins: [ new webpack.DllPlugin({ context: __dirname, // 向外拋出的`vendors.dll.js`代碼的具體映射,引用`dll`文件的時(shí)候通過(guò)它來(lái)做映射關(guān)系的 path: path.join(__dirname, "../dist/vendors-manifest.json"), name: "vendors_lib", }) ] } // base.js { plugins: [ new webpack.DllReferencePlugin({ context: __dirname, manifest: require("../dist/vendors-manifest.json"), }), ] }
這樣在watch文件時(shí),打包就會(huì)跳過(guò)verdors中存在的那些包了。
有一點(diǎn)要注意的,如果最終需要上傳這些靜態(tài)資源,記得連帶著verdors.dll.js一并上傳
在本地開(kāi)發(fā)時(shí),vendors文件并不會(huì)自動(dòng)注入到html模版中去,所以我們有用到了另一個(gè)插件,add-asset-html-webpack-plugin。
同時(shí)在使用中可能還會(huì)遇到webpack無(wú)限次數(shù)的重新打包,這個(gè)需要配置ignore來(lái)解決-.-:
// dev.js const HtmlWebpackPlugin = require("html-webpack-plugin") const AddAssetHtmlPlugin = require("add-asset-html-webpack-plugin") { plugins: [ // 將`ejs`模版文件放到目標(biāo)文件夾,并注入入口`js`文件 new HtmlWebpackPlugin({ template: path.resolve(__dirname, "../index.ejs"), filename: path.resolve(__dirname, "../../views/index.ejs"), }), // 將`vendors`文件注入到`ejs`模版中 new AddAssetHtmlPlugin({ filepath: path.resolve(__dirname, "../../client-dist/vendors.dll.js"), includeSourcemap: false, }), // 忽略`ejs`和`js`的文件變化,避免`webpack`無(wú)限重新打包的問(wèn)題 new webpack.WatchIgnorePlugin([ /.ejs$/, /.js$/, ]), ] }TypeScript相關(guān)的配置
TS的配置分了兩塊,一個(gè)是webpack的配置,另一個(gè)是tsconfig的配置。
首先是webpack,針對(duì)ts、tsx文件我們使用了兩個(gè)loader:
{ rules: [ { test: /.tsx?$/, use: ["babel-loader", "ts-loader"], exclude: /node_modules/, } ], resolve: { // 一定不要忘記配置ts tsx后綴 extensions: [".tsx", ".ts", ".js"], } }
ts-loader用于將TS的一些特性轉(zhuǎn)換為JS兼容的語(yǔ)法,然后執(zhí)行babel進(jìn)行處理react/jsx相關(guān)的代碼,最終生成可執(zhí)行的JS代碼。
然后是tsconfig的配置,ts-loader的執(zhí)行是依托于這里的配置的,大致的配置如下:
{ "compilerOptions": { "module": "esnext", "target": "es6", "allowSyntheticDefaultImports": true, // import的相對(duì)起始路徑 "baseUrl": ".", "sourceMap": true, // 構(gòu)建輸出目錄,但因?yàn)槭褂昧薫webpack`,所以這個(gè)配置并沒(méi)有什么卵用 "outDir": "../client-dist", // 開(kāi)啟`JSX`模式, // `preserve`的配置讓`tsc`不會(huì)去處理它,而是使用后續(xù)的`babel-loader`進(jìn)行處理 "jsx": "preserve", "strict": true, "moduleResolution": "node", // 開(kāi)啟裝飾器的使用 "experimentalDecorators": true, "emitDecoratorMetadata": true, // `vs code`所需要的,在開(kāi)發(fā)時(shí)找到對(duì)應(yīng)的路徑,真實(shí)的引用是在`webpack`中配置的`alias` "paths": { "@Common": [ "../src/common" ], "@Common/*": [ "../src/common/*" ] } }, "exclude": [ "node_modules" ] }ESLint的配置
最近這段時(shí)間,我們團(tuán)隊(duì)基于airbnb的ESLint規(guī)則進(jìn)行了一些自定義,創(chuàng)建了自家的eslint-config-blued
同時(shí)還存在了react和typescript的兩個(gè)衍生版本。
關(guān)于ESLint的配置文件.eslintrc,在本項(xiàng)目中存在兩份。一個(gè)是根目錄的blued-typescript,另一個(gè)是client-src下的blued-react + blued-typescript。
因?yàn)楦夸浀母嘤糜?b>node項(xiàng)目,所以沒(méi)必要把react什么的依賴也裝進(jìn)來(lái)。
# .eslintrc extends: blued-typescript # client-src/.eslintrc extends: - blued-react - blued-typescript
一個(gè)需要注意的小細(xì)節(jié)
因?yàn)槲覀兊?b>react與typescript實(shí)現(xiàn)版本中都用到了parser。
react使用的是babel-eslint,typescript使用的是typescript-eslint-parser。
但是parser只能有一個(gè),從option的命名中就可以看出extends、plugins、rules,到了parser就沒(méi)有復(fù)數(shù)了。
所以這兩個(gè)插件在extends中的順序就變得很關(guān)鍵,babel現(xiàn)在并不能理解TS的語(yǔ)法,但好像babel開(kāi)發(fā)者有支持TS的意愿。
但就目前來(lái)說(shuō),一定要保證react在前,typescript在后,這樣parser才會(huì)使用typescript-eslint-parser來(lái)進(jìn)行覆蓋。
除了上邊提到的兩端公用代碼以外,還需要添加一個(gè)controller用于吐頁(yè)面,因?yàn)槭褂玫氖?b>routing-controllers這個(gè)庫(kù),渲染一個(gè)靜態(tài)頁(yè)面被封裝的非常棒,僅僅需要修改兩個(gè)頁(yè)面,一個(gè)用于設(shè)置render模版的根目錄,另一個(gè)用來(lái)設(shè)置要吐出來(lái)的模版名稱:
// controller/index.ts import { Get, Controller, Render, } from "routing-controllers" @Controller("/") export default class { @Get("/") @Render("index") // 指定一個(gè)模版的名字 async router() { // 渲染頁(yè)面時(shí)的一些變量 // 類似之前的 ctx.state = XXX return { title: "First TypeScript React App", } } } // app.ts import koaViews from "koa-views" // 添加模版所在的目錄 // 以及使用的渲染引擎、文件后綴 app.use(koaViews(path.join(__dirname, "../views"), { options: { ext: "ejs", }, extension: "ejs", }))
如果是多個(gè)頁(yè)面,那就創(chuàng)建多個(gè)用來(lái)Render的ts文件就好了
深坑,注意目前的routing-controller對(duì)于Koa的支持還不是很好,(原作者對(duì)Koa并不是很了解,導(dǎo)致Render對(duì)應(yīng)的接口被請(qǐng)求一次以后,后續(xù)所有的其他的接口都會(huì)直接返回該模版文件,原因是在負(fù)責(zé)模版渲染的URL觸發(fā)時(shí),本應(yīng)返回?cái)?shù)據(jù),但是目前的處理卻是添加了一個(gè)中間件到Koa中,所以任何請(qǐng)求都會(huì)將該模版文件作為數(shù)據(jù)來(lái)返回)所以@Render并不能適用于Koa驅(qū)動(dòng)。
不過(guò)我已經(jīng)提交了PR了,跑通了測(cè)試用例,坐等被合并代碼,但是這是一個(gè)臨時(shí)的修改方案,涉及到這個(gè)庫(kù)針對(duì)外部中間件注冊(cè)的順序問(wèn)題,所以對(duì)于app.ts還要有額外的修改才能夠?qū)崿F(xiàn)。
// app.ts 的修改 import "reflect-metadata" import Koa from "koa" import koaViews from "koa-views" import { useKoaServer } from "routing-controllers" import { distPath } from "./config" // 手動(dòng)創(chuàng)建koa實(shí)例,然后添加`render`的中間件,確保`ctx.render`方法會(huì)在請(qǐng)求的頭部就被添加進(jìn)去 const koa = new Koa() koa.use(koaViews(path.join(__dirname, "../views"), { options: { ext: "ejs", }, extension: "ejs", })) // 使用`useKoaServer`而不是`createKoaServer` const app = useKoaServer(koa, { controllers: [`${__dirname}/controllers/**/*{.js,.ts}`], }) // 后續(xù)的邏輯就都一樣了 export default app
當(dāng)然,這個(gè)是新版發(fā)出以后的邏輯了,基于現(xiàn)有的結(jié)構(gòu)也可以繞過(guò)去,但是就不能使用@Render裝飾器了,拋開(kāi)koa-views直接使用內(nèi)部的consolidate:
// controller/index.ts // 這個(gè)修改不需要改動(dòng)`app.ts`,可以直接使用`createKoaServer` import { Get, Controller, } from "routing-controllers" import cons from "consolidate" import path from "path" @Controller() export default class { @Get("/") async router() { // 直接在接口返回時(shí)獲取模版渲染后的數(shù)據(jù) return cons.ejs(path.resolve(__dirname, "../../views/index.ejs"), { title: "Example For TypeScript React App", }) } }
目前的示例代碼采用的上邊的方案
小結(jié)至此,一個(gè)完整的TS前后端項(xiàng)目架構(gòu)就已經(jīng)搭建完成了(剩下的任務(wù)就是往骨架里邊填代碼了)。
我已經(jīng)更新了之前的typescript-exmaple 在里邊添加了本次重構(gòu)所使用的一些前端TS+React的示例,還包括針對(duì)@Render的一些兼容。
TypeScript是一個(gè)很棒的想法,解決了N多javaScript種令人詬病的問(wèn)題。
使用靜態(tài)語(yǔ)言來(lái)進(jìn)行開(kāi)發(fā)不僅能夠提高開(kāi)發(fā)的效率,同時(shí)還能降低錯(cuò)誤出現(xiàn)的幾率。
結(jié)合著強(qiáng)大的vs code,Enjoy it.
如果在使用TS的過(guò)程中有什么問(wèn)題、或者有什么更好的想法,歡迎來(lái)溝通討論。
One more thingsBlued前端/Node團(tuán)隊(duì)招人。。初中高都有HC
坐標(biāo)帝都朝陽(yáng)雙井,有興趣的請(qǐng)聯(lián)系我:
wechat: github_jiasm
mail: jiashunming@blued.com
歡迎砸簡(jiǎn)歷
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/97066.html
摘要:怎么影響了我的思考方式對(duì)前端開(kāi)發(fā)者來(lái)說(shuō),能強(qiáng)化了面向接口編程這一理念。使用的過(guò)程就是在加深理解的過(guò)程,確實(shí)面向接口編程天然和靜態(tài)類型更為親密。 電影《降臨》中有一個(gè)觀點(diǎn),語(yǔ)言會(huì)影響人的思維方式,對(duì)于前端工程師來(lái)說(shuō),使用 typescript 開(kāi)發(fā)無(wú)疑就是在嘗試換一種思維方式做事情。 其實(shí)直到最近,我才開(kāi)始系統(tǒng)的學(xué)習(xí) typescript ,前后大概花了一個(gè)月左右的時(shí)間。在這之前,我也在...
摘要:怎么影響了我的思考方式對(duì)前端開(kāi)發(fā)者來(lái)說(shuō),能強(qiáng)化了面向接口編程這一理念。使用的過(guò)程就是在加深理解的過(guò)程,確實(shí)面向接口編程天然和靜態(tài)類型更為親密。摘要: 學(xué)會(huì)TS思考方式。 原文:TypeScript - 一種思維方式 作者:zhangwang Fundebug經(jīng)授權(quán)轉(zhuǎn)載,版權(quán)歸原作者所有。 電影《降臨》中有一個(gè)觀點(diǎn),語(yǔ)言會(huì)影響人的思維方式,對(duì)于前端工程師來(lái)說(shuō),使用 typescript 開(kāi)...
摘要:前端進(jìn)階進(jìn)階構(gòu)建項(xiàng)目一配置最佳實(shí)踐狀態(tài)管理之痛點(diǎn)分析與改良開(kāi)發(fā)中所謂狀態(tài)淺析從時(shí)間旅行的烏托邦,看狀態(tài)管理的設(shè)計(jì)誤區(qū)使用更好地處理數(shù)據(jù)愛(ài)彼迎房源詳情頁(yè)中的性能優(yōu)化從零開(kāi)始,在中構(gòu)建時(shí)間旅行式調(diào)試用輕松管理復(fù)雜狀態(tài)如何把業(yè)務(wù)邏輯這個(gè)故事講好和 前端進(jìn)階 webpack webpack進(jìn)階構(gòu)建項(xiàng)目(一) Webpack 4 配置最佳實(shí)踐 react Redux狀態(tài)管理之痛點(diǎn)、分析與...
摘要:使用官方的的另外一種版本和一起使用自動(dòng)配置了一個(gè)項(xiàng)目支持。需要的依賴都在文件中。帶靜態(tài)類型檢驗(yàn),現(xiàn)在的第三方包基本上源碼都是,方便查看調(diào)試。大型項(xiàng)目首選和結(jié)合,代碼調(diào)試維護(hù)起來(lái)極其方便。 showImg(https://segmentfault.com/img/bVbrTKz?w=1400&h=930); 阿特伍德定律,指的是any application that can be wr...
閱讀 2033·2023-04-25 14:50
閱讀 2913·2021-11-17 09:33
閱讀 2618·2019-08-30 13:07
閱讀 2845·2019-08-29 16:57
閱讀 913·2019-08-29 15:26
閱讀 3554·2019-08-29 13:08
閱讀 1996·2019-08-29 12:32
閱讀 3391·2019-08-26 13:57