摘要:歷史上由于是作為的替代品出現,當時要解決的問題是處理瀏覽器兼容問題,打包或,做一些公共資源替換,雪碧圖等,最后可以順帶合并到一個文件,但模塊化功能遠遠比弱,基本上只能合并,但不能理解模塊概念。
1 引言
說到前端編譯方案,也就是如何打包項目,如何編譯組件,可選方案有很多,比如:
通過 webpack / parcel / gulp 構建項目。
通過 parcel / gulp / babel 構建組件。
如果你喜歡零配置的 parcel,那么項目和組件都可以拿它來編譯。
如果你業務比較復雜,需要使用 webpack 做深度定制,那么常見組合是:項目 - webpack,組件 - gulp。
但項目與組件的編譯存在異同點,不同構建工具支持的生態也存在異同點。
webpack parcel gulp 生態的區別babel 一般不會解析模塊,也就是一般僅做代碼預處理,而不會改變文件結構,也對 require、import 語句不敏感。
webpack / parcel 主要就是解決模塊化打包問題,因為瀏覽器還不支持(現在部分支持 type="module")。
gulp 理論上可以將 babel、webpack、parcel 作為插件,但這是后來的事。歷史上由于 gulp 是作為 grunt 的替代品出現,當時要解決的問題是處理瀏覽器兼容問題,打包 scss 或 less,做一些公共資源替換,雪碧圖等,最后可以順帶合并到一個文件,但模塊化功能遠遠比 webpack 弱,基本上只能合并,但不能 “理解模塊概念”。
項目構建與組件構建的區別項目構建的目的主要在于發布 CDN,所以大家一般不在乎構建腳本的通用性。換句話說,無論項目使用了怎樣的構建方式,怎樣理解 import 語句,甚至寫出 require.context 等自定義語法,只要最終編譯出符合瀏覽器規范的代碼(考慮到兼容性)就足夠。
組件構建的目的主要在于發布 NPM,除了 ESNext 規范會使用 Babel 編譯成 ES3,大部分代碼寫的很收斂,甚至對 SASS 的使用都要與 Typescript 插件一起組合成復雜的 Gulp Task。
所以往往大家會對項目采取復雜的構建約束策略,而對組件的編譯采取相對簡單的辦法,確保發布代碼的通用性。
所以在大部分項目使用 webpack 支持 worker-loader 時,編寫組件時發現這段代碼不靈了。或者至少你得付出一些代價,因為組件的調試依然可以利用 webpack-dev-server,這時可以加上 worker-loader,但由于 gulp 沒有靠譜的 worker 插件,你的組件可能需要將 Worker 引用部分原樣輸出,希望由引用它的項目做掉對 worker-loader 的支持。
其實這種心態是很危險的,不僅導致了組件不通用,甚至引發了各構建工具的 Tree Shaking 優化。原因就是構建組件的代碼太原始,冗余的代碼沒有刪除,甚至直接引用的 SASS 代碼仍然保留,更危險的是帶上了一些特殊 webpack loader 才支持的語法。
之所以說 Antd 是一個擁有優秀基因的前端組件庫,是因為他遵循了前端組件最基本的代碼素養:
編譯后的代碼全部符合基本 JS 規范,換個角度來說,使用 webpack 內置基本 js loader 就能完全解析。
將 css 代碼抽離出來,這樣不會強制項目對 node_modules 的代碼應用 css-loader。
所以一個 靠譜的組件庫 的產出文件,應該符合基本 ES 模塊化規范,且不包括任何特殊語法。
但是這引發了一個新的問題:組件開發體驗比項目差很多。
比如組件想使用雪碧圖自動優化、想使用 worker-loader 方便快捷的調用多線程,想用自己的 css modules,甚至想把項目里一堆 PostCSS 快捷語法搬過來時怎么辦?難道組件開發就不能獲得與項目開發一樣的體驗嗎?
要解決這個問題,筆者介紹一種基于 webpack 的通用構建方案,讓本地調試、CDN 打包、ES6 -> ES3 轉換 都使用統一套配置代碼,同一套 loader。
2 精讀核心思想只有一句話:利用 webpack-node-externals 忽略 Webpack 對指向 node_modules 的 require 或 import 語句:
進行項目/組件調試時,開啟 development 模式。
進行項目編譯時,開啟 production 模式。
進行組件編譯時,開啟 production 模式,且利用 webpack-node-externals 插件忽略 node_modules。
可以想像,根據第三條,如果所有組件都按照這個模式輸出代碼,那么 webpack 對 node_modules 編譯時,只需要將所有 require 代碼進行合并,不需要執行任何 loader,也不需要壓縮,不需要 TreeShaking,因為這些在組件代碼編譯時全部已經做好了,這種構建效率幾乎達到最大。
實際案例我們拿支持 typescript、sass、css-modules、worker-loader 的場景作為案例。
我們創建三個文件 entry.tsx entry.worker.ts 與 entry.scss:
entry.scss:
.container { border: 1px solid #ccc; } .primary { color: blue; &:hover { color: green; } }
entry.worker.ts:
import hello from "hello"; const ctx: Worker = self as any; ctx.onmessage = event => { ctx.postMessage(hello()); }; export default null as any;
entry.tsx:
import * as React from "react"; import styles from "./entry.scss"; import * as MyWorker from "./parser.worker"; const worker = new MyWorker(); export default () => ();
在上面三個文件中,我們分別利用了 Typescript 編譯、SCSS 編譯、css-modules 解析、worker-loader 解析(利用 webpack 自動生成字符串代碼并利用 Blob URL 方式載入,這樣就不需要創建新文件也可以用 worker 了,也不會存在跨域問題)。
為了支持這幾個特性對如上代碼做調試、項目發布、組件發布,我們分別看下這三個場景該如何配置編譯腳本。
本地調試本地調試是不用區分組件與項目的。因為無論何種情況,都需要進行基本的項目編譯,載入所有自定義 loader 并打成一個 bundle 包。
此時我們只要維護一份 webpack 配置即可:
const webpackConfig = { mode: "development", module: { rules: [ { test: /.worker.tsx?$/, use: { loader: "worker-loader", options: { inline: true } }, include: path.join(projectRootPath, "src") }, { test: /.tsx?$/, use: [ [ "babel-loader", { plugins: [ [ "babel-plugin-react-css-modules", { filetypes: { ".scss": { syntax: "postcss-scss" } } } ] ] } ], "ts-loader" ], include: path.join(projectRootPath, "src") }, { test: /.scss$/, use: [ "style-loader", [ "css-loader", { importLoaders: 1, modules: true } ], "sass-loader" ], include: path.join(projectRootPath, "src") } ] } }; export default webpackConfig;
利用這個配置加上 webpack-dev-server 即可完成組件與項目的本地調試。
項目發布項目發布時,需要將所有代碼打入到一個 bundle 包,此時只需使用 webpack-cli 即可,對配置做如下修改:
export default { ...webpackConfig, mode: "production" };組件發布
組件發布時,依然使用 webpack-cli 構建,但利用 webpack-node-externals 忽略對 node_modules 的解析。
import * as nodeExternals from "webpack-node-externals"; export default { ...webpackConfig, mode: "production", externals: [nodeExternals()] };
此時編譯的組件代碼,包含了 Typescript 編譯、SCSS 編譯、css-modules 解析、worker-loader 解析,但所有 node_modules 代碼都保持原樣,比如下面的代碼:
做了代碼去重、按需加載、打包、壓縮,但因為保持了 require 原樣,因此大小只有源碼體積。
同時上述三個場景都在復用 webpack 一套代碼的基礎上,利用了 webpack 的生態,因此維護性和拓展性都很強。后續再加入新功能,再也不需要到處找 babel 或 gulp 的插件了!
3 總結本文從 webpack 為切入點,但其實還可以從 parcel 或 gulp 為切入點,實現前端項目、組件構建體系的統一。
不過從可定制性來看,webpack 插件生態更完善,所以筆者選擇了 webpack。
留下一個思考題:你的項目、組件是如何構建的呢?是用了一套代碼,還是兩套呢?
討論地址是:精讀《如何編譯前端項目與組件》 · Issue #125 · dt-fe/weekly
如果你想參與討論,請點擊這里,每周都有新的主題,周末或周一發布。前端精讀 - 幫你篩選靠譜的內容。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/101270.html
摘要:引言本周精讀的文章是。精讀總的來說,雖然拆分子倉庫拆分子包是進行項目隔離的天然方案,但當倉庫內容出現關聯時,沒有任何一種調試方式比源碼放在一起更高效。前端精讀幫你篩選靠譜的內容。 1. 引言 本周精讀的文章是 The many Benefits of Using a Monorepo。 現在介紹 Monorepo 的文章很多,可以分為如下幾類:直接介紹 Lerna API 的;介紹如何...
摘要:精讀前端可以從多個角度理解,比如規范框架語言社區場景以及整條研發鏈路。同是前端未來展望,不同的文章側重的格局不同,兩個標題相同的文章內容可能大相徑庭。作為使用者,現在和未來的主流可能都是微軟系,畢竟微軟在操作系統方面人才儲備和經驗積累很多。 1. 引言 前端展望的文章越來越不好寫了,隨著前端發展的深入,需要擁有非常寬廣的視野與格局才能看清前端的未來。 筆者根據自身經驗,結合下面幾篇文章...
摘要:引言雖然現在與更流行,但使用它們會導致過分依賴濫用做唯一定位,違背了選擇器的初衷。本期精讀的文章是,帶你重新理解強大的選擇器。討論地址是精讀使用屬性選擇器如果你想參與討論,請點擊這里,每周都有新的主題,周末或周一發布。 1 引言 雖然現在 Css Module 與 Css-in-js 更流行,但使用它們會導致過分依賴 濫用 class 做唯一定位,違背了 Css 選擇器的初衷。 本期精...
摘要:經過連續幾期的介紹,手寫編譯器系列進入了智能提示模塊,前幾期從詞法到文法語法,再到構造語法樹,錯誤提示等等,都是為智能提示做準備。 1 引言 詞法、語法、語義分析概念都屬于編譯原理的前端領域,而這次的目的是做 具備完善語法提示的 SQL 編輯器,只需用到編譯原理的前端部分。 經過連續幾期的介紹,《手寫 SQL 編譯器》系列進入了 智能提示 模塊,前幾期從 詞法到文法、語法,再到構造語法...
閱讀 3344·2021-11-22 15:22
閱讀 2867·2021-10-12 10:12
閱讀 2162·2021-08-21 14:10
閱讀 3831·2021-08-19 11:13
閱讀 2849·2019-08-30 15:43
閱讀 3230·2019-08-29 16:52
閱讀 446·2019-08-29 16:41
閱讀 1438·2019-08-29 12:53