nestjs-ember-static
TypeScript icon, indicating that this package has built-in type declarations

0.2.4 • Public • Published

Description

The nestjs-ember-static package enhances the Nest framework to serve static Single Page Applications (SPAs) built with Ember. Drawing inspiration from @nestjs/serve-static, this module stands out by offering tailored features for Ember applications.

Why choose this over @nestjs/serve-static? Primarily for its support of live reloading and configuration injection. Designed to facilitate the integration of EmberJS applications within a NestJS mono repository, this module ensures that both front-end and back-end can be accessed through the same port. During development, it preserves the Ember application's live reloading capabilities. Moreover, it provides a feature where the back-end can inject configuration metadata directly into the generated index.html. This functionality makes it ideal for developers seeking a seamless development experience with combined Ember and NestJS applications.

Installation

$ pnpm install nestjs-ember-static

Usage

To incorporate the EmberModule into your Nest application, you can begin by editing your app.module.ts file. Below are detailed setups using both the EmberModule.forRoot and EmberModule.forRootAsync methods, along with an additional section on injecting custom configurations.

Basic Configuration with forRoot

For simpler setups where asynchronous operations and dynamic configurations are not required, you can use the forRoot method directly:

import { Module } from '@nestjs/common';
import { join } from 'path';
import { EmberModule } from 'nestjs-ember-static';

@Module({
  imports: [
    EmberModule.forRoot({
      rootPath: join(__dirname, '../frontend/dist'), // Adjust the path as necessary
    }),
  ],
})
export class AppModule {}

This configuration will statically serve your Ember SPA from the specified directory.

Advanced Asynchronous Configuration with forRootAsync

For more complex scenarios that require dynamic path resolution or other asynchronous setup procedures, you can utilize the forRootAsync method:

import { Module } from '@nestjs/common';
import { join } from 'path';
import { EmberModule } from 'nestjs-ember-static';
import { ConfigService } from '@nestjs/config'; // Ensure ConfigService is imported if not already

@Module({
  imports: [
    EmberModule.forRootAsync({
      useFactory: async (configService: ConfigService): Promise<EmberModuleOptions> => {
        const env = configService.get('env', { infer: true });
        return {
          rootPath:
            env['name'] === 'development'
              ? join(env['appRoot'], '../frontend/dist') // Development path
              : join(env['appRoot'], 'client'), // Production path
        };
      },
      inject: [ConfigService], // Inject ConfigService to use it in the factory function
    }),
  ],
})
export class AppModule {}

Additional Configuration Injection

For advanced configuration, such as dynamically setting properties or modifying the application title, you can add extra providers within the module. This enhances the module's flexibility and functionality, especially during development:

extraProviders: [
  {
    provide: EMBER_INJECTION_HELPERS,
    useFactory: (configService: ConfigService) => [
      new EmberConfigInjectionHelper('ember-app/config/environment', configService),
      new TitleInjectionHelper('Ember Application'),
    ],
    inject: [ConfigService],
  },
];

By following these configurations, you can effectively integrate and serve your Ember SPA within a NestJS application, leveraging both static and dynamic setup options.

Inject Helpers

To implement custom inject helpers for dynamic configuration, you need to define them by extending an abstract class. They get passes the rootElement from the html document (see node-html-parser for how to mutate that).

export abstract class AbstractInjectionHelper {
  public abstract process(htmlElement: HTMLElement): void;
}

Here's an example of how you can create your own inject helpers:

export class TitleInjectionHelper extends AbstractInjectionHelper {
  constructor(private title: string) {
    super();
  }

  public process(htmlElement: HTMLElement): void {
    htmlElement.querySelector('title')!.textContent = this.title;
  }
}`

Note: The InjectionHelpers given in this library are meant to be implemented by you. These are provided as examples to illustrate how you might structure your own helpers for configuration injection.

Adding Route Guards

The EmberModule supports route guards to control access to your Ember SPA.

To add a route guard, provide a guard into the EmberModule via the extraProviders:

import { Module } from '@nestjs/common';
import { join } from 'path';
import { EMBER_MODULE_GUARD, EmberModule } from 'nestjs-ember-static';
import { ConfigService } from '@nestjs/config';
import { MyCustomGuard } from './guards/my-custom.guard';

@Module({
  imports: [
    EmberModule.forRootAsync({
      extraProviders: [
        {
          provide: EMBER_INJECTION_HELPERS,
          useFactory: (configService: ConfigService) => [
            new EmberClientConfigInjector('@3dlayermaker/device-controller-frontend/config/environment', configService),
          ],
          inject: [ConfigService],
        },
        {
          provide: EMBER_MODULE_GUARD,
          useClass: ControllerApiAuthGuard,
        },
      ],
    }),
  ],
})
export class AppModule {}

The guard will be applied to all routes handled by the EmberModule. The guard should implement the CanActivate interface, and you can control access by implementing the canActivate method.

Development mode

While developing, I typically run both the NestJS application and the EmberJS application simultaneously. My package.json includes a script like:

"start:dev": "concurrently --kill-others 'cd apps/frontend && pnpm run start' 'cd apps/backend && pnpm run start:debug'"

In production, I build the EmberJS app and store it in the client directory of the NestJS application. This setup allows me to use a dynamic rootPath as shown in the example above. In development mode (when the environment variable NODE_ENV is set to 'development'), any request to /ember-cli-live-reload.js on the NestJS application is forwarded to the same route on the running Ember application. In production, this route will return a 404.

Backing frameworks Express and Fastify

The nestjs-ember-static module supports both Express and Fastify as backing frameworks. This flexibility allows you to choose the framework that best suits your application's needs and preferences. Whether you are using the default Express or have switched to Fastify for improved performance, the nestjs-ember-static module integrates seamlessly with both, ensuring consistent functionality and easy setup.

However, it's important to note that Fastify has some limitations. For more details on these limitations, refer to the @nestjs/serve-static module documentation. This will help you understand any potential constraints and how they might impact your application when using Fastify as the backing framework.

Additional Options

The EmberModule utilizes the same options as @nestjs/serve-static with the addition of the following:

Property Type Description
liveReloadPort number The port the /ember-cli-live-reload.js resource is served on by the Ember app in development. Default: 4200

These options provide additional flexibility and control when serving your Ember SPA with the NestJS framework.

Support

Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please read more here.

Stay in touch

License

As this is based on serve-static, this module is also licensed under the MIT license.

Readme

Keywords

none

Package Sidebar

Install

npm i nestjs-ember-static

Weekly Downloads

25

Version

0.2.4

License

MIT

Unpacked Size

214 kB

Total Files

55

Last publish

Collaborators

  • bushbaby