來源:http://fxued.kugou.com/2015/12/14/gong-cheng-hua-zhong-de-mo-kuai-hua/javascript
經歷過雜亂的js函數式編程的人在認識了模塊化和模塊加載器以後,必定以爲是一種福音。模塊化讓咱們更加有組織有模塊地去編寫咱們的代碼,模塊化加載器讓咱們更加方便和清晰地進行模塊定義和依賴管理。如今主要的模塊化規範是commonJS,AMD和CMD,commonJS主要是用於node服務端,AMD和CMD主要用於瀏覽器端,表明框架分別是requireJS和seaJS;做爲前端,固然更熟悉的是requireJS和seaJS,可是對於我我的而言,commonJS的編碼方式我更喜歡,由於簡單,無需使用define包裝。
現在,要特別感謝前端工程化的出現,讓commonJS的編碼方式在前端變成可能,好比咱們熟悉的Browserify,固然做爲國內最強大的前端工程化工具——fis,固然也對模塊化也有本身的實現。下面咱們來學習一下fis3是如何實現模塊化構建的。固然你也能夠直接閱讀官方文檔:fis3模塊化
模塊化框架通常包含了模塊的依賴分析、模塊加載並保持依賴順序等功能。但在 FIS 中,依賴自己在構建過程當中就已經分析完成,並記錄在靜態資源映射表中,那麼對於線上運行時,模塊化框架就能夠省掉依賴分析這個步驟了。
fis3中針對前端模塊化框架的特性自動添加define包裝,以及根據配置生成對應的require依賴標識主要是經過對應的模塊化插件實現的:
fis3-hook-commonjs
fis3-hook-amd
fis3-hook-cmd
生成了規範的模塊文件以後,如何將模塊之間的依賴關係生成靜態資源映射表,則是經過
fis3-postpackager-loader。
這個插件用於分析頁面中使用的和依賴的資源(js或css), 並將這些資源作必定的優化後插入頁面中。
下面咱們結合栗子來學習一下這些模塊化插件是如何工做的。先看看咱們專題頁項目的目錄結構!css
static/ #項目靜態文件目錄 common/ #公共靜態文件目錄 js/ lib/ #類庫文件 mod.js require.js sea.js mod/ #須要模塊化的文件 react.js jquery.js css/ #css文件目錄 style.css images/ #圖片文件目錄 style.png commponents/ #公共組件,也是須要模塊化加載的 HelloMessage/ HelloMessage.jsx HelloMessage.css helloworld/ #簡單的例子 index.html index.css index.jsx fis-conf.js #fis配置文件 package.json #包配置文件
在瀏覽器環境運行的代碼,若是咱們但願採用commonJS規範做爲模塊化開發,則須要安裝fis3-hook-commonjs插件,npm install fis3-hook-commonjs --save
,還要配合mod.js來使用;
安裝完成以後看一下fis-conf.js如何配置:html
###fis-conf.js /*設置編譯範圍*/ fis.set('project.files', ['static/**']); /*設置發佈路徑*/ fis.match(/\/static\/(.*)/i, { release: '/staticPub/$1', /*全部資源發佈時產出到 /staticPub 目錄下*/ url: '/staticPub/$1' /*全部資源訪問路徑設置*/ }); /*指定模塊化插件*/ fis.hook('commonjs', { paths: { jquery: '/static/common/js/mod/jquery', //設置jquery別名 react: '/static/common/js/mod/react' //設置react別名 } }); /*指定哪些目錄下的文件執行define包裹*/ fis.match('/static/common/js/mod/**', { isMod: true }); fis.match('/static/common/components/**', { isMod: true }); fis.match('/static/helloworld/**', { isMod: true }); /*模塊化加載器配置*/ fis.match('::package', { postpackager: fis.plugin('loader', { allInOne: true, //js&css打包成一個文件 sourceMap: true, //是否生成依賴map文件 useInlineMap: true //是否將sourcemap做爲內嵌腳本輸出 }) }); /*支持react*/ fis.match('*.jsx', { rExt: '.js', parser: fis.plugin('react', {}) });
注意:須要對目標文件設置 isMod 屬性,說明這些文件是模塊化代碼。這樣纔會被自動加上define包裝,才能在瀏覽器裏面運行。fis3-postpackager-loader的做用則是分析這些文件的依賴關係並生成對應的sourceMap文件,讓mod.js分析並加載模塊對應的文件到瀏覽器中。前端
#helloworld/index.html <!DOCTYPE html> <html> <head> <title>繁星網 | 全球最大音樂現場直播平臺</title> <meta charset="utf-8"> <link rel="stylesheet" type="text/css" href="/static/common/css/style.css"> <link rel="stylesheet" type="text/css" href="./css/index.css"> </head> <body> <div id="helloApp"></div> </body> <script type="text/javascript" src="/static/common/js/lib/mod.js"></script> <script type="text/javascript"> require(['./index']);//異步加載index.js模塊 </script> </html> #helloworld/index.jsx //引入React和HelloMessage模塊 var React = require('react'); var HelloMessage = require('/static/common/components/HelloMessage/HelloMessage.react'); React.render( <HelloMessage message="I like commonjs!" />, document.getElementById('helloApp') ); #common/components/HelloMessage/HelloMessage.react.jsx var React = require('react'); var HelloMessage = React.createClass({ render: function() { return ( <h1>Hello, {this.props.message}</h1> ); } }); module.exports = HelloMessage;
helloworld/index.html須要引入mod.js做爲模塊化加載器,而後經過require([./index])異步加載index模塊;
helloworld/index.jsx依賴React和HelloMessage模塊,寫法就是咱們熟悉的commonJS的方式;
common/components/HelloMessage/index.jsx就是HelloMessage模塊,它也依賴React模塊;
從上面的jsx文件咱們能夠輕易地發現,不論是react仍是jsx文件都沒有任何define包裝,寫法就commonJS如出一轍,可是這樣在瀏覽器確定是跑不起來的,還須要fis幫咱們構建模塊包裝和依賴分析。OK,一切準備就緒,咱們就開始執行fis腳本:java
fis3 release -d ./
咱們來看看staticPub目錄下面產出的編譯文件:node
#helloworld/index.html <!DOCTYPE html> <!DOCTYPE html> <html> <head> <title>繁星網 | 全球最大音樂現場直播平臺</title> <meta charset="utf-8"> <link rel="stylesheet" type="text/css" href="/staticPub/helloworld/index.html_aio.css" /> </head> <body> <div id="helloApp"></div> <script type="text/javascript" src="/staticPub/common/js/lib/mod.js"></script> <script type="text/javascript">/*resourcemap*/ require.resourceMap({ "res": { "static/common/js/mod/react": { "url": "/staticPub/common/js/mod/react.js", "type": "js" }, "static/common/components/HelloMessage/HelloMessage.react": { "url": "/staticPub/common/components/HelloMessage/HelloMessage.react.js", "type": "js", "deps": [ "static/common/js/mod/react" ] }, "static/helloworld/index": { "url": "/staticPub/helloworld/index.js", "type": "js", "deps": [ "static/common/js/mod/react", "static/common/components/HelloMessage/HelloMessage.react" ] } }, "pkg": {} }); require(['static/helloworld/index']);//異步加載index.js模塊 </script> </body> </html>
咱們來看看有哪些變化:
一、index.html中css文件被打包成一個
<link rel="stylesheet" type="text/css" href="/static/common/css/style.css">
<link rel="stylesheet" type="text/css" href="./css/index.css">
變成了一個
<link rel="stylesheet" type="text/css" href="/staticPub/helloworld/index.html_aio.css" />
二、上面的index.html多了一份sourceMap腳本;
這是由於在fis3-postpackager-loader的配置中加了useInlineMap:true,能夠閱讀文檔瞭解更多配置。
咱們再來看看helloworld/index.jsx和HellowMessage/HelloMessage.react.jsx的變化:react
#hellowrold/index.js define('static/helloworld/index', function(require, exports, module) { //引入React和HelloMessage模塊 var React = require('static/common/js/mod/react'); var HelloMessage = require('static/common/components/HelloMessage/HelloMessage.react'); React.render( React.createElement(HelloMessage, {message: "I like commonjs!"}), document.getElementById('helloApp') ); }); #common/components/HelloMessage/HelloMessage.react.js define('static/common/components/HelloMessage/HelloMessage.react', function(require, exports, module) { var React = require('static/common/js/mod/react'); var HelloMessage = React.createClass({displayName: "HelloMessage", render: function() { return ( React.createElement("h1", null, "Hello, ", this.props.message) ); } }); module.exports = HelloMessage; }); #common/js/mod/react.js define('static/common/js/mod/react', function(require, exports, module) { //react code... }
一、全部的.jsx變成了.js文件,這是fis3-parser-react插件作的;
二、js文件都加了define包裝,好比"static/helloworld/index"是index模塊的moduleId;
三、require('react')編譯成了require('static/common/js/mod/react'),由於咱們經過path配置了別名;
咱們能夠發現,經過fis生成的js代碼define的moduleId跟index.html中sourceMap的moduleId是一致的。這樣mod.js就能經過resourceMap的依賴關係加載到全部的模塊啦!下面是demo在瀏覽器中的運行結果截圖: 以上就是經過fis3-hook-commonjs實現模塊化的過程,固然插件還有一些配置項供開發人員配置,感興趣的同窗能夠經過閱讀fis3-hook-commonjs的文檔自行了解。jquery
首先安裝fis3-hook-amd插件,npm install fis3-hook-amd --save
。 若是咱們理解fis3-hook-commonjs的使用方式,換成fis3-hook-amd就很簡單,使用方式的惟一的不一樣就是hook的插件由commonjs變爲amd:git
fis.hook('amd', { paths: { jquery: '/static/common/js/mod/jquery', react: '/static/common/js/mod/react' } });
固然此時咱們的模塊化框架要用require.js啦!因此index.html咱們要把mod.js換成require.js。<script type="text/javascript" src="/static/common/js/lib/require.js"></script>
執行fis3編譯:fis3-release -d ./
下面咱們看看編譯以後的產出文件:github
#helloworld/index.html <!DOCTYPE html> <html> <head> <title>繁星網 | 全球最大音樂現場直播平臺</title> <meta charset="utf-8"> <link rel="stylesheet" type="text/css" href="/staticPub/helloworld/index.html_aio.css" /> </head> <body> <div id="helloApp"></div> <script type="text/javascript" src="/staticPub/common/js/lib/require.js"></script> <script type="text/javascript">/*resourcemap*/ require.config({paths:{ "static/common/js/mod/jquery": "/staticPub/common/js/mod/jquery", "static/common/js/mod/react": "/staticPub/common/js/mod/react", "static/common/components/HelloMessage/HelloMessage.react": "/staticPub/common/components/HelloMessage/HelloMessage.react", "static/helloworld/index": "/staticPub/helloworld/index" }}); require(['static/helloworld/index']);//異步加載index.js模塊 </script> </body> </html> #helloworld/index.js define('static/helloworld/index', ['require', 'exports', 'module', 'static/common/js/mod/react', 'static/common/components/HelloMessage/HelloMessage.react'], function(require, exports, module) { //引入React和HelloMessage模塊 var React = require('static/common/js/mod/react'); var HelloMessage = require('static/common/components/HelloMessage/HelloMessage.react'); React.render( React.createElement(HelloMessage, {message: "I like AMD!"}), document.getElementById('helloApp') ); }); #common/components/HelloMessage/HelloMessage.js define('static/common/components/HelloMessage/HelloMessage.react', ['require', 'exports', 'module', 'static/common/js/mod/react'], function(require, exports, module) { var React = require('static/common/js/mod/react'); var HelloMessage = React.createClass({displayName: "HelloMessage", render: function() { return ( React.createElement("h1", null, "Hello, ", this.props.message) ); } }); module.exports = HelloMessage; });
注意,index.html內嵌腳本生成的sourceMap變成下面的格式,由於是AMD規範嘛:
require.config({paths:{ "static/common/js/mod/jquery": "/staticPub/common/js/mod/jquery", "static/common/js/mod/react": "/staticPub/common/js/mod/react", "static/common/components/HelloMessage/HelloMessage.react": "/staticPub/common/components/HelloMessage/HelloMessage.react", "static/helloworld/index": "/staticPub/helloworld/index" }});
js文件也被包裝成了遵循AMD規範的define形式。下面是demo執行結果:
安裝fis3-hook-cmd插件,npm install fis3-hook-cmd --save
。 該fis-conf.js配置文件:
/*指定模塊化插件*/ fis.hook('cmd', { paths: { jquery: '/static/common/js/mod/jquery', react: '/static/common/js/mod/react' } });
改index.html模塊加載器:<script type="text/javascript" src="/static/common/js/lib/sea.js"></script>
異步加載入口index模塊改成:seajs.use(['./index']);//異步加載index.js模塊
執行fis3編譯:fis3-release -d ./
注意:運行完成以後你會發現程序沒法運行,由於react模塊找不到,爲何呢?通常狀況下,咱們下載的開源框架都本身實現了amd包裝,好比react的源碼:
/** * React v0.13.0 */ (function(f) { if (typeof exports === "object" && typeof module !== "undefined") { module.exports = f() //注意看這裏,這就是默認是用amd } else if (typeof define === "function" && define.amd) { define([], f) } else { var g; if (typeof window !== "undefined") { g = window } else if (typeof global !== "undefined") { g = global } else if (typeof self !== "undefined") { g = self } else { g = this } g.React = f() } })(function() { var define, module, exports; //這裏纔是react的內部實現,源碼會返回一個React對象 return React; });
對於這類框架fis3-hook-amd會識別define.amd並將define([], f)替換成define('static/common/js/mod/react', [], f),可是咱們運行fis3-hook-cmd就沒法識別了,因此就沒法經過define定義模塊,define([], f)不會有任何變化。咱們把define.amd改爲define.cmd再運行一下fis就會發現了define([], f)變成了define('static/common/js/mod/react', [], f)。
再看看編譯以後的產出文件:
#helloworld/index.html <!DOCTYPE html> <html> <head> <title>繁星網 | 全球最大音樂現場直播平臺</title> <meta charset="utf-8"> <link rel="stylesheet" type="text/css" href="/staticPub/helloworld/index.html_aio.css" /> </head> <body> <div id="helloApp"></div> <script type="text/javascript" src="/staticPub/common/js/lib/sea.js"></script> <script type="text/javascript">/*resourcemap*/ seajs.config({alias:{ "static/common/js/mod/react": "/staticPub/common/js/mod/react", "static/common/components/HelloMessage/HelloMessage.react": "/staticPub/common/components/HelloMessage/HelloMessage.react", "static/helloworld/index": "/staticPub/helloworld/index" }}); // require(['./index']);//異步加載index.js模塊 seajs.use(['static/helloworld/index']);//異步加載index.js模塊 </script> </body> </html> #helloworld/index.js define('static/helloworld/index', ['static/common/js/mod/react', 'static/common/components/HelloMessage/HelloMessage.react'], function(require, exports, module) { //引入React和HelloMessage模塊 var React = require('static/common/js/mod/react'); var HelloMessage = require('static/common/components/HelloMessage/HelloMessage.react'); React.render( React.createElement(HelloMessage, {message: "I like CMD!"}), document.getElementById('helloApp') ); }); #common/components/HelloMessage/HelloMessage.js define('static/common/components/HelloMessage/HelloMessage.react', ['static/common/js/mod/react'], function(require, exports, module) { var React = require('static/common/js/mod/react'); var HelloMessage = React.createClass({displayName: "HelloMessage", render: function() { return ( React.createElement("h1", null, "Hello, ", this.props.message) ); } }); module.exports = HelloMessage; });
再來看看index.html內嵌腳本生成的sourceMap:
seajs.config({alias:{ "static/common/js/mod/react": "/staticPub/common/js/mod/react", "static/common/components/HelloMessage/HelloMessage.react": "/staticPub/common/components/HelloMessage/HelloMessage.react", "static/helloworld/index": "/staticPub/helloworld/index" }});
查看結果:
由於工程化,讓模塊化變得簡單,可複用!你不用在意使用你模塊的人是使用commonJS仍是seaJS仍是requireJS做爲模塊加載器,你只須要專心開發你的模塊,並經過require加載你要依賴的模塊便可。怎麼樣?是否是很爽?那就用起來吧~ 有興趣的同窗能夠看一下demo:fis3-mudule-demo