搭建前端工程化
在我們日常開發項目時,基本上會采用官方腳手架進行開發。然后使用官方腳手架開發也有缺點:不能很好的自定義一些功能。下面我將總結出來我是如何從零開始搭建前端工程的,希望對大家有所幫助。
1. 工程化的目的
- 前端工程化就是通過流程規范化、標準化提升團隊協作效率
- 通過組件化、模塊化提升代碼質量
- 使用構建工具、自動化工具提升開發效率
2. 工程化開發的流程
- 編譯 => 打包(合并) => 壓縮 (webpack 或者 rollup)
- 代碼檢查 => eslint
- 測試 => jest
- 發包
- 持續繼承
3. 編譯工具的選擇
大的編譯工具主要包括兩種,分別時webpack 和 rollup,下面我們將講解如何配置。
3.1 webpack 配置
const webpack = require("webpack");
// html 的插件,可以指定一個index.html 將對應的js文件插入到頁面中
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 內置了 webpack-dev-server, 如果5一下的需要單獨安裝
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 的內置插件。熱更新使用
new webpack.HotModuleReplacementPlugin()
],
};
注意: webpack5 內置了 webpack-dev-server,可以直接使用 webpack server 命令啟動。
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'; // 啟動本地服務的插件
import path from 'path'
// 區分開發環境
const isDev = process.env.NODE_ENV === 'development'
// rollup 支持es6語法
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, // 根據源碼產生映射文件
},
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 兩種方案,一種時基于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', { // 一個預設集合
// chrome, opera, edge, firefox, safari, ie, ios, android, node, electron
// targets 和 browerslist 合并取最低版本
// 啟用更符合規范的轉換,但速度會更慢,默認為 `false`,從目前來看,是更嚴格的轉化,包括一些代碼檢查。
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 模塊, 所以刪除此插件
// 支持裝飾器模式開發
['@babel/plugin-proposal-decorators', { 'legacy': true }],
['@babel/plugin-proposal-class-properties', { 'loose': true }],
];
module.exports = {
presets,
plugins
};
建議: webpack 在業務中開發推薦使用babel 編譯。 編輯js庫使用tsc編譯。
3.4 ts 配置文件梳理
基本參數
參數 | 解釋 |
---|---|
target | 用于指定編譯之后的版本目標 |
module | 生成的模塊形式:none、commonjs、amd、system、umd、es6、es2015 或 esnext 只有 amd 和 system 能和 outFile 一起使用 target 為 es5 或更低時可用 es6 和 es2015 |
lib | 編譯時引入的 ES 功能庫,包括:es5 、es6、es7、dom 等。如果未設置,則默認為: target 為 es5 時: ["dom", "es5", "scripthost"] target 為 es6 時: ["dom", "es6", "dom.iterable", "scripthost"] |
allowJs | 是否允許編譯JS文件,默認是false,即不編譯JS文件 |
checkJs | 是否檢查和報告JS文件中的錯誤,默認是false |
jsx | 指定jsx代碼用于的開發環境 preserve 指保留JSX語法,擴展名為.jsx ,react-native是指保留jsx語法,擴展名js,react指會編譯成ES5語法 詳解
|
declaration | 是否在編譯的時候生成相應的.d.ts 聲明文件 |
declarationDir | 生成的 .d.ts 文件存放路徑,默認與 .ts 文件相同 |
declarationMap | 是否為聲明文件.d.ts生成map文件 |
sourceMap | 編譯時是否生成.map 文件 |
outFile | 是否將輸出文件合并為一個文件,值是一個文件路徑名,只有設置module 的值為amd 和system 模塊時才支持這個配置 |
outDir | 指定輸出文件夾 |
rootDir | 編譯文件的根目錄,編譯器會在根目錄查找入口文件 |
composite | 是否編譯構建引用項目 |
removeComments | 是否將編譯后的文件中的注釋刪掉 |
noEmit | 不生成編譯文件 |
importHelpers | 是否引入tslib 里的輔助工具函數 |
downlevelIteration | 當target為ES5 或ES3 時,為for-of 、spread 和destructuring 中的迭代器提供完全支持 |
isolatedModules | 指定是否將每個文件作為單獨的模塊,默認為true |
嚴格檢查
**
參數 | 解釋 |
---|---|
strict | 是否啟動所有類型檢查 |
noImplicitAny | 不允許默認any類型 |
strictNullChecks | 當設為true時,null和undefined值不能賦值給非這兩種類型的值 |
strictFunctionTypes | 是否使用函數參數雙向協變檢查 |
strictBindCallApply | 是否對bind、call和apply綁定的方法的參數的檢測是嚴格檢測的 |
strictPropertyInitialization | 檢查類的非undefined屬性是否已經在構造函數里初始化 |
noImplicitThis | 不允許this 表達式的值為any 類型的時候 |
alwaysStrict | 指定始終以嚴格模式檢查每個模塊 |
額外檢查
**
參數 | 解釋 |
---|---|
noUnusedLocals | 檢查是否有定義了但是沒有使用的變量 |
noUnusedParameters | 檢查是否有在函數體中沒有使用的參數 |
noImplicitReturns | 檢查函數是否有返回值 |
noFallthroughCasesInSwitch | 檢查switch中是否有case沒有使用break跳出 |
模塊解析檢查
**
參數 | 解釋 |
---|---|
moduleResolution | 選擇模塊解析策略,有node 和classic 兩種類型,詳細說明
|
baseUrl | 解析非相對模塊名稱的基本目錄 |
paths | 設置模塊名到基于baseUrl 的路徑映射 |
rootDirs | 可以指定一個路徑列表,在構建時編譯器會將這個路徑列表中的路徑中的內容都放到一個文件夾中 |
typeRoots | 指定聲明文件或文件夾的路徑列表 |
types | 用來指定需要包含的模塊 |
allowSyntheticDefaultImports | 允許從沒有默認導出的模塊中默認導入 |
esModuleInterop | 為導入內容創建命名空間,實現CommonJS和ES模塊之間的互相訪問 |
preserveSymlinks | 不把符號鏈接解析為其真實路徑 |
sourcemap檢查
**
參數 | 解釋 |
---|---|
sourceRoot | 調試器應該找到TypeScript文件而不是源文件位置 |
mapRoot | 調試器找到映射文件而非生成文件的位置,指定map文件的根路徑 |
inlineSourceMap | 指定是否將map文件的內容和js文件編譯在一個同一個js文件中 |
inlineSources | 是否進一步將.ts文件的內容也包含到輸出文件中 |
**
試驗選項
**
參數 | 解釋 |
---|---|
experimentalDecorators | 是否啟用實驗性的裝飾器特性 |
emitDecoratorMetadata | 是否為裝飾器提供元數據支持 |
試驗選項
參數 | 解釋 |
---|---|
files | 配置一個數組列表,里面包含指定文件的相對或絕對路徑,編譯器在編譯的時候只會編譯包含在files中列出的文件 |
include | include也可以指定要編譯的路徑列表,但是和files的區別在于,這里的路徑可以是文件夾,也可以是文件 |
exclude | exclude表示要排除的、不編譯的文件,他也可以指定一個列表 |
extends | extends可以通過指定一個其他的tsconfig.json文件路徑,來繼承這個配置文件里的配置 |
compileOnSave | 在我們編輯了項目中文件保存的時候,編輯器會根據tsconfig.json 的配置重新生成文件 |
references | 一個對象數組,指定要引用的項目 |
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. 代碼校驗
4.1 代碼檢查的目的:
- 規范的代碼可以促進團隊合作
- 規范的代碼可以降低維護成本
- 規范的代碼有助于 code review(代碼審查)
4.2 常見的代碼規范文檔
4.3 代碼與檢查插件eslint(vscode)
在vscode 商店中搜索 eslint, 然后安裝
image.png
配置生效方式一:(修改vscode 配置)
{
"eslint.options": { "configFile": "C:/mydirectory/.eslintrc.json" }
}
方式二:給每個單獨的工程增加配置文件
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. 單元測試
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'],
};
注意: 這里只是簡單的列舉了下需要的東西,和一個夠用的配置。如果要按照具體需求配置的話,需要查看文檔。
6. git提交規范與changelog (發包)
6.1 優點
- 良好的git commit好處
- 可以加快code review 的流程
- 可以根據git commit 的元數據生成changelog
- 可以讓其它開發者知道修改的原因
6.2 良好的commit
commitizen是一個格式化commit message的工具
validate-commit-msg 用于檢查項目的
Commit message
是否符合格式conventional-changelog-cli可以從
git metadata
生成變更日志統一團隊的git commit 標準
-
可以使用
angular
的git commit
日志作為基本規范- 提交的類型限制為 feat、fix、docs、style、refactor、perf、test、chore、revert等
- 提交信息分為兩部分,標題(首字母不大寫,末尾不要加標點)、主體內容(描述修改內容)
日志提交友好的類型選擇提示 使用commitize工具
-
不符合要求格式的日志拒絕提交 的保障機制
- 需要使用
validate-commit-msg
工具
- 需要使用
-
統一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>
- 代表某次提交的類型,比如是修復bug還是增加feature
- 表示作用域,比如一個頁面或一個組件
- 主題 ,概述本次提交的內容
- 詳細的影響內容
- 修復的bug和issue鏈接
| 類型 | 含義 |
| :--- | :--- |
| feat | 新增feature |
| fix | 修復bug |
| docs | 僅僅修改了文檔,比如README、CHANGELOG、CONTRIBUTE等 |
| style | 僅僅修改了空格、格式縮進、偏好等信息,不改變代碼邏輯 |
| refactor | 代碼重構,沒有新增功能或修復bug |
| perf | 優化相關,提升了性能和體驗 |
| test | 測試用例,包括單元測試和集成測試 |
| chore | 改變構建流程,或者添加了依賴庫和工具 |
| revert | 回滾到上一個版本 |
| ci | CI 配置,腳本文件等更新 |
6.4 升級package.json 版本
安裝依賴 cnpm install standard-version inquirer shelljs -D
執行腳本:
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:升級主要版本
minor: 升級次要版本
patch:升級補丁版本
6.5 生成CHANGELOG.md
-
conventional-changelog-cli
默認推薦的 commit 標準是來自angular項目 - 參數
-i CHANGELOG.md
表示從CHANGELOG.md
讀取changelog
- 參數 -s 表示讀寫
CHANGELOG.md
為同一文件 - 參數 -r 表示生成 changelog 所需要使用的 release 版本數量,默認為1,全部則是0
cnpm i conventional-changelog-cli -D
"scripts": {
"changelogs": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0"
}
彩蛋:
你是否在github上見過這樣的release文檔
如果你感興趣,下面會教你怎么配置
安裝 cnpm install gh-release -D
// package.json
"scripts": {
"rel": "gh-release"
},
操作步驟:
- 使用 git cz 提交代碼。
- 使用 standard-version 升級版本
- 使用 changelogs 生成md (注意:這個時候不要提交代碼了)
- 執行 npm run rel。
就可以得到上面好看的文檔了。
7. 持續集成
繼續集成有很多中不同的方案,實現方式也各有不同,方式:1.Travis CI 。2 jenkins 等。 由于配置比較不復雜這里就不展開說了,具體的可以根據公司的情況,和運維一起進行配置。
結束!!!!