vue 中的模块化
以 vue2.0 https://github.com/vuejs/vue 为例
在我们执行 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 或 http://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 方式加载模块.
前两个都不存在, 则将模块公开到全局(Windows 或 global).
来源: https://juejin.im/post/5c249bef6fb9a04a037946ad