Symfony & ViteJS video tutorial: Using ViteJS with Symfony


In this video I suggest you discover how to integrate ViteJS in a Symfony project.

Why ?

Before even going any further, we may wonder the interest of using ViteJS on Symfony, especially since it already offers asset management via Symfony Encore.
ViteJS has several advantages:

  • A faster development server than the one offered by Webpack with hot reload and fast refresh support in the case of React.
  • The configuration is simpler for the most common needs and you will have control over the configuration if you have more specific needs (for example, we will create a plugin to update the page when the branch files change).

Also, this exercise will allow you to discover how the import of assets works and to adapt it to other tools.

How? 'Or' What ?

We are going to create a branch function which will allow loading a particular asset by adapting the path according to the environment.

  • In development, the assets will be retrieved from the Vite development server: http: // localhost: 3000 / assets / main.jsx.
  • In production, assets will have a hash (for cache invalidation) and should be retrieved from the public folder: /assets/main.jsx.13NJ13U04N.js

It is this second case which is often problematic because it is necessary to be able to recover the names of the compiled files. Fortunately ViteJS allows the generation of a file manifest.json which will contain the list of our different entry points and the generated files.

// Example of manifest.json file
{
  "main.jsx": {
    "file": "main.jsx.0312cc1b.js",
    "src": "main.jsx",
    "isEntry": true,
    "dynamicImports": (
      "demo.js"
    ),
    "css": (
      "main.jsx.35ea8056.css"
    ),
    "assets": (
      "logo.ecc203fb.svg"
    )
  },
  "demo.js": {
    "file": "demo.441114fc.js",
    "src": "demo.js",
    "isDynamicEntry": true
  }
}

It will therefore be necessary to read and parse this file to obtain the path to the CSS / JS files (and use the cache to avoid repeating this step on each load).

Some code

First of all, it will be necessary to adapt the configuration of ViteJS to the architecture of Symfony

import {defineConfig} from 'quickly'
import reactRefresh from '@ vitejs / plugin-react-refresh' // Specific to react
import {resolve} from 'path'

// (optional) This plugin allows you to launch a refresh of the page when modifying a branch file
const twigRefreshPlugin = {
  name: 'twig-refresh',
  configureServer ({watcher, ws}) {
    watcher.add (resolve ('templates / ** / *. twig'))
    watcher.on ('change', function (path) {
      if (path.endsWith ('. twig')) {
        ws.send ({
          type: 'full-reload'
        })
      }
    })
  }
}

// https://vitejs.dev/config/
export default defineConfig ({
  plugins: (reactRefresh (), twigRefreshPlugin),
  root: './assets',
  base: '/ assets /',
  server: {
    watch: {
      disableGlobbing: false, // required for the twig plugin
    }
  },
  build: {
    manifest: true,
    assetsDir: '',
    outDir: '../public/assets/',
    rollupOptions: {
      output: {
        manualChunks: undefined // We don't want to create a vendors file, because we only have an entry point here
      },
      input: {
        'main.jsx': './assets/main.jsx'
      }
    }
  }
})

Then we will register the function to use in Twig:

  ('html')))
        );
    }

    public function asset (string $ entry, array $ deps)
    {
        if ($ this-> isDev) {
            return $ this-> assetDev ($ entry, $ deps);
        }
        return $ this-> assetProd ($ entry);
    }

    public function assetDev (string $ entry, array $ deps): string
    {
        $ html = <<< HTML

HTML;
        if (in_array ('react', $ deps)) {
            $ html. = '';
        }
        $ html. = <<< HTML

HTML;
        return $ html;
    }

    public function assetProd (string $ entry): string
    {
        if ($ this-> manifestData === null) {
            $ item = $ this-> cache-> getItem (self :: CACHE_KEY);
            if ($ item-> isHit ()) {
                $ this-> manifestData = $ item-> get ();
            } else {
                $ this-> manifestData = json_decode (file_get_contents ($ this-> manifest), true);
                $ item-> set ($ this-> manifestData);
                $ this-> cache-> save ($ item);
            }
        }
        $ file = $ this-> manifestData ($ entry) ('file');
        $ css = $ this-> manifestData ($ entry) ('css') ?? ();
        $ imports = $ this-> manifestData ($ entry) ('imports') ?? ();
        $ html = <<< HTML

HTML;
        foreach ($ css as $ cssFile) {
            $ html. = <<< HTML

HTML;
        }

        foreach ($ imports as $ import) {
            $ html. = <<< HTML

HTML;
        }

        return $ html;
    }

}

Finally, it will be necessary to register the service in order to pass it the necessary arguments.

services:

    # ....

    App  Twig  ViteAssetExtension:
        arguments:
            $ isDev: '% env (VITE_DEV)%'
            $ manifest: '% kernel.project_dir% / public / assets / manifest.json'
            $ cache: '@vite_cache_pool'

We will use here an environment variable to activate the use of the development server.