用工具沉澱技術積累

大部分團隊都或多或少在實際的項目中有技術的積累,這其中包括基礎技術能力的積累,也包括了與產品、項目緊密結合的最佳實踐的積累。

在不同的團隊中,有不同的沉澱積累的方式,有文檔,有串講,更有口口相傳。而在我們的團隊,我們選擇將知識沉澱成工具,以最快捷的方式傳播最佳實踐。而最終成型的,就是我們的內部工具ee-fe-tools

背景

回顧團隊內的產品,大約8個產品,其中除了1個有著祖傳jQuery實現的部分,另一個有著祖傳Angular1實現的部分外,大部分產品都是一致的React + Webpack [+ Redux]的形式,團隊的成員也有著相對一致的技術棧知識,這是我們產生統一工具的基本起點。

在統一工具之前,我們發現,無論多麼熟練的工程師,在上述的技術選型之個,額外加上antd等一堆東西,從0開始啟動一個新的系統也不得不花上2-3天的時間,這其中包含了大量的配置型工作,包括NPM基本配置、Lint工具、Git Hook、Webpack等等。而如果有一個統一的工具可以協助這些,則能非常有效地將新系統的啟動時間降到小時級別。

Lint

Lint是任何項目中非常重要的一環,因此我們的工具提供了et lint的功能。

這一功能是對eslint及stylelint的封裝,內置了430+條規則來檢測所有的JavaScript和LESS文件。在進行封裝後,所有的eslint-plugin-*babel-eslintstylelint-config-*等等都不再被項目直接依賴,只需要一個簡單的et lint命令,就能看到:

一天好心情(個鬼)

Lint部分的實現相對簡單,其中包括的重點是:

  • 配置好合理的Cache位置,eslint默認在當前目錄下放一個.eslintcache文件,這就會需要使用方來改動.gitignore以避免緩存文件被上傳到倉庫中。因此我們的工具將緩存目錄修改到了node_modules/.cache/.eslintcache中。
  • 兩個工具生成的結果的對象結構略有不同,為了更好地顯示在命令行中,編寫了一些邏輯將格式進行統一。最終使用eslint-formatter-pretty來進行格式化,這個格式很友好地在文件名後添加了行號,如index.js:9:7,直接點擊能讓vscode等編輯器快速定位到具體的行上。
  • stylelint-webpack-plugin存在一個BUG,當前無法在同時開啟emitErrors和failOnError的情況下看到錯誤信息。

除此之外,在開發的過程中,我們也協助stylelint修復了一個BUG,同時為stylelint-webpack-plugin的BUG提供了一些修復思路。

當然Lint部分還--changed--staged參數來控制範圍,對於包含著大量的難以修復規範問題的祖傳代碼的項目,配合husky也能取得較好的效果。考慮到有Partially Staged這種情況存在,我們沒有推薦在Git Hook中使用--fixgit add指令自動修復問題。

我們將我們的Lint配置開放了出來:

  • @ecomfe/eslint-config
  • @ecomfe/stylelint-config

構建

既然社區都已經有了Webpack工程師這個神奇的稱號,其配置在我們的項目中也是一項艱巨的挑戰。

大多數的團隊會使用複製粘貼,或者直接提供一個基礎配置的NPM包的形式來加速Webpack的配置過程,鮮有使用工具的形式來封裝的情況。UmiJS是一個可行的選擇,但構建的配置事實上代表著一種開發的約定,而我們的項目在長期的開發過程中,有自己一套固有的開發形式與約定,對此進行遷移,或fork了UmiJS進行定製,都是不小的成本。考慮到我個人對Webpack還算熟悉(被逼的),自己重新進行一套封裝反而是比較直接的選擇。

在構建上的工作量與Lint不可同日而語,簡單來說,我們實現了:

  • 將複雜的Webpack配置變成一個簡單的settings.js文件,使用約10個屬性來區別不同項目的特殊性。
  • 為了應對Angular1或者jQuery這樣的場景,允許項目自身再額外增加一些配置。
  • 基於統一的技術棧,封裝了css-modulespostcss等12個loader,importadd-react-displaynamereact-requirereact-transformreact-css-modules等9個babel plugin。
  • 基於對文件名的約定,實現了LESS變數的自動導入(src/styles/*.var.less),區分全局與CSS Modules的樣式(*.global.less),區分Web Worker與普通模塊(*.worker.js)等。
  • process.env.XXX及其它一些關鍵內容統一到了DefinePlugin中。
  • 為CSS Modules提供一個更漂亮的類名的模板(是的就是這麼蛋疼,為了好看不異一切代價),且同時在css-loaderbabel-plugin-react-css-modules中生效。
  • 為項目提供了基於Feature Matrix的構建功能。
  • 提供默認的splitChunks配置,項目在無配置的情況下可默認拆分為infrastructureuichartvendorsapp等多個Chunk,對於我們內部且相對重型的系統而言,可以有效地利用HTTP的鏈接數,並且使得前3個chunk的hash更為穩定,減少緩存失效的影響。
  • 使用webpack-bundle-analyzer與unused-files-webpack-plugin對構建結果進行分析。
  • 支持第三方包類型的項目構建,使得工具也同時用於我們自己的UI庫等的構建工作。
  • 提供versiontimemodetarget等一系列構建相關的全局變數,以便在採集數據、排查問題時使用。
  • 生成默認的HTML文件模板,除基本結構外,包含了構建的版本號等關鍵信息,以供線上排查問題。

默認的Chunk拆分讓系統能第一時間享受到一個體積平衡、充分利用鏈接數據的配置:

看看這曼妙身材(個鬼)

為了隱藏起所有的這些配置,我們將各種依賴從項目中移到了我們的工具里,這導致在實際構建過程中,普通的寫法如:

// babel.config.js{ presets: [env]}

是無效的,這會導致從項目的node_modules來查找,而實際上babel-preset-env的真正位置是node_modules/@baidu/ee-fe-tools/node_modules/babel-preset-env

因此,在整個配置中,大量使用了require.resolve來確定實際的路徑。唯一的遺憾是babel-plugin-react-css-modules官方不支持其運行時的輔助函數使用上述形式,也就是說如果一個項目使用styleName,則依舊必須安裝這個插件,非常的醜陋。

同時babel-plugin-react-css-modules還帶來了另一個問題,在此摘錄代碼進行說明:

// 由於構建等是通過`ee-fe-tools`包進行的,因此在這個過程中所有`require.resolve`調用都會在這個包下開始查找,// 而`babel-plugin-react-css-modules`是會調用`require.resolve`來分析相應的樣式表依賴,這會導致找不到文件//// ```// Cannot find module reset-css/reset.less// ```//// 為了讓整個`require.resolve`過程從項目(不是這個包)的`node_modules`下開始找,需要`module-resolver`這個插件,// 但是這個插件如果應用於所有的模塊,則會影響其它插件的工作(比如`babel-plugin-import`完全失效),// 因此配置該插件僅對**非相對路徑的樣式文件**生效[ require.resolve(babel-plugin-module-resolver), { resolvePath(sourcePath) { if (sourcePath.startsWith(.)) { return null; } const extension = path.extname(sourcePath); if (extension === .less || extension === .css) { // 使用與webpack的`resolve.modules`一樣的配置 const paths = [path.join(cwd, src), path.join(cwd, node_modules)]; return require.resolve(sourcePath, {paths}); } return null; } }]

這致使我們使用了Node 8.9.0以後才有的一個API(require.resolvepaths選項),好在作為公司的工程效率部門,我們對Node構建的環境有絕對的掌控權,這不成問題。

而以上這些所產生的最終結果,是項目只要寫一個settings.js,然後運行et build,在最少配置3-4個選項,最多也就配置10個選項的情況下,就可以完成整個項目的構建,還直接享受了各種優化的效果,在不同項目中都得到了可觀的效果:

開發調試

在有了構建的基礎後,開發調試並不是一個難題,它僅僅是對webpack-dev-server的一個封裝。在這一環節上,我們提供了:

  • 與構建不同的配置,禁用掉壓縮、拆Chunkt優化,調整Source Maps配置,提升構建速度。
  • 提供分級的HMR,依據項目的新舊以及技術實施的嚴格程度,可以僅對樣式進行HMR,或連同組件一起形成全局的HMR。
  • 提供了對公司內部測試環境的直接登錄能力,在配置中聲明presetUsers,啟動調試伺服器後就會自動跳轉至一個頁面,選擇用戶就自動登錄。
  • 構建完成後自動打開瀏覽器頁面,且在重構建時不會自動打開(與devServer.open略有不同,我們是在構建成功後才實際打開)。

webpack-dev-server的封裝的麻煩之處在於,原本webpack-dev-server會自動給webpack配置的entry欄位注入自己的運行時,但由於現在是通過程序的形式調用,就沒有了這一功能。在經過一系列搜索後,我們發現可以這樣子來完成:

const devServer = require(webpack-dev-server);devServer.addDevServerEntrypoints(config, devServerConfig);

在進行了工具的封裝後,只需要et dev即可打開調試伺服器,等待瀏覽器打開新的標籤,隨後點擊一個預設的用戶,即可進入調試。

我們有嘗試在這個過程中增加DLL的功能,但並未帶來什麼提升(甚至拖了速度的後腿),因此在搞清楚具體的最優化DLL配置之前,當前並沒有內置DLL的能力。

效果總結

我們發現,在提供了基礎的工具後,我們的新項目啟動時間可以壓至2小時以內。

這2小時的工作主要包含了建庫、npm init、生成基礎的目錄結構、引入工具並配置scripts等。在後續我們進一步提供初始代碼庫的模板,後有望將時間進一步壓縮至半小時以內。

而對於未來,我們期待著工具可以提供更多的能力,比如:

  • 基本的腳手架代碼生成。
  • 在必要的情況下ExtractCSS的能力。
  • 自動上傳產出到CDN的功能。

考慮到工具統一的第一步已經走出,後續分散的維護和優化變為了集中對一個代碼庫的維護,我們可以想像得到一個非常遠大的前景。

以下是我們的工具的宣傳資料:

使用工具沉澱技術.pdf?

pan.baidu.com


推薦閱讀:

webpack:從入門到真實項目配置
webpack3之Scope Hoisting
webpack2.X、Vue學習以及將兩者相結合
??webpack 4 發布了!?

TAG:webpack | 前端開發 |