Building for Production

I love our new setup! So it's time to talk about optimizing our build files for production. Yep, it's time to get serious, and make sure our files are minified and optimized to kick some performance butt!

Because, right now, if you check out the size of the build directory:

ls -la public/build

... yea! These files are pretty huge - rep_log.js is over 1 megabyte and so is layout.js! If you looked inside, you would find the problem immediately:

24 lines assets/js/login.js
... lines 1 - 2
import $ from 'jquery';
... lines 4 - 24

jQuery is packaged individually inside each of these! That's super wasteful! Our users should only need to download jQuery one time.

The Shared Entry

No problem! Webpack has an awesome solution. Open webpack.config.js. Move the layout entry to the top - though, order doesn't matter. Now, change the method to createSharedEntry():

30 lines webpack.config.js
... lines 1 - 3
Encore
... lines 5 - 10
.createSharedEntry('layout', './assets/js/layout.js')
... lines 12 - 25
;
... lines 27 - 30

Before we talk about this, move back to your terminal and restart Encore:

yarn run encore dev --watch

Then, I'll open a new tab - I love tabs! - and, when it finishes, check the file sizes again:

ls -la public/build

Woh! rep_log.js is down from 1 megabyte to 300kb! layout.js is still big because it does still contain jQuery. But login.js - which was almost 800kb is now... 4!

What is this magical shared entry!? To slightly over-simplify it, each project should have exactly one shared entry. And its JS file and CSS file should be included on every page.

When you set layout.js as a shared entry, any modules included in layout.js are not repeated in other files. For example, when Webpack sees that jquery is required by login.js, it says:

Hold on! jquery is already included in layout.js - the shared entry. So, I don't need to also put it in login.js.

It's a great solution to the duplication problem: if you have a library that is commonly used, just make sure that you import it in layout.js, even if you don't need it there. You can experiment with the right balance.

The manifest.js File

As soon as you do this, if you refresh, it works! I'm kidding - you'll totally get an error:

webpackJsonp is not defined

To fix that, in your base layout, right before layout.js, add one more script tag. Point it to a new build/manifest.js file:

107 lines templates/base.html.twig
... lines 1 - 96
{% block javascripts %}
... lines 98 - 100
<script src="{{ asset('build/manifest.js') }}"></script>
<script src="{{ asset('build/layout.js') }}"></script>
{% endblock %}
... lines 104 - 107

The reason we need to do this is... well.. a bit technical. But basically, this helps with long-term caching, because it allows your giant layout.js file to change less often between deploys.

Production Build

Ok, this is great, but the files are still pretty big because they're not being minified. How can we tell Encore to do that? In your terminal, run:

yarn run encore production

That's it! This will take a bit longer: there's more magic happening behind the scenes. When it finishes, go back to your first open tab and run:

ls -la public/build

Let's check out the file sizes! The development rep_log.js that was 310kb is down to 74! Layout went from about 1Mb to 125kb. The CSS files are also way smaller. Yep, building for production is just one command: Encore handles all the details.

Adding Shortcut scripts

Oh, and here's a trick to be even lazier. Open package.json. I'm going to paste a new script section:

20 lines package.json
{
"devDependencies": {
... lines 3 - 11
},
"scripts": {
"dev-server": "encore dev-server",
"dev": "encore dev",
"watch": "encore dev --watch",
"build": "encore production"
}
}

This gives you different shortcut commands for the different ways that you'll run Encore. Oh, we didn't talk about the dev-server, but it's another option for local development.

Anyways, now, in the terminal, we can just say:

yarn watch

Or any of the other script commands - like yarn build for production.

How to Deploy

Talking about production, there's one last big question we need to answer: how the heck do you deploy your assets to production? Do we need to install Node on the production server?

The answer is.... it depends. It depends on how sophisticated your deployment system is. Honestly, if you have a very simple deploy system - like a simple script, or maybe even some commands you run manually - then the easiest option is to install Node and yarn on your server and run encore production on your server after pulling down the latest files.

I know: this isn't a great solution: it's a bummer to install Node just for this reason. But, it is a valid option and totally simple.

A better solution is to run Encore on a different machine and then send the final, built files to your server. This highlights an important point: after you execute Encore, 100% of the files you need live in public/build. So, for example, after you execute:

yarn run encore production

you could send the public/build directory to your production machine and it would work perfectly. If you have a "build" server, that's a great place to run this command. Or, if you watched our Ansistrano Tutorial, you could run Encore locally, and use the copy module to deploy those files.

If you have any questions on your specific situation, you can ask us in the comments.

Leave a comment!

  • 2018-06-04 Victor Bocharsky

    Hey Matt,

    So there're a few strategies:
    - Deploy changes to prod and build assets on the prod server, such tools as Ansistrano allow you to do so in a background, i.e. your website won't be totally style-broken when you re-build your assets.
    - Or build assets locally and just push them to production, so you even do not need to have NodeJS installed on production. And once again, Ansistrano handles it well.

    In case your wondering about Ansistrano deploy tool, we have a screencast about it here: https://knpuniversity.com/s...

    > What about just committing your build files?

    That's another, 3rd and good strategy... but have some disadvantages, but mostly depends on your configuration. Here's a one of them which I do not like a lot: If you use a hash strategy to solve the cache assets problem (like file's names as main_9foiwhf.js), each build the same files will have a totally different names which means Git unable to manually understand that it was just renaming, and Git will think you removed old file and created a totally new file, and as a result, your Git repository will be slowly bloated. Btw, sometimes you want to handle some images with Encore which will copy to the build directory that also will bloat your Git repo.

    So, I think 3rd strategy is not a good one, so I'd recommend to choose between those 2 I mentioned in the beginning.

    Cheers!

  • 2018-06-03 Matt Johnson

    That sucks about prod... I wonder if you could use something like salt to build it on the master and then push it to the minion when you deploy.

    What about just committing your build files?

  • 2018-05-22 Victor Bocharsky

    Hey Cesar,

    Glad you find a working solution for you. And actually many devs lean to this deploy strategy because do not want to install NodeJS on their production server at all. So I even do not consider this as a workaround. Anyway, if it works for you - that's awesome!

    Cheers!

  • 2018-05-21 Cesar Delgado

    Thanks Victor. I didn't want to increase the memory because is a server of pre-production. I have followed your second recommendation and it's working well and the deploy it's much faster.

    Cheers!

  • 2018-05-21 Andrea

    Ok, now i've understood. Thanks

  • 2018-05-21 Victor Bocharsky

    Hey Andrea,

    Ah, sorry, I totally missed it! Yes, you're right, we do have manifest.js file here due to the shared entry. So, did you change the line for layout.js to:


    Encore
    .createSharedEntry('layout', './assets/js/layout.js')

    Keep in mind, you don't have to have any other line related to "layout.js" except this one in your webpack.config.js.

    And another important condition: You need to restart the Encore, i.e. stop watching files and re-run that command again:

    yarn run encore dev --watch

    Unless you restart the Encore, you won't see that manifest.js file in the build/ directory.

    Cheers!

  • 2018-05-21 Andrea

    I'm talking about the file named in this tutorial, this is the link: https://knpuniversity.com/s...
    Maybe i missed something?

  • 2018-05-21 Victor Bocharsky

    Hey Andrea,

    OK, what is manifest.js then? :) Because IIRC we do not talk about manifest.js file but manifest.json instead. Does it your custom JS file? If so, probably some name conflicts? Could you try to rename this file into a different file name?

    Cheers!

  • 2018-05-21 Victor Bocharsky

    Hey Cesar,

    OK, great job to find the root of your problem, well done! Well, I haven't heard about this problem before, so nothing much I can tell you about solutions, probably better to google for a good advices in your case. But if you won't find any working solution on the internet I do see a few solutions for you:

    - Increase memory, looks like you really need just a bit more memory to make it working;
    - Or stop installing deps and building assets on your prod server. Instead, install and build them locally, then just use synchronize Ansible module to *rsync* the built assets to your prod server. This way you even do not need to have NodeJS on your server

    Cheers!

  • 2018-05-21 Cesar Delgado

    Hi Victor,

    It's not working from Ansible in any way. Neither with command, shell or via nodes that you told me.

    But, I was trying to do it manually in the current directory in my server and sometimes it didn't work and it was giving me an exit code 137. I was looking in the internet about that and, apparently, it's related to memory. However, I don't understand how is that possible because I check my server and it was fine in terms of memory and I am trying in a pre-production environment.

    Have you ever had this error?

    Cesar

  • 2018-05-18 Andrea

    Sorry, i'm talking about manifest.js and not manifest.json (the second one is correctly inside the build folder, the first one not.).

  • 2018-05-18 Diego Aguiar

    Hey Andrea

    The "manifest.json" file is only generated when you execute Encore, when the process finishes, then you will see all your assets and the manifest file inside your build folder

    Cheers!

  • 2018-05-18 Victor Bocharsky

    Hey Cesar,

    Hm, your task looks valid, let's try to change it slightly, try to run encore via "nodejs":


    - name: Install Webpack Encore Assets
    command: nodejs ./node_modules/.bin/encore production
    args:
    chdir: '{{ ansistrano_release_path.stdout }}'
    environment:
    NODE_ENV: production

    And probably setting NODE_ENV env var could help here. If not, try to change command module to shell one and try again.

    Cheers!

  • 2018-05-18 Andrea

    I'm using webpack encore in my new project, everything going fine but i don't find any manifest.js in my build folder.

    Actually, my console not notifing me the webpackJsonp error but i'm not sure about that.

    Is it normal?
    Is this file generate by encore only in certain situation or i missing something?

    Thanks in advance

  • 2018-05-17 Cesar Delgado

    Hey Victor,

    This is task and it's located in after_symlink_shared.yml


    - name: Install Webpack Encore Assets
    command: './node_modules/.bin/encore production'
    args:
    chdir: '{{ ansistrano_release_path.stdout }}'


    Your proposal is to change command for shell?

    Please, let me know.

  • 2018-05-17 Victor Bocharsky

    Hey Cesar,

    Hm, 0 is good here, ok, could you show this entire Ansible task to us? Probably the problem in it. Also, do you use command Ansible module? If so, try to replace it with shell module and try again. Or do the reverse if you use shell module.

    Cheers!

  • 2018-05-16 Cesar Delgado

    Hi Victor,

    I was working with ansible 2.4 and I had the error. Now, I upgrade to ansible 2.5 and I have the same error.

    I run the command manually in my server and it works fine (I run it in the current directory). Also, it returns 0.

    Do you have any idea what the problem can be?

    Cesar

  • 2018-05-16 Victor Bocharsky

    Hey Cesar,

    I think somehow the command returns not a 0 code that's why Ansible thinks it fails... Could you double check it? On the prod server run the command manually and then check its status:


    ./node_modules/.bin/encore production
    echo $?

    Does it return 0 or a different number? Because that's weird that you can run this command successfully manually, but not with Ansible :/

    Btw, had you upgraded your Ansible before that error occurred?

    Cheers!

  • 2018-05-15 Cesar Delgado

    Hi guys, I am using Ansistrano to deploy my Symfony 3 app and my deploy playbook was working fine until today that I had an weird error executing the task Install Webpack Encore Assets.

    The error is this: FAILED! => {"changed": true, "cmd": ["./node_modules/.bin/encore", "production"], "delta": "0:02:22.350866", "end": "2018-05-15 18:16:34.750211", "failed": true, "msg": "non-zero return code", "rc": -9, "start": "2018-05-15 18:14:12.399345", "stderr": "", "stderr_lines": [], "stdout": "Running webpack ...", "stdout_lines": ["Running webpack ..."]}

    I didn't find anything related to this in Google and my app is working in localhost. Also, I have tried to execute the command './node_modules/.bin/encore production' directly on my server and is working. I didn't make any changes to my webpackconfig.js. So, I don't know what this can be. I have tried many times to deploy and the same error is still there.

    Can you give me a tip or some advice to fix this? I hope you can help me.

  • 2018-04-24 weaverryan

    Haha, cheers buddy! :)

  • 2018-04-23 Dan Meigs

    Hey guys!

    Thanks for another great tutorial. Webpack Encore is awesome!