前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【CodeSandbox】:Sandpack Packager 解析

【CodeSandbox】:Sandpack Packager 解析

作者头像
WEBJ2EE
发布2021-02-26 16:03:34
1.6K0
发布2021-02-26 16:03:34
举报
文章被收录于专栏:WebJ2EEWebJ2EE
代码语言:javascript
复制
目录
1. CodeSandbox 是什么?
2. Sandpack Packager 是什么?
3. Sandpack Packager 基本流程?
4. Sandpack Packager 源码分析的前置知识
  4.1. 官方仓库
  4.2. 调试前注意事项
  4.3. 【NPM库】:string-hash
  4.4. 【NPM库】:meriyah
  4.5. 【NPM库】:acorn-walk
  4.6. 【NPM库】:pacote
  4.7. 【NPM库】:npm-package-arg
  4.8. 【NPM库】:browser-resolve
  4.9. 【NPM库】:recursiveReaddir
  4.10. 【NPM库】:recursive-readdir-sync
  4.11. 【NPM库】:JSON5
  4.12. 【NPM库】:fs
5. Sandpack Packager 源码分析
  5.1. 关键数据结构
  5.2. 总体流程
  5.3. 依赖关系解析流程-getContents
  5.4. 依赖关系解析流程-findDependencyDependencies
6. 演示

1. CodeSandbox 是什么?

CodeSandbox 是一个在线的代码编辑器,主要聚焦于创建 Web 应用项目。

  • 支持常见文件格式:JavaScript、TypeScript、CSS、Less、Sass、HTML 等。
  • 支持自动代码提示。
  • 该平台的前端版本是开源的。

2. Sandpack Packager 是什么?

CodeSandbox 大体上分3部分:Editor、Packager、Sandbox。

  • Editor(编辑器):主要用于修改文件,CodeSandbox 这里集成了 VSCode, 文件变动后会通知 Sandbox 进行转译。
  • Sandbox(代码运行器):Sandbox 在一个单独的 iframe 中运行, 负责代码的转译(Transpiler)和运行(Evalation)
  • Packager(包管理器):类似于yarn和npm,负责拉取和缓存 npm 依赖。
    • A packager used to aggregate all relevant files from a combination of npm dependencies.
      • The packager installs the dependencies using yarn and finds all relevant files by traversing the AST of all files in the directory of the entry point. It searches for require statements and adds them to the file list. This happens recursively, so we get a dependency graph.

3. Sandpack Packager 基本流程?

  • CodeSandbox 客户端拿到 package.json 之后,将 dependencies 转换为一个由依赖和版本号组成的Combination(标识符, 例如 v1/combinations/babel-runtime@7.3.1&csbbust@1.0.0&react@16.8.4&react-dom@16.8.4&react-router@5.0.1&react-router-dom@5.0.1&react-split-pane@0.1.87.json), 再拿这个 Combination 到服务器请求。服务器会根据 Combination 作为缓存键来缓存打包结果,如果没有命中缓存,则进行打包。
  • 打包实际上还是使用yarn来下载所有依赖,只不过这里为了剔除 npm 模块中多余的文件,服务端还遍历了所有依赖的入口文件(package.json#main), 解析 AST 中的 require 语句,递归解析被 require 模块. 最终形成一个依赖图, 只保留必要的文件.
  • 最终输出 Manifest 文件,它的结构大概如下, 他就相当于 WebpackDllPlugin 的dll.js+manifest.json的结合体:
代码语言:javascript
复制
{
  // 模块内容
  "contents": {
    "/node_modules/react/index.js": {
      "content": "'use strict';↵↵if ....", // 代码内容
      "requires": [                        // 依赖的其他模块
        "./cjs/react.development.js",
      ],
    },
    "/node_modules/react-dom/index.js": {/*..*/},
    "/node_modules/react/package.json": {/*...*/},
    //...
  },
  // 模块具体安装版本号
  "dependencies": [{name: "@babel/runtime", version: "7.3.1"}, {name: "csbbust", version: "1.0.0"},/*…*/],
  // 模块别名, 比如将react作为preact-compat的别名
  "dependencyAliases": {},
  // 依赖的依赖, 即间接依赖信息. 这些信息可以从yarn.lock获取
  "dependencyDependencies": {
    "object-assign": {
      "entries": ["object-assign"], // 模块入口
      "parents": ["react", "prop-types", "scheduler", "react-dom"], // 父模块
      "resolved": "4.1.1",
      "semver": "^4.1.1",
    }
    //...
  }
}

4. Sandpack Packager 源码分析的前置知识

4.1. 官方仓库

代码语言:javascript
复制
https://github.com/codesandbox/dependency-packager#sandpack-packager

4.2. 调试前注意事项

  • 部分逻辑不兼容 Windows 平台
    • 这个项目里面的部分逻辑,是不兼容 windows 平台的,如果你想在 windows 下面运行,那就得改改源码了。
  • 与 Sentry 相关的逻辑可以注掉
  • 与 S3 相关逻辑也可以注释掉
    • dependency-packager 默认会采用 Amazon 的 S3 存储作为缓存,为了测试方便,可以直接注释掉。

4.3. 【NPM库】:string-hash

A fast string hashing function for Node.JS. The hashing function returns a number between 0 and 4294967295 (inclusive).

  • Note that the return value is always an unsigned, 32-bit integer.
代码语言:javascript
复制
const stringHash = require("string-hash");
console.log(stringHash("foo")); // prints "193420387"

4.4. 【NPM库】:meriyah

100% compliant, self-hosted javascript parser with high focus on both performance and stability. Stable and already used in production.

  • Conforms to the standard ECMAScript® 2020 (ECMA-262 10th Edition) language specification
  • Support TC39 proposals via option
  • Support for additional ECMAScript features for Web Browsers
  • JSX support via option
  • Optionally track syntactic node locations
  • Emits an ESTree-compatible abstract syntax tree.
  • No backtracking
  • Low memory usage
  • Very well tested (~99 000 unit tests with full code coverage)
  • Lightweight - ~90 KB minified

4.5. 【NPM库】:acorn-walk

An abstract syntax tree walker for the ESTree format.

代码语言:javascript
复制
const acorn = require("acorn")
const walk = require("acorn-walk")

walk.simple(acorn.parse("let x = 10"), {
  Literal(node) {
    console.log(`Found a literal: ${node.value}`)
  }
})

4.6. 【NPM库】:pacote

Fetches package manifests and tarballs from the npm registry.

代码语言:javascript
复制
// index.js
const pacote = require('pacote');
pacote.manifest('echarts@latest').then(manifest => console.log(manifest));

4.7. 【NPM库】:npm-package-arg

Parses package name and specifier passed to commands like npm install or npm cache add, or as found in package.json dependency sections.

代码语言:javascript
复制
// index.js
const npa = require("npm-package-arg")
const parsed = npa('echarts@latest');
console.log(parsed);

4.8. 【NPM库】:resolve

implements the node require.resolve() algorithm such that you can require.resolve() on behalf of a file asynchronously and synchronously.

4.9. 【NPM库】:recursiveReaddir

Recursively list all files in a directory and its subdirectories. It does not list the directories themselves.

代码语言:javascript
复制
// index.js
var recursive = require("recursive-readdir");
recursive("d:/a", function (err, files) {

    console.log(files);
});

4.10. 【NPM库】:recursive-readdir-sync

A simple Node module for synchronously listing all files in a directory, or in any subdirectories. It does not list directories themselves.

代码语言:javascript
复制
var recursiveReadSync  = require("recursive-readdir-sync");
const files = recursiveReadSync("d:/a");
console.log(files);

4.11. 【NPM库】:JSON5

The JSON5 Data Interchange Format (JSON5) is a superset of JSON that aims to alleviate some of the limitations of JSON by expanding its syntax to include some productions from ECMAScript 5.1.

代码语言:javascript
复制
{
  // comments
  unquoted: 'and you can quote me on that',
  singleQuotes: 'I can use "double quotes" here',
  lineBreaks: "Look, Mom! \
No \\n's!",
  hexadecimal: 0xdecaf,
  leadingDecimalPoint: .8675309, andTrailing: 8675309.,
  positiveSign: +1,
  trailingComma: 'in objects', andIn: ['arrays',],
  "backwardsCompatible": "with JSON",
}

4.12. 【NPM库】:fs

  • fs.readdir
    • 异步读取目录的内容。
代码语言:javascript
复制
- D:/a
-- a.txt
-- b
---- c
------ c.txt
代码语言:javascript
复制
const fs = require('fs');

// 异步地读取目录的内容
fs.readdir('d:/a', (err, files) => {
  if (err) throw err;
  console.log(files);
});
  • fs.stat
    • 用于获取文件状态。
代码语言:javascript
复制
const fs = require('fs');
const path = require("path");

const folder = "d:/a";
fs.readdir(folder, (err, files) => {
    if (err) throw err;

    files.forEach((file) => {
        const absoluteFilePath = path.resolve(folder, file);

        fs.stat(absoluteFilePath, function (err, stats) {
            console.log(absoluteFilePath, file, stats.isDirectory());
        });
    });
});

5. Sandpack Packager 源码分析

5.1. 关键数据结构

  • IDependency:依赖(这个数据结构是我自己加的,只是为了方便描述)
    • Sandpack Packager 接收到依赖解析请求后,内部会将其表示为 IDependency 格式。例如:echarts@latest 即表示为 {name: "echarts", version: "latest"}。
代码语言:javascript
复制
interface IDependency {
  name: string;
  version: string;
}
  • IPackage:对 package.json 解构的描述
    • Sandpack Packager 接收到依赖解析请求后,会通过 yarn 安装请求的依赖,并递归读取本次安装依赖的所有相关模块的 package.json 数据。
代码语言:javascript
复制
export interface IPackage {
  name: string;
  main?: string;
  browser?: string | { [path: string]: string | false };
  unpkg?: string;
  module?: string;
  es2015?: string;
  version: string;
  dependencies?: {
    [depName: string]: string;
  };
  peerDependencies?: {
    [depName: string]: string;
  };
  [key: string]: any;
}
  • IFileContent:文件内容(这个数据结构是我自己加的,只是为了方便描述)
代码语言:javascript
复制
interface IFileContent {
  path: string;
  content: string;
}
  • IFileRequires:文件依赖(这个数据结构是我自己加的,只是为了方便描述))
代码语言:javascript
复制
interface IFileRequires {
  isModule: boolean;
  requires: string[];
}
  • IFileData:文件解析后数据
代码语言:javascript
复制
export interface IFileData {
  [path: string]: {
    content: string;
    isModule: boolean;
    requires?: string[];
  };
}

5.2. 总体流程

functions\packager\index.ts:

5.3. 依赖关系解析流程-getContents

  • functions\packager\packages\find-requires.ts
代码语言:javascript
复制
function findRequires(
  packageName: string,
  rootPath: string,
  packageInfos: { [dep: string]: IPackage },
): Promise<IFileData>

function buildRequireObject(
  filePath: string,
  packagePath: string,
  existingContents: IFileData,
): IFileData
  • functions\packager\packages\resolve-required-files.ts:
    • 作者不讲武德,分析了半天,最后的最后,大片优化逻辑都屏蔽了,这段代码最后返回的数组,至多包含一个元素,就是模块的入口文件....
代码语言:javascript
复制
async function resolveRequiredFiles(
  packagePath: string,
  packageInfo: IPackage,
)

5.4. 依赖关系解析流程-findDependencyDependencies

代码语言:javascript
复制
interface IDependencyDependenciesInfo {
  dependencyDependencies: {
    [depName: string]: {
      parents: string[];
      semver: string;
      resolved: string;
      entries: string[];
    };
  };
  dependencyAliases: {
    [dep: string]: {
      [dep: string]: string;
    };
  };
  peerDependencies: { [depName: string]: string };
}

6. 演示

参考:

How we make npm packages work in the browser; announcing the new packager: https://hackernoon.com/how-we-make-npm-packages-work-in-the-browser-announcing-the-new-packager-6ce16aa4cee6 Creating a parallel, offline, extensible, browser based bundler for CodeSandbox: https://hackernoon.com/how-i-created-a-parallel-offline-extensible-browser-based-bundler-886db508cc31 Sandpack Packager: https://github.com/codesandbox/dependency-packager Webpack.DllPlugin: https://webpack.js.org/plugins/dll-plugin/ NPM Packager Service: http://webpack-dll-prod.herokuapp.com/ https://github.com/cerebral/webpack-packager CodeSandBox 作者 Ives van Hoorne 博客: https://medium.com/@compuives esnextb.in: https://github.com/voronianski/esnextbin 一些工具: https://github.com/meriyah/meriyah https://github.com/darkskyapp/string-hash https://github.com/acornjs/acorn/tree/master/acorn-walk https://github.com/npm/pacote https://github.com/npm/npm-package-arg https://github.com/browserify/resolve https://github.com/jergason/recursive-readdir https://github.com/json5/json5 ReasonML: https://reasonml.github.io/

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-02-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 WebJ2EE 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档