Symfony on a lambda: assets

Posted on

In this article we will learn about some of the frequently asked Php programming questions in technical like Symfony on a lambda: assets. When creating scripts and web applications, error handling is an important part. If your code lacks error checking code, your program may look very unprofessional and you may be open to security risks. Error handling in PHP is simple. An error message with filename, line number and a message describing the error is sent to the browser. This tutorial contains some of the most common error checking methods in PHP. Below are some solution about Symfony on a lambda: assets.



Symfony on a lambda: assets

If you are lost, you can find the project on Github: link
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:

<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 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:

  @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:

  module.exports = {
    plugins: [
      require('tailwindcss'),
    ]
  };
  • In webpack.config.js, add enablePostCssLoader() and enableSassLoader():
  Encore
        //...
      .enablePostCssLoader()
      .enableSassLoader(function (options) {}, {
        resolveUrlLoader: false
      })
  ;
  • Run npm install postcss-loader@^3.0.0 --save-devand 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

#landing {
  #landing-background {
    background-color: #2b6cb0;
    background-image: url("data:image/svg+xml,%3Csvg  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

{% 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.

//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.

// ...

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.

//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 !

Leave a Reply

Your email address will not be published. Required fields are marked *