前段时候有写过一个TypeScript在node项目中的实践。
在里边有诠释了为何要运用TS
,以及在Node
中的一个项目构造是如何的。
然则那仅仅是一个纯接口项目,恰巧遇上近期的另一个项目重构也由我来掌管,经由上次的实践今后,尝到了TS
所带来的甜头,坚决果断的挑选用TS
+React
来重构这个项目。
此次的重构不仅包括Node
的重构(之前是Express
的项目),同时还包括前端的重构(之前是由jQuery
驱动的多页运用)。
项目构造
由于如今项目是没有做前后星散的盘算的(一个内部东西平台类的项目),所以大抵构造就是基于上次Node
项目的构造,在其之上增加了一些FrontEnd
的目次构造:
.
├── 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
个中标绿(也多是一个+
号显现)的文件为本次新增的。
个中client-dist
与views
都是经由历程webpack
天生的,现实的源码文件都在client-src
下。_就这个构造拆分前后星散实在没有什么本钱_
在下边分了也许如许的一些文件夹:
dir/file | desc |
---|---|
index.ejs | 项目的进口html 文件,采纳ejs 作为衬着引擎 |
index.tsx | 项目的进口js 文件,后缀运用tsx ,缘由有二:<br/>1. 我们会运用ts 举行React 递次的开辟 <br/>2. .tsx 文件在vs code上的icon 比较悦目 :p |
tsconfig.json | 是用于tsc 编译实行的一些设置文件 |
components | 组件寄存的目次 |
config | 种种设置项寄存的位置,相似要求接口的host 或许种种状况的map 映照之类的(可以明白为罗列对象们都在这里) |
utils | 一些大众函数寄存的位置,种种可复用的代码都应该放在这里 |
dist | 种种静态资本的寄存位置,图片之类文件 |
webpack | 里边寄存了种种环境的webpack 剧本敕令以及dll 的天生 |
前后端复用代码的一个尝试
现实上边还漏掉了一个新增的文件夹,我们在src
目次下新增了一个common
目次,这个目次是寄存一些大众的函数和大众的config
,不同于utils
或许config
的是,这里的代码是前后端同享的,所以这里边的函数肯定假如完全的不包括任何环境依靠,不包括任何营业逻辑的。
相似的数字千分位,日期格式化,抑或是效劳监听的端口号,这些不包括任何逻辑,也对环境没有强依靠的代码,我们都可以放在这里。
这也是没有做前后星散带来的一个小甜头吧,前后可以同享一部份代码。
要完成如许的设置,基于上述项目须要修正以下几处:
- 1
src
下的utils
和config
部份代码迁移到common
文件夹下,主假如用于辨别是不是可前后通用 - 2 为了将对之前
node
构造方面的影响降至最低,我们须要在common
文件夹下新增一个index.ts
索引文件,并在utils/index.ts
下援用它,如许关于node
方面运用来讲,并不须要体贴这个文件是来自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' // 并不须要体贴是来自common照样来自utils
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 同时我们还须要设置
tsconfig.json
用于vs code
可以找到对应的目次,不然会在编辑器中提醒can't find module XXX
// client-src/tsconfig.json
{
"compilerOptions": {
"paths": {
// 用于引入某个`module`
"@Common/*": [
"../src/common/*"
]
}
}
}
- 5 末了在
client-src/utils/index.ts
写上相似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
环境的搭建
假如运用vs code
举行开辟,而且运用了ESLint
的话,须要修正TS
语法支撑的后缀,增加typescriptreact
的一些处置惩罚,如许才会自动修复一些ESLint
的划定规矩:
"eslint.validate": [
"javascript",
"javascriptreact",
{
"language": "typescript",
"autoFix": true
},
{
"language": "typescriptreact",
"autoFix": true
}
]
webpack的设置
由于在前端运用了React
,根据如今的主流,webpack
肯定是必不可少的。
并没有挑选成熟的cra
(create-react-app)来举行环境搭建,缘由有下:
-
webpack
更新到4今后并没有尝试过,想本身耍一耍 - 结合著
TS
以及公司内部的东西,会有一些自定义设置状况的涌现,忧郁二次开辟太烦琐
然则实在也没有太多的设置,本次重构选用的UI框架为Google Material的完成:material-ui
而他们采纳的是jss 来举行款式的编写,所以也不会涉及到之前习用的scss
的那一套loader
了。
webpack
分了也许以下几个文件:
file | desc |
---|---|
common.js | 大众的webpack 设置,相似env 之类的选项 |
dll.js | 用于将一些不会修正的第三方库举行提早打包,加速开辟时编译效力 |
base.js | 可以明白为是webpack 的基本设置文件,通用的loader 以及plugins 在这里 |
pro.js | 临盆环境的特别设置(代码紧缩、资本上传) |
dev.js | 开辟环境的特别设置(source-map ) |
dll
是一个很早之前的套路了,也许须要修正这么几处:
- 建立一个零丁的
webpack
文件,用于天生dll
文件 - 在一般的
webpack
文件中举行援用天生的dll
文件
// dll.js
{
entry: {
// 须要提早打包的库
vendors: [
'react',
'react-dom',
'react-router-dom',
'babel-polyfill',
],
},
output: {
filename: 'vendors.dll.js',
path: path.resolve(__dirname, '../../client-dist'),
// 输出时不要少了这个option
library: 'vendors_lib',
},
plugins: [
new webpack.DllPlugin({
context: __dirname,
// 向外抛出的`vendors.dll.js`代码的详细映照,援用`dll`文件的时刻经由历程它来做映照关联的
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
文件时,打包就会跳过verdors
中存在的那些包了。
有一点要注重的,假如终究须要上传这些静态资本,记得连带着verdors.dll.js
一并上传
在当地开辟时,vendors
文件并不会自动注入到html
模版中去,所以我们有效到了另一个插件,add-asset-html-webpack-plugin。
同时在运用中能够还会碰到webpack
无穷次数的从新打包,这个须要设置ignore
来处理-.-:
// dev.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin')
{
plugins: [
// 将`ejs`模版文件放到目的文件夹,并注入进口`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`无穷从新打包的题目
new webpack.WatchIgnorePlugin([
/\.ejs$/,
/\.js$/,
]),
]
}
TypeScript相干的设置
TS
的设置分了两块,一个是webpack
的设置,另一个是tsconfig
的设置。
首先是webpack
,针对ts
、tsx
文件我们运用了两个loader
:
{
rules: [
{
test: /\.tsx?$/,
use: ['babel-loader', 'ts-loader'],
exclude: /node_modules/,
}
],
resolve: {
// 肯定不要遗忘设置ts tsx后缀
extensions: ['.tsx', '.ts', '.js'],
}
}
ts-loader
用于将TS
的一些特征转换为JS
兼容的语法,然后实行babel
举行处置惩罚react/jsx
相干的代码,终究天生可实行的JS
代码。
然后是tsconfig
的设置,ts-loader
的实行是依托于这里的设置的,大抵的设置以下:
{
"compilerOptions": {
"module": "esnext",
"target": "es6",
"allowSyntheticDefaultImports": true,
// import的相对肇端途径
"baseUrl": ".",
"sourceMap": true,
// 构建输出目次,但由于运用了`webpack`,所以这个设置并没有什么卵用
"outDir": "../client-dist",
// 开启`JSX`形式,
// `preserve`的设置让`tsc`不会去处置惩罚它,而是运用后续的`babel-loader`举行处置惩罚
"jsx": "preserve",
"strict": true,
"moduleResolution": "node",
// 开启装潢器的运用
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
// `vs code`所须要的,在开辟时找到对应的途径,实在的援用是在`webpack`中设置的`alias`
"paths": {
"@Common": [
"../src/common"
],
"@Common/*": [
"../src/common/*"
]
}
},
"exclude": [
"node_modules"
]
}
ESLint的设置
近来这段时候,我们团队基于airbnb
的ESLint
划定规矩举行了一些自定义,建立了自家的eslint-config-blued
同时还存在了react和typescript的两个衍生版本。
关于ESLint
的设置文件.eslintrc
,在本项目中存在两份。一个是根目次的blued-typescript
,另一个是client-src
下的blued-react
+ blued-typescript
。
由于根目次的更多用于node
项目,所以没必要把react
什么的依靠也装进来。
# .eslintrc
extends: blued-typescript
# client-src/.eslintrc
extends:
- blued-react
- blued-typescript
一个须要注重的小细节
由于我们的react
与typescript
完成版本中都用到了parser
。 react
运用的是babel-eslint,typescript
运用的是typescript-eslint-parser。
然则parser
只能有一个,从option
的定名中就可以看出extends
、plugins
、rules
,到了parser
就没有复数了。
所以这两个插件在extends
中的递次就变得很症结,babel
如今并不能明白TS
的语法,但彷佛babel
开辟者有支撑TS
的志愿。
但就如今来讲,肯定要保证react
在前,typescript
在后,如许parser
才会运用typescript-eslint-parser
来举行掩盖。
node层的修正
除了上边提到的两头公用代码之外,还须要增加一个controller
用于吐页面,由于运用的是routing-controllers
这个库,衬着一个静态页面被封装的异常棒,仅仅须要修正两个页面,一个用于设置render
模版的根目次,另一个用来设置要吐出来的模版称号:
// controller/index.ts
import {
Get,
Controller,
Render,
} from 'routing-controllers'
@Controller('/')
export default class {
@Get('/')
@Render('index') // 指定一个模版的名字
async router() {
// 衬着页面时的一些变量
// 相似之前的 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',
}))
假如是多个页面,那就建立多个用来Render
的ts
文件就好了
深坑,注重
如今的routing-controller
关于Koa
的支撑还不是很好,(原作者对Koa
并非很相识,致使Render
对应的接口被要求一次今后,后续一切的其他的接口都邑直接返回该模版文件,缘由是在担任模版衬着的URL
触发时,本应返回数据,然则如今的处置惩罚倒是增加了一个中间件到Koa
中,所以任何要求都邑将该模版文件作为数据来返回)所以@Render
并不能适用于Koa
驱动。
不过我已提交了PR了,跑通了测试用例,坐等被兼并代码,然则这是一个暂时的修正计划,涉及到这个库针对外部中间件注册的递次题目,所以关于app.ts
还要有分外的修正才可以完成。
// app.ts 的修正
import 'reflect-metadata'
import Koa from 'koa'
import koaViews from 'koa-views'
import { useKoaServer } from 'routing-controllers'
import { distPath } from './config'
// 手动建立koa实例,然后增加`render`的中间件,确保`ctx.render`要领会在要求的头部就被增加进去
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}`],
})
// 后续的逻辑就都一样了
export default app
固然,这个是新版发出今后的逻辑了,基于现有的构造也可以绕过去,然则就不能运用@Render
装潢器了,抛开koa-views
直接运用内部的consolidate:
// controller/index.ts
// 这个修正不须要修改`app.ts`,可以直接运用`createKoaServer`
import {
Get,
Controller,
} from 'routing-controllers'
import cons from 'consolidate'
import path from 'path'
@Controller()
export default class {
@Get('/')
async router() {
// 直接在接口返回时猎取模版衬着后的数据
return cons.ejs(path.resolve(__dirname, '../../views/index.ejs'), {
title: 'Example For TypeScript React App',
})
}
}
如今的示例代码采纳的上边的计划
小结
至此,一个完全的TS前后端项目架构就已搭建完成了(剩下的使命就是往骨架里边填代码了)。
我已更新了之前的typescript-exmaple 在里边增加了本次重构所运用的一些前端TS
+React
的示例,还包括针对@Render
的一些兼容。
TypeScript
是一个很棒的主意,处理了N多javaScript
种使人诟病的题目。
运用静态言语来举行开辟不仅可以进步开辟的效力,同时还能下降毛病涌现的概率。
结合著壮大的vs code
,Enjoy it.
假如在运用TS
的历程中有什么题目、或许有什么更好的主意,迎接来沟通议论。
One more things
Blued前端/Node团队招人。。初中高都有HC
坐标帝都旭日双井,有兴致的请联络我:
wechat: github_jiasm
mail: jiashunming@blued.com
迎接砸简历