Skip to content

Commit

Permalink
feat(compartment-mapper): implement require.resolve as an external co…
Browse files Browse the repository at this point in the history
…nfiguration item
  • Loading branch information
naugtur committed Jun 28, 2022
1 parent c73efab commit 45054de
Show file tree
Hide file tree
Showing 13 changed files with 95 additions and 50 deletions.
2 changes: 2 additions & 0 deletions packages/compartment-mapper/src/archive.js
Expand Up @@ -253,6 +253,7 @@ const digestLocation = async (powers, moduleLocation, options) => {
modules: exitModules = {},
dev = false,
captureSourceLocation = undefined,
requireResolve = null,
} = options || {};
const { read, computeSha512 } = unpackReadPowers(powers);
const {
Expand Down Expand Up @@ -292,6 +293,7 @@ const digestLocation = async (powers, moduleLocation, options) => {
const makeImportHook = makeImportHookMaker(
read,
packageLocation,
requireResolve,
sources,
compartments,
exitModules,
Expand Down
4 changes: 3 additions & 1 deletion packages/compartment-mapper/src/bundle.js
Expand Up @@ -129,10 +129,11 @@ const sortedModules = (
* @param {string} moduleLocation
* @param {Object} [options]
* @param {ModuleTransforms} [options.moduleTransforms]
* @param {Function} [options.requireResolve]
* @returns {Promise<string>}
*/
export const makeBundle = async (read, moduleLocation, options) => {
const { moduleTransforms } = options || {};
const { moduleTransforms, requireResolve = null } = options || {};
const {
packageLocation,
packageDescriptorText,
Expand Down Expand Up @@ -165,6 +166,7 @@ export const makeBundle = async (read, moduleLocation, options) => {
const makeImportHook = makeImportHookMaker(
read,
packageLocation,
requireResolve,
sources,
compartments,
);
Expand Down
14 changes: 6 additions & 8 deletions packages/compartment-mapper/src/import-hook.js
Expand Up @@ -48,6 +48,7 @@ function getImportsFromRecord(record) {
/**
* @param {ReadFn|ReadPowers} readPowers
* @param {string} baseLocation
* @param {Function|null} [requireResolve]
* @param {Sources} sources
* @param {Record<string, CompartmentDescriptor>} compartments
* @param {Record<string, any>} exitModules
Expand All @@ -57,6 +58,7 @@ function getImportsFromRecord(record) {
export const makeImportHookMaker = (
readPowers,
baseLocation,
requireResolve = null,
sources = Object.create(null),
compartments = Object.create(null),
exitModules = Object.create(null),
Expand Down Expand Up @@ -169,21 +171,17 @@ export const makeImportHookMaker = (
const moduleBytes = await read(moduleLocation).catch(
_error => undefined,
);
const requireResolve = specifier => {
console.warn('Warning: Getting correct result form require.resolve in Endo is only possible if the package was resolved earlier.')
return packageSources[specifier]
? packageSources[specifier].sourceLocation
: null;
};
if (moduleBytes !== undefined) {
// eslint-disable-next-line no-await-in-loop
const envelope = await parse(
moduleBytes,
candidateSpecifier,
moduleLocation,
packageLocation,
readPowers,
requireResolve,
{
readPowers,
requireResolve,
},
);
const {
parser,
Expand Down
9 changes: 7 additions & 2 deletions packages/compartment-mapper/src/import.js
Expand Up @@ -37,7 +37,8 @@ export const parserForLanguage = {
* @returns {Promise<Application>}
*/
export const loadLocation = async (readPowers, moduleLocation, options) => {
const { moduleTransforms = {}, dev = false } = options || {};
const { moduleTransforms = {}, dev = false, requireResolve = null } =
options || {};

const { read } = unpackReadPowers(readPowers);

Expand Down Expand Up @@ -73,7 +74,11 @@ export const loadLocation = async (readPowers, moduleLocation, options) => {
__shimTransforms__,
Compartment,
} = options;
const makeImportHook = makeImportHookMaker(readPowers, packageLocation);
const makeImportHook = makeImportHookMaker(
readPowers,
packageLocation,
requireResolve,
);
const { compartment } = link(compartmentMap, {
makeImportHook,
parserForLanguage,
Expand Down
4 changes: 2 additions & 2 deletions packages/compartment-mapper/src/link.js
Expand Up @@ -77,7 +77,7 @@ const makeExtensionParser = (
parserForLanguage,
transforms,
) => {
return async (bytes, specifier, location, packageLocation, readPowers, requireResolve) => {
return async (bytes, specifier, location, packageLocation, options) => {
let language;
if (has(languageForModuleSpecifier, specifier)) {
language = languageForModuleSpecifier[specifier];
Expand Down Expand Up @@ -106,7 +106,7 @@ const makeExtensionParser = (
);
}
const { parse } = parserForLanguage[language];
return parse(bytes, specifier, location, packageLocation, readPowers, requireResolve);
return parse(bytes, specifier, location, packageLocation, options);
};
};

Expand Down
31 changes: 25 additions & 6 deletions packages/compartment-mapper/src/parse-cjs-shared-export-wrapper.js
Expand Up @@ -64,17 +64,26 @@ export const getModulePaths = (readPowers, location) => {
* ModuleEnvironmentRecord wrapper
* Creates shared export processing primitives to be used both Location and Archive usecases of cjs
*
* @param {Object} moduleEnvironmentRecord
* @param {Compartment} compartment
* @param {Record<string, string>} resolvedImports
* @param {Object} in
* @param {Object} in.moduleEnvironmentRecord
* @param {Compartment} in.compartment
* @param {Record<string, string>} in.resolvedImports
* @param {string} in.location
* @param {Function} [in.requireResolve]
* @returns {{
* module: { exports: any },
* moduleExports: any,
* afterExecute: Function,
* require: Function,
* }}
*/
export const wrap = (moduleEnvironmentRecord, compartment, resolvedImports, requireResolve) => {
export const wrap = ({
moduleEnvironmentRecord,
compartment,
resolvedImports,
location,
requireResolve,
}) => {
// This initial default value makes things like exports.hasOwnProperty() work in cjs.
moduleEnvironmentRecord.default = create(
compartment.globalThis.Object.prototype,
Expand Down Expand Up @@ -132,9 +141,19 @@ export const wrap = (moduleEnvironmentRecord, compartment, resolvedImports, requ
return namespace;
}
};
require.resolve = freeze(requireResolve)
if (requireResolve) {
require.resolve = freeze((specifier, options) =>
requireResolve(location, specifier, options),
);
} else {
require.resolve = freeze(specifier => {
const error = Error(`Cannot find module '${specifier}'`);
defineProperty(error, 'code', { value: 'MODULE_NOT_FOUND' });
throw error;
});
}

freeze(require)
freeze(require);

const afterExecute = () => {
const exportsHaveBeenOverwritten = finalExports !== originalExports;
Expand Down
8 changes: 4 additions & 4 deletions packages/compartment-mapper/src/parse-cjs.js
Expand Up @@ -13,8 +13,7 @@ export const parseCjs = async (
_specifier,
location,
_packageLocation,
readPowers,
requireResolve,
{ readPowers, requireResolve } = {},
) => {
const source = textDecoder.decode(bytes);

Expand All @@ -39,12 +38,13 @@ export const parseCjs = async (
`(function (require, exports, module, __filename, __dirname) { ${source} //*/\n})\n//# sourceURL=${location}`,
);

const { require, moduleExports, module, afterExecute } = wrap(
const { require, moduleExports, module, afterExecute } = wrap({
moduleEnvironmentRecord,
compartment,
resolvedImports,
location,
requireResolve,
);
});

functor(require, moduleExports, module, filename, dirname);

Expand Down
8 changes: 5 additions & 3 deletions packages/compartment-mapper/src/parse-pre-cjs.js
Expand Up @@ -11,7 +11,7 @@ export const parsePreCjs = async (
_specifier,
location,
_packageLocation,
readPowers,
{ readPowers, requireResolve } = {},
) => {
const text = textDecoder.decode(bytes);
const { source, imports, exports, reexports } = parseLocatedJson(
Expand All @@ -29,11 +29,13 @@ export const parsePreCjs = async (
const execute = (moduleEnvironmentRecord, compartment, resolvedImports) => {
const functor = compartment.evaluate(source);

const { require, moduleExports, module, afterExecute } = wrap(
const { require, moduleExports, module, afterExecute } = wrap({
moduleEnvironmentRecord,
compartment,
resolvedImports,
);
requireResolve,
location,
});

functor(require, moduleExports, module, filename, dirname);

Expand Down
5 changes: 4 additions & 1 deletion packages/compartment-mapper/src/types.js
Expand Up @@ -197,7 +197,9 @@ export {};
* @param {string} specifier
* @param {string} location
* @param {string} packageLocation
* @param {ReadFn | ReadPowers} [readPowers]
* @param {object} [options]
* @param {ReadFn | ReadPowers} [options.readPowers]
* @param {Function} [options.requireResolve]
* @returns {Promise<{
* bytes: Uint8Array,
* parser: Language,
Expand Down Expand Up @@ -308,4 +310,5 @@ export {};
* @property {Record<string, any>} [modules]
* @property {boolean} [dev]
* @property {CaptureSourceLocationHook} [captureSourceLocation]
* @property {Function} [requireResolve]
*/

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions packages/compartment-mapper/test/scaffold.js
@@ -1,6 +1,7 @@
import 'ses';
import fs from 'fs';
import crypto from 'crypto';
import { createRequire } from 'module';
import url from 'url';
import { ZipReader, ZipWriter } from '@endo/zip';
import {
Expand Down Expand Up @@ -87,6 +88,8 @@ export function scaffold(

const application = await loadLocation(readPowers, fixture, {
dev: true,
requireResolve: (from, specifier, options) =>
createRequire(from).resolve(specifier, options),
});
const { namespace } = await application.import({
globals,
Expand All @@ -107,6 +110,8 @@ export function scaffold(
modules,
Compartment,
dev: true,
requireResolve: (from, specifier, options) =>
createRequire(from).resolve(specifier, options),
});
return namespace;
});
Expand Down
32 changes: 28 additions & 4 deletions packages/compartment-mapper/test/test-cjs-compat.js
Expand Up @@ -15,8 +15,8 @@ const fixtureDirname = new URL(
import.meta.url,
).toString();

const assertFixture = (t, { namespace }) => {
const { assertions } = namespace;
const assertFixture = (t, { namespace, testCategoryHint }) => {
const { assertions, results } = namespace;

assertions.packageExportsShenanigans();
assertions.packageWithDefaultField();
Expand All @@ -25,12 +25,36 @@ const assertFixture = (t, { namespace }) => {
assertions.moduleWithCycle();
assertions.defaultChangesAfterExec();
assertions.packageNestedFile();
assertions.requireResolve();

if (testCategoryHint === 'Location') {
t.deepEqual(results.requireResolvePaths, [
"Cannot find module '.'",
'/skipped/fixtures-cjs-compat/node_modules/require-resolve/package.json',
'/skipped/fixtures-cjs-compat/node_modules/require-resolve/nested/index.js',
'/skipped/fixtures-cjs-compat/node_modules/require-resolve/nested/file.js',
'/skipped/fixtures-cjs-compat/node_modules/require-resolve/nested/file.js.map',
"Cannot find module './nested/file.missing'",
'/skipped/fixtures-cjs-compat/node_modules/app/index.js',
'fs',
'/skipped/fixtures-cjs-compat/node_modules/nested-export/callBound.js',
]);
} else {
t.deepEqual(results.requireResolvePaths, [
"Cannot find module '.'",
"Cannot find module './package.json'",
"Cannot find module './nested'",
"Cannot find module './nested/file.js'",
"Cannot find module './nested/file.js.map'",
"Cannot find module './nested/file.missing'",
"Cannot find module 'app'",
"Cannot find module 'fs'",
"Cannot find module 'nested-export/callBound'",
]);
}
t.pass();
};

const fixtureAssertionCount = 1;
const fixtureAssertionCount = 2;

scaffold(
'fixtures-cjs-compat',
Expand Down

0 comments on commit 45054de

Please sign in to comment.