如何实现并部署自己的npm解析服务

大家好,我卡颂。

你是否好奇 ------ codesandbox是如何在线运行代码的?

要回答这个问题,我们先看看前端项目是如何在本地跑起来的。简单来说分为3步:

  1. 执行npm install安装依赖

  2. 使用打包工具(比如webpack)打包、编译代码(如果使用Vite会省去打包的步骤,但会执行预构建

  3. 将步骤2的产物通过script标签注入页面

codesandbox能在线运行代码,显然他也实现了上述步骤,具体来说,codesandbox内置了2个在线服务:

  • npm解析服务 ------ 用于实现上述步骤1

  • 在线打包服务 ------ 用于实现上述步骤2、3

本文我们来聊聊如何实现并部署自己的npm解析服务。

欢迎围观朋友圈、加入人类高质量前端交流群,带飞

codesandbox简要工作原理

下面是一个常见的codesandbox界面,包含两部分:

  • 左边的文件系统、代码编辑器

  • 右边的效果预览区域

其中效果预览区域 是一个iframe,对于上图中的例子,iframe的地址是https://pjdp86.csb.app/。如果你打开这个地址,会发现他就是代码的预览效果:

但这并不意味着codesandbox帮我们部署了项目。实际上,这个地址中前端代码是在页面打开后再编译、打包的。

打开codesandbox项目时经常看到的下述界面,就是前端编译代码的画面:

具体来说,当我们打开一个codesandbox项目,iframe对应地址初始化时,会执行如下操作:

  1. 下载项目代码(即编辑器中显示的代码)

  2. 根据项目package.json中指明的依赖,从npm解析服务下载项目依赖的代码

  3. 下载在线打包器(一个mini webpack)、编译器(babel)相关代码

  4. 在线打包、编译

  5. 运行打包后的代码

正是有了在线打包、编译的流程,codesandbox才能在线运行:

  • React项目(需要编译JSX

  • TS项目(需要编译TS语法)

  • Vue项目(需要编译SFC文件)

回到本文的主题 ------ npm解析服务 。当我们从项目package.json中获取到依赖库的名称后,完全可以从CDN直接请求依赖库对应的代码,为什么还需要一个独立的npm解析服务呢?

npm解析服务的作用

之所以需要独立的npm解析服务 ,主要是因为 ------ npm包本身可能还依赖别的npm包,如果每次初始化iframe时依次下载:

  • package.json中指定的依赖

  • 依赖的依赖

  • 依赖的依赖的依赖

  • ...

那会极大拖慢项目初始化的时间。同时,这样做也可能会下载大量实际不会使用的代码。

所以,需要一个npm解析服务,当第一个用户第一次请求某个库时,依次完成:

  1. 从库的入口代码解析AST,分析其中的require语句,递归的解析这个库的依赖

  2. 下载依赖代码,将所有依赖的代码汇总到一个JSON文件

  3. 将步骤2的JSON文件保存在对象存储中

  4. 返回步骤2的JSON文件

那么,后续所有用户在请求这个库时,都能直接从对象存储中直接获取解析好的JSON文件,这能极大提高在线安装依赖的速度。

比如,react@18.2.0经由npm解析服务 解析后会返回如下JSON

json 复制代码
{
  "contents": {
    "/node_modules/react/index.js": {
      // 库的代码
      "content": "...省略",
      "isModule": false,
      // 依赖的其他模块
      "requires": [
        "./cjs/react.production.min.js",
        "./cjs/react.development.js"
      ]
    },
    "/node_modules/react/cjs/react.production.min.js": {/*省略*/},
    "/node_modules/react/cjs/react.development.js": {/*省略*/},
    "/node_modules/js-tokens/package.json": {/*省略*/},
    "/node_modules/loose-envify/package.json": {/*省略*/},
    "/node_modules/react/package.json": {/*省略*/}
  },
  // 库的版本信息
  "dependency": {
    "name": "react",
    "version": "18.2.0"
  },
  "peerDependencies": {},
  // 依赖的依赖
  "dependencyDependencies": {
    "loose-envify": {/*省略*/},
    "js-tokens": {/*省略*/}
  },
  "dependencyAliases": {}
}

上述JSON中,入口代码在/node_modules/react/index.js,通过递归分析他的AST,发现他依赖了:

  • "./cjs/react.production.min.js"

  • "./cjs/react.development.js"

于是,这2个文件对应代码也包含在JSON中。

当下一个用户加载的项目依赖react@18.2.0,就能直接从对象存储中获取上述JSON

npm解析服务的实现

codesandbox在线打包相关的代码都是开源的,比如:

所以,我们可以基于dependency-packager部署自己的npm解析服务

dependency-packager是一个serverless服务,通过AWS Lambda部署。由于采用的是开源的serverless框架,所以我们可以很方便的将项目中AWS Lambda的部分替换成其他serverless服务商(比如阿里云函数计算)。

整个dependency-packager包含两个serverless函数:

  • api:实际对外提供的服务

  • packager:根据包名和版本号生成JSON的服务

他们的关系如下:

其中,生成的JSON保存在AWS S3中。同样,这里也可以替换成其他云服务厂家的存储方案。

packager服务的工作流程如下:

其中,验证依赖的入口文件会尝试下面这些文件后缀:

js 复制代码
const found = [
  path.join(basedir, pkg.module),
  path.join(basedir, pkg.module + ".js"),
  path.join(basedir, pkg.module + ".cjs"),
  path.join(basedir, pkg.module + ".mjs"),
  path.join(basedir, pkg.module, "index.js"),
  path.join(basedir, pkg.module, "index.mjs"),
].find((p) => {
  try {
    const l = fs.statSync(p);
    return l.isFile();
  } catch (e) {
    return false;
  }
});

验证完成后,会以package.json中的modulemain字段作为入口文件,将代码转换为AST,分析AST中的require语句(cjs语法中引入模块的语法),找到依赖的模块。最终将这些模块汇总在JSON中。

总结

codesandbox在线打包相关的代码都是开源的,包括:

  • 编辑器

  • npm解析服务

  • 在线打包服务

其中,npm解析服务作为一个serverless服务包括两部分:

  • api服务

  • packager服务

packager服务代码量不多,如果想尝试部署自己的serverless服务,是个不错的选择。

相关推荐
沙蒿同学9 分钟前
我把架构约定编译成了会变红的测试:Wails v2 + Go + Vue3 桌面脚手架实战
前端·后端·github
cpolar技术支持12 分钟前
本地 Playwright 测试报告怎么远程复盘?Trace Viewer 跑起来后,用 cpolar 分享失败现场
前端·自动化测试·测试工具·cpolar·playwright
wordbaby12 分钟前
企业级后台管理系统路由设计与最佳实践指南
前端
胡志辉的博客21 分钟前
【完全开源】IP 纯净度检测 可一键部署到自己的CF
前端·javascript·chrome·ip·chromium
Hilaku1 小时前
作为面试官,我最怕遇到什么样的候选人?
前端·javascript·程序员
TiDi1 小时前
Pinia优化重复请求
前端
web3d5201 小时前
01-用 Leafletjs 10 分钟搭一张水利一张图(Vue3 + Vite 实战)
前端·javascript
晚安日记wanna2 小时前
Vue2 的 defineProperty 差在哪四层追问筛掉九成候选人
前端·vue.js·面试
TiDi2 小时前
吸顶导航交互实现
前端
kisshyshy2 小时前
从Props透传到自定义Hook:系统梳理React跨层级通信与逻辑复用
前端·架构·代码规范