From ed2a53eb67a595a2ec3beb858456a7a33be4b189 Mon Sep 17 00:00:00 2001 From: shirakaba <14055146+shirakaba@users.noreply.github.com> Date: Sun, 10 Mar 2024 22:31:36 +0900 Subject: [PATCH] README --- .vscode/extensions.json | 3 + CONTRIBUTING.md | 46 ++++++++++ README.md | 196 ++++++++++++++++++++++++++++++++++++++++ package.json | 24 +++++ 4 files changed, 269 insertions(+) create mode 100644 .vscode/extensions.json create mode 100644 CONTRIBUTING.md create mode 100644 README.md diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..1d7ac85 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"] +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..f984bd2 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,46 @@ +# Contributing + +## Setting up the repo + +```sh +git clone git@github.com:shirakaba/dom-events-wintercg.git +cd dom-events-wintercg +npm install +``` + +## Running the tests + +```sh +npm run test + +# Or, in watch mode: +npm run test -- --watch +``` + +## Linting and formatting + +Any PRs made to the repo should be properly formatted and raise no linting errors. + +Upon opening this repo in VS Code, you should automatically be recommended to install the Eslint and Prettier IDE extensions: [dbaeumer.vscode-eslint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) and [esbenp.prettier-vscode](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode). + +I personally enable `editor.formatOnSave` so that my code is prettified upon save, and I have a keyboard shortcut set up for applying linting autofixes (`eslint.executeAutofix`). But you can alternatively run these upon: + +```sh +npm run format +npm run lint + +# Or, applying autofixes: +npm run lint -- --fix +``` + +## Versioning + +We follow [semver](https://semver.org) in lockstep with Node.js. + +- The major number will reflect which version of the Node.js internals the code is derived from (e.g. `21.x.x` if derived from Node.js `21.7.1`). +- The minor number will likely always be fixed to `0` (given that both the API and implementation should be pretty stable), but we'll see whether it ever makes sense to increment it. +- The patch number will freely increment. + +## Coding philosophy + +As this repo extracts code from Node.js core, we want to minimise the difference between the copied Node.js core code and our own to make it easier to adopt any future upstream changes. Hence, our linting and formatting configurations derive from theirs, and we avoid refactoring code unnecessarily. diff --git a/README.md b/README.md new file mode 100644 index 0000000..be667d9 --- /dev/null +++ b/README.md @@ -0,0 +1,196 @@ +# DOM Events implementation for WinterCG + +[![npm status](https://img.shields.io/npm/v/dom-events-wintercg.svg)](https://npm.im/dom-events-wintercg) + +A polyfill for the [DOM Events](https://dom.spec.whatwg.org/#introduction-to-dom-events) APIs: + +- [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent) +- [Event](https://developer.mozilla.org/en-US/docs/Web/API/Event) +- [EventTarget](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget) + +Implementation extracted from the Node.js codebase [as of 10th March 2024](https://github.com/nodejs/node/blob/575ced813988af00478aa1e6759487888c607238/lib/internal/event_target.js) (version `21.7.1`, I believe). Some simplified internals extracted from [readable-stream](https://github.com/nodejs/readable-stream/tree/main). + +To clarify, this project is not affiliated with [WinterCG](https://wintercg.org) (i.e. is not an official work). It merely implements part of the WinterCG [Common Minimum API](https://github.com/wintercg/proposal-common-minimum-api) proposal. + +## Installation + +Install this npm package as follows, depending on which package manager you're using. + +- **npm:** + + ```sh + npm install --save dom-events-wintercg + ``` + +- **Yarn:** + + ```sh + yarn add dom-events-wintercg + ``` + +- **pnpm:** + + ```sh + pnpm add dom-events-wintercg + ``` + +- **Bun:** + + ```sh + bun add dom-events-wintercg + ``` + +- **Deno:** No need to install. Just add the [npm: specifier](https://docs.deno.com/runtime/manual/node/npm_specifiers) when importing. + +## Usage + +### As a polyfill + +Run this polyfill in your app's entrypoint file so that it fills in the APIs as early as possible in the app lifecycle. + +```js +import { polyfill } from 'dom-events-wintercg'; + +polyfill(globalThis); + +// Event, EventTarget, and CustomEvent will now be available in global scope + +const eventTarget = new EventTarget(); +const event = new Event('click', {}); +eventTarget.addEventListener('click', (event) => { + console.log(`Fired "${event.type}" event!`, event); +}); +eventTarget.dispatchEvent(event, 'abc'); +``` + +And for TypeScript typings, add the `DOM` lib in `tsconfig.json`: + +```js +{ + "compilerOptions": { + "lib": ["DOM"], + // ... + } +} +``` + +### As a module + +Here, we import from the npm package each time we want to use an API, rather than polyfilling globally. + +```js +import { Event, EventTarget } from 'dom-events-wintercg'; + +const eventTarget = new EventTarget(); +const event = new Event('click', {}); +eventTarget.addEventListener('click', (event) => { + console.log(`Fired "${event.type}" event!`, event); +}); +eventTarget.dispatchEvent(event, 'abc'); +``` + +Some limited TypeScript typings will be inferred from the library's JavaScript source code, but if you'd rather use the `lib.dom.d.ts` typings built into TypeScript (which I would recommend), then: + +1. Add the `DOM` lib in `tsconfig.json`: + + ```js + { + "compilerOptions": { + "lib": ["DOM"], + // ... + } + } + ``` + +2. Do this little dance: + + ```ts + import { + Event as EventImpl, + EventTarget as EventTargetImpl, + } from 'dom-events-wintercg'; + + // Redeclare the implementation using the types from lib.dom.d.ts + const Event = EventImpl as unknown as Event; + const EventTarget = EventTargetImpl as unknown as EventTarget; + + const eventTarget = new EventTarget(); + const event = new Event('click', {}); + eventTarget.addEventListener('click', (event) => { + console.log(`Fired "${event.type}" event!`, event); + }); + eventTarget.dispatchEvent(event, 'abc'); + ``` + +### Via a bundler + +This is my best-effort attempt to document usage with a bundler. These instructions are **untested**, so please open a PR if you find they need tweaking! + +In all cases, you can set up TypeScript typings via adding the `DOM` lib to your `tsconfig.json`: + +```js +{ + "compilerOptions": { + "lib": ["DOM"], + // ... + } +} +``` + +Below, I'll describe for each bundler how to integrate this package into your bundle. + +#### Webpack 5 + +This configuration ensures that `CustomEvent`, `Event`, and `EventTarget` are available from global scope: + +```js +const webpackConfig = { + plugins: [ + new webpack.ProvidePlugin({ + CustomEvent: ['dom-events-wintercg', 'CustomEvent'], + Event: ['dom-events-wintercg', 'Event'], + EventTarget: ['dom-events-wintercg', 'EventTarget'], + }), + ], +}; +``` + +Additionally, you can polyfill _some of_ the Node.js [events](https://nodejs.org/api/events.html) module (e.g. to use a Node.js library in a browser app) as follows. ⚠️ Be warned that while this package implements `CustomEvent`, `Event`, and `EventTarget`, it does not implement _all_ the APIs in the Node.js `events` module. For example, it does not implement `EventEmitter`. + +```diff + const webpackConfig = { ++ resolve: { ++ fallback: { ++ events: require.resolve('dom-events-wintercg'), ++ }, ++ }, + plugins: [ + new webpack.ProvidePlugin({ + CustomEvent: ['dom-events-wintercg', 'CustomEvent'], + Event: ['dom-events-wintercg', 'Event'], + EventTarget: ['dom-events-wintercg', 'EventTarget'], + }), + ], + }; +``` + +## Prerequisities + +Your JS engine or runtime must support the following APIs (this is a non-exhaustive list): + +- At least [ES6](https://www.w3schools.com/js/js_es6.asp). I'm not sure exactly what this repo makes use of, but technically the linter allows up to ES2022. +- [Private properties](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_properties) +- [FinalizationRegistry](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/FinalizationRegistry) +- [globalThis](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis) +- [WeakMap](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) +- [WeakSet](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) +- [WeakRef](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakRef) +- Basic [ESM](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) (`import` and `export`) + +## Differences from browser EventTarget + +Beyond the differences explained in the Node.js [SDK docs](https://nodejs.org/api/events.html#nodejs-eventtarget-vs-dom-eventtarget), see this excellent article from NearForm about how they first [brought EventTarget to Node.js](https://www.nearform.com/insights/node-js-and-the-struggles-of-being-an-eventtarget/), which covers some of the compromises they had to make in the implementation. In particular, there is no concept of bubbling or capturing, and `event.preventDefault()` is a bit useless, as it never has a "default action" to prevent. + +## Integrating into runtimes + +This library, being runtime-agnostic, does nothing to keep the event loop alive for Worker event listeners. See the [Node.js internals](https://github.com/nodejs/node/blob/ba06c5c509956dc413f91b755c1c93798bb700d4/lib/internal/worker/io.js#L293) for how they implemented that. diff --git a/package.json b/package.json index eda6e63..d064038 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,32 @@ { "name": "dom-events-wintercg", "version": "21.0.0", + "description": "An polyfill for DOM Events and related APIs, extracted from Node.js, for use in WinterCG runtimes", + "author": "Jamie Birch", + "license": "MIT", "type": "module", "main": "src/index.js", + "files": [ + "LICENCE-nodejs", + "src" + ], + "repository": { + "type": "git", + "url": "git://github.com/shirakaba/event-target-wintercg" + }, + "bugs": { + "url": "https://github.com/shirakaba/event-target-wintercg/issues" + }, + "keywords": [ + "AbortController", + "AbortSignal", + "CustomEvent", + "DOMException", + "Event", + "EventTarget", + "polyfill", + "WinterCG" + ], "scripts": { "format": "prettier --write .", "lint": "eslint --max-warnings=0 ."