Encore: Setting up your Project
Warning: You are browsing the documentation for Symfony 6.1, which is no longer maintained.
Read the updated version of this page for Symfony 7.1 (the current stable version).
Encore: Setting up your Project
After installing Encore, your app already
has a few files, organized into an assets/
directory:
assets/app.js
assets/bootstrap.js
assets/controllers.json
assets/styles/app.css
assets/controllers/hello_controller.js
With Encore, think of your app.js
file like a standalone JavaScript
application: it will require all of the dependencies it needs (e.g. jQuery or React),
including any CSS. Your app.js
file is already doing this with a JavaScript
import
statement:
1 2 3 4
// assets/app.js
// ...
import './styles/app.css';
Encore's job (via Webpack) is simple: to read and follow all of the import
statements and create one final app.js
(and app.css
) that contains everything
your app needs. Encore can do a lot more: minify files, pre-process Sass/LESS,
support React, Vue.js, etc.
The other files - bootstrap.js
, controllers.json
and hello_controller.js
relate to a topic you'll learn about soon: Stimulus & Symfony UX.
Configuring Encore/Webpack
Everything in Encore is configured via a webpack.config.js
file at the root
of your project. It already holds the basic config you need:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// webpack.config.js
const Encore = require('@symfony/webpack-encore');
Encore
// directory where compiled assets will be stored
.setOutputPath('public/build/')
// public path used by the web server to access the output path
.setPublicPath('/build')
.addEntry('app', './assets/app.js')
// uncomment this if you want use jQuery in the following example
.autoProvidejQuery()
;
// ...
The key part is addEntry()
: this tells Encore to load the assets/app.js
file and follow all of the require()
statements. It will then package everything
together and - thanks to the first app
argument - output final app.js
and
app.css
files into the public/build
directory.
To build the assets, run the following if you use the Yarn package manager:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
# compile assets and automatically re-compile when files change
$ yarn watch
# or
$ npm run watch
# or, run a dev-server that can sometimes update your code without refreshing the page
$ yarn dev-server
# or
$ npm run dev-server
# compile assets once
$ yarn dev
# or
$ npm run dev
# on deploy, create a production build
$ yarn build
# or
$ npm run build
All of these commands - e.g. dev
or watch
- are shortcuts that are defined
in your package.json
file.
Caution
Whenever you make changes in your webpack.config.js
file, you must
stop and restart encore
.
Congrats! You now have three new files:
public/build/app.js
(holds all the JavaScript for your "app" entry)public/build/app.css
(holds all the CSS for your "app" entry)public/build/runtime.js
(a file that helps Webpack do its job)
Note
In reality, you probably have a few more files in public/build
. Some of
these are due to code splitting, an optimization
that helps performance, but doesn't affect how things work. Others help Encore
do its work.
Next, to include these in your base layout, you can leverage two Twig helpers from WebpackEncoreBundle:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
{# templates/base.html.twig #}
<!DOCTYPE html>
<html>
<head>
<!-- ... -->
{% block stylesheets %}
{# 'app' must match the first argument to addEntry() in webpack.config.js #}
{{ encore_entry_link_tags('app') }}
<!-- Renders a link tag (if your module requires any CSS)
<link rel="stylesheet" href="/build/app.css"> -->
{% endblock %}
{% block javascripts %}
{{ encore_entry_script_tags('app') }}
<!-- Renders app.js & a webpack runtime.js file
<script src="/build/runtime.js" defer></script>
<script src="/build/app.js" defer></script>
See note below about the "defer" attribute -->
{% endblock %}
</head>
<!-- ... -->
</html>
That's it! When you refresh your page, all of the JavaScript from
assets/app.js
- as well as any other JavaScript files it included - will
be executed. All the CSS files that were required will also be displayed.
The encore_entry_link_tags()
and encore_entry_script_tags()
functions
read from a public/build/entrypoints.json
file that's generated by Encore to know the exact
filename(s) to render. This file is especially useful because you can
enable versioning or
point assets to a CDN without making any changes to your
template: the paths in entrypoints.json
will always be the final, correct paths.
And if you use splitEntryChunks() (where Webpack splits the output into even
more files), all the necessary script
and link
tags will render automatically.
If you are not using Symfony you won't have the encore_entry_*
functions available.
Instead, you can point directly to the final built files or write code to parse
entrypoints.json
manually. The entrypoints file is needed only if you're using
certain optional features, like splitEntryChunks()
.
1.9.0
The defer
attribute on the script
tags delays the execution of the
JavaScript until the page loads (similar to putting the script
at the
bottom of the page). The ability to always add this attribute was introduced
in WebpackEncoreBundle 1.9.0 and is automatically enabled in that bundle's
recipe in the config/packages/webpack_encore.yaml
file. See
WebpackEncoreBundle Configuration for more details.
Requiring JavaScript Modules
Webpack is a module bundler, which means that you can import
other JavaScript
files. First, create a file that exports a function, class or any other value:
1 2 3 4
// assets/greet.js
export default function(name) {
return `Yo yo ${name} - welcome to Encore!`;
};
We'll use jQuery to print this message on the page. Install it via:
1 2 3 4 5
# if you use the Yarn package manager
$ yarn add jquery --dev
# if you use the npm package manager
$ npm install jquery --save-dev
Great! Use import
to import jquery
and greet.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13
// assets/app.js
// ...
+ // loads the jquery package from node_modules
+ import $ from 'jquery';
+ // import the function from greet.js (the .js extension is optional)
+ // ./ (or ../) means to look for a local file
+ import greet from './greet';
+ $(document).ready(function() {
+ $('body').prepend('<h1>'+greet('jill')+'</h1>');
+ });
That's it! If you previously ran encore dev --watch
, your final, built files
have already been updated: jQuery and greet.js
have been automatically
added to the output file (app.js
). Refresh to see the message!
Stimulus & Symfony UX
As simple as the above example is, instead of building your application inside of
app.js
, we recommend Stimulus: a small JavaScript framework that makes it
easy to attach behavior to HTML. It's powerful, and you will love it! Symfony
even provides packages to add more features to Stimulus. These are called the
Symfony UX Packages.
If you followed the setup instructions, you should already have Stimulus installed
and ready to go! In fact, that's the purpose of the assets/bootstrap.js
file:
to initialize Stimulus and automatically load any "controllers" from the
assets/controllers/
directory.
Let's look at a simple Stimulus example. In a Twig template, suppose you have:
1 2 3 4 5 6 7 8 9
<div {{ stimulus_controller('say-hello') }}>
<input type="text" {{ stimulus_target('say-hello', 'name') }}>
<button {{ stimulus_action('say-hello', 'greet') }}>
Greet
</button>
<div {{ stimulus_target('say-hello', 'output') }}></div>
</div>
The stimulus_controller('say-hello')
renders a data-controller="say-hello"
attribute. Whenever this element appears on the page, Stimulus will automatically
look for and initialize a controller called say-hello-controller.js
. Create
that in your assets/controllers/
directory:
1 2 3 4 5 6 7 8 9 10
// assets/controllers/say-hello-controller.js
import { Controller } from '@hotwired/stimulus';
export default class extends Controller {
static targets = ['name', 'output']
greet() {
this.outputTarget.textContent = `Hello, ${this.nameTarget.value}!`
}
}
The result? When you click the "Greet" button, it prints your name! And if
more {{ stimulus_controller('say-hello') }}
elements are added to the page - like
via Ajax - those will instantly work: no need to reinitialize anything.
Ready to learn more about Stimulus?
- Read the Stimulus Documentation
- Find out more about how the Symfony UX system works
- See a list of all Symfony UX packages
Learn more about the Symfony Stimulus Bridge - including the superpower of making your controllers load lazily!
Screencast
Or check out the Stimulus Screencast on SymfonyCasts.
Turbo: Lightning Fast Single-Page-Application Experience
Symfony comes with tight integration with another JavaScript library called Turbo. Turbo automatically transforms all link clicks and form submits into an Ajax call, with zero (or nearly zero) changes to your Symfony code! The result? You get the speed of a single page application without having to write any JavaScript.
To learn more, check out the symfony/ux-turbo package.
Screencast
Or check out the Turbo Screencast on SymfonyCasts.
Page-Specific JavaScript or CSS
So far, you only have one final JavaScript file: app.js
. Encore may be split
into multiple files for performance (see split chunks),
but all of that code is still downloaded on every page.
What if you have some extra JavaScript or CSS (e.g. for performance) that you only want to include on certain pages?
Lazy Controllers
One very nice solution if you're using Stimulus is to leverage lazy controllers.
To activate this on a controller, add a special stimulusFetch: 'lazy'
above
your controller class:
1 2 3 4 5 6 7
// assets/controllers/lazy-example-controller.js
import { Controller } from '@hotwired/stimulus';
/* stimulusFetch: 'lazy' */
export default class extends Controller {
// ...
}
That's it! This controller's code - and any modules that it imports - will be
split to separate files by Encore. Then, those files won't be downloaded until
the moment a matching element (e.g. <div data-controller="lazy-example">
)
appears on the page!
Note
If you write your controllers using TypeScript, make sure
removeComments
is not set to true
in your TypeScript config.
Multiple Entries
Another option is to create page-specific JavaScript or CSS (e.g. checkout, account, etc.). To handle this, create a new "entry" JavaScript file for each page:
1 2
// assets/checkout.js
// custom code for your checkout page
1 2
// assets/account.js
// custom code for your account page
Next, use addEntry()
to tell Webpack to read these two new files when it builds:
1 2 3 4 5 6 7
// webpack.config.js
Encore
// ...
.addEntry('app', './assets/app.js')
+ .addEntry('checkout', './assets/checkout.js')
+ .addEntry('account', './assets/account.js')
// ...
And because you just changed the webpack.config.js
file, make sure to stop
and restart Encore:
1 2 3 4 5
# if you use the Yarn package manager
$ yarn watch
# if you use the npm package manager
$ npm run watch
Webpack will now output a new checkout.js
file and a new account.js
file
in your build directory. And, if any of those files require/import CSS, Webpack
will also output checkout.css
and account.css
files.
Finally, include the script
and link
tags on the individual pages where
you need them:
1 2 3 4 5 6 7 8 9 10 11 12
{# templates/.../checkout.html.twig #}
{% extends 'base.html.twig' %}
+ {% block stylesheets %}
+ {{ parent() }}
+ {{ encore_entry_link_tags('checkout') }}
+ {% endblock %}
+ {% block javascripts %}
+ {{ parent() }}
+ {{ encore_entry_script_tags('checkout') }}
+ {% endblock %}
Now, the checkout page will contain all the JavaScript and CSS for the app
entry
(because this is included in base.html.twig
and there is the {{ parent() }}
call)
and your checkout
entry. With this, JavaScript & CSS needed for every page
can live inside the app
entry and code needed only for the checkout page can
live inside checkout
.
Using Sass/LESS/Stylus
You've already mastered the basics of Encore. Nice! But, there are many more
features that you can opt into if you need them. For example, instead of using plain
CSS you can also use Sass, LESS or Stylus. To use Sass, rename the app.css
file to app.scss
and update the import
statement:
1 2 3
// assets/app.js
- import './styles/app.css';
+ import './styles/app.scss';
Then, tell Encore to enable the Sass preprocessor:
1 2 3 4 5 6
// webpack.config.js
Encore
// ...
+ .enableSassLoader()
;
Because you just changed your webpack.config.js
file, you'll need to restart
Encore. When you do, you'll see an error!
1 2
> Error: Install sass-loader & sass to use enableSassLoader()
> yarn add sass-loader@^12.0.0 sass --dev
Encore supports many features. But, instead of forcing all of them on you, when you need a feature, Encore will tell you what you need to install. Run:
1 2 3 4 5 6 7
# if you use the Yarn package manager
$ yarn add sass-loader@^12.0.0 sass --dev
$ yarn encore dev --watch
# if you use the npm package manager
$ npm install sass-loader@^12.0.0 sass --save-dev
$ npm run watch
Your app now supports Sass. Encore also supports LESS and Stylus. See CSS Preprocessors: Sass, LESS, Stylus, etc..
Compiling Only a CSS File
Caution
Using addStyleEntry()
is supported, but not recommended. A better option
is to follow the pattern above: use addEntry()
to point to a JavaScript
file, then require the CSS needed from inside of that.
If you want to only compile a CSS file, that's possible via addStyleEntry()
:
1 2 3 4 5 6
// webpack.config.js
Encore
// ...
.addStyleEntry('some_page', './assets/styles/some_page.css')
;
This will output a new some_page.css
.
Keep Going!
Encore supports many more features! For a full list of what you can do, see Encore's index.js file. Or, go back to list of Frontend articles.