summaryrefslogtreecommitdiff
path: root/std/bundle/utils.ts
blob: 062c6223134b51af67f611015f5790ece86b9797 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.

import { assert } from "../testing/asserts.ts";
import { exists } from "../fs/exists.ts";

export interface DefineFactory {
  /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
  (...args: any): object | void;
}

export interface ModuleMetaData {
  dependencies: string[];
  factory?: DefineFactory | object;
  exports: object;
}

type Define = (
  id: string,
  dependencies: string[],
  factory: DefineFactory
) => void;

/* eslint-disable @typescript-eslint/no-namespace */
declare global {
  namespace globalThis {
    // eslint-disable-next-line no-var
    var define: Define | undefined;
  }
}
/* eslint-enable @typescript-eslint/no-namespace */

/** Evaluate the bundle, returning a queue of module IDs and their data to
 * instantiate.
 */
export function evaluate(
  text: string
): [string[], Map<string, ModuleMetaData>] {
  const queue: string[] = [];
  const modules = new Map<string, ModuleMetaData>();

  globalThis.define = function define(
    id: string,
    dependencies: string[],
    factory: DefineFactory
  ): void {
    modules.set(id, {
      dependencies,
      factory,
      exports: {}
    });
    queue.push(id);
  };
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  (Deno as any).core.evalContext(text);
  // Deleting `define()` so it isn't accidentally there when the modules
  // instantiate.
  delete globalThis.define;

  return [queue, modules];
}

/** Drain the queue of module IDs while instantiating the modules. */
export function instantiate(
  queue: string[],
  modules: Map<string, ModuleMetaData>
): void {
  let id: string | undefined;
  while ((id = queue.shift())) {
    const module = modules.get(id)!;
    assert(module != null);
    assert(module.factory != null);

    const dependencies = module.dependencies.map(
      (id): object => {
        if (id === "require") {
          // TODO(kitsonk) support dynamic import by passing a `require()` that
          // can return a local module or dynamically import one.
          return (): void => {};
        } else if (id === "exports") {
          return module.exports;
        }
        const dep = modules.get(id)!;
        assert(dep != null);
        return dep.exports;
      }
    );

    if (typeof module.factory === "function") {
      module.factory!(...dependencies);
    } else if (module.factory) {
      // when bundling JSON, TypeScript just emits it as an object/array as the
      // third argument of the `define()`.
      module.exports = module.factory;
    }
    delete module.factory;
  }
}

/** Load the bundle and return the contents asynchronously. */
export async function load(args: string[]): Promise<string> {
  // TODO(kitsonk) allow loading of remote bundles via fetch.
  assert(args.length >= 2, "Expected at least two arguments.");
  const [, bundleFileName] = args;
  assert(
    await exists(bundleFileName),
    `Expected "${bundleFileName}" to exist.`
  );
  return new TextDecoder().decode(await Deno.readFile(bundleFileName));
}