有关于webpack模块化打包工具javascript
流程基本是定义模块,导出模块 ,使用模块,java
好比同时引进jquery和zepto ,这二者同时引用了 '$' ,此时就会产生命名冲突,这样会形成咱们没有办法管理依赖,也没有办法控制依赖加载的顺序,那么咱们有如下几种解决方案node
require
方法‘同步’加载依赖其余模块,经过moudle.exports 导出须要暴露的接口 =>CommonJS 是AMD最具表明性的实现, node 内部实现了Common.jsa.js文件jquery
import.exports = 'Hello world';
复制代码
b.js文件webpack
let str = require(./a.js);
复制代码
若是说require方法在node中是同步的,那么他是如何实现的呢?web
let fs = require('fs');//读取文件
function require(moduleName){ //moduleName路径
let content = fs.readFileSync(moduleName,'utf8');
//最后一个参数是函数的内容体
let fn = new function(‘exports’,'moudle','require','_dirname','_filename',content + '\n return moudle.response') let moudle = {
exports:{}
}
return fn(moudle.exports,moudle,require,_dirname,_dirname)
}
/* function(‘exports’,'moudle','require','_dirname','filename',content + '\n return moudle.response'){ import.exports = 'Hello world'; return import.exports } */
复制代码
AMD优势json
经过require声明模块数组
define('name',[],function(){
})
define('age',[],function(){
})
require(['name','age'],function(name,age){
console.log(name,age)
})
let factories = {};//将name和依赖绑定
function define(moudlName,dependencies,factory){//模块名字,依赖,工厂函数
factories[moudlName] = factory;
}
let function require(mods,callback){
let result = mods.map(function(mod){
let factory = factory[mod];//取出来每一个相对应的函数
let exports;
exports = factory();
return exports;//执行结果是一个新的数组
})
calback.apllay(null,result)
}
复制代码
这样咱们就实现了简单的AMD的实现,那么还有更复杂的状况 假设咱们加依赖浏览器
经过require声明模块缓存
define('name',[],function(){
})
define('age',['name'],function(name){
return name + 9
})
require(['name','age'],function(name,age){
console.log(name,age)
})
复制代码
##那么如今如何管理依赖呢?
let factories = {};//将name和依赖绑定
function define(moudlName,dependencies,factory){
//模块名字,依赖,工厂函数
factory.dependencies = dependencies;//将依赖系在函数上
factories[moudlName] = factory;
require(dependencies,fucntion(...args)){
factory.applay(null,arguments);
exports = factory.apply(null ,arguments)
};
return exports;
}
let function require(mods,callback){
let result = mods.map(function(mod){
let factory = factory[mod];//取出来每一个相对应的函数
let exports;
exports = factory();
return exports;//执行结果是一个新的数组
})
calback.apllay(null,result)
}
require(['age'],function(name,age){
console.log(name,age)
})
复制代码
ConmonJS规范
下面咱们根据以上三个特色,考虑各类状况,一步步实现一个简单的CommenJS引入功能
首先咱们要引入所用到的模块
a. 读文件
b. 获取文件路径
c. 运行环境
d. 加载策略=>针对js/json/node文件
e. Module
f. 缓存
g. requier方法
let fs = require('fs');
let path = require('path');
let vm = require('vm');
function Module(p) {
this.id = p; // 当前模块的标识
this.exports = {}; // 每一个模块都有一个exports属性
this.loaded = false; // 这个模块默认没有加载完
}
Module._extensions = {
//js优先级高于json,和node
'.js': function (Module) {},
'.json': function (Module) {},
'.node': 'xxx'
}
Module._cacheModule = {}// 根据的是绝对路径进行缓存的
function require(moduleId){//咱们将加载模块以参数形式传递过来
}
复制代码
以上是咱们读取模块必备的条件,下面咱们挨个增长内容, 2. 在require接收到路径的时候,咱们首先要对此路径作解析,假设咱们给个方法_resolveFileName(moduleId)
对路径做出解析
// 解析绝对路径的方法 返回一个绝对路径
Module._resolveFileName = function (moduleId) {
let p = path.resolve(moduleId);
// 没有后缀在加上后缀 若是传过来的有后缀就不用加了
if (!path.extname(moduleId)) {//extname是path内部方法,在这里用到的相似用法,清自行查阅,笔者就先很少作解释了
//keys将一个对象转成数组
let arr = Object.keys(Module._extensions);
//若是没有后蕞名称,由于只有三种状况,咱们挨个对比,在这里是有前后顺序的,假设传过来a,咱们会现识别a.js
for (let i = 0; i < arr.length; i++) {
let file = p + arr[i];
try {
fs.accessSync(file);//accessSync 同步断定分拣是否存在
return file;
} catch (e) {
console.log(e)
}
}
} else {
return p;//若是有后坠就直接查找此文件,无需匹配
}
}
function req(moduleId) {
let p = Module._resolveFileName(moduleId);// p是一个绝对路径
}
复制代码
Module._extensions = {
//js优先级高于json,和node
'.js': function (Module) {},//这里的function指的是加载该类型文件的方法
'.json': function (Module) {},
'.node': 'xxx'
}
function req(moduleId) {
let p = Module._resolveFileName(moduleId);// p是一个绝对路径
if (Module._cacheModule[p]) {
// 模块存在,若是有直接把对象返回便可稍后补充
}
// 表示没有缓存就生成一个模块
let module = new Module(p);
// 加载模块
let content = module.load(p); // 加载模块
Module._cacheModule[p] = module;
module.exports = content; //最终以module.export导出
return module.exports
}
复制代码
在此过程当中 ,咱们生成一个模块,new了一个moudle
, 将路径传过去,还记得上面的代码,在new的过程当中,咱们给模块加了id,exports,load
,而后我加载此模块,而且将它添加到缓存中 load
方法是在实例上调用的,咱们将吧这个方法写在Module
的原型上
//在new以后就有了这些标识
function Module(p) {
this.id = p; // 当前模块的标识
this.exports = {}; // 每一个模块都有一个exports属性
this.loaded = false; // 这个模块默认没有加载完
}
Module.prototype.load = function (filepath) {
//判断加载的文件是json仍是node,仍是js
let ext = path.extname(filepath);
//根据文件类型添加方法
let content = Module._extensions[ext](this); //成功读取文件内容
//this指当前模块的实例 有id exports loaded
return content;
}
复制代码
//exports,require,module
Module.warpper = ['(function(exports,require,module){', '\n})'];
Module._extensions = {
//js优先级高于json,和node
'.js': function (Module) {
let script = fs.readFileSync(module.id, 'utf8');
let fn = Module.warpper[0] + script + Module.warpper[1];
//咱们须要执行此文件,可是eval会在当前做用域向上查找,咱们只想在require以后执行此文件,所以这里使用沙漏限制环境
vm.runInThisContext(fn).call(Module.exports, Module.exports, req, module)
return module.exports;
},
'.json': function (Module) {
return JSON.parse(fs.readFileSync(module.id, 'utf8')); // 读取那个文件
},
'.node': 'xxx'
}
复制代码