Categories
Web Development

Symfony 5+ how to include page specific javascript or css when using Webpack encore and SASS

In this article I will cover how to do this with Webpack in Symfony with CSS and SASS. This is slightly confusing.

Symfony has it’s own Webpack configuration called encore. Read that documentation article if you need more info, more links at the bottom of the page.

Webpack Config

First lets checkout the file app\webpack.config.js

Mine currently looks like this.


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

// Manually configure the runtime environment if not already configured yet by the "encore" command.
// It's useful when you use tools that rely on webpack.config.js file.
if (!Encore.isRuntimeEnvironmentConfigured()) {
    Encore.configureRuntimeEnvironment(process.env.NODE_ENV || 'dev');
}

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')
    // only needed for CDN's or sub-directory deploy
    //.setManifestKeyPrefix('build/')

    /*
     * 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.scss) if your JavaScript imports CSS.
     */
    .addEntry('app', './assets/js/app.js')
    .addEntry('modalAction', './assets/js/modal-call-to-action.js')
    .addEntry('registration', './assets/js/registration.js')
    .addEntry('featuredImage','./assets/js/featured-image.js')
    .addEntry('editAboutUser','./assets/js/edit-about-user.js')
    .addEntry('sogiDraw', './assets/js/sogi-draw.js')
    // When enabled, Webpack "splits" your files into smaller pieces for greater optimization.
    .splitEntryChunks()

    // will require an extra script tag for runtime.js
    // but, you probably want this, unless you're building a single-page app
    .enableSingleRuntimeChunk()

    /*
     * FEATURE CONFIG
     *
     * Enable & configure other features below. For a full
     * list of features, see:
     * https://symfony.com/doc/current/frontend.html#adding-more-features
     */
    .cleanupOutputBeforeBuild()
    .enableBuildNotifications()
    .enableSourceMaps(!Encore.isProduction())
    // enables hashed filenames (e.g. app.abc123.css)
    .enableVersioning(Encore.isProduction())

    // enables @babel/preset-env polyfills
    .configureBabelPresetEnv((config) => {
        config.useBuiltIns = 'usage';
        config.corejs = 3;
    })

    // enables Sass/SCSS support
    .enableSassLoader()

    // uncomment if you use TypeScript
    //.enableTypeScriptLoader()

    // uncomment to get integrity="..." attributes on your script & link tags
    // requires WebpackEncoreBundle 1.4 or higher
    //.enableIntegrityHashes(Encore.isProduction())

    // uncomment if you're having problems with a jQuery plugin
    .autoProvidejQuery()

    // uncomment if you use API Platform Admin (composer req api-admin)
    //.enableReactPreset()
    //.addEntry('admin', './assets/js/admin.js')
;

module.exports = Encore.getWebpackConfig();

See each of the addEntry() lines. Each one of those is including  the processing of the Javascript files. If you need some CSS for example on every page you would put a line like the following inside the app.js file at the top to import the CSS.

import {DrawingForm} from "../javascript/templates/drawing-form";
import {DrawingActions} from "../javascript/objects/DrawingActions";
import '/assets/css/dialog/styles.scss';

In the last line here I am including styles.scss with an import statement. The .scss indicates the file is a SASS file.

This mixing of JS and CSS imports gets to be REALLY confusing. You see just importing the CSS into the Javascript is not enough. Nope you must also include the CSS and the Matching Javascript files with tags.

If you just link to the Javascript that is not enough and you will see your JS elements don’t have any related CSS. Importing the CSS just tells Webpack to process it and create another file.

homer simpson meme
Many baby steps to get to your CSS

So the import statement inside the Javascript file for the .scss file tells webpack to take that import along with any other .scss imports it finds and combine them all into one .css file. If there are no .scss inports there will be no matching .css file created.

All of this is supposed to make web development easier.

Modern web development keeps getting easier by the day
Look at the SASS files

Now lets look inside the styles.css really quick to see what it is doing.

@import 'variables';
@import 'dialog-search-form';
//@import 'grids';
@import 'forms';
@import 'overlay';
@import 'dialog';
@import 'canvas-color-picker';
@import 'editor-canvas';
@import 'image-selector';

.content {
    margin: 55px auto 0 auto;
    background-color: #fff;
    width: 95%;
    height: auto;
}
.hidden {
    display: none;
}

.clear-fix:after {
    content: "";
    display: table;
    clear: both;
}

#drawingCanvas{
    background-color: #fff;

}
.canvas-border, #drawingCanvas {
    border: 2px solid #000;
    margin: 0px auto;
    display: block;
}
.center-image {
    text-align: center;
    margin: 0px auto;
}

Notice all of the import statements. This is because I am using SASS. Each of those files contains a small amount of SCSS and Webpack uses SASS to compile it all into a  single file in the end. The import statement lets Webpack do tree shaking and remove unused code.

borat nice meme
Very nice!!!

So to include a page specific CSS you should create a javascript file inside any folder, this is what gets confusing.( you can use an existing JS file) I keep mine in assets\css\ sometimes in further folders like Dialog or Overlay etc. Then inside that file you import the CSS file like this

import ‘/assets/css/dialog/styles.scss’;

replacing styles.scss with your stylesheet or SASS file name.

But that is not all. You still need to include the final css file that webpack creates and saves in public\build\ folder in your template.

If you imported anything in the .scss file webpack compiles it and includes it in the matching .css file. Webpack will create a matching JS and CSS file based on your addEntry() method calls and store them in the build folder.

So you never make changes, EVER to the files in public\build you store all of your files in the assets folder and add an entryPoint definition in the Webpack Encore file.

So to get your CSS working, (actually included in the page) you need to add a line like this in the template. In your templates head section add  a line to include the CSS file like this.

{{ encore_entry_link_tags('styles') }}

That line is in my base.html.twig file which is the main page template that all of my other page templates inherit from.  So public\build\app.css will be available to every page in my app. Notice it does not include the file type. Twig template knows what to do since encore_entry_link_tags is a twig function.

Page specific CSS only.

So to summarize. If you wanted a CSS file included in only one page of your app, you would do the following. This is a dialog I want to include in only one page of my application.

Step #1 create the css

Create a css file in app\assets\css\dialog\dialog.scss directory for example. In my case I will be using SASS so I will create app\assets\css\dialog\dialog.scss Inside there I import the other tiny SASS .scss files I need.

Step #2 create the javascript

Create a Javascript file or import the css in an existing Javascript file. This is just how Webpack works, you need to include your CSS in your JS files and it does everything else (tree shaking), at least for now. So I create the Javascript file app\assets\js\dialog.js Inside that file I import the css like this

import '/assets/css/dialog/dialog.scss';

Now with this in place, Webpack will compile both the css and the javascript from that file and save it in app\public\build\dialog.css and app\public\build\dialog.js These are the files we must include with template specific functions, not the .scss files. (show below) Webpack will take all .scss import calls and do tree shaking to only include the code that is used.

Step #3 register the Entry point

Now you must register dialog.js (whatever your Javascript file name is) inside the webpack.encore.js file shown above add a line like this.

.addEntry('my-page', './assets/js/dialog.js')

Now restart webpack. In my case I am running webpack with watch so I use the following after I ctrl + c to stop the current watch

yarn run encore dev --watch

Webpack Encore will now create the new Javascript and CSS imported in it to the matching file names inside the public\build\ directory.

Now you must include both the Javascript and the CSS files. This is what tripped me up. I was like WTF why is the related CSS not working but the javascript is?

Uhm, wait… what?

Step #4 include the CSS file

So this is one of the most important steps. You have to include the CSS file located in the public\build\ directory, in the head area of your Twig template like this.

{{ encore_entry_link_tags('dialog') }}

Notice there is no .css it is just the name of the file. also note encore_entry_link_tags is used for the CSS only and javascript has it’s own function covered next. This CSS file was created by Webpack thanks to it being imported in dialog.js. The addEntry() function will create the Javascript file plus a CSS file for each import statement it finds. It stores all the imported scss in a single file that matches the javscript file name but with .css tile type.

Step #5 include the Javascript

Now you must include the matching Javascript that uses the imported CSS file. This is the Javascript Webpack Encore created due to the addEntry() line in the Webpack configuration above.

{{ encore_entry_script_tags('dialog') }}

This should be placed in the section where you include all of your Javascripts, mine are included before the closing body tag. Notice this looks exactly the same as including the CSS, except for the function name is now encore_entry_script_tags

I only have a few page specific Javascripts.  Which is why I can never remember this process.

Next up “Don’t forget to call your templates parent” More fun I discovered while using Twig templates.

Links

Learn how to activate SASS in Symfony webpack encore.

Managing CSS and Javascript from Symfony Documentation.

How to activate CSS preprocessors in Symfony documentation.

Categories
Resources Web Development

SASS CSS resources and links

SASS makes CSS way easier. You don’t have to type the same things over and over. You don’t have to hunt down instances of colors to change the theme of your website. You don’t have to hunt down font definitions to change them. You get variables and functions right in your CSS to make everything easier, faster and cleaner. In the past CSS was actually way more time consuming than writing the backend code that handled the websites/apps requests. Times have changed.

Articles

Sass Basics – basic intro and documentation

Sass Documentation

Sass vs. SCSS: which syntax is better? – an article that discusses the history of SASS and the .scss vs .sass file extensions.

Videos

Learn Sass In 20 Minutes | Sass Crash Course