1import { register } from 'node:module';
2import { MessageChannel } from 'node:worker_threads';
3
4
5const { port1, port2 } = new MessageChannel();
6
7register('./mock-loader.mjs', import.meta.url, {
8  data: {
9    port: port2,
10    mainImportURL: import.meta.url,
11  },
12  transferList: [port2],
13});
14
15/**
16 * This is the Map that saves *all* the mocked URL -> replacement Module
17 * mappings
18 * @type {Map<string, {namespace, listeners}>}
19 */
20export const mockedModules = new Map();
21let mockVersion = 0;
22
23/**
24 * @param {string} resolved an absolute URL HREF string
25 * @param {object} replacementProperties an object to pick properties from
26 *                                       to act as a module namespace
27 * @returns {object} a mutator object that can update the module namespace
28 *                   since we can't do something like old Object.observe
29 */
30export function mock(resolved, replacementProperties) {
31  const exportNames = Object.keys(replacementProperties);
32  const namespace = { __proto__: null };
33  /**
34   * @type {Array<(name: string)=>void>} functions to call whenever an
35   *                                     export name is updated
36   */
37  const listeners = [];
38  for (const name of exportNames) {
39    let currentValueForPropertyName = replacementProperties[name];
40    Object.defineProperty(namespace, name, {
41      __proto__: null,
42      enumerable: true,
43      get() {
44        return currentValueForPropertyName;
45      },
46      set(v) {
47        currentValueForPropertyName = v;
48        for (const fn of listeners) {
49          try {
50            fn(name);
51          } catch {
52            /* noop */
53          }
54        }
55      },
56    });
57  }
58  mockedModules.set(encodeURIComponent(resolved), {
59    namespace,
60    listeners,
61  });
62  mockVersion++;
63  // Inform the loader that the `resolved` URL should now use the specific
64  // `mockVersion` and has export names of `exportNames`
65  //
66  // This allows the loader to generate a fake module for that version
67  // and names the next time it resolves a specifier to equal `resolved`
68  port1.postMessage({ mockVersion, resolved, exports: exportNames });
69  return namespace;
70}
71