Skip to content

Commit 83d03a1

Browse files
authored
Download all WordPress assets on boot (#1532)
## Motivation for the change, related issues To achieve [offline support](#1483) we need to download all WordPress files into the Playground filesystem. Today Playground downloads WordPress partially on boot and downloads assets as they are needed using fetch requests. This PR downloads all assets into the Playground filesystem without blocking the boot process. ## Implementation details Zip files are generated using the WordPress build script and are accessible in the `/wp-VERSION/` folder like any other assets. The download is triggered after WordPress is installed. The fetch is async and won't block the boot process. In case some of the assets are needed immediately on boot they will still be downloaded using fetch to ensure they are available to WordPress. Once the assets are downloaded they are stored in the Playground filesystem and will be served from there. ## Testing Instructions (or ideally a Blueprint) - Run `npx nx bundle-wordpress:nightly playground-wordpress-builds` - Confirm that a `packages/playground/wordpress-builds/public/wp-nightly/wordpress-static.zip` file was created - [Load Playground using this blueprint](http://127.0.0.1:5400/website-server/?php=8.0&wp=nightly) - In network tools see that the `wordpress-static.zip` file was downloaded - Open `/wp-admin/` - In network tools confirm that static assets like _thickbox.js_ were loaded from the service worker instead of a fetch
1 parent cffa3e2 commit 83d03a1

File tree

13 files changed

+92
-12
lines changed

13 files changed

+92
-12
lines changed

packages/playground/remote/src/lib/boot-playground-remote.ts

+10
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,13 @@ export async function bootPlaygroundRemote() {
197197
) {
198198
return await workerApi.bindOpfs(options, onProgress);
199199
},
200+
201+
/**
202+
* Download WordPress assets.
203+
*/
204+
async backfillStaticFilesRemovedFromMinifiedBuild() {
205+
await workerApi.backfillStaticFilesRemovedFromMinifiedBuild();
206+
},
200207
};
201208

202209
await workerApi.isConnected();
@@ -232,6 +239,9 @@ export async function bootPlaygroundRemote() {
232239
throw e;
233240
}
234241

242+
wpFrame.addEventListener('load', () => {
243+
webApi.backfillStaticFilesRemovedFromMinifiedBuild();
244+
});
235245
/*
236246
* An assertion to make sure Playground Client is compatible
237247
* with Remote<PlaygroundClient>

packages/playground/remote/src/lib/playground-client.ts

+1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export interface WebClientMixin extends ProgressReceiver {
5252
replayFSJournal: PlaygroundWorkerEndpoint['replayFSJournal'];
5353
addEventListener: PlaygroundWorkerEndpoint['addEventListener'];
5454
removeEventListener: PlaygroundWorkerEndpoint['removeEventListener'];
55+
backfillStaticFilesRemovedFromMinifiedBuild: PlaygroundWorkerEndpoint['backfillStaticFilesRemovedFromMinifiedBuild'];
5556

5657
/** @inheritDoc @php-wasm/universal!UniversalPHP.onMessage */
5758
onMessage: PlaygroundWorkerEndpoint['onMessage'];

packages/playground/remote/src/lib/worker-thread.ts

+63-1
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,14 @@ import transportFetch from './playground-mu-plugin/playground-includes/wp_http_f
3535
import transportDummy from './playground-mu-plugin/playground-includes/wp_http_dummy.php?raw';
3636
/** @ts-ignore */
3737
import playgroundWebMuPlugin from './playground-mu-plugin/0-playground.php?raw';
38-
import { PHPWorker } from '@php-wasm/universal';
38+
import { PHP, PHPWorker } from '@php-wasm/universal';
3939
import {
4040
bootWordPress,
4141
getLoadedWordPressVersion,
4242
} from '@wp-playground/wordpress';
4343
import { wpVersionToStaticAssetsDirectory } from '@wp-playground/wordpress-builds';
4444
import { logger } from '@php-wasm/logger';
45+
import { unzipFile } from '@wp-playground/common';
4546

4647
const scope = Math.random().toFixed(16);
4748

@@ -186,6 +187,67 @@ export class PlaygroundWorkerEndpoint extends PHPWorker {
186187
async replayFSJournal(events: FilesystemOperation[]) {
187188
return replayFSJournal(this.__internal_getPHP()!, events);
188189
}
190+
191+
async backfillStaticFilesRemovedFromMinifiedBuild() {
192+
await backfillStaticFilesRemovedFromMinifiedBuild(
193+
this.__internal_getPHP()!
194+
);
195+
}
196+
}
197+
198+
async function backfillStaticFilesRemovedFromMinifiedBuild(php: PHP) {
199+
if (!php.requestHandler) {
200+
logger.warn('No PHP request handler available');
201+
return;
202+
}
203+
204+
try {
205+
const remoteAssetListPath = joinPaths(
206+
php.requestHandler.documentRoot,
207+
'wordpress-remote-asset-paths'
208+
);
209+
210+
/**
211+
* Don't download static assets if they're already downloaded.
212+
* WordPress may be loaded either from a production release or a minified bundle.
213+
* Minified bundles are shipped without most CSS files, JS files, and other static assets.
214+
* Instead, they contain a list of remote assets in wordpress-remote-asset-paths.
215+
* We use this list to determine if we should fetch them on demand or if they are already downloaded.
216+
* If the list is empty, we assume the assets are already downloaded.
217+
* See https://github.com/WordPress/wordpress-playground/pull/1531 to understand how we use the remote asset list to backfill assets on demand.
218+
*/
219+
if (
220+
!php.fileExists(remoteAssetListPath) ||
221+
(await php.readFileAsText(remoteAssetListPath)) === ''
222+
) {
223+
return;
224+
}
225+
const wpVersion = await getLoadedWordPressVersion(php.requestHandler);
226+
const staticAssetsDirectory =
227+
wpVersionToStaticAssetsDirectory(wpVersion);
228+
if (!staticAssetsDirectory) {
229+
return;
230+
}
231+
const response = await fetch(
232+
joinPaths('/', staticAssetsDirectory, 'wordpress-static.zip')
233+
);
234+
235+
if (!response.ok) {
236+
throw new Error(
237+
`Failed to fetch WordPress static assets: ${response.status} ${response.statusText}`
238+
);
239+
}
240+
241+
await unzipFile(
242+
php,
243+
new File([await response.blob()], 'wordpress-static.zip'),
244+
php.requestHandler.documentRoot
245+
);
246+
// Clear the remote asset list to indicate that the assets are downloaded.
247+
await php.writeFile(remoteAssetListPath, '');
248+
} catch (e) {
249+
logger.warn('Failed to download WordPress assets', e);
250+
}
189251
}
190252

191253
const apiEndpoint = new PlaygroundWorkerEndpoint(

packages/playground/wordpress-builds/build/Dockerfile

+10-2
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,19 @@ RUN rm -rf wordpress-static/wp-content/mu-plugins
4444
# to request a remote asset or delegate the request for a missing file to PHP.
4545
RUN find wordpress-static -type f | sed 's#^wordpress-static/##'> wordpress-remote-asset-paths
4646

47-
# Make the remote asset listing available remotely so it can be downloaded
47+
# Make the remote asset listing available remotely so it can be downloaded
4848
# directly in cases where an older minified WordPress build without this file
4949
# has been saved to browser storage.
5050
RUN cp wordpress-remote-asset-paths wordpress-static/
5151

52+
# ZIP the static files
53+
RUN cd wordpress-static/ && \
54+
zip -r ../wordpress-static.zip . && \
55+
cd ..
56+
57+
# Move ZIP to the public output directory
58+
RUN cp wordpress-static.zip wordpress-static/
59+
5260
# Move the static files to the final output directory
5361
RUN mkdir /root/output/$OUT_FILENAME
5462
RUN mv wordpress-static/* /root/output/$OUT_FILENAME/
@@ -135,6 +143,6 @@ RUN cd wordpress && \
135143
# Build the final wp.zip file
136144
RUN mv wordpress /wordpress && \
137145
cp wordpress-remote-asset-paths /wordpress/ && \
146+
cp wordpress-static.zip /wordpress/ && \
138147
cd /wordpress && \
139148
zip /root/output/$OUT_FILENAME.zip -r .
140-
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

packages/playground/wordpress/src/boot.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ async function isWordPressInstalled(php: PHP) {
219219
return (
220220
(
221221
await php.run({
222-
code: `<?php
222+
code: `<?php
223223
require '${php.documentRoot}/wp-load.php';
224224
echo is_blog_installed() ? '1' : '0';
225225
`,

packages/playground/wordpress/src/index.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { PHP, UniversalPHP } from '@php-wasm/universal';
22
import { joinPaths, phpVar } from '@php-wasm/util';
33
import { unzipFile } from '@wp-playground/common';
44
export { bootWordPress } from './boot';
5-
export { getLoadedWordPressVersion } from './version-detect';
65

6+
export * from './version-detect';
77
export * from './rewrite-rules';
88

99
/**
@@ -18,7 +18,7 @@ export async function setupPlatformLevelMuPlugins(php: UniversalPHP) {
1818
await php.writeFile(
1919
'/internal/shared/preload/env.php',
2020
`<?php
21-
21+
2222
// Allow adding filters/actions prior to loading WordPress.
2323
// $function_to_add MUST be a string.
2424
function playground_add_filter( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) {
@@ -28,7 +28,7 @@ export async function setupPlatformLevelMuPlugins(php: UniversalPHP) {
2828
function playground_add_action( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) {
2929
playground_add_filter( $tag, $function_to_add, $priority, $accepted_args );
3030
}
31-
31+
3232
// Load our mu-plugins after customer mu-plugins
3333
// NOTE: this means our mu-plugins can't use the muplugins_loaded action!
3434
playground_add_action( 'muplugins_loaded', 'playground_load_mu_plugins', 0 );
@@ -57,7 +57,7 @@ export async function setupPlatformLevelMuPlugins(php: UniversalPHP) {
5757
}
5858
return $redirect_url;
5959
} );
60-
60+
6161
// Needed because gethostbyname( 'wordpress.org' ) returns
6262
// a private network IP address for some reason.
6363
add_filter( 'allowed_redirect_hosts', function( $deprecated = '' ) {
@@ -75,7 +75,7 @@ export async function setupPlatformLevelMuPlugins(php: UniversalPHP) {
7575
if(!file_exists(WP_CONTENT_DIR . '/fonts')) {
7676
mkdir(WP_CONTENT_DIR . '/fonts');
7777
}
78-
78+
7979
$log_file = WP_CONTENT_DIR . '/debug.log';
8080
define('ERROR_LOG_FILE', $log_file);
8181
ini_set('error_log', $log_file);
@@ -88,7 +88,7 @@ export async function setupPlatformLevelMuPlugins(php: UniversalPHP) {
8888
await php.writeFile(
8989
'/internal/shared/preload/error-handler.php',
9090
`<?php
91-
(function() {
91+
(function() {
9292
$playground_consts = [];
9393
if(file_exists('/internal/shared/consts.json')) {
9494
$playground_consts = @json_decode(file_get_contents('/internal/shared/consts.json'), true) ?: [];
@@ -204,7 +204,7 @@ export async function preloadSqliteIntegration(
204204
205205
/**
206206
* Loads the SQLite integration plugin before WordPress is loaded
207-
* and without creating a drop-in "db.php" file.
207+
* and without creating a drop-in "db.php" file.
208208
*
209209
* Technically, it creates a global $wpdb object whose only two
210210
* purposes are to:

packages/playground/wordpress/src/version-detect.ts

-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ export async function getLoadedWordPressVersion(
1515
if (!versionString) {
1616
throw new Error('Unable to read loaded WordPress version.');
1717
}
18-
1918
return versionStringToLoadedWordPressVersion(versionString);
2019
}
2120

0 commit comments

Comments
 (0)