如何实现并部署自己的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服务,是个不错的选择。

相关推荐
Boilermaker199211 分钟前
【Java EE】SpringIoC
前端·数据库·spring
中微子22 分钟前
JavaScript 防抖与节流:从原理到实践的完整指南
前端·javascript
天天向上102437 分钟前
Vue 配置打包后可编辑的变量
前端·javascript·vue.js
趣多多代言人38 分钟前
从零开始手写嵌入式实时操作系统
开发语言·arm开发·单片机·嵌入式硬件·面试·职场和发展·嵌入式
芬兰y1 小时前
VUE 带有搜索功能的穿梭框(简单demo)
前端·javascript·vue.js
好果不榨汁1 小时前
qiankun 路由选择不同模式如何书写不同的配置
前端·vue.js
小蜜蜂dry1 小时前
Fetch 笔记
前端·javascript
拾光拾趣录1 小时前
列表分页中的快速翻页竞态问题
前端·javascript
小old弟1 小时前
vue3,你看setup设计详解,也是个人才
前端
Lefan1 小时前
一文了解什么是Dart
前端·flutter·dart