|
| 1 | +--- |
| 2 | +title: Isomorphic packages |
| 3 | +slug: /developers/architecture/isomorphic-packages |
| 4 | +description: Learn what isomorphic packages are in WordPress Playground, why they matter, and how to avoid browser-only or Node.js-only assumptions. |
| 5 | +--- |
| 6 | + |
| 7 | +# Isomorphic packages |
| 8 | + |
| 9 | +Many WordPress Playground packages are designed to be isomorphic, also known as universal. In this context, an isomorphic package can run in more than one JavaScript environment, most commonly the browser and Node.js, without changing its public API. |
| 10 | + |
| 11 | +This matters because Playground is used in many places: |
| 12 | + |
| 13 | +- In the browser at [playground.wordpress.net](https://playground.wordpress.net/). |
| 14 | +- In embedded iframes controlled through the JavaScript API. |
| 15 | +- In Node.js tools such as `@wp-playground/cli`. |
| 16 | +- In build, test, and automation workflows. |
| 17 | + |
| 18 | +When a package stays isomorphic, the same feature can be reused across those environments. For example, code that prepares a Blueprint, validates a PHP version, or talks to a Playground client can be shared by browser apps, CLI tools, tests, and documentation examples. |
| 19 | + |
| 20 | +## Tradeoffs |
| 21 | + |
| 22 | +Isomorphic code is more portable, but it also has to avoid APIs that only exist in one runtime. |
| 23 | + |
| 24 | +Browser-only APIs include: |
| 25 | + |
| 26 | +- `window` |
| 27 | +- `document` |
| 28 | +- `DOMParser` |
| 29 | +- `localStorage` |
| 30 | +- Service Worker APIs |
| 31 | + |
| 32 | +Node.js-only APIs include: |
| 33 | + |
| 34 | +- `fs` |
| 35 | +- `path` |
| 36 | +- `process` |
| 37 | +- `Buffer` |
| 38 | +- Native Node.js streams |
| 39 | + |
| 40 | +Using these APIs directly can make a package harder to reuse. A browser app that imports `fs`, or a Node.js script that imports `document`, will fail unless a bundler, polyfill, or adapter fills the gap. |
| 41 | + |
| 42 | +## Patterns for isomorphic code |
| 43 | + |
| 44 | +Prefer platform-neutral APIs when possible: |
| 45 | + |
| 46 | +- Use `fetch`, `URL`, `URLSearchParams`, `TextEncoder`, and `TextDecoder` when they fit the task. |
| 47 | +- Accept data as arguments instead of reading it from the DOM or file system inside shared code. |
| 48 | +- Put browser-specific or Node.js-specific code behind a small adapter. |
| 49 | +- Keep environment detection near the edge of the package, not scattered through shared logic. |
| 50 | + |
| 51 | +For example, a shared package can accept a file-like object or a URL instead of reading directly from `document.querySelector()` or `fs.readFileSync()`. The browser entry point can collect the file from an input element, while the Node.js entry point can read it from disk and pass the same kind of data to the shared function. |
| 52 | + |
| 53 | +## Examples |
| 54 | + |
| 55 | +An isomorphic helper can build a Playground URL from a Blueprint object because it only needs JavaScript objects and URL APIs: |
| 56 | + |
| 57 | +```ts |
| 58 | +export function blueprintToUrl(blueprint: object) { |
| 59 | + const url = new URL('https://playground.wordpress.net/'); |
| 60 | + url.searchParams.set('blueprint', JSON.stringify(blueprint)); |
| 61 | + return url.toString(); |
| 62 | +} |
| 63 | +``` |
| 64 | + |
| 65 | +A browser-specific helper is not isomorphic because it reads from the DOM: |
| 66 | + |
| 67 | +```ts |
| 68 | +export function getBlueprintFromTextarea() { |
| 69 | + return JSON.parse(document.querySelector('textarea')?.value || '{}'); |
| 70 | +} |
| 71 | +``` |
| 72 | + |
| 73 | +A Node.js-specific helper is not isomorphic because it reads from the local file system: |
| 74 | + |
| 75 | +```ts |
| 76 | +import { readFileSync } from 'fs'; |
| 77 | + |
| 78 | +export function getBlueprintFromFile(path: string) { |
| 79 | + return JSON.parse(readFileSync(path, 'utf8')); |
| 80 | +} |
| 81 | +``` |
| 82 | + |
| 83 | +Both non-isomorphic examples can still be useful. The key is to keep them in browser or Node.js entry points, and keep the core package logic portable where reuse across Playground environments is expected. |
0 commit comments