Symfony on a lambda: assets
Tags: php, symfony, lambda, serverless
Published: 2019-11-18 10:48:32

---
title: Symfony on a lambda: assets
published: true
description: Let's add assets to our application
tags: PHP, Symfony, Lambda, Serverless
series: Symfony on a lambda
canonical_url: https://julesmatsounga.com/en/article/symfony-lambda-chapter-2
---

# Symfony on a lambda: assets

If you are lost, you can find the project on Github: [link](https://github.com/hyoa/symfony-on-lambda)
 Each branch will match a chapter.

### Webpack Encore

We saw in the previous chapter how to deploy our application on a lambda. We now have a site that is working on AWS, but it doesn't have any design.

Symfony offer to its developer **Webpack Encore** that package CSS and JS easely without touching the **Webpack** configuration.

It is really easy to install, you just have to type `composer require symfony/webpack-encore-bundle` then `npm install`.
This two commands will add the required files and dependencies.

The most important file here is **webpack-config.js** that you can find at the root of your project. Its define tasks performed by **Webpack** when you will run it (the location of your assets, the destination of the builded files, if there is versioning and much more).

### First asset

When we installed **Webpack Encore** it has also created an **app.js** and a **app.css** in the folder **assets**.
We have to import them in the twig !

Our **templates/home/index.html.twig** herits from the **base** template. We will import the assets in this one.

In **templates/base.html.twig**, we will add:

```twig
<head>
	...
  {% block stylesheets %}
    {{ encore_entry_link_tags('app') }}
  {% endblock %}
</head>	
<body>
	...
  {% block javascripts %}
    {{ encore_entry_script_tags('app') }}
  {% endblock %}
</block>
```

This blocks will import the assets that Webpack will have package.

We can now run the command to launch Webpack: `npm run build`.
If you now look in  **public/build**, you will see your assets created and a **manifest.json** and also an **entrypoints.json**.

> If you look closely, you will see that your **app.js** and **app.css** have a hash in their file name. That what we called versioning.
> To improve user experience, browsers cache websites assets (js, css, images, videos, ...) to avoid that the user download them again.
> The cache is, among others, based on the file name. So by changing the file name at each build, we ensure that the browser download the latest version of our assets !

Now, if you launch your application (`php bin/console server:run`), you will see modification (a grey background and *Hello Webpack Encore! Edit me in assets/js/app.js* in the console).

**Webpack Encore** put at your disposal different commands:

* **build**: create your production assets with versioning
* **dev**: create your development assets
* **watch**: look the at the modification in assets file and rebuild when necessary
* **dev-server**: used for hot reloading. It refresh your browser when a change is detected in your js files.

### Tailwindcss

Like a majority of sites, we need some visual customization. Here, we won't write a lot of CSS but work with  [Tailwindcss](https://tailwindcss.com/) instead. It is a CSS framework based on **utility-first**. Its mean that we add class on our html element to stylise them. It can look a bit tedious but it is really easy to use.

#### Installation

* Run `npm install tailwindcss`

* Then we need to create an **app.scss** in **assets/scss** and add the following lines:

  ```scss
  @tailwind base;
  
  @tailwind components;
  
  @tailwind utilities;
  ```

* Change `require('../css/app.css')`by `require('../scss/app.scss');` in **assets/js/app.js**

* Create a **postcss.config.js** file at the root of your project with the following configuration:

  ```js
  module.exports = {
    plugins: [
      require('tailwindcss'),
    ]
  };
  ```

* In **webpack.config.js**, add `enablePostCssLoader()` and `enableSassLoader()`:

  ```js
  Encore
  		//...
      .enablePostCssLoader()
      .enableSassLoader(function (options) {}, {
        resolveUrlLoader: false
      })
  ;
  ```

* Run `npm install postcss-loader@^3.0.0 --save-dev`and `npm install sass-loader@^7.0.1 node-sass --save-dev`

* Relaunch **Webpack Encore** (`npm run dev-server` or `npm run watch` ou `npm run dev`)

Its look like we installed a lot of thing but there is not so much to explain. It's the requirements to run Tailwindcss on our project.

#### Let's design

As I explained, Tailwindcss is utility-first, that mean we will use utility class that define the css that will be applied to our elements. The Tailwindcss documentations is impressive and has everything you need to work.

We will create the scss file of our page: **assets/scss/components/landing.scss**

```scss
#landing {
  #landing-background {
    background-color: #2b6cb0;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='152' height='152' viewBox='0 0 152 152'%3E%3Cg fill-rule='evenodd'%3E%3Cg id='temple' fill='%23aee4e0' fill-opacity='0.4'%3E%3Cpath d='M152 150v2H0v-2h28v-8H8v-20H0v-2h8V80h42v20h20v42H30v8h90v-8H80v-42h20V80h42v40h8V30h-8v40h-42V50H80V8h40V0h2v8h20v20h8V0h2v150zm-2 0v-28h-8v20h-20v8h28zM82 30v18h18V30H82zm20 18h20v20h18V30h-20V10H82v18h20v20zm0 2v18h18V50h-18zm20-22h18V10h-18v18zm-54 92v-18H50v18h18zm-20-18H28V82H10v38h20v20h38v-18H48v-20zm0-2V82H30v18h18zm-20 22H10v18h18v-18zm54 0v18h38v-20h20V82h-18v20h-20v20H82zm18-20H82v18h18v-18zm2-2h18V82h-18v18zm20 40v-18h18v18h-18zM30 0h-2v8H8v20H0v2h8v40h42V50h20V8H30V0zm20 48h18V30H50v18zm18-20H48v20H28v20H10V30h20V10h38v18zM30 50h18v18H30V50zm-2-40H10v18h18V10z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
    height: 50vh;
  }
}
```

Then will will update our template **templates/index.html.twig**

```twig
{% extends 'base.html.twig' %}

{% block body %}
    <main id="landing" class="w-screen h-screen bg-blue-100">
        <div id="landing-background">
        </div>
        <section class="w-1/2 mx-auto -mt-10 bg-white shadow-md py-2 text-center rounded">
            <h1 class="text-4xl">Freelancer Tools</h1>
            <p>
                Enhance your productivity with some tools !
            </p>
        </section>
        <section class="w-1/2 flex mx-auto">
            <section class="w-1/2 p-4">
                <div class="border shadow bg-white rounded">
                    <header class="text-2xl text-center mb-2 pt-2 bg-blue-600 text-white rounded-t">
                        <h3>FREE</h3>
                    </header>
                    <main>
                       <ul class="px-4 text-center">
                           <li class="border-b border-gray-400 py-2">Manage your clients</li>
                           <li class="py-2">Create invoice</li>
                       </ul>
                    </main>
                </div>
            </section>
            <section class="w-1/2 p-4">
                <div class="border shadow bg-white rounded">
                    <header class="text-2xl text-center mb-2 pt-2 bg-blue-600 text-white rounded-t">
                        <h3>PREMIUM</h3>
                    </header>
                    <main>
                        <ul class="px-4 text-center">
                            <li class="border-b border-gray-400 py-2">All free features</li>
                            <li class="border-b border-gray-400 py-2">Send emails</li>
                            <li class="py-2">Notifications</li>
                        </ul>
                    </main>
                </div>
            </section>
        </section>
    </main>
{% endblock %}
```

I let you change this file as your need to have something with a better look :p

### On the cloud

We now know how to build our asset, but we need them available on the cloud. We can't let them on the lambda, it is not their purpose to deliver assets. AWS has a service designed for that: **AWS S3**.

In a first place, we need to create a Bucket S3. Then we will be able to upload our assets on it and have access from a browser.

#### Bucket creation

We still wont use the AWS ui to create our bucket but use the **serverless.yml** instead.

```yaml
//serverless.yml
service: [name of your project]

provider:
  // ...
custom:
  prefix: ${self:service}-${self:provider.region}-${self:provider.stage} #Prefix that change depending of the service name, the region where is deployed the application and the environment.
plugins:
  // ...

functions:
  // ...
	
resources:
  Resources:
    Assets: #Name of the resource on CloudFormation
      Type: AWS::S3::Bucket #Type of the resource
      Properties:
        BucketName: ${self:custom.prefix}-assets
    AssetsPolicy:
      Type: AWS::S3::BucketPolicy
      Properties:
        Bucket: !Ref Assets #Reference of the bucket above
        PolicyDocument:
          Statement:
            - Effect: Allow
              Principal: '*'
              Action: 's3:GetObject' #Only read right
              Resource: arn:aws:s3:::${self:custom.prefix}-assets/* #arn of the bucket
```

This modification allow us to define a **S3 Bucket** totally public on which we will be able to read documents that are inside.

Let's run `serverless deploy` to launch the creation.

#### Push assets

Now that our bucket is created, we need to deploy the assets on it. We wont do it manually, **aws-cli** provide a command to do it.

We need to run `npm run build` then `aws s3 sync public/build s3://[bucket name]`.

> To obtain the name of the bucket, you can run `aws s3 ls` that will display all the bucket on your account. You  just have to pick the right one.

#### Link the assets

But it is not done. We know have to tell to Symfony the location of our assets, because by default it use the current domain as a path.

We will update **webpack.config.json** to change the public path when we run a production build. The public path is the url of our bucket.

```js
// ...

if (Encore.isProduction()) {
  Encore.setPublicPath('https://[name of bucket].s3.eu-west-2.amazonaws.com');
  Encore.setManifestKeyPrefix('build/');
}

module.exports = Encore.getWebpackConfig();
```

Now, if you run `npm run build` and `aws s3 sync public/build s3://[name of bucket]`, you should see on your local application (in the network tab of the console) that the assets are pulled directly from the S3.

Everything is ready, we can run again all the commands to see the change online:

`npm run build && aws s3 sync public/build s3://[nom du bucket] && serverless deploy`

We now have functional assets on the cloud !

#### Going further

Lambda have a limited space, so we shouldn't push what it is not required on it. We can specify exclude files and directories in **serverless.yml**.

```yaml
//serverless.yml

service: [nom du projet]

package:
    exclude:
        - node_modules/**
        - var/log/**
        - public/build/**
        - var/cache/**
        - '!var/cache/prod/**' #We want to keep the warmup of Symfony
        - assets/**
        - tests/**
    include:
        - public/build/manifest.json
        - public/build/entrypoints.json
```

Ignoring this files reduce the size of our package.

It is also recommended to ignore dev dependencies by running `composer install --optimize-autoloader --no-dev` but this command will break your development environment. A good practice is to run this command on a other directory. We will talk about this in an other chapter !

***

We now know how to work with assets. We will see in the future chapter how to manage sessions and connections !