本文你將學到:javascript
本文全部例子都存放於 github.com/hua1995116/…css
今天在使用 rollup 打包的時候遇到了一個問題html
Error: 'Map' is not exported by node_modules/immutable/dist/immutable.js
複製代碼
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
global.Immutable = factory();
複製代碼
發現 immutable
是以 UMD 的形式暴露。查閱資料後發現 Rollup 並不支持 CommonJS 和 AMD 的打包方式,想要成功引入 commonJS 的模塊,必需要加載插件 github.com/rollup/plug… 固然並非對全部的 CommonJS 都自動支持,只針對相似於靜態的寫法才能導出,例如針動態模塊導出,以及隱式地導出將沒法自動導出,這樣的場景下須要手動指定導出模塊。以上的例子就是一個動態的方式,只有當 factory
函數執行了才能知道導出的模塊,須要手動指定。java
commonjs({
namedExports: {
// left-hand side can be an absolute path, a path
// relative to the current directory, or the name
// of a module in node_modules
'immutable': ['Map']
}
});
複製代碼
固然上述只是我寫這篇文章的一個原由,就是由於我對這一塊的迷惑,因此使得我想從新複習一下這一塊知識,上面將的可能你徹底聽不懂我在說什麼,沒有關係,下面開始切入正題。node
由於在最一開始,是我引入了這個概念,因此由我出來填坑,固然對這個工具很是熟悉的朋友能夠跳過。不熟悉的朋友你只須要知道,這個是一個打包 ES Module 的工具。webpack
Rollup 是一個 JavaScript 模塊打包器,能夠將小塊代碼編譯成大塊複雜的代碼,例如 library 或應用程序。Rollup 對代碼模塊使用新的標準化格式,這些標準都包含在 JavaScript 的 ES6 版本中,而不是之前的特殊解決方案,如 CommonJS 和 AMD。ES6 模塊可使你自由、無縫地使用你最喜好的 library 中那些最有用獨立函數,而你的項目沒必要攜帶其餘未使用的代碼。ES6 模塊最終仍是要由瀏覽器原生實現,但當前 Rollup 可使你提早體驗。git
CommonJS規範es6
CommonJS
主要運行於服務器端,該規範指出,一個單獨的文件就是一個模塊。 Node.js爲主要實踐者,它有四個重要的環境變量爲模塊化的實現提供支持:module
、exports
、require
、global
。require
命令用於輸入其餘模塊提供的功能,module.exports
命令用於規範模塊的對外接口,輸出的是一個值的拷貝,輸出以後就不能改變了,會緩存起來。github
// 模塊 a.js
const name = 'qiufeng'
module.exports = {
name,
github: 'https://github.com/hua1995116'
}
複製代碼
// 模塊 b.js
// 引用核心模塊或者第三方包模塊,不須要寫完整路徑
const path = require('path');
// 引用自定義模塊能夠省略.js
const { name, github } = require('./a');
console.log(name, github, path.basename(github));
// 輸出 qiufeng https://github.com/hua1995116 hua1995116
複製代碼
代碼地址: github.com/hua1995116/…web
CommonJS
採用同步加載模塊,而加載的文件資源大多數在本地服務器,因此執行速度或時間沒問題。可是在瀏覽器端,限於網絡緣由,更合理的方案是使用異步加載。
AMD是"Asynchronous Module Definition"的縮寫,意思就是"異步模塊定義"。它採用異步方式加載模塊,模塊的加載不影響它後面語句的運行。全部依賴這個模塊的語句,都定義在一個回調函數中,等到加載完成以後,這個回調函數纔會運行。其中 RequireJS 是最佳實踐者。
模塊功能主要的幾個命令:define
、require
、return
和define.amd
。define
是全局函數,用來定義模塊,define(id?, dependencies?, factory)
。require命令用於輸入其餘模塊提供的功能,return命令用於規範模塊的對外接口,define.amd屬性是一個對象,此屬性的存在來代表函數遵循AMD規範。
// model1.js
define(function () {
console.log('model1 entry');
return {
getHello: function () {
return 'model1';
}
};
});
複製代碼
// model2.js
define(function () {
console.log('model2 entry');
return {
getHello: function () {
return 'model2';
}
};
});
複製代碼
// main.js
define(function (require) {
var model1 = require('./model1');
console.log(model1.getHello());
var model2 = require('./model2');
console.log(model2.getHello());
});
複製代碼
<script src="https://cdn.bootcss.com/require.js/2.3.6/require.min.js"></script>
<script> requirejs(['main']); </script>
複製代碼
// 輸出結果
// model1 entry
// model2 entry
// model1
// model2
複製代碼
代碼地址: github.com/hua1995116/…
在這裏,咱們使用define來定義模塊,return來輸出接口, require來加載模塊,這是AMD官方推薦用法。
CMD(Common Module Definition - 通用模塊定義)規範主要是Sea.js推廣中造成的,一個文件就是一個模塊,能夠像Node.js通常書寫模塊代碼。主要在瀏覽器中運行,固然也能夠在Node.js中運行。
它與AMD很相似,不一樣點在於:AMD 推崇依賴前置、提早執行,CMD推崇依賴就近、延遲執行。
不懂 依賴就近、延遲執行
的能夠比較下面和上面的例子。
// model1.js
define(function (require, exports, module) {
console.log('model1 entry');
exports.getHello = function () {
return 'model1';
}
});
複製代碼
// model2.js
define(function (require, exports, module) {
console.log('model2 entry');
exports.getHello = function () {
return 'model2';
}
});
複製代碼
// main.js
define(function(require, exports, module) {
var model1 = require('./model1'); //在須要時申明
console.log(model1.getHello());
var model2 = require('./model2'); //在須要時申明
console.log(model2.getHello());
});
複製代碼
<script src="https://cdn.bootcss.com/seajs/3.0.3/sea.js"></script>
<script> seajs.use('./main.js') </script>
複製代碼
// 輸出
// model1 entry
// model1
// model2 entry
// model2
複製代碼
總結: 對比和 AMD 的寫法就能夠看出 AMD 和 CMD 的區別。雖然如今 CMD 已經涼了。可是 CMD 更加接近於 CommonJS 的寫法,可是 AMD 更加接近於瀏覽器的異步的執行方式。
UMD(Universal Module Definition - 通用模塊定義)模式,該模式主要用來解決CommonJS模式和AMD模式代碼不能通用的問題,並同時還支持老式的全局變量規範。
示例展現
// bundle.js
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = global || self, global.myBundle = factory());
}(this, (function () { 'use strict';
var main = () => {
return 'hello world';
};
return main;
})));
複製代碼
// index.html
<script src="bundle.js"></script>
<script>
console.log(myBundle());
</script>
複製代碼
define爲
函數,而且是否存在define.amd
,來判斷是否爲AMD規範,module
是否爲一個對象,而且是否存在module.exports
來判斷是否爲CommonJS
規範ES modules(ESM)是 JavaScript 官方的標準化模塊系統。
import
和export
來肯定。 能夠和Commonjs模塊混合使用。使用方式
// index.js
import { name, github } from './demo.js';
console.log(name(), github());
document.body.innerHTML = `<h1>${name()} ${github()}</h1>`
複製代碼
export function name() {
return 'qiufeng';
}
export function github() {
return 'https://github.com/hua1995116';
}
複製代碼
<script src="./index.js" type="module"></script>
複製代碼
代碼地址: github.com/hua1995116/…
詳細能夠查看 深刻理解 ES6 模塊機制
// a.js
const b = require('./b');
console.log(b.age);
setTimeout(() => {
console.log(b.age);
console.log(require('./b').age);
}, 100);
複製代碼
// b.js
let age = 1;
setTimeout(() => {
age = 18;
}, 10);
module.exports = {
age
}
// 執行:node a.js
// 執行結果:
// 1
// 1
// 1
複製代碼
CommonJS 主要有執行主要有如下兩個特色
// a.js
import { age } from './b.js';
console.log(age);
setTimeout(() => {
console.log(age);
import('./b.js').then(({ age }) => {
console.log(age);
})
}, 100);
// b.js
export let age = 1;
setTimeout(() => {
age = 2;
}, 10);
// 打開 index.html
// 執行結果:
// 1
// 2
// 2
複製代碼
舉個例子以下:
動態加載,只有當模塊運行後,才能知道導出的模塊是什麼。
var test = 'hello'
module.exports = {
[test + '1']: 'world'
}
複製代碼
靜態編譯, 在編譯階段就能知道導出什麼模塊。
export function hello() {return 'world'}
複製代碼
關於 ES6 模塊編譯時執行會致使有如下兩個特色:
import 優先執行:
// a.js
console.log('a.js')
import { age } from './b.js';
// b.js
export let age = 1;
console.log('b.js 先執行');
// 運行 index.html 執行結果:
// b.js 先執行
// a.js
複製代碼
雖然 import 順序比較靠後,可是 因爲 import 提高效果會優先執行。
export 變量聲明提高:
// a.js
import { foo } from './b.js';
console.log('a.js');
export const bar = 1;
export const bar2 = () => {
console.log('bar2');
}
export function bar3() {
console.log('bar3');
}
// b.js
export let foo = 1;
import * as a from './a.js';
console.log(a);
// 運行 node --experimental-modules a.js 執行結果:
// [Module] {
// bar: <uninitialized>,
// bar2: <uninitialized>,
// bar3: [Function: bar3]
}
複製代碼
從上述例子中能夠看出,a 模塊引用了 b 模塊,b 模塊也引用了 a 模塊,export 聲明優先於其餘內容。因爲變量和函數的提高不同,此處不作過多介紹。
此處有一個小插曲,我一開始用瀏覽器進行執行的結果爲:
{
bar: 1
bar2: () => { console.log('bar2'); }
bar3: ƒ bar3()
}
a.js
複製代碼
讓我一度以爲是否是 export 有什麼特殊的聲明提高?由於我發現深刻理解 ES6 模塊機制
一文中是使用的 babel-node
, 是不是由於環境不同致使的。所以我使用了 node v12.16.0
,進行測試 node --experimental-modules a.js
, 發現結果與 深刻理解 ES6 模塊機制
中結果一致,後來想到 console.log
的顯示問題,console.log
經常會有一些異步的顯示。後來我通過測試發現確實是 console.log
搞的鬼
console.log(a);
-> console.log(JSON.stringify(a))
會出現一個 Uncaught ReferenceError: bar is not defined
的錯誤,是由於 bar
未初始化致使。後續也會將這個 console
的表現形式報告給 chromium
。
介紹完了,各個模塊的標準後,爲何又將這個 Tree shaking 呢?由於模塊化的一次又一次地變動,讓咱們的模塊系統變得愈來愈好,而 Tree shaking 就是得益 ES modules 的發展的產物。
這個概念是Rollup提出來的。Rollup推薦使用ES2015 Modules來編寫模塊代碼,這樣就可使用Tree-shaking來對代碼作靜態分析消除無用的代碼,能夠查看Rollup網站上的REPL示例,代碼打包先後以前的差別,就會清晰的明白Tree-shaking的做用。
tree shaking 的實際例子
// main.js
import * as utils from './utils';
const array = [1,2,3,1,2,3]
console.log(utils.arrayUnique(array));
複製代碼
Tree shaking
和 沒有Tree shaking
打包對比。
沒有 Tree-shaking 的狀況下,會將 utils 中的全部文件都進行打包,使得體積暴增。
ES Modules 之因此能 Tree-shaking 主要爲如下四個緣由(摘自尤雨溪在知乎的回答):
import
只能做爲模塊頂層的語句出現,不能出如今 function 裏面或是 if 裏面。import
的模塊名只能是字符串常量。import
的語句出現的位置在哪裏,在模塊初始化的時候全部的 import
都必須已經導入完成。import binding
是 immutable
的,相似 const。好比說你不能 import { a } from ‘./a’ 而後給 a 賦值個其餘什麼東西。沒錯,就是反作用,那麼什麼是反作用呢,請看下面的例子。
// effect.js
console.log(unused());
export function unused() {
console.log(1);
}
複製代碼
// index.js
import {unused} from './effect';
console.log(42);
複製代碼
此例子中 console.log(unused());
就是反作用。在 index.js
中並不須要這一句 console.log
。而 rollup
並不知道這個全局的函數去除是否安全。所以在打包地時候你能夠顯示地指定treeshake.moduleSideEffects
爲 false,能夠顯示地告訴 rollup
外部依賴項沒有其餘反作用。
不指定的狀況下的打包輸出。 npx rollup index.js --file bundle.js
console.log(unused());
function unused() {
console.log(1);
}
console.log(42);
複製代碼
指定沒有反作用下的打包輸出。npx rollup index.js --file bundle-no-effect.js --no-treeshake.moduleSideEffects
console.log(42);
複製代碼
代碼地址: github.com/hua1995116/…
固然以上只是反作用的一種,詳情其餘幾種看查看 rollupjs.org/guide/en/
CommonJS 同步加載, AMD 異步加載, UMD = CommonJS + AMD , ES Module 是標準規範, 取代 UMD,是大勢所趨。 Tree-shaking 牢記反作用。
blog.fundebug.com/2018/08/15/…