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

相关文章
相关标签/搜索