前言:最近在 No.js 里实现了一个简单的模块加载器,本文简单介绍一下加载器的实现。

创新互联主要从事网站制作、成都网站制作、网页设计、企业做网站、公司建网站等业务。立足成都服务天等,十年网站建设经验,价格优惠、服务专业,欢迎来电咨询建站服务:18980820575
因为 JS 本身没有模块加载的概念,随着前端的发展,各种加载技术也发展了起来,早期的seajs,requirejs,现在的 webpack,Node.js等等,模块加载器的背景是代码的模块化,因为我们不可能把所有代码写到同一个文件,所以模块加载器主要是解决模块中加载其他模块的问题,不仅是前端语言,c语言、python、php同样也是这样。No.js 参考的是 Node.js的实现。比如我们有以下两个模块。module1.js
- const func = require("module2");func();
 
module2.js
- module.exports = () => {
 - // some code
 - }
 
我们看看如何实现模块加载的功能。首先看看运行时执行的时候,是如何加载第一个模块的。No.js 在初始化时会通过 V8 执行 No.js文件。
- const {
 - loader,
 - process,
 - } = No;
 - function loaderNativeModule() {
 - // 原生 JS模块列表
 - const modules = [
 - {
 - module: 'libs/module/index.js',
 - name: 'module'
 - },
 - ];
 - No.libs = {};
 - // 初始化
 - for (let i = 0; i < modules.length; i++) {
 - const module = {
 - exports: {},
 - };
 - loader.compile(modules[i].module).call(null, loader.compile, module.exports, module);
 - No.libs[modules[i].name] = module.exports;
 - }
 - }
 - function runMain() {
 - No.libs.module.load(process.argv[1]);
 - }
 - loaderNativeModule();runMain();
 
No.js文件的逻辑主要是两个,加载原生 JS 模块和执行用户的 JS。首先来看一下如何加载原生JS模块,模块加载是通过loader.compile实现的,loader.compile是 V8 函数的封装。
- void No::Loader::Compile(V8_ARGS) {
 - V8_ISOLATE
 - V8_CONTEXT
 - String::Utf8Value filename(isolate, args[0].As
 ()); - int fd = open(*filename, 0 , O_RDONLY);
 - std::string content;
 - char buffer[4096];
 - while (1)
 - {
 - memset(buffer, 0, 4096);
 - int ret = read(fd, buffer, 4096);
 - if (ret == -1) {
 - return args.GetReturnValue().Set(newStringToLcal(isolate, "read file error"));
 - }
 - if (ret == 0) {
 - break;
 - }
 - content.append(buffer, ret);
 - }
 - close(fd);
 - ScriptCompiler::Source script_source(newStringToLcal(isolate, content.c_str()));
 - Local
 params[] = { - newStringToLcal(isolate, "require"),
 - newStringToLcal(isolate, "exports"),
 - newStringToLcal(isolate, "module"),
 - };
 - MaybeLocal
 fun = - ScriptCompiler::CompileFunctionInContext(context, &script_source, 3, params, 0, nullptr);
 - if (fun.IsEmpty()) {
 - args.GetReturnValue().Set(Undefined(isolate));
 - } else {
 - args.GetReturnValue().Set(fun.ToLocalChecked());
 - }
 - }
 
Compile首先读取模块的内容,然后调用CompileFunctionInContext函数。CompileFunctionInContext函数的原理如下。假设文件内容是 1 + 1。执行以下代码后
- const ret = CompileFunctionInContext("1+1", ["require", "exports", "module"])
 
ret变成
- function (require, exports, module) {
 - 1 + 1;
 - }
 
所以CompileFunctionInContext的作用是把代码封装到一个函数中,并且可以设置该函数的形参列表。回到原生 JS 的加载过程。
- for (let i = 0; i < modules.length; i++) {
 - const module = {
 - exports: {},
 - };
 - loader.compile(modules[i].module).call(null, loader.compile, module.exports, module);
 - No.libs[modules[i].name] = module.exports;
 - }
 
首先通过loader.compile和模块内容得到一个函数,然后传入参数执行该函数。我们看看原生JS 模块的代码。
- class Module {
 - // ...
 - };
 - module.exports = Module;
 
最后导出了一个Module函数并记录到全局变量 No中。原生模块就加载完毕了,接着执行用户 JS。
- function runMain() {
 - No.libs.module.load(process.argv[1]);
 - }
 
我们看看No.libs.module.load。
- static load(filename, ...args) {
 - if (map[filename]) {
 - return map[filename];
 - }
 - const module = new Module(filename, ...args);
 - return (map[filename] = module.load());
 - }
 
新建一个Module对象,然后执行他的load函数。
- load() {
 - const result = loader.compile(this.filename);
 - result.call(this, Module.load, this.exports, this);
 - return this.exports;
 - }
 
load函数最终调用loader.compile拿到一个函数,最后传入三个参数执行该函数,就可以通过module.exports拿到模块的导出内容。从中我们也看到,模块里的require、module和exports到底是哪里来的,内容是什么。
                网站栏目:一篇带给你No.js的模块加载器实现
                
                URL链接:http://www.csdahua.cn/qtweb/news2/212652.html
            
网站建设、网络推广公司-快上网,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 快上网