This is the meeting we had, with a summary of how the dev and build scripts generate the client and server bundles, and with an special emphasis on the Webpack configuration and how we thought packages should be able to extend that configuration.
It also has an explanation of a changes we plan to do: Instead of generating the all the client bundles for all the sites in a single Webpack run, use a single Webpack run for each site.
By the way, we also had a FD to customize babel configuration (Customize Babel configuration). But maybe we can close that for now, because configuring Babel will be possible using this:
Indeed, that’s doable. Wondering how often would someone need to only adjust the babel configuration and not the whole config? On the flip side, having to walk the webpack config only to get to the babel loader it’s painful. Gonna test it out.
One more thing I was meaning to ask is that currently how I thought about it, is that only the configuration files from within packages are read and apply. Should we allow a site specific configuration as well? Meaning:
/packages
/packages/theme/[...]
/packages/theme/frontity.config.ts # <--This is allowed and works
[...]
/frontity.settings.ts
/frontity.config.ts # <-- Should we allow this?
When I first thought about the frontity.config.js file, I thought that a site/root one would be cool, but right now I can’t think of any configuration that it is not better done at a package-level.
So to avoid confusion and anti-patterns, I would not include it at first. If we discover a real need for it later, we can always add it
If we include the frontity configuration as well, as you showed in the video, we can create a package @frontity/vercel that uses the new .vercel_build_output folder for the build if process.env.VERCEL exists.
Another thing we want to add later to the frontity.config.js file is the ability to modify the site settings and add a package priority.
For example, the @frontity/amp package could set mode: "amp" automatically using this:
// High priority, run before any other package.
export const priority = 1
// Set the mode to "amp" so the user doesn't have to.
export const settings = ({ settings }) => {
settings.mode = "amp";
}
So definitely it’s a good idea to use named exports
Frontity uses webpack as the build tool, to bundle and generate the needed JavaScript to run on the server but also on the client. We use a specially crafted set of configurations to generate them, which takes into account the target for the bundles – which can be server, module and es5, and because of that extending the configuration or customising it was not an easy task. One would have to patch the frontity package or give up on their customisations. That is not ideal, as you can imagine, since customising the bundling configuration is usually something that site frameworks are allowing and encourages the community to take it further.
Webpack is a powerful and established tool. It allows one to use build time configurations to effectively enhance the generated output, regardless of the target.
Context
The inner architecture of frontity is composed of three main configuration, which in the end are funnelled into webpack’s configuration.
Frontity configuration
Babel configuration
Webpack configuration
Goals
The main goal is to allow one to define a frontity.config.(j|t)s file that can modify the configuration by mutating the passed config. A generic example is this:
// `example` here is a placeholder for frontity, babel or webpack
export const example = ({
// config, is the default configuration
config,
// target, refers to the three possible targets: server, module and es5
target,
// mode, is either development or production
mode
}) => {
// inside here you can modify them as you wish
};
Out of scope
The config file is designed to be defined by package and used by a site. It can not be defined at the site level. This is intended and it is like this to keep each custom configuration in it’s own context and scope.
Technical explanation
The proposed solution is the one that reads the packages configuration files, and creates a dictionary
Read the config files
First we need to read up the configuration files from all the packages, before running webpack. In order to do that, we have to construct a sort of dictionary where we can collect each defined configuration function.
const dictionary = {
frontity: [
// list of config functions for frontity context
],
babel: [ /* ... */],
webpack: [ /* ... */]
};
Why the need of a dictionary?
Multiple packages can export and define customisations for each of the three contexts. That means that we need to be able to run all of the packages customisations.
After we have the above dictionary computed, we can pass it along to the getConfig function in the scripts file.
Run each configuration context
All the magic happens in the getConfig function. This function runs the frontity, babel and webpack functions to generate the configurations to be used as configurations for the compiler. So, that means at this point we need to run the customisation functions. This results in something like:
const config = ({
mode,
extraConfigurations,
...rest // other configurations
}) => {
// Default config for frontity
const frontity = getFrontity();
// Run the entries for frontity.
extraConfigurations.frontity.forEach(fn => {
fn({ config: frontity, mode });
});
// Default config for babel. Keep in mind this configuration is split by target.
const babel = getBabel();
// For each target run the extra configuration for babel
for (const target in babel) {
extraConfigurations.babel.forEach(fn => {
fn({ config: babel, mode, target });
});
}
// This returns the default webpack configuration split for each target as well.
const webpack = getWebpack({
mode,
babel,
frontity,
...rest // rest of the passed configs
});
// For each target run the extra configuration for webpack
for (const target in babel) {
extraConfigurations.webpack.forEach(fn => {
fn({ config: webpack, mode, target });
});
}
return {
babel,
webpack,
frontity,
};
};
That’s it!
At this point, each package extra configuration ran, and the returned configurations for frontity, babel and webpack are customised.
The final implementation is so simple that I think a video demo isn’t needed . I’ve created this simple CodeSandbox to show an example of how to extend both WebPack and Babel. Some comments about it:
We could have added the frontity.config.js file to the mars-theme , but I wanted to show how easy it is to create a new package for this.
If you want to check the different behaviour in the own CodeSandbox, you can change the package.json dev script (this is what CodeSandbox uses), add the flag --production , and restart the server. Without the flag you can see “Development Mode” in the console, and if you add the flag you shouldn’t see it after restarting the server.
I think the explanation is great, thanks Cristian Just a minor comment. I think you used the Implementation Proposal template instead of the Final Implementation template. It’s true that in this Feature Discussion we didn’t have an initial implementation proposal, but as it is already released and done, I think we can write the Final Implementation directly. Anyway, I think the explanation you provided should be enough. Could you please slightly update the titles to reflect it is the Final Implementation please?