搭建前端工程化

搭建前端工程化

在我們?nèi)粘i_發(fā)項(xiàng)目時(shí),基本上會(huì)采用官方腳手架進(jìn)行開發(fā)。然后使用官方腳手架開發(fā)也有缺點(diǎn):不能很好的自定義一些功能。下面我將總結(jié)出來我是如何從零開始搭建前端工程的,希望對(duì)大家有所幫助。

1. 工程化的目的

  • 前端工程化就是通過流程規(guī)范化、標(biāo)準(zhǔn)化提升團(tuán)隊(duì)協(xié)作效率
  • 通過組件化、模塊化提升代碼質(zhì)量
  • 使用構(gòu)建工具、自動(dòng)化工具提升開發(fā)效率

2. 工程化開發(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文件插入到頁面中
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ū)分開發(fā)環(huán)境
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, // 根據(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`,從目前來看,是更嚴(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 模塊, 所以刪除此插件
    //  支持裝飾器模式開發(fā)  
    ['@babel/plugin-proposal-decorators', { 'legacy': true }],
    ['@babel/plugin-proposal-class-properties', { 'loose': true }],
];

module.exports = {
    presets,
    plugins
};

建議: webpack 在業(yè)務(wù)中開發(fā)推薦使用babel 編譯。 編輯js庫使用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 功能庫,包括: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語法,擴(kuò)展名為.jsx,react-native是指保留jsx語法,擴(kuò)展名js,react指會(huì)編譯成ES5語法 詳解
declaration 是否在編譯的時(shí)候生成相應(yīng)的.d.ts聲明文件
declarationDir 生成的 .d.ts 文件存放路徑,默認(rèn)與 .ts 文件相同
declarationMap 是否為聲明文件.d.ts生成map文件
sourceMap 編譯時(shí)是否生成.map文件
outFile 是否將輸出文件合并為一個(gè)文件,值是一個(gè)文件路徑名,只有設(shè)置module的值為amdsystem模塊時(shí)才支持這個(gè)配置
outDir 指定輸出文件夾
rootDir 編譯文件的根目錄,編譯器會(huì)在根目錄查找入口文件
composite 是否編譯構(gòu)建引用項(xiàng)目
removeComments 是否將編譯后的文件中的注釋刪掉
noEmit 不生成編譯文件
importHelpers 是否引入tslib里的輔助工具函數(shù)
downlevelIteration 當(dāng)target為ES5ES3時(shí),為for-ofspreaddestructuring中的迭代器提供完全支持
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 選擇模塊解析策略,有nodeclassic兩種類型,詳細(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)目

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 常見的代碼規(guī)范文檔

4.3 代碼與檢查插件eslint(vscode)

在vscode 商店中搜索 eslint, 然后安裝


image.png

配置生效方式一:(修改vscode 配置)

{
  "eslint.options": { "configFile": "C:/mydirectory/.eslintrc.json" }
}

方式二:給每個(gè)單獨(dú)的工程增加配置文件


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. 單元測(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)

  1. 良好的git commit好處
  • 可以加快code review 的流程
  • 可以根據(jù)git commit 的元數(shù)據(jù)生成changelog
  • 可以讓其它開發(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)

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


image.png

6.3 提交的格式

<type>(<scope>):<subject/>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
  • 代表某次提交的類型,比如是修復(fù)bug還是增加feature
  • 表示作用域,比如一個(gè)頁面或一個(gè)組件
  • 主題 ,概述本次提交的內(nèi)容
  • 詳細(xì)的影響內(nèi)容
  • 修復(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)建流程,或者添加了依賴庫和工具 |
    | 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)是來自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上見過這樣的release文檔

image.png

如果你感興趣,下面會(huì)教你怎么配置

安裝 cnpm install gh-release -D

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

操作步驟:

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

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

7. 持續(xù)集成

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

結(jié)束!!!!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 一、新建項(xiàng)目 1、先安裝腳手架vue-cli,已集成webpack cnpm install vue-cli -g...
    chqqq閱讀 1,409評(píng)論 0 0
  • 1. TypeScript工程化開發(fā) 前端工程化就是通過流程規(guī)范化、標(biāo)準(zhǔn)化提升團(tuán)隊(duì)協(xié)作效率 通過組件化、模塊化提升...
    張Piers閱讀 1,064評(píng)論 0 0
  • 前言 web應(yīng)用復(fù)雜度的增加,特別是單頁面應(yīng)用的風(fēng)靡。組件化,工程化,自動(dòng)化成了前端發(fā)展的趨勢(shì)。每個(gè)前端團(tuán)隊(duì)都在打...
    前端的爬行之旅閱讀 545評(píng)論 0 1
  • 1.webpack與grunt、gulp的不同? Grunt、Gulp是基于任務(wù)運(yùn)行的工具: 它們會(huì)自動(dòng)執(zhí)行指定的...
    北冥有魚_425c閱讀 2,181評(píng)論 0 4
  • 夜鶯2517閱讀 127,752評(píng)論 1 9