SeaJS 和 Browserify 的模塊化方案有哪些區別?
共同點是都基於Node所採用的包模式,使用require、exports、module這三個關鍵字組織模塊。不同點:
- 依賴分析時機不一樣:SeaJS是在客戶端運行時解析依賴,可以說是「運行時」解析;而Browserify是在服務端就依賴分析打包成單個文件,可以說是「預編譯」;
- 因此,SeaJS一定程度上可以控制模塊載入的粒度,而Browserify一刀切;
- Browserify可以基於依賴版本打包npm中的依賴包。
補充:
現在我自己寫了一個模塊載入器:island205/browserify-loader · GitHub ,使用這個載入器既可以像 Browserify 那樣編寫 Node.js 式的模塊代碼,也可以像 SeaJS 一樣分開載入。相比 browserify 和 webpack,SeaJS、RequireJS 這些模塊載入器都是各色 MD(Module Definition)風起雲湧時期的產物。彼時時興 CommonJS,針對瀏覽器又有了 RequireJS 代表的 AMD(Asynchronous Module Definition) ,SeaJS 則借鑒 RequireJS 的 define,同時參考 Node.js 中 CommonJS 的行為模式,形成了自己的一套模塊載入與執行模式 CMD(Common Module Definition)。相關內容展開,不妨看看 淘寶前端 Kissy 框架的 KMD 標準和支付寶 seajs 的 CMD 標準有什麼區別? - 陳養劍的回答
相比 browserify 和 webpack 所採用的預編譯、打包模式,彼時流行的模塊載入器都有幾個共同的問題:- 項目稍微大一點之後,載入的前端模塊就變得特別多,上線時需要用配套工具做合併、打包
- 沒有或者自建有一套模塊管理方案,比如 SeaJS 自己搞的 SPM
- 彼此打架,SeaJS 不兼容 RequireJS 的,反之亦然
- 無法像 NPM 那樣嵌套依賴,也不能直接支持多版本並存
然後說這兩個打包工具,browserify 是發自 Node.js 的 CommonJS 規範的,從一開始就立足於 Node.js 風格的 CommonJS 模塊編寫規範,然後配合 NPM 以及自己在 package.json 里擴充的 browser 欄位,形成一套基於 node_modules 目錄的代碼管理方案。它比較理想化,因此沒有直接兼容那些採用 AMD 的模塊,或許通過圍繞 browerify 生態圈建設的各色 transformer,可以支持 AMD 模塊。
webpack 則是大一統的方案,它同時支持 Node.js 的 CommonJS 與 RequireJS 的 AMD,非常不挑食。並且針對預編譯、打包方案中常見的問題,都有所優化:- 支持熱重載,提高代碼量比較多的項目的編譯、打包速度
- 支持全局模塊,例如 jQuery,方便在代碼中盡情使用 $
- 提供 Express 中間件,自動打包
其他回答中還提到 SystemJS 與正在制定中的 Loader 規範,我也順便發表一下看法。我不認為這是個正確的方向,模塊預編譯這種事情放到客戶端實在是太不合適了。非同步 `import` 引入的 Promise 也會讓前端代碼變得混亂,何況 async/await 還要寫時日。
然後回到 SeaJS 與 Browserify 對比這個問題。純粹的 SeaJS/SPM 方案已經過時了,相比預編譯方案的四個缺點也已經列出;而 Browserify 自己又有些 Unix 主義,真正把它用好需要結合 Browserify 生態圈裡的各色 transformer,不過我很喜歡 package.json 里的 browser 欄位。
最後是一點私貨。以上各個方案里,最值得學習使用的自然是 webpack,不過我認為開發時不需要把所有代碼都編譯打包到一塊,完全依靠 Source Map 調試也是件很痛苦的事情,尤其是還有老瀏覽器要兼容的時候。所以我效仿 SeaJS 當年所做的,取了 SeaJS 和 browerify 的長處,開發了 erzu/oceanify · GitHub ,提供一款支持 NPM 風格的嵌套依賴,又能在服務端預編譯模塊的 Koa/Express 中間件。通過這個工具,模塊仍然是獨立載入的,但是依賴可以直接通過 NPM 安裝,而且會在首次載入時預編譯,之後就不會管它。上線的時候,又可以預編譯打包所有模塊,並生成 Source Map,方便查看、調試源碼。
詳細的特性還可以列出很多,文檔都還在重寫中,就不在這裡多賣了。贊同寸志和吳俊宏。
我覺得最重要的一點,Browserify的出現,不僅僅是讓NPM(或者說之前適用於Node.js)上的代碼可以給瀏覽器端使用。更重要的意義,是Browserify代碼組織更符合CommonJS規範,讓瀏覽器前端代碼也可以通過預編譯實現CommonJS規範。反觀無論是Seajs還是RequireJS,本質上也是希望實現CommonJS規範。但是由於瀏覽器能力限制,這也沒有辦法,因為他們都是在「運行時」解析,因此必須都要在邏輯代碼外包裹一層。由此造就出了CMD、AMD規範。
至於Browserify無法提供條件載入的問題,我想這個太好解決了。本問題中的解答中已經有人給出了方案。最壞的環境,自己寫個小插件即可。我想這個不是什麼大問題。
另外,值得注意的是ES6已經提供了模塊化的解決方案。如果你想現在就使用ES6的方案,可以參考該項目esnext/es6-module-transpiler · GitHub。通過該項目,你可以將符合ES6(暫時規範)的代碼,編譯成符合AMD或者CommonJS規範的代碼。代碼兼容:似乎大家都不care兼容性測試用例,客戶端使用的話,seajs不需要特別處理時的兼容性覆蓋比browserify多一些(不考慮國情,或者產品受眾給力,可以不考慮這點)。
代碼發布:不論browserify還是seajs,都會使用combine後的代碼發布,從這一點上看,客戶端使用成本差異不大。
開發調試:使用語法差異(可以用工具抹平,這個不是啥大問題);- 打包合併,brow得打包了才能玩,seajs可以打散調用和調試,靈活的多,利於糾錯和發現問題;
- 打包工具,brow略勝seajs,spm是針對特定業務的,如果個人使用seajs,需要自己搞一個簡單的分析打包工具;
- 客戶端條件載入,saejs略勝一籌,因為它本身就是載入器,提供了map,alias,甚至在定義的時候,可以使用js常規的條件判斷去做一些事情。(比如對良民使用jq2作為base,對刁民使用jq1作為base,或者不載入某些功能【如果換做brow,你需要打包好幾份腳本了】)
PS: 工作用KISSY(KMD),無聊玩耍時才用seajs(CMD)的利益不相關用戶(雖然KISSY的loader和seajs差不多)。
PPS: @題葉 有說到一個問題,關於兩者都無法解決的問題,我這邊使用遇到的有『模塊劃分粒度』,目前劃分模塊按照 基礎(base)-&>擴展帶導出函數(ext)-&>完整模塊(module)來玩,但是終歸不同的層級之間會有冗餘,但是吧一些模塊拆的更細之後,被更多的東西依賴之後,可維護性降低,複雜度會提高。還有一點,關於啥時候seajs更適合,參考開發調試第三點『靈活』,我覺得可以作為例子。
PPPS: @寸志@題葉,前後端代碼復用,或者代碼平滑遷移或許急不得。提問題的時候想先把語法差異貼在第一個回答里的, 等同學深入, @寸志 好快啊.
Browserify 是一個基於 Node 模塊化方案的瀏覽器端版本, 通過壓縮代碼實現
同類的方案很多, 大致是實現一遍 require 函數, 再把所有的代碼打包發送到瀏覽器端這樣做的目的無疑是為了兼容 Node 生態圈的大量工具和代碼, 方便 JS 跨平台個人傾向這個方案, 因為 Node 上的不少模塊直接可以直接用在瀏覽器端了
而且語法基本按照 Node 在服務端的方案, 調試就靠 SourceMap 了var foo = require("./foo");
var bar = require("../lib/bar");
var gamma = require("gamma");
var elem = document.getElementById("result");
var x = foo(100) + bar("baz");
elem.textContent = gamma(x);
SeaJS 在 Node 簡潔的模塊語法上增加了 define() 函數來實現更好的模塊化
也因為語法的差異, 自然沒有直接用 NPM 管理的方便, 但為瀏覽器做了更多語法不同為了區分, SeaJS 就需要定製的包管理工具 SPM, 自己搭建倉庫SeaJS 也有將代碼打包的機制, 但不像 Browserify 每次都打包去運行了...define(function(require, exports) {
// 獲取模塊 a 的介面
var a = require("./a");
// 調用模塊 a 的方法
a.doSomething();
});
define(function(require, exports, module) {
// 正確寫法
module.exports = {
foo: "bar",
doSomething: function() {};
};
});
或者兩者都無法解決的問題有哪些?
兩者在打包時都對js文件進行解析,構建AST。browserify查找語法樹種global域下的require和module變數,對其調用進行變形替換。這是靜態分析,像module[『exp『+』ort』] = {} , require("path"+"to"+『file")目前還無法被正確替換。較明顯的差別:browserify不需要 define(function(require, exports, module) {...}) 。代碼更符合CommonJS模塊化規範,可以和nodejs共同require同一個文件,以及node_modules里的庫和process這些內建庫,但無法直接使用C++addon。seajs使用uglify構建AST;browserify使用Esprima, 生成的AST兼容Mozilla規範。在搭配了live-reload的開發環境,代碼一變動便能自動刷新瀏覽器,uglify耗時較長,所以如果使用seajs,一般不需要打包。開發流程除了選用grunt等主流js框架,推薦Mimosa。
相同點是:都是歷史階段性的產物,現在都不推薦大家使用。
不同點是:Sea.js 的作者充分理解 Browserify 的優劣,但 Browserify 的作者始終不明白 Sea.js,唉。用 聖經.js
browserify中發現的一個問題:jquery和jquery-form-validator一起用browserify導入編譯後,會出問題(瀏覽器總是報錯:$.validate is not a function)。即使用browserify-shim來配置package.json文件後,還是有問題的。而且,編譯後文件太大,像atom之類的編輯器打開編譯後的文件,經常出現卡頓。儘管browserify的思路是好的。可惜調試上並沒有提供很好的解決方案,兼容不同的jquery插件也仍然需要用戶自己去手動配置package.json。略顯麻煩。
一個原則在客戶端運行的程序越少越好
推薦閱讀:
※Web 前端 IDE 用的都是什麼啊?
※為什麼中國開源界喜歡「自主研發」輪子?
※作為一個伺服器,node.js 是性能最高的嗎?
※如何看待 Azer Ko?ulu 刪除了自己的所有 npm 庫?
※Node.js 適合用來做 web 開發嗎?
TAG:前端開發 | JavaScript | Nodejs | SeaJS | Browserify |