Webpack-encore: Support for multiple webpack configs

Created on 21 Jun 2017  路  7Comments  路  Source: symfony/webpack-encore

Webpacks supports 3 different kinds of exports for its config:

  • a config object, which is what Encore generates
  • an array of config objects, which will then be similar to running webpack multiple times (once per config) but in a single run. This is handy when you want to isolate different shared entries entirely for instance.
  • a promise resolving to one of the previous format (handy to allow building the config asynchronously if you need to load it from elsewhere)

Encore does not need to do anything special to support promises. People needing this could simply use Encore inside their promise.

However, Encore currently cannot be used for the array case (at least not without accessing internal APIs). This is because the public API of Encore is currently built around a stateful singleton.
Note that all internal APIs are ready for that though, as they rely on a non-singleton WebpackConfig object.

feature

Most helpful comment

Hey @aeyoll!

That's a nice setup :). Yea, can you create an issue - and posting the relevant parts of your webpack.config.js might be helpful. It does sound like a bug, or a missing option or something.

Cheers!

All 7 comments

Btw, here is a potential use case for that (adapted from our asset building toolchain at Incenteev, even though it is not using webpack currently).

We actually have 2 different frontends: one for desktop+tablet and another one for mobile (yeah, we do UA-sniffing to change the UI entirely on mobile rather than just being responsive like for desktop vs tablet).
As such, we have different CSS and JS assets in our project (I will list JS ones here, but each one has a CSS counterpart actually):

  • app.js (desktop+tablet code)
  • vendor.js
  • mobile.js
  • vendor.mobile.js

As you can see, the vendor file loaded on mobile is not the same than the desktop one (mobile one is much smaller, as a bunch of vendors are used only by configuration interfaces which are not accessible in the mobile version).
Using a single webpack config would not allow to build this case (at least AFAIK), as we need to extract common chunks separately for mobile and desktop.
However, this would work fine with 2 independent configurations. And I would love being able to use Encore to build each of them (instead of having to use raw webpack to build at least one of them)

I actually found a proposal to provide such feature without any impact for people using the simple case: allowing to reset the state of the singleton to build a new configuration after the first one.

I actually have 2 proposals:

  • resetting manually:

    Encore
        .setOutputPath('web/build/')
        .setPublicPath('/build')
        .addEntry('app', './assets/js/main.js')
        .addStyleEntry('global', './assets/css/global.scss')
        .enableSassLoader()
        .autoProvidejQuery()
        .enableSourceMaps(!Encore.isProduction())
    ;
    
    // build the first configuration
    const firstConfig = Encore.getWebpackConfig();
    
    // Reset Encore to build the second config
    Encore.reset();
    
    Encore
        .setOutputPath('web/build/')
        .setPublicPath('/build')
        .addEntry('mobile', './assets/js/mobile.js')
        .addStyleEntry('mobile', './assets/css/mobile.less')
        .enableLessLoader()
        .enableSourceMaps(!Encore.isProduction())
    ;
    
    // build the second configuration
    const secondConfig = Encore.getWebpackConfig();
    
    // export the final configuration
    module.exports = [firstConfig, secondConfig];
    
  • resetting automatically when generating the config (so that next usages are building a new one rather than building on top of the previous config):

    Encore
        .setOutputPath('web/build/')
        .setPublicPath('/build')
        .addEntry('app', './assets/js/main.js')
        .addStyleEntry('global', './assets/css/global.scss')
        .enableSassLoader()
        .autoProvidejQuery()
        .enableSourceMaps(!Encore.isProduction())
    ;
    
    // build the first configuration
    const firstConfig = Encore.getWebpackConfig();
    
    // Encore state is clean again here
    Encore
        .setOutputPath('web/build/')
        .setPublicPath('/build')
        .addEntry('mobile', './assets/js/mobile.js')
        .addStyleEntry('mobile', './assets/css/mobile.less')
        .enableLessLoader()
        .enableSourceMaps(!Encore.isProduction())
    ;
    
    // build the second configuration
    const secondConfig = Encore.getWebpackConfig();
    
    // export the final configuration
    module.exports = [firstConfig, secondConfig];
    

If we go this way, there is no change for people not using this feature, and the diff is minimal:

diff --git a/index.js b/index.js
index 1b72052..645be9d 100644
--- a/index.js
+++ b/index.js
@@ -20,7 +20,7 @@ if (!runtimeConfig) {
     throw new Error('Are you trying to require index.js directly?');
 }

-const webpackConfig = new WebpackConfig(runtimeConfig);
+let webpackConfig = new WebpackConfig(runtimeConfig);

 module.exports = {
     /**
@@ -402,6 +402,10 @@ module.exports = {
         return webpackConfig.isProduction();
     },

+    reset() {
+        webpackConfig = new WebpackConfig(runtimeConfig);
+    },
+
     /**
      * Use this at the bottom of your webpack.config.js file:
      *

and this is the diff for the second proposal:

diff --git a/index.js b/index.js
index 1b72052..12d8085 100644
--- a/index.js
+++ b/index.js
@@ -20,7 +20,7 @@ if (!runtimeConfig) {
     throw new Error('Are you trying to require index.js directly?');
 }

-const webpackConfig = new WebpackConfig(runtimeConfig);
+let webpackConfig = new WebpackConfig(runtimeConfig);

 module.exports = {
     /**
@@ -413,7 +413,11 @@ module.exports = {
         try {
             validator(webpackConfig);

-            return configGenerator(webpackConfig);
+            const config = configGenerator(webpackConfig);
+
+            webpackConfig = new WebpackConfig(runtimeConfig);
+
+            return config;
         } catch (error) {
             // prettifies errors thrown by our library
             const pe = new PrettyError();

My previous thinking about this involved changing the API to avoid depending on a singleton, but this makes the usage harder for people not needing this feature.

@weaverryan do you prefer the explicit or the automatic resetting ?

Wow... that's a really clever and simple idea! I'd prefer the explicit resetting.

OK, I'm sending a PR

Hey guys,
I'm very interested by this feature, one use case could be for instance to have a bundle for a frontend app, and another for a backend app on the same Symfony project. If I set different output paths in each config, and using the packages assets configuration to specify two manifest.json files, I'm able to make it work using the encore dev command.

framework:
    assets:
        packages:
            frontend:
                json_manifest_path: '%kernel.project_dir%/web/build/frontend/manifest.json'
            backend:
                json_manifest_path: '%kernel.project_dir%/web/build/backend/manifest.json'

However, when I try to use the webpack-dev-server, it looks like it's trying to serve the content from the first webpack configuration, making it impossible to use:

> encore dev-server --hot --port 8001                               

Running webpack-dev-server ...    

Project is running at http://localhost:8001/                        
webpack output is served from /build/frontend/  

Maybe one way to solve it would be to set an option to specify the output directory for the dev-server, but I don't think it exists at the moment. If you need me to give you my Encore config or to create an issue, I would be happy to do it

Thanks a lot

Hey @aeyoll!

That's a nice setup :). Yea, can you create an issue - and posting the relevant parts of your webpack.config.js might be helpful. It does sound like a bug, or a missing option or something.

Cheers!

Was this page helpful?
0 / 5 - 0 ratings