Skip to content

Commit

Permalink
0.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathantneal committed Dec 14, 2021
0 parents commit 65d1510
Show file tree
Hide file tree
Showing 17 changed files with 625 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
node_modules
package-lock.json
mod.js*
yarn.lock
*.log*
.*
!.gitignore
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# WebAPI

**WebAPI** lets you use Web APIs in Node.

```shell
npm install @astropub/webapi
```



## Usage

**WebAPI** lets you use Web APIs in Node.

As individual exports.

```js
import { AbortController, Blob, Event, EventTarget, File, fetch, Response } from '@astropub/webapi'
```

Polyfilling `globalThis`.

```js
import { polyfill } from '@astropub/webapi'

polyfill(globalThis)
```

---



## License

Licensed under the CC0-1.0 License.
124 changes: 124 additions & 0 deletions build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { rollup } from 'rollup'
import { nodeResolve } from '@rollup/plugin-node-resolve'
import { default as MagicString } from 'magic-string'
import { readFile as nodeReadFile } from 'node:fs/promises'
import { default as inject } from '@rollup/plugin-inject'

const readFileCache = Object.create(null)

const readFile = (/** @type {string} */ id) => readFileCache[id] || (readFileCache[id] = nodeReadFile(id, 'utf8'))

const pathToDOMException = new URL('./src/lib/DOMException.js', import.meta.url).pathname

const plugins = [
nodeResolve({
dedupe: [
'net',
'node:net'
]
}),
inject({
'AbortController': [ 'abort-controller/dist/abort-controller.mjs', 'AbortController' ],
'ReadableStream': [ 'web-streams-polyfill/dist/ponyfill.es6.mjs', 'ReadableStream' ],
'globalThis.ReadableStream': [ 'web-streams-polyfill/dist/ponyfill.es6.mjs', 'ReadableStream' ],
'DOMException': [pathToDOMException, 'DOMException'],
}),
{
async load(id) {
const pathToEsm = id
const pathToMap = `${pathToEsm}.map`

const code = await readFile(pathToEsm, 'utf8')

const indexes = []

const replacements = [
[ /(^|\n)import\s+[^']+'node:(fs|path|worker_threads)'/g, `` ],

[ /\nif \(\s*typeof Global[\W\w]+?\n\}/g, `` ],
[ /\nif \(\s*typeof window[\W\w]+?\n\}/g, `` ],
[ /\nif \(!globalThis\.ReadableStream\) \{[\W\w]+?\n\}/g, `` ],
[ /\nif \(typeof SymbolPolyfill[\W\w]+?\n\}/g, `` ],

[ /\nconst globals = getGlobals\(\);/g, `` ],
[ /\nconst queueMicrotask = [\W\w]+?\n\}\)\(\);/g, ``],
[ /\nconst NativeDOMException =[^;]+;/g, `` ],
[ /\nconst SymbolPolyfill\s*=[^;]+;/g, '\nconst SymbolPolyfill = Symbol;'],
[ /\n(const|let) DOMException[^;]*;/g, `let DOMException$1=DOMException` ],
[ /\nconst DOMException = globalThis.DOMException[\W\w]+?\}\)\(\)/g, `` ],

[ / new DOMException\$1/g, `new DOMException` ],
[ / from 'net'/g, `from 'node:net'` ],
[ / throw createInvalidStateError/g, `throw new DOMException` ],
[ /= createAbortController/g, `= new AbortController` ],
[ /\nconst queueMicrotask = [\W\w]+?\n\}\)\(\)\;/g, `` ],
]

for (const [replacee, replacer] of replacements) {
replacee.index = 0

let replaced = null

while ((replaced = replacee.exec(code)) !== null) {
const leadIndex = replaced.index
const tailIndex = replaced.index + replaced[0].length

indexes.unshift([ leadIndex, tailIndex, replacer ])
}
}

if (indexes.length) {
const magicString = new MagicString(code)

indexes.sort(
([leadOfA], [leadOfB]) => leadOfA - leadOfB
)

for (const [leadIndex, tailindex, replacer] of indexes) {
magicString.overwrite(leadIndex, tailindex, replacer)
}

const magicMap = magicString.generateMap({ source: pathToEsm, file: pathToMap, includeContent: true })

const modifiedEsm = magicString.toString()
const modifiedMap = magicMap.toString()

return { code: modifiedEsm, map: modifiedMap }
}
},
},
]



async function build() {
const configs = [
{
inputOptions: {
input: 'src/polyfill.js',
plugins: plugins,
onwarn(warning, warn) {
if (warning.code !== 'UNRESOLVED_IMPORT') warn(warning)
},
},
outputOptions: {
inlineDynamicImports: true,
file: 'mod.js',
format: 'esm',
sourcemap: true,
},
},
]

for (const config of configs) {
const bundle = await rollup(config.inputOptions)

// or write the bundle to disk
await bundle.write(config.outputOptions)

// closes the bundle
await bundle.close()
}
}

build()
67 changes: 67 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
{
"name": "@astropub/webapi",
"description": "Use Web APIs in Node",
"version": "0.1.0",
"type": "module",
"exports": {
".": "./mod.js"
},
"files": [
"mod.js",
"mod.js.map"
],
"keywords": [
"astro",
"api",
"cancelAnimationFrame",
"clearImmediate",
"clearInterval",
"fetch",
"requestAnimationFrame",
"setImmediate",
"setInterval",
"web"
],
"license": "CC0-1.0",
"repository": "astro-community/webapi",
"author": "Jonathan Neal <[email protected]>",
"bugs": "https://github.com/astro-community/webapi/issues",
"homepage": "https://github.com/astro-community/webapi#readme",
"devDependencies": {
"@rollup/plugin-inject": "^4.0.3",
"@rollup/plugin-node-resolve": "^13.1.1",
"@rollup/plugin-typescript": "^8.3.0",
"@skypack/package-check": "0.2.2",
"abort-controller": "^3.0.0",
"event-target-shim": "^6.0.2",
"fetch-blob": "^3.1.3",
"formdata-polyfill": "^4.0.10",
"magic-string": "^0.25.7",
"node-fetch": "^3.1.0",
"rollup": "^2.61.1",
"rollup-plugin-terser": "^7.0.2",
"web-streams-polyfill": "^3.2.0"
},
"scripts": {
"build": "node build.js",
"release": "npm publish --access public",
"test": "package-check"
},
"prettier": {
"semi": false,
"singleQuote": true,
"trailingComma": "es5",
"useTabs": true,
"overrides": [
{
"files": [
".stackblitzrc",
"*.json"
],
"options": {
"useTabs": false
}
}
]
}
}
7 changes: 7 additions & 0 deletions src/lib/AnimationFrame.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export declare var requestAnimationFrame: {
(callback: (...args: any[]) => any): number
}

export declare var cancelAnimationFrame: {
(requestId: number): void
}
39 changes: 39 additions & 0 deletions src/lib/AnimationFrame.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// @ts-check

import { setTimeout as nodeSetTimeout, clearTimeout as nodeClearTimeout } from 'node:timers'
import { __function_bind, __performance_now } from './utils.js'

const INTERNAL = { tick: 0, pool: new Map }

/** @type {<TArgs extends any[], TFunc extends (...args: TArgs) => any>(callback: TFunc) => number} */
export function requestAnimationFrame(callback) {
if (!INTERNAL.pool.size) {
nodeSetTimeout(() => {
const next = __performance_now()

for (const func of INTERNAL.pool.values()) {
func(next)
}

INTERNAL.pool.clear()
}, 16)
}

const func = __function_bind(callback, undefined)
const tick = ++INTERNAL.tick

INTERNAL.pool.set(tick, func)

return tick
}

/** @type {{ (timeoutId: number): void }} */
export function cancelAnimationFrame(requestId) {
const timeout = INTERNAL.pool.get(requestId)

if (timeout) {
nodeClearTimeout(timeout)

INTERNAL.pool.delete(requestId)
}
}
11 changes: 11 additions & 0 deletions src/lib/CustomEvent.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { Event } from './Event'

interface CustomEvent<TEventType extends string = string, TEventInit extends EventInit = EventInit> extends Event {}

export declare var CustomEvent: CustomEvent

interface EventInit {
bubbles?: boolean
cancelable?: boolean
composed?: boolean
}
7 changes: 7 additions & 0 deletions src/lib/CustomEvent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// @ts-check

import { Event } from 'event-target-shim'

class CustomEvent extends Event {}

export { CustomEvent }
36 changes: 36 additions & 0 deletions src/lib/DOMException.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// @ts-check

export class DOMException extends Error {
constructor(message = '', name = 'Error') {
super(message)

this.code = 0
this.name = name
}

static INDEX_SIZE_ERR = 1
static DOMSTRING_SIZE_ERR = 2
static HIERARCHY_REQUEST_ERR = 3
static WRONG_DOCUMENT_ERR = 4
static INVALID_CHARACTER_ERR = 5
static NO_DATA_ALLOWED_ERR = 6
static NO_MODIFICATION_ALLOWED_ERR = 7
static NOT_FOUND_ERR = 8
static NOT_SUPPORTED_ERR = 9
static INUSE_ATTRIBUTE_ERR = 10
static INVALID_STATE_ERR = 11
static SYNTAX_ERR = 12
static INVALID_MODIFICATION_ERR = 13
static NAMESPACE_ERR = 14
static INVALID_ACCESS_ERR = 15
static VALIDATION_ERR = 16
static TYPE_MISMATCH_ERR = 17
static SECURITY_ERR = 18
static NETWORK_ERR = 19
static ABORT_ERR = 20
static URL_MISMATCH_ERR = 21
static QUOTA_EXCEEDED_ERR = 22
static TIMEOUT_ERR = 23
static INVALID_NODE_TYPE_ERR = 24
static DATA_CLONE_ERR = 25
}
9 changes: 9 additions & 0 deletions src/lib/Timeout.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/** Evaluates an expression after a number of milliseconds, returning a numeric id for the timer. */
export declare var setTimeout: {
<TArgs extends any[]>(callback: (...args: TArgs) => void, delay?: number, ...args: TArgs): number
}

/** Clears a timer set with a given id. */
export declare var clearTimeout: {
(timeout: number): void
}
28 changes: 28 additions & 0 deletions src/lib/Timeout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// @ts-check

import { setTimeout as nodeSetTimeout, clearTimeout as nodeClearTimeout } from 'node:timers'
import { __function_bind } from './utils.js'

const INTERNAL = { tick: 0, pool: new Map }

/** @type {<TArgs extends any[], TFunc extends (...args: TArgs) => any>(callback: TFunc, delay?: number, ...args: TArgs) => number} */
export function setTimeout(callback, delay = 0, ...args) {
const func = __function_bind(callback, globalThis)
const tick = ++INTERNAL.tick
const timeout = nodeSetTimeout(func, delay, ...args)

INTERNAL.pool.set(tick, timeout)

return tick
}

/** @type {{ (timeoutId: number): void }} */
export function clearTimeout(timeoutId) {
const timeout = INTERNAL.pool.get(timeoutId)

if (timeout) {
nodeClearTimeout(timeout)

INTERNAL.pool.delete(timeoutId)
}
}
Loading

0 comments on commit 65d1510

Please sign in to comment.