读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)。

猜你喜欢

转载自juejin.im/post/5c249bef6fb9a04a037946ad