1. TypeScript工程化開發(fā)
- 前端工程化就是通過流程規(guī)范化炸庞、標(biāo)準(zhǔn)化提升團(tuán)隊(duì)協(xié)作效率
- 通過組件化燕雁、模塊化提升代碼質(zhì)量
- 使用構(gòu)建工具、自動(dòng)化工具提升開發(fā)效率
- 編譯 => 打包(合并) => 壓縮 => 代碼檢查 => 測(cè)試 => 持續(xù)集成
2.初始化項(xiàng)目
mkdir zhufeng_typescript_development
cd zhufeng_typescript_development
npm init
package name: (zhufeng_typescript_development)
version: (1.0.0)
description: TypeScript工程化開發(fā)
entry point: (index.js)
test command:
git repository: https://gitee.com/zhufengpeixun/zhufeng_typescript_development
keywords: typescript,react
author: zhangrenyang
license: (ISC) MIT
3. git規(guī)范和changelog
3.1 良好的git commit好處
- 可以加快code review 的流程
- 可以根據(jù)git commit 的元數(shù)據(jù)生成changelog
- 可以讓其它開發(fā)者知道修改的原因
3.2 良好的commit
commitizen是一個(gè)格式化commit message的工具
validate-commit-msg 用于檢查項(xiàng)目的
Commit message
是否符合格式conventional-changelog-cli可以從
git metadata
生成變更日志統(tǒng)一團(tuán)隊(duì)的git commit 標(biāo)準(zhǔn)
-
可以使用
angular
的git commit
日志作為基本規(guī)范- 提交的類型限制為 feat僧免、fix捏浊、docs、style浊洞、refactor法希、perf靶瘸、test怨咪、chore、revert等
- 提交信息分為兩部分唉匾,標(biāo)題(首字母不大寫巍膘,末尾不要加標(biāo)點(diǎn))峡懈、主體內(nèi)容(描述修改內(nèi)容)
日志提交友好的類型選擇提示 使用commitize工具
-
不符合要求格式的日志拒絕提交 的保障機(jī)制
- 需要使用
validate-commit-msg
工具
- 需要使用
-
統(tǒng)一changelog文檔信息生成
- 使用
conventional-changelog-cli
工具
- 使用
cnpm i commitizen validate-commit-msg conventional-changelog-cli -D
commitizen init cz-conventional-changelog --save --save-exact
git cz
3.3 提交的格式
<type>(<scope>):<subject/>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
- <type>代表某次提交的類型,比如是修復(fù)bug還是增加feature
- <scope>表示作用域,比如一個(gè)頁(yè)面或一個(gè)組件
- <subject> 主題 梅鹦,概述本次提交的內(nèi)容
- <body> 詳細(xì)的影響內(nèi)容
- <footer> 修復(fù)的bug和issue鏈接
類型 | 含義 |
---|---|
feat | 新增feature |
fix | 修復(fù)bug |
docs | 僅僅修改了文檔齐唆,比如README箍邮、CHANGELOG叨叙、CONTRIBUTE等 |
style | 僅僅修改了空格、格式縮進(jìn)擂错、偏好等信息味滞,不改變代碼邏輯 |
refactor | 代碼重構(gòu),沒有新增功能或修復(fù)bug |
perf | 優(yōu)化相關(guān)钮呀,提升了性能和體驗(yàn) |
test | 測(cè)試用例剑鞍,包括單元測(cè)試和集成測(cè)試 |
chore | 改變構(gòu)建流程,或者添加了依賴庫(kù)和工具 |
revert | 回滾到上一個(gè)版本 |
ci | CI 配置爽醋,腳本文件等更新 |
3.4 husky
-
validate-commit-msg
可以來檢查我們的commit規(guī)范 - husky可以把
validate-commit-msg
作為一個(gè)githook
來驗(yàn)證提交消息
cnpm i husky validate-commit-msg --save-dev
"husky": {
"hooks": {
"commit-msg": "validate-commit-msg"
}
}
3.5 生成CHANGELOG.md
-
conventional-changelog-cli
默認(rèn)推薦的 commit 標(biāo)準(zhǔn)是來自angular項(xiàng)目 - 參數(shù)
-i CHANGELOG.md
表示從CHANGELOG.md
讀取changelog
- 參數(shù) -s 表示讀寫
CHANGELOG.md
為同一文件 - 參數(shù) -r 表示生成 changelog 所需要使用的 release 版本數(shù)量蚁署,默認(rèn)為1光戈,全部則是0
cnpm i conventional-changelog-cli -D
"scripts": {
"changelogs": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0"
}
4. 支持Typescript
tsc --init
基本參數(shù)
參數(shù) | 解釋 |
---|---|
target | 用于指定編譯之后的版本目標(biāo) |
module | 生成的模塊形式:none、commonjs镇饺、amd奸笤、system监右、umd健盒、es6惰帽、es2015 或 esnext 只有 amd 和 system 能和 outFile 一起使用 target 為 es5 或更低時(shí)可用 es6 和 es2015 |
lib | 編譯時(shí)引入的 ES 功能庫(kù)该酗,包括:es5 、es6莱衩、es7操骡、dom 等册招。如果未設(shè)置是掰,則默認(rèn)為: target 為 es5 時(shí): ["dom", "es5", "scripthost"] target 為 es6 時(shí): ["dom", "es6", "dom.iterable", "scripthost"] |
allowJs | 是否允許編譯JS文件键痛,默認(rèn)是false,即不編譯JS文件 |
checkJs | 是否檢查和報(bào)告JS文件中的錯(cuò)誤丁频,默認(rèn)是false |
jsx | 指定jsx代碼用于的開發(fā)環(huán)境 preserve 指保留JSX語(yǔ)法,擴(kuò)展名為.jsx ,react-native是指保留jsx語(yǔ)法席里,擴(kuò)展名js,react指會(huì)編譯成ES5語(yǔ)法 詳解
|
declaration | 是否在編譯的時(shí)候生成相應(yīng)的.d.ts 聲明文件 |
declarationDir | 生成的 .d.ts 文件存放路徑,默認(rèn)與 .ts 文件相同 |
declarationMap | 是否為聲明文件.d.ts生成map文件 |
sourceMap | 編譯時(shí)是否生成.map 文件 |
outFile | 是否將輸出文件合并為一個(gè)文件,值是一個(gè)文件路徑名繁疤,只有設(shè)置module 的值為amd 和system 模塊時(shí)才支持這個(gè)配置 |
outDir | 指定輸出文件夾 |
rootDir | 編譯文件的根目錄,編譯器會(huì)在根目錄查找入口文件 |
composite | 是否編譯構(gòu)建引用項(xiàng)目 |
removeComments | 是否將編譯后的文件中的注釋刪掉 |
noEmit | 不生成編譯文件 |
importHelpers | 是否引入tslib 里的輔助工具函數(shù) |
downlevelIteration | 當(dāng)target為ES5 或ES3 時(shí),為for-of 褐啡、spread 和destructuring 中的迭代器提供完全支持 |
isolatedModules | 指定是否將每個(gè)文件作為單獨(dú)的模塊,默認(rèn)為true |
嚴(yán)格檢查
參數(shù) | 解釋 |
---|---|
strict | 是否啟動(dòng)所有類型檢查 |
noImplicitAny | 不允許默認(rèn)any類型 |
strictNullChecks | 當(dāng)設(shè)為true時(shí)懂盐,null和undefined值不能賦值給非這兩種類型的值 |
strictFunctionTypes | 是否使用函數(shù)參數(shù)雙向協(xié)變檢查 |
strictBindCallApply | 是否對(duì)bind莉恼、call和apply綁定的方法的參數(shù)的檢測(cè)是嚴(yán)格檢測(cè)的 |
strictPropertyInitialization | 檢查類的非undefined屬性是否已經(jīng)在構(gòu)造函數(shù)里初始化 |
noImplicitThis | 不允許this 表達(dá)式的值為any 類型的時(shí)候 |
alwaysStrict | 指定始終以嚴(yán)格模式檢查每個(gè)模塊 |
額外檢查
參數(shù) | 解釋 |
---|---|
noUnusedLocals | 檢查是否有定義了但是沒有使用的變量 |
noUnusedParameters | 檢查是否有在函數(shù)體中沒有使用的參數(shù) |
noImplicitReturns | 檢查函數(shù)是否有返回值 |
noFallthroughCasesInSwitch | 檢查switch中是否有case沒有使用break跳出 |
模塊解析檢查
參數(shù) | 解釋 |
---|---|
moduleResolution | 選擇模塊解析策略,有node 和classic 兩種類型,詳細(xì)說明
|
baseUrl | 解析非相對(duì)模塊名稱的基本目錄 |
paths | 設(shè)置模塊名到基于baseUrl 的路徑映射 |
rootDirs | 可以指定一個(gè)路徑列表捶惜,在構(gòu)建時(shí)編譯器會(huì)將這個(gè)路徑列表中的路徑中的內(nèi)容都放到一個(gè)文件夾中 |
typeRoots | 指定聲明文件或文件夾的路徑列表 |
types | 用來指定需要包含的模塊 |
allowSyntheticDefaultImports | 允許從沒有默認(rèn)導(dǎo)出的模塊中默認(rèn)導(dǎo)入 |
esModuleInterop | 為導(dǎo)入內(nèi)容創(chuàng)建命名空間,實(shí)現(xiàn)CommonJS和ES模塊之間的互相訪問 |
preserveSymlinks | 不把符號(hào)鏈接解析為其真實(shí)路徑 |
sourcemap檢查
參數(shù) | 解釋 |
---|---|
sourceRoot | 調(diào)試器應(yīng)該找到TypeScript文件而不是源文件位置 |
mapRoot | 調(diào)試器找到映射文件而非生成文件的位置,指定map文件的根路徑 |
inlineSourceMap | 指定是否將map文件的內(nèi)容和js文件編譯在一個(gè)同一個(gè)js文件中 |
inlineSources | 是否進(jìn)一步將.ts文件的內(nèi)容也包含到輸出文件中 |
試驗(yàn)選項(xiàng)
參數(shù) | 解釋 |
---|---|
experimentalDecorators | 是否啟用實(shí)驗(yàn)性的裝飾器特性 |
emitDecoratorMetadata | 是否為裝飾器提供元數(shù)據(jù)支持 |
試驗(yàn)選項(xiàng)
參數(shù) | 解釋 |
---|---|
files | 配置一個(gè)數(shù)組列表,里面包含指定文件的相對(duì)或絕對(duì)路徑吝岭,編譯器在編譯的時(shí)候只會(huì)編譯包含在files中列出的文件 |
include | include也可以指定要編譯的路徑列表苍碟,但是和files的區(qū)別在于微峰,這里的路徑可以是文件夾颜凯,也可以是文件 |
exclude | exclude表示要排除的症概、不編譯的文件彼城,他也可以指定一個(gè)列表 |
extends | extends可以通過指定一個(gè)其他的tsconfig.json文件路徑募壕,來繼承這個(gè)配置文件里的配置 |
compileOnSave | 在我們編輯了項(xiàng)目中文件保存的時(shí)候舱馅,編輯器會(huì)根據(jù)tsconfig.json 的配置重新生成文件 |
references | 一個(gè)對(duì)象數(shù)組,指定要引用的項(xiàng)目 |
5. 支持React
5.1 安裝
cnpm i typescript webpack webpack-cli webpack-dev-server ts-loader cross-env webpack-merge clean-webpack-plugin html-webpack-plugin -D
cnpm i babel-loader @babel/core @babel/cli @babel/plugin-proposal-class-properties @babel/plugin-proposal-object-rest-spread @babel/preset-env @babel/preset-typescript -D
5.2 webpack.config.js
webpack.config.js
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require("path");
module.exports = {
mode: "development",
devtool:false,
entry: "./src/index.tsx",
output: {
filename: "[name].[hash].js",
path: path.join(__dirname, "dist"),
},
devServer: {
hot: true,
contentBase: path.join(__dirname, "dist"),
historyApiFallback: {
index: "./index.html",
},
},
resolve: {
extensions: [".ts", ".tsx", ".js", ".json"],
alias: {
"@": path.resolve("src"), // 這樣配置后 @ 可以指向 src 目錄
},
},
module: {
rules: [
{
test: /\.tsx?$/,
loader: "ts-loader"
}
],
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html"
}),
new webpack.HotModuleReplacementPlugin()
],
};
5.3 src\index.tsx
src\index.tsx
import * as React from 'react';
import * as ReactDOM from 'react-dom';
let root = document.getElementById('root');
let props = { className: 'title' };
let element= React.createElement('div', props, 'hello');
ReactDOM.render(element, root);
5.4 src\index.html
src\index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>typescript</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
5.5 package.json
{
"scripts": {
+ "start": "cross-env NODE_ENV=development webpack serve --config ./config/webpack.dev.js",
+ "build": "cross-env NODE_ENV=production npm run eslint && webpack --config ./config/webpack.prod.js",
"eslint": "eslint src --ext .ts",
"eslint:fix": "eslint src --ext .ts --fix",
"changelogs": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",
"test": "mocha --require ts-node/register test/**/*"
}
}
6. 代碼規(guī)范
- 規(guī)范的代碼可以促進(jìn)團(tuán)隊(duì)合作
- 規(guī)范的代碼可以降低維護(hù)成本
- 規(guī)范的代碼有助于 code review(代碼審查)
6.1 常見的代碼規(guī)范文檔
6.2 代碼檢查
- Eslint 是一款插件化的 JavaScript 靜態(tài)代碼檢查工具干毅,ESLint 通過規(guī)則來描述具體的檢查行為
6.2.1 模塊安裝
cnpm i eslint typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin --save-dev
6.2.2 eslintrc配置文件
.eslintrc.js
module.exports = {
"parser":"@typescript-eslint/parser",
"plugins":["@typescript-eslint"],
"rules":{
"no-var":"error",
"no-extra-semi":"error",
"@typescript-eslint/indent":["error",2]
},
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module",
"ecmaFeatures": {
"modules": true
}
}
}
6.2.3 代碼檢查
package.json
"scripts": {
"start": "webpack",
"build": "tsc",
"eslint": "eslint src --ext .ts",
"eslint:fix": "eslint src --ext .ts --fix"
}
src/1.ts
var name2 = 'zhufeng';;;
if(true){
let a = 10;
}
執(zhí)行命令
npm run eslint
1:1 error Unexpected var, use let or const instead no-var
1:23 error Unnecessary semicolon no-extra-semi
1:24 error Unnecessary semicolon no-extra-semi
3:1 error Expected indentation of 2 spaces but found 4 @typescript-eslint/indent
6.2.4 配置自動(dòng)修復(fù)
- 安裝vscode的eslint插件
- 配置自動(dòng)修復(fù)參數(shù)
.vscode\settings.json
{
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
],
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
}
7.單元測(cè)試
7.1 安裝配置
cnpm i jest @types/jest ts-jest -D
npx ts-jest config:init
7.2 src\calculator.tsx
src\calculator.tsx
function sum(a: number, b: number) {
return a + b;
}
function minus(a: number, b: number) {
return a - b;
}
module.exports = {
sum,
minus
}
7.3 tests\calculator.spec.tsx
tests\calculator.spec.tsx
let math = require('../src/calculator');
test('1+1=2', () => {
expect(math.sum(1, 1)).toBe(2);
});
test('1-1=0', () => {
expect(math.minus(1, 1)).toBe(0);
});
7.4 package.json
package.json
"scripts": {
+ "test": "jest"
},
8. 持續(xù)集成
- Travis CI 提供的是持續(xù)集成服務(wù)(Continuous Integration符隙,簡(jiǎn)稱 CI)霹疫。它綁定 Github 上面的項(xiàng)目猎拨,只要有新的代碼吧恃,就會(huì)自動(dòng)抓取硬毕。然后,提供一個(gè)運(yùn)行環(huán)境,執(zhí)行測(cè)試节腐,完成構(gòu)建箱熬,還能部署到服務(wù)器
- 持續(xù)集成指的是只要代碼有變更赤炒,就自動(dòng)運(yùn)行構(gòu)建和測(cè)試,反饋運(yùn)行結(jié)果表悬。確保符合預(yù)期以后卤恳,再將新代碼集成到主干
- 持續(xù)集成的好處在于,每次代碼的小幅變更本今,就能看到運(yùn)行結(jié)果,從而不斷累積小的變更挪凑,而不是在開發(fā)周期結(jié)束時(shí),一下子合并一大塊代碼
8.1 登錄并創(chuàng)建項(xiàng)目
-
Travis CI 只支持 Github,所以你要擁有
GitHub
帳號(hào) - 該帳號(hào)下面有一個(gè)項(xiàng)目,面有可運(yùn)行的代碼,還包含構(gòu)建或測(cè)試腳本
- 你需要激活了一個(gè)倉(cāng)庫(kù)菇绵,
Travis
會(huì)監(jiān)聽這個(gè)倉(cāng)庫(kù)的所有變化
8.2 .travis.yml
-
Travis
要求項(xiàng)目的根目錄下面咬最,必須有一個(gè).travis.yml
文件惑申。這是配置文件圈驼,指定了 Travis 的行為 - 該文件必須保存在 Github 倉(cāng)庫(kù)里面绩脆,一旦代碼倉(cāng)庫(kù)有新的
Commit
靴迫,Travis 就會(huì)去找這個(gè)文件,執(zhí)行里面的命令 - 這個(gè)文件采用 YAML 格式慌随。下面是一個(gè)最簡(jiǎn)單的 Node 項(xiàng)目的
.travis.yml
文件- language 字段指定了默認(rèn)運(yùn)行環(huán)境,所有的語(yǔ)言在此
language: node_js
node_js:
- "11"
install: npm install
script: npm test
8.3 實(shí)戰(zhàn)
8.3.1 生成項(xiàng)目并上傳github
npx create-react-app zhufeng-typescript-development
8.3.2 同步倉(cāng)庫(kù)
- 登錄travis-ci.com選擇同步倉(cāng)庫(kù)
8.3.3 設(shè)置倉(cāng)庫(kù)環(huán)境變量
變量名 | 含義 | |
---|---|---|
GH_TOKEN | 用戶生成的令牌 | |
GH_REF | 倉(cāng)庫(kù)地址 | github.com/zhufengnodejs/zhufeng_typescript_development.git |
8.3.4 Github生成訪問令牌 (即添加授權(quán))
- 訪問令牌的作用就是授權(quán)倉(cāng)庫(kù)操作權(quán)限
- Github>settings>Personal access tokens> Generate new token > Generate token> Copy Token
8.3.5 .travis.yml
language: node_js
node_js:
- '11'
install:
- npm install
script:
- hexo g
after_script:
- cd ./public
- git init
- git config user.name "${USERNAME}"
- git config user.email "${UESREMAIL}"
- git add -A
- git commit -m "Update documents"
- git push --force "https://${GH_TOKEN}@${GH_REF}" "master:${GH_BRANCH}"
branches:
only:
- master
9.React元素
8.1 原生組件
src\index.tsx
import * as React from 'react';
import * as ReactDOM from 'react-dom';
let root: HTMLElement | null = document.getElementById('root');
interface Props {
className: string
}
let props: Props = { className: 'title' };
let element: React.DetailedReactHTMLElement<Props, HTMLDivElement> = (
React.createElement<Props, HTMLDivElement>('div', props, 'hello')
)
ReactDOM.render(element, root);
src\typings.tsx
export interface DOMAttributes {
children?: ReactNode;
}
export interface HTMLAttributes extends DOMAttributes {
className?: string;
}
export interface ReactElement<P = any,T extends string> {
type: T;
props: P;
}
export interface DOMElement extends ReactElement{}
export interface ReactHTML { div: HTMLDivElement }
export interface DetailedReactHTMLElement extends DOMElement{
type: keyof ReactHTML;
}
export type ReactText = string | number;
export type ReactChild = ReactElement | ReactText;
export type ReactNode = ReactChild | boolean | null | undefined;
export declare function createElement<P extends {}>(
type: string,
props?: P,
...children: ReactNode[]): ReactElement;
8.2 函數(shù)組件
src\index.tsx
import * as React from 'react';
import * as ReactDOM from 'react-dom';
let root: HTMLElement | null = document.getElementById('root');
interface Props {
className: string
}
let props: Props = { className: 'title' };
function Welcome(props: Props):React.DetailedReactHTMLElement<Props, HTMLDivElement> {
return React.createElement<Props, HTMLDivElement>('div', props, 'hello');
}
let element: React.FunctionComponentElement<Props> = (
React.createElement<Props>(Welcome, props)
)
ReactDOM.render(element, root);
src\typings.tsx
export interface DOMAttributes {
children?: ReactNode;
}
export interface HTMLAttributes extends DOMAttributes {
className?: string;
}
+export type JSXElementConstructor<P> = ((props: P) => ReactElement | null)
+export interface ReactElement<P = any, T extends string | JSXElementConstructor<any> = string> {
type: T;
props: P;
}
export interface DOMElement extends ReactElement{}
export interface ReactHTML { div: HTMLDivElement }
export interface DetailedReactHTMLElement extends DOMElement{
type: keyof ReactHTML;
}
export type ReactText = string | number;
export type ReactChild = ReactElement | ReactText;
export type ReactNode = ReactChild | boolean | null | undefined;
+type PropsWithChildren<P> = P & { children?: ReactNode };
+interface FunctionComponent<P = {}> {
+ (props: PropsWithChildren<P>): ReactElement | null;
+}
+interface FunctionComponentElement<P> extends ReactElement<P, FunctionComponent<P>> {}
+export declare function createElement<P extends {}>(
+ type: FunctionComponent<P>,
+ props?: P,
+ ...children: ReactNode[]): FunctionComponentElement<P>;
export declare function createElement<P extends {}>(
type: string,
props?: P,
...children: ReactNode[]): ReactElement;
8.3 類組件
src\index.tsx
import * as React from 'react';
import * as ReactDOM from 'react-dom';
let root: HTMLElement | null = document.getElementById('root');
interface Props {
className: string
}
interface State {
count:number
}
class Welcome extends React.Component<Props, State> {
state = { count: 0 }
render():React.DetailedReactHTMLElement<Props, HTMLDivElement> {
return React.createElement<Props, HTMLDivElement>('div', this.props, this.state.count);
}
}
let props: Props = { className: 'title' };
let element = (
React.createElement<Props>(Welcome, props)
)
ReactDOM.render(element, root);
src\typings.tsx
export interface DOMAttributes {
children?: ReactNode;
}
export interface HTMLAttributes extends DOMAttributes {
className?: string;
}
export type JSXElementConstructor<P> =
| ((props: P) => ReactElement | null)
| (new (props: P) => Component<P, any>);
export interface ReactElement<P = any, T extends string | JSXElementConstructor<any> = string> {
type: T;
props: P;
}
export interface DOMElement extends ReactElement{}
export interface ReactHTML { div: HTMLDivElement }
export interface DetailedReactHTMLElement extends DOMElement{
type: keyof ReactHTML;
}
export type ReactText = string | number;
export type ReactChild = ReactElement | ReactText;
export type ReactNode = ReactChild | boolean | null | undefined;
type PropsWithChildren<P> = P & { children?: ReactNode };
interface FunctionComponent<P = {}> {
(props: PropsWithChildren<P>): ReactElement | null;
}
interface FunctionComponentElement<P> extends ReactElement<P, FunctionComponent<P>> {}
type ComponentState = any;
declare class Component<P, S> {
setState(state: any): void;
render(): ReactNode;
}
interface ComponentClass<P = {}, S = ComponentState> {
new(props: P): Component<P, S>;
}
interface ComponentElement<P> extends ReactElement<P, ComponentClass<P>> {}
export declare function createElement<P extends {}>(
type: ComponentClass<P>,
props?: P,
...children: ReactNode[]): ComponentElement<P>;
export declare function createElement<P extends {}>(
type: FunctionComponent<P>,
props?: P,
...children: ReactNode[]): FunctionComponentElement<P>;
export declare function createElement<P extends {}>(
type: string,
props?: P,
...children: ReactNode[]): ReactElement;