讀vue原始碼看前端百態1--模組化
Vue中的模組化
以vue2.0為例
在我們執行npm run dev
時,會看到package.json
中,有
"dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev"
複製程式碼
根據 scripts/config.js
檔案中的配置:
// Runtime+compiler development build (Browser)
'web-full-dev': {
entry: resolve('web/entry-runtime-with-compiler.js'),
dest: resolve('dist/vue.js' ),
format: 'umd',
env: 'development',
alias: { he: './entity-decoder' },
banner
}
複製程式碼
這裡注意到format
引數的值為umd
,
注意看這個檔案,在builds
物件中還有
// Runtime+compiler CommonJS build (CommonJS)
'web-full-cjs': {
entry: resolve('web/entry-runtime-with-compiler.js'),
dest: resolve('dist/vue.common.js' ),
format: 'cjs',
alias: { he: './entity-decoder' },
banner
},
// Runtime+compiler CommonJS build (ES Modules)
'web-full-esm': {
entry: resolve('web/entry-runtime-with-compiler.js'),
dest: resolve('dist/vue.esm.js'),
format: 'es',
alias: { he: './entity-decoder' },
banner
},
// Runtime+compiler development build (Browser)
'web-full-dev' : {
entry: resolve('web/entry-runtime-with-compiler.js'),
dest: resolve('dist/vue.js'),
format: 'umd',
env: 'development',
alias: { he: './entity-decoder' },
banner
},
複製程式碼
我們看到三種模組: CommonJS
、ES Modules
和umd
什麼是模組?
- 將一個複雜的程式依據一定的規則(規範)封裝成幾個塊(檔案), 並進行組合在一起
- 塊的內部資料與實現是私有的, 只是向外部暴露一些介面(方法)與外部其它模組通訊
模組化
接下來,我們來學習下常用的模組:
CommonJS
常用的Node便是採用 CommonJS 模組規範。每個檔案就是一個模組,有自己的作用域。
在伺服器端,模組的載入是執行時同步載入的;
在瀏覽器端,模組需要提前編譯打包處理。
我們一起來看一看CommonJS的例子:
// example.js
var x = 5;
var addX = function (value) {
return value + x;
};
module.exports.x = x;
exports.addX = addX;
//index.js
const example = require('./example.js')
console.log(example.x); // 5
console.log(example.addX(1)); // 6
example.x = 6
console.log(example.addX(1)); // 6
執行node index.js
複製程式碼
CommonJS語法
暴露模組:module.exports = value 或 exports.xxx = value
引入模組:require(xxx),如果是第三方模組,xxx為模組名,如require('express');如果是自定義模組,xxx為模組檔案路徑,如上例,require('./example.js')
CommonJS載入機制
引入的是暴露值的拷貝,所以要注意
// example.js
var x = {
name: 'kitty'
};
var outputX = function (value) {
return x;
};
module.exports= {
x,
outputX
}
// index.js
const example = require('./example.js')
console.log(example.x); // { name: 'kitty' }
console.log(example.outputX()); // { name: 'kitty' }
example.x.name = 'cat'
console.log(example.x); // { name: 'cat' }
console.log(example.outputX()); //{ name: 'cat' }
執行node index.js
複製程式碼
CommonJS在瀏覽器的實現
// example.js 如上
// example2.js
module.exports = function() {
console.log('example2')
}
// index.js
const example = require('./example.js')
const example2 = require('./example2.js')
example2();
console.log(example.x);
// index.html
<html>
<body>
<script src="./index.js"></script>
</body>
<html>
複製程式碼
直接啟動index.html
, 開啟控制檯,會發現:
上面說了,模組需要提前編譯打包處理。 這裡我們用browerify打包一下
// 全域性安裝
npm install browserify -g
// 根目錄下執行
browserify index.js -o bundle.js
// index.html替換script引用地址
<html>
<body>
<script src="./bundle.js"></script>
</body>
<html>
複製程式碼
可以看一看打包過後的bundle.js
直接啟動index.html
, 開啟控制檯,會發現,哈哈,你成功了!!!
ES6模組
ES6 模組的設計思想是儘量的靜態化,使得編譯時就能確定模組的依賴關係,以及輸入和輸出的變數。CommonJS 和 AMD 模組,都只能在執行時確定這些東西。比如,CommonJS 模組就是物件,輸入時必須查詢物件屬性。
是不是感覺似懂非懂,沒有關係,我們就是例子為王。
ES6模組語法
暴露模組:export命令用於規定模組的對外介面,比如export xxx
, xxx是一個物件;或者指定預設輸出,用到export default命令,比如export default xxx
。
引入模組:import命令用於引入其他模組提供的功能。比如import xxx from **
,其中xxx是要載入的變數名或函式名;指定預設輸出時,xxx可以為匿名函式指定的任意名字。
ES6載入機制
CommonJS引入的是暴露值的拷貝,而ES6是對值的引用。
// example.js
export let x = 5;
export function addX () {
return x++;
};
//index.js
import { x, addX } from './example.js'
console.log(x); // 5
addX()
console.log(x); // 6
複製程式碼
執行node index.js
,怎麼回事?
SyntaxError: Unexpected token import
複製程式碼
這是因為node尚未支援ES6的module方式,所以我們需要babel-cli進行將ES6編譯為ES5程式碼。
- 更換目錄
example.js -> src/example.js
index.js -> src/index.js
複製程式碼
2.全域性安裝babel-cli
npm install babel-cli -g
3.定義.babelrc
檔案
{
"presets": ["es2015"]
}
複製程式碼
4.使用ES6編譯為ES5程式碼:
babel src -d lib
複製程式碼
- 好了,你可以進入lib資料夾,執行
node index.js
,就可以輸出結果了。
ES6在瀏覽器的實現
// index.html
<html>
<body>
<script src="./lib/index.js"></script>
</body>
<html>
複製程式碼
有報錯了,但是是不是看起來很熟悉呢?
是的!
模組需要提前編譯打包處理。
你知道怎麼做了?
答對了!
// 全域性安裝
npm install browserify -g
// 根目錄下執行
browserify lib/index.js -o bundle.js
// index.html替換script引用地址
<html>
<body>
<script src="./bundle.js"></script>
</body>
<html>
複製程式碼
當然可以看看lib資料夾中的ES6轉換ES5程式碼以及打包過後的bundle.js,這裡就不說了。 直接啟動index.html, 開啟控制檯,會發現,哈哈,你成功了!!!
UMD
UMD (Universal Module Definition), 希望提供一個前後端跨平臺的解決方案(支援AMD與CommonJS模組方式)。
CommonJS載入模組是同步的,Node.js主要用於伺服器程式設計,模組檔案一般已經存在於本地磁碟,所以載入起來比較快,所以CommonJS規範比較適用;
而AMD是非同步載入模組,允許指定回撥函式,在瀏覽器環境下,要從伺服器載入模組,這時就必須採用非同步模式,因此瀏覽器一般採用的是AMD規範。
AMD語法規範
暴露模組:
define([有依賴模組,無依賴可以省略],
function() {
return 模組
})
複製程式碼
引入模組:
require([依賴模組],callback)
複製程式碼
AMD載入機制
RequireJS是一個工具庫,主要用於客戶端的模組管理。它的模組管理遵守AMD規範,RequireJS的基本思想是,通過define
方法,將程式碼定義為模組;通過require
方法,實現程式碼的模組載入。
AMD在瀏覽器的實現
// example.js
define(function (){
var add = function (x,y){
return x+y;
}
return {
add
}
}
// index.js
(function () {
require.config({
paths: {
example: './example' // 不能寫example.js會報錯
}
})
require(['example'], function(example) {
console.log(example.add(2, 2))
})
})()
// require.js
複製這個
https://requirejs.org/docs/release/2.3.6/minified/require.js的程式碼
// index.html
<html>
<body>
<script data-main="./index.js" src="./require.js"></script>
</body>
<html>
複製程式碼
直接啟動index.html, 開啟控制檯,會發現,哈哈,你成功了!!!
開啟控制檯network,看到分步載入
回頭看一下Vue原始碼中的umd格式的打包檔案./dist/vue.runtime.js
UMD的實現很簡單:
- 先判斷是否支援Node.js模組格式(exports是否存在),存在則使用Node.js模組格式。
- 再判斷是否支援AMD(define是否存在),存在則使用AMD方式載入模組。
- 前兩個都不存在,則將模組公開到全域性(window或global)。