摘要:因此,你還是需要各種各樣雜七雜八的工具來轉(zhuǎn)換你的代碼噢,我可去你媽的吧,這些東西都是干嘛的我就是想用個(gè)模塊化,我到底該用啥子本文正旨在列出幾種可用的在生產(chǎn)環(huán)境中放心使用模塊化的方法,希望能幫到諸位后來者這方面的中文資源實(shí)在是忒少了。
原文發(fā)表在我的博客上。最近搗鼓了一下 ES6 的模塊化,分享一些經(jīng)驗(yàn) :)
Python3 已經(jīng)發(fā)布了九年了,Python 社區(qū)卻還在用 Python 2.7;而 JavaScript 社區(qū)正好相反,大家都已經(jīng)開始把還沒有實(shí)現(xiàn)的語言特性用到生產(chǎn)環(huán)境中了 (′_ゝ `)
雖然這種奇妙情況的形成與 JavaScript 自身早期的設(shè)計(jì)缺陷以及瀏覽器平臺(tái)的特殊性質(zhì)都有關(guān)系,但也確實(shí)能夠體現(xiàn)出 JavaScript 社區(qū)的技術(shù)棧迭代是有多么屌快。如果你昏迷個(gè)一年半載再去看前端圈,可能社區(qū)的主流技術(shù)棧已經(jīng)變得它媽都不認(rèn)識(shí)了(如果你沒什么實(shí)感,可以看看《在 2016 年學(xué)習(xí) JavaScript 是一種怎樣的體驗(yàn)》這篇文章,你會(huì)感受到的,你會(huì)的)。
JavaScript 模塊化現(xiàn)狀隨著 JavaScript 越來越廣泛的應(yīng)用,朝著單頁應(yīng)用(SPA)方向發(fā)展的網(wǎng)頁與代碼量的愈發(fā)龐大,社區(qū)需要一種更好的代碼組織形式,這就是模塊化:將你的一大坨代碼分裝為多個(gè)不同的模塊。
但是在 ES6 標(biāo)準(zhǔn)出臺(tái)之前,由于標(biāo)準(zhǔn)的缺失(連 CSS 都有 @import,JavaScript 卻連個(gè)毛線都沒),這幾年里 JavaScript 社區(qū)里冒出了各種各樣的模塊化解決方案(群魔亂舞),懵到一種極致。主要的幾種模塊化方案舉例如下:
主要用于服務(wù)端,模塊同步加載(也因此不適合在瀏覽器中運(yùn)行,不過也有 Browserify 之類的轉(zhuǎn)換工具),Node.js 的模塊化實(shí)現(xiàn)就是基于 CommonJS 規(guī)范的,通常用法像這樣:
// index.js const {bullshit} = require("./bullshit"); console.log(bullshit()); // bullshit.js function someBullshit() { return "hafu hafu"; } modules.export = { bullshit: someBullshit };
而且 require() 是動(dòng)態(tài)加載模塊的,完全就是模塊中 modules.export 變量的傳送門,這也就意味著更好的靈活性(按條件加載模塊,參數(shù)可為表達(dá)式 etc.)。
AMD即異步模塊定義(Asynchronous Module Definition),不是那個(gè)日常翻身的農(nóng)企啦。
主要用于瀏覽器端,模塊異步加載(還是用的回調(diào)函數(shù)),可以給模塊注入依賴、動(dòng)態(tài)加載代碼塊等。具體實(shí)現(xiàn)有 RequireJS,代碼大概長這樣:
// index.js require(["bullshit"], words => { console.log(words.bullshit()); }); // bullshit.js define("bullshit", ["dep1", "dep2"], (dep1, dep2) => { function someBullshit() { return "hafu hafu"; } return { bullshit: someBullshit }; });
可惜不能在 Node.js 中直接使用,而且模塊定義與加載也比較冗長。
ES6 Module?在 ES6 模塊標(biāo)準(zhǔn)出來之前,主要的模塊化方案就是上述 CommonJS 和 AMD 兩種了,一種用于服務(wù)器,一種用于瀏覽器。其他的規(guī)范還有:
最古老的 IIFE(立即執(zhí)行函數(shù));
CMD(Common Module Definition,和 AMD 挺像的,可以參考:與 RequireJS 的異同);
UMD(Universal Module Definition,兼容 AMD 和 CommonJS 的語法糖規(guī)范);
等等,這里就按下不表。
ES6 的模塊化代碼大概長這樣:
// index.js import {bullshit} from "./bullshit"; console.log(bullshit()); // bullshit.js function someBullshit() { return "hafu hafu"; } export { someBullshit as bullshit };
那我們?yōu)樯稇?yīng)該使用 ES6 的模塊化規(guī)范呢?
這是 ECMAScript 官方標(biāo)準(zhǔn)(嗯);
語義化的語法,清晰明了,同時(shí)支持服務(wù)器端和瀏覽器;
靜態(tài) / 編譯時(shí)加載(與上面?zhèn)z規(guī)范的動(dòng)態(tài) / 運(yùn)行時(shí)加載不同),可以做靜態(tài)優(yōu)化(比如下面提到的 tree-shaking),加載效率高(不過相應(yīng)地靈活性也降低了,期待 import() 也成為規(guī)范);
輸出的是值的引用,可動(dòng)態(tài)修改;
嗯,你說的都對(duì),那我tm到底要怎樣才能在生產(chǎn)環(huán)境中用上 ES6 的模塊化特性呢?
很遺憾,你永遠(yuǎn)無法控制用戶的瀏覽器版本,可能要等上一萬年,你才能直接在生產(chǎn)環(huán)境中寫 ES6 而不用提心吊膽地?fù)?dān)心兼容性問題。因此,你還是需要各種各樣雜七雜八的工具來轉(zhuǎn)換你的代碼:Babel、Webpack、Browserify、Gulp、Rollup.js、System.js ……
噢,我可去你媽的吧,這些東西都tm是干嘛的?我就是想用個(gè)模塊化,我到底該用啥子?
本文正旨在列出幾種可用的在生產(chǎn)環(huán)境中放心使用 ES6 模塊化的方法,希望能幫到諸位后來者(這方面的中文資源實(shí)在是忒少了)。
問題分析想要開心地寫 ES6 的模塊化代碼,首先你需要一個(gè)轉(zhuǎn)譯器(Transpiler)來把你的 ES6 代碼轉(zhuǎn)換成大部分瀏覽器都支持的 ES5 代碼。這里我們就選用最多人用的 Babel(我不久之前才知道原來 Babel 就是巴別塔里的「巴別」……)。
用了 Babel 后,我們的 ES6 模塊化代碼會(huì)被轉(zhuǎn)換為 ES5 + CommonJS 模塊規(guī)范的代碼,這倒也沒什么,畢竟我們寫的還是 ES6 的模塊,至于編譯生成的結(jié)果,管它是個(gè)什么屌東西呢(笑)
所以我們需要另外一個(gè)打包工具來將我們的模塊依賴給打包成一個(gè) bundle 文件。目前來說,依賴打包應(yīng)該是最好的方法了。不然,你也可以等上一萬年,等你的用戶把瀏覽器升級(jí)到全部支持 HTTP/2(支持連接復(fù)用后模塊不打包反而比較好)以及 定義 ( ??。)
所以我們整個(gè)工具鏈應(yīng)該是這樣的:
而目前來看,主要可用的模塊打包工具有這么幾個(gè):
Browserify
Webpack
Rollup.js
本來我還想講一下 FIS3 的,結(jié)果去看了一下,人家竟然還沒原生的支持 ES6 Modules,而且 fis3-hook-commonjs 插件也幾萬年沒更新了,所以還是算了吧。至于 SystemJS 這類動(dòng)態(tài)模塊加載器本文也不會(huì)涉及,就像我上面說的一樣,在目前這個(gè)時(shí)間點(diǎn)上還是先用模塊打包工具比較好。
下面分別介紹這幾個(gè)工具以及如何使用它們配合 Babel 實(shí)現(xiàn) ES6 模塊轉(zhuǎn)譯。
BrowserifyBrowserify 這個(gè)工具也是有些年頭了,它通過打包所有的依賴來讓你能夠在瀏覽器中使用 CommonJS 的語法來 require("modules"),這樣你就可以像在 Node.js 中一樣在瀏覽器中使用 npm 包了,可以爽到。而且我也很喜歡 Browserify 這個(gè) LOGO
既然 Babel 會(huì)把我們的 ES6 Modules 語法轉(zhuǎn)換成 ES5 + CommonJS 規(guī)范的模塊語法,那我們就可以直接用 Browserify 來解析 Babel 的轉(zhuǎn)譯生成物,然后把所有的依賴給打包成一個(gè)文件,豈不是美滋滋。
不過除了 Babel 和 Browserify 這倆工具外,我們還需要一個(gè)叫做 babelify 的東西……好吧好吧,這是最后一個(gè)了,真的。
那么,babelify 是拿來干嘛的呢?因?yàn)?Browserify 只看得懂 CommonJS 的模塊代碼,所以我們得把 ES6 模塊代碼轉(zhuǎn)換成 CommonJS 規(guī)范的,再拿給 Browserify 去看:這一步就是 Babel 要干的事情了。但是 Browserify 人家是個(gè)模塊打包工具啊,它是要去分析 AST(抽象語法樹),把那些 reuqire() 的依賴文件給找出來再幫你打包的,你總不能把所有的源文件都給 Babel 轉(zhuǎn)譯了再交給 Browserify 吧?那太蠢了,我的朋友。
babelify (Browserify transform for Babel) 要做的事情,就是在所有 ES6 文件拿給 Browserify 看之前,先把它用 Babel 給轉(zhuǎn)譯一下(browserify().transform),這樣 Browserify 就可以直接看得懂并打包依賴,避免了要用 Babel 先轉(zhuǎn)譯一萬個(gè)文件的尷尬局面。
好吧,那我們要怎樣把這些工具搗鼓成一個(gè)完整的工具鏈呢?下面就是喜聞樂見的依賴包安裝環(huán)節(jié):
# 我用的 yarn,你用 npm 也差不多 # gulp 也可以全局安裝,方便一點(diǎn) # babel-preset 記得選適合自己的 # 最后那倆是用來配合 gulp stream 的 $ yarn add --dev babel-cli babel-preset-env babelify browserify gulp vinyl-buffer vinyl-source-stream
這里我們用 Gulp 作為任務(wù)管理工具來實(shí)現(xiàn)自動(dòng)化(什么,都 7012 年了你還不知道 Gulp?那為什么不去問問神奇海螺呢?),gulpfile.js 內(nèi)容如下:
var gulp = require("gulp"), browserify = require("browserify"), babelify = require("babelify"), source = require("vinyl-source-stream"), buffer = require("vinyl-buffer"); gulp.task("build", function () { return browserify(["./src/index.js"]) .transform(babelify) .bundle() .pipe(source("bundle.js")) .pipe(gulp.dest("dist")) .pipe(buffer()); });
相信諸位都能看得懂吧,browserify() 第一個(gè)參數(shù)是入口文件,可以是數(shù)組或者其他亂七八糟的,具體參數(shù)說明請(qǐng)自行參照 Browserify 文檔。而且記得在根目錄下創(chuàng)建 .babelrc 文件指定轉(zhuǎn)譯的 preset,或者在 gulpfile.js 中配置也可以,這里就不再贅述。
最后運(yùn)行 gulp build,就可以生成能直接在瀏覽器中運(yùn)行的打包文件了。
? browserify $ gulp build [12:12:01] Using gulpfile E:wwwrootes6-module-testrowserifygulpfile.js [12:12:01] Starting "build"... [12:12:01] Finished "build" after 720 msRollup.js
我記得這玩意最開始出來的時(shí)候號(hào)稱為「下一代的模塊打包工具」,并且自帶了可大大減小打包體積的 tree-shaking 技術(shù)(DCE 無用代碼移除的一種,運(yùn)用了 ES6 靜態(tài)分析語法樹的特性,只打包那些用到了的代碼),在當(dāng)時(shí)很新鮮。
但是現(xiàn)在 Webpack2+ 已經(jīng)支持了 Tree Shaking 的情況下,我們又有什么特別的理由去使用 Rollup.js 呢?不過畢竟也是一種可行的方法,這里也提一提:
# 我也不知道為啥 Rollup.js 要依賴這個(gè) external-helpers $ yarn add --dev rollup rollup-plugin-babel babel-preset-env babel-plugin-external-helpers
然后修改根目錄下的 rollup.config.js:
import babel from "rollup-plugin-babel"; export default { entry: "src/index.js", format: "esm", plugins: [ babel({ exclude: "node_modules/**" }) ], dest: "dist/bundle.js" };
還要修改 .babelrc 文件,把 Babel 轉(zhuǎn)換 ES6 模塊到 CommonJS 模塊的轉(zhuǎn)換給關(guān)掉,不然會(huì)導(dǎo)致 Rollup.js 處理不來:
{ "presets": [ ["env", { "modules": false }] ], "plugins": [ "external-helpers" ] }
然后在根目錄下運(yùn)行 rollup -c 即可打包依賴,也可以配合 Gulp 來使用,官方文檔里就有,這里就不贅述了。可以看到,Tree Shaking 的效果還是很顯著的,經(jīng)測試,未使用的代碼確實(shí)不會(huì)被打包進(jìn)去,比起上面幾個(gè)工具生成的結(jié)果要清爽多了:
Webpack對(duì),Webpack,就是那個(gè)喪心病狂想要把啥玩意都給模塊化的模塊打包工具。既然人家已經(jīng)到了 3.0.0 版本了,所以下面的都是基于 Webpack3 的。什么?現(xiàn)在還有搞前端的不知道 Webpack?神奇海螺以下略。
喜聞樂見的依賴安裝環(huán)節(jié):
# webpack 也可以全局安裝,方便一些 $ yarn add --dev babel-loader babel-core babel-preset-env webpack
然后配置 webpack.config.js:
var path = require("path"); module.exports = { entry: "./src/index.js", output: { filename: "bundle.js", path: path.resolve(__dirname, "dist") }, module: { rules: [ { test: /.js$/, exclude: /node_modules/, use: { loader: "babel-loader", options: { presets: ["env"] } } } ] } };
差不多就是這么個(gè)配置,babel-loader 的其他 options 請(qǐng)參照文檔,而且這個(gè)配置文件的括號(hào)嵌套也是說不出話,ZTMJLWC。
然后運(yùn)行 webpack:
? webpack $ webpack Hash: 5c326572cf1440dbdf64 Version: webpack 3.0.0 Time: 1194ms Asset Size Chunks Chunk Names bundle.js 2.86 kB 0 [emitted] main [0] ./src/index.js 106 bytes {0} [built] [1] ./src/bullshit.js 178 bytes {0} [built]
情況呢就是這么個(gè)情況:
Tips: 關(guān)于 Webpack 的 Tree Shaking
Webpack 現(xiàn)在是自帶 Tree-Shaking 的,不過需要你把 Babel 默認(rèn)的轉(zhuǎn)換 ES6 模塊至 CommonJS 格式給關(guān)掉,就像上面 Rollup.js 那樣在 .babelrc 中添加個(gè) "modules": false。原因的話上面也提到過,tree-shaking 是基于 ES6 模塊的靜態(tài)語法分析的,如果交給 Webpack 的是已經(jīng)被 Babel 轉(zhuǎn)換成 CommonJS 的代碼的話那就沒戲了。
而且 Webpack 自帶的 tree-shaking 只是把沒用到的模塊從 export 中去掉而已,之后還要再接一個(gè) UglifyJS 之類的工具把冗余代碼干掉才能達(dá)到 Rollup.js 那樣的效果。
Webpack 也可以配合 Gulp 工作流讓開發(fā)更嗨皮,有興趣的可自行研究。目前來看,這三種方案中,我本人更傾向于使用 Webpack,不知道諸君會(huì)選用什么呢?
寫在后面前幾天我在搗鼓 printempw/blessing-skin-server 那坨 shi 一樣 JavaScript 代碼的模塊化的時(shí)候,打算試著使用一下 ES6 標(biāo)準(zhǔn)中的模塊化方案,并找了 Google 大老師問 ES6 模塊轉(zhuǎn)譯打包相關(guān)的資源,找了半天,幾乎沒有什么像樣的中文資源。全是講 ES6 模塊是啥、有多好、為什么要用之類的,沒幾個(gè)是講到底該怎么在生產(chǎn)環(huán)境中使用的(也有可能是我搜索姿勢不對(duì)),說不出話。遂撰此文,希望能幫到后來人。
且本人水平有限,如果文中有什么錯(cuò)誤,歡迎在下方評(píng)論區(qū)批評(píng)指出。
參考鏈接Getting import/export working ES6 style using Browserify + Babelify + Gulp = -5hrs of life
rollup.js ? guide
使用 webpack 2 tree-shaking 機(jī)制時(shí)需要注意的細(xì)節(jié)
webpack+babel 加載 es6 模塊
Documentation - webpack
如何評(píng)價(jià) Webpack 2 新引入的 Tree-shaking 代碼優(yōu)化技術(shù)?
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/83871.html
摘要:前端日?qǐng)?bào)精選了解中的全局對(duì)象和全局作用域張鑫旭鑫空間鑫生活子進(jìn)程你應(yīng)該知道的一切直出內(nèi)存泄露問題的追查實(shí)踐我他喵的到底要怎樣才能在生產(chǎn)環(huán)境中用上模塊化騰訊前端大會(huì)大咖說大咖干貨,不再錯(cuò)過發(fā)布發(fā)布中文翻譯在使用進(jìn)行本地開發(fā)代碼 2017-07-07 前端日?qǐng)?bào) 精選 了解JS中的全局對(duì)象window.self和全局作用域self ? 張鑫旭-鑫空間-鑫生活Node.js 子進(jìn)程:你應(yīng)該知道...
摘要:大家好,我是辣條。最先審核沒通過,說我腳本涉嫌控制電腦違法違規(guī),經(jīng)過我再三的溝通之下,完整代碼刪除了,希望能通過審核。 大家好,我是辣條。? 前言 開學(xué)沒多久,事又多正愁缺寫博客的素材,這不馬上就來了,憨憨室友又要整活?,看在友(紅)情(包)的份上必須幫忙。 我起初的想法是通過郵箱發(fā)送表白...
摘要:這種情況通常發(fā)生在反向代理的時(shí)候,前端發(fā)起請(qǐng)求代理服務(wù)器,代理服務(wù)器發(fā)起請(qǐng)求到,這時(shí)候就容易導(dǎo)致域名不一致,請(qǐng)一定要注意這點(diǎn)。 寫在最前 前后端分離其實(shí)有兩類: 開發(fā)階段使用dev-server,生產(chǎn)階段是打包成靜態(tài)文件整個(gè)放入后端項(xiàng)目中。 開發(fā)階段使用dev-server,生產(chǎn)階段是打包成靜態(tài)文件放入單獨(dú)的靜態(tài)資源服務(wù)器中,如nginx。 這兩種方案最大的區(qū)別就是生產(chǎn)階段。由于第...
摘要:這種情況通常發(fā)生在反向代理的時(shí)候,前端發(fā)起請(qǐng)求代理服務(wù)器,代理服務(wù)器發(fā)起請(qǐng)求到,這時(shí)候就容易導(dǎo)致域名不一致,請(qǐng)一定要注意這點(diǎn)。 寫在最前 前后端分離其實(shí)有兩類: 開發(fā)階段使用dev-server,生產(chǎn)階段是打包成靜態(tài)文件整個(gè)放入后端項(xiàng)目中。 開發(fā)階段使用dev-server,生產(chǎn)階段是打包成靜態(tài)文件放入單獨(dú)的靜態(tài)資源服務(wù)器中,如nginx。 這兩種方案最大的區(qū)別就是生產(chǎn)階段。由于第...
摘要:這種情況通常發(fā)生在反向代理的時(shí)候,前端發(fā)起請(qǐng)求代理服務(wù)器,代理服務(wù)器發(fā)起請(qǐng)求到,這時(shí)候就容易導(dǎo)致域名不一致,請(qǐng)一定要注意這點(diǎn)。 寫在最前 前后端分離其實(shí)有兩類: 開發(fā)階段使用dev-server,生產(chǎn)階段是打包成靜態(tài)文件整個(gè)放入后端項(xiàng)目中。 開發(fā)階段使用dev-server,生產(chǎn)階段是打包成靜態(tài)文件放入單獨(dú)的靜態(tài)資源服務(wù)器中,如nginx。 這兩種方案最大的區(qū)別就是生產(chǎn)階段。由于第...
閱讀 3631·2021-11-23 09:51
閱讀 2838·2021-11-23 09:51
閱讀 707·2021-10-11 10:59
閱讀 1711·2021-09-08 10:43
閱讀 3266·2021-09-08 09:36
閱讀 3326·2021-09-03 10:30
閱讀 3323·2021-08-21 14:08
閱讀 2240·2021-08-05 09:59