DEV Community

Cover image for Bridging Analog to Angular with esbuild and Vite
Brandon Roberts for Analog

Posted on

Bridging Analog to Angular with esbuild and Vite

It wasn't long ago that Analog went 1.0 bringing a stable full-stack meta-framework to the Angular community. One of the biggest conversations around Analog is the SFC and if its only available in Analog applications. There's good news, as this post covers how you can use Analog features in your existing Angular applications.

TL;DR GitHub Repo - https://github.com/brandonroberts/angular-esbuild-analog-example

If you've been outside of the Angular bubble and wondered how we got here, Josh Morony has a video on what started as the .ng file format for Angular, and how it evolved into the Analog SFC.

Customizing Esbuild in Angular Applications

Much has been said about Angular's migration from Webpack to esbuild and Vite, dramatically improving build performance and developer experience. We can take this a step further with a custom esbuild builder that allows additional features including esbuild plugins, middleware, and HTML transformers.

The Angular Devkit exposes these APIs for library authors to build on top of what Angular handles out of the box. Jenia "JeB" Barabanov has been maintaining custom Angular builders for Webpack and Jest for a while already, and introduced a custom builder for esbuild and the Vite dev server.

You can check out his repo at: https://github.com/just-jeb/angular-builders

With this custom esbuild builder, it opens up the door to add a custom esbuild plugin to support the Analog SFC.

Adding the Analog SFC to an Angular application

With the custom esbuild builder, you have the option to try the Analog SFC in an Angular 17.1+ application.

Note: The Analog SFC is still experimental!

With that out of the way, let's dive in!

First, you need to install the custom esbuild builder and Analog Vite plugin for Angular:

npm i -D @angular-builders/custom-esbuild @analogjs/vite-plugin-angular
Enter fullscreen mode Exit fullscreen mode

Next, you need to update the angular.json builder for build to use the new builder.

For the application builder:

- "builder": "@angular-devkit/build-angular:application",
+ "builder": "@angular-builders/custom-esbuild:application",
Enter fullscreen mode Exit fullscreen mode

For the dev-server builder:

- "builder": "@angular-devkit/build-angular:dev-server",
+ "builder": "@angular-builders/custom-esbuild:dev-server",
Enter fullscreen mode Exit fullscreen mode

Next, create an esbuild directory in the root of the application with two files.

├── esbuild
│   ├── plugins.js
│   ├── package.json
└── angular.json
Enter fullscreen mode Exit fullscreen mode

In the package.json, add { "type": "module" } and save it.

In the plugins.js, add the esbuild plugin for the Analog SFC.

import { analogSFC } from '@analogjs/vite-plugin-angular/esbuild';

export default [analogSFC()];
Enter fullscreen mode Exit fullscreen mode

In the options for the application builder, add a plugins array that includes the path to the esbuild/plugins.js file.

  "builder": "@angular-builders/custom-esbuild:application",
  "options": {
    "outputPath": "dist/angular-esbuild-analog-example",
    "scripts": [],
    "plugins": ["./esbuild/plugins.js"]
  },
Enter fullscreen mode Exit fullscreen mode

Next, create an analog.d.ts in the src directory with some type information.

interface ImportAttributes {
  analog: 'imports' | 'providers' | 'viewProviders' | 'exposes';
}

declare global {
  import type { Component } from '@angular/core';

  interface Window {
    /**
     * Define the metadata for the component.
     * @param metadata
     */
    defineMetadata: (
      metadata: Omit<
        Component,
        | 'template'
        | 'standalone'
        | 'changeDetection'
        | 'styles'
        | 'outputs'
        | 'inputs'
      >
    ) => void;

    /**
     * Invoke the callback when the component is initialized.
     */
    onInit: (initFn: () => void) => void;
    /**
     * Invoke the callback when the component is destroyed.
     */
    onDestroy: (destroyFn: () => void) => void;
  }
}

declare module '*.analog' {
  import { Type } from "@angular/core";

  const cmp: Type<any>;
  export default cmp;
}
Enter fullscreen mode Exit fullscreen mode

Now comes the fun part!

Create a hello.analog file in the src folder with a component.

<script lang="ts">
  // hello.analog
  import { signal } from '@angular/core';

  const count = signal(0);

  function add() {
    count.set(count() + 1);
  }
</script>

<template>
  <h2>
    Hello Angular from Analog SFC
  </h2>

  <p>
    {{count()}}
  </p>

  <button (click)="add()">Increment</button>
</template>

<style>
  h2 {
    color: blue;
  }
</style>
Enter fullscreen mode Exit fullscreen mode

Lastly, in the app.routes.ts file, define a route that navigates to the Hello component. Analog SFCs can be used in combination with the Angular Router!

import { Routes } from '@angular/router';
import Hello from './hello.analog';

export const routes: Routes = [
  { path: '', component: Hello }
];
Enter fullscreen mode Exit fullscreen mode

Run ng serve and navigate to the application running locally.

Analog SFC in Angular

Now ship to production! Just kidding 😉.

Analog SFCs are also interopable with Angular components, but must be enabled by the experimental-local compilationMode in the tsconfig.json

We've love for you to try out the SFC and give us your feedback. To find out more, check out the Analog SFC docs. Also join the community on Discord.

Using a custom esbuild builders opens up many possibilities to bring more Analog features to Angular applications. We're excited to explore more opportunities in the future. If you're also interested to learn more about how Analog and Vite work together with Angular, check out the Angular, Analog, and Vite post by Robin Goetz.

Join the Community 🥇

If you enjoyed this post, click the ❤️ so other people will see it. Follow AnalogJS and me on Twitter/X, and subscribe to my YouTube Channel for more content!

Top comments (3)

Collapse
 
jzubero profile image
Julian Scheuchenzuber

Tried to get this working in a fresh Angular Ionic app (v17.3.3). The builder setup seems not to be fully compatible yet. On ionic serve / ng run app:server I get:

[error] Error: Only the "application" and "browser-esbuild" builders support plugins.
Enter fullscreen mode Exit fullscreen mode

Excerpt from angular.json:

"serve": {
          "builder": "@angular-builders/custom-esbuild:dev-server",
          "configurations": {
            "production": {
              "buildTarget": "app:build:production"
            },
            "development": {
              "buildTarget": "app:build:development"
            },
            "ci": {
              "progress": false
            }
          },
          "defaultConfiguration": "development"
        }
Enter fullscreen mode Exit fullscreen mode

Am I doing something wrong?

Collapse
 
jangelodev profile image
João Angelo

Hi Brandon Roberts,
Your tips are very useful.
Thanks for sharing.

Collapse
 
mezieb profile image
Okoro chimezie bright

Thanks for sharing