Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

node compatibility typescript infrastructure #258

Merged
merged 4 commits into from
Jan 11, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions build/wd_api_bundle.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
load("@capnp-cpp//src/capnp:cc_capnp_library.bzl", "cc_capnp_library")

CAPNP_TEMPLATE = """@{schema_id};

using Modules = import "/workerd/jsg/modules.capnp";

const {const_name} :Modules.Bundle = (
modules = [
{modules}
]);
"""

MODULE_TEMPLATE = """(name = "{name}", src = embed "{path}", internal = {internal})"""

def _relative_path(file_path, dir_path):
if not file_path.startswith(dir_path):
fail("file_path need to start with dir_path: " + file_path + " vs " + dir_path)
return file_path.removeprefix(dir_path)

def _gen_api_bundle_capnpn_impl(ctx):
output_dir = ctx.outputs.out.dirname + "/"

def _render_module(name, label, internal):
return MODULE_TEMPLATE.format(
name = name,
# capnp doesn't allow ".." dir escape, make paths relative.
# this won't work for embedding paths outside of rule directory subtree.
path = _relative_path(
ctx.expand_location("$(location {})".format(label), ctx.attr.data),
output_dir,
),
internal = "true" if internal else "false",
)

modules = [
_render_module(ctx.attr.builtin_modules[m], m.label, False)
for m in ctx.attr.builtin_modules
]
modules += [
_render_module(ctx.attr.internal_modules[m], m.label, True)
for m in ctx.attr.internal_modules
]

content = CAPNP_TEMPLATE.format(
schema_id = ctx.attr.schema_id,
modules = ",\n".join(modules),
const_name = ctx.attr.const_name,
)
ctx.actions.write(ctx.outputs.out, content)

gen_api_bundle_capnpn = rule(
implementation = _gen_api_bundle_capnpn_impl,
attrs = {
"schema_id": attr.string(mandatory = True),
"out": attr.output(mandatory = True),
"builtin_modules": attr.label_keyed_string_dict(allow_files = True),
"internal_modules": attr.label_keyed_string_dict(allow_files = True),
"data": attr.label_list(allow_files = True),
"const_name": attr.string(mandatory = True),
},
)

def wd_api_bundle(
name,
schema_id,
const_name,
builtin_modules = {},
internal_modules = {},
**kwargs):
"""Generate cc capnp library with api bundle.

NOTE: Due to capnpc embed limitation all modules must be in the same or sub directory of the
actual rule usage.

Args:
name: cc_capnp_library rule name
builtin_modules: js src label -> module name dictionary
internal_modules: js src label -> module name dictionary
const_name: capnp constant name that will contain bundle definition
schema_id: capnpn schema id
**kwargs: rest of cc_capnp_library arguments
"""
data = list(builtin_modules) + list(internal_modules)

gen_api_bundle_capnpn(
name = name + "@gen",
out = name + ".capnp",
schema_id = schema_id,
const_name = const_name,
builtin_modules = builtin_modules,
internal_modules = internal_modules,
data = data,
)

cc_capnp_library(
name = name,
srcs = [name + ".capnp"],
strip_include_prefix = "",
visibility = ["//visibility:public"],
data = data,
deps = ["//src/workerd/jsg:modules_capnp"],
**kwargs
)
35 changes: 35 additions & 0 deletions src/node/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
load("@aspect_rules_ts//ts:defs.bzl", "ts_config", "ts_project")
load("@//:build/wd_api_bundle.bzl", "wd_api_bundle")

modules = glob(["*.ts"])

internal_modules = glob(["internal/*.ts"])

ts_config(
name = "node@tsconfig",
src = ":tsconfig.json",
)

ts_project(
name = "node",
srcs = modules + internal_modules,
tsconfig = "node@tsconfig",
)

wd_api_bundle(
name = "bundle",
# builtin modules are accessible under "node:<module_name>" name
builtin_modules = dict([(
m.removesuffix(".ts") + ".js",
"node:" + m.removesuffix(".ts"),
) for m in modules]),
const_name = "nodeBundle",
include_prefix = "node",
# internal modules are accessible under "node-internal:<module_name>" name without "internal/"
# folder prefix.
internal_modules = dict([(
m.removesuffix(".ts") + ".js",
"node-internal:" + m.removeprefix("internal/").removesuffix(".ts"),
) for m in internal_modules]),
schema_id = "0xbcc8f57c63814005",
)
1 change: 1 addition & 0 deletions src/node/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Node Compatibility Layer
7 changes: 7 additions & 0 deletions src/node/buffer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import * as bufferImpl from 'node-internal:bufferImpl';
mikea marked this conversation as resolved.
Show resolved Hide resolved

export class Buffer {
public toString(): string {
return bufferImpl.toString(this);
}
}
5 changes: 5 additions & 0 deletions src/node/internal/bufferImpl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import * as buffer from "node:buffer";

export function toString(buf: buffer.Buffer): string {
return `Buffer[${buf}]`;
}
33 changes: 33 additions & 0 deletions src/node/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"compilerOptions": {
"target": "ES2020",
mikea marked this conversation as resolved.
Show resolved Hide resolved
"module": "ES2020",
"lib": [
"ES2020"
],
"alwaysStrict": true,
"strict": true,
"allowUnreachableCode": false,
"allowUnusedLabels": false,
"exactOptionalPropertyTypes": true,
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noPropertyAccessFromIndexSignature": true,
"noUncheckedIndexedAccess": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"strictNullChecks": true,
"types": [
// "@cloudflare/workers-types"
],
"paths": {
"node:*": ["./*"],
"node-internal:*": ["./internal/*"],
}
},
"include": [
"*.ts",
"internal/*.ts"
],
}
11 changes: 9 additions & 2 deletions src/workerd/jsg/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ wd_cc_library(
),
visibility = ["//visibility:public"],
deps = [
":modules_capnp",
"//src/workerd/util",
"@capnp-cpp//src/kj",
"@workerd-v8//:v8",
"//src/workerd/util",
],
)

Expand All @@ -37,10 +38,10 @@ js_capnp_library(
npm_package(
name = "jsg_js",
srcs = [":rtti_capnp_js"],
visibility = ["//visibility:public"],
# Required to ensure source files are copied when running internal builds
# that depend on `workerd` as an external repository
include_external_repositories = ["workerd"],
visibility = ["//visibility:public"],
)

wd_cc_library(
Expand All @@ -55,6 +56,12 @@ wd_cc_library(
],
)

wd_cc_capnp_library(
name = "modules_capnp",
srcs = ["modules.capnp"],
visibility = ["//visibility:public"],
)

[kj_test(
src = f,
deps = [
Expand Down
21 changes: 21 additions & 0 deletions src/workerd/jsg/modules.capnp
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
@0xc8cbb234694939d5;

using Cxx = import "/capnp/c++.capnp";
$Cxx.namespace("workerd::jsg");

struct Bundle {
# Group of modules to be loaded together.
# Bundles are currently generated during compilation process and linked with the workerd,
# but loading bundles from somewhere else will also be possible.
modules @0 :List(Module);
}

struct Module {
# Javascript module with its source code.

name @0 :Text;
src @1 :Data;

internal @2 :Bool;
# internal modules can't be imported by user's code
}
8 changes: 8 additions & 0 deletions src/workerd/jsg/modules.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "jsg.h"
#include <kj/filesystem.h>
#include <kj/map.h>
#include <workerd/jsg/modules.capnp.h>

namespace workerd::jsg {

Expand Down Expand Up @@ -238,6 +239,13 @@ class ModuleRegistryImpl final: public ModuleRegistry {
entries.insert(Entry(specifier, Type::BUNDLE, kj::fwd<ModuleInfo>(info)));
}

void addBuiltinBundle(Bundle::Reader bundle) {
for (auto module: bundle.getModules()) {
addBuiltinModule(module.getName(), module.getSrc().asChars(),
mikea marked this conversation as resolved.
Show resolved Hide resolved
module.getInternal() ? Type::INTERNAL : Type::BUILTIN);
}
}

void addBuiltinModule(kj::StringPtr specifier,
kj::ArrayPtr<const char> sourceCode,
Type type = Type::BUILTIN) {
Expand Down
1 change: 1 addition & 0 deletions src/workerd/server/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ wd_cc_library(
deps = [
":workerd_capnp",
"@capnp-cpp//src/capnp:capnpc",
"//src/node:bundle",
"//src/workerd/io",
"//src/workerd/jsg",
],
Expand Down
7 changes: 7 additions & 0 deletions src/workerd/server/workerd-api.c++
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
#include <openssl/hmac.h>
#include <openssl/rand.h>

#include <node/bundle.capnp.h>

namespace workerd::server {

JSG_DECLARE_ISOLATE_TYPE(JsgWorkerdIsolate,
Expand Down Expand Up @@ -232,6 +234,7 @@ kj::Own<jsg::ModuleRegistry> WorkerdApiIsolate::compileModules(
Worker::ValidationErrorReporter& errorReporter) const {
auto& lock = kj::downcast<JsgWorkerdIsolate::Lock>(lockParam);
v8::HandleScope scope(lock.v8Isolate);
auto& featureFlags = *impl->features;

auto modules = kj::heap<jsg::ModuleRegistryImpl<JsgWorkerdIsolate_TypeWrapper>>();

Expand Down Expand Up @@ -312,6 +315,10 @@ kj::Own<jsg::ModuleRegistry> WorkerdApiIsolate::compileModules(
}
}

if (featureFlags.getNodeJsCompat()) {
modules->addBuiltinBundle(NODE_BUNDLE);
}
mikea marked this conversation as resolved.
Show resolved Hide resolved

jsg::setModulesForResolveCallback<JsgWorkerdIsolate_TypeWrapper>(lock, modules);

return modules;
Expand Down