JQuery loading after my script inside Twig template

1.5k Views Asked by At

I have some Javascript code inside a Twig template that uses jQuery. The script seems to be loading before jQuery, so it throws a $ is not defined error. I can't figure out why it's loading before the main bundle that includes jQuery (compiled with webpack-encore).

JQuery does load because I can reference it from the console or wrap the script inside a setTimeout to force it to be loaded later.

I have this base template:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>
            {% block title %}Welcome!
            {% endblock %}
        </title>
        {% block stylesheets %}
            {{ encore_entry_link_tags('app') }}
        {% endblock %}

        {% block javascripts %}
            {{ encore_entry_script_tags('app') }}
        {% endblock %}
    </head>
    <body>
        {% block body %}{% endblock %}
    </body>
</html>

The page template:

{% extends 'base.html.twig' %}
{% block body %}
/// ...
<script>
$.change(/*...*/); // $ is not defined
</script>
{% endblock %}

webpack.config.js:

const path = require('path');
const Encore = require('@symfony/webpack-encore');

if (!Encore.isRuntimeEnvironmentConfigured()) {
    Encore.configureRuntimeEnvironment(process.env.NODE_ENV || 'dev');
}

Encore
    .setOutputPath('public/build/')
    .setPublicPath('/build')
    .enableSassLoader()
    .addEntry('app', './assets/app.js')
    .enableStimulusBridge('./assets/controllers.json')
    .splitEntryChunks()
    .enableSingleRuntimeChunk()
    .cleanupOutputBeforeBuild()
    .enableBuildNotifications()
    .enableSourceMaps(!Encore.isProduction())
    .enableVersioning(Encore.isProduction())
    .configureBabel((config) => {
        config.plugins.push('@babel/plugin-proposal-class-properties');
    })
    .configureBabelPresetEnv((config) => {
        config.useBuiltIns = 'usage';
        config.corejs = 3;
    })

    .autoProvidejQuery()
;
module.exports = Encore.getWebpackConfig();

app.js:

require('./styles/app.scss');
import $ from 'jquery';
global.$ = global.jQuery = $;

This was the generated HTML:

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="/build/app.css">
    <script src="/build/runtime.js" defer></script>
    <script src="/build/app.js" defer></script>
</head>

<body>
    <main class="col p-4 content flex-grow-1">
        <!-- page content -->
        <script>
            // $ is not defined here
        </script>
    </main>
</body>
</html>
2

There are 2 best solutions below

0
On BEST ANSWER

The problem was found in these lines from the generated HTML:

<script src="/build/runtime.js" defer></script>
<script src="/build/app.js" defer></script>

The defer attribute forces the Javascript files where jQuery is defined, to be loaded after the page is rendered. Since I'm adding inline Javascript in the page, it's loaded before app.js.

Since there is no way to add deferred inline Javascript in HTML, there are two alternatives:

  • Set the following configuration in config/packages/webpack_encore.yaml:
script_attributes:
    defer: false

This disables the behavior described. This solution is not ideal because using defer is desirable in my case.

  • Define Javascript code outside of the template (import it with encore_entry_script_tags).

Save your Javascript file in assets/ and, if you have a similar base template as the one in the question, import it in your page template like this:

{% block javascripts %}
    {{ parent() }}
    {{ encore_entry_script_tags('your_file') }}
{% endblock %}

In webpack.config.js:

Encore
//...
.addEntry('your_file','./assets/your_file.js');
1
On

NOTE: You must note that you can’t use require and import at the same time in your node program and it is more preferred to use require instead of import as you are required to use the experimental module flag feature to run import program.

--https://www.geeksforgeeks.org/difference-between-node-js-require-and-es6-import-and-export/

The article above also mentions that with require "you can directly run the code" in this case the code is defining the $ function, so you want to run that.

My projects are as follows with no problems:

app.js:

require('./styles/app.scss');
const $ = require('jquery');
window.$ = $;

Comment the following line in webpack.config.js:

//.autoProvidejQuery()