搭建前端工程化

搭建前端工程化

在我們日常開發項目時,基本上會采用官方腳手架進行開發。然后使用官方腳手架開發也有缺點:不能很好的自定義一些功能。下面我將總結出來我是如何從零開始搭建前端工程的,希望對大家有所幫助。

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的值為amdsystem模塊時才支持這個配置
outDir 指定輸出文件夾
rootDir 編譯文件的根目錄,編譯器會在根目錄查找入口文件
composite 是否編譯構建引用項目
removeComments 是否將編譯后的文件中的注釋刪掉
noEmit 不生成編譯文件
importHelpers 是否引入tslib里的輔助工具函數
downlevelIteration 當target為ES5ES3時,為for-ofspreaddestructuring中的迭代器提供完全支持
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 選擇模塊解析策略,有nodeclassic兩種類型,詳細說明
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" }
}

方式二:給每個單獨的工程增加配置文件


image.png

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 優點

  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 標準

  • 可以使用angulargit 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 命令:可以很方便的操作。


image.png

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文檔

image.png

如果你感興趣,下面會教你怎么配置

安裝 cnpm install gh-release -D

// package.json
 "scripts": {
    "rel": "gh-release"
  },

操作步驟:

  1. 使用 git cz 提交代碼。
  2. 使用 standard-version 升級版本
  3. 使用 changelogs 生成md (注意:這個時候不要提交代碼了)
  4. 執行 npm run rel。

就可以得到上面好看的文檔了。

7. 持續集成

繼續集成有很多中不同的方案,實現方式也各有不同,方式:1.Travis CI 。2 jenkins 等。 由于配置比較不復雜這里就不展開說了,具體的可以根據公司的情況,和運維一起進行配置。

結束!!!!

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,533評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,055評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,365評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,561評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,346評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,889評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,978評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,118評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,637評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,558評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,739評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,246評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,980評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,619評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,347評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,702評論 2 370

推薦閱讀更多精彩內容