1. 程式人生 > >讀vue原始碼看前端百態1--模組化

讀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 }, 複製程式碼

我們看到三種模組: CommonJSES Modulesumd

什麼是模組?

  • 將一個複雜的程式依據一定的規則(規範)封裝成幾個塊(檔案), 並進行組合在一起
  • 塊的內部資料與實現是私有的, 只是向外部暴露一些介面(方法)與外部其它模組通訊

模組化

接下來,我們來學習下常用的模組:

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程式碼。

  1. 更換目錄
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
複製程式碼
  1. 好了,你可以進入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)。