模块化:更优雅的代码管理
// rollup.config.js
// file 打成单文件 dir 打成 chunk
module.exports = {
input: './src/index.js',
output: [
{
//dir: './dist/cjs',
file: './dist/index-cjs.js',
format: 'cjs'
},
{
//dir: './dist/amd',
file: './dist/index-amd.js',
format: 'amd'
},
{
//dir: './dist/esm',
file: './dist/index-esm.js',
format: 'esm'
},
// iife 和 umd 不支持 spliting 为什么不支持?
{
//dir: './dist/iife',
file: './dist/index-iife.js',
format: 'iife'
},
{
//dir: './dist/umd',
file: './dist/index-umd.js',
format: 'umd',
name: 'res'
}
]
}
// index.js
// 打成单文件的引入
import { safeGet } from './a.js'
safeGet({}, 'a.b.c')
// 打成多文件的引入
import('./a.js').then(({default: d}) => {
console.log(d)
})
// a.js
import { get } from 'lodash'
export const safeGet = (target, path, defaultValue) => {
return get(target, path, defaultValue) || defaultValue
}
AMD
define(模块名字?, [依赖]?, factory) // factory 模块自己的逻辑
define('foo', ['utils', 'bar'], function (utils, bar) {
utils.add(1, 2)
return { name: 'foo' }
})
直接配置依赖路径
RequestJs.config({ paths: {
'jquery': 'jquery CDN 地址'
}})
// 配置后使用
RequestJs(['jquery'], function (jquery) {
//....
})
加载模块
RequestJs(['moduleA'], function (moduleA) {})
定义模块
RequestJs(['moduleA'], [], function () {
return 'hello'
})
行为
define('a', function () {
console.log('a load')
return {
run: function () { console.log('a run') }
}
})
define('b', function () {
console.log('b load')
return {
run: function () { console.log('b run') }
}
})
require(['a', 'b'], function (a, b) {
console.log('main run')
a.run()
b.run()
})
// a load
// b load
// main run
// a run
// b run
结论
require的时候加载了依赖的模块
AMD mini 实现
const def = new Map()
const defaultOptions = {
path: ''
}
// from CDN 加载CDN文件
const _import = (url) => {
return new Promise((resolve, reject) => {
System.import(uel).then(resolve, reject)
})
}
// normal script 加载其他文件
const _load = (url) => {
return new Promise((resolve, reject) => {
const head = document.getElementByTagName('head')[0]
const node = document.createElement('script')
node.type = 'text/javascript'
node.src = url
node.async = true
node.onload = resolve
node.onerror = reject
head.appendChild(node)
})
}
// 为什么不写 let const var? 通过这种方式 直接挂到全局(window)
rj = {}
rj.config = (options) => Object.assign(defaultOptions, options)
// 定义模块 触发是在 require 的时候 define 只是存
define = (name, deps, factory) => {
// todo 参数判断 (省略)
def.set(name, {name, deps, factory})
}
const _getUrl = (dep) => {
const p = location.pathname
return p.splie(0, p.lastIndexOf('/')) + '/' + dep + '.js'
}
// 触发加载依赖的地方
require = (deps, factory) => {
return new Promise((resolve, reject) => {
Promise.all(deps.map(dep => {
// 走 CDN
if (defaultOptions.paths[dep]) return _import(defaultOptions.paths[dep])
// 其他
return _load(_getUrl(dep)).then(() => {
const {deps, factory} = def.get(dep)
// 没有依赖
if (deps.length === 0) { return factory(null) }
// 有依赖
return require(deps, factory)
})
})).then(resolve, reject)
})
.then(instances => factory(...instances))
}
CMD
// sea.js
define('a', function (require, exports, module) {
console.log('a load')
exports.run = function () { console.log('a run') }
})
define('b', function (require, exports, module) {
console.log('b load')
exports.run = function () { console.log('b run') }
})
define('main', function (require, exports, module) {
console.log('main run')
let a = require('a')
a.run()
let b = require('b')
b.run()
})
seajs.use('main')
// main run
// a load
// a run
// b load
// b run
CMD mini 实现
const modules = {}
const exports = {}
sj = {}
const _getUrl = (dep) => {
const p = location.pathname
return p.splie(0, p.lastIndexOf('/')) + '/' + dep + '.js'
}
const getDepsFromFn = (fn) => {
let matches = []
// 1、(?:require\() ——> require( ——> (?:) 非捕获性分组
// 2、(?:['"]) ——> require('
// 3、([^'"]+) ——> a ——> 避免回溯
let reg = /(?:require\()(?:['"])([^'"]+)/g
let r = null
while((r = res.exec(fn.toString())) !== null) {
res.lastIndex
matches.push(r[1])
}
return matches
}
// script 标签加载模块
const _load = (url) => {
return new Promise((resolve, reject) => {
const head = document.getElementByTagName('head')[0]
const node = document.createElement('script')
node.type = 'text/javascript'
node.src = url
node.async = true
node.onload = resolve
node.onerror = reject
head.appendChild(node)
})
}
// 提取依赖 正则表达式 状态机
define (id, factory) => {
const url = _getUrl(id)
const deps = getDepsFromFn(factory)
if (!modules[id]) {
modules[id] = {url, id, factory}
}
}
const _module = this
const _exports = (id) => exports[id] || (exports[id] = {})
// 这加载模块
const _require = (id) => {
return _load(_getUrl(id)).then(() => {
// 加载之后
const {factory, deps} = modules[id]
if (!deps || deps.length === 0) {
factory(_require, _exports(id), _module)
return _exports(id)
}
return sj.use(deps, factory)
})
}
sj.use = (mods, callback) => {
mods = Array.isArray(mods) ? mods : [mods]
return new Promise((resolve, reject) => {
Promise.all(mods.map(mod => {
return _load(_getUrl(mod)).then(() => {
const {factory} = modules[mod]
return factory(_require, _exports(mod), _module)
})
})).then(resolve, reject)
}).then(instances => callback && callback(...instances))
}
CommonJS
文件是一个模块,私有。内置两个变量 module require (exports = module.exports)
一个引入一个导出,就构成了通信的基本结构
缓存,require 会缓存一下
// a.js
var name = 'morrain'
var age = 18
exports.name = name
exports.getAge = function(){
return age
}
// b.js
var a = require('a.js')
console.log(a.name) // 'morrain'
a.name = 'rename'
var b = require('a.js')
console.log(b.name) // 'rename'
引用拷贝还是值拷贝的问题(CMJ 是值拷贝)
// a.js
var name = 'morrain'
var age = 18
exports.name = name
exports.age = age
exports.setAge = function(a){
age = a
}
// b.js
var a = require('a.js')
console.log(a.age) // 18
a.setAge(19)
console.log(a.age) // 18
CMJ mini 实现
const cache = {}
(function(modules) {
const require = (mn) => {
if (cache[mn]) return cache[mn].exports;
let module = cache[mn] = {
name: mn,
exports: {}
}
modules[mn](module, exports, require)
return module.exports
}
return require('index.js')
})({
'a.js': function(module, exports, require) {
// ...
}
})