cmd 是阿里大神玉伯提出的基于浏览器的前端模块化规范,并在 seajs 中实现了这个规范。相对于另一个在国外比较流行的前端模块化规范 amd,cmd 对于 nodejs 的使用者来说更加友好,使得类似 commonJS 模块的写法可以在浏览器中使用,同时解决了浏览器中模块异步加载的困扰。 关于 cmd 更详细的内容可以移步.md 今天,我们一起来学习如何实现一个浏览器端的简单的 cmd loader。
下图展示了一个 cmd loader 的模块加载大体流程:
理解了整个过程,那么我们就来开始实现我们的代码,我们暂且给这个加载器命名为 mcmd 吧。首先是加载器的功能模块划分:
构建
我们使用 commonJS 的方式进行编码,并使用 browserify 配合 gulp 来构建我们的项目。
var gulp = require('gulp');
var uglify = require('gulp-uglify');
var concat = require('gulp-concat');
var browserify = require('browserify');
var source = require('vinyl-source-stream');
var buffer = require('vinyl-buffer')
var pg = require('./package');
var versionName = pg.name + '.' + pg.version
gulp.task('default', ['build']);
gulp.task('build', function () {
browserify('./src/mcmd.js')
.bundle()
.pipe(source(versionName))
.pipe(buffer())
.pipe(concat(versionName + '.js'))
.pipe(gulp.dest('./prd'))
.pipe(uglify())
.pipe(concat(versionName + '.min.js'))
.pipe(gulp.dest('./prd'));
});
确定好了功能划分和构建方式,下面我们就来实现每一个功能模块:
入口文件
将我们的 cmd loader 挂在 d 上,把 define 方法也挂在 window.define 上,初始化其他的方法和配置。
var g = >.html<window;
g.define = require('./define');
g.mcmd = {
use: require('./use'),
require: require('./require'),
// 模块缓存
modules: {},
// 默认配置
config: {
root: '/'
},
// 修改配置
setConfig: function (obj) {
for (var key in obj) {
fig[key]>.html< = obj[key];
}
},
// 模块状态常量
MODULE_STATUS: {
PENDDING: 0,
LOADING: 1,
COMPLETED: 2,
ERROR: 3
}
};
use.js
实现了 mcmd.use 方法,接收两个参数,第一个是id或者id数组,第二个是回调函数。内部会使用 load.js 来获取模块,并通过 promise 来处理获取多个模块的并发异步场景。
var Promise =>.html< require('./promise');
var load = require('./load');
if (!Array.isArray(ids)) {
ids = [ids]
}
Promise.all(ids.map(function (id) {
return + id);
})).then(function (list) {
if (typeof callback === 'function') {
callback.apply(window, list);
}
}, function (errorInfo) {
throw errorInfo;
});
}
load.js
获取一个模块,并绑定事件,接收两个参数,一个是模块id,一个是回调函数,并返回一个 promise 对象。当模块 complete(加载完成)时,执行回调,同时 resolve 返回的 promise 对象。
var Promise>.html< = require('./promise');
var Module = require('./module');
var util = require('./util');
详见:
module.js
模块的构造函数,实现了模块的创建,加载,事件传递,状态维护等。
// 构造函数
function Module(id) {
dules[id] = this; // 缓存模块
this.id = id;
this.status = mcmd.MODULE_STATUS.PENDDING; // 状态
this.factory = null; // 执行代码
this.dependences =>.html< null; //依赖
this.callbacks = {}; // 绑定的事件回调函数
this.load();
}
// 静态方法创建模块
ate = function (id) {
return new Module(id>.html<);
}
// 通过创建 script 标签异步加载模块
Module.prototype.load = function () {
var id = this.id;
var script = ateElement('script');
script.src = id;
r = function (event) {
this.setStatus(mcmd.MODULE_STATUS.ERROR, {
id: id,
error: ( =>.html< new Error('module can not load.'))
});
}.bind(this);
document.head.appendChild(script);
this.setStatus(mcmd.MODULE_STATUS.LOADING);
}
// 事件绑定方法
= function (event, callback) {
(this.callbacks[event]>.html< || (this.callbacks[event] = [])).push(callback);
if (
(this.status ===>.html< mcmd.MODULE_STATUS.LOADING && event === 'load') ||
(this.status === mcmd.MODULE_STATUS.COMPLETED && event === 'complete')
) {
callback(this);
}
if (this.status ===>.html< mcmd.MODULE_STATUS.ERROR && event === 'error') {
callback(this, );
}
}
// 事件触发方法
Module.prototype.fire = function (event, arg) {
(this.callbacks[event] || []).forEach(function (callback) {
callback(arg || this);
}.bind(this));
}
// 设置状态方法,并抛出相应的事件
Module.prototype.setStatus = >.html<function (status, info) {
if (this.status !== status) {
this.status = status;
switch (status) {
case mcmd.MODULE_STATUS.LOADING:
this.fire('load');
break;
case mcmd.MODULE>.html<_STATUS.COMPLETED:
this.fire('complete');
break;
case mcmd.MODULE_STATUS.ERROR:
this.fire('error', info);
break;
default:
break;
}
}
}
实现 window.define 方法。接收一个参数 factory(cmd规范中不止一个,为了保持简单,我们只实现一个),即模块的代码包裹函数。通过 getCurrentScript 这个函数获取到当前执行脚本的 script 节点 src ,提取出模块 id ,找到模块对象。然后提取出 factory 中的依赖子模块,如果没有依赖,则直接触发模块的 “complete” 事件, 如果有依赖,则创建依赖的模块,绑定事件并加载,等依赖的模块加载完成后,再触发 “complete” 事件。
var util =>.html< require('./util');
var Promise = require('./promise');
var Module = require('./module');
// 获取当前执行的script节点
// 参考 .html
function getCurrentScript() {
var doc = document;
if(doc.currentScript) >.html<{
return doc.currentScript.src;
}
var stack;
try {
a.b.c();
} catch(e) {
stack = e>.html<.stack;
if(!stack && window.opera){
stack = (String(e).match(/of linked script S+/g) || []).join(" ");
}
}
if(stack) {
stack = stack.split(>.html< /[@ ]/g).pop();
stack = stack[0] == "(" ? stack.slice(1,-1) : stack;
place(/(:d+)?:d+$/i, "");
}
var nodes>.html< = ElementsByTagName("script");
for(var i = 0, node; node = nodes[i++];) {
adyState === "interactive") {
return node.className >.html<= node.src;
}
}
}
// 解析依赖,这里只做简单的提取,实际需要考虑更多情况,参考seajs
function getDenpendence(factory) {
var list = factory.match(/require(>.html<.+?)/g);
if (list) {
list = list.map(function (dep) {
place(/(^require(['"])|(['"])$)/g, '');
});
}
return list;
}
require.js
返回模块的 exports 属性, 这里通过封装的 ModuleExports 方法获取并返回。
var util = require('./util');
这里只有一个 getModuleExports 方法, 接收一个模块,返回模块的接口。当模块的 exports 属性不存在时,说明模块的 factory 没有被执行过。这时我们需要执行下 factory,传入 require, 创建的exports,以及 module 本身作为参数。最后获取模块的暴露的数据并返回。
整个 mcmd 项目我都放在了 github 上,大家可以去看看:。
本文发布于:2024-02-04 22:49:10,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170718155860435.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |