Skip to content

Allow using self-hosted remote with published Playground client#2750

Closed
brandonpayton wants to merge 4 commits intotrunkfrom
allow-self-hosted-remote-with-published-client-package
Closed

Allow using self-hosted remote with published Playground client#2750
brandonpayton wants to merge 4 commits intotrunkfrom
allow-self-hosted-remote-with-published-client-package

Conversation

@brandonpayton
Copy link
Member

@brandonpayton brandonpayton commented Oct 8, 2025

Motivation for the change, related issues

Projects like Telex are interested in hosting their own Playground remote.html so they can control when they upgrade to new Playground versions. Unfortunately, the published @wp-playground/client builds only allows loading /remote.html from known origins like https://playground.wordpress.net.

It is possible to build custom versions that allow additional remote origins, but this is an additional burden.

This PR adjusts Playground client to work with any /remote.html if its embedded build version matches the Playground client version.

Implementation details

The PR:

  • Adds a build-time transform to insert a build version into remote.html, so the Playground client can fetch the remote.html and confirm the client and remote versions match.
  • Updates @wp-playground/client's startPlaygroundWeb() function to check remote compatibility by fetching remote.html and confirming that it contains the same build version as @wp-playground/client.
    • Exception: Remote URLs based on playground.wordpress.net and wasm.wordpress.net are assumed to be compatible so folks with out-of-date Playground packages are not proactively broken by this compatibility check.
  • Removes support for ADDITIONAL_REMOTE_ORIGINS at build time because it is no longer needed.

Testing Instructions (or ideally a Blueprint)

  • CI
  • Run npm run dev to confirm Playground loads properly in dev mode.

@brandonpayton
Copy link
Member Author

There is a big thing that needs fixed before this PR is ready for review.

Initially, I started by updating use of virtual Vite modules to reflect a common build version, but using those is awkward because we end up having to declare those virtual modules in packages that are merely dependent upon another package that uses that virtual module.

So I switched to using a real module that has a build-time transform to insert the production build version. Unfortunately, this overlooked the fact that dependent package builds still consume the original package source, not the built packages. So many built packages in this PR are still just using the dev-mode build version.

In the morning, I will see if we can just solve this issue by marking @wp-playground/common as an external package in its dependents' build configs. If there is a reason that is no good, I plan to switch back to virtual modules.

"$schema": "../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "packages/playground/remote/src",
"projectType": "library",
"implicitDependencies": ["playground-wordpress-builds"],
Copy link
Member Author

@brandonpayton brandonpayton Oct 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unrelated to this PR, but I added this after experiencing what appeared to be a timing issue with the Playground remote build (which includes files from playground-wordpress-builds).

It probably does not matter because the build issue was due to something else, but I don't think it hurts to acknowledge the dependency here.

@brandonpayton
Copy link
Member Author

I switched this PR back to using a virtual module for build-version. This is now ready for review.

@brandonpayton brandonpayton marked this pull request as ready for review October 8, 2025 20:33
@adamziel
Copy link
Collaborator

adamziel commented Oct 8, 2025

Unfortunately, the published @wp-playground/client builds only allows loading /remote.html from known origins like https://playground.wordpress.net.

Every time remote.html is deployed, a new public client.js guaranteed to be compatible with it is also deployed at https://playground.wordpress.net/client/index.js. Importing that client directly in the frontend app is the way to go. Bundling the npm client package is a bad idea. A new remote.html deployment may just make the bundled client obsolete. I sometimes wish we’ve never published the client package. It typically works, which creates a false sense of security. Maybe we should release a new major version that throws an exception with migration instructions when you import it, just to prevent apps from bundling it?

A custom telex deployment will also include an up-to-date client library that should have telex domain baked in — importing that client is the way to go. It should just work right now without any changes to the codebase.

@adamziel
Copy link
Collaborator

adamziel commented Oct 8, 2025

To be clear, I mean a direct cross-origin import from the Playground domain.

<!DOCTYPE html>
<iframe id="wp-playground" style="width: 1200px; height: 800px"></iframe>
<script type="module">
	import { startPlaygroundWeb } from 'https://playground.wordpress.net/client/index.js';

	const client = await startPlaygroundWeb({
		iframe: document.getElementById('wp-playground'),
		remoteUrl: `https://playground.wordpress.net/remote.html`,
		blueprint: {
			landingPage: '/wp-admin/',
			preferredVersions: {
				php: '8.0',
				wp: 'latest',
			},
			steps: [
				{
					step: 'login',
					username: 'admin',
					password: 'password',
				},
				{
					step: 'installPlugin',
					pluginData: {
						resource: 'wordpress.org/plugins',
						slug: 'friends',
					},
				},
			],
		},
	});

	const response = await client.run({
		// wp-load.php is only required if you want to interact with WordPress.
		code: '<?php require_once "/wordpress/wp-load.php"; $posts = get_posts(); echo "Post Title: " . $posts[0]->post_title;',
	});
	console.log(response.text);
</script>

@brandonpayton
Copy link
Member Author

brandonpayton commented Oct 8, 2025

@adamziel, thanks for the clarification here. That makes sense and helps my understanding.

The types are even published to https://playground.wordpress.net/client/index.d.ts, so maybe there is a way folks referencing the remote client/index.js can use those as well.

I'll close this PR.

@brandonpayton brandonpayton deleted the allow-self-hosted-remote-with-published-client-package branch October 8, 2025 23:31
@brandonpayton
Copy link
Member Author

@adamziel I forgot to respond to your comments.

Maybe we should release a new major version that throws an exception with migration instructions when you import it, just to prevent apps from bundling it?

I'd need to consider the ramifications a bit more, but that sounds reasonable at the moment. Should we even go further and have new versions throw an error with migration instructions during package install?

A custom telex deployment will also include an up-to-date client library that should have telex domain baked in — importing that client is the way to go. It should just work right now without any changes to the codebase.

This is a good point. Telex currently uses the @wp-playground/client NPM package AFAICT, but I'll make a PR for them to switch to the remote module. That way, I can learn how to consume the remote types, and they can make the switch easily.

@adamziel
Copy link
Collaborator

adamziel commented Oct 8, 2025

Should we even go further and have new versions throw an error with migration instructions during package install?

This sounds even better!

I also thought about publishing a passthrough package that just imports the remote client, but I don't think it's possible without breaking bc. We can likely "import" it in script modules, but I don't see any way of doing it in non-module scripts. We'd need to fetch() and eval() or append a script tag to body and wait until it's loaded, but no one expects that require() call to return a Promise.

Also, bundlers gonna bundle. Even if we got this to work, I'm sure we'd quickly learn a 1000 clever techniques webpack et al use to still inline the remote script. Or a 1000 cryptic error nessages when they fail.

@brandonpayton
Copy link
Member Author

Should we even go further and have new versions throw an error with migration instructions during package install?

This sounds even better!

@adamziel one thing we need is a good way for folks to consume @wp-playground/client types. Would it be worth keeping the same package to provide the types, or should we do something like the following?

  • Make @wp-playground/client fail to install. Present a helpful migration message.
  • Publish a @wp-playground/client-types package or something similar to be clear it is just the types for a specific version.

What do you think?

In playing with a local copy of Telex in dev mode, what I've been able to get working is via a dynamic import like:

const { startPlaygroundWeb } = await import("https://playground.wordpress.net/client/index.js");
client = await startPlaygroundWeb({
    iframe: iframeRef.current,
    remoteUrl: "https://playground.wordpress.net/remote.html",
});

Vite doesn't seem to like the static imports of remote modules. Based on web searches, it sounds like there are ways to configure Vite to work with such remote imports, but it looked like it required an annoying amount of precise config.

The dynamic import worked, and the types were able to be resolved by adding a path mapping to the types in the locally-installed @wp-playground/client package like:

"paths": {
  "https://playground.wordpress.net/client/index.js": [
    "./node_modules/@wp-playground/client/index.d.ts"
  ],
}

This was pretty straightforward really. But folks need a way to access the types locally. They could add automation to download the remote typedefs (e.g., from https://playground.wordpress.net/client/index.d.ts), but being able to install the types for a specific version from NPM would be easier and much less error-prone.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants