diff --git a/packages/playground/website/vite.config.ts b/packages/playground/website/vite.config.ts index 3be04cafef..8d67916f18 100644 --- a/packages/playground/website/vite.config.ts +++ b/packages/playground/website/vite.config.ts @@ -19,6 +19,7 @@ import { fileURLToPath } from 'node:url'; import { copyFileSync, existsSync } from 'node:fs'; import { join } from 'node:path'; import { buildVersionPlugin } from '../../vite-extensions/vite-build-version'; +import { listAssetsRequiredForOfflineMode } from '../../vite-extensions/vite-list-assets-required-for-offline-mode'; const proxy = { '^/plugin-proxy': { @@ -134,6 +135,13 @@ export default defineConfig(({ command, mode }) => { } }, } as Plugin, + /** + * Generate a list of files needed for the website to function offline. + */ + listAssetsRequiredForOfflineMode({ + outputFile: 'assets-required-for-offline-mode.json', + distDirectoriesToList: ['./', '../remote', '../client'], + }) as Plugin, ], // Configuration for building your library. diff --git a/packages/vite-extensions/vite-list-assets-required-for-offline-mode.ts b/packages/vite-extensions/vite-list-assets-required-for-offline-mode.ts new file mode 100644 index 0000000000..cceff00120 --- /dev/null +++ b/packages/vite-extensions/vite-list-assets-required-for-offline-mode.ts @@ -0,0 +1,121 @@ +import { readdirSync, statSync, writeFileSync } from 'node:fs'; +import { join } from 'node:path'; + +/** + * We want to cache all files from the website root directory, except for the files listed below. + */ +const patternsToNotCache = [ + /** + * Static files that are not needed for the website to function offline. + */ + '/package.json', + '/README.md', + '/.DS_Store', + '/index.cjs', + '/index.d.ts', + /\/lib\/.*/, // Remote lib files + /\/test-fixtures\/.*/, // Test fixtures + /** + * WordPress assets removed from the minified builds, for example: + * + * /wp-6.2/wp-content/themes/twentytwentyone/style.css + * + * We don't need to cache them, because they're only used in a short time + * window after the Playground is initially loaded and before they're all backfilled + * from a dedicated ZIP file shipping all the static assets for a given minified + * build. + * + * See + */ + /^\/wp-[\w+((\.\d+)?)]+\/.*/, + /** + * Demos are not needed for the website to function offline. + */ + /^\/demos\/.*/, + /** + * Files needed only by the playground.wordpress.net server. + */ + '/.htaccess', + /** + * We can't download the PHP scripts – only get their execution result. This is fine, + * we don't need them for the offline mode anyway. This includes things like plugins-proxy.php. + */ + /\/.*\.php$/, + /** + * WordPress, PHP, and SQLite files that are loaded during boot. + * Eagerly loading all the PHP and WordPress releases offered by Playground would use an + * extra ~200MB of bandwidth every time you load Playground on a new device. + * + * However, most of the time time you only want to load a specific Playground configuration. + * + * Therefore, in here we're excluding the PHP and WP releases from being loaded + * eagerly and instead we're defaulting to caching the specific release that's + * loaded anyway when booting Playgroung. + */ + /^\/assets\/php_.*\.wasm$/, // PHP WASM files + /^\/assets\/php_.*\.js$/, // PHP JS files + /^\/assets\/wp-.*\.zip$/, // Minified WordPress builds and static assets bundles + /^\/assets\/sqlite-database-integration-[\w]+\.zip/, // SQLite plugin +]; + +function listFiles(dirPath: string, fileList: string[] = []) { + const files = readdirSync(dirPath); + + files.forEach((file) => { + const filePath = join(dirPath, file); + const fileStat = statSync(filePath); + + if (fileStat.isDirectory()) { + listFiles(filePath, fileList); + } else { + fileList.push(filePath); + } + }); + + return fileList; +} + +/** + * This Vite plugin saves a list of those files as a JSON file served on + * `playground.wordpress.net`. Playground then consults the list to + * download and cache all those files at the end of the boot process. + * . + */ +export const listAssetsRequiredForOfflineMode = ({ + outputFile, + distDirectoriesToList, +}: { + outputFile: string; + distDirectoriesToList: string[]; +}) => { + return { + name: 'list-assets-required-for-offline-mode', + apply: 'build', + writeBundle({ dir: outputDir }: { dir: string }) { + const files = distDirectoriesToList.flatMap((dir) => { + const absoluteDirPath = join(outputDir, dir); + console.log(`Listing files in ${absoluteDirPath}`); + return listFiles(absoluteDirPath) + .map((file) => { + file = file.replace(absoluteDirPath, ''); + if (file.startsWith('/')) { + return file; + } + return `/${file}`; + }) + .filter((item) => { + return !patternsToNotCache.some((pattern) => { + if (pattern instanceof RegExp) { + return pattern.test(item); + } + return pattern === item; + }); + }); + }); + writeFileSync( + join(outputDir, outputFile), + JSON.stringify(files, null, 2) + ); + }, + }; +};