模塊化的歷史和模塊加載器

模塊化的歷史和模塊加載器

模塊化的需求來源

  1. 前端代碼日漸複雜,web應用愈來愈像桌面應用
  2. 複用別人的代碼
  3. 更少的網絡請求css

    http://huangxuan.me/js-module-7day/#/html

    實現模塊化

    基本原理

  4. 函數做用域
function m1(){
    // 模塊內容
    // 這種方式污染全局變量
}
  1. 對象寫法
var module1 = new Object({
    _count = 0;
    m1: function(){
    }
    // 模塊成員會被暴露,內部狀態能夠被外部改寫
})
  1. IIFE
var module1 = (function($){
    var _count = 0;
    var m1 = function(){
        // ...
    }
    var _$body = $('body');
    var foo = function(){
        console.log(_$body);
    }
    return {
        m1: m1,
        foo: foo
    }
    // 
})(jQuery)
module1.foo();

模塊加載器

js加載問題

  1. js文件加載的時候,頁面會出現假死(使用defer或async就沒法達到2中的要求)
  2. js文件的依賴關係須要經過script標籤順序去控制
  3. 若是模塊過多且不打包,網絡請求多前端

    模塊加載器解決的問題

  4. 實現js文件的異步加載,避免網頁失去響應
  5. 管理模塊之間的依賴關係,便於代碼的編寫和維護java

    模塊加載器演變史

    require.js(AMD規範)

    使用require.js並指定入口文件
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script src="js/require.js" data-main="js/main"></script>
</body>
</html>
// 主模塊(入口文件)的寫法
require.config({
  paths: {
    "jquery": 'jquery.min',
    "underscore": 'underscore.min',
    "backbone": 'backbone.min'
  }
});

require(['jquery', 'underscore','backbone'], function($, _, Backbone){
  console.log(_.min([1,10,2]))
});

require(['math'], function (math) {
  alert(math.min([10, 11]))
});
// 定義模塊的方法
define(['underscore'], function(_){
  var min = function (x, y){
    return _.min(x,y);
  };
  return {
    min: min
  }
});

此外,require.js提供了一個優化工具r.js,能夠將模塊打包,減小網絡請求node

http://www.hangge.com/blog/cache/detail_1704.htmlreact

require.js還有一些插件,能夠將圖片和文本打包jquery

sea.js(CMD規範)

// 引入sea.js而且配置入口文件
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">dfslljdslf</div>
<script src="../sea-modules/seajs/seajs/2.2.0/sea.js"></script>
<script>
    seajs.config({
      base: '../sea-modules/',
      alias: {
        'jquery': 'jquery/jquery/1.10.1/jquery.js'
      }
    })

    seajs.use('../static/test/main.js');
</script>
</body>
</html>
// 入口文件寫法
define(function (require, exports, module) {
  var test = require('./test');
  test.fadeOut();
});
// 定義模塊方法
define(function(require, exports, module){
  var $ = require('jquery');
  exports.fadeOut = function(){
    $('#app').fadeOut();
  }
})
區別

寫法上:require.js依賴前置,sea.js依賴就近,語法更像common.js
加載和執行順序上:require.js依賴提早執行,sea.js依賴懶執行,打日誌能夠看出來webpack

https://blog.csdn.net/zshake/article/details/53054722git

common.js(node.js服務器端)

// 最終導出的是exports對象
console.log("example.js");
exports.message = "hi";
exports.say = function (){
    console.log("hello");
};
// 使用require加載模塊
var example = require('./example.js');

ES6 module

ES6 模塊的設計思想是儘可能的靜態化,使得編譯時就能肯定模塊的依賴關係,以及輸入和輸出的變量。CommonJS 和 AMD 模塊,都只能在運行時肯定這些東西。好比,CommonJS 模塊就是對象,輸入時必須查找對象屬性。

// CommonJS模塊
let { stat, exists, readFile } = require('fs');

// 等同於
let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;

上面代碼的實質是總體加載fs模塊(即加載fs的全部方法),生成一個對象(_fs),而後再從這個對象上面讀取 3 個方法。這種加載稱爲「運行時加載」,由於只有運行時才能獲得這個對象,致使徹底沒辦法在編譯時作「靜態優化」。
ES6 模塊不是對象,而是經過export命令顯式指定輸出的代碼,再經過import命令輸入。

// ES6模塊
import { stat, exists, readFile } from 'fs';

上面代碼的實質是從fs模塊加載 3 個方法,其餘方法不加載。這種加載稱爲「編譯時加載」或者靜態加載,即 ES6 能夠在編譯時就完成模塊加載,效率要比 CommonJS 模塊的加載方式高。固然,這也致使了無法引用 ES6 模塊自己,由於它不是對象。
因爲 ES6 模塊是編譯時加載,使得靜態分析成爲可能。有了它,就能進一步拓寬 JavaScript 的語法,好比引入宏(macro)和類型檢驗(type system)這些只能靠靜態分析實現的功能。

// export寫法
// 1.直接導出
export var firstName = 'Michael';
// 2.先聲明後導出
var firstName = 'Michael';
export {firstName}
// 3.導出函數或類
export function multiply(x,y){
    return x+y;
}
// 4.export命令規定的是對外的接口,必須與模塊內部的變量創建一一對應關係
export 1 // 報錯
var m = 1;
export m; //報錯
export var m = 1; // 不報錯
export {m} // 不報錯

// import寫法
import {firstName, lastName, year} from './profile.js';
import { lastName as surname } from './profile.js';
// js後綴能夠省略
import {myMethod} from 'util';
// import命令具備提高效果,會提高到整個模塊的頭部,首先執行
foo();
import { foo } from 'my_module';
// 因爲import是靜態執行,因此不能使用表達式和變量,這些只有在運行時才能獲得結果的語法結構
import { 'f' + 'oo' } from 'my_module'; // 報錯
// import語句會執行所加載的模塊,所以能夠有下面的寫法
import 'lodash'; // 僅僅執行lodash模塊,可是不輸入任何值
// 模塊的總體加載
// circle.js
export function area(radius) {
  return Math.PI * radius * radius;
}
export function circumference(radius) {
  return 2 * Math.PI * radius;
}

import * as circle from './circle';
console.log('圓面積:' + circle.area(4));
console.log('圓周長:' + circle.circumference(14));

// export default用法
// export-default.js
export default function () {
  console.log('foo');
}
// import-default.js
import customName from './export-default'; // 不用括號
customName(); // 'foo'
// 一個模塊只能有一個默認輸出,所以export default命令只能使用一次

import()方法
在語法上,條件加載就不可能實現。若是import命令要取代 Node 的require方法,這就造成了一個障礙。由於require是運行時加載模塊,import命令沒法取代require的動態加載功能。

const path = './' + fileName;
const myModual = require(path);

上面的語句就是動態加載,require到底加載哪個模塊,只有運行時才知道。import命令作不到這一點。
所以,有一個提案,建議引入import()函數,完成動態加載。

import(specifier)

上面代碼中,import函數的參數specifier,指定所要加載的模塊的位置。import命令可以接受什麼參數,import()函數就能接受什麼參數,二者區別主要是後者爲動態加載。
import()返回一個 Promise 對象。下面是一個例子。

const main = document.querySelector('main');

import(`./section-modules/${someVariable}.js`)
  .then(module => {
    module.loadPageInto(main);
  })
  .catch(err => {
    main.textContent = err.message;
  });

import()相似於 Node 的require方法,區別主要是前者是異步加載,後者是同步加載。
適用場景

  • 按需加載
button.addEventListener('click', event => {
  import('./dialogBox.js')
  .then(dialogBox => {
    dialogBox.open();
  })
  .catch(error => {
    /* Error handling */
  })
});
  • 條件加載
if (condition) {
  import('moduleA').then(...);
} else {
  import('moduleB').then(...);
}
  • 動態模塊路徑
import(f())  // 根據函數f的返回結果,加載不一樣的模塊
.then(...);

注意點
import()加載模塊成功之後,這個模塊會做爲一個對象,看成then方法的參數。

// 解構賦值
import('./myModule.js')
.then(({export1, export2}) => {
  // ...·
});
// 同時加載多個模塊
Promise.all([
  import('./module1.js'),
  import('./module2.js'),
  import('./module3.js'),
])
.then(([module1, module2, module3]) => {
   ···
});

http://es6.ruanyifeng.com/#docs/module

打包工具

require.js有相似r.js的打包工具,此外還有一些打包工具

browserify

讓瀏覽器加載Nodejs模塊

$ browserify main.js -o bundle.js

https://javascript.ruanyifeng.com/tool/browserify.html

webpack

能夠打包各類文件資源,可支持各類模塊規範

webpack.config.js配置文件

配置文件自己就是一個模塊

var path = require('path');
var webpack = require('webpack');

var devFlagPlugin = new webpack.DefinePlugin({
  _DEV_: JSON.stringify(JSON.parse(process.env.DEBUG || 'false'))
});

module.exports={
  entry: {  // 入口文件,String/Array/Object
    bundle1: 'main1.jsx',
    bundle2: 'main2.jsx',
    bundle: 'main.jsx'
  },
  output:{   // 指定輸出位置和文件名
    filename:'[name].js',  
    path: path.resolve(__dirname, './')
  },
  mode:'development', 
  module: { // loader 讓 webpack 可以去處理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)
    rules: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        use:{
          loader: 'babel-loader',  // 處理ES6
          options: {
            presets: ['es2015', 'react']
          }
        }
      },
      {
        test: /\.css$/,  // 處理css文件
        use: [
          {
            loader: 'style-loader',
          },
          {
            loader: 'css-loader',
            options: {
              modules: true,
            }
          }
        ]
      },
      {
        test: /\.(jpg|png)$/, // 解析圖片模塊
        use: {
          loader: 'url-loader',
          options: {
            limit: 8192
          }
        }
      }
    ]
  },
  plugins: [ // loader 被用於轉換某些類型的模塊,而插件則能夠用於執行範圍更廣的任務,須要經過使用 new 操做符來建立它的一個實例
    devFlagPlugin,  // 根據node命令行參數設置變量_DEV_,_DEV_能夠在任何模塊使用
    new webpack.optimize.CommonsChunkPlugin({ // code splitting
      name: 'commons',
      filename: 'commons.js',
    })
  ]
};
// main.js
const React = require('react');
const ReactDOM = require('react-dom');
var style = require('./app.css'); // 導入css文件模塊(css module)

var img1 = new Image();
img1.src = require('./big.png'); // 導入圖片資源
document.body.appendChild(img1);
if(_DEV_){
  document.write(new Date());
}

ReactDOM.render(
  <div>
    <h1 className={style.h1}>Hello, world!</h1>
    <h2 className="h2">Hello, webpack</h2>
  </div>,
  document.querySelector('#wrapper')
);

webpack.config.js還能夠導出一個函數或promise對象,能夠導出多種規範的模塊,能夠熱替換,只更新修改的模塊。
webpack 從命令行或配置文件中定義的一個模塊列表開始,處理你的應用程序。 從這些入口起點開始,webpack 遞歸地構建一個依賴圖,這個依賴圖包含着應用程序所需的每一個模塊,而後將全部這些模塊打包爲少許的 bundle - 一般只有一個 - 可由瀏覽器加載。

https://www.webpackjs.com/concepts/
https://github.com/ruanyf/webpack-demos#demo10-code-splitting-source
http://www.ruanyifeng.com/blog/2012/10/javascript_module.html
http://www.ruanyifeng.com/blog/2012/10/asynchronous_module_definition.html
http://www.ruanyifeng.com/blog/2012/11/require_js.html

相關文章
相關標籤/搜索