Как работает веб-пакет CodeSandbox на стороне браузера? Часть 1

JavaScript Webpack
Как работает веб-пакет CodeSandbox на стороне браузера? Часть 1

Обратите внимание на этот вопросCodeSandboxЭто операционная среда браузера с песочницей для поддержки множества популярных шаблонов зданий, например,create-react-app,vue-cli,parcelи т.п. Его можно использовать для быстрого прототипирования, отображения DEMO, устранения ошибок и т. д.

Есть много подобных продуктов, таких какcodepen,JSFiddle,WebpackBin(устаревший).

CodeSandbox является более мощным и может рассматриваться как среда выполнения Webpack на стороне браузера.Он даже поддерживает режим VsCode в версии V3, поддерживает плагины Vscode и режим Vim, а также темы.

Кроме того, CodeSandbox поддерживает автономную работу (PWA). По сути, это может быть близко к опыту программирования локального VSCode.Учащиеся с iPad также могут попробовать разрабатывать на его основе. Поэтому для быстрого прототипирования я обычно использую CodeSandbox напрямую.


содержание



Свинец


Первое впечатление автора о CodeSandbox — это то, что эта штука работает на сервере, верно? Напримерcreate-react-appДля запуска вам нужна среда node, вам нужно установить множество зависимостей через npm, затем упаковать его через Webpack и, наконец, запустить сервер разработки для запуска в браузере.

На самом деле упаковка и запуск CodeSandbox не зависит от сервера, он полностью осуществляется в браузере.Примерная структура выглядит следующим образом:

  • Editor: редактор. В основном используется для изменения файлов, здесь интегрирован CodeSandboxVsCode, будет уведомлен, когда файл будет измененSandboxПеревести, планируется статья, посвященная реализации редактора CodeSandbox
  • Sandbox: бегун кода.Песочница запускается в отдельном iframe, отвечающем за перевод кода (Transpiler) и запуск (Evalation)., Как на верхнем рисунке, Редактор слева и Песочница справа.
  • Packagerменеджер пакетов. Подобно пряже и npm, он отвечает за извлечение и кэширование зависимостей npm.

Автор CodeSandboxIves van Hoorneтакже пыталсяWebpackПеренесено в браузер для запуска, поскольку почти все интерфейсы командной строки теперь создаются с использованием Webpack. Если вы можете портировать Webpack в браузер, вы можете использовать мощную экосистему Webpack и механизм перевода (загрузчик/плагин), недорогую совместимость с различными интерфейсами командной строки.

Однако Webpack слишком тяжелый😱, а сжатый размер составляет 3,5 МБ, что едва ли приемлемо; большая проблема заключается в том, чтобы смоделировать среду работы Node на стороне браузера, что слишком дорого и не стоит потерь.

Поэтому CodeSandbox решил создать свой собственный упаковщик, который легче и оптимизирован для платформы CodeSandbox. Например, CodeSandbox заботится только о построении кода среды разработки, и цель состоит в том, чтобы запустить его. По сравнению с Webpack, следующие функции: вырезать :

  • Режим производства. CodeSandbox учитывает только режим разработки и не требует учета некоторых особенностей производства, таких как
    • сжатие кода, оптимизация
    • Tree-shaking
    • оптимизация производительности
    • разделение кода
  • Вывод в файл, не нужно упаковывать в чанки
  • Связь с сервером.Песочница напрямую транслирует и запускается на месте, в то время как Webpack необходимо установить длительное соединение с сервером разработки для получения команд, таких как HMR.
  • Обработка статических файлов (например, изображений). Эти изображения необходимо загрузить на сервер CodeSandbox.
  • механизм плагинов и т. д.

Так что можно считать, чтоCodeSandbox — это упрощенная версия Webpack, оптимизированная для браузерных сред, например, с использованием рабочих процессов для параллельного перевода.

Упаковщик CodeSandbox использует closeWebpack LoaderAPI, чтобы можно было легко портировать некоторые загрузчики Webpack.Например, этоcreate-react-appреализация (см.исходный код):

import stylesTranspiler from "../../transpilers/style";
import babelTranspiler from "../../transpilers/babe";
// ...
import sassTranspiler from "../../transpilers/sass";
// ...

const preset = new Preset(
  "create-react-app",
  ["web.js", "js", "json", "web.jsx", "jsx", "ts", "tsx"],
  {
    hasDotEnv: true,
    setup: manager => {
      const babelOptions = {
        /*..*/
      };
      preset.registerTranspiler(
        module =>
          /\.(t|j)sx?$/.test(module.path) && !module.path.endsWith(".d.ts"),
        [
          {
            transpiler: babelTranspiler,
            options: babelOptions
          }
        ],
        true
      );
      preset.registerTranspiler(
        module => /\.svg$/.test(module.path),
        [
          { transpiler: svgrTranspiler },
          {
            transpiler: babelTranspiler,
            options: babelOptions
          }
        ],
        true
      );
      // ...
    }
  }
);

Видно, что пресет CodeSandbox и конфигурация Webpack аналогичны по длине.Однако в настоящее время вы можете использовать только пресет, предопределенный CodeSandbox, который не поддерживает конфигурацию, подобную Webpack. Лично я думаю, что это соответствует позиционированию CodeSandbox. Это инструмент быстрого прототипирования. Что вы делаете с Webpack? ?

В настоящее время поддерживаются следующие пресеты:




Базовая структура каталогов

Клиент CodeSandbox с открытым исходным кодом, иначе статьи не будет, его базовая структура каталогов выглядит следующим образом:

  • packages
    • appПриложение CodeSandbox
      • appРеализация редактора
      • embedЗапустите codeandbox, встроенный в веб-страницу
      • sandboxЗапустите песочницу, в которой выполняются сборки и предварительный просмотр кода, что эквивалентно сокращенной версии Webpack.
        • eval
          • preset
            • create-react-app
            • parcel
            • vue-cli
            • ...
          • transpiler
            • babel
            • sass
            • vue
            • ...
        • Компилятор compile.ts
    • commonРазместите общие компоненты, методы инструментов, ресурсы
    • codesandbox-api: инкапсулирует унифицированный протокол для связи между песочницей и редактором (на основе почтового сообщения).
    • codesandbox-browserfs: это «файловая система» на стороне браузера, которая эмулирует API файловой системы NodeJS и поддерживает сохранение или получение файлов локально или из нескольких серверных служб.
    • react-sandpack: SDK, представленный codeandbox, который можно использовать для настройки ваших собственных codeandbox.

Исходный код здесь




Процесс сборки проекта

packager -> transpilation -> evaluation

Строительство песочницы делится на три этапа:

  • PackagerФаза загрузки пакета, которая загружает и обрабатывает все зависимости модуля npm.
  • TranspilationФаза транспиляции, транспиляция всего измененного кода, построение графа зависимостей модуля
  • EvaluationНа этапе выполнения используйтеevalЗапустите код модуля для предварительного просмотра

Ниже будут описаны технические моменты в соответствии с вышеуказанными шагами.



Packager

Несмотря на то, что npm — это «черная дыра», мы не можем жить без него. По сути, примерно анализировать front-end проектnode_modules, 80% состоит из различных зависимостей развития.

Поскольку CodeSandbox уже охватывает часть создания кода, нам не нужноdevDependencies, то естьВ CodeSandbox нам нужно только установить все зависимости, необходимые для запуска фактического кода, что может сократить количество загрузок сотен зависимостей, поэтому пока не беспокойтесь о способности браузера справиться с этим..


WebpackDllPlugin

На метод упаковки зависимостей CodeSandbox влияютWebpackDllPluginВдохновленный DllPlugin упакует все зависимости в однуdllфайл и создайтеmanifestфайл для описания метаданных dll (как показано ниже).

Транспиляция или среда выполнения Webpack могут ссылаться на индекс модуля в манифесте (например,__webpack_require__('../node_modules/react/index.js')) для загрузки модуля в dll. потому чтоWebpackDllPluginЭто предварительный перевод зависимостей перед запуском или переводом, поэтому эту часть зависимого кода можно игнорировать на этапе перевода кода проекта, что может повысить скорость сборки (реальный сценарий не оказывает большого влияния на ускорение упаковки Dll для зависимостей npm):

файл манифеста


Онлайн-сервис упаковки

Основываясь на этой идее, CodeSandbox создает собственную онлайн-службу упаковки.В отличие от WebpackDllPlugin, CodeSandbox предварительно создает файлы манифеста на стороне сервера и не различает Dll-файлы и файлы манифеста. Конкретные идеи заключаются в следующем:

Короче говоря, клиент 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), а затем передать эту комбинацию в запрос сервера. Сервер будет кэшировать результат упаковки в соответствии с Комбинацией в качестве ключа кэша, и если кэш не попал, будет выполнена упаковка.

Упаковка на самом деле все еще используетyarnЧтобы загрузить все зависимости, но здесь, чтобы исключить избыточные файлы в модуле npm, сервер также просматривает все файлы зависимых записей (package.json#main), анализирует инструкции require в AST и рекурсивно анализирует необходимые модули. Окончательный вид Граф зависимостей, в котором хранятся только нужные файлы.

Окончательный выходной файл манифеста, его структура примерно следующая, он эквивалентен комбинации dll.js+manifest.json WebpackDllPlugin:

{
  // 模块内容
  "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",
    }
    //...
  }
}

Бессерверное мышление
Стоит отметить, что серверная часть Packager в CodeSandbox использует Serverless (на основе AWS Lambda).Бессерверная архитектура делает сервис Packager более масштабируемым и может гибко справляться со сценариями с высокой степенью параллелизма. Время отклика Packager значительно улучшилось после использования Serverless, а стоимость также снизилась.

Packager также с открытым исходным кодом,зрители


Отступать

Функции AWS Lambda имеют ограничения, например/tmpПространство может быть не более 500 МБ.Хотя большинство зависимых сценариев упаковки не превышают этот предел, для повышения надежности (например, приведенная выше схема может быть неправильной или некоторые модули могут быть пропущены), у Packager также есть запасной вариант. схема.

Позже автор CodeSanbox разработал новую песочницу, которая поддерживает размещение шагов управления пакетами на стороне браузера, которую можно использовать в сочетании с вышеуказанными методами упаковки. Принцип также относительно прост:При транспиляции модуля, если обнаруживается, что модуль npm, от которого зависит модуль, не найден, ленивая загрузка возвращается с удаленного, Давайте посмотрим, как это работает:

В резервной схеме CodeSandbox не будет загружать все пакеты в package.json, а загрузит их лениво, если поиск модуля завершится ошибкой. Например, при переводе входного файла обнаруживается, что модуль реакции отсутствует в очереди модулей локального кэша, в это время он будет загружен с удаленного, а затем переведен.

То есть, поскольку зависимости модулей будут статически анализироваться на этапе перевода, загружаются только те файлы, которые действительно зависят от них, и нет необходимости загружать весь пакет npm, что экономит затраты на передачу по сети. .

CodeSandbox черезunpkg.comилиcdn.jsdelivr.netдля получения информации о модулях и загрузки файлов, таких как

  • Получить package.json:https://unpkg.com/react@latest/package.json
  • Получите структуру каталогов пакета:https://unpkg.com/antd@3.17.0/?metaЭто рекурсивно вернет всю информацию о каталоге для пакета.
  • Скачать определенные файлы:https://unpkg.com/react@16.8.6/cjs/react.production.min.jsилиhttps://cdn.jsdelivr.net/npm/@babel/runtime@7.3.1/helpers/interopRequireDefault.js



Transpilation

После Packager давайте взглянем на Transpilation, этот этапНачните с входного файла приложения, переведите исходный код, проанализируйте AST, найдите подчиненные зависимые модули, а затем рекурсивно переведите и, наконец, сформируйте «граф зависимостей».:

Весь транспилятор CodeSandbox работает в одном iframe:

Редактор отвечает за изменение исходного кода, и изменения исходного кода будут переданы компилятору через почтовое сообщение, которое будет нестиModule+template

  • ModuleСодержит все содержимое исходного кода и пути к модулям, включая package.json. Компилятор будет читать зависимости npm в соответствии с package.json;
  • templateПредустановка, представляющая компилятор, например.create-react-app,vue-cli, определяет некоторые правила загрузчика для перевода различных типов файлов, а предустановка также определяет шаблон и входные файлы приложения. Из вышеизложенного мы знаем, что эти шаблоны в настоящее время предопределены.

базовый объект

Прежде чем подробно представить транспиляцию, давайте взглянем на некоторые основные объекты и поймем взаимосвязь между этими объектами:

  • ManagerЭто основной объект Sandbox, отвечающий за управление информацией о конфигурации (Preset), зависимостями проекта (Manifest) и обслуживанием всех модулей проекта (TranspilerModule).
  • ManifestИз приведенного выше Packager мы знаем, что Manifest поддерживает всю информацию о зависимых модулях npm.
  • TranspiledModuleПредставляет сам модуль. Он поддерживает результаты перевода, результаты выполнения кода и информацию о зависимых модулях и отвечает за управление переводом (вызов Transpiler) и выполнение определенных модулей.
  • PresetШаблон сборки проекта, такой какvue-cli,create-react-app, Настройте правила перевода файла проекта и структуру каталогов приложения (входной файл).
  • TranspilerЭквивалент загрузчика Webpack, отвечающего за транспилирование файлов указанного типа. например, babel, typescript, pug, sass и т. д.
  • WorkerTranspilerЭто подкласс Transpiler, который планирует пул рабочих для выполнения задач перевода, тем самым повышая производительность перевода.

Manager

Менеджер — это менеджер, который контролирует весь процесс перевода и выполнения в целом. Теперь давайте посмотрим на общий процесс перевода:

В целом ситуацию можно разделить на следующие четыре этапа:

  • фаза конфигурации: на этапе настройки будет создан объект Preset, определен файл ввода и т. д. В настоящее время CodeSandbox поддерживает только несколько ограниченных шаблонов приложений, таких как vue-cli, create-react-app. Соглашения о структуре каталогов различны для разных шаблонов, таких как файлы записей и файлы шаблонов html. К тому же правила обработки файлов разные, например, vue-cli нужно обрабатывать.vueдокумент.
  • Этап загрузки зависимостей: То есть на этапе упаковщика загружаются все зависимости проекта и создается объект манифеста.
  • Изменить фазу расчета: рассчитать новые, обновленные и удаленные модули в соответствии с исходным кодом, переданным редактором.
  • Этап перевода: Перевод действительно запущен.Сначала заново переводятся модули, которые нужно обновить рассчитанные на предыдущем этапе. Затем из входного файла в качестве отправной точки переведите и постройте новый граф зависимостей. Не будет повторного перевода неизмененных модулей и их подмодулей


TranspiledModule

TranspiledModule используется для управления конкретным модулем, который будет поддерживать результаты перевода и операции, информацию о зависимостях модуля, а также управлять переводом и выполнением модуля:

TranspiledModule получит список транспиляторов, соответствующих текущему модулю, из Preset и пройдёт транспилер для перевода исходного кода.В процессе перевода он проанализирует AST, проанализирует оператор импорта модуля и соберет новые зависимости. завершится, он будет рекурсивно переводить список зависимостей. Давайте посмотрим на примерный код:

  async transpile(manager: Manager) {
    // 已转译
    if (this.source)  return this
    // 避免重复转译, 一个模块只转译一次
    if (manager.transpileJobs[this.getId()]) return this;
    manager.transpileJobs[this.getId()] = true;

    // ...重置状态 

    // 🔴从Preset获取Transpiler列表
    const transpilers = manager.preset.getLoaders(this.module, this.query);

    // 🔴 链式调用Transpiler
    for (let i = 0; i < transpilers.length; i += 1) {
      const transpilerConfig = transpilers[i];
      // 🔴构建LoaderContext,见下文
      const loaderContext = this.getLoaderContext(
        manager,
        transpilerConfig.options || {}
      );

      // 🔴调用Transpiler转译源代码
      const {
        transpiledCode,
        sourceMap,
      } = await transpilerConfig.transpiler.transpile(code, loaderContext); // eslint-disable-line no-await-in-loop

      if (this.errors.length) {
        throw this.errors[0];
      }
    }

    this.logWarnings();

    // ...

    await Promise.all(
      this.asyncDependencies.map(async p => {
        try {
          const tModule = await p;
          this.dependencies.add(tModule);
          tModule.initiators.add(this);
        } catch (e) {
          /* let this handle at evaluation */
        }
      })
    );
    this.asyncDependencies = [];

    // 🔴递归转译依赖的模块
    await Promise.all(
      flattenDeep([
        ...Array.from(this.transpilationInitiators).map(t =>
          t.transpile(manager)
        ),
        ...Array.from(this.dependencies).map(t => t.transpile(manager)),
      ])
    );

    return this;
  }


Transpiler

Transpiler эквивалентен загрузчику webpack, а его конфигурация и базовый API такие же, как и у webpack (см.API загрузчика webpack), вероятно, непротиворечивы, такие как цепочка перевода и контекст загрузчика. Давайте посмотрим на основное определение Transpiler:

export default abstract class Transpiler {
  initialize() {}

  dispose() {}

  cleanModule(loaderContext: LoaderContext) {}

  // 🔴 代码转换
  transpile(
    code: string,
    loaderContext: LoaderContext
  ): Promise<TranspilerResult> {
    return this.doTranspilation(code, loaderContext);
  }

  // 🔴 抽象方法,由具体子类实现
  abstract doTranspilation(
    code: string,
    loaderContext: LoaderContext
  ): Promise<TranspilerResult>;

  // ...
}

Интерфейс Transpiler очень прост,transpileПринимает два параметра:

  • codeто есть исходный код.

  • loaderContextПредоставляемый TranspiledModule, он может использоваться для доступа к информации о контексте перевода, такой как конфигурация Transpiler, поиск модулей, зарегистрированные зависимости и т. д. Приблизительный вид выглядит следующим образом:

    export type LoaderContext = {
      // 🔴 信息报告
      emitWarning: (warning: WarningStructure) => void;
      emitError: (error: Error) => void;
      emitModule: (title: string, code: string, currentPath?: string, overwrite?: boolean, isChild?: boolean) => TranspiledModule;
      emitFile: (name: string, content: string, sourceMap: SourceMap) => void;
      // 🔴 配置信息
      options: {
        context: string;
        config?: object;
        [key: string]: any;
      };
      sourceMap: boolean;
      target: string;
      path: string;
      addTranspilationDependency: (depPath: string, options?: { isAbsolute?: boolean; isEntry?: boolean; }) => void;
      resolveTranspiledModule: ( depPath: string, options?: { isAbsolute?: boolean; ignoredExtensions?: Array<string>; }) => TranspiledModule;
      resolveTranspiledModuleAsync: ( depPath: string, options?: { isAbsolute?: boolean; ignoredExtensions?: Array<string>; }) => Promise<TranspiledModule>;
       // 🔴 依赖收集
      addDependency: ( depPath: string, options?: { isAbsolute?: boolean; isEntry?: boolean; }) => void;
      addDependenciesInDirectory: ( depPath: string, options?: { isAbsolute?: boolean; isEntry?: boolean; }) => void;
      _module: TranspiledModule;
    };
    


Давайте начнем с простого и рассмотрим реализацию модуля JSON в Transpiler.Каждый подкласс Transpiler должен реализовать doTranspilation, получить исходный код и асинхронно вернуть результат обработки:

class JSONTranspiler extends Transpiler {
  doTranspilation(code: string) {
    const result = `
      module.exports = JSON.parse(${JSON.stringify(code || '')})
    `;

    return Promise.resolve({
      transpiledCode: result,
    });
  }
}


BabelTranspiler

Не все модули такие же простые, как JSON, например, Typescript и Babel. Чтобы повысить эффективность перевода, Codesandbox будет использовать Worker для выполнения многопроцессного перевода.Планирование работы нескольких Worker определяетсяWorkerTranspilerГотово, это подкласс Transpiler, который поддерживает пул рабочих. Сложные задачи перевода, такие как Babel, Typescript и Sass, реализованы на основе WorkerTranspiler:


Одной из наиболее типичных реализаций является BabelTranspiler.При запуске Sandbox он заранее создаст три воркера, чтобы повысить скорость запуска перевода.BabelTranspiler будет предпочтительно использовать эти три воркера для инициализации пула воркеров:

// 使用worker-loader fork三个loader,用于处理babel编译
import BabelWorker from 'worker-loader?publicPath=/&name=babel-transpiler.[hash:8].worker.js!./eval/transpilers/babel/worker/index.js';

window.babelworkers = [];
for (let i = 0; i < 3; i++) {
  window.babelworkers.push(new BabelWorker());
}

Вот использование веб-пакетаworker-loader, который инкапсулирует указанный модуль как рабочий объект. Сделайте Workers проще в использовании:

// App.js
import Worker from "./file.worker.js";

const worker = new Worker();

worker.postMessage({ a: 1 });
worker.onmessage = function(event) {};

worker.addEventListener("message", function(event) {});

Конкретный процесс Babel Tranpiler выглядит следующим образом:

WorkerTranspiler будет поддерживать空闲的Worker队列с одним任务队列, его задача — заставить Worker потреблять очередь задач. Конкретная работа по переводу выполняется в Worker:




Evaluation

Хотя CodeSandbox называется упаковщиком, он не упаковывает, что означает, что он не упаковывает и не объединяет все модули в файлы фрагментов, такие как Webpack.

Transpilationот入口文件Запустите перевод, затем проанализируйте правила импорта модулей в файле и рекурсивно переведите зависимые модули.Evaluationэтап, CodeSandbox построил полныйграфик зависимости, Теперь запустим приложение 🏃

Принцип Evaluation также относительно прост, как и Transpilation, он начинается с входного файла:использоватьevalВыполнить входной файл, если он вызывается во время выполненияrequire, затем рекурсивно оценить зависимый модуль.

Если вы знаете принцип импорта модулей Node, вы можете легко понять этот процесс:

  • ① Сначала инициализируйте html, найдитеindex.htmlзадайте для document.body.innerHTML содержимое тела HTML-шаблона.

  • ② Внедрение внешних ресурсов. Пользователи могут настроить некоторые внешние статические файлы, такие как css и js, которые необходимо добавить в заголовок.

  • ③ оценить входной модуль

  • ④ Все модули будут переведены в спецификацию модуля Commonjs. Таким образом, вам нужно имитировать эту среду модуля. Посмотрите на код примерно:

    // 实现require方法
    function require(path: string) {
      // ... 拦截一些特殊模块
    
      // 在Manager对象中查找模块
      const requiredTranspiledModule = manager.resolveTranspiledModule(
        path,
        localModule.path
      );
    
      // 模块缓存, 如果存在缓存则说明不需要重新执行
      const cache = requiredTranspiledModule.compilation;
    
      return cache
        ? cache.exports
        : // 🔴递归evaluate
          manager.evaluateTranspiledModule(
            requiredTranspiledModule,
            transpiledModule
          );
    }
    
    // 实现require.resolve
    require.resolve = function resolve(path: string) {
      return manager.resolveModule(path, localModule.path).path;
    };
    
    // 模拟一些全局变量
    const globals = {};
    globals.__dirname = pathUtils.dirname(this.module.path);
    globals.__filename = this.module.path;
    
    // 🔴放置执行结果,即CommonJS的module对象
    this.compilation = {
      id: this.getId(),
      exports: {}
    };
    
    // 🔴eval
    const exports = evaluate(
      this.source.compiledCode,
      require,
      this.compilation,
      manager.envVariables,
      globals
    );
    
  • ⑤ Используйте eval для запуска модуля. Также посмотрите на код:

    export default function(code, require, module, env = {}, globals = {}) {
      const exports = module.exports;
      const global = g;
      const process = buildProcess(env);
      g.global = global;
      const allGlobals = {
        require,
        module,
        exports,
        process,
        setImmediate: requestFrame,
        global,
        ...globals
      };
    
      const allGlobalKeys = Object.keys(allGlobals);
      const globalsCode = allGlobalKeys.length ? allGlobalKeys.join(", ") : "";
      const globalsValues = allGlobalKeys.map(k => allGlobals[k]);
      // 🔴将代码封装到一个函数下面,全局变量以函数形式传入
      const newCode = `(function evaluate(` + globalsCode + `) {` + code + `\n})`;
      (0, eval)(newCode).apply(this, globalsValues);
    
      return module.exports;
    }
    

Ok! Оценка объясняется здесь. Фактический код намного сложнее, чем здесь, например, поддержка HMR (горячая замена модуля). Заинтересованные читатели могут самостоятельно перейти к исходному коду CodeSandbox.




Техническая карта

Я случайно написал еще одну длинную статью.Внятно объяснить такой сложный код действительно сложно.Я недостаточно хорош.По прошлому опыту,это очередная статья,которая никому не интересна,не говоря уже о вас.У меня нет терпения самому читать подобные статьи, так что постараюсь их потом избегать!

  • worker-loader: инкапсулирует указанный модуль как Worker
  • babel: перевод кода JavaScript, поддерживает ES, Flow, Typescript
  • browserfs: эмулировать среду Node в браузере
  • localForage: репозиторий на стороне клиента, предпочтительнее использовать (IndexedDB или WebSQL) эти схемы асинхронного хранения, предоставить интерфейс, подобный LocalStorage.
  • lru-cache: последний использованный кеш

расширять