搭建前端工程化
在我們?nèi)粘i_(kāi)發(fā)項(xiàng)目時(shí),基本上會(huì)采用官方腳手架進(jìn)行開(kāi)發(fā)。然后使用官方腳手架開(kāi)發(fā)也有缺點(diǎn):不能很好的自定義一些功能睛低。下面我將總結(jié)出來(lái)我是如何從零開(kāi)始搭建前端工程的,希望對(duì)大家有所幫助。
1. 工程化的目的
- 前端工程化就是通過(guò)流程規(guī)范化钱雷、標(biāo)準(zhǔn)化提升團(tuán)隊(duì)協(xié)作效率
- 通過(guò)組件化骂铁、模塊化提升代碼質(zhì)量
- 使用構(gòu)建工具、自動(dòng)化工具提升開(kāi)發(fā)效率
2. 工程化開(kāi)發(fā)的流程
- 編譯 => 打包(合并) => 壓縮 (webpack 或者 rollup)
- 代碼檢查 => eslint
- 測(cè)試 => jest
- 發(fā)包
- 持續(xù)繼承
3. 編譯工具的選擇
大的編譯工具主要包括兩種罩抗,分別時(shí)webpack 和 rollup拉庵,下面我們將講解如何配置。
3.1 webpack 配置
const webpack = require("webpack");
// html 的插件套蒂,可以指定一個(gè)index.html 將對(duì)應(yīng)的js文件插入到頁(yè)面中
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"),
},
// webpack5 內(nèi)置了 webpack-dev-server名段, 如果5一下的需要單獨(dú)安裝
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"
}),
// webpack 的內(nèi)置插件。熱更新使用
new webpack.HotModuleReplacementPlugin()
],
};
注意: webpack5 內(nèi)置了 webpack-dev-server,可以直接使用 webpack server 命令啟動(dòng)泣懊。
3.2 rollup的配置
import ts from 'rollup-plugin-typescript2'; // 解析ts的插件
import {
nodeResolve
} from '@rollup/plugin-node-resolve'; // 解析第三方模塊的插件
import commonjs from '@rollup/plugin-commonjs'; // 讓第三方非esm模塊支持編譯
import json from 'rollup-plugin-json'; // 支持編譯json
import serve from 'rollup-plugin-serve'; // 啟動(dòng)本地服務(wù)的插件
import path from 'path'
// 區(qū)分開(kāi)發(fā)環(huán)境
const isDev = process.env.NODE_ENV === 'development'
// rollup 支持es6語(yǔ)法
export default {
input: 'packages/index.ts',
output: {
// amd iife commonjs umd..
name:'hp',
format: 'umd', // esm 模塊
file: path.resolve(__dirname, 'dist/index.js'), // 出口文件
sourcemap: true, // 根據(jù)源碼產(chǎn)生映射文件
},
plugins: [
commonjs({
include: 'node_modules/**', // Default: undefined
extensions: ['.js','.ts']
// exclude: ['node_modules/foo/**', 'node_modules/bar/**'], // Default: undefined
}),
json({
// All JSON files will be parsed by default,
// but you can also specifically include/exclude files
include: 'node_modules/**',
// preferConst: true,
// namedExports: true // Default: true
}),
nodeResolve({ // 第三方文件解析
browser:true,
extensions: ['.js', '.ts']
}),
ts({
tsconfig: path.resolve(__dirname, 'tsconfig.json')
}),
isDev ? serve({
openPage: '/public/index.html',
contentBase: '',
port: 3000
}) : null
]
}
3.3 編譯typescript文件伸辟。
比你ts 兩種方案,一種時(shí)基于babel 編譯馍刮,一種是基于 tsc 編譯信夫。
基于tsc 編譯
// webpack 中
// cnpm i typescript ts-loader -D
module: {
rules: [
{
test: /\.tsx?$/,
loader: "ts-loader"
}
],
},
// rollup 中
// cnpm i rollup-plugin-typescript2 -D
ts({
tsconfig: path.resolve(__dirname, 'tsconfig.json')
})
基于babel 編譯
分別在 webpack 和 rollup 引入babel 插件
babel 的配置文件
const presets = [
['@babel/preset-env', { // 一個(gè)預(yù)設(shè)集合
// chrome, opera, edge, firefox, safari, ie, ios, android, node, electron
// targets 和 browerslist 合并取最低版本
// 啟用更符合規(guī)范的轉(zhuǎn)換,但速度會(huì)更慢卡啰,默認(rèn)為 `false`静稻,從目前來(lái)看,是更嚴(yán)格的轉(zhuǎn)化匈辱,包括一些代碼檢查振湾。
spec: false,
// 有兩種模式:normal, loose。其中 normal 更接近 es6 loose 更接近 es5
loose: false,
// "amd" | "umd" | "systemjs" | "commonjs" | "cjs" | false, defaults to "commonjs"
modules: false, // 代表是esm 模塊
// 打印插件使用情況
debug: false,
useBuiltIns: 'usage', // 按需引入
corejs: { version: 3, proposals: true } // 考慮使用2亡脸,還是3
}],
['@babel/preset-typescript', { // ts的配置
'isTSX': true,
'allExtensions': true
}],
'@vue/babel-preset-jsx'
];
const plugins = [
'@babel/plugin-syntax-jsx',
'@babel/plugin-transform-runtime',
'@babel/plugin-syntax-dynamic-import',
// ['@babel/plugin-transform-modules-commonjs'], 支持tree sharking 必須是esm 模塊押搪, 所以刪除此插件
// 支持裝飾器模式開(kāi)發(fā)
['@babel/plugin-proposal-decorators', { 'legacy': true }],
['@babel/plugin-proposal-class-properties', { 'loose': true }],
];
module.exports = {
presets,
plugins
};
建議: webpack 在業(yè)務(wù)中開(kāi)發(fā)推薦使用babel 編譯。 編輯js庫(kù)使用tsc編譯浅碾。
3.4 ts 配置文件梳理
基本參數(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代碼用于的開(kāi)發(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 | 檢查是否有定義了但是沒(méi)有使用的變量 |
noUnusedParameters | 檢查是否有在函數(shù)體中沒(méi)有使用的參數(shù) |
noImplicitReturns | 檢查函數(shù)是否有返回值 |
noFallthroughCasesInSwitch | 檢查switch中是否有case沒(méi)有使用break跳出 |
模塊解析檢查
**
參數(shù) | 解釋 |
---|---|
moduleResolution | 選擇模塊解析策略荐开,有node 和classic 兩種類型,詳細(xì)說(shuō)明
|
baseUrl | 解析非相對(duì)模塊名稱的基本目錄 |
paths | 設(shè)置模塊名到基于baseUrl 的路徑映射 |
rootDirs | 可以指定一個(gè)路徑列表,在構(gòu)建時(shí)編譯器會(huì)將這個(gè)路徑列表中的路徑中的內(nèi)容都放到一個(gè)文件夾中 |
typeRoots | 指定聲明文件或文件夾的路徑列表 |
types | 用來(lái)指定需要包含的模塊 |
allowSyntheticDefaultImports | 允許從沒(méi)有默認(rèn)導(dǎo)出的模塊中默認(rèn)導(dǎo)入 |
esModuleInterop | 為導(dǎo)入內(nèi)容創(chuàng)建命名空間,實(shí)現(xiàn)CommonJS和ES模塊之間的互相訪問(wèn) |
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可以通過(guò)指定一個(gè)其他的tsconfig.json文件路徑,來(lái)繼承這個(gè)配置文件里的配置 |
compileOnSave | 在我們編輯了項(xiàng)目中文件保存的時(shí)候膨处,編輯器會(huì)根據(jù)tsconfig.json 的配置重新生成文件 |
references | 一個(gè)對(duì)象數(shù)組,指定要引用的項(xiàng)目 |
tsconfig.json 的基本配置
{
"compilerOptions": {
"outDir": "./dist",
"sourceMap": true,
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"module": "commonjs",
"target": "es5",
"jsx": "react",
"baseUrl": ".",
"paths": {
"@/*": [
"src/*"
]
}
},
"include": [
"./src/**/*"
]
}
ts配置文件梳理完畢见秤。
4. 代碼校驗(yàn)
4.1 代碼檢查的目的:
- 規(guī)范的代碼可以促進(jìn)團(tuán)隊(duì)合作
- 規(guī)范的代碼可以降低維護(hù)成本
- 規(guī)范的代碼有助于 code review(代碼審查)
4.2 常見(jiàn)的代碼規(guī)范文檔
4.3 代碼與檢查插件eslint(vscode)
在vscode 商店中搜索 eslint, 然后安裝
image.png
配置生效方式一:(修改vscode 配置)
{
"eslint.options": { "configFile": "C:/mydirectory/.eslintrc.json" }
}
方式二:給每個(gè)單獨(dú)的工程增加配置文件
4.4 安裝模塊
cnpm i eslint typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin --save-dev
4.5 基本配置
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
}
}
}
// package.json
"scripts": {
+ "lint": "eslint --fix --ext .js,.vue src",
}
4.6 代碼與檢查
cnpm i husky lint-staged --save-dev
// package.json
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"src/**/*.js": [
"eslint --fix",
"git add"
],
"src/**/*.less": [
"stylelint --fix",
"git add"
]
},
5. 單元測(cè)試
5.1 安裝與配置
cnpm i jest @types/jest ts-jest jest -D // 依賴包
npx ts-jest config:init // 生成配置文件
// package.json
"scripts": {
+ "jest-test": "jest -o",
+ "jest-coverage": "jest --coverage"
},
5.2 配置文件展示
module.exports = {
roots: [
"<rootDir>/packages"
],
testRegex: 'test/(.+)\\.test\\.(jsx?|tsx?)$',
transform: {
"^.+\\.tsx?$": "ts-jest"
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
};
注意: 這里只是簡(jiǎn)單的列舉了下需要的東西,和一個(gè)夠用的配置真椿。如果要按照具體需求配置的話秦叛,需要查看文檔。
6. git提交規(guī)范與changelog (發(fā)包)
6.1 優(yōu)點(diǎn)
- 良好的git commit好處
- 可以加快code review 的流程
- 可以根據(jù)git commit 的元數(shù)據(jù)生成changelog
- 可以讓其它開(kāi)發(fā)者知道修改的原因
6.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
使用 git cz 命令:可以很方便的操作。
6.3 提交的格式
<type>(<scope>):<subject/>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
- 代表某次提交的類型,比如是修復(fù)bug還是增加feature
- 表示作用域围苫,比如一個(gè)頁(yè)面或一個(gè)組件
- 主題 裤园,概述本次提交的內(nèi)容
- 詳細(xì)的影響內(nèi)容
- 修復(fù)的bug和issue鏈接
| 類型 | 含義 |
| :--- | :--- |
| feat | 新增feature |
| fix | 修復(fù)bug |
| docs | 僅僅修改了文檔,比如README剂府、CHANGELOG拧揽、CONTRIBUTE等 |
| style | 僅僅修改了空格、格式縮進(jìn)腺占、偏好等信息淤袜,不改變代碼邏輯 |
| refactor | 代碼重構(gòu),沒(méi)有新增功能或修復(fù)bug |
| perf | 優(yōu)化相關(guān)衰伯,提升了性能和體驗(yàn) |
| test | 測(cè)試用例铡羡,包括單元測(cè)試和集成測(cè)試 |
| chore | 改變構(gòu)建流程,或者添加了依賴庫(kù)和工具 |
| revert | 回滾到上一個(gè)版本 |
| ci | CI 配置意鲸,腳本文件等更新 |
6.4 升級(jí)package.json 版本
安裝依賴 cnpm install standard-version inquirer shelljs -D
執(zhí)行腳本:
const inquirer = require('inquirer'); // 命令行交互模塊
const shell = require('shelljs');
if (!shell.which('git')) {
shell.echo('Sorry, this script requires git');
shell.exit(1);
}
const getVersion = async() => {
return new Promise((resolve, reject) => {
inquirer.prompt([
{
type: 'list',
name: 'version',
choices: ['patch', 'minor', 'major'],
message: 'please choose argument [major|minor|patch]: '
}
]).then(answer => {
resolve(answer.version);
}).catch(err => {
reject(err);
});
});
};
const main = async() => {
const version = await getVersion();
shell.echo(`\nReleasing ${version} ...\n`);
await shell.exec(`npm run standard-version -- --release-as ${version}`);
};
main();
major:升級(jí)主要版本
minor: 升級(jí)次要版本
patch:升級(jí)補(bǔ)丁版本
6.5 生成CHANGELOG.md
-
conventional-changelog-cli
默認(rèn)推薦的 commit 標(biāo)準(zhǔn)是來(lái)自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"
}
彩蛋:
你是否在github上見(jiàn)過(guò)這樣的release文檔
如果你感興趣临扮,下面會(huì)教你怎么配置
安裝 cnpm install gh-release -D
// package.json
"scripts": {
"rel": "gh-release"
},
操作步驟:
- 使用 git cz 提交代碼论矾。
- 使用 standard-version 升級(jí)版本
- 使用 changelogs 生成md (注意:這個(gè)時(shí)候不要提交代碼了)
- 執(zhí)行 npm run rel。
就可以得到上面好看的文檔了杆勇。
7. 持續(xù)集成
繼續(xù)集成有很多中不同的方案贪壳,實(shí)現(xiàn)方式也各有不同,方式:1.Travis CI 蚜退。2 jenkins 等闰靴。 由于配置比較不復(fù)雜這里就不展開(kāi)說(shuō)了,具體的可以根據(jù)公司的情況钻注,和運(yùn)維一起進(jìn)行配置蚂且。
結(jié)束!7怠P铀馈!