Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

A World without Build Systems?

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

Whoa, hey! Welcome to my frontend laboratory where we're going to do something that I honestly thought I would never do again. Something bold! Something... maybe just a bit crazy. We're going to write a modern frontend with zero build system.

How we got here

Back-story time! 7 years ago I was talking about how modern JavaScript requires a build system. I was shouting to the world that we needed to transition from creating JavaScript and CSS files in a "public" directory towards building them with a system, like Webpack or Vite.

These build systems were created because browsers didn't support modern features that we wanted to use. I'm talking about the import statement, const, the class syntax, and so on. If you tried to run this kind of JavaScript in a browser, you would have been greeted with sad error messages.

So, the build system would transpile (that's a fancy word for "convert") that new looking JavaScript to old looking JavaScript, so it could run in the browser. It would also combine JavaScript and CSS files, so we would have fewer requests, it could create versioned filenames, process TypeScript and JSX, Sass, and much more.

These systems are incredibly powerful. But they also add complexity and can slow down coding. So I'm here, 7 years later to say that... we might not need those build systems anymore! In this tutorial, we're going to write all the modern JavaScript that we know and love... but with zero build system, and no Node. Just you and the browser: the way the Gods of the Internet intended it.

Is this for Every Project?

Now, I admit, doing this won't be the best option for every project. If you want to use TypeScript, or you're using React, Vue or Next.js, you'll probably still want a build system... and you should probably use their build system. Skipping a build system also means no automatic tree-shaking - if you know and care about that - though we'll learn how that can still work.

For the most part, coding with and without a build system is identical, but I'll point out the small differences along the way. And if you're wondering about things like Sass preprocessors, or Tailwind, you can totally use those and we'll see how. The final site is also going to be as performant and fast as one built with a build system.

Project Setup

Okay, let's get to work! Coding without a build system is a joy: no node or batteries required. So you should absolutely download the course code from this page and code along with me. After you unzip that file, you'll have a start/ directory with the same code that you see here. Pop open this README.md file. As usual, it holds all the setup details you'll need. I've done most of them already. The last step is to find your terminal, move into the project, and run:

symfony serve -d

to use the symfony binary to start the built-in web server. I'll hold "command", click and... hello Mixed Vinyl! But wow is this thing weird and ugly-looking.

This is a Symfony 6.3 project - the same project we've built in the Symfony series. It has Doctrine installed... but there's nothing particularly special about it, and right now, it has literally zero CSS and JavaScript. There's no assets/ directory and nothing hiding inside the public/ directory.

The first thing I want to explore is the reality that our browser can handle more modern stuff than we might realize... certainly much more than I realized a few months ago. Let's see what all the hype is about by taking our browser for a modern JavaScript test drive next.

Leave a comment!

Login or Register to join the conversation
Marius Avatar

In case anyone else gets an error about the environment variable GITHUB_TOKEN not being set: It's stored in a secret and Symfony's secrets require the PHP extension "sodium" to be loaded.

Neither composer check-platform-reqs nor symfony local:check:requirements mentions the extension. Maybe add it to composer.json of the course code?

1 Reply

Hey Marius,

Thanks for this tip! Please, correct me if I'm wrong, but I don't see we use Symfony secrets in this course. Is it something that you use internally for your project?


Marius Avatar

Hey Victor,

It's not being used in the course, but the downloadable code contains a secret called GITHUB_TOKEN (with the value "CHANGEME" in dev). I guess it's the one from chapter 21 in the "Symfony 6 Fundamentals: Services, Config & Environments" course.

Not a big deal, especially if you go through the courses in order. I just happened to do the courses on different computers and this one didn't have sodium installed yet.

1 Reply

Yea... this is a good tip - for some reason, some PHP installs still don't have Sodium. And, as you said, it's not actually needed. I think we can comment-out its usage to avoid the error entirely.

Tac-Tacelosky Avatar
Tac-Tacelosky Avatar Tac-Tacelosky | posted 1 day ago

Usually I open the 'finish' directory and run composer install (and then yarn install && yarn dev, but of course that's exactly what I'm eager to not do with this tutorial!)

But running the finished project needs more than that, I guess because of tailwind.

Tailwind isn't even in composer.json, so something seems wrong.

I got stuck on something doing the tutorial, so I want to go to the finished project and run it, but now I'm stuck on that.

Tac-Tacelosky Avatar
Tac-Tacelosky Avatar Tac-Tacelosky | Tac-Tacelosky | posted 1 day ago | edited

Since this course is all about not needing a build system, I think you should check in the app.tailwind.css file, so that the 'finish' file works.

Otherwise, you have to build the css file, but then there should be installation instructions in the README. The tailwind bundle downloads the executable, but there appears to be no way to run the bin/console command and get the output file in the right directory. Downloading the tailwind binary works better (since the tutorial has instructions on how to run it).

So after way some playing around, I was able to build the finished project. But please, either add the tailwind css file or the build instructions to the readme of the 'final' folder.

I LOVE AssetMapper. I can't wait to understand it enough to drop the build system.


Hey Tac!

I think you should check in the app.tailwind.css file, so that the 'finish' file works.

Ah, good idea actually :). Btw, now that https://github.com/symfonycasts/tailwind-bundle exists, adding Tailwind is even easier, and you don't need to think about this app.tailwind.css file anymore.

I LOVE AssetMapper. I can't wait to understand it enough to drop the build system.

It's going to get even better in 6.4! We just merged CSS support, smarter preloading & it looks like downloading vendor files locally (vs using the CDN) will be a first-class citizen. Good stuff coming!


Nayte91 Avatar
Nayte91 Avatar Nayte91 | posted 1 month ago | edited

And first of all, thank you for you course, once again it's a charm to follow it.

I have a question that I didn't find any help for:

let's say that I come from a good old webpack encore project. Nothing fancy here, I think it's a good candidate for a assetmapper conversion. My app.js is like this:

import './styles/globals.css';
import './styles/table.css';
import './styles/organisms/header.css';
import './styles/molecules/_header_navigation_menu.css';
import './styles/molecules/_header_authentication_menu.css';
import './styles/atoms/_header_link.css';
import './styles/atoms/_header_button.css';
import './styles/molecules/_header_popover.css';
import './styles/organisms/replay_research_form.css';
import './styles/organisms/replay_form.css';
import './styles/molecules/_replay_filters.css';
import './styles/molecules/_replay_searchPreview.css';
import './styles/molecules/_call_to_action.css';
import './styles/organisms/_ranking_table.css';

import './bootstrap.js'; // start the Stimulus application

As we can see, beside stimulus (I will handle its removal with your chapter 12!), it's just a bunch of css files (classic atomic design pattern). But, there is so any css files that I wonder if it's a good idea to add plain html <link tas for each of them? or is there a way to avoid having so many tags? Actually, the compilation is not that bad to allow dev to separate components CSS in a file per each.

What would you do in such a a situation? Stop having 15 .css files? Keep webpack? Put them all with assetmapper? Or do I miss a trick for this usecase?

Thank you in advance! Have a lovely day,


Hey @Nayte91!

I think this is a VERY legitimate question :). Somehow, though it feels totally normal to have 15 import statements for CSS files, it feels weird to have 15 link tags for those same 15 CSS files :). But I think we should not feel weird about this: we need to list all 15 CSS files somewhere, there's not really any difference between doing it as import statements of link tags :).

However, I think you might be referring more to the possible issue that 15 link tags means 15 requests for those CSS files, vs the 1 that this would compile to in Webpack. With HTTP/2, this is largely not a concern. However, there is certainly SOME upper limit where it is a problem (e.g. imagine you have 1000 CSS files), but I'm not sure where that is. My advice is to put those as 15 links tags and run Lighthouse on it to be sure it's not a problem. You could even - as we do in the last chapter - send a preload header to hint even EARLIER that these file are needed - https://symfonycasts.com/screencast/asset-mapper/preloading#preloading-via-a-header

My guess is that putting 15 link tags is a non-issue, but I get that it feels weird :). Time will tell whether this is something that we al "get used to" or if, perhaps, so many people find it weird for some reason that we add a layer to combine the files. But I'm hoping it doesn't come to that: that's the point of HTTP/2, it removes that need for combining files (so I hope we won't need to re-add it for some reason).


Cesar Avatar

Hi guys:
If I understood correctly in the first video, I would not need Webpack Encore if I use AssetMapper? At least in most of the cases I mean
I am asking just to be sure.


yep it is SO, when you are using modern browser, honestly pretty unbelievable, I remember the times of IE6 and now you can just write modern JS and it will work.... awesome thing!


seb-jean Avatar
seb-jean Avatar seb-jean | posted 3 months ago

"Symfony Gazette", excellent :D.

Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
    "require": {
        "php": ">=8.1",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "babdev/pagerfanta-bundle": "^4.0", // v4.2.0
        "doctrine/doctrine-bundle": "^2.7", // 2.10.0
        "doctrine/doctrine-migrations-bundle": "^3.2", // 3.2.4
        "doctrine/orm": "^2.12", // 2.15.2
        "knplabs/knp-time-bundle": "^1.18", // v1.20.0
        "pagerfanta/doctrine-orm-adapter": "^4.0", // v4.1.0
        "pagerfanta/twig": "^4.0", // v4.1.0
        "stof/doctrine-extensions-bundle": "^1.7", // v1.7.1
        "symfony/asset": "6.3.*", // v6.3.0
        "symfony/asset-mapper": "6.3.*", // v6.3.0
        "symfony/console": "6.3.*", // v6.3.0
        "symfony/dotenv": "6.3.*", // v6.3.0
        "symfony/flex": "^2", // v2.3.1
        "symfony/framework-bundle": "6.3.*", // v6.3.0
        "symfony/http-client": "6.3.*", // v6.3.0
        "symfony/monolog-bundle": "^3.0", // v3.8.0
        "symfony/proxy-manager-bridge": "6.3.*", // v6.3.0
        "symfony/runtime": "6.3.*", // v6.3.0
        "symfony/stimulus-bundle": "^2.9", // v2.9.1
        "symfony/twig-bundle": "6.3.*", // v6.3.0
        "symfony/ux-turbo": "^2.9", // v2.9.1
        "symfony/web-link": "6.3.*", // v6.3.0
        "symfony/yaml": "6.3.*", // v6.3.0
        "twig/extra-bundle": "^2.12|^3.0", // v3.6.1
        "twig/twig": "^2.12|^3.0" // v3.6.1
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.4
        "symfony/debug-bundle": "6.3.*", // v6.3.0
        "symfony/maker-bundle": "^1.41", // v1.49.0
        "symfony/stopwatch": "6.3.*", // v6.3.0
        "symfony/web-profiler-bundle": "6.3.*", // v6.3.0
        "zenstruck/foundry": "^1.21" // v1.33.0