Buy

Building Webpack Encore Assets

Our site is at least functional. Well any page that doesn't use the database is functional... like the about page. But it is super ugly. Oof. Why? It's simple! Our CSS file - build/styles.css is missing! Oooh, a mystery!

Our MooTube asset setup is pretty awesome: instead of having simple CSS files, we're getting sassy with Sass! And to build that into CSS, we're using an awesome library called Webpack Encore. Actually, we have a tutorial on Webpack... so go watch that if you're curious!

Basically, Webpack Encore is a Node executable: you run it from the command line, and it - in our simple app - transforms that Sass file into build/styles.css.

The reason styles.css wasn't deployed is that its directory - /web/build - is ignored in .gitignore!

22 lines .gitignore
... lines 1 - 20
/web/build

We need to do more work to deploy it.

Running assets:install

But before we get too far into that, there's one other little command that you often run during deployment:

bin/console assets:install --symlink

Actually, when you run composer install, this command is run automatically... so you may not even realize it's happening. And for deploy... well... you may or may not even need it! Here's the deal: sometimes, a bundle - usually a third-party bundle - will come with some CSS, JS or other public assets. Those files, of course, live in the vendor/ directory... which is a problem... because it means they're not publicly accessible. To make them public, we run this command. For each bundle that has public assets, it creates a symlink in the web/bundles/ directory.

For our app... yea... we don't have any bundles that do this! But, let's run that command on deploy to be safe. Open up after-symlink-shared.yml. Let's add a new task called "Install bundle assets":

45 lines ansible/deploy/after-symlink-shared.yml
... lines 1 - 14
- name: Warm up the cache
command: '{{ release_console_path }} cache:warmup --env=prod'
- name: Install bundle assets
... lines 19 - 45

Set this to run a command, and use the release_console_path variable that we setup in deploy.yml:

39 lines ansible/deploy.yml
---
- hosts: aws
... lines 3 - 14
vars:
release_console_path: "{{ ansistrano_release_path.stdout }}/bin/console"
... lines 17 - 39

Add assets:install --symlink and then --env=prod:

45 lines ansible/deploy/after-symlink-shared.yml
... lines 1 - 17
- name: Install bundle assets
command: '{{ release_console_path }} assets:install {{ release_web_path }} --symlink --no-debug --env=prod'
... lines 20 - 45

Tip

The --symlink is optional, and depending on your setup, you may need to not pass this flag

That's important: we need to run all of our console commands with --env=prod. Running commands in dev mode won't even work, because our require-dev Composer dependencies were never installed.

Perfect!

Installing Node Dependencies

Let's move on to the real task: building our assets on production. I'm not going to talk about Encore too much, but building the assets is a two-step process.

First, run:

yarn install

to download all of your Node dependencies. Basically, this reads package.json

8 lines package.json
{
"devDependencies": {
"@symfony/webpack-encore": "^0.12.0",
"node-sass": "^4.5.3",
"sass-loader": "^6.0.6"
}
}

And downloads that stuff into a node_modules/ directory. It's basically like Composer for Node.

Step 2 is to run Encore and build your assets. First, I'll clear out the build directory:

rm -rf web/build/*

Then, run:

./node_modules/.bin/encore production

Cool! Check it out! Yes! We still have styles.css, and it's beautifully minified.

Where to Build the Assets?

So how can we do this during deploy? Well... we have a few options. First, you could decide to run these commands locally and commit those files to your repository. That makes deploying easy... but you need to remember to run this command before each deploy. And committing built files to your repo is a bummer. This is an easy, but hacky way to handle things.

But... we do need to run these commands somewhere. The most obvious solution is actually to run these commands on your production server during deployment. This is what we're going to do... but it's also a bummer. It means that we will need to install Node on our production server... just to build these assets.

So, if you really don't like the idea of running these commands on production, you have a few other options. If you already have a build system of some sort, you could build your assets on that machine, upload them to S3 or something similar, then download them during deployment. Or, skip the downloading part, and update your script and link tags to point to S3 or some CDN.

A second option is to add a play to your deploy playbook that would first build those assets locally, before using the copy module to move them up to production.

Installing Node & Yarn

We're going to build the assets on production. This is not the best solution, but it's easy, and works great in most situations. The only big requirement is that we need to install Node and Yarn there. I'm going to open up my provision playbook: playbook.yml. Then, near the bottom, paste some tasks that do this:

171 lines ansible/playbook.yml
---
- hosts: webserver
... lines 3 - 33
tasks:
... lines 35 - 124
# Node
- name: Register NodeJS distribution
shell: 'curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -'
changed_when: false
- name: Install NodeJS
become: true
apt:
name: nodejs
state: latest
update_cache: yes
# Yarn
- name: Add Yarn APT key
become: true
apt_key:
url: 'https://dl.yarnpkg.com/debian/pubkey.gpg'
state: present
- name: Add Yarn to the source lists
become: true
lineinfile:
path: '/etc/apt/sources.list.d/yarn.list'
regexp: 'deb https://dl.yarnpkg.com/debian/ stable main'
line: 'deb https://dl.yarnpkg.com/debian/ stable main'
create: yes
- name: Install Yarn package manager
become: true
apt:
name: yarn
state: latest
update_cache: yes
... lines 158 - 171

If you're not using Ansible to provision your server, just install Node and yarn however you want. Let's get this running!

ansible-playbook ansible/playbook.yml -i ansible/hosts.ini --ask-vault-pass -l aws

Use beefpass for the password. Go go go!

Build the Assets on Deploy

While that's doing its magic, open after-symlink-shared.yml. We need to run 2 commands on deploy. Add a new task called "Install Node dependencies". Use command set to yarn install. Oh, and make sure this runs in our project directory: add args then chdir set to our handy ansistrano_release_path.stdout:

32 lines ansible/deploy/after-symlink-shared.yml
... lines 1 - 17
- name: Install bundle assets
command: '{{ release_console_path }} assets:install --symlink --env=prod'
- name: Install Node dependencies
command: yarn install
args:
chdir: '{{ ansistrano_release_path.stdout }}'
... lines 25 - 32

Copy that to make the second task: Install Webpack Encore assets. For the command, use ./node_modules/.bin/encore production:

37 lines ansible/deploy/after-symlink-shared.yml
... lines 1 - 20
- name: Install Node dependencies
command: yarn install
args:
chdir: '{{ ansistrano_release_path.stdout }}'
- name: Install Webpack Encore assets
command: './node_modules/.bin/encore production'
args:
chdir: '{{ ansistrano_release_path.stdout }}'
... lines 30 - 37

Ok, that's pretty easy! The annoying part is just needing to setup Node on your production server. Let's go back and check on the provision!

Oh boy... let's fast forward... ding!

Thanks to that, we should have Node and yarn up on the server. Let's deploy! Same command, but with deploy.yml, and we don't need the -l aws... this already only runs against the aws host:

ansible-playbook ansible/deploy.yml -i ansible/hosts.ini --ask-vault-pass

Use beefpass, deploy master and... hold your breath! Most of this will be the same... but watch those two new tasks. The Node dependencies will take some time to install at first.

Ok done! No errors. So... let's try it! Refresh! We have CSS! Yes!

We are finally ready to fix the database... and our poor homepage.

Leave a comment!