require 動態載入_Javascript 5種動態庫
技術標籤:require 動態載入
要解決的問題
- 複用其他人的工作
- 減少 executable 的體積
- 不共享原始碼的前提下複用
解決方案
exectuable不會攜帶所有的演算法實現。它會構建在executor提供的builtin api的基礎上。同時也會利用第三方的 dynamic library 依賴 executor 的 dynamic linker 動態連結進來使用。這個動態連結其他人的工作的機制在很多種機器中都有提供。
構成
- executable:動態連結發起的源頭,executor第一個載入的東西
- dynamic library:提供被複用的演算法,動態連結庫
- dynamic library linker:一般是executor的一個元件,提供動態連結的能力。也可能是第三方利用executor的 api 獨立實現的linker。
解決方案案例
JavaScript 主流的動態連結庫有以下幾種,各自使用的linker是不同的
傳統瀏覽器
- executable:直接嵌入到 html 的
<script>
標籤的 JavaScript 程式碼 - dynamic library:由 html 檔案的
<script src="xxx">
引用的 .js 檔案 - dynamic linker:瀏覽器自身,通過全域性變數實現彼此的互通。script標籤的順序決定了載入順序,後被載入的js可以引用前面js定義的全域性變數。
// http://localhost/library.js function call_library() { console.log('i am the library') } // http://localhost/index.html <html> <head> <script src="./library.js"></script> <script> console.log('i am the executable') call_library() // window.call_library() </script> </head> <body> </body> </html>
executable 和 dynamic library 之間的通過全域性變數 window 上的全域性變數實現互相呼叫。 call_library
這個函式其實就是定義在 window
上的變數。 用瀏覽器訪問 http://localhost/index.html
在瀏覽器的控制檯輸出
i am the executable
i am the library
JavaScript 程式碼內沒有直接的動態載入的支援,用 script 標籤載入的 url 無法動態計算出來。一個hack的方法是通過 DOM API 建立 script 標籤。
// http://localhost/library.js console.log('i am the library') // http://localhost/index.html <html> <head> <script> var head= document.getElementsByTagName('head')[0]; var script= document.createElement('script'); script.type= 'text/javascript'; script.src= './library.js'; head.appendChild(script); </script> </head> <body> </body> </html>
CJS 的代表 nodejs
- executable:直接用引數傳遞給 node 命令的 js 檔案
- dynamic library:js檔案 或者 package.json 定義的 js package
- dynamic linker:用 node 提供的全域性 require api 載入其他的 js 檔案
用 js 檔案做為動態連結庫
// /opt/executable.js
console.log('i am the executable')
require('./library.js')
// /opt/library.js
console.log('i am the library')
node /opt/executable.js
// Output:
// i am the executable
// i am the library
用包含 package.json 的目錄做為動態連結庫
目錄結構如下
- executable.js
- node_modules
- library
- package.json
- library.js
- library
// /opt/executable.js
console.log('i am the executable')
require('library')
// /opt/node_modules/library/package.json
{
"main": "library.js"
}
// /opt/node_modules/library/library.js
console.log('i am the library')
node /opt/executable.js
// Output:
// i am the executable
// i am the library
AMD 的代表 require.js
require.js 是基於傳統瀏覽器之上,用js自身實現的一個動態連結庫的linker。
- executable:
<script data-main="exectuable.js" src="https://unpkg.com/[email protected]/require.js">
- dynamic library:用
define(function(require, exports, module) {})
包裝的 js 檔案 - dynamic library linker:require.js
使用例子如下
// http://localhost/index.html
<html>
<head>
<script data-main="executable.js" src="https://unpkg.com/[email protected]/require.js"></script>
</head>
<body>
</body>
</html>
// http://localhost/executable.js
define(function(require, exports, module) {
const lib = require('./library.js')
console.log(lib.greeting)
})
// http://localhost/library.js
define(function(require, exports, module) {
exports.greeting = 'hello'
})
用瀏覽器訪問 http://localhost/index.html,在瀏覽器的控制檯裡輸出
hello
在傳統瀏覽器裡實現了 nodejs 的 require/exports 的語法。底層使用的還是動態生成 script 標籤的方式。
支援 ES6 Module 的瀏覽器
支援 ES6 Module 的瀏覽器,Chrome 從61起
- exectuable:直接嵌入到 html 的
<script type="module">
標籤的 JavaScript 程式碼 - dynamic library:用 url 可以訪問到的 javascript 檔案
- dynamic library linker:瀏覽器根據 JavaScript 頭部宣告的
import {yyy} from './xxx.js'
,動態載入相對當前 url 的 xxx.js
和傳統瀏覽器不同,import 引用的 js 會被瀏覽器載入,無需用 script 標籤 src 引用進來。
// http://localhost/library.js
console.log('i am the library')
// http://localhost/index.html
<html>
<head>
<script type="module">
import './library.js'
</script>
</head>
<body>
</body>
</html>
用瀏覽器訪問 http://localhost/index.html
在瀏覽器的控制檯輸出
i am the library
支援 ES6 Module 的 nodejs
- executable:直接用引數傳遞給 node 命令的 js 檔案
- dynamic library:mjs檔案 或者 package.json 定義的 js package
- dynamic library linker:用 ES6 Module 的 import 語法呼叫 nodejs 的 require 機制
// /opt/executable.mjs
import './library.mjs'
console.log('i am the executable')
// /opt/library.mjs
console.log('i am the library')
node --experimental-modules executable.mjs
// Output:
// i am the executable
// i am the library
nodejs 使用 ES6 Module 的語法進行動態連結需要兩個條件
- 檔名字尾從 .js 變成 .mjs
- 新增 --experimental-modules 的命令列引數
如果 import 的包不是相對路徑,也是從 node_modules 裡查詢 package.json
實現 System.register 的 system.js
基於 CJS 或者 AMD 都無法模擬實現 ES6 動態連結庫的全部特性。System.register 是另外一種定義動態連結庫的格式,它基於 ES5 的語法實現,可以完全模擬 ES6 Module 提供的特性。
system.js 實現了 System.register 這種格式,執行在支援 Promise 的瀏覽器裡。
- executable:瀏覽器html內嵌的script標籤
- dynamic library:由 System.register 定義
- dynamic library linker:s.js (system.js 的核心部分)
例如
// http://localhost/index.html
<html>
<head>
<script src="https://unpkg.com/[email protected]/dist/s.js"></script>
<script>
(async () => {
let lib = await System.import('./library.js')
console.log(lib.greeting)
})()
</script>
</head>
<body>
</body>
</html>
// http://localhost/library.js
System.register([], function($__export, $__moduleContext) {
let greeting = 'hello'
$__export({
greeting: greeting
});
return {
// setters: [], // (setters can be optional when empty)
execute: function() {
}
};
});
用瀏覽器訪問 http://localhost/index.html
,會在瀏覽器控制檯列印
hello