fis3工程化中的模塊化開發

 來源: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模塊化

在瀏覽器環境運行的代碼,若是咱們但願採用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在瀏覽器中的運行結果截圖: mod.js運行結果以上就是經過fis3-hook-commonjs實現模塊化的過程,固然插件還有一些配置項供開發人員配置,感興趣的同窗能夠經過閱讀fis3-hook-commonjs的文檔自行了解。jquery

AMD模塊化

首先安裝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-amd

CMD模塊化

安裝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"
    }});

查看結果: fis3-hook-cmd

由於工程化,讓模塊化變得簡單,可複用!你不用在意使用你模塊的人是使用commonJS仍是seaJS仍是requireJS做爲模塊加載器,你只須要專心開發你的模塊,並經過require加載你要依賴的模塊便可。怎麼樣?是否是很爽?那就用起來吧~ 有興趣的同窗能夠看一下demo:fis3-mudule-demo

相關文章
相關標籤/搜索