前言
在實(shí)際項目開發(fā)中無論 M 端、PC 端,或多或少都有一個 utils 文件目錄去管理項目中用到的一些常用的工具方法,比如:時間處理、價格處理、解析 url 參數(shù)、加載腳本等,其中很多是重復(fù)、基礎(chǔ)、或基于某種業(yè)務(wù)場景的工具,存在項目間冗余的痛點(diǎn)以及工具方法規(guī)范不統(tǒng)一的問題。
在實(shí)際開發(fā)過程中,經(jīng)常使用一些開源工具庫,如 lodash,以方便、快捷的進(jìn)行項目開發(fā)。但是當(dāng) npm 上沒有自己中意或符合自身業(yè)務(wù)的工具時,我們不得不自己動手,此時擁有自己的、基于業(yè)務(wù)的工具庫就顯得尤為重要。
我們所熟知的 Vue、React 等諸多知名前端框架,或公司提供的一些類庫,它們是如何開發(fā)、構(gòu)建、打包出來的,本文將帶領(lǐng)你了解到如何從 0 到 1 構(gòu)建基于自身業(yè)務(wù)的前端工具庫。
構(gòu)建工具庫主流方案
1. WEBPACK
webpack 提供了構(gòu)建和打包不同模塊化規(guī)則的庫,只是需要自己去搭建開發(fā)底層架構(gòu)。
vue-cli,基于 webpack , vue-cli 腳手架工具可以快速初始化一個 vue 應(yīng)用,它也可以初始化一個構(gòu)建庫。
2. ROLLUP
rollup 是一個專門針對 JavaScript 模塊打包器,可以將應(yīng)用或庫的小塊代碼編譯成更復(fù)雜的功能代碼。
Vue、React 等許多流行前端框架的構(gòu)建和打包都能看到 rollup 的身影。
為什么采用 ROLLUP 而不是 WEBPACK
webpack 主要職能是開發(fā)應(yīng)用,而 rollup 主要針對的就是 js 庫的開發(fā),如果你要開發(fā) js 庫,那 webpack 的繁瑣配置和打包后的文件體積就不太適用了,通過 webpack 打包構(gòu)建出來的源代碼增加了很多工具函數(shù)以外的模塊依賴代碼。
rollup 只是把業(yè)務(wù)代碼轉(zhuǎn)碼成目標(biāo) js ,小巧且輕便。rollup 對于代碼的 Tree-shaking 和 ES6 模塊有著算法優(yōu)勢上的支持,如果只想構(gòu)建一個簡單的庫,并且是基于 ES6 開發(fā)的,加上其簡潔的 API,rollup 得到更多開發(fā)者的青睞。
工具庫底層架構(gòu)設(shè)計
構(gòu)建工具庫底層架構(gòu)大概需要哪些功能的支持:?
架構(gòu)依賴需知
在對底層架構(gòu)設(shè)計的基礎(chǔ)上,首先需要把用到的依賴庫簡單熟悉一下:
rollup 全家桶
?? rollup(工具庫打包構(gòu)建核心包) ?? rollup-plugin-livereload(rollup 插件,熱更新,方便本地 debugger 開發(fā)) ?? rollup-plugin-serve(rollup 插件,本地服務(wù)代理,方便在本地 html 中調(diào)試工具) ?? rollup-plugin-terser(rollup 插件,代碼壓縮混淆) ?? rollup-plugin-visualizer(rollup 插件,可視化并分析 Rollup bundle,以查看模塊占用) ?? @rollup/plugin-babel(rollup 插件,rollup 的 babel 插件,ES6 轉(zhuǎn) ES5) ?? @rollup/plugin-commonjs(rollup 插件,用來將 CommonJS 模塊轉(zhuǎn)換為 ES6,這樣它們就可以包含在 Rollup 包中) ?? @rollup/plugin-json(rollup 插件,它將.json 文件轉(zhuǎn)換為 ES6 模塊) ?? @rollup/plugin-node-resolve(rollup 插件,它使用節(jié)點(diǎn)解析算法定位模塊,用于在節(jié)點(diǎn)模塊中使用第三方 node_modules 包) ?? @rollup/plugin-typescript(rollup 插件,對 typescript 的支持,將 typescript 進(jìn)行 tsc 轉(zhuǎn)為 js)
typescript 相關(guān)
?? typescript(使用 ts 開發(fā)工具庫) ?? tslib(TypeScript 的運(yùn)行庫,它包含了 TypeScript 所有的幫助函數(shù)) ?? @typescript-eslint/eslint-plugin(TypeScript 的 eslint 插件,約束 ts 書寫規(guī)范) ?? @typescript-eslint/parser(ESLint 解析器,它利用 TypeScript ESTree 來允許 ESLint 檢測 TypeScript 源代碼)
文檔相關(guān)
?? typedoc(TypeScript 項目的文檔生成器) ?? gulp(使用 gulp 構(gòu)建文檔系統(tǒng)) ?? gulp-typedoc(Gulp 插件來執(zhí)行 TypeDoc 工具) ?? browser-sync(文檔系統(tǒng)熱更新)
單元測試相關(guān)
?? jest(一款優(yōu)雅、簡潔的 JavaScript 測試框架) ?? @types/jest(Jest 的類型定義) ?? ts-jest(一個支持源映射的 Jest 轉(zhuǎn)換器,允許您使用 Jest 來測試用 TypeScript 編寫的項目) ?? @babel/preset-typescript(TypeScript 的 Babel 預(yù)設(shè))
其他依賴
?? eslint(代碼規(guī)范約束) ?? @babel/core(@rollup/plugin-babel 依賴的 babel 解析插件) ?? @babel/plugin-transform-runtime(babel 轉(zhuǎn)譯依賴) ?? @babel/preset-env(babel 轉(zhuǎn)譯依賴) ?? chalk(控制臺字符樣式) ?? rimraf(UNIX 命令 rm -rf 用于 node) ?? cross-env(跨平臺設(shè)置 node 環(huán)境變量)
底層架構(gòu)搭建
1. 初始化項目
新建一個文件夾 utils-demo,執(zhí)行 npm init,過程會詢問構(gòu)建項目的基本信息,按需填寫即可:
npm init
2. 組織工具庫業(yè)務(wù)開發(fā) SRC 目錄結(jié)構(gòu)
創(chuàng)建工具庫業(yè)務(wù)開發(fā) src 文件目錄,明確怎樣規(guī)劃工具庫包,里面放置的是工具庫開發(fā)需要的業(yè)務(wù)代碼:
3. 安裝項目依賴
要對 typescript 代碼進(jìn)行解析支持需要安裝對 ts 支持的依賴,以及對開發(fā)的工具的一些依賴包:
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這里遇到一個坑,關(guān)于最新 chalk5.0.0 不支持在 nodejs 中 require () 導(dǎo)入,所以鎖定包版本 chalk@^4.1.2 要對 typescript 進(jìn)行解析和編譯還需要配置 tsconfig.json,該文件中指定了用來編譯這個項目的根文件和編譯選項,在項目根目錄,使用 tsc --init 命令快速生成 tsconfig.json 文件(前提全局安裝 typescript)
npm i typescript -g tsc --init初始化 tsconfig 完成之后,根目錄自動生成 tsconfig.json 文件,需要對其進(jìn)行簡單的配置,以適用于 ts 項目,其中具體含義可以參考tsconfig.json官網(wǎng)
4. 組織項目打包構(gòu)建 SCRIPTS 目錄結(jié)構(gòu)
1) 根目錄創(chuàng)建項目打包構(gòu)建 scripts 腳本文件目錄,里面放置的是有關(guān)于項目打包構(gòu)建需要的文件: ??生成 rollup 配置項函數(shù)核心代碼:
const moduleName = camelCase(name) // 當(dāng)format為iife和umd時必須提供,將作為全局變量掛在window下:window.moduleName=... const banner = generateBanner() // 包說明文案 // 生成rollup配置文件函數(shù) 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, // 當(dāng)format為iife和umd時必須提供,將作為全局變量掛在window下:window.moduleName=... exports: 'named' /** Disable warning for default imports */, banner, // 打包出來的文件在最頂部的說明文案 globals: {} // 如果external設(shè)置了打包忽略的項目依賴,在此配置,項目依賴的全局變量 }, plugins // rollup插件 }) } buildType.forEach(({ format, ext }) => { let plugins = [...defaultPlugins] // 生產(chǎn)環(huán)境加入包分析以及代碼壓縮 plugins = [ ...plugins, visualizer({ gzipSize: true, brotliSize: true }), terser() ] pushPlugins({ format, plugins, ext }) }) return result }2) rollup 在打包構(gòu)建的過程中需要進(jìn)行 babel 的轉(zhuǎn)譯,需要在根目錄添加.babelrc 文件告知 babel:
{ "presets": [ [ "@babel/preset-env" ] ], "plugins": ["@babel/plugin-transform-runtime"] }
3) 此時距離打包構(gòu)建工具庫只差一步之遙,配置打包腳本命令,在 package.json 中配置命令:
"scripts": { "build": "rimraf lib && rollup -c ./scripts/rollup.config.js" // rollup打包 },
4) 執(zhí)行 yarn build,根目錄會構(gòu)建出一個 lib 文件夾,里面有打包構(gòu)建的文件,還多了一個 stats.html,這個是可視化并分析 Rollup bundle,用來查看工具模塊占用空間:?
?架構(gòu)搭建優(yōu)化 項目搭建到這里,不知機(jī)智的你能否發(fā)現(xiàn)問題: 1) 只要添加了一個工具,就要在入口文件導(dǎo)出需要打包構(gòu)建的工具,在多人開發(fā)提交代碼的時候?qū)⒁齺頉_突的產(chǎn)生:?
2) 使用工具庫的時候,按需引用的顆粒度太細(xì)了,不能滿足一些要求顆粒度粗的朋友,比如: ??我想使用該包里面 date 相關(guān)工具,要這樣嗎?
import { dateA, dateB, dateC } from "utils-demo"
能不能這樣?
import { date } from "utils-demo" date.dateA() date.dateB() date.dateC()?在一些使用 script 腳本引入的場景下,就僅僅需要 date 相關(guān)的工具,要這樣嗎?
能不能這樣?
這樣僅僅使用 date 里面的工具,就沒有必要將所有的工具都引入了 解決方案: 1) 針對第一個代碼沖突的問題,可以根據(jù) src > modules 下目錄結(jié)構(gòu)自動生成入口文件 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 下各種類型工具文件夾下面也自動生成入口文件,除了全部導(dǎo)出,再追加 import * as 模塊類名稱 類型的導(dǎo)出?

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 表示監(jiān)聽的工具模塊的修改 "build": "rimraf lib && yarn entry && cross-env NODE_ENV=production rollup -c ./scripts/rollup.config.js" },3) 根據(jù)最開始架構(gòu)設(shè)計的模塊,在項目根目錄新建 debugger 文件夾,里面存放的是工具調(diào)試的 html 靜態(tài)頁面

(isProd ? buildType : devType).forEach(({ format, ext }) => { let plugins = [...defaultPlugins] if (isProd) { // 生產(chǎn)環(huán)境加入包分析以及代碼壓縮 plugins = [...plugins, visualizer({ gzipSize: true, brotliSize: true }), terser()] } else { // 非生產(chǎn)環(huán)境加入熱更新和本地服務(wù)插件,方便本地debugger plugins = [...plugins, ...[ // 熱更新 rollUpLiveLoad({ watch: ['debugger', 'lib'], delay: 300 }), // 本地服務(wù)代理 rollupServe({ open: true, // resolveFile('')代理根目錄原因是為了在ts代碼里debugger時可以方便看到調(diào)試信息 contentBase: [resolveFile('debugger'), resolveFile('lib'), resolveFile('')] }) ]] } pushPlugins({ format, plugins, ext }) })5) 執(zhí)行 yarn dev 之后瀏覽器會新打開窗口,輸入剛添加的工具鏈接,并且它是熱更新的:?



1. 環(huán)境搭建
1) 首先全局安裝 jest 使用 init 來初始化 jest 配置項
npm jest -g jest --init 下面是本人設(shè)置的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執(zhí)行完之后根目錄會自動生成 jest.config.ts 文件,里面設(shè)置了單元測試的配置規(guī)則,package.json 里面也多了一個 script 指令 test。 2) 關(guān)于 jest.config.js 文件配置項具體含義可以查看官網(wǎng),要想完成 jest 對于 TypeScript 的測試,還需要安裝一些依賴:
yarn add jest ts-jest @babel/preset-typescript @types/jest -D3) jest 還需要借助 .babelrc 去解析 TypeScript 文件,再進(jìn)行測試,編輯 .babelrc 文件,添加依賴包 @babel/preset-typescript:
{ "presets": [ "@babel/preset-typescript", [ "@babel/preset-env" ] ], "plugins": ["@babel/plugin-transform-runtime"] }
2. 單元測試文件的編寫
1) 通過以上環(huán)節(jié),jest 單元測試環(huán)境基本搭建完畢,接下來在__tests__下編寫測試用例 ??2) 執(zhí)行 yarn test 可以看到關(guān)于 debounce 防抖工具函數(shù)的測試情況顯示在了控制臺: ???stmts 是語句覆蓋率(statement coverage):是不是每個語句都執(zhí)行了? ???Branch 分支覆蓋率(branch coverage):是不是每個 if 代碼塊都執(zhí)行了? ???Funcs 函數(shù)覆蓋率(function coverage):是不是每個函數(shù)都調(diào)用了? ???Lines 行覆蓋率(line coverage):是不是每一行都執(zhí)行了???
?3) 同時還會發(fā)現(xiàn)項目根目錄多了一個 coverage 文件夾,里面就是 jest 生成的測試報告:?
?3. 單元測試文件的編寫引發(fā)的思考 每次修改單元測試都要執(zhí)行 yarn test 去查看測試結(jié)果,怎么解決? jest 提供了 watch 指令,只需要配置 scripts 腳本就可以做到,單元測試的熱更新。
"scripts": { "test": "jest --watchAll" },以后會寫很多工具的測試用例,每次 test 都將所有工具都進(jìn)行了測試,能否只測試自己寫的工具? jest 也提供了測試單個文件的方法,這樣 jest 只會對防抖函數(shù)進(jìn)行測試(前提全局安裝了 jest)。
jest debounce.test.ts --watch
工具庫包的發(fā)布
至此工具庫距離開發(fā)者使用僅一步之遙,就是發(fā)布到 npm 上,發(fā)包前需要在 package.json 中聲明庫的一些入口,關(guān)鍵詞等信息。
"main": "lib/main.js", // 告知引用該包模塊化方式的默認(rèn)文件路徑 "module": "lib/main.esm.js", // 告知引用該包模塊化方式的文件路徑 "types": "lib/types/index.d.ts", // 告知引用該包的類型聲明文件路徑 "sideEffects": false, // false 為了告訴 webpack 我這個 npm 包里的所有文件代碼都是沒有副作用的 "files": [ // 開發(fā)者引用該包后node_modules包里面的文件 "lib", "README.md" ], "keywords": [ "typescript", "utils-demo", "utils" ], "scripts": { "pub": "npm publish" },登陸npm,你會看到自己的 packages 里面有了剛剛發(fā)布的工具庫包:?

以上就是作者整理的從 0 到 1 構(gòu)建基于自身業(yè)務(wù)的前端工具庫的全過程,希望能給閱讀本文的開發(fā)人員帶來一些新的想法與嘗試。 在此基礎(chǔ)上已經(jīng)成功在京東 npm 源發(fā)布了應(yīng)用于京東汽車前端的工具庫@jdcar/car-utils,并在各個業(yè)務(wù)線及系統(tǒng)得到落地。 當(dāng)然,架構(gòu)優(yōu)化之路也還遠(yuǎn)未結(jié)束,比如:打包構(gòu)建的速度、本地開發(fā)按需構(gòu)建、工具庫腳手架化等,后續(xù)我們也會基于自身業(yè)務(wù)以及一些新技術(shù),持續(xù)深入優(yōu)化,在性能上進(jìn)一步提升,在功能上進(jìn)一步豐富。本文或存在一些淺顯不足之處,也歡迎大家評論指點(diǎn)。
-
框架
+關(guān)注
關(guān)注
0文章
404瀏覽量
17796 -
代碼
+關(guān)注
關(guān)注
30文章
4887瀏覽量
70266 -
開源工具
+關(guān)注
關(guān)注
0文章
27瀏覽量
4625
原文標(biāo)題:從0到1構(gòu)建基于自身業(yè)務(wù)的前端工具庫
文章出處:【微信號:OSC開源社區(qū),微信公眾號:OSC開源社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
中國能源互聯(lián)網(wǎng),從0到1的漸變
主流的微前端的實(shí)現(xiàn)庫原理及其用法
【OK210試用體驗(yàn)】構(gòu)建標(biāo)準(zhǔn)c庫newlib
MicropPython的學(xué)習(xí),如何從0到1?
MicropPython的學(xué)習(xí),如何從0到1?
如何將OpenCSD庫與Yocto上的perf工具集成?
筆記:四旋翼無人機(jī)從0到1的實(shí)現(xiàn),目錄鏈接

如何從0到1構(gòu)建一個穩(wěn)定、高性能的Redis集群?

從校招新星到前端技術(shù)專家的成長之路

從C端到B端:我的前端技術(shù)進(jìn)階之路

評論