Skip to content

Commit

Permalink
src: add node_modules.h for module loader
Browse files Browse the repository at this point in the history
  • Loading branch information
anonrig committed Oct 23, 2023
1 parent cd6b86b commit 2690c64
Show file tree
Hide file tree
Showing 16 changed files with 392 additions and 151 deletions.
104 changes: 23 additions & 81 deletions lib/internal/modules/package_json_reader.js
Original file line number Diff line number Diff line change
@@ -1,105 +1,35 @@
'use strict';

const {
JSONParse,
ObjectPrototypeHasOwnProperty,
SafeMap,
StringPrototypeEndsWith,
StringPrototypeIndexOf,
StringPrototypeLastIndexOf,
StringPrototypeSlice,
} = primordials;
const {
ERR_INVALID_PACKAGE_CONFIG,
} = require('internal/errors').codes;
const { internalModuleReadJSON } = internalBinding('fs');
const modulesBinding = internalBinding('modules');
const { resolve, sep, toNamespacedPath } = require('path');
const permission = require('internal/process/permission');
const { kEmptyObject, setOwnProperty } = require('internal/util');

const { fileURLToPath, pathToFileURL } = require('internal/url');
const { kEmptyObject } = require('internal/util');

const cache = new SafeMap();
const { pathToFileURL } = require('internal/url');

let manifest;

/**
* @typedef {{
* exists: boolean,
* pjsonPath: string,
* exports?: string | string[] | Record<string, unknown>,
* imports?: string | string[] | Record<string, unknown>,
* name?: string,
* main?: string,
* type: 'commonjs' | 'module' | 'none',
* }} PackageConfig
*/

/**
* @param {string} jsonPath
* @param {{
* base?: string,
* specifier: string,
* isESM: boolean,
* }} options
* @returns {PackageConfig}
* @returns {import('typings/internalBinding/modules').PackageConfig}
*/
function read(jsonPath, { base, specifier, isESM } = kEmptyObject) {
if (cache.has(jsonPath)) {
return cache.get(jsonPath);
}

const string = internalModuleReadJSON(
const parsed = modulesBinding.readPackageJSON(
toNamespacedPath(jsonPath),
);
const result = {
__proto__: null,
exists: false,
pjsonPath: jsonPath,
main: undefined,
name: undefined,
type: 'none', // Ignore unknown types for forwards compatibility
exports: undefined,
imports: undefined,
};

if (string !== undefined) {
let parsed;
try {
parsed = JSONParse(string);
} catch (cause) {
const error = new ERR_INVALID_PACKAGE_CONFIG(
jsonPath,
isESM && (base ? `"${specifier}" from ` : '') + fileURLToPath(base || specifier),
cause.message,
);
setOwnProperty(error, 'cause', cause);
throw error;
}

result.exists = true;

// ObjectPrototypeHasOwnProperty is used to avoid prototype pollution.
if (ObjectPrototypeHasOwnProperty(parsed, 'name') && typeof parsed.name === 'string') {
result.name = parsed.name;
}

if (ObjectPrototypeHasOwnProperty(parsed, 'main') && typeof parsed.main === 'string') {
result.main = parsed.main;
}

if (ObjectPrototypeHasOwnProperty(parsed, 'exports')) {
result.exports = parsed.exports;
}

if (ObjectPrototypeHasOwnProperty(parsed, 'imports')) {
result.imports = parsed.imports;
}

// Ignore unknown types for forwards compatibility
if (ObjectPrototypeHasOwnProperty(parsed, 'type') && (parsed.type === 'commonjs' || parsed.type === 'module')) {
result.type = parsed.type;
}
if (parsed !== undefined) {
parsed.pjsonPath = jsonPath;

if (manifest === undefined) {
const { getOptionValue } = require('internal/options');
Expand All @@ -108,12 +38,24 @@ function read(jsonPath, { base, specifier, isESM } = kEmptyObject) {
null;
}
if (manifest !== null) {
const jsonURL = pathToFileURL(jsonPath);
manifest.assertIntegrity(jsonURL, string);
// TODO(@anonrig): Find a way to assert integrity without returning string.
// const jsonURL = pathToFileURL(jsonPath);
// manifest.assertIntegrity(jsonURL, string);
}

return parsed;
}
cache.set(jsonPath, result);
return result;

return {
__proto__: null,
exists: false,
pjsonPath: jsonPath,
main: undefined,
name: undefined,
type: 'none', // Ignore unknown types for forwards compatibility
exports: undefined,
imports: undefined,
};
}

/**
Expand Down
9 changes: 5 additions & 4 deletions lib/internal/modules/run_main.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const {
} = primordials;

const { containsModuleSyntax } = internalBinding('contextify');
const { getPackageJSONType } = internalBinding('modules');
const { getOptionValue } = require('internal/options');
const path = require('path');

Expand Down Expand Up @@ -68,10 +69,10 @@ function shouldUseESMLoader(mainPath) {
if (mainPath && StringPrototypeEndsWith(mainPath, '.mjs')) { return true; }
if (!mainPath || StringPrototypeEndsWith(mainPath, '.cjs')) { return false; }

const { readPackageScope } = require('internal/modules/package_json_reader');
const pkg = readPackageScope(mainPath);
// No need to guard `pkg` as it can only be an object or `false`.
switch (pkg.data?.type) {
// TODO(@anonrig): Move resolve functionality to C++.
const type = getPackageJSONType(path.resolve(mainPath, 'package.json'));

switch (type) {
case 'module':
return true;
case 'commonjs':
Expand Down
2 changes: 2 additions & 0 deletions node.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
'src/node_main_instance.cc',
'src/node_messaging.cc',
'src/node_metadata.cc',
'src/node_modules.cc',
'src/node_options.cc',
'src/node_os.cc',
'src/node_perf.cc',
Expand Down Expand Up @@ -234,6 +235,7 @@
'src/node_messaging.h',
'src/node_metadata.h',
'src/node_mutex.h',
'src/node_modules.h',
'src/node_object_wrap.h',
'src/node_options.h',
'src/node_options-inl.h',
Expand Down
3 changes: 2 additions & 1 deletion src/base_object_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ namespace node {
V(blob_binding_data, BlobBindingData) \
V(process_binding_data, process::BindingData) \
V(timers_binding_data, timers::BindingData) \
V(url_binding_data, url::BindingData)
V(url_binding_data, url::BindingData) \
V(modules_binding_data, modules::BindingData)

#define UNSERIALIZABLE_BINDING_TYPES(V) \
V(http2_binding_data, http2::BindingData) \
Expand Down
1 change: 1 addition & 0 deletions src/node_binding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
V(js_stream) \
V(js_udp_wrap) \
V(messaging) \
V(modules) \
V(module_wrap) \
V(mksnapshot) \
V(options) \
Expand Down
1 change: 1 addition & 0 deletions src/node_binding.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ static_assert(static_cast<int>(NM_F_LINKED) ==
V(encoding_binding) \
V(fs) \
V(mksnapshot) \
V(modules) \
V(timers) \
V(process_methods) \
V(performance) \
Expand Down
1 change: 1 addition & 0 deletions src/node_errors.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ void AppendExceptionLine(Environment* env,
V(ERR_INVALID_ARG_TYPE, TypeError) \
V(ERR_INVALID_FILE_URL_HOST, TypeError) \
V(ERR_INVALID_FILE_URL_PATH, TypeError) \
V(ERR_INVALID_PACKAGE_CONFIG, Error) \
V(ERR_INVALID_OBJECT_DEFINE_PROPERTY, TypeError) \
V(ERR_INVALID_MODULE, Error) \
V(ERR_INVALID_STATE, Error) \
Expand Down
1 change: 1 addition & 0 deletions src/node_external_reference.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ class ExternalReferenceRegistry {
V(messaging) \
V(mksnapshot) \
V(module_wrap) \
V(modules) \
V(options) \
V(os) \
V(performance) \
Expand Down
64 changes: 0 additions & 64 deletions src/node_file.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1050,68 +1050,6 @@ static void ExistsSync(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(err == 0);
}

// Used to speed up module loading. Returns an array [string, boolean]
static void InternalModuleReadJSON(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Isolate* isolate = env->isolate();
uv_loop_t* loop = env->event_loop();

CHECK(args[0]->IsString());
node::Utf8Value path(isolate, args[0]);
THROW_IF_INSUFFICIENT_PERMISSIONS(
env, permission::PermissionScope::kFileSystemRead, path.ToStringView());

if (strlen(*path) != path.length()) {
return; // Contains a nul byte.
}
uv_fs_t open_req;
const int fd = uv_fs_open(loop, &open_req, *path, O_RDONLY, 0, nullptr);
uv_fs_req_cleanup(&open_req);

if (fd < 0) {
return;
}

auto defer_close = OnScopeLeave([fd, loop]() {
uv_fs_t close_req;
CHECK_EQ(0, uv_fs_close(loop, &close_req, fd, nullptr));
uv_fs_req_cleanup(&close_req);
});

const size_t kBlockSize = 32 << 10;
std::vector<char> chars;
int64_t offset = 0;
ssize_t numchars;
do {
const size_t start = chars.size();
chars.resize(start + kBlockSize);

uv_buf_t buf;
buf.base = &chars[start];
buf.len = kBlockSize;

uv_fs_t read_req;
numchars = uv_fs_read(loop, &read_req, fd, &buf, 1, offset, nullptr);
uv_fs_req_cleanup(&read_req);

if (numchars < 0) {
return;
}
offset += numchars;
} while (static_cast<size_t>(numchars) == kBlockSize);

size_t start = 0;
if (offset >= 3 && 0 == memcmp(chars.data(), "\xEF\xBB\xBF", 3)) {
start = 3; // Skip UTF-8 BOM.
}
const size_t size = offset - start;

args.GetReturnValue().Set(
String::NewFromUtf8(
isolate, &chars[start], v8::NewStringType::kNormal, size)
.ToLocalChecked());
}

// Used to speed up module loading. Returns 0 if the path refers to
// a file, 1 when it's a directory or < 0 on error (usually -ENOENT.)
// The speedup comes from not creating thousands of Stat and Error objects.
Expand Down Expand Up @@ -3255,7 +3193,6 @@ static void CreatePerIsolateProperties(IsolateData* isolate_data,
SetMethod(isolate, target, "rmdir", RMDir);
SetMethod(isolate, target, "mkdir", MKDir);
SetMethod(isolate, target, "readdir", ReadDir);
SetMethod(isolate, target, "internalModuleReadJSON", InternalModuleReadJSON);
SetMethod(isolate, target, "internalModuleStat", InternalModuleStat);
SetMethod(isolate, target, "stat", Stat);
SetMethod(isolate, target, "lstat", LStat);
Expand Down Expand Up @@ -3375,7 +3312,6 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(RMDir);
registry->Register(MKDir);
registry->Register(ReadDir);
registry->Register(InternalModuleReadJSON);
registry->Register(InternalModuleStat);
registry->Register(Stat);
registry->Register(LStat);
Expand Down
Loading

0 comments on commit 2690c64

Please sign in to comment.