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

extract primitives/load function #327

Merged
merged 4 commits into from
May 9, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion .changeset/beige-lions-film.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
"@edge-runtime/jest-environment": patch
'@edge-runtime/jest-environment': patch
---

Add "react-server" export condition to Jest environment
2 changes: 1 addition & 1 deletion .changeset/gold-apples-walk.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
"edge-runtime": patch
'edge-runtime': patch
---

chore: upgrade `async-listen` dependency
6 changes: 6 additions & 0 deletions .changeset/light-chicken-wait.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@edge-runtime/primitives': patch
'@edge-runtime/vm': patch
---

Extract a `@edge-runtime/primitives/load` entrypoint that loads the primitives given a scoped global context
2 changes: 0 additions & 2 deletions packages/integration-tests/tests/request.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ test('serialize body into JSON', async () => {

test('adds duplex: half to all requests', () => {
const request = new Request('https://example.vercel.sh')
// @ts-expect-error duplex is not defined on Request
expect(request.duplex).toBe('half')
})

Expand All @@ -49,7 +48,6 @@ test('can be extended', async () => {
headers: { 'x-test': 'hello' },
})

// @ts-expect-error duplex is not defined on Request
expect(request.duplex).toBe('half')
expect(request.myField).toBe('default value')
request.setField('new value')
Expand Down
4 changes: 4 additions & 0 deletions packages/primitives/load/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"main": "../dist/load.js",
"types": "../types/load.d.ts"
}
193 changes: 2 additions & 191 deletions packages/primitives/src/primitives/index.js
Original file line number Diff line number Diff line change
@@ -1,194 +1,5 @@
// @ts-check

const path = require('path')
const nodeCrypto = require('crypto')
import { load } from './load'

function load() {
/** @type {Record<string, any>} */
const context = {}

const encodingImpl = require('./encoding')
Object.assign(context, {
TextDecoder: encodingImpl.TextDecoder,
TextEncoder: encodingImpl.TextEncoder,
atob: encodingImpl.atob,
btoa: encodingImpl.btoa,
})

const consoleImpl = requireWithFakeGlobalScope({
context,
path: path.resolve(__dirname, './console.js'),
scopedContext: {},
})
Object.assign(context, { console: consoleImpl.console })

const eventsImpl = require('./events')
Object.assign(context, {
Event: eventsImpl.Event,
EventTarget: eventsImpl.EventTarget,
FetchEvent: eventsImpl.FetchEvent,
PromiseRejectionEvent: eventsImpl.PromiseRejectionEvent,
})

const streamsImpl = require('./streams')
const textEncodingStreamImpl = requireWithFakeGlobalScope({
context,
path: path.resolve(__dirname, './text-encoding-streams.js'),
scopedContext: streamsImpl,
})

Object.assign(context, {
ReadableStream: streamsImpl.ReadableStream,
ReadableStreamBYOBReader: streamsImpl.ReadableStreamBYOBReader,
ReadableStreamDefaultReader: streamsImpl.ReadableStreamDefaultReader,
TextDecoderStream: textEncodingStreamImpl.TextDecoderStream,
TextEncoderStream: textEncodingStreamImpl.TextEncoderStream,
TransformStream: streamsImpl.TransformStream,
WritableStream: streamsImpl.WritableStream,
WritableStreamDefaultWriter: streamsImpl.WritableStreamDefaultWriter,
})

const abortControllerImpl = requireWithFakeGlobalScope({
context,
path: path.resolve(__dirname, './abort-controller.js'),
scopedContext: eventsImpl,
})
Object.assign(context, abortControllerImpl)

const urlImpl = require('./url')
Object.assign(context, {
URL: urlImpl.URL,
URLSearchParams: urlImpl.URLSearchParams,
URLPattern: urlImpl.URLPattern,
})

const blobImpl = requireWithFakeGlobalScope({
context,
path: path.resolve(__dirname, './blob.js'),
scopedContext: streamsImpl,
})
Object.assign(context, {
Blob: blobImpl.Blob,
})

const structuredCloneImpl = requireWithFakeGlobalScope({
path: path.resolve(__dirname, './structured-clone.js'),
context,
scopedContext: streamsImpl,
})
Object.assign(context, {
structuredClone: structuredCloneImpl.structuredClone,
})

const fetchImpl = requireWithFakeGlobalScope({
context,
path: path.resolve(__dirname, './fetch.js'),
cache: new Map([
['abort-controller', { exports: abortControllerImpl }],
['streams', { exports: streamsImpl }],
]),
scopedContext: {
...streamsImpl,
...urlImpl,
...abortControllerImpl,
...eventsImpl,
structuredClone: context.structuredClone,
},
})
Object.assign(context, {
fetch: fetchImpl.fetch,
File: fetchImpl.File,
FormData: fetchImpl.FormData,
Headers: fetchImpl.Headers,
Request: fetchImpl.Request,
Response: fetchImpl.Response,
WebSocket: fetchImpl.WebSocket,
})

if (typeof SubtleCrypto !== 'undefined') {
Object.assign(context, {
crypto: globalThis.crypto,
Crypto: globalThis.Crypto,
CryptoKey: globalThis.CryptoKey,
SubtleCrypto: globalThis.SubtleCrypto,
})
// @ts-ignore
} else if (nodeCrypto.webcrypto) {
Object.assign(context, {
// @ts-ignore
crypto: nodeCrypto.webcrypto,
// @ts-ignore
Crypto: nodeCrypto.webcrypto.constructor,
// @ts-ignore
CryptoKey: nodeCrypto.webcrypto.CryptoKey,
// @ts-ignore
SubtleCrypto: nodeCrypto.webcrypto.subtle.constructor,
})
} else {
const cryptoImpl = require('./crypto')
Object.assign(context, {
crypto: cryptoImpl.crypto,
Crypto: cryptoImpl.Crypto,
CryptoKey: cryptoImpl.CryptoKey,
SubtleCrypto: cryptoImpl.SubtleCrypto,
})
}

return context
}

module.exports = load()

import Module from 'module'
import { dirname } from 'path'
import { readFileSync } from 'fs'

/**
* @param {Object} params
* @param {unknown} params.context
* @param {Map<string, any>} [params.cache]
* @param {string} params.path
* @param {Set<string>} [params.references]
* @param {Record<string, any>} params.scopedContext
* @returns {any}
*/
function requireWithFakeGlobalScope(params) {
const resolved = path.resolve(params.path)
const getModuleCode = `(function(module,exports,require,__dirname,__filename,globalThis,${Object.keys(
params.scopedContext
).join(',')}) {${readFileSync(resolved, 'utf-8')}\n})`
const module = {
exports: {},
loaded: false,
id: resolved,
}

const moduleRequire = (Module.createRequire || Module.createRequireFromPath)(
resolved
)

function throwingRequire(path) {
if (path.startsWith('./')) {
const moduleName = path.replace(/^\.\//, '')
if (!params.cache || !params.cache.has(moduleName)) {
throw new Error(`Cannot find module '${moduleName}'`)
}
return params.cache.get(moduleName).exports
}
return moduleRequire(path)
}

throwingRequire.resolve = moduleRequire.resolve.bind(moduleRequire)

eval(getModuleCode)(
module,
module.exports,
throwingRequire,
dirname(resolved),
resolved,
params.context,
...Object.values(params.scopedContext)
)

return module.exports
}
module.exports = load({})
Loading