This document defines a project titled Oden (an anagram of Node and Deno). It is intended to be a way to run "JavaScript" on a server but with the following caveats:
- Only JavaScript features that are beneficial in a server context are retained
- Many existing JavaScript APIs and globals are locked behind
require()calls - Compatability with existing scripts and libraries is not a consideration
- This is essentially a fork of JavaScript (via modifications to V8)
- This is essentially a fork of Node.js (via modifications to Node.js APIs)
Another way to think about this project is what if node, the binary for interpreting scripts and running backend services, had full control of the language being interpreted, similar to python and php?
This is not a complete set of changes for defining how Oden will work but is instead an initial pass.
Further Reading #
The following documents have more information about the background behind Oden:
- Server-Side JavaScript Arc Part 1: A Retrospective on the Golden Age of Node.js
- Server-Side JavaScript Arc Part 2: A Server-First JavaScript Runtime
- Oden.js gist: Try a simplified version of Oden using the Node.js REPL
Node.js API Modifications #
Based on the number of modifications required this would need to be implemented as a "hard fork" of Node.js. I don't think it would be feasable to float patches on top of Node.js.
New Internal Modules #
Errors #
The following JavaScript globals are moved to top level exports of a new require('errors') module:
// before
global.EvalError;
global.URIError;
global.AggregateError;
global.DOMException;
// after
const {
EvalError,
URIError,
AggregateError,
DOMException,
Error, // also remains a global for convenience
TypeError, // also remains a global for convenience
SyntaxError, // also remains a global for convenience
RangeError, // also remains a global for convenience
ReferenceError // also remains a global for convenience
} = require('errors');
Math #
The following JavaScript global is made the top level export of a new require('math') module and is removed from global:
// before
global.Math;
// after
const Math = require('math');
Threads #
The following JavaScript globals are moved to top level exports of a new require('threads') module:
// before
global.BroadcastChannel;
global.Atomics;
global.MessagePort;
global.MessageEvent;
global.MessageChannel;
global.SharedArrayBuffer;
// after
const {
BroadcastChannel,
Atomics,
MessagePort,
MessageEvent,
MessageChannel,
SharedArrayBuffer
} = require('threads');
Array Buffer #
The following JavaScript globals are moved to top level exports of a new require('array_buffer') module:
// before
global.SharedArrayBuffer;
global.ArrayBuffer;
global.BigInt64Array;
global.BigUint64Array;
global.DataView;
global.Float32Array;
global.Float64Array;
global.Int16Array;
global.Int32Array;
global.Int8Array;
global.Uint16Array;
global.Uint32Array;
global.Uint8Array;
global.Uint8ClampedArray;
global.Blob;
// after
const {
SharedArrayBuffer, // redundant with require('threads').SharedArrayBuffer
ArrayBuffer,
TypedArray, // currently not directly available in JavaScript
BigInt64Array,
BigUint64Array,
DataView,
Float32Array,
Float64Array,
Int16Array,
Int32Array,
Int8Array,
Uint16Array,
Uint32Array,
Uint8Array,
Uint8ClampedArray,
Blob
} = require('array_buffer');
Text Encoder #
The following JavaScript globals are moved to top level exports of a new require('text_encoder') module:
// before
global.TextEncoder;
global.TextEncoderStream;
global.TextDecoder;
global.TextDecoderStream;
// after
const {
TextEncoder,
TextEncoderStream,
TextDecoder,
TextDecoderStream
} = require('text_encoder');
Intl #
The following JavaScript globals are moved to top level exports of a new require('intl') module:
// before
global.Intl;
// after
const Intl = require('intl');
Reflection #
The following JavaScript globals are moved to top level exports of a new require('reflection') module:
// before
global.Reflect;
global.Proxy;
// after
const {
Reflect,
Proxy
} = require('reflection');
Navigator #
The following JavaScript globals are moved to top level exports of a new require('navigator') module:
// before
global.navigator;
global.Navigator;
// after
const {
navigator,
Navigator
} = require('navigator');
Finalization Registry #
The following JavaScript globals are moved to top level exports of a new require('finalization_registry') module:
// before
global.FinalizationRegistry;
// after
const FinalizationRegistry = require('finalization_registry');
Date Time #
The following JavaScript globals are moved to top level exports of a new require('datetime') module:
// before
global.Date;
global.Temporal;
// after
const {
Date,
Temporal
} = require('datetime');
Mutated Existing Modules #
The following modules are extended by moving existing globals to the list of exports:
Timers #
// before
global.queueMicrotask;
const sleep = ms => new Promise((resolve) => timers.setTimeout(resolve, ms));
// after
const {
queueMicrotask,
sleep
} = require('timers');
Events #
The following JavaScript globals are moved to top level exports of the require('events') object:
// before
global.EventTarget;
global.Event;
global.CloseEvent;
global.CustomEvent;
// after
const {
EventTarget,
Event,
CloseEvent,
CustomEvent
} = require('events');
Crypto #
The following JavaScript globals are moved to top level exports of the require('crypto') object:
// before
global.Crypto;
global.CryptoKey;
global.SubtleCrypto;
// after
const {
Crypto,
CryptoKey,
SubtleCrypto
} = require('crypto');
File #
The following JavaScript globals are moved to top level exports of the require('fs') object:
// before
global.File;
// after
const {
File
} = require('fs');
HTTP #
The following JavaScript globals are moved to top level exports of the require('http') object:
// before
global.fetch;
global.FormData;
global.Request;
global.Response;
global.Headers;
global.AbortController;
global.AbortSignal;
global.WebSocket;
// after
const {
fetch,
FormData,
Request,
Response,
Headers,
AbortController,
AbortSignal,
WebSocket
} = require('http');
URL #
The following JavaScript globals are moved to top level exports of the require('url') object:
// before
global.encodeURI;
global.decodeURI;
global.encodeURIComponent;
global.escape;
global.unescape;
global.decodeURIComponent;
global.URL; // already a part of require('url') but removed from global
global.URLSearchParams; // already a part of require('url') but removed from global
global.URLPattern; // already a part of require('url') but removed from global
// after
const {
encodeURI,
decodeURI,
encodeURIComponent,
escape,
unescape,
decodeURIComponent
} = require('url');
Stream #
The following JavaScript globals are moved to top level exports of the require('stream') object:
// before
global.CompressionStream;
global.DecompressionStream;
global.ReadableByteStreamController;
global.ReadableStream;
global.ReadableStreamBYOBReader;
global.ReadableStreamBYOBRequest;
global.ReadableStreamDefaultController;
global.ReadableStreamDefaultReader;
global.TransformStream;
global.TransformStreamDefaultController;
global.WritableStream;
global.WritableStreamDefaultController;
global.WritableStreamDefaultWriter;
global.ByteLengthQueuingStrategy;
global.CountQueuingStrategy;
// after
const {
CompressionStream,
DecompressionStream,
ReadableByteStreamController,
ReadableStream,
ReadableStreamBYOBReader,
ReadableStreamBYOBRequest,
ReadableStreamDefaultController,
ReadableStreamDefaultReader,
TransformStream,
TransformStreamDefaultController,
WritableStream,
WritableStreamDefaultController,
WritableStreamDefaultWriter,
ByteLengthQueuingStrategy,
CountQueuingStrategy
} = require('stream');
Other Removed Globals #
The following globals are removed as they already exist in an internal module:
// redundant with require('process');
global.process;
// redundant with require('timers');
global.setTimeout;
global.clearTimeout;
global.setImmediate;
global.clearImmediate;
global.setInterval;
global.clearInterval;
// redundant with require('perf_hooks');
global.performance;
global.Performance;
global.PerformanceEntry;
global.PerformanceMark;
global.PerformanceMeasure;
global.PerformanceObserver;
global.PerformanceObserverEntryList;
global.PerformanceResourceTiming;
Dropped Functionality #
- The
Bufferclass is removed entirely in favor ofTypedArray. - Either completely remove
require()or completely removeimport.- Exactly one of CJS or ESM is ideal.
JavaScript & V8 Modifications #
I don't know much about V8 internals but I think it would also need to be a "hard fork" of V8 versus a floating patch.
Modified Functionality #
typeof Operator #
Calling typeof null now returns a string value of 'null'.
Object Stringification #
Generally, when objects are stringified they will be converted to JSON:
Object.prototype.toString = function(...args) { return JSON.stringify(this, ...args); };
Set.prototype.toString = function() { return JSON.stringify(Array.from(this)); };
Map.prototype.toString = function() { return JSON.stringify(Object.fromEntries(this)); };
Date.prototype.toString = Date.prototype.toISOString;
Dropped Functionality #
String Prototype Methods #
The following string prototype methods are removed:
// before
String.prototype.anchor;
String.prototype.big;
String.prototype.blink;
String.prototype.bold;
String.prototype.fixed;
String.prototype.fontcolor;
String.prototype.fontsize;
String.prototype.italics;
String.prototype.link;
String.prototype.small;
String.prototype.strike;
String.prototype.sub;
String.prototype.sup;
btoa and atob #
The btoa and atob global functions are removed however the functionality still exists as string methods:
// before
global.btoa;
global.atob;
// after
String.prototype.toBase64 = function() { return _btoa(this); };
String.prototype.fromBase64 = function() { return _atob(this); };
URL Methods #
Several global URL functions are removed and made into string methods. Note that these removed functions are moved to the require('url') internal module, as mentioned above.
// before
global.encodeURI;
global.decodeURI;
global.escape;
global.unescape;
global.decodeURIComponent;
// after
String.prototype.encodeURI = function() { return global.encodeURI(this); };
String.prototype.decodeURI = function() { return global.decodeURI(this); };
String.prototype.escapeURI = function() { return global.escape(this); };
String.prototype.unescapeURI = function() { return global.unescape(this); };
String.prototype.decodeURIComponent = function() { return global.decodeURIComponent(this);
New String Methods #
The following additional String prototype methods are added:
// convert a JSON string into a new object:
String.prototype.fromJson = function() { return JSON.parse(this); };
// create an array buffer from a string:
String.prototype.toArrayBuffer = function() { return (new _encoder()).encode(this); };
History Log
Complete History Log| Operation | Instigator | Revision | When |
|---|---|---|---|
| New RFC revision created: 13 | Thomas Hunter II | r13 | |
| New RFC revision created: 12 | Thomas Hunter II | r12 | |
| New RFC revision created: 11 | Thomas Hunter II | r11 | |
| New RFC revision created: 10 | Thomas Hunter II | r10 | |
| New RFC revision created: 9 | Thomas Hunter II | r9 | |
| New RFC revision created: 8 | Thomas Hunter II | r8 | |
| RFC visibility changed from Internal to Public | Thomas Hunter II | r7 | |
| New RFC revision created: 7 | Thomas Hunter II | r7 | |
| RFC status changed from draft to review | Thomas Hunter II | r6 | |
| New RFC revision created: 6 | Thomas Hunter II | r6 |
RFC Revision: