前言
在實際項目開發中無論 M 端、PC 端,或多或少都有一個 utils 文件目錄去管理項目中用到的一些常用的工具方法,比如:時間處理、價格處理、解析 url 參數、加載腳本等,其中很多是重復、基礎、或基于某種業務場景的工具,存在項目間冗余的痛點以及工具方法規范不統一的問題。
在實際開發過程中,經常使用一些開源工具庫,如 lodash,以方便、快捷的進行項目開發。但是當 npm 上沒有自己中意或符合自身業務的工具時,我們不得不自己動手,此時擁有自己的、基于業務的工具庫就顯得尤為重要。
我們所熟知的 Vue、React 等諸多知名前端框架,或公司提供的一些類庫,它們是如何開發、構建、打包出來的,本文將帶領你了解到如何從 0 到 1 構建基于自身業務的前端工具庫。
構建工具庫主流方案
1. WEBPACK
webpack 提供了構建和打包不同模塊化規則的庫,只是需要自己去搭建開發底層架構。
vue-cli,基于 webpack , vue-cli 腳手架工具可以快速初始化一個 vue 應用,它也可以初始化一個構建庫。
2. ROLLUP
rollup 是一個專門針對 JavaScript 模塊打包器,可以將應用或庫的小塊代碼編譯成更復雜的功能代碼。
Vue、React 等許多流行前端框架的構建和打包都能看到 rollup 的身影。
為什么采用 ROLLUP 而不是 WEBPACK
webpack 主要職能是開發應用,而 rollup 主要針對的就是 js 庫的開發,如果你要開發 js 庫,那 webpack 的繁瑣配置和打包后的文件體積就不太適用了,通過 webpack 打包構建出來的源代碼增加了很多工具函數以外的模塊依賴代碼。
rollup 只是把業務代碼轉碼成目標 js ,小巧且輕便。rollup 對于代碼的 Tree-shaking 和 ES6 模塊有著算法優勢上的支持,如果只想構建一個簡單的庫,并且是基于 ES6 開發的,加上其簡潔的 API,rollup 得到更多開發者的青睞。
工具庫底層架構設計
構建工具庫底層架構大概需要哪些功能的支持:?
架構依賴需知
在對底層架構設計的基礎上,首先需要把用到的依賴庫簡單熟悉一下:
rollup 全家桶
?? rollup(工具庫打包構建核心包) ?? rollup-plugin-livereload(rollup 插件,熱更新,方便本地 debugger 開發) ?? rollup-plugin-serve(rollup 插件,本地服務代理,方便在本地 html 中調試工具) ?? rollup-plugin-terser(rollup 插件,代碼壓縮混淆) ?? rollup-plugin-visualizer(rollup 插件,可視化并分析 Rollup bundle,以查看模塊占用) ?? @rollup/plugin-babel(rollup 插件,rollup 的 babel 插件,ES6 轉 ES5) ?? @rollup/plugin-commonjs(rollup 插件,用來將 CommonJS 模塊轉換為 ES6,這樣它們就可以包含在 Rollup 包中) ?? @rollup/plugin-json(rollup 插件,它將.json 文件轉換為 ES6 模塊) ?? @rollup/plugin-node-resolve(rollup 插件,它使用節點解析算法定位模塊,用于在節點模塊中使用第三方 node_modules 包) ?? @rollup/plugin-typescript(rollup 插件,對 typescript 的支持,將 typescript 進行 tsc 轉為 js)
typescript 相關
?? typescript(使用 ts 開發工具庫) ?? tslib(TypeScript 的運行庫,它包含了 TypeScript 所有的幫助函數) ?? @typescript-eslint/eslint-plugin(TypeScript 的 eslint 插件,約束 ts 書寫規范) ?? @typescript-eslint/parser(ESLint 解析器,它利用 TypeScript ESTree 來允許 ESLint 檢測 TypeScript 源代碼)
文檔相關
?? typedoc(TypeScript 項目的文檔生成器) ?? gulp(使用 gulp 構建文檔系統) ?? gulp-typedoc(Gulp 插件來執行 TypeDoc 工具) ?? browser-sync(文檔系統熱更新)
單元測試相關
?? jest(一款優雅、簡潔的 JavaScript 測試框架) ?? @types/jest(Jest 的類型定義) ?? ts-jest(一個支持源映射的 Jest 轉換器,允許您使用 Jest 來測試用 TypeScript 編寫的項目) ?? @babel/preset-typescript(TypeScript 的 Babel 預設)
其他依賴
?? eslint(代碼規范約束) ?? @babel/core(@rollup/plugin-babel 依賴的 babel 解析插件) ?? @babel/plugin-transform-runtime(babel 轉譯依賴) ?? @babel/preset-env(babel 轉譯依賴) ?? chalk(控制臺字符樣式) ?? rimraf(UNIX 命令 rm -rf 用于 node) ?? cross-env(跨平臺設置 node 環境變量)
底層架構搭建
1. 初始化項目
新建一個文件夾 utils-demo,執行 npm init,過程會詢問構建項目的基本信息,按需填寫即可:
npm init
2. 組織工具庫業務開發 SRC 目錄結構
創建工具庫業務開發 src 文件目錄,明確怎樣規劃工具庫包,里面放置的是工具庫開發需要的業務代碼:
3. 安裝項目依賴
要對 typescript 代碼進行解析支持需要安裝對 ts 支持的依賴,以及對開發的工具的一些依賴包:
yarn add typescript tslib rollup rollup-plugin-livereload rollup-plugin-serve rollup-plugin-terser rollup-plugin-visualizer @rollup/plugin-babel @rollup/plugin-commonjs @rollup/plugin-json @rollup/plugin-node-resolve @rollup/plugin-typescript @babel/core @babel/plugin-transform-runtime @babel/preset-env rimraf lodash chalk@^4.1.2 -D這里遇到一個坑,關于最新 chalk5.0.0 不支持在 nodejs 中 require () 導入,所以鎖定包版本 chalk@^4.1.2 要對 typescript 進行解析和編譯還需要配置 tsconfig.json,該文件中指定了用來編譯這個項目的根文件和編譯選項,在項目根目錄,使用 tsc --init 命令快速生成 tsconfig.json 文件(前提全局安裝 typescript)
npm i typescript -g tsc --init初始化 tsconfig 完成之后,根目錄自動生成 tsconfig.json 文件,需要對其進行簡單的配置,以適用于 ts 項目,其中具體含義可以參考tsconfig.json官網
4. 組織項目打包構建 SCRIPTS 目錄結構
1) 根目錄創建項目打包構建 scripts 腳本文件目錄,里面放置的是有關于項目打包構建需要的文件: ??生成 rollup 配置項函數核心代碼:
const moduleName = camelCase(name) // 當format為iife和umd時必須提供,將作為全局變量掛在window下:window.moduleName=... const banner = generateBanner() // 包說明文案 // 生成rollup配置文件函數 const generateConfigs = (options) => { const { input, outputFile } = options console.log(chalk.greenBright(`獲取打包入口:${input}`)) const result = [] const pushPlugins = ({ format, plugins, ext }) => { result.push({ input, // 打包入口文件 external: [], // 如果打包出來的文件有項目依賴,可以在這里配置是否將項目依賴一起打到包里面還是作為外部依賴 // 打包出口文件 output: { file: `${outputFile}${ext}`, // 出口文件名稱 sourcemap: true, // // 是否生成sourcemap format, // 打包的模塊化格式 name: moduleName, // 當format為iife和umd時必須提供,將作為全局變量掛在window下:window.moduleName=... exports: 'named' /** Disable warning for default imports */, banner, // 打包出來的文件在最頂部的說明文案 globals: {} // 如果external設置了打包忽略的項目依賴,在此配置,項目依賴的全局變量 }, plugins // rollup插件 }) } buildType.forEach(({ format, ext }) => { let plugins = [...defaultPlugins] // 生產環境加入包分析以及代碼壓縮 plugins = [ ...plugins, visualizer({ gzipSize: true, brotliSize: true }), terser() ] pushPlugins({ format, plugins, ext }) }) return result }2) rollup 在打包構建的過程中需要進行 babel 的轉譯,需要在根目錄添加.babelrc 文件告知 babel:
{ "presets": [ [ "@babel/preset-env" ] ], "plugins": ["@babel/plugin-transform-runtime"] }
3) 此時距離打包構建工具庫只差一步之遙,配置打包腳本命令,在 package.json 中配置命令:
"scripts": { "build": "rimraf lib && rollup -c ./scripts/rollup.config.js" // rollup打包 },
4) 執行 yarn build,根目錄會構建出一個 lib 文件夾,里面有打包構建的文件,還多了一個 stats.html,這個是可視化并分析 Rollup bundle,用來查看工具模塊占用空間:?
?架構搭建優化 項目搭建到這里,不知機智的你能否發現問題: 1) 只要添加了一個工具,就要在入口文件導出需要打包構建的工具,在多人開發提交代碼的時候將引來沖突的產生:? 2) 使用工具庫的時候,按需引用的顆粒度太細了,不能滿足一些要求顆粒度粗的朋友,比如: ??我想使用該包里面 date 相關工具,要這樣嗎?
import { dateA, dateB, dateC } from "utils-demo"
能不能這樣?
import { date } from "utils-demo" date.dateA() date.dateB() date.dateC()?在一些使用 script 腳本引入的場景下,就僅僅需要 date 相關的工具,要這樣嗎?
能不能這樣?
這樣僅僅使用 date 里面的工具,就沒有必要將所有的工具都引入了 解決方案: 1) 針對第一個代碼沖突的問題,可以根據 src > modules 下目錄結構自動生成入口文件 index.ts ?自動構建入口文件核心代碼:
const fs = require('fs') // node fs模塊 const chalk = require('chalk') // 自定義輸出樣式 const { resolveFile, getEntries } = require('./utils') let srcIndexContent = ` // tips:此文件是自動生成的,無需手動添加 ` getEntries(resolveFile('src/modules/*')).forEach(({ baseName, entry }) => { let moduleIndexContent = ` // tips:此文件是自動生成的,無需手動添加 ` try { // 判斷是否文件夾 const stats = fs.statSync(entry) if (stats.isDirectory()) { getEntries(`${entry}/*.ts`).forEach(({ baseName }) => { baseName = baseName.split('.')[0] if (baseName.indexOf('index') === -1) { moduleIndexContent += ` export * from './${baseName}' ` } }) fs.writeFileSync(`${entry}/index.ts`, moduleIndexContent, 'utf-8') srcIndexContent += ` export * from './modules/${baseName}' export * as ${baseName} from './modules/${baseName}' ` } else { srcIndexContent += ` export * from './modules/${baseName.split('.')[0]}' ` } } catch (e) { console.error(e) } }) fs.writeFileSync(resolveFile('src/index.ts'), srcIndexContent, 'utf-8')2) 針對顆粒度的問題,可以將 modules 下各種類型工具文件夾下面也自動生成入口文件,除了全部導出,再追加 import * as 模塊類名稱 類型的導出? ??至此,基本上解決了工具庫打包的問題,但是架構中還缺少本地開發調試的環境,下面為大家介紹如何架構中添加本地開發調試的系統。 ?本地開發調試系統 首先要明確要加入本地開發調試系統的支持,需要做到以下: ???跨平臺(window 不支持 NODE_ENV=xxx)設置環境變量,根據環境配置不同的 rollup 配置項 ???引入本地開發需要的 html 靜態服務器環境,并能做到熱更新 1) 跨平臺設置環境變量很簡單,使用 cross-env 指定 node 的環境
yarn add cross-env -D
2) 配置 package.json 命令
"scripts": { "entry": "node ./scripts/build-entry.js", "dev": "rimraf lib && yarn entry && cross-env NODE_ENV=development rollup -w -c ./scripts/rollup.config.js", // -w 表示監聽的工具模塊的修改 "build": "rimraf lib && yarn entry && cross-env NODE_ENV=production rollup -c ./scripts/rollup.config.js" },3) 根據最開始架構設計的模塊,在項目根目錄新建 debugger 文件夾,里面存放的是工具調試的 html 靜態頁面 4) 接下來就是配置 scripts > rollup.config.js ,將 NODE_ENV=development 環境加入 rollup 配置,修改生成 rollup 配置項函數核心代碼:
(isProd ? buildType : devType).forEach(({ format, ext }) => { let plugins = [...defaultPlugins] if (isProd) { // 生產環境加入包分析以及代碼壓縮 plugins = [...plugins, visualizer({ gzipSize: true, brotliSize: true }), terser()] } else { // 非生產環境加入熱更新和本地服務插件,方便本地debugger plugins = [...plugins, ...[ // 熱更新 rollUpLiveLoad({ watch: ['debugger', 'lib'], delay: 300 }), // 本地服務代理 rollupServe({ open: true, // resolveFile('')代理根目錄原因是為了在ts代碼里debugger時可以方便看到調試信息 contentBase: [resolveFile('debugger'), resolveFile('lib'), resolveFile('')] }) ]] } pushPlugins({ format, plugins, ext }) })5) 執行 yarn dev 之后瀏覽器會新打開窗口,輸入剛添加的工具鏈接,并且它是熱更新的:? ?工具庫文檔系統 一個完備的工具庫需要有一個文檔來展示開發的工具函數,它可能需要具備以下幾點支持: ???支持工具庫中方法的可視化預覽 ???支持修改工具的時候,具備熱更新機制 ?typedoc(TypeScript 項目的文檔生成器)能完美支持 typescript 開發工具庫的文檔生成器的支持,它的核心原理就是讀取源代碼,根據工具的注釋、ts 的類型規范等,自動生成文檔頁面 關于熱更新機制的支持,第一個自然想到?browser-sync(文檔系統熱更新) 由于文檔系統的預覽功能有很多插件組合來實現的,可以借助 gulp (基于流的自動化構建工具),typedoc 正好有對應的?gulp-typedocGulp?插件來執行 TypeDoc 工具插件? 構建完成后打開文檔系統,并且它是熱更新的,修改工具方法后自動更新文檔:? ??單元測試 為確保用戶使用的工具代碼的安全性、正確性以及可靠性,工具庫的單元測試必不可少。單元測試選用的是 Facebook 出品的 Jest 測試框架,它對于 TypeScript 有很好的支持。
1. 環境搭建
1) 首先全局安裝 jest 使用 init 來初始化 jest 配置項
npm jest -g jest --init 下面是本人設置的jest的配置項 Would you like to use Jest when running "test" script in "package.json"? … yes Would you like to use Typescript for the configuration file? … yes Choose the test environment that will be used for testing ? jsdom (browser-like) Do you want Jest to add coverage reports? … yes Which provider should be used to instrument code for coverage? ? babel Automatically clear mock calls, instances and results before every test? … yes執行完之后根目錄會自動生成 jest.config.ts 文件,里面設置了單元測試的配置規則,package.json 里面也多了一個 script 指令 test。 2) 關于 jest.config.js 文件配置項具體含義可以查看官網,要想完成 jest 對于 TypeScript 的測試,還需要安裝一些依賴:
yarn add jest ts-jest @babel/preset-typescript @types/jest -D3) jest 還需要借助 .babelrc 去解析 TypeScript 文件,再進行測試,編輯 .babelrc 文件,添加依賴包 @babel/preset-typescript:
{ "presets": [ "@babel/preset-typescript", [ "@babel/preset-env" ] ], "plugins": ["@babel/plugin-transform-runtime"] }
2. 單元測試文件的編寫
1) 通過以上環節,jest 單元測試環境基本搭建完畢,接下來在__tests__下編寫測試用例 ??2) 執行 yarn test 可以看到關于 debounce 防抖工具函數的測試情況顯示在了控制臺: ???stmts 是語句覆蓋率(statement coverage):是不是每個語句都執行了? ???Branch 分支覆蓋率(branch coverage):是不是每個 if 代碼塊都執行了? ???Funcs 函數覆蓋率(function coverage):是不是每個函數都調用了? ???Lines 行覆蓋率(line coverage):是不是每一行都執行了??? ?3) 同時還會發現項目根目錄多了一個 coverage 文件夾,里面就是 jest 生成的測試報告:? ?3. 單元測試文件的編寫引發的思考 每次修改單元測試都要執行 yarn test 去查看測試結果,怎么解決? jest 提供了 watch 指令,只需要配置 scripts 腳本就可以做到,單元測試的熱更新。
"scripts": { "test": "jest --watchAll" },以后會寫很多工具的測試用例,每次 test 都將所有工具都進行了測試,能否只測試自己寫的工具? jest 也提供了測試單個文件的方法,這樣 jest 只會對防抖函數進行測試(前提全局安裝了 jest)。
jest debounce.test.ts --watch
工具庫包的發布
至此工具庫距離開發者使用僅一步之遙,就是發布到 npm 上,發包前需要在 package.json 中聲明庫的一些入口,關鍵詞等信息。
"main": "lib/main.js", // 告知引用該包模塊化方式的默認文件路徑 "module": "lib/main.esm.js", // 告知引用該包模塊化方式的文件路徑 "types": "lib/types/index.d.ts", // 告知引用該包的類型聲明文件路徑 "sideEffects": false, // false 為了告訴 webpack 我這個 npm 包里的所有文件代碼都是沒有副作用的 "files": [ // 開發者引用該包后node_modules包里面的文件 "lib", "README.md" ], "keywords": [ "typescript", "utils-demo", "utils" ], "scripts": { "pub": "npm publish" },登陸npm,你會看到自己的 packages 里面有了剛剛發布的工具庫包:? ?寫在最后
以上就是作者整理的從 0 到 1 構建基于自身業務的前端工具庫的全過程,希望能給閱讀本文的開發人員帶來一些新的想法與嘗試。 在此基礎上已經成功在京東 npm 源發布了應用于京東汽車前端的工具庫@jdcar/car-utils,并在各個業務線及系統得到落地。 當然,架構優化之路也還遠未結束,比如:打包構建的速度、本地開發按需構建、工具庫腳手架化等,后續我們也會基于自身業務以及一些新技術,持續深入優化,在性能上進一步提升,在功能上進一步豐富。本文或存在一些淺顯不足之處,也歡迎大家評論指點。
-
框架
+關注
關注
0文章
399瀏覽量
17434 -
代碼
+關注
關注
30文章
4748瀏覽量
68356 -
開源工具
+關注
關注
0文章
27瀏覽量
4444
原文標題:從0到1構建基于自身業務的前端工具庫
文章出處:【微信號:OSC開源社區,微信公眾號:OSC開源社區】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論