如今主流的模块范例
- UMD
- CommonJs
- es6 module
umd 模块(通用模块)
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.libName = factory());
}(this, (function () { 'use strict';})));
假如你在js
文件头部看到如许的代码,那末这个文件运用的就是 UMD
范例
实际上就是 amd + commonjs + 全局变量 这三种作风的连系
这段代码就是对当前运转环境的推断,假如是 Node
环境 就是运用 CommonJs
范例, 假如不是就推断是不是为 AMD
环境, 末了导出全局变量
有了 UMD
后我们的代码和同时运转在 Node
和 浏览器上
所以如今前端大多数的库末了打包都运用的是 UMD
范例
CommonJs
Nodejs
环境所运用的模块体系就是基于CommonJs
范例完成的,我们如今所说的CommonJs
范例也大多是指Node
的模块体系
模块导出
关键字:module.exports
exports
// foo.js
//一个一个 导出
module.exports.age = 1
module.exports.foo = function(){}
exports.a = 'hello'
//团体导出
module.exports = { age: 1, a: 'hello', foo:function(){} }
//团体导出不能用`exports` 用exports不能在导入的时刻运用
exports = { age: 1, a: 'hello', foo:function(){} }
这里须要注重 exports
不能被赋值,能够明白为在模块最先前exports = module.exports
, 由于赋值以后exports
失去了 对module.exports
的援用,成为了一个模块内的局部变量
模块导入
关键字:require
const foo = require('./foo.js')
console.log(foo.age) //1
模块导入划定规矩:
假定以下目次为 src/app/index.js
的文件 挪用 require()
./moduleA
相对途径开首
在没有指定后缀名的状况下
先去寻觅同级目次同级目次:src/app/
-
src/app/moduleA
无后缀名文件 依据javascript
剖析 -
src/app/moduleA.js
js文件 依据javascript
剖析 -
src/app/moduleA.json
json文件 依据json
剖析 -
src/app/moduleA.node
node文件 依据加载的编译插件模块dlopen
同级目次没有 moduleA
文件会去找同级的 moduleA
目次:src/app/moduleA
-
src/app/moduleA/package.json
推断该目次是不是有package.json
文件, 假如有 找到main
字段定义的文件返回, 假如main
字段指向文件不存在 或main
字段不存在 或package.json
文件不存在向下实行 src/app/moduleA/index.js
src/app/moduleA/index.json
src/app/moduleA/index.node
终了
/module/moduleA
绝对途径开首
直接在/module/moduleA
目次中寻觅 划定规矩同上
react
没有途径开首
没有途径开首则视为导入一个包
会先推断moduleA
是不是是一个中心模块 如path
,http
,优先导入中心模块
不是中心模块 会从当前文件的同级目次的node_modules
寻觅
-
/src/app/node_modules/
寻觅划定规矩同上 以导入react
为例先 node_modules 下 react 文件 -> react.js -> react.json -> react.node ->react目次 -> react package.json main -> index.js -> index.json -> index.node
假如没找到 继承向父目次的node_modules
中找 /src/node_modules/
/node_modules/
直到末了找不到 终了
require wrapper
Node
的模块 实际上能够明白为代码被包裹在一个函数包装器
内
一个简朴的require demo
:
function wrapper (script) {
return '(function (exports, require, module, __filename, __dirname) {' +
script +
'\n})'
}
function require(id) {
var cachedModule = Module._cache[id];
if(cachedModule){
return cachedModule.exports;
}
const module = { exports: {} }
// 这里先将援用到场缓存 背面轮回援用会说到
Module._cache[id] = module
//固然不是eval这么简朴
eval(wrapper('module.exports = "123"'))(module.exports, require, module, 'filename', 'dirname')
return module.exports
}
也能够检察:node module 源码
从以上代码我们能够晓得:
- 模块只实行一次 以后挪用猎取的
module.exports
都是缓存哪怕这个js
还没实行终了(由于先到场缓存后实行模块) - 模块导出就是
return
这个变量的实在跟a = b
赋值一样, 基本范例导出的是值, 援用范例导出的是援用地点 -
exports
和module.exports
持有雷同援用,由于末了导出的是module.exports
, 所以对exports
举行赋值会致使exports
操纵的不再是module.exports
的援用
轮回援用
// a.js
module.exports.a = 1
var b = require('./b')
console.log(b)
module.exports.a = 2
// b.js
module.exports.b = 11
var a = require('./a')
console.log(a)
module.exports.b = 22
//main.js
var a = require('./a')
console.log(a)
运转此段代码连系上面的require demo
,剖析每一步历程:
-
实行 node main.js -> 第一行 require(a.js)
,(node
实行也能够明白为挪用了require要领,我们省略require(main.js)
内容) -
进入 require(a)要领: 推断缓存(无) -> 初始化一个 module -> 将 module 到场缓存 -> 实行模块 a.js 内容
,(须要注重 是先到场缓存, 后实行模块内容) -
a.js: 第一行导出 a = 1 -> 第二行 require(b.js)
(a 只实行了第一行) 进入 require(b) 内 同 1 -> 实行模块 b.js 内容
b.js: 第一行 b = 11 -> 第二行 require(a.js)
-
require(a) 此时 a.js 是第二次挪用 require -> 推断缓存(有)-> cachedModule.exports -> 回到 b.js
(由于js
对象援用题目 此时的cachedModule.exports = { a: 1 }
) b.js:第三行 输出 { a: 1 } -> 第四行 修正 b = 22 -> 实行终了回到 a.js
a.js:第二行 require 终了 猎取到 b -> 第三行 输出 { b: 22 } -> 第四行 导出 a = 2 -> 实行终了回到 main.js
main.js:猎取 a -> 第二行 输出 { a: 2 } -> 实行终了
以上就是node
的module
模块剖析和运转的大抵划定规矩
es6 module
ES6
之前 javascript
一向没有属于本身的模块范例,所以社区制订了 CommonJs
范例, Node
从 Commonjs
范例中自创了头脑因而有了 Node
的 module
,而 AMD 异步模块
也一样脱胎于 Commonjs
范例,以后有了运转在浏览器上的 require.js
es6 module
基本语法:
export
export * from 'module'; //重定向导出 不包含 module内的default
export { name1, name2, ..., nameN } from 'module'; // 重定向定名导出
export { import1 as name1, import2 as name2, ..., nameN } from 'module'; // 重定向重定名导出
export { name1, name2, …, nameN }; // 与之前声明的变量名绑定 定名导出
export { variable1 as name1, variable2 as name2, …, nameN }; // 重定名导出
export let name1 = 'name1'; // 声明定名导出 或许 var, const,function, function*, class
export default expression; // 默许导出
export default function () { ... } // 或许 function*, class
export default function name1() { ... } // 或许 function*, class
export { name1 as default, ... }; // 重定名为默许导出
export
划定规矩
-
export * from ''
或许export {} from ''
,重定向导出,重定向的定名并不能在本模块运用,只是搭建一个桥梁,比方:这个a
并不能在本模块内运用 -
export {}
, 与变量名绑定,定名导出 -
export Declaration
,声明的同时,定名导出, Declaration就是:var
,let
,const
,function
,function*
,class
这一类的声明语句 -
export default AssignmentExpression
,默许导出, AssignmentExpression的 局限很广,能够大抵明白 为除了声明Declaration
(实在二者是有交织的),a=2
,i++
,i/4
,a===b
,obj[name]
,name in obj
,func()
,new P()
,[1,2,3]
,function(){}
等等许多
import
// 定名导出 module.js
let a = 1,b = 2
export { a, b }
export let c = 3
// 定名导入 main.js
import { a, b, c } from 'module'; // a: 1 b: 2 c: 3
import { a as newA, b, c as newC } from 'module'; // newA: 1 b: 2 newC: 3
// 默许导出 module.js
export default 1
// 默许导入 main.js
import defaultExport from 'module'; // defaultExport: 1
// 夹杂导出 module.js
let a = 1
export { a }
const b = 2
export { b }
export let c = 3
export default [1, 2, 3]
// 夹杂导入 main.js
import defaultExport, { a, b, c as newC} from 'module'; //defaultExport: [1, 2, 3] a: 1 b: 2 newC: 3
import defaultExport, * as name from 'module'; //defaultExport: [1, 2, 3] name: { a: 1, b: 2, c: 3 }
import * as name from 'module'; // name: { a: 1, b: 2, c: 3, default: [1, 2, 3] }
// module.js
Array.prototype.remove = function(){}
//副作用 只运转一个模块
import 'module'; // 实行module 不导出值 屡次挪用module.js只运转一次
//动态导入(异步导入)
var promise = import('module');
import
划定规矩
-
import { } from 'module'
, 导入module.js
的定名导出 -
import defaultExport from 'module'
, 导入module.js
的默许导出 -
import * as name from 'module'
, 将module.js的
的一切导出合并为name
的对象,key
为导出的定名,默许导出的key
为default
-
import 'module'
,副作用,只是运转module
,不为了导出内容比方 polyfill,屡次挪用次语句只能实行一次 -
import('module')
,动态导入返回一个Promise
,TC39
的stage-3
阶段被提出 tc39 import
ES6 module
特征
ES6 module
的语法是静态的
import
会自动提升到代码的顶层
export
和 import
只能出如今代码的顶层,下面这段语法是毛病的
//if for while 等都没法运用
{
export let a = 1
import defaultExport from 'module'
}
true || export let a = 1
import
的导入名不能为字符串或在推断语句,下面代码是毛病的
import 'defaultExport' from 'module'
let name = 'Export'
import 'default' + name from 'module'
静态的语法意味着能够在编译时肯定导入和导出,越发疾速的查找依靠,能够运用lint
东西对模块依靠举行搜检,能够对导入导出加上范例信息举行静态的范例搜检
ES6 module
的导出是绑定的
运用 import
被导入的模块运转在严厉形式下
运用 import
被导入的变量是只读的,能够明白默许为 const
装潢,没法被赋值
运用 import
被导入的变量是与原变量绑定/援用的,能够明白为 import
导入的变量不管是不是为基本范例都是援用通报
// js中 基本范例是值通报
let a = 1
let b = a
b = 2
console.log(a,b) //1 2
// js中 援用范例是援用通报
let obj = {name:'obj'}
let obj2 = obj
obj2.name = 'obj2'
console.log(obj.name, obj2.name) // obj2 obj2
// es6 module 中基本范例也按援用通报
// foo.js
export let a = 1
export function count(){
a++
}
// main.js
import { a, count } from './foo'
console.log(a) //1
count()
console.log(a) //2
// export default 是没法 a 的动态绑定 这一点跟 CommonJs 有点相似 都是值的拷贝
let a = 1;
export default a
// 能够用另一种体式格局完成 default 的动态绑定
let a = 1;
export { a as default }
export function count(){
a++
}
// 就跟上面 main.js 一样
上面这段代码就是 CommonJs
导出变量 和 ES6
导出变量的区分
es module 轮回援用
// bar.js
import { foo } from './foo'
console.log(foo);
export let bar = 'bar'
// foo.js
import { bar } from './bar'
console.log(bar);
export let foo = 'foo'
// main.js
import { bar } from './bar'
console.log(bar)
实行 main.js -> 导入 bar.js
bar.js -> 导入 foo.js
foo.js -> 导入 bar.js -> bar.js 已实行过直接返回 -> 输出 bar -> bar is not defined, bar 未定义报错
我们能够运用function
的体式格局处置惩罚:
// bar.js
import { foo } from './foo'
console.log(foo());
export function bar(){
return 'bar'
}
// foo.js
import { bar } from './bar'
console.log(bar());
export function foo(){
return 'foo'
}
// main.js
import { bar } from './bar'
console.log(bar)
由于函数声明会提醒到文件顶部,所以就能够直接在 foo.js
挪用还没实行终了的bar.js
的 bar
要领,不要在函数内运用外部变量,由于变量还未声明(let,const
)和赋值,var
CommonJs 和 ES6 Module 的区分
实在上面我们已说到了一些区分
-
CommonJs
导出的是变量的一份拷贝,ES6 Module
导出的是变量的绑定(export default
是特别的) -
CommonJs
是单个值导出,ES6 Module
能够导出多个 -
CommonJs
是动态语法能够写在推断里,ES6 Module
静态语法只能写在顶层 -
CommonJs
的this
是当前模块,ES6 Module
的this
是undefined
易殽杂点
模块语法与解构
module语法
与解构语法
很轻易殽杂,比方:
import { a } from 'module'
const { a } = require('module')
只管看上去很像,然则不是统一个东西,这是两种完整不一样的语法与作用,ps:两个人撞衫了,穿一样的衣服你不能说这俩人就是统一个人module
的语法: 上面有写 import/export { a } / { a, b } / { a as c} FromClause
解构
的语法:
let { a } = { a: 1 }
let { a = 2 } = { }
let { a: b } = { a: 1 }
let { a: b = 2, ...res } = { name:'a' }
let { a: b, obj: { name } } = { a: 1, obj: { name: '1' } }
function foo({a: []}) {}
他们是差别异常大的两个东西,一个是模块导入导出,一个是猎取对象的语法糖
导出语法与对象属性简写
一样下面这段代码也轻易殽杂
let a = 1
export { a } // 导出语法
export default { a } // 属性简写 导出 { a: 1 } 对象
module.exports = { a } // 属性简写 导出 { a: 1 } 对象
export default
和 module.exports
是相似的
ES6 module 支撑 CommonJs 状况
先简朴说一下各个环境的 ES6 module
支撑 CommonJs
状况,背面零丁说如安在差别环境中运用
由于 module.exports
很像 export default
所以 ES6模块
能够很轻易兼容 CommonJs
在ES6 module
中运用CommonJs
范例,依据各个环境,打包东西差别也是不一样的
我们如今大多运用的是 webpack
举行项目构建打包,由于如今前端开辟环境都是在 Node
环境缘由,而 npm
的包都是 CommonJs
范例的,所以 webpack
对ES6
模块举行扩大 支撑 CommonJs
,并支撑node
的导入npm
包的范例
假如你运用 rollup
,想在ES Module
中支撑Commonjs
范例就须要下载rollup-plugin-commonjs
插件,想要导入node_modules
下的包也须要rollup-plugin-node-resolve
插件
假如你运用 node
,能够在 .mjs
文件运用 ES6
,也支撑 CommonJs
检察 nodejs es-modules.md
在浏览器环境 不支撑CommonJs
node 与 打包东西webpack,rollup
的导入 CommonJs
差别
// module.js
module.export.a = 1
// index.js webpack rollup
import * as a from './module'
console.log(a) // { a: 1, default: { a:1 } }
// index.mjs node
import * as a from './module'
console.log(a) // { default: { a:1 } }
node
只是把 module.exports
团体当作 export default
打包东西除了把 module.export
团体当作 export default
,还把 module.export
的每一项 又当作 export
输出,如许做是为了越发简约 import defaultExport from './foo'
, defaultExport.foo()
import { foo } from './foo'
, foo()
运用 ES6 Module
能够在 es6module example 堆栈中猎取代码在当地举行测试考证
浏览器中运用
你须要起一个Web服务器
来访问,双击当地运转 index.html
并不会实行 type=module
标签
我们能够对 script
标签的 type
属性加上 module
先定义两个模块
// index.js
import module from './module.js'
console.log(module) // 123
// module.js
export default 123
在html
中内联挪用
<!-- index.html -->
<script type="module">
import module from './module.js'
console.log(module) // 123
</script>
在html
中经由过程 script
的 src
援用
<!-- index.html -->
<script type="module" src="index.js"></script>
// 控制台 123
浏览器导入途径划定规矩
https://example.com/apples.mjs
http://example.com/apples.js
//example.com/bananas
./strawberries.mjs.cgi
../lychees
/limes.jsx
data:text/javascript,export default 'grapes';
blob:https://whatwg.org/d0360e2f-caee-469f-9a2f-87d5b0456f6f
补充:
- 不加 后缀名 找不到详细的文件
- 后端能够修正接口
/getjs?name=module
这一类的,不过后端要返回Content-Type: application/javascript
确保返回的是js
,由于浏览器是依据MIME type
辨认的
由于 ES6 Module
在浏览器中兼容并非很好兼容性表,这里就不引见浏览器支撑状况了,我们平常不会直接在浏览器中运用
Nodejs中运用
在 Node v8.5.0
以上支撑 ES Module
,须要 .mjs
扩大名
NOTE: DRAFT status does not mean ESM will be implemented in Node core. Instead that this is the standard, should Node core decide to implement ESM. At which time this draft would be moved to ACCEPTED.
(上面链接能够晓得
ES Module
的状况是
DRAFT
, 属于草拟阶段)
// module.mjs
export default 123
// index.mjs
import module from './module.mjs'
console.log(module) // 123
我们须要实行 node --experimental-modules index.mjs
来启动
会提醒一个 ExperimentalWarning: The ESM module loader is experimental.
该功用是试验性的(此提醒不影响实行)ES Module
中导入 CommonJs
// module.js
module.exports.a = 123 // module.exports 就相当于 export default
// index.mjs
import module from './module.js'
console.log(module) // { a: 123 }
import * as module from './module.js'
console.log(module) // { get default: { a: 123 } }
import { default as module } from './module.js';
console.log(module) // { a: 123 }
import module from 'module'; // 导入npm包 导入划定规矩与 require 差不多
导入途径划定规矩与require
差不多
这里要注重 module
扩大名为 .js
,.mjs
专属于 es module
,import form
导入的文件后缀名只能是.mjs
,在 .mjs
中 module
未定义, 所以挪用 module.exports,exports
会报错
node
中 CommonJs
导入 es module
只能运用 import()
动态导入/异步导入
// es.mjs
let foo = {name: 'foo'};
export default foo;
export let a = 1
// cjs
import('./es').then((res)=>{
console.log(res) // { get default: {name: 'foo'}, a: 1 }
});
webpack中运用
从 webpack2
就默许支撑 es module
了,并默许支撑 CommonJs
,支撑导入 npm
包, 这里 import
语法上面写太多 就不再写了
rollup中运用
rollup
专注于 es module
,能够将 es module
打包为主流的模块范例,注重这里与 webpack
的区分,我们能够在 webpack
的 js
中运用 Commonjs
语法, 然则 rollup
不支撑,rollup
须要 plugin
支撑,包含加载 node_modules
下的包 form 'react'
也须要 plugin
支撑
能够看到 es module
在浏览器
与node
中兼容性差与试验功用的
我们大多时刻在 打包东西 中运用
Tree-shaking
在末了我们说一下常常跟 es module
一同涌现的一个名词 Tree-shaking
Tree-shaking
我们先直译一下 树木摇摆 就是 摇摆树木把上面枯死的树恭弘=叶 恭弘晃下来,在代码中就是把没有用到的代码删除Tree-shaking
最早由 rollup
提出,以后 webpack 2
也最先支撑
这都是基于 es module
模块特征的静态剖析
rollup
下面代码运用 rollup
举行打包:
// module.js
export let foo = 'foo'
export let bar = 'bar'
// index.js
import { foo } from './module'
console.log(foo) // foo
在线运转 我们能够修正例子与导出多种范例
打包效果:
let foo = 'foo';
console.log(foo); // foo
能够看到 rollup
打包效果异常的简约,并去掉了没有用到的 bar
是不是支撑对导入 CommonJs
的范例举行 Tree-shaking
:
// index.js
import { a } from './module'
console.log(a) // 1
// module.js
module.exports.a = 1
module.exports.b = 2
打包为 es module
var a_1 = 2;
console.log(a_1);
能够看到去掉了未运用的 b
webpack
我们下面看看 webpack
的支撑状况
// src/module.js
export function foo(){ return 'foo' }
export function bar(){ return 'bar' }
// src/index.js
import { foo } from './module'
console.log(foo())
实行 npx webpack -p
(我们运用webpack 4,0设置,-p开启天生形式 自动紧缩)
打包后我们在打包文件搜刮 bar
没有搜到,bar
被删除
我们将上面例子修正一下:
// src/module.js
module.exports.foo = function (){ return 'foo' }
module.exports.bar = function (){ return 'bar' }
// src/index.js
import { foo } from './module'
console.log(foo())
打包后搜刮 bar
发明bar
存在,webpack
并不支撑对CommonJs
举行 Tree-shaking
pkg.module
webpack
不支撑 Commonjs
Tree-shaking
,但如今npm
的包都是CommonJs
范例的,这该怎么办呢 ?假如我发了一个新包是 es module
范例, 然则假如代码运转在 node
环境,没有经由打包 就会报错
有一种按需加载的计划
全途径导入,导入详细的文件:
// src/index.js
import remove from 'lodash/remove'
import add from 'lodash/add'
console.log(remove(), add())
运用一个还好,假如用多个的话会有许多 import
语句
还能够运用插件如 babel-plugin-lodash, & lodash-webpack-plugin
但我们不能发一个库就本身写插件
这时候就提出了在 package.json
加一个 module
的字段来指向 es module
范例的文件,main -> CommonJs
,那末module - es module
pkg.module
webpack
与 rollup
都支撑 pkg.module
加了 module
字段 webpack
就能够辨认我们的 es module
,然则另有一个题目就是 babel
我们平常运用 babel
都邑消除 node_modules
,所以我们这个 pkg.module
只是的 es6 module
必需是编译以后的 es5
代码,由于 babel
不会帮我们编译,我们的包就必需是 具有 es6 module 范例的 es5 代码
假如你运用了 presets-env
由于会把我们的代码转为 CommonJs
所以就要设置 "presets": [["env", {"modules":false}]
不将es module
转为 CommonJs
webpack
与 rollup
的区分
-
webpack
不支撑导出es6 module
范例,rollup
支撑导出es6 module
-
webpack
打包后代码许多冗余没法直接看,rollup
打包后的代码简约,可读,像源码 -
webpack
能够举行代码支解,静态资本处置惩罚,HRM
,rollup
专注于es module
,tree-shaking
越发壮大的,精简
假如是开辟运用能够运用 webpack
,由于能够举行代码支解,静态资本,HRM
,插件
假如是开辟相似 vue
,react
等类库,rollup
更好一些,由于能够使你的代码精简,无冗余代码,实行更快,导出多种模块语法
结语
本文章引见了 Commonjs
和 ES6 Module
,导入导出的语法划定规矩,途径剖析划定规矩,二者的区分,轻易殽杂的处所,在差别环境的区分,在差别环境的运用,Tree-shaking
,与 webpack
,rollup
的区分
愿望您读完文章后,能对前端的模块化有更深的相识