Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Static assets of bundled plugins not recognized by service worker to rewrite request #109

Closed
eliot-akira opened this issue Jan 11, 2023 · 5 comments · Fixed by #702
Closed
Labels
[Aspect] Browser [Type] Bug An existing feature does not function as intended

Comments

@eliot-akira
Copy link
Collaborator

eliot-akira commented Jan 11, 2023

This took me a while to figure out, so I thought I'd write some notes in case any of it may be useful.

I wanted to bundle some plugins/themes in wp.data, for several reasons: to cache the whole bundle, without having to make requests to wp.org on every page load; and to run plugins that are not published to the directory, for example pre-release versions or commercial products. It's also for a curated demo experience, so users won't be able to install arbitrary plugins, only the pre-bundled ones.

In src/wordpress-playground/wordpress/Dockerfile, I added a step to bundle the plugins.

Before the line with comment "Separate WordPress static files":

RUN rm -rf wordpress/wp-content/plugins/* && \
    mkdir -p wordpress/wp-content/mu-plugins && \
    cd wordpress/wp-content/mu-plugins && \
    # Install plugins
    for plugin_name in example-1 example-2; do \
      plugin_file=$plugin_name.latest-stable.zip && \
      wget https://downloads.wordpress.org/plugin/$plugin_file && \
      unzip $plugin_file && \
      rm $plugin_file && \
      # Create entry file in mu-plugins root
      echo "<?php require_once __DIR__.'/$plugin_name/$plugin_name.php';" > $plugin_name.php; \
    done

So that part works, and the plugins are activated on site load.


The issue was on the frontend, where their static assets were not being found. I tracked it down to the service worker, where it calls isUploadedFilePath to check if a requested URL path should be rewritten to the static assets directory. It was expecting all plugin assets to be in the virtual file system in the browser, not as actual static files on the server.

In my case, the bundled plugins are in the mu-plugins directory, and users are expected to use only the bundled theme - so I was able to solve the issue by commenting out some lines in that function.

export const isUploadedFilePath = (path) => {
	return (
		path.startsWith('/wp-content/uploads/') ||
		path.startsWith('/wp-content/plugins/')
		// || path.startsWith('/wp-content/mu-plugins/')
		// || path.startsWith('/wp-content/themes/')
	);
};

I'm not sure if there's a better solution, if the service worker can recognize the difference between a "bundled" plugin/theme (with static files on the server) and those that are "pre-installed" on site load (with static files in the browser's virtual file system).

@adamziel
Copy link
Collaborator

Thank you for writing it up @eliot-akira ! It would make a great documentation page.

if the service worker can recognize the difference between a "bundled" plugin/theme (with static files on the server) and those that are "pre-installed" on site load (with static files in the browser's virtual file system).

Maybe the service worker could ask the worker thread about each requested URL and forward the request to the server if nothing is found in the virtual file system. I need to think a bit more about this.

By the way, is your deployment public somewhere? I'm super curious about what you're building!

@eliot-akira
Copy link
Collaborator Author

eliot-akira commented Jan 12, 2023

Here is a test site where I started on an interactive plugin demo. Not much to see yet though, I'm still learning how to customize and wield the power of the playground.

(Notably, the site URL is without wordpress.html thanks to NGINX index directive. I remember I had an issue with such URLs being intercepted by the service worker - but I think the "scoped URL" logic solves this. So probably the same can be achieved with Apache, to serve that HTML file on site root URL.)


Many ideas are coming to mind as I play around, like: recording (or pre-written) "session" or "scenario" of user actions, as JSON files that can be shared and replayed. Populating demo content on site load with faker.js. Installing pre-release plugins from a Git repo (I guess just zip files from sources other than wp.org). Adapting the Dockerfile(s) to generate new bundles and deploy test site from a CI/build pipeline..

Here's a "dual-pane" experiment I tried, with two iframes side-by-side to see frontend and admin screen at the same time. That one is a bit fragile, after some navigation it runs into an empty page with scoped URL not found. I imagine it probably will take some effort to support multiple iframes on a single page (all connected to the same WordPress instance). Another dual-pane idea is a kind of "WordPress REPL", with a code editor or terminal screen on one side, and the site preview that reloads on every command.

Anyway, I'm enjoying thinking about the potential of such portable, scriptable, and ephemeral WordPress instances. Seeing the import/export prototype (the entire snapshot in ~10Mb zip), I believe the Playground can become a practical basis for instant WordPress dev environments, in the same way as Replit, CodeSandbox, GitHub CodeSpaces. I imagine it will lower the barrier to entry for learning WordPress site development, and may even become useful as a prototyping tool that deploys to a real/live site.


I'm also interested in running WordPress on WASM from Node.js. Might not be efficient or performant, but it could be useful to spin up a self-contained interactive site for testing/scripting, for example as an alternative to using Playwright.

Recently I've been learning how to use wp-env, as a convenient way to spin up a local dev environment using Docker. It's interesting to see some common threads with WP Playground, how a whole full-stack "world" is created and destroyed. It seems like WordPress is becoming a more "commodified resource", in a good sense of the term, so that it takes only a single command (or browser visit to URL) to instantly create a new WordPress site complete with database and server.

One thing I'm learning from the experience, is that it would be nice to have a quicker and easier way to import and export content, code, and full site configurations - so they can be shared among temporary sites (in browser or Docker container) and more permanent sites (real Apache/NGINX servers). And maybe something like a Dockerfile but for WordPress setups, or like an extended .wp-env.json for WP Playground.

(By the way, a possibly relevant article published this week: SQLite Wasm in the browser backed by the Origin Private File System. Apparently this File System is a web standard coming to all browsers eventually.)

@adamziel
Copy link
Collaborator

Here is a test site where I started on an interactive plugin demo. Not much to see yet though, I'm still learning how to customize and wield the power of the playground.

Oh this is awesome! It's, in all likelihood, the first Playground deployment other than wasm.wordpress.net.

(Notably, the site URL is without wordpress.html thanks to NGINX index directive. I remember I had an issue with such URLs being intercepted by the service worker - but I think the "scoped URL" logic solves this. So probably the same can be achieved with Apache, to serve that HTML file on site root URL.)

That's a good point! I think scopes solve that indeed as the browser would never try to reach the site on /. Interesting!

Many ideas are coming to mind as I play around, like: recording (or pre-written) "session" or "scenario" of user actions, as JSON files that can be shared and replayed. Populating demo content on site load with faker.js. Installing pre-release plugins from a Git repo (I guess just zip files from sources other than wp.org). Adapting the Dockerfile(s) to generate new bundles and deploy test site from a CI/build pipeline.

I love it! The recordings would be extremely useful for bug reports. Also, having a playground for an arbitrary commit would be pretty amazing. Building WordPress, theme, or a plugin entirely in the browser wouldn't be trivial or fast, but if the build step can be handled on Github then Playground could easily install the resulting zip file.

Here's a "dual-pane" experiment I tried, with two iframes side-by-side to see frontend and admin screen at the same time. That one is a bit fragile, after some navigation it runs into an empty page with scoped URL not found. I imagine it probably will take some effort to support multiple iframes on a single page (all connected to the same WordPress instance).

Oh the dual-pane idea is amazing. I wasn't able to reproduce the URL not found issue – do you have any notes on that? I can't think of any technical limitations that would prevent running multiple iframes so perhaps there's a simple fix.

Another dual-pane idea is a kind of "WordPress REPL", with a code editor or terminal screen on one side, and the site preview that reloads on every command.

Yes! This may get explored at the CloudFest Hackaton in March, see #104. I would love to turn Playground into a full WordPress development environment with no setup required. Today I managed to get PHPUnit to work, see #111

I'm also interested in running WordPress on WASM from Node.js. Might not be efficient or performant, but it could be useful to spin up a self-contained interactive site for testing/scripting, for example as an alternative to using Playwright.

This could very well work – I found the Node.js version to be surprisingly fast.

It seems like WordPress is becoming a more "commodified resource", in a good sense of the term, so that it takes only a single command (or browser visit to URL) to instantly create a new WordPress site complete with database and server.

Docker introduced support for WASM apps recently https://www.docker.com/blog/build-share-run-webassembly-apps-docker/. A Docker-compatible build could open doors to integrations with many terminal apps. At that point everything could be configurable using some sort of declarative config as you mentioned.

(By the way, a possibly relevant article published this week: SQLite Wasm in the browser backed by the Origin Private File System. Apparently this File System is a web standard coming to all browsers eventually.)

Nice! Thank you for sharing!

@eliot-akira
Copy link
Collaborator Author

eliot-akira commented Jan 13, 2023

Ooh, I hadn't seen the article about WASM apps on Docker. It's so interesting to learn what they're working on. TensorFlow on WasmEdge, WASI-NN (Neural Network proposal for WASI). Such advanced hackery I respect, wizards at work. I'm guessing WASI-NN will somehow commodify machine learning and AI to be easier to run on your own servers and in the browser.

It reminds me of a new project I saw, called WPCodey, a "WordPress code snippets assistant" that generates code based on natural language input. I think it uses OpenAI Codex, which powers GitHub Copilot. Who knows, in the future we could have a WordPress AI assistant trained on domain-specific knowledge, like the Developer's Handbook, the entire codebase of WordPress and all plugins in the directory, with whom we can ask questions and develop websites by talking, code by conversation.


I wasn't able to reproduce the URL not found issue – do you have any notes on that? I can't think of any technical limitations that would prevent running multiple iframes so perhaps there's a simple fix.

Maybe it was only an issue on the local dev server, it looked like some URLs' scope prefix was not being removed and returning 404. I'll dig deeper and write an issue description if I understand what's happening.


turn Playground into a full WordPress development environment with no setup required

It's been fun to see how the project is growing and improving. WordPress IDE in the browser, I know it will get there. It's surprisingly fast to load now, snappy to navigate and operate, like experiencing WordPress as a single-page app.

I think the way it runs "on the edge", in the browser and on the user's machine, has some advantages over traditional websites on remote servers. I'm still trying to understand the implications. I love that it's "local first" software. I'm also wondering what it means to have a WordPress environment as a self-contained package of static files that can be served and cached by a CDN (with some URL routing magic); or run it on "serverless" architecture like Cloudflare workers. Well, the sky's the limit! :)

@adamziel adamziel added [Type] Bug An existing feature does not function as intended [Aspect] Browser and removed Infrastructure labels Jun 1, 2023
@adamziel adamziel mentioned this issue Oct 4, 2023
10 tasks
adamziel added a commit that referenced this issue Oct 16, 2023
Replaces approximate rules such as

```js
if(path.startsWith('/wp-content')) {
    // serve from VFS
} else {
    // serve from remote server
}
```

With actual file existence check.

When a static file is requested from Playground, the service worker
doesn't know whether to serve it from VFS or playground.wordpress.net.
It tries to guess that based on approximate rules such as
`path.startsWith('/wp-content')`. This usually works, but is not
very reliable. For example, when you store an entirely new WordPress instance
in VFS, you would expect all the static assets to be served from VFS,
but that is not what the request handler would do.

This does not play well with previewing WordPress Pull Requests as we're
actually replacing the entire WordPress instance in VFS with an
arbitrary branch.

Closes #109

1. Start Playground `npm run start`
2. Use it for a bit, go to wp-admin, confirm that static assets like CSS
   and JS files are loaded without issues
3. Upload something, confirm that worked too

CC @sejas @danielbachhuber as this is a breaking change in public API and may be
relevant for wp-now. While that sounds scary, everything should "just work" without
major changes required in wp-now, other than perhaps removing usages of
the `isStaticFilePath` option.
adamziel added a commit that referenced this issue Oct 16, 2023
#702)

Replaces approximate rules such as

```js
if(path.startsWith('/wp-content')) {
    // serve from VFS
} else {
    // serve from remote server
}
```

With actual file existence check.

## Context

When a static file is requested from Playground, the service worker
doesn't know whether to serve it from VFS or playground.wordpress.net.
It tries to guess that based on approximate rules such as
`path.startsWith('/wp-content')`. This usually works, but is not very
reliable. For example, when you store an entirely new WordPress instance
in VFS, you would expect all the static assets to be served from VFS,
but that is not what the request handler would do.

This does not play well with previewing WordPress Pull Requests as we're
actually replacing the entire WordPress instance in VFS with an
arbitrary branch.

Related to #700
Closes #109

## Testing Instructions

1. Start Playground `npm run start`
2. Use it for a bit, go to wp-admin, confirm that static assets like CSS
and JS files are loaded without issues
3. Upload something, confirm that worked too

## Breaking change

The `isStaticFilePath` option is removed in this PR..

This is a breaking change in public API and may be relevant for wp-now
CC @sejas @danielbachhuber.

While that sounds scary, everything should "just work" without major
changes required in wp-now, other than removing this usage of
`isStaticFilePath`:


https://github.com/WordPress/playground-tools/blob/d792d7f03323aa0ec34e5885e4aebd5741d96f24/packages/wp-now/src/wp-now.ts#L53-L65
@adamziel
Copy link
Collaborator

@eliot-akira Since #702, the service worker will always try to serve requests using the PHP "server" and only fall back to fetch() if the requested file was static and could not be found in VFS.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Aspect] Browser [Type] Bug An existing feature does not function as intended
Projects
None yet
3 participants