Skip to content

Commit

Permalink
A boot() method to explicitly initialize the PHP worker (#1669)
Browse files Browse the repository at this point in the history
## Motivation for the change, related issues

Refactors the `remote.html` and `worker-thread.ts` boot flows. 

* **Before this PR**, both files passed configuration using query
arguments and relied on implicit logic surrounding the endpoint class
* **After this PR**, both API Endpoint classes expose a `boot()` method
that is strongly typed, accepts the configuration values, and explicitly
orchestrates all the boot logic when it is called.

This work may eventually enable:

* A reusable, isomorphic Worker implementation that can be reused in the
browser and in a local CLI setup.
* Exposing `bootWordPress()` on a worker instance.
* Hot-swapping the PHP runtime and WordPress instance (e.g. by killing
the current worker and starting a new one).
* Spawning new workers, not just PHP class instances, via
PHPProcessManager.

Related to #1398

## Follow-up work

* Explore spawning multiple workers for handling requests. This might
also require:
* Exposing the full boot protocol (`bootWordPress()`) on a worker
instance instead of the simplified `boot()` method.
   * Load balancer across all workers handling the same site instance.
* An OPFS Emscripten filesystem backend so that each worker can see the
changes made by other workers. This is a potential blocker as Emscripten
didn't seem to have a good way of doing that a few months ago.

## Testing Instructions (or ideally a Blueprint)

* Confirm the tests are all green
* Carefully review the changes, point out anything that seems off

cc @brandonpayton @bgrgicak

---------

Co-authored-by: Brandon Payton <brandon@happycode.net>
  • Loading branch information
adamziel and brandonpayton authored Aug 19, 2024
1 parent 298438b commit ccadc7d
Show file tree
Hide file tree
Showing 37 changed files with 896 additions and 1,096 deletions.
27 changes: 12 additions & 15 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,61 +4,58 @@ All notable changes to this project are documented in this file by a CI job
that runs on every NPM release. The file follows the [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
format.

## [v0.9.30] (2024-08-19)
## [v0.9.30] (2024-08-19)

### Website

- Ask users to report errors if Playground load fails. ([#1686](https://github.com/WordPress/wordpress-playground/pull/1686))
- Ask users to report errors if Playground load fails. ([#1686](https://github.com/WordPress/wordpress-playground/pull/1686))

### Bug Fixes

- Avoid Blueprint schema formatting changes by build. ([#1685](https://github.com/WordPress/wordpress-playground/pull/1685))
- Avoid Blueprint schema formatting changes by build. ([#1685](https://github.com/WordPress/wordpress-playground/pull/1685))

### Various

- [Website] Improves the messaging around exporting a zip if needed, when connecting to GitHub. ([#1689](https://github.com/WordPress/wordpress-playground/pull/1689))
- [Website] Improves the messaging around exporting a zip if needed, when connecting to GitHub. ([#1689](https://github.com/WordPress/wordpress-playground/pull/1689))

### Contributors

The following contributors merged PRs in this release:

@brandonpayton @jonathanbossenger


## [v0.9.29] (2024-08-12)
## [v0.9.29] (2024-08-12)

### Tools

- Add max-len rule. ([#1613](https://github.com/WordPress/wordpress-playground/pull/1613))
- Add max-len rule. ([#1613](https://github.com/WordPress/wordpress-playground/pull/1613))

### Experiments


#### GitHub integration

- Add site manager view and sidebar. ([#1661](https://github.com/WordPress/wordpress-playground/pull/1661))
- Add sites from the site manager. ([#1680](https://github.com/WordPress/wordpress-playground/pull/1680))
- Add site manager view and sidebar. ([#1661](https://github.com/WordPress/wordpress-playground/pull/1661))
- Add sites from the site manager. ([#1680](https://github.com/WordPress/wordpress-playground/pull/1680))

### PHP WebAssembly

- Offline mode end-to-end tests. ([#1648](https://github.com/WordPress/wordpress-playground/pull/1648))
- Offline mode end-to-end tests. ([#1648](https://github.com/WordPress/wordpress-playground/pull/1648))

### Website

- Add nice redirects for the new documentation site. ([#1681](https://github.com/WordPress/wordpress-playground/pull/1681))
- Fix site manager button styles. ([#1676](https://github.com/WordPress/wordpress-playground/pull/1676))
- Add nice redirects for the new documentation site. ([#1681](https://github.com/WordPress/wordpress-playground/pull/1681))
- Fix site manager button styles. ([#1676](https://github.com/WordPress/wordpress-playground/pull/1676))

### Bug Fixes

- Revert "Offline mode end-to-end tests". ([#1673](https://github.com/WordPress/wordpress-playground/pull/1673))
- Revert "Offline mode end-to-end tests". ([#1673](https://github.com/WordPress/wordpress-playground/pull/1673))

### Contributors

The following contributors merged PRs in this release:

@adamziel @bgrgicak


## [v0.9.28] (2024-08-05)

### Blueprints
Expand Down
27 changes: 12 additions & 15 deletions packages/docs/site/docs/main/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,61 +9,58 @@ All notable changes to this project are documented in this file by a CI job
that runs on every NPM release. The file follows the [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
format.

## [v0.9.30] (2024-08-19)
## [v0.9.30] (2024-08-19)

### Website

- Ask users to report errors if Playground load fails. ([#1686](https://github.com/WordPress/wordpress-playground/pull/1686))
- Ask users to report errors if Playground load fails. ([#1686](https://github.com/WordPress/wordpress-playground/pull/1686))

### Bug Fixes

- Avoid Blueprint schema formatting changes by build. ([#1685](https://github.com/WordPress/wordpress-playground/pull/1685))
- Avoid Blueprint schema formatting changes by build. ([#1685](https://github.com/WordPress/wordpress-playground/pull/1685))

### Various

- [Website] Improves the messaging around exporting a zip if needed, when connecting to GitHub. ([#1689](https://github.com/WordPress/wordpress-playground/pull/1689))
- [Website] Improves the messaging around exporting a zip if needed, when connecting to GitHub. ([#1689](https://github.com/WordPress/wordpress-playground/pull/1689))

### Contributors

The following contributors merged PRs in this release:

@brandonpayton @jonathanbossenger


## [v0.9.29] (2024-08-12)
## [v0.9.29] (2024-08-12)

### Tools

- Add max-len rule. ([#1613](https://github.com/WordPress/wordpress-playground/pull/1613))
- Add max-len rule. ([#1613](https://github.com/WordPress/wordpress-playground/pull/1613))

### Experiments


#### GitHub integration

- Add site manager view and sidebar. ([#1661](https://github.com/WordPress/wordpress-playground/pull/1661))
- Add sites from the site manager. ([#1680](https://github.com/WordPress/wordpress-playground/pull/1680))
- Add site manager view and sidebar. ([#1661](https://github.com/WordPress/wordpress-playground/pull/1661))
- Add sites from the site manager. ([#1680](https://github.com/WordPress/wordpress-playground/pull/1680))

### PHP WebAssembly

- Offline mode end-to-end tests. ([#1648](https://github.com/WordPress/wordpress-playground/pull/1648))
- Offline mode end-to-end tests. ([#1648](https://github.com/WordPress/wordpress-playground/pull/1648))

### Website

- Add nice redirects for the new documentation site. ([#1681](https://github.com/WordPress/wordpress-playground/pull/1681))
- Fix site manager button styles. ([#1676](https://github.com/WordPress/wordpress-playground/pull/1676))
- Add nice redirects for the new documentation site. ([#1681](https://github.com/WordPress/wordpress-playground/pull/1681))
- Fix site manager button styles. ([#1676](https://github.com/WordPress/wordpress-playground/pull/1676))

### Bug Fixes

- Revert "Offline mode end-to-end tests". ([#1673](https://github.com/WordPress/wordpress-playground/pull/1673))
- Revert "Offline mode end-to-end tests". ([#1673](https://github.com/WordPress/wordpress-playground/pull/1673))

### Contributors

The following contributors merged PRs in this release:

@adamziel @bgrgicak


## [v0.9.28] (2024-08-05)

### Blueprints
Expand Down
10 changes: 10 additions & 0 deletions packages/php-wasm/web/src/lib/directory-handle-mount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ declare global {
}
}

export type MountDevice =
| {
type: 'opfs';
path: string;
}
| {
type: 'local-fs';
handle: FileSystemDirectoryHandle;
};

export interface MountOptions {
initialSync: {
direction?: 'opfs-to-memfs' | 'memfs-to-opfs';
Expand Down
6 changes: 5 additions & 1 deletion packages/php-wasm/web/src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ export type { LoaderOptions as PHPWebLoaderOptions } from './load-runtime';

export { loadWebRuntime } from './load-runtime';
export { getPHPLoaderModule } from './get-php-loader-module';
export { registerServiceWorker, setPhpApi } from './register-service-worker';
export {
registerServiceWorker,
setPhpInstanceUsedByServiceWorker,
} from './register-service-worker';
export { setupPostMessageRelay } from './setup-post-message-relay';

export { spawnPHPWorkerThread } from './worker-thread/spawn-php-worker-thread';
export { createDirectoryHandleMountHandler } from './directory-handle-mount';
export type {
MountDevice,
MountOptions,
SyncProgress,
SyncProgressCallback,
Expand Down
2 changes: 1 addition & 1 deletion packages/php-wasm/web/src/lib/register-service-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const phpApiPromise = new Promise<Client>((resolve) => {
* @param {Client} api The PHP API client.
*
*/
export function setPhpApi(api: Client) {
export function setPhpInstanceUsedByServiceWorker(api: Client) {
if (!api) {
throw new PhpWasmError('PHP API client must be a valid client object.');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,9 @@
* Spawns a new Worker Thread.
*
* @param workerUrl The absolute URL of the worker script.
* @param config
* @returns The spawned Worker Thread.
*/
export async function spawnPHPWorkerThread(
workerUrl: string,
startupOptions: Record<string, string | string[]> = {}
) {
export async function spawnPHPWorkerThread(workerUrl: string) {
const worker = new Worker(workerUrl, { type: 'module' });
return new Promise<Worker>((resolve, reject) => {
worker.onerror = (e) => {
Expand All @@ -20,10 +16,6 @@ export async function spawnPHPWorkerThread(
(error as any).filename = e.filename;
reject(error);
};
worker.postMessage({
type: 'startup-options',
startupOptions,
});
// There is no way to know when the worker script has started
// executing, so we use a message to signal that.
function onStartup(event: { data: string }) {
Expand Down
116 changes: 37 additions & 79 deletions packages/playground/client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export {
SupportedPHPVersionsList,
LatestSupportedPHPVersion,
} from '@php-wasm/universal';
export type { PlaygroundClient } from '@wp-playground/remote';
export type { PlaygroundClient, MountDescriptor } from '@wp-playground/remote';

export { phpVar, phpVars } from '@php-wasm/util';

Expand All @@ -34,8 +34,9 @@ import {
} from '@wp-playground/blueprints';
import { consumeAPI } from '@php-wasm/web';
import { ProgressTracker } from '@php-wasm/progress';
import { PlaygroundClient } from '@wp-playground/remote';
import type { MountDescriptor, PlaygroundClient } from '@wp-playground/remote';
import { collectPhpLogs, logger } from '@php-wasm/logger';

export interface StartPlaygroundOptions {
iframe: HTMLIFrameElement;
remoteUrl: string;
Expand All @@ -57,7 +58,6 @@ export interface StartPlaygroundOptions {
* @private
*/
sapiName?: string;

/**
* Called before the blueprint steps are run,
* allows the caller to delay the Blueprint execution
Expand All @@ -67,6 +67,8 @@ export interface StartPlaygroundOptions {
*/
onBeforeBlueprint?: () => Promise<void>;
siteSlug?: string;
mounts?: Array<MountDescriptor>;
shouldInstallWordPress?: boolean;
}

/**
Expand All @@ -86,7 +88,8 @@ export async function startPlaygroundWeb({
onClientConnected = () => {},
sapiName,
onBeforeBlueprint,
siteSlug,
mounts,
shouldInstallWordPress,
}: StartPlaygroundOptions): Promise<PlaygroundClient> {
assertValidRemote(remoteUrl);
allowStorageAccessByUserActivation(iframe);
Expand All @@ -95,28 +98,46 @@ export async function startPlaygroundWeb({
progressbar: !disableProgressBar,
});
progressTracker.setCaption('Preparing WordPress');

// Set a default blueprint if none is provided.
if (!blueprint) {
blueprint = {
phpExtensionBundles: ['kitchen-sink'],
};
}

const compiled = compileBlueprint(blueprint, {
progress: progressTracker.stage(0.5),
onStepCompleted: onBlueprintStepCompleted,
});
const playground = await doStartPlaygroundWeb(
iframe,
setQueryParams(remoteUrl, {
php: compiled.versions.php,
wp: compiled.versions.wp,
['sapi-name']: sapiName,
['php-extension']: compiled.phpExtensions,
['networking']: compiled.features.networking ? 'yes' : 'no',
'site-slug': siteSlug,
}),
progressTracker
);

await new Promise((resolve) => {
iframe.src = remoteUrl;
iframe.addEventListener('load', resolve, false);
});

// Connect the Comlink API client to the remote worker,
// boot the playground, and run the blueprint steps.
const playground = consumeAPI<PlaygroundClient>(
iframe.contentWindow!,
iframe.ownerDocument!.defaultView!
) as PlaygroundClient;
await playground.isConnected();
progressTracker.pipe(playground);
const downloadPHPandWP = progressTracker.stage();
await playground.onDownloadProgress(downloadPHPandWP.loadingListener);
await playground.boot({
mounts,
sapiName,
shouldInstallWordPress,
phpVersion: compiled.versions.php,
wpVersion: compiled.versions.wp,
phpExtensions: compiled.phpExtensions,
withNetworking: compiled.features.networking,
});
await playground.isReady();
downloadPHPandWP.finish();

collectPhpLogs(logger, playground);
onClientConnected(playground);

Expand Down Expand Up @@ -151,39 +172,6 @@ function allowStorageAccessByUserActivation(iframe: HTMLIFrameElement) {
}
}

/**
* Internal function to connect an iframe to the playground remote.
*
* @param iframe
* @param remoteUrl
* @param progressTracker
* @returns
*/
async function doStartPlaygroundWeb(
iframe: HTMLIFrameElement,
remoteUrl: string,
progressTracker: ProgressTracker
) {
await new Promise((resolve) => {
iframe.src = remoteUrl;
iframe.addEventListener('load', resolve, false);
});

// Connect the Comlink client and wait until the
// playground is ready.
const playground = consumeAPI<PlaygroundClient>(
iframe.contentWindow!,
iframe.ownerDocument!.defaultView!
) as PlaygroundClient;
await playground.isConnected();
progressTracker.pipe(playground);
const downloadPHPandWP = progressTracker.stage();
await playground.onDownloadProgress(downloadPHPandWP.loadingListener);
await playground.isReady();
downloadPHPandWP.finish();
return playground;
}

const officialRemoteOrigin = 'https://playground.wordpress.net';
function assertValidRemote(remoteHtmlUrl: string) {
const url = new URL(remoteHtmlUrl, officialRemoteOrigin);
Expand Down Expand Up @@ -215,33 +203,3 @@ function setQueryParams(url: string, params: Record<string, unknown>) {
urlObject.search = qs.toString();
return urlObject.toString();
}

/**
* @deprecated Use `startPlayground` instead.
*
* @param iframe Any iframe with Playground's remote.html loaded.
* @param options Optional. If `loadRemote` is set, the iframe's `src` will be set to that URL.
* In other words, use this option if your iframe doesn't have
* remote.html already
* loaded.
*/
export async function connectPlayground(
iframe: HTMLIFrameElement,
options?: { loadRemote?: string }
): Promise<PlaygroundClient> {
logger.warn(
'`connectPlayground` is deprecated and will be removed. Use `startPlayground` instead.'
);
if (options?.loadRemote) {
return startPlaygroundWeb({
iframe,
remoteUrl: options.loadRemote,
});
}
const client = consumeAPI<PlaygroundClient>(
iframe.contentWindow!,
iframe.ownerDocument!.defaultView!
) as PlaygroundClient;
await client.isConnected();
return client;
}
Loading

0 comments on commit ccadc7d

Please sign in to comment.