Using webpack on a common Symfony Bundle

Tags: , , , ,



I have a “common” bundle that is used on several Symfony projects. This bundle contains the assets we use on our different projects (js and css). Until now this bundle was mainly adding assets with macros that allowed to load the libraries cdn.

{% macro daterangepicker_js() %}
    <script src="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.min.js"></script>
{% endmacro %}

We use macros to load only the scripts we want for each page. We manage the dependencies ourselves.

But we want to start using npm + webpack to manage the js library updates (jquery, bootstrap,etc…). So I installed webpack on the “common” bundle (and not on our different projects). I also created an entry for each library (see code below). And to avoid re-importing all dependencies in each entry, I activated the splitEntryChunks option.

Now my webpack.config.js look like this:

Encore
    // directory where compiled assets will be stored
    .setOutputPath('Resources/public/build/')
    // public path used by the web server to access the output path
    .setPublicPath('/build')
    // only needed for CDN's or sub-directory deploy
    .setManifestKeyPrefix('bundles/commonbundle')

    /*
     * ENTRY CONFIG
     *
     * Add 1 entry for each "page" of your app
     * (including one that's included on every page - e.g. "app")
     *
     * Each entry will result in one JavaScript file (e.g. app.js)
     * and one CSS file (e.g. app.css) if your JavaScript imports CSS.
     */
    .addEntry('jquery', './assets/jquery.js')
    .addEntry('jquery-datepicker', './assets/jquery-datepicker.js')
    .addEntry('bootstrap', './assets/bootstrap.js')
    .addEntry('bootstrap-select', './assets/bootstrap-select.js')


    .cleanupOutputBeforeBuild()
    .enableBuildNotifications()

    .enableSourceMaps(false)
    .enableVersioning(false)
    .enableSingleRuntimeChunk()
    .autoProvidejQuery()
    .splitEntryChunks()
    .configureSplitChunks(function (splitChunks) {
        // change the configuration
        splitChunks.name = true;
        splitChunks.chunks = 'all';
    })
    .configureBabelPresetEnv((config) => {
        config.useBuiltIns = 'usage';
        config.corejs = 3;
    })

And my twig macros look like this :

{% macro jquery_js() %}
    <script src="{{ asset('bundles/commonbundle/build/runtime.js') }}"></script>
    <script src="{{ asset('bundles/commonbundle/build/vendors~bootstrap~bootstrap-select~jquery~jquery-datepicker.js') }}"></script>
    <script src="{{ asset('bundles/commonbundle/build/jquery.js') }}"></script>
{% endmacro %}

{% macro bootstrap_js() %}
    <script src="{{ asset('bundles/commonbundle/build/runtime.js') }}"></script>
    <script src="{{ asset('bundles/commonbundle/build/vendors~bootstrap~bootstrap-select~jquery~jquery-datepicker.js') }}"></script>
    <script src="{{ asset('bundles/commonbundle/build/vendors~bootstrap.js') }}"></script>
    <script src="{{ asset('bundles/commonbundle/build/bootstrap.js') }}"></script>
{% endmacro %}

{% macro bootstrapselect_js() %}
    <script src="{{ asset('bundles/commonbundle/build/runtime.js') }}"></script>
    <script src="{{ asset('bundles/commonbundle/build/vendors~bootstrap~bootstrap-select~jquery~jquery-datepicker.js') }}"></script>
    <script src="{{ asset('bundles/commonbundle/build/vendors~bootstrap-select.js') }}"></script>
    <script src="{{ asset('bundles/commonbundle/build/bootstrap-select.js') }}"></script>
{% endmacro %}

{% macro datepicker_js() %}
    <script src="{{ asset('bundles/commonbundle/build/runtime.js') }}"></script>
    <script src="{{ asset('bundles/commonbundle/build/vendors~bootstrap~bootstrap-select~jquery~jquery-datepicker.js') }}"></script>
    <script src="{{ asset('bundles/commonbundle/build/vendors~jquery-datepicker.js') }}"></script>
    <script src="{{ asset('bundles/commonbundle/build/jquery-datepicker.js') }}"></script>
{% endmacro %}


I can’t use {{ encore_entry_script_tags() }} because webpack is not installed on our mains applications so y tried to load the chunks myself.

It’s really ugly and it doesn’t even work.

Bootstrap-select initialization is called 4 times and I end up with duplicate selection fields.

also, I have this error message from datepicker :

Uncaught TypeError: Cannot read property ‘regional’ of undefined

How can i improve the webpack configuration ? If possible, I would like to continue to use exactly the same macros, i can’t refactor all our other project to stop using them.

Answer

So I managed to change my chunks configuration and now it’s easier to maintain inside my macros webpack.config.js :

    .enableSingleRuntimeChunk()
    .autoProvidejQuery()
    .splitEntryChunks()
    .configureSplitChunks(function (splitChunks) {
        splitChunks.name = function (module, chunks, cacheGroupKey) {
            const moduleFileName = module.identifier().split(/[/,\]/).reduceRight(item => item).replace(/.[^/.]+$/, "");
            return `${cacheGroupKey}~${moduleFileName}`;
        };
        splitChunks.chunks = 'all';
    })

it creates one chunk per library, but then it’s easy to divide them into my macros.

{% macro jquery_js() %}
    <script src="{{ asset('bundles/commonbundle/build/runtime.js') }}"></script>
    <script src="{{ asset('bundles/commonbundle/build/vendors~jquery.js') }}"></script>
    <script src="{{ asset('bundles/commonbundle/build/jquery.js') }}"></script>
{% endmacro %}

{% macro bootstrap_js() %}
    <script src="{{ asset('bundles/commonbundle/build/vendors~jquery.js') }}"></script>
    <script src="{{ asset('bundles/commonbundle/build/vendors~bootstrap.js') }}"></script>
    <script src="{{ asset('bundles/commonbundle/build/bootstrap.js') }}"></script>
{% endmacro %}

{% macro bootstrapselect_js() %}
    <script src="{{ asset('bundles/commonbundle/build/vendors~jquery.js') }}"></script>
    <script src="{{ asset('bundles/commonbundle/build/vendors~bootstrap-select.js') }}"></script>
    <script src="{{ asset('bundles/commonbundle/build/bootstrap-select.js') }}"></script>
{% endmacro %}

The vendors/chunks are duplicated but the browser don’t load them each time because they are the same file, so it’s ok.

It’s probably not the cleanest solution but it works in my specific use case.



Source: stackoverflow