From 2c2de31ba9629d69dc0442bc1eca7e325c8b3aca Mon Sep 17 00:00:00 2001 From: Dwayne Charrington Date: Mon, 23 Dec 2024 11:03:19 +1100 Subject: [PATCH] feat(blog): common migration gotchas --- .../2024/12/23/common-migration-gotchas.md | 732 ++++++++++++++++++ 1 file changed, 732 insertions(+) create mode 100644 content/blog/2024/12/23/common-migration-gotchas.md diff --git a/content/blog/2024/12/23/common-migration-gotchas.md b/content/blog/2024/12/23/common-migration-gotchas.md new file mode 100644 index 0000000..f80d7f6 --- /dev/null +++ b/content/blog/2024/12/23/common-migration-gotchas.md @@ -0,0 +1,732 @@ ++++ +title = "Common Migration Gotchas: Aurelia 1 to Aurelia 2" +authors = ["Dwayne Charrington"] +description = "Common migration challenges and how to overcome them when upgrading from Aurelia 1 to Aurelia 2." +date = 2024-12-23T10:00:00Z +lastmod = 2024-12-23T10:00:00Z +tags = ["migration", "aurelia2"] ++++ + +Migrating to Aurelia 2 offers a wealth of improvements but also introduces significant changes that can trip up even experienced Aurelia developers. This guide focuses on the nuances of Aurelia 2's registration system, routing, dependency injection (DI), and plugin ecosystem, as well as tackling other migration hurdles. + +--- + +## 🔄 Registration and App Bootstrapping + +The most noticeable change in Aurelia 2 is how applications are bootstrapped. Gone are the days of the `aurelia-app` attribute with no value defaulting to standard configuration. Instead, Aurelia 2 uses a more explicit registration system: + +Instead of chaining methods such as `plugin` or `feature`, you now explicitly register components, services, and plugins using native ES modules. + +**Aurelia 1 Approach** + +```typescript +export function configure(aurelia: Aurelia): void { + aurelia.use + .standardConfiguration() + .globalResources(PLATFORM.moduleName('./global-component')) + .plugin(PLATFORM.moduleName('some-plugin')) + .feature('./my-feature'); + + aurelia.start().then(() => aurelia.setRoot(PLATFORM.moduleName('app'))); +} +``` + +**Aurelia 2 Approach** + +```typescript +import { MyGlobalComponent } from './global-component'; +import { SomePlugin } from 'some-plugin'; + +Aurelia + .register(MyGlobalComponent, SomePlugin) + .app(MyApp) + .start(); +``` + +The biggest change in Aurelia 2 is how integrations work. String-based conventions are replaced with native ES modules, and there's no distinction between resources, plugins, and features - everything is just a dependency that can be registered to a container. + +### Aurelia 1 Plugin/Feature Pattern + +```typescript +// producer/index.ts +export function configure(config: FrameworkConfiguration) { + config.globalResources(['./my-component', './my-component-2']); +} + +// main.ts (consumer) +aurelia.use + .plugin('producer') // Using as plugin + .feature('./producer'); // Using as feature +``` + +#### Aurelia 2 Integration Pattern + +```typescript +// producer/index.ts +import { IContainer } from 'aurelia'; +import { MyComponent } from './my-component'; +import { MyComponent2 } from './my-component-2'; + +export const Producer = { + register(container: IContainer) { + container.register( + MyComponent, + MyComponent2 + ); + }, +}; + +// main.ts (consumer) +Aurelia.register(Producer); +``` + +#### Key Changes +- Components registered with `Aurelia.register()` are globally accessible +- `PLATFORM.moduleName` is no longer needed for bundling +- Use direct ES module imports instead of string-based module names +- No distinction between plugins, features, and resources +- Registration is more explicit and type-safe +- Container-based registration provides more flexibility and control + +--- + +## 💉 Dependency Injection: No More Auto-Injection + +In Aurelia 1, the `@autoinject` decorator was used to automatically inject dependencies into a class. This is no longer supported in Aurelia 2. Instead, you need to explicitly inject dependencies using the `resolve` function or the `@inject` decorator. + +**Aurelia 1 Approach** + +```typescript +import { autoinject } from 'aurelia-framework'; + +@autoinject +export class MyComponent { + constructor(private readonly myService: MyService) { + this.myService.doSomething(); + } +} +``` + +**Aurelia 2 Approach** + +```typescript +import { resolve } from 'aurelia'; + +export class MyComponent { + private readonly myService = resolve(MyService); + + constructor() { + this.myService.doSomething(); + } +} +``` + +You can also use `resolve` inline with the constructor: + +```typescript +export class MyComponent { + constructor(private readonly myService = resolve(MyService)) { + this.myService.doSomething(); + } +} +``` + +#### Using Constructor Injection +```typescript +import { inject } from 'aurelia'; + +@inject(MyService) +export class MyComponent { + constructor(private readonly myService: MyService) { + this.myService.doSomething(); + } +} +``` + +--- + +## 🎯 Enhanced Dependency Injection with Interfaces + +While basic DI still works in Aurelia 2, the framework introduces powerful new patterns using `DI.createInterface` that provide better type safety and flexibility. + +### Using DI.createInterface + +The new interface-based DI system offers two main approaches: + +#### 1. Strongly-Typed with Default Implementation +```typescript +export class ApiClient { + async getProducts(filter) { /* ... */ } +} + +export interface IApiClient extends ApiClient {} +export const IApiClient = DI.createInterface('IApiClient', x => x.singleton(ApiClient)); +``` + +#### 2. Interface-Only (Loose Coupling) +```typescript +export interface IApiClient { + getProducts(filter): Promise; +} + +export const IApiClient = DI.createInterface('IApiClient'); + +// Registration needed when no default is provided +Aurelia.register(Registration.singleton(IApiClient, ApiClient)); +``` + +### Consuming Interfaces + +There are multiple ways to inject interfaces in your components: + +```typescript +export class MyComponent { + // Using resolve + private readonly api = resolve(IApiClient); + + // Future convention (once implemented) + constructor(private readonly api: IApiClient) {} +} +``` + +You can also use the `@inject` decorator to inject the interface: + +```typescript +import { inject } from 'aurelia'; + +@inject(IApiClient) +export class MyComponent { + constructor(private readonly api: IApiClient) {} +} +``` + +--- + +## 🎨 Template Syntax Changes + +Aurelia 2 introduces several changes to the template syntax, including the removal of the `require` attribute, optional `