Webpack-encore: Question: why aren't we allowed to make multiple shared entries?

Created on 8 Sep 2017  路  24Comments  路  Source: symfony/webpack-encore

Hi,

Just today I noticed that in webpack-encore it's impossible to create two shared entries?
For example I'd like to have one for my frontend and one for my backend because different scripts will be on each area of my app but they will still be reused through each app's respective area.

However in
node_modules/@symfony/webpack-encore/lib/WebpackConfig.js:199

it's throwing an exception createSharedEntry() cannot be called multiple times: you can only create *one* shared entry.

If I disable that check it creates the two shared entries with different names normally and they seem to work OK. Is there a specific reason not to create multiple shared entries?

Thanks in advance.

feature

Most helpful comment

I'll try. I hope that this help :)

backend.acme.com - all files require jquery and bootstrap but backend_admin page has specified code available only for page administrators

.addEntry('backend', [
'./assets/js/backend/backend.js',
])
.addEntry('backend_admin', [
'./assets/js/backend/admin.js',
])
.createSharedEntry('backend_vendor', [
'jquery',
'bootstrap'
])

Every backend page:

<script src="{{ asset('build/backend_vendor.js') }}"></script>
<script src="{{ asset('build/backend.js') }}"></script>

When admin visit backend page:

{{ parent() }}
<script src="{{ asset('build/backend_admin.js') }}"></script>

Another subdomain, support.acme.com, all page require jquery and moment:

.addEntry('support', [
'./assets/js/support/support.js',
])
.addEntry('support_logged', [
'./assets/js/support/support_logged.js',
])
.createSharedEntry('support_vendor', [
'jquery',
'moment'
])

Every support page:

<script src="{{ asset('build/support_vendor.js') }}"></script>
<script src="{{ asset('build/support.js') }}"></script>

When user is logged to support:

{{ parent() }}
<script src="{{ asset('build/support_logged.js') }}"></script>

All 24 comments

Hi @ioweb-gr,

What createSharedEntry() does currently is basically configuring a single CommonsChunkPlugin.

If you were to remove that check in WebpackConfig.js and call it twice it would override the value of sharedCommonsEntryName that was previously set and you wouldn't have an instance of the CommonsChunkPlugin for the first entry.

For instance, doing the following:

Encore
    .createSharedEntry('entry1', ['lib1', 'lib2'])
    .createSharedEntry('entry2', ['lib3', 'lib4'])

Would end-up being the same as doing:

Encore
    .createEntry('entry1', ['lib1', 'lib2'])
    .createSharedEntry('entry2', ['lib3', 'lib4'])

It'd need some modifications to allow to call it twice (and add multiple instances of CommonsChunkPlugin) but I don't see anything else preventing it from being done (WDYT @weaverryan ?).

Meanwhile if you want to create multiple shared entries you can do it manually using both Encore.addEntry(/*...*/) and Encore.addPlugin(new webpack.optimize.CommonsChunkPlugin({/*...*/}));

Hmm, multiple shared entries only makes sense with 2 instances of the plugin (as your last code example suggests), right? So, you could have a shared called frontend and another called backend (on a different plugin instance).

I think this makes sense: you would personally manage which one shared entry you include on your page. But what if an entry that's used only on your backend requires a module that is only required by your frontend shared entry? Wouldn't that module be packaged in the frontend shared entry (but not in the backend shared) and removed from the entry that is used on the backend? Wouldn't this cause the module to be missing on the backend (because you have a script tag for the backend shared and the specific other backend entry... neither of which would contain the module)?

I haven't given much thought to multiple entries - I'm trying to see if it makes sense at all :).

Multiple shared entries in a single Webpack config are building on top of each other (meaning one of the shared entries expect the other one to be present). But this is a valid use case in some projects.

If you have 2 independent shared entries (meaning backend and frontend entries are fully separate), the solution to have shared entries in each of them is to use the multi-config mode of webpack (which is now supported by Encore since #48, even though the dev-server seems to have issues with it)

note that in Incenteev, I actually have use case for both of these patterns (except that I'm not using webpack yet)

Personally I'm really new to webpack but reading on the documentation about common chunks plugin, it seemed a pretty reasonable use case.

Sometimes you want your frontend to be completely separate to your backend and I could see no reason to bombard the client with many modules that will never be used.

In my opinion it's better to have more control on the structure and handle what you add and where you add them especially if you're not building a SPA

@weaverryan Maybe I'm not understanding correctly but wouldn't having another "common" shared entry solve this issue?

There is an example in the Webpack repository with multiple instances of the CommonsChunkPlugin and they seem to use this approach.

@Lyrkan Yea... there is definitely a valid use-case here (as discussed) of having multiple commons by using multiple instances of the plugin - the link to the Webpack repository is a great resource to prove that. We/I need to play with this a bit - configure a nice frontend/backend commons setup and see how the API would look in Webpack for this. Said differently, the Webpack repo example makes my head spin, so I need to play with things to see how we should implement it :)

@weaverryan In case that helps and you want to do some tests I started to work on it a while ago: Lyrkan/webpack-encore@6dfb2e7499454e22ff1a54934ac08755a1120c7e

I wasn't really satisfied with it though, not sure why, it just didn't feel right... (and the 3rd example of the doc is kind of useless, I forgot to change it ^^).

For us this feature would be also very handy!

@Lyrkan any progress on this?

Hi @piotrantosik,

No real progress on this issue. I'm still not sure that the commit from my previous comment is the right approach to solve it.

If you have a project with an use case for multiple shared entries and are able to test that branch I'd be really glad if you could give some feedback on it though.

Ok, i will try test at the weekend or early next week.

And I would still like to hear people鈥檚 use-cases (maybe in the only one who doesn鈥檛 understand well).

Why do you guys want this feature? How will it help you? I truly want to understand - should not be a hard feature to implement... if I can understand the goal :).

Simple use case :) I have a app which support multiple subdomains, eg. backend.acme.com, clients.acme.com, support.acme.com

Some views are completely independent, so I would like separate frontend files per subdomain. Combining them into one is not a good idea for me.

Thanks @piotrantosik! Can you give me a fake example? Like, what entries would you have, what files would (or would not) be in each common entry, and what script tags would you include on each page?

I'll try. I hope that this help :)

backend.acme.com - all files require jquery and bootstrap but backend_admin page has specified code available only for page administrators

.addEntry('backend', [
'./assets/js/backend/backend.js',
])
.addEntry('backend_admin', [
'./assets/js/backend/admin.js',
])
.createSharedEntry('backend_vendor', [
'jquery',
'bootstrap'
])

Every backend page:

<script src="{{ asset('build/backend_vendor.js') }}"></script>
<script src="{{ asset('build/backend.js') }}"></script>

When admin visit backend page:

{{ parent() }}
<script src="{{ asset('build/backend_admin.js') }}"></script>

Another subdomain, support.acme.com, all page require jquery and moment:

.addEntry('support', [
'./assets/js/support/support.js',
])
.addEntry('support_logged', [
'./assets/js/support/support_logged.js',
])
.createSharedEntry('support_vendor', [
'jquery',
'moment'
])

Every support page:

<script src="{{ asset('build/support_vendor.js') }}"></script>
<script src="{{ asset('build/support.js') }}"></script>

When user is logged to support:

{{ parent() }}
<script src="{{ asset('build/support_logged.js') }}"></script>

@weaverryan what do you think about example? is helpful?

+1

Instead of specifying every vendor library, isn't it better to just add all of them that reside in node_modules?

const config = Encore.getWebpackConfig();

config.plugins.push(

    // Obtain implicit common vendor chunks
    new webpack.optimize.CommonsChunkPlugin({

        names: ['vendor', 'manifest'],

        minChunks(module) {
            // This assumes your vendor imports exist in the node_modules directory
            return module.context && /node_modules/.test(module.context);
        }
    })
);

Piotr - your example is great, I just haven鈥檛 had time :). I would love a PR from someone who wants this.

Adding all of node_modules is a simple idea, but it鈥檚 bad for performance in practice, as it would likely mean that libraries are included in your shared js file (which is loaded on every page) even though those js libraries might only be needed in an entry for a very obscure page. In other words, a shared entry full of stuff that鈥檚 rarely needed by the user :)

@weaverryan well, it depends on whether your site uses same libraries across all pages. It's not only about convenience but also avoiding 3rd-party code duplication among entry points.

This would also be extremely useful when making multiple SPA's using react for example.

// react_base.html.twig
...
{% block root_scripts %}
  <script src="{{ asset('manifest.js') }}"></script>
  <script src="{{ asset('app.js') }}"></script>
{% block application %}
  {#- scenes or pages go here -#}
{% endblock %}

{% endblock %}
...

Both a back-end and a front-end SPA could look like the following, with the only difference between them being that back-end might require "heavy editors and dashboard functionality" that would slow down your front-end, should it be included in your main shared entry.

// some_backend_spa.html.twig
{% extends 'react_base.html.twig' %}

{% block application %}
  <script src="{{ asset('scenes/backendSceneUsingHeavySomeStuff.js') }}"></script>
{% endblock %}

You would ideally want to extend the above template from one that holds a shared script that contains everything from app.js and all the "heavy editors and dashboard functionality" on top as a shared asset, like so;

// react_backend_base.html.twig
{% extends 'react_base.html.twig' %}

{% block root_scripts %}
{{ parent() }}
  <script src="{{ asset('heavyEditorsAndDashboardFunctionality.js') }}"></script>
{% block application %}
  {#- scenes or pages go here -#}
{% endblock %}

{% endblock %}

At which case the 2nd script (some_backend_spa.html.twig) could extend react_backend_base.html.twig.

Personally i'd also be fine with a separate front-end and back-end shared entry, but the above would be even more ideal with regards to manageability of the code.

My use case is to split the vendors (change seldom) from the shared app code (changing often). This should improve caching.

https://github.com/webpack/webpack/tree/master/examples/common-chunk-and-vendor-chunk

Hi guys!

Closing this issue. The reason is that the CommonsChunkPlugin was replaced by the SplitChunksPlugin, which the latest version of Encore supports. The way it splits is much more intelligent and highly configurable.

Cheers!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

zek0faws picture zek0faws  路  4Comments

iammichiel picture iammichiel  路  3Comments

wenmingtang picture wenmingtang  路  4Comments

MatthD picture MatthD  路  4Comments

Growiel picture Growiel  路  3Comments