第二個問題,我想從這個最簡單的 HTML 頁面開始。css
<!DOCTYPE html> <html> <head> <title>Test</title> </head> <body> ... </body>
當咱們想寫一些樣式的時候,咱們一般會引入一個外部的 CSS 文件,就像這樣:html
<link rel="stylesheet" href="style.css">
有時咱們可能會想用一個好比說 Bootstrap 這種的 UI 框架,固然咱們也是經過一個 <link>
標籤去引入:前端
<link rel="stylesheet" href="bootstrap.css">
固然了,JavaScript 代碼也是相似的,咱們可能會有 jQuery、Bootstrap、一些插件以及一些業務代碼。jquery
<script src="jquery.js"></script> <script src="bootstrap.js"></script> <script src="plugin-A.js"></script> <script src="plugin-B.js"></script> <script src="app.js"></script>
當咱們在多個 JavaScript 文件之間進行通信時,咱們可能會把一個變量掛到 window 上,變成一個全局的變量。當項目變得愈來愈複雜,這些全局變量也會變得愈來愈多,在我剛入職計蒜客的時候,我甚至看到一個頁面的全局變量多達 40 多個。當我嘗試去維護的時候,我發現這些文件的耦合度很是高。而且因爲全局變量很是多,很容易就出現 命名衝突 的狀況。webpack
並且,在不少時候,咱們多個 JS 文件之間是有依賴關係的,好比說咱們圖中的 plugin-B。js
若是依賴了 plugin-A.js
,當別人想使用 plugin-B.js
可是沒有引入 plugin-A.js
的時候,那麼 plugin-B.js
就不能正常運行了。因此咱們遇到了一個比較繁瑣的 文件依賴 問題。web
因此,咱們第二個問題就是:如何解決命名衝突和依賴混亂問題?shell
首先,之因此咱們會有命名衝突問題,是由於那些 .js
文件都是共享做用域的,並且它們還定義了一些全局的變量。咱們要作的,就是要 限制做用域,而且 移除全局變量。還有依賴混亂問題,咱們能夠經過規定一些特殊的語法,來在代碼中聲明依賴關係,再開發一個工具來自動化處理文件之間的依賴,就能夠解決依賴混亂的問題了。npm
這種作法咱們稱爲 模塊化。bootstrap
咱們把每個 .js
文件都視爲一個 模塊,模塊內部有本身的做用域,不會影響到全局。而且,咱們 約定一些關鍵詞來進行依賴聲明和 API 暴露。而這些約定的關鍵詞就是經過制定一些 規範 去進行規範的。segmentfault
比較有名模塊化規範的是 CMD、AMD、CommonJS 和 ES6 Module,它們都是爲了實如今瀏覽器端模塊化開發的目的。前面兩個規範分別來自 SeaJS 及 RequireJS,這兩個規範如今基本已經不多人用了;CommonJS 因爲是被 NodeJS 所採用的,因此不少人用;而 ES6 Module 天然是來自去年正式發佈的 ECMAScript 2015 所採用的了,之後會逐漸成爲最主要的模塊化規範。
由於咱們這個系列文章中使用的工具都是基於 NodeJS 寫的,並且後面的工具還會用到,因此咱們就介紹一下 CommonJS 的語法吧。
好比咱們想在一個文件名爲 foo.js
的模塊中把 { bar: 123 }
這個對象暴露出去,讓別人能使用,咱們能夠用 module.exports
這個關鍵詞:
module.exports = { bar: 123 }
好比咱們某個模塊依賴了 foo.js
這個模塊,那麼咱們可使用 require
這個關鍵詞來聲明咱們對 foo.js
的依賴:
require('foo.js') // 返回 { bar: 123 }
CommonJS 規範的語法就這麼簡單。
瞭解完規範以後呢,咱們還須要一個工具來自動處理它們的這些依賴:
Webpack 能夠來幫咱們解決這個問題。
它的安裝方法很簡單,用咱們上篇文章中學習到的 NPM 就能夠了:執行 npm install -g webpack
,這樣就能夠把 webpack 安裝到全局下了。
咱們往第一個咱們文件名爲 foo.js
的模塊裏填入上面的那段代碼:
module.exports = { bar: 123 }
而後咱們再來寫第二個文件去引用它,好比咱們叫它 entry.js
,咱們在裏面填入:
var foo = require('./foo.js') console.log(foo.bar)
接下來,咱們就能夠開始用 Webpack 了,咱們打開 Terminal,進入到當前目錄,而後執行:
webpack entry.js --output-filename build/output.js
這條命令的意思是指定 entry.js
爲入口文件,最終打包後的文件路徑爲 build/output.js
。
沒有意外的話,咱們就會看到這樣的輸出:
Hash: 08ed99b71325392159ff Version: webpack 1.13.0 Time: 68ms Asset Size Chunks Chunk Names ./build/output.js 1.69 kB 0 [emitted] main [0] multi main 40 bytes {0} [built] [1] ./entry.js 47 bytes {0} [built] [2] ./foo.js 32 bytes {0} [built]
這表示咱們打包成功了,而且依照咱們的配置生成路徑爲 ./build/output.js
(有興趣的同窗能夠打開咱們打開剛剛生成的 output.js' 看看,結構並不複雜),而這個
output.js` 是能夠直接在瀏覽器裏使用的。
剛剛那種方法是咱們直接在命令行裏選擇入口文件和輸出文件,但你們有沒有以爲每次都這麼敲命令太麻煩了?顯然的,除了這種方法以外,咱們還能夠經過配置文件來實現。咱們建立一個叫 webpack.config.js
的文件,在裏面這樣寫:
module.exports = { // 這裏就是咱們剛剛說的 CommonJS 的關鍵詞 entry: './entry.js', // 入口文件 output: { path: './build', filename: 'output.js' // 輸出文件路徑及文件名 } }
這樣寫完以後呢,咱們如今只須要執行一下 webpack
就能夠完成跟剛纔同樣的編譯了。而且,咱們還能夠加一個 -w
(watch)來監聽文件的變化,這樣咱們以後修改文件以後就不用再手動去執行 webpack
了,它自動就會從新編譯。
要實現非 JS 文件的模塊化,咱們須要使用 Webpack 的 loader。Loader 能夠幫咱們把一些非 JS 的資源變成能夠在 JS 中使用。
好比咱們想 require 一個 CSS 資源,那咱們就會須要 css-loader,它能夠把 CSS 文件變成 JS 的語法,而後咱們能夠再經過 style-loader 把已經被轉換成 JS 語法的 CSS 再插入到 DOM 中,讓咱們的樣式生效。咱們來試試:
// entry.js require('./style.css') // style.css p { color: red; } // webpack.config.js module.exports = { entry: './entry.js', // 入口文件 output: { path: './build', filename: 'output.js' // 輸出文件路徑及文件名 }, module: { loaders: [ { test: /\.css$/, loader: 'style!css' } // `!` 是管道,loader 會從後往前依次執行 ] } }
咱們一樣執行 webpack
,就能夠打包完成了,以後咱們直接把生成出來的 output.js
放到頁面裏用就能夠了。
不少人習慣性地會按照傳統把項目設計成這樣:
咱們能夠看到,好比說這個 Button,它把本身的 JS 抽離了出來變成一個模塊,本身的樣式和模板也分別抽離了出來變成一個模塊。
可是咱們想,這種按照傳統的目錄結構有個很大的問題,那就是每一個組件的模板、樣式和腳本都分別在不一樣的目錄當中,當咱們在建立、修改和刪除一個組件的時候須要在不一樣的目錄之間來回切換,這樣會很麻煩。因而,很天然地咱們就會想把每一個組件都放到同一個目錄當中去:
左邊是原來的目錄結構,右邊是咱們從新設計後的目錄結構。咱們會發現,當咱們把目錄結構設計成以組件的形式來分割,而不是以傳統的 JS、CSS、HTML 這樣去分割的時候,會給咱們帶來不少好處。
咱們把這種將模板、樣式和邏輯都抽象出來獨立出來的作法稱之爲 組件化。
好比說,咱們在開發 button 組件的時候,再也不須要分別在幾個文件夾之間跳來跳去,去修改它們的模板、樣式和邏輯。咱們只須要在 button 組件的文件夾裏修改就行了。
這種設計其實也是跟咱們在軟件工程中的「關注度分離」原則是很是吻合的,當咱們須要對某個組件進行開發的時候,咱們只須要關注這個組件的自己,當咱們關注的東西越少,咱們出錯的可能性就越小,代碼的內聚性就更好,耦合性也更低。
以咱們計蒜客的課程列表頁爲例,我把組件的劃分用框框把它們給突顯出來:
好比上面的導航條是一個組件,大的課程列表是一個組件,課程列表裏的每一個課的面板也是一個組件,列表右上角也引用了一個按鈕組件。這麼劃分完以後呢,咱們就能夠對劃分出來的組件進行分工了。
一般我會在文檔中畫一個相似這樣的圖,上面就是各個組件的依賴關係,以及經過不一樣的顏色來表示不一樣的開發同窗所要去開發的組件。拿到組件分配任務的同窗,就能夠對本身負責的組件進行開發了。
實際上,除了咱們手動去作這麼一套組件化的工做以外,業界其實已經有比較火的組件化的庫和框架了,好比 Facebook 的 React 和咱們國內有名的 @尤雨溪 開發的 Vue。
我我的比較喜歡 Vue,由於它能夠以很是平滑和優雅的方式融入到一個已經存在的項目裏面去,用做者尤雨溪的話來講,那就是小而美。咱們在新的項目中幾乎徹底拋棄 jQuery 而去用 Vue 了,老的項目也在重構中慢慢地引入 Vue。
因爲時間的關係,這一塊我就再也不擴展開去討論了,你們感受興趣的話能夠在一下子結束以後來跟我討論一下,也能夠回去以後本身上網多瞭解瞭解。
關於《模塊化工具和一些組件化的思想》我們大概就聊到這裏,下一篇將會關於自動化構建工具,敬請期待。
最後的最後,仍是要給個人 Live 打個廣告哈!若是你想了解關於前端工程師的自我修養應該是怎樣的、如何成爲一個優雅的前端工程師,歡迎點擊參加:《前端工程師的自我修養》。
這是前端工具鏈課系列分享第二篇,若是想看第一篇能夠點擊:《包管理工具》。