Node.js技术原理分析系列6——基于 V8 封装一个自己的 JavaScript 运行时

Node.js 技术原理分析系列 —— Node.js 调试能力分析 Node.js 提供了丰富而强大的调试能力,从简单的控制台日志到复杂的性能分析工具。掌握这些调试技术对于构建高质量、高性能的 Node.js 应用至关重要。随着 Node.js 的不断发展,其调试能力也在持续增强,为开发者提供更好的工具来解决复杂问题。通过合理利用这些调试能力,开发者可以更高效地诊断和解决 Node.js 应用中的各种问题,从而提高应用的稳定性和性能。 阅读详情

Node.js 是一个开源的、跨平台的JavaScript运行时环境,它允许开发者在服务器端运行JavaScript代码。Node.js 是基于Chrome V8引擎构建的,专为高性能、高并发的网络应用而设计,广泛应用于构建服务器端应用程序、网络应用、命令行工具等。

本系列将分为9篇文章为大家介绍 Node.js 技术原理:从调试能力分析到内置模块新增,从性能分析工具 perf_hooks 的用法到 Chrome DevTools 的性能问题剖析,再到 ABI 稳定的理解、基于 V8 封装 JavaScript 运行时、模块加载方式探究、内置模块外置以及 Node.js addon 的全面解读等主题,每一篇都干货满满。

在上一节中我们探讨了 Node.js 中的 ABI 稳定相关内容,在本节中则主要分享《基于 V8 封装一个自己的 JavaScript 运行时》相关内容,本文内容为本系列第6篇,由体验技术团队曹杨毅原创,以下为正文内容。

前言

Google 推出的 V8 引擎,自 2008 年随 Chrome 浏览器面世以来,大幅提升了 JavaScript 性能,重新定义了其应用范畴。作为 Chrome 和 Node.js 的核心动力,V8 推动 JavaScript 成为了一个跨越前后端的全栈开发语言。

但是 V8 本身只是一个 JavaScript 解释器,能够对 JavaScript 语言进行解释,并不具备与操作系统,或者其他软件或者资源交互的能力。但是在代码执行中, 我们往往根据我们的需要调用计算机的某些资源或者和其他软件发生交互。 为了使用户能够根据自己的需要,使用 JavaScript 脚本去完成自己的某些目的, V8 解释器提供了丰富的 API 使我们能够方便的根据自己的需要去扩展扩展 JavaScript 脚本的能力,例如:使用 Node.js 执行的 JavaScript 脚本具备文件操作和网络功能,在浏览器中执行的 JavaScript 脚本具备操作 HTML 文档的能力。这也是我们平时使用的最多的 JavaScript 运行时。

相比起浏览器(包括完整网络功能和 HTML 文本渲染系统)这样的 Javascript 运行时,node(只是提供了一些调用操作系统的 API)能够帮助我们能更加直观的去理解 JavaScript 运行时的实现和工作原理。文件的操作和向控制台输出是 node 具备的两种基础的能力。在接下来的叙述中,本文通过实现一个具备文件写和向控制台输出能力的 JavaScript 运行时,来向大家展示在浏览器和 node 中是如何使用 V8 的。

实验目标

在这个迷你运行时中实现这样的 2 个 JavaScript 接口:

  • function print(data: string): void;

往标准输出流输出字符串,该功能跟大家所熟悉的各大编程语言中的 print 函数是一样的。

  • function writeFile(path: string, data: string): void;

往文件里面写入字符串。

当我们的运行时做好之后,我们应该能用它去执行这样的一段 JavaScript 代码

print("Hello World!");
// 预期应该能在命令行终端打印出:Hello World!

writeFile("test.txt", "Hello World!");
// 预期应该能生成test.txt,并在txt文档里面写入了一个字符串:Hello World!

获取V8静态库

我们使用 V8 来构建我们的 JavaScript 运行时,我们需要在项目中做两件事情:第一件事是在项目中包含 V8 的头文件,第二件事是在项目中包含该依赖的动态库或者静态库。(本文采用了在 C++ 中引入库最简单的方式在构建阶段直接引入静态库)。

我们先构建一个 V8 的静态库,然后再将该库引入到我们的项目中。在 V8 静态库的构建过程中,会涉及一些访问外网下载依赖的过程,所以需要确保自己的环境是能通外网的(挂代理或者直接购买使用香港、新加坡等 region 的云服务器)。

在机器上执行如下命令,进行 V8 的构建(建议使用非 root 用户)

# 准备谷歌的工具包
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
pushd depot_tools
git checkout bb2fc21a89ff58de78a2d3361a8adfca2975f773
popd
exportPATH=`pwd`/depot_tools:$PATH

# 更新依赖,这一步花时间比较久。更新完成之后它会打出帮助文档,属于正常现象。
gclient

# 下载 V8 工程代码,进入源码工程中,切换到 12.4.1 版本
fetch v8
cd v8
git checkout 22fed79ddf53976802b5275e8831776b11ac0faa

# 切完分支需要执行一次 gclient sync 操作
gclient sync

# 生成配置文件
tools/dev/v8gen.py x64.release.sample

# 把 V8 代码编译成静态链接库
ninja -C out.gn/x64.release.sample v8_monolith

生成 V8 的静态库 libv8_monolith.a, 我们将我们需要的头文件( V8 源码里面的 include 文件夹)和该静态库取出来备用。

编写主程序

在 my_file.cc 中,我们用 C++ 实现 Example::print 函数以及 Example::print 函数

// function writeFile(path: string, data: string): void; 
voidExample::WriteFile(constFunctionCallbackInfo<Value> &args) {
    Isolate* isolate = args.GetIsolate();
    v8::String::Utf8Valuestr(isolate, args[1]);
    v8::String::Utf8Valuepath(isolate, args[0]);
    FILE* outFile = fopen(*path, "w");
    if (outFile != nullptr) {
        fputs(*str, outFile);
        fclose(outFile);
    } else {
        fprintf(stderr, "Unable to open file: %s\n", *path);
    }
}

// function print(data: string): void;
voidExample::Print(constFunctionCallbackInfo<Value> &args) {
    Isolate* isolate = args.GetIsolate();
    v8::String::Utf8Valuestr(isolate, args[0]);
    printf("%s\n", *str);
}

将我们用 C++ 实现的函数注入到 JavaScript 执行环境中

void register_builtins(Isolate* isolate, Local<Object> global) {
    // 创建 Example 对象模板
    Local<ObjectTemplate> Example = ObjectTemplate::New(isolate);
    // 设置 Example 对象的方法
    Example->Set(
      String::NewFromUtf8(isolate, "print", NewStringType::kNormal).ToLocalChecked(),
      FunctionTemplate::New(isolate, Example::Print)
    );
    Example->Set(
      String::NewFromUtf8(isolate, "writeFile", NewStringType::kNormal).ToLocalChecked(),
      FunctionTemplate::New(isolate, Example::WriteFile)
    );

    // 将 Example 对象挂到 global 对象上
    Local<Object> exampleInstance = Example->NewInstance(isolate->GetCurrentContext()).ToLocalChecked();
    global->Set(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "example", NewStringType::kNormal).ToLocalChecked(), exampleInstance).FromJust();
}

接着 V8 的 API 来构造我们的主程序,这部分内容比较固定。主要就是使用 V8 来创建一个 JavaScript 的执行环境用来执行我们的 JavaScript 代码,我们在 V8 官方示例上按需稍加修改就行。

using namespace v8;
using namespace Example;

int main(int argc, char* argv[]) {
V8::InitializeICUDefaultLocation(argv[0]);
V8::InitializeExternalStartupData(argv[0]);
std::unique_ptr<Platform> platform = platform::NewDefaultPlatform();                                                    // V8 的一些通用初始化逻辑
V8::InitializePlatform(platform.get());
V8::Initialize();
Isolate::CreateParams create_params;                                                                                    // 创建 Isolate 时传入的参数
  create_params.array_buffer_allocator = ArrayBuffer::Allocator::NewDefaultAllocator();
Isolate* isolate = Isolate::New(create_params);                                                                         // 创建一个 Isolate,V8 的对象
  {
    Isolate::Scopeisolate_scope(isolate);
    HandleScopehandle_scope(isolate);                                                                                    // 创建一个 HandleScope,用于下面分配 Handle
    Local<ObjectTemplate> global = ObjectTemplate::New(isolate);                                                          // 创建一个对象模版,用于创建全局对象
    Local<Context> context = Context::New(isolate, nullptr, global);                                                      // 创建一个上下文
    Context::Scopecontext_scope(context);
    Local<Object> globalInstance = context->Global();                                                                     // 获取 JS 全局对象
    register_builtins(isolate, globalInstance);                                                                           // 注册 C++ 模块
    {
      if(argc < 2) {
        fprintf(stdout, "Usage: %s filename\n", argv[0]);
        return1;
      }
      char* filename = argv[1];                                                                                           // 打开 JS 文件,将 JS 文件内容读取到内存
      int fd = open(filename, 0, O_RDONLY);
      struct stat info;
      fstat(fd, &info);
      char *ptr = (char *)malloc(info.st_size + 1);
      read(fd, (void *)ptr, info.st_size);
      ptr[info.st_size] = '\0';

      Local<String> source = String::NewFromUtf8(isolate, ptr, NewStringType::kNormal, info.st_size).ToLocalChecked();    // 要执行的 JS 代码
      Local<Script> script = Script::Compile(context, source).ToLocalChecked();                                           // 将 JS 代码编译成字节码
      free(ptr);                                                                                                          // 内存中的 JS 代码已经用不上了,释放掉这部分内存
      script->Run(context).ToLocalChecked();                                                                              // 执行 JS
    }
  }

  isolate->Dispose();
v8::V8::Dispose();
delete create_params.array_buffer_allocator;
return0;
}

功能验证

首先将我们的"迷你运行时"编译出来,得到一个可执行文件 my_node(这个名字是我们编译的时候设置的)

接下来我们就能使用我们熟悉的 JavaScript 语法来编写我们的 JavaScript 代码。

我们编写了一段测试用例,用来测试我们的 print 函数和 writeFile 函数是否正常使用。为了表现出 V8 引擎的功能是正常的,我们这里把用例稍微多写几句,多使用一些 JavaScript 语法

const { print, writeFile } = example;

// 简单测试我们实现的 print 函数
print("Hello World!");

// 简单测试我们实现的 writeFile 函数
writeFile("test.txt", "Hello World!");

// 测试 class 关键字
classA{
    sum(a, b){
        return  a + b;
    }
}
let a = newA();
print("1 + 2 = " + a.sum(1, 2));

// 测试 for 关键字
let sum = 0;
for(let i = 0; i < 10; i++){
    sum += i;
}
print("sum = " + sum);

// 测试 function 关键字
functionfunc(){
    print("This is My Function!");
}
func();

// 测试 json 序列化
print(JSON.stringify({TEXT: 'Hello World!'}));

接下来使用我们的"迷你运行时"来执行这个文件,发现它们均能够顺利执行。

小结

本文旨在帮助大家理解在 node 和浏览器中是如何使用 V8 的,在 node 当中通过本文叙述的方式使用 V8 API 向 JavaScript 执行环境中注入了文件操作和网络通讯相关的函数,在浏览器中则 使用 V8 API 向 JavaScript 执行环境中注入了 DOM API 来操作 HTML 文档

完整代码:

https://github.com/caoyangyicn/my_node

参考链接:

https://juejin.cn/post/7213994652487172151

https://v8.dev/docs/embed


下一节,将分享《Node.js模块加载方式分析》相关内容,请大家持续关注本系列内容~学习完本系列,你将获得:

  • 提升调试与性能优化能力
  • 深入理解模块化与扩展机制
  • 探索底层技术与定制化能力

同时欢迎大家给OpenTiny提建议:【OpenTiny调研征集】共创技术未来,分享您的声音!

关于OpenTiny

欢迎加入 OpenTiny 开源社区。添加微信小助手:opentiny-official 一起参与交流前端技术~

OpenTiny 官网:https://opentiny.design

OpenTiny 代码仓库:https://github.com/opentiny

TinyVue 源码:https://github.com/opentiny/tiny-vue

TinyEngine 源码: https://github.com/opentiny/tiny-engine

欢迎进入代码仓库 Star🌟TinyEngine、TinyVue、TinyNG、TinyCLI~ 如果你也想要共建,可以进入代码仓库,找到 good first issue标签,一起参与开源贡献~

高级图像增强——模拟雨天,雾,雪,阳光,阴影,运动模糊,畸变等14种效果,同步扩充标签文件 本文提供了一些高级的,特殊的图像增强方法,具体包括、光。 阅读详情

相关推荐

OpenV2X 车路协同开源平台 Columbia 版本正式发布!

OpenV2X车路协同开源平台Columbia 版本正式发布!

weixin_65571684的博客 1198

node.js 详解

node.js 详解

m0_55557411的博客 5万+

5G时代必知:LDPC与Polar码实战解析(附MATLAB仿真代码)

本文深入解析5G时代核心信道编码技术LDPC码与Polar码的原理差异及性能表现,通过MATLAB仿真代码展示其在实际通信系统中的应用。LDPC码适用于大数据块高速传输,而Polar码在小数据块低时延场景表现优异,帮助开发者优化5G系统设计。

sugar的博客 643

【Nodejs】Node.js简介

Node 的重要性已经不言而喻,很多互联网公司都已经有大量的高性能系统运行在 Node 之上。Node 凭借其单线程、异步等举措实现了极高的性能基准。此外,目前最为流行的 Web 开发模式是前后端分离的形式,即前端开发者与后端开发者在自己喜欢的 IDE 上独立进行开发,然后通过 HTTP 或是 RPC 等方式实现数据与流程的交互。

DomCode 5362

HFSS - 双频微带天线设计与仿真

一、设计指标 中心频率:1.9GHz、2.45GHz 匹配情况:中心频率处S11S_{11}S11​参数小于-20dB 介质板厚度:1.6mm 介质板介电常数:3.8 馈电方式:50Ω\OmegaΩ同轴线馈电 二、双频微带天线 获得双频工作的一种最简单的方法是辐射贴片的长度对应-一个频率谐振,其宽度对应另一个频率谐振,然后从对角线的一角馈电,就能使同一个辐射贴片工作于两个频率上。其结构如下图所示。 从图中可以得知,x轴上的A点(x,0)为激发TM10_{10}10​模式的50Ω\OmegaΩ馈电点,由

MaxWei 1万+

大彩串口屏+LUA避坑指南:从Flash读写到控件状态配置的5个常见问题

本文针对大彩串口屏的LUA脚本开发,深度剖析了Flash数据存储与控件状态配置中的5个常见陷阱。重点讲解了如何通过标志位安全初始化Flash、正确处理多字节数据转换,以及如何精准配置按钮与文本控件的状态与事件响应,帮助开发者提升开发效率与代码可靠性。

weixin_27945229的博客 244

node.js运行环境_Node.js是一个很棒的运行时环境-这就是为什么要使用它的原因

node.js运行环境 可扩展,可扩展,易于使用,自给自足和高效运行时环境简介 (An introduction to the scalable, extensible, easily available, self-sufficient, and highly effective runtime environment) Node.js is a cross-platform runtime e...

cumian9828的博客 1499

DirectX function “GetDeviceRemovedReason“ failed with DXGI_ERROR_DEVICE_HUNG (“The GPU will not resp

游戏报错"DXGI_ERROR_DEVICE_HUNG"的解决方案 在使用RTX 40系显卡运行FIFA 23等游戏时,可能出现DirectX报错导致游戏崩溃。这主要由于最新显卡驱动与DirectX 12的兼容性问题。解决方法包括: 降级显卡驱动版本 修改游戏配置文件,将DIRECTX_SELECT设为1 在显卡控制面板中关闭垂直同步、抗锯齿、DLSS 3等功能 调整电源模式为性能优先 这些操作可以有效解决因驱动兼容性导致的游戏崩溃问题。

猿小白的博客 3893

Node -day01

一、Node简介 Node.js是一个能够在服务器端运行JavaScript的开放源代码、跨平台JavaScript运行环境。 Node.js主要用于编写像Web服务器一样的网络应用 官网地址:Node.js 中文官网:Node.js 中文网 1.1 什么是Node.js Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境[在服务器端的运行环境]。 1.2 为什么学习Node.js ● 学习Node.js是为了解前后端交互流程 ● 同时为后面学习前端

ILove_bugs的博客 460

高分六号WFV预处理详细步骤(辐射校正、大气校正、几何校正)

高分六号WFV传感器获得的影像的预处理分为两个步骤:辐射校正和大气校正,辐射校正为了消除传感器的自身误差,大气校正为了消除大气的折射误差。 一、辐射校正 按照红框中信息进行更改。 辐射校正前地物光谱反射率: 校正后的地物光谱曲线的变化: 二、大气校正 ENVI中大气校正一般采用Flaash模型,但是这个模型需要设置许多的参数十分的麻烦,所以采用ENVI商店(app store,这个也需要下载的)里的Flaash Easy To Use 这个插件,可以快速地进行大气校正。 大气校正后的地物反射率 这

qq_41828818的博客 1万+

彩票的未来:基于人工智能与大数据的创新预测模型

随着人工智能(AI)和大数据技术的迅猛发展,彩票行业正面临前所未有的变革。本文将探索如何通过结合深度学习、机器学习和数据挖掘技术,构建先进的预测模型,以提高彩票开奖结果的预测准确性。通过分析历史数据、应用现代化的计算技术,我们提出了一种创新的彩票预测软件架构,旨在推动彩票行业向着更加智能和高效的方向发展。

Sapphire521的博客 1442

import { performance } from ‘node:perf_hooks‘

错误代码出现原因的话可能是node版本问题,要求node版本要大于16的解决。

Xiaochen1239的博客 3594

深入浅出:使用Python爬取链家二手房数据

在数据驱动的时代,房地产数据作为社会经济的重要指标,一直是数据分析师、投资者和研究人员关注的焦点。链家作为国内领先的房产中介平台,其二手房数据具有较高的真实性和时效性。然而,官方API的访问限制和数据获取的门槛,让很多初学者望而却步。本文将带你从零开始,使用Python编写一个符合robots.txt规范的链家二手房爬虫,获取标题、户型、价格等信息。文章不仅会提供完整代码,还会深入讲解反爬策略、请求头伪装、数据解析、异常处理等实战技术。目录前言:为什么要写这篇博客?一、爬虫前的准备:法律与道德。

2201_76125261的博客 91

Node.js 技术原理分析系列 6—— 基于 V8 封装一个自己的 JavaScript 运行时

Google 推出的 V8 引擎,自 2008 年随 Chrome 浏览器面世以来,大幅提升了 JavaScript 性能,重新定义了其应用范畴。作为 Chrome 和 Node.js 的核心动力,V8 推动 JavaScript 成为了一个跨越前后端的全栈开发语言。但是 V8 本身只是一个 JavaScript 解释器,能够对 JavaScript 语言进行解释,并不具备与操作系统,或者其他软件或者资源交互的能力。但是在代码执行中, 我们往往根据我们的需要调用计算机的某些资源或者和其他软件发生交互。

2401_83574175的博客 1085

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

Node.js技术原理分析系列1——Node.js调试能力分析

本文由体验技术团队屈金雄原创。Node.js 是一个开源的、跨平台的 JavaScript 运行时环境,它允许开发者在服务器端运行 JavaScript 代码。Node.js 是基于 Chrome V8引擎构建的,专为高性能、高并发的网络应用而设计,广泛应用于构建服务器端应用程序、网络应用、命令行工具等。

OpenTiny的博客 1813

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

【Node.js】深入理解 V8 JavaScript 引擎

V8 是由 Google 开发的开源 JavaScript 引擎,用 C++ 编写,最初用于 Chrome 浏览器,现在广泛用于其他项目,包括 Node.js 和 Deno。核心特点高性能:通过即时编译 (JIT) 和优化技术提升 JavaScript 执行效率。跨平台支持:运行在 Linux、Windows 和 macOS 等多个平台。多语言支持:支持 JavaScript 和 WebAssembly。

佚名程序员 2806

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

node.js和JavaScript的关系

1. 什么是node.js node.js是一个基于 Chrome V8 引擎的 JavaScript 运行时的环境。 2. 什么是JavaScript 2.1比较JavaScript 和 Java 两者之间没有关系 V8是google开源的JavaScript引擎,用于执行JavaScript;类似JVM执行java字节码。在node.js 诞生之前,V8是内置于Chromeium中的,现在也...

weixin_41160054的博客 5549

Node.js 技术原理分析系列5——理解 Node.js 中的 ABI 稳定

首先,我们需要明确的是,并非所有 node 用户都需要关注“ABI 稳定”这一概念。如果一个用户对 node 的使用,只包含纯 JS 代码,那么用户没必要理解这个概念。但是如果直接或间接使用了 V8 ABI,例如使用 addon、libnode、electron 等技术,就需要对这个概念有所理解。本文主要以 addon 场景为例,对这个概念进行介绍。ABI(Application Binary Interface)直译过来是应用程序二进制接口。与常说的 api 类似,都是用于程序之间的交互。

OpenTiny的博客 1035

Node.js技术原理分析系列8——将Node.js内置模块外置

本人最近的工作涉及 Node.js 的定制开发,直白点说就是,需要修改 Node.js 源码。一般来说,Node.js 源码修改后,需要重新构建才能生效。如果频繁修改,频繁构建,就很费时间。这时,我们可以用内置模块外置这种办法,避免重新构建。

W11929311582的博客 1041

node.js和javascript的关系

node.js是一个基于 Chrome V8引擎的 JavaScript 运行时环境 一、类比JavaScript和java JavaScript java V8 JVM node.js JRE JavaScript和java都是一门编程语言,至于两者的关系,是雷锋和雷峰塔的关系 V8是google开源的JavaScript引擎,用于执行JavaScript,...

【小石头的茅坑】 71万+

《V8 引擎狂飙,Node.js 续写 JavaScript 传奇》

Node.js是一个跨平台JS运行环境,使开发者可以搭建服务器端的JS应用程序使用 Node.js 编写服务器端程序✓ 编写数据接口,提供网页资源浏览功能等等✓ 前端工程化:为后续学习 Vue 和 React 等框架做铺垫前端工程化:开发项目直到上线,过程中集成的所有工具和技术Node.js 是前端工程化的基础(因为 Node.js 可以主动读取前端代码内容)概念:项目是由很多个模块文件组成的好处:提高代码复用性,按需加载,独立作用域使用:需要标准语法导出和导入进行使用。

2301_79896470的博客 1698

Node.js和前端JavaScript有什么区别?

Node.js 和前端 JavaScript 在语法上基本相同,但它们的运行环境、用途、模块系统以及性能优化策略有着显著的区别。前端 JavaScript 专注于浏览器端的交互和界面逻辑,而 Node.js 扩展了 JavaScript 的应用范围,使其能够在服务器端运行,处理文件、网络和数据流等任务。了解这些区别对于选择合适的工具和策略来构建高效、可维护的应用程序至关重要。最后问候亲爱的朋友们,并邀请你们阅读我的全新著作书籍详情。

有我更精彩的博客 1508

PyExecJS支持的8种JavaScript运行时对比:Node.js与PyV8性能测试

PyExecJS是一个能让Python直接运行JavaScript代码的强大工具,通过它开发者可以在Python环境中无缝调用JavaScript功能。然而,PyExecJS的性能表现很大程度上取决于所选择的JavaScript运行时环境。本文将深入对比PyExecJS支持的8种运行时,并重点分析Node.js与PyV8的性能差异,帮助开发者选择最适合自己项目需求的方案。 ## 一、PyExec

gitblog_00072的博客 667

Node.js技术原理分析系列2——如何在Node.js中新增一个内置模块

作为 Node.js 的使用者,想必同学们对“内置模块”这个概念并不陌生:Node.js 内置模块也叫核心模块,预置在 Node.js 运行时中,这些内置模块不需要额外下载安装,可以在 js 代码中通过 require 引入。常用的内置模块包括 fs、http 等对于这些天天使用的功能,大家有没有好奇过它们是怎么开发出来的呢?为了更好地了解 Node.js 的底层实现,我把这套流程自己走了一遍。这里可以跟大家分享一下:如何基于 Node.js 开源代码定制开发,添加一组新的内置模块。

OpenTiny的博客 1466

如何用Node.js运行js文件?

h

VR_tyy的博客 1万+
上一篇: OpenTiny技术直播讲师招募:与开源同行,点亮技术影响力!
下一篇: 直播分享|TinyPro:一行命令,搭建包含前后端的后台管理系统
OpenTiny社区
博客等级 码龄4年 1540粉丝 266原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值