Description
Right now we are running Webpack once for all sites.
Since we added the possibility of adding custom Webpack/Babel configurations, this means that packages that add a configuration for one site, leak that configuration to the rest.
It would be great to isolate those package configurations from each, and to do that we need to run Webpack once for each site.
When I refer to “run Webpack once” it is not exactly once, because each site needs 3 runs:
- The client “module” bundle.
- The client “es5” bundle.
- The server bundle.
So right now we run it 3 times, each one for all sites. We need to run it 3 times per site.
This switch is also beneficial for other use cases:
-
PWA/Offline packages
Right now these are not possible because the Webpack packages that create the service worker include all the assets of the Webpack run. Right now, that means that an offline plugin will download all the assets for all the sites, instead for just a single one. This is also solved with this change. -
Embedded mode assets download
– We need to have a way to easily download all the assets of a single site at once –
– If we end up with a folder/manifest/zip that contains all the assets of a single site and it has a way to reference it, like a hash, we can embed that identifier/hash in the HTML response, and WordPress can download all the assets of that site the first time it receives an HTML file with that hash and then serve those assets from the WordPress file system. –
-
Client-only and Server-only sites
const settings = [ { name: "main-site", bundles: ["client", "server"] // ... }, { name: "amp-site", bundles: ["server"] // ... } ];
To get a better overview Luis and I had a meeting about it and recorded a video. https://www.youtube.com/watch?v=Qy9y6cSEdPE
Functionalities
- Isolate Webpack builds of one site from another to avoid leakage of Webpack/Babel configurations.
Requirements
-
End up with a single
server.js
file. -
Accept the site name parameter when the
dev
CLI command is run. -
Prompt for the site name if it’s not defined when the
dev
CLI command is run.
Future functionalities
- Accept the site name parameter when the
build
CLI command is run.
Dependencies
None.
Possible solution
1. Pass --name
or prompt for the name in multisite projects
- Get rid of
dev
,build
andserve
commands in thefrontity
package because those commands are in the@frontity/core
package. (Optional, just in case there’s nothing in thefrontity
command that requires to be separated from the CLI).
npx frontity dev
- Throw in
dev
script (command) in@frontity/core
if site is not specified and this is a multisite project, including the list of sites. - Capture error in
dev
CLI and prompt for a site.
const err = new Error("siteName missing");
err.siteNames = { ... }
throw err;
- Then capture this error in the CLI and prompt for the name.
npx frontity build
- Never prompts (so it doesn’t throw).
Future functionalities:
- If it has a name as an argument, it only builds that site.
2. Generate individual server entry points
- There needs to be a single server entry point for each site.
- The server entry points need to include only the packages of a single site.
- We can use
site-name/server.ts
.
3. Generating a Webpack/Babel/Frontity config for each site
-
In
build
we can iterate over the sitesawait Promise.all( sites.map(async (site) => { // ... }) );
-
In
dev
we filter for the specific siteconst sites = [await getAllSites()].filter((config) => config.name === site);
-
We must not bundle the external dependencies of the server bundles in this step. They will be bundled in the final single
server.js
file. -
We must not bundle the
frontity.settings
on the server bundles in this step. They will be bundled in the final singleserver.js
file.
4. Generate a top-level Koa server that routes between sites
import { getSettings } from "@frontity/file-settings";
export default (req, res) => {
const app = new Koa();
app.use(async (ctx, next) => {
const servers = await import(`./build/${name}/server.js`);
// Get settings.
const settings = await getSettings({
url: ctx.href,
name: ctx.query.frontity_name
});
// Logic to route between sites.
const server = servers[settings.name];
// Run the server...
});
return app.callback();
};
Webpack "Dynamic expressions in import()
" docs: Module Methods | webpack
5. Bundle server.js
Finally, we generate the final server.js
file, bundling the top-level Koa server with the individual server files, the external dependencies and the frontity settings.
- The entry point of Webpack is the top-level Koa server.
- In
dev
, we will do it as well to make sure that if something doesn’t work in the gateway, the developer knows as soon as possible and is able to easily debug it.
// npx frontity dev
const siteServerConfigWebpack = config.webpack.server;
const serverConfigWebpack; /* gateway server webpack config */
await webpackAsync(clientWebpack);
await webpackAsync(siteServerConfigWebpack);
// Start a custom webpack-dev-server.
const compiler = webpack([clientWebpack, serverConfigWebpack]);
// npx frontity build
const gatewayServerWebpackConfig = {};
// [...]
await Promise.all(
sites.map(async (site) => {
console.log("Building server bundle");
await webpackAsync(site.webpack.server);
})
);
console.log("Building gateway server bundle");
await webpackAsync(gatewayServerWebpackConfig);
console.log();