Customize Webpack configuration

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?

What are your thoughts?

Recorded another video about it Loom | Free Screen & Video Recording Software

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 :slightly_smiling_face:

1 Like

By the way, for context, @cristianbote has started a PR to add this feature: Custom webpack config by cristianbote Ā· Pull Request #812 Ā· frontity/frontity Ā· GitHub

I mentioned there this:

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 :slightly_smiling_face::clap:

The PR is now merged :tada::tada:

3 Likes

I would like to test it first and decide after that. If itā€™s simple maybe with a CodeSandbox itā€™s enough. I will let you know.

1 Like

I can surely try! :smiley:

1 Like

Final Implementation

Pull Request

Functionalities

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.

1 Like

@luisherranz @SantosGuillamot let me know if the IP needs more deep dive or a better explanation.

The final implementation is so simple that I think a video demo isnā€™t needed :clap:. 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.
  • I changed the Devtool property for WebPack in development mode, but you could change anything in the WebPack configuration.
  • I added the simple Babel plugin transform-remove-console to remove console.logs only in production, but again you can change anything in the Babel configuration.
  • 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.
  • This is the code I added for this example:
export const webpack = ({ config, mode }) => {
  if (mode === "development") {
    config.devtool = "eval-cheap-source-map";
  }
};

export const babel = ({ config, mode }) => {
  if (mode === "production") {
    config.plugins.push([
      "transform-remove-console",
      {
        rules: [
          {
            exclude: ["error", "warning"]
          }
        ]
      }
    ]);
  }
};

3 Likes

I think the explanation is great, thanks Cristian :slightly_smiling_face: 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?

1 Like

Oh no! :see_no_evil: on it. Thank you!

I have created this issue to document this

Feel free to add any information that you consider may be useful for this documentation

@cristianbote realized that configurations from packages that are only added to one site can leak to another site.

So to make this perfect, we should do this in npx frontity build:

  1. Run webpack indiviually per site.

    This actually means running it 3 times per site, for: module, es5 and server.

  2. Do a final step to bundle all the server.js files together.

In npx frontity dev, we should ask the user which site he/she wants to start and only bundle that:

npx frontity dev 

> What site do you want to start?
- main-site
- another-site
- yet-another-site
npx frontity dev --site main-site

We were already planning to do this for pwa/offline support and to make the Embedded mode --public-path configuration unnecessary.

@cristianbote do you want to have a call to open an FD and outline a possible IP together?

@santosguillamot do you think we should address this as part of the Webpack/Babel config feature, or it is fine for now?

1 Like

Sure thing! Would love to.

I think it would be better to open a new Feature Discussion for this. Could you do it after the call you are planning to have with a detailed explanation please? I have a couple of questions but I can share them once the FD is opened.

Yes, that was the idea :slightly_smiling_face:

Awesome! :slightly_smiling_face:

1 Like

Hi,

I am having a webpack window is not defined error with one of the modules Iā€™m using. I believe that by updating the globalObject to ā€œthisā€ it should resolve the issue. Is it possible to update the webpack config within Frontity?

I have created a package, added frontity.config.js to it with the below

export const webpack = ({ config, mode }) => {
  config.output.globalObject = "this";
};

but that didnā€™t seem to work. I have followed the Sandbox example but I canā€™t make it work. Could you please help?

Thanks,
Jeff

hey jeff,

I had a similar issue with window related to Konva. I dont know if this will help you but here is how I solved it in my project:

const webpack_1 = require("webpack");

export const webpack = ({ config, mode }) => {
  if (mode === "development") {
    config.devtool = "eval-cheap-source-map";
  }
  config.plugins.push(new webpack_1.IgnorePlugin(/canvas|jsdom/, /konva/));
};

It basically adds the package to the ignore plugin to prevent the compilation error.

1 Like

Hi guys ! Iā€™m quite new to frontity but I assume the solution will work for my needs as I donā€™t wanā€™t to rewrite all the various calls to my wordpress rest api.
Thanks a lot then.
What Iā€™m doing with it is a React-Three-Fiber (3D) app that needs ā€œglb or gltfā€ files to be loaded, so I wanted to open a new topic but this one seems to go straight to my problem :

  • Modifying my webpack.config.js

First of, Itā€™s quite unclear for me about how to add the frontity.config.js (where ? in the root folder or in my brand new custom theme package ?)
// Sorry about this last point it was quite clear in description that it could be created at both locations

Then Iā€™d like to know if itā€™s possible to provide an example of how to allow new filetypes ?
(thereā€™s the codeSandBox from @SantosGuillamot that seems to be functionnal as it has been released but I already tried it and I donā€™t manage to remove this sticky log that Iā€™ve put in my ā€œhomeā€ component, --production tried).

Then what Iā€™ve tried is that, located at the root :
image

Sorry if I landed the wrong place.
Iā€™ll set a gravatar but itā€™s my professionnal address and iā€™d prefer to choose it wisely :blush:
Have a nice day !

1 Like