~vpzom/bracketmonster

e6a733db0c15d2a77351bd201a2cf47da77dfb5b — Colin Reeder 3 months ago d224551 code-splitting
Preload route scripts
3 files changed, 73 insertions(+), 3 deletions(-)

A rosebush/chunk-manifest-plugin.js
M rosebush/src/server/index.tsx
M rosebush/webpack.config.js
A rosebush/chunk-manifest-plugin.js => rosebush/chunk-manifest-plugin.js +25 -0
@@ 0,0 1,25 @@
const NAME = "ChunkManifestPlugin";

module.exports = class ChunkManifestPlugin {
	apply(compiler) {
		compiler.hooks.emit.tap(NAME, compilation => {
			const chunks = compilation.chunks.map(info => {
				return {
					files: info.files,
					modules: Array.from(info.modulesIterable, module => module.request),
				};
			});

			const manifest = JSON.stringify(chunks);

			compilation.assets["chunk-manifest.json"] = {
				source() {
					return manifest;
				},
				size() {
					return manifest.length;
				}
			};
		});
	}
};

M rosebush/src/server/index.tsx => rosebush/src/server/index.tsx +46 -2
@@ 5,7 5,7 @@ import isomorphicCookie from "isomorphic-cookie";
import fetch from "node-fetch";
import { Fragment, h } from "preact";
import { render } from "preact-render-to-string";
import { Route } from "preact-router";
import { Route, RouteProps } from "preact-router";
import * as serveStatic from "serve-static";

import App from "../client/App";


@@ 16,10 16,46 @@ const API_HOST = process.env.API_HOST || "http://localhost:5000";
const SOCKET_HOST = process.env.SOCKET_HOST || "ws://localhost:5555";

const assets = JSON.parse(fs.readFileSync("dist/client/webpack-assets.json", "utf-8"));
const chunks: Array<{files: string[]; modules: string[]}> = JSON.parse(fs.readFileSync("dist/client/chunk-manifest.json", "utf-8"));
const staticServer = serveStatic("dist/client", {index: false, immutable: true});

interface RouteInfo {
	path: string;
	component: string;
}

let lastRoute: null | RouteInfo = null;

function rewindRouteClear() {
	const result = lastRoute;
	lastRoute = null;

	return result;
}

function TracingRoute(props: RouteProps<any> & {routeInfo: RouteInfo}) {
	lastRoute = props.routeInfo;
	return <Route {...props} />;
}

const routesChildren = routes.map(info => {
	return <Route path={info.path} component={require("../client/pages/" + info.component).default} />;
	return <TracingRoute path={info.path} component={require("../client/pages/" + info.component).default} routeInfo={info} />;
});

const routePreloads: {[key: string]: string[]} = {};

routes.forEach(info => {
	routePreloads[info.component] = [];
});

chunks.forEach(chunk => {
	chunk.modules.forEach(moduleReq => {
		if(!moduleReq || moduleReq.indexOf("/pages/") < 0) return;
		for(const page in routePreloads) {
			if(moduleReq.indexOf("/pages/" + page) < 0) continue;
			routePreloads[page].push(...chunk.files);
		}
	});
});

console.log("hi");


@@ 91,6 127,11 @@ http.createServer(async (req_, res) => {
			const content = render(app, {}, {pretty: true});
			const helmetInfo = rewindClear();

			const routeInfo: RouteInfo | null = rewindRouteClear();
			const preloads = routeInfo === null ?
				[] :
				routePreloads[routeInfo.component];

			const headContent = render(<Fragment>{Helmet.renderContent(helmetInfo)}</Fragment>, {}, {pretty: true});

			res.writeHead(200, {"Content-type": "text/html"});


@@ 103,6 144,9 @@ http.createServer(async (req_, res) => {
			var SOCKET_HOST = ${JSON.stringify(SOCKET_HOST)};
			var INIT_USER_INFO = ${JSON.stringify(userInfo)};
		</script>
		${preloads.map(file => {
			return `<link rel="preload" href="/static/${file}" as="script">`;
		}).join("\n")}
		<link rel="stylesheet" href="${assets.main.css}">
	</head>
	<body>

M rosebush/webpack.config.js => rosebush/webpack.config.js +2 -1
@@ 1,4 1,5 @@
const AssetsPlugin = require("assets-webpack-plugin");
const ChunkManifestPlugin = require("./chunk-manifest-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const NodeExternalsPlugin = require("@nerd-coder/webpack-node-externals");
const path = require("path");


@@ 50,7 51,7 @@ module.exports = [
				"react$": path.resolve(__dirname, "partial-compat.js"),
			},
		},
		plugins: [...plugins, new AssetsPlugin({useCompilerPath: true})],
		plugins: [...plugins, new AssetsPlugin({useCompilerPath: true}), new ChunkManifestPlugin()],
		output: {
			path: path.resolve(__dirname, "dist/client"),
			filename: "[name]-[hash].js",