From 8e35a280c54872110c56324c04cb547e4f8c57f9 Mon Sep 17 00:00:00 2001 From: Sergey Astapov Date: Tue, 9 Jul 2024 14:34:30 -0400 Subject: [PATCH] Convert to TypeScript and add glint support --- ember-lazy-mount/package.json | 1 + .../src/components/lazy-mount.hbs | 2 +- .../{lazy-mount.js => lazy-mount.ts} | 60 ++++++++++------- .../{engine-loader.js => engine-loader.ts} | 26 +++++--- ember-lazy-mount/src/template-registry.ts | 6 +- .../unpublished-development-types/index.d.ts | 14 ++++ pnpm-lock.yaml | 65 +++++++++++++++++++ 7 files changed, 138 insertions(+), 36 deletions(-) rename ember-lazy-mount/src/components/{lazy-mount.js => lazy-mount.ts} (80%) rename ember-lazy-mount/src/services/{engine-loader.js => engine-loader.ts} (66%) diff --git a/ember-lazy-mount/package.json b/ember-lazy-mount/package.json index 554bc46..4aa7bd4 100644 --- a/ember-lazy-mount/package.json +++ b/ember-lazy-mount/package.json @@ -74,6 +74,7 @@ "@typescript-eslint/parser": "^7.7.1", "babel-plugin-ember-template-compilation": "^2.2.5", "concurrently": "^8.2.2", + "ember-source": "~4.12.4", "ember-template-lint": "^6.0.0", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", diff --git a/ember-lazy-mount/src/components/lazy-mount.hbs b/ember-lazy-mount/src/components/lazy-mount.hbs index 906b011..df7dd46 100644 --- a/ember-lazy-mount/src/components/lazy-mount.hbs +++ b/ember-lazy-mount/src/components/lazy-mount.hbs @@ -3,6 +3,6 @@ {{yield (hash isLoading=true error=null)}} {{else if this.error}} {{yield (hash isLoading=false error=this.error)}} -{{else}} +{{else if this.loadedName}} {{mount this.loadedName model=@model}} {{/if}} diff --git a/ember-lazy-mount/src/components/lazy-mount.js b/ember-lazy-mount/src/components/lazy-mount.ts similarity index 80% rename from ember-lazy-mount/src/components/lazy-mount.js rename to ember-lazy-mount/src/components/lazy-mount.ts index 689c611..6759c3e 100644 --- a/ember-lazy-mount/src/components/lazy-mount.js +++ b/ember-lazy-mount/src/components/lazy-mount.ts @@ -5,11 +5,27 @@ import { action, setProperties } from '@ember/object'; import { inject as service } from '@ember/service'; import { buildWaiter } from '@ember/test-waiters'; +import type EngineLoaderService from '../services/engine-loader.ts'; + const waiter = buildWaiter('ember-lazy-mount:lazy-mount'); +interface LazyMountSignature { + Args: { + name: string; + model: unknown; + }; + Blocks: { + default: [ + | { isLoading: true; error: null } + | { isLoading: false; error: Error } + | undefined, + ]; + }; +} + /** * The `{{lazy-mount}}` component works just like the - * [`{{mount}}` helper](https://emberjs.com/api/ember/3.5/classes/Ember.Templates.helpers/methods/mount?anchor=mount). + * [`{{mount}}` helper](https://api.emberjs.com/ember/5.9/classes/Ember.Templates.helpers/methods/mount?anchor=mount). * * It accepts the name of the engine as a positional parameter and also an * optional `model` parameter. @@ -43,7 +59,7 @@ const waiter = buildWaiter('ember-lazy-mount:lazy-mount'); * the engine. * * ```hbs - * {{#lazy-mount engineName model=optionalDataForTheEngine as |engine|}} + * {{#lazy-mount this.engineName model=this.optionalDataForTheEngine as |engine|}} * {{#if engine.isLoading}} * 🕑 The engine is loading... * {{else if engine.error}} @@ -59,10 +75,10 @@ const waiter = buildWaiter('ember-lazy-mount:lazy-mount'); * the model of the engine. * @public */ -export default class LazyMount extends Component { +export default class LazyMount extends Component { tagName = ''; - @service engineLoader; + @service declare engineLoader: EngineLoaderService; /** * The name of the engine to load and subsequently mount. @@ -71,7 +87,7 @@ export default class LazyMount extends Component { * @type {string} * @public */ - name = null; + name: string | null = null; /** * Optional model that will be passed through to the engine. @@ -82,7 +98,7 @@ export default class LazyMount extends Component { * @type {any?} * @public */ - model = null; + model: unknown = null; /** * Optional callback called when the engine starts loading. @@ -91,7 +107,7 @@ export default class LazyMount extends Component { * @type {(() => void)?} * @public */ - onLoad = null; + onLoad: (() => void) | null = null; /** * Optional callback called when the engine finished loading. @@ -100,7 +116,7 @@ export default class LazyMount extends Component { * @type {(() => void)?} * @public */ - didLoad = null; + didLoad: (() => void) | null = null; /** * Optional callback called when the engine filed to load. @@ -109,7 +125,7 @@ export default class LazyMount extends Component { * @type {((error: Error) => void)?} * @public */ - onError = null; + onError: ((error: Error) => void) | null = null; /** * When the engine was loaded successfully, this will then be the name of the @@ -122,7 +138,7 @@ export default class LazyMount extends Component { * @type {string?} * @private */ - loadedName = null; + loadedName: string | null = null; /** * If an error occurred while loading the engine, it will be set here. @@ -131,7 +147,7 @@ export default class LazyMount extends Component { * @type {Error?} * @private */ - error = null; + error: Error | null = null; /** * While the bundle is being loaded, this property is `true`. @@ -140,9 +156,9 @@ export default class LazyMount extends Component { * @type {boolean} * @private */ - isLoading = false; + isLoading: boolean = false; - @action initLoadEngine(name) { + @action initLoadEngine(name: string): void { assert(`lazy-mount: Argument 'name' is missing.`, name); if (name !== this.loadedName) { @@ -166,21 +182,21 @@ export default class LazyMount extends Component { * @async * @private */ - async loadEngine(name = this.name) { + async loadEngine(name: string): Promise { const shouldCancel = this._thread(); const engineLoader = this.engineLoader; this.setLoading(); if (!engineLoader.isLoaded(name)) { - let token = waiter.beginAsync(); + const token = waiter.beginAsync(); try { await engineLoader.load(name); if (shouldCancel()) return; } catch (error) { if (shouldCancel()) return; - this.setError(error); + this.setError(error as Error); return; } finally { waiter.endAsync(token); @@ -191,17 +207,17 @@ export default class LazyMount extends Component { } setLoading() { - this.onLoad && this.onLoad(); + this.onLoad?.(); setProperties(this, { loadedName: null, error: null, isLoading: true }); } - setLoaded(loadedName) { - this.didLoad && this.didLoad(); + setLoaded(loadedName: string) { + this.didLoad?.(); setProperties(this, { loadedName, error: null, isLoading: false }); } - setError(error) { - this.onError && this.onError(error); + setError(error: Error) { + this.onError?.(error); setProperties(this, { loadedName: null, error, isLoading: false }); } @@ -209,7 +225,7 @@ export default class LazyMount extends Component { * The following is a really low-fidelity implementation of something that * would be handled by ember-concurrency or ember-lifeline. */ - _threadId = null; + _threadId: Record | null = null; _thread() { const threadId = (this._threadId = {}); diff --git a/ember-lazy-mount/src/services/engine-loader.js b/ember-lazy-mount/src/services/engine-loader.ts similarity index 66% rename from ember-lazy-mount/src/services/engine-loader.js rename to ember-lazy-mount/src/services/engine-loader.ts index df73338..398f554 100644 --- a/ember-lazy-mount/src/services/engine-loader.js +++ b/ember-lazy-mount/src/services/engine-loader.ts @@ -1,5 +1,11 @@ -import { getOwner } from '@ember/owner'; import Service from '@ember/service'; +import { getOwner } from '@ember/owner'; + +import type Owner from '@ember/owner'; + +type ExtendedOwner = Owner & { + hasRegistration(name: string): boolean; +}; export default class EngineLoaderService extends Service { /** @@ -11,9 +17,9 @@ export default class EngineLoaderService extends Service { * @param {String} name * @return {Boolean} */ - isLoaded(name) { - const owner = getOwner(this); - return owner.hasRegistration(`engine:${name}`); + isLoaded(name: string) { + const owner = getOwner(this); + return Boolean(owner?.hasRegistration(`engine:${name}`)); } /** @@ -23,12 +29,14 @@ export default class EngineLoaderService extends Service { * * @param {String} name */ - register(name) { + register(name: string) { if (this.isLoaded(name)) return; const owner = getOwner(this); - owner.register( + owner?.register( `engine:${name}`, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore globalThis.require(`${name}/engine`).default, ); } @@ -39,11 +47,11 @@ export default class EngineLoaderService extends Service { * @param {String} name * @async */ - async load(name) { + async load(name: string) { if (this.isLoaded(name)) return; - const assetLoader = getOwner(this).lookup('service:asset-loader'); - await assetLoader.loadBundle(name); + const assetLoader = getOwner(this)?.lookup('service:asset-loader'); + await assetLoader?.loadBundle(name); this.register(name); } } diff --git a/ember-lazy-mount/src/template-registry.ts b/ember-lazy-mount/src/template-registry.ts index 0758d89..9d40eb3 100644 --- a/ember-lazy-mount/src/template-registry.ts +++ b/ember-lazy-mount/src/template-registry.ts @@ -2,10 +2,8 @@ // Add all your components, helpers and modifiers to the template registry here, so apps don't have to do this. // See https://typed-ember.gitbook.io/glint/environments/ember/authoring-addons -// import type MyComponent from './components/my-component'; +import type LazyMount from './components/lazy-mount.ts'; -// Remove this once entries have been added! 👇 -// eslint-disable-next-line @typescript-eslint/no-empty-interface export default interface Registry { - // MyComponent: typeof MyComponent + LazyMount: typeof LazyMount; } diff --git a/ember-lazy-mount/unpublished-development-types/index.d.ts b/ember-lazy-mount/unpublished-development-types/index.d.ts index 4d16f38..ce23eaa 100644 --- a/ember-lazy-mount/unpublished-development-types/index.d.ts +++ b/ember-lazy-mount/unpublished-development-types/index.d.ts @@ -15,3 +15,17 @@ declare module '@glint/environment-ember-loose/registry' { // See https://typed-ember.gitbook.io/glint/using-glint/ember/using-addons } } + +class AssetLoaderService { + loadBundle(name: string): Promise; +} + +declare module '@ember/service' { + interface Registry { + 'asset-loader': AssetLoaderService; + } +} + +declare interface globalThis { + require(string): unknown; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4dfbc9e..002ed0c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -78,6 +78,9 @@ importers: concurrently: specifier: ^8.2.2 version: 8.2.2 + ember-source: + specifier: ~4.12.4 + version: 4.12.4(@babel/core@7.24.7)(@glimmer/component@1.1.2)(@glint/template@1.4.0) ember-template-lint: specifier: ^6.0.0 version: 6.0.0 @@ -2362,6 +2365,14 @@ packages: '@glimmer/util': 0.92.0 dev: true + /@glimmer/vm-babel-plugins@0.84.2(@babel/core@7.24.7): + resolution: {integrity: sha512-HS2dEbJ3CgXn56wk/5QdudM7rE3vtNMvPIoG7Rrg+GhkGMNxBCIRxOeEF2g520j9rwlA2LAZFpc7MCDMFbTjNA==} + dependencies: + babel-plugin-debug-macros: 0.3.4(@babel/core@7.24.7) + transitivePeerDependencies: + - '@babel/core' + dev: true + /@glimmer/vm-babel-plugins@0.92.0(@babel/core@7.24.7): resolution: {integrity: sha512-s/jPlTykZb3YzzOCVmGyMP8NihonHM+eY5WBQl+MOCXe2KdGkTAxFgnuGYzHTtJ/JzCRa/YRXQhJhncJSg6L2A==} engines: {node: '>=16'} @@ -4037,6 +4048,14 @@ packages: '@glimmer/syntax': 0.84.3 babel-import-util: 3.0.0 + /babel-plugin-filter-imports@4.0.0: + resolution: {integrity: sha512-jDLlxI8QnfKd7PtieH6pl4tZJzymzfCDCPGdTq/grgbiYAikwDPp/oL0IlFJn0HQjLpcLkyYhPKkUVneRESw5w==} + engines: {node: '>=8'} + dependencies: + '@babel/types': 7.24.7 + lodash: 4.17.21 + dev: true + /babel-plugin-htmlbars-inline-precompile@5.3.1: resolution: {integrity: sha512-QWjjFgSKtSRIcsBhJmEwS2laIdrA6na8HAlc/pEAhjHgQsah/gMiBFRZvbQTy//hWxR4BMwV7/Mya7q5H8uHeA==} engines: {node: 10.* || >= 12.*} @@ -6900,6 +6919,47 @@ packages: - encoding dev: true + /ember-source@4.12.4(@babel/core@7.24.7)(@glimmer/component@1.1.2)(@glint/template@1.4.0): + resolution: {integrity: sha512-HUlNAY+qr/Jm4c/5E11n5w6IvLY7Rr4DxmFv/0LZ3R5LqDSubM1jEmny5zDjOfadMa4pawoCmFFWXVeJEXwppg==} + engines: {node: '>= 14.*'} + peerDependencies: + '@glimmer/component': ^1.1.2 + dependencies: + '@babel/helper-module-imports': 7.24.7 + '@babel/plugin-transform-block-scoping': 7.24.7(@babel/core@7.24.7) + '@ember/edition-utils': 1.2.0 + '@glimmer/component': 1.1.2(@babel/core@7.24.7) + '@glimmer/vm-babel-plugins': 0.84.2(@babel/core@7.24.7) + '@simple-dom/interface': 1.4.0 + babel-plugin-debug-macros: 0.3.4(@babel/core@7.24.7) + babel-plugin-filter-imports: 4.0.0 + broccoli-concat: 4.2.5 + broccoli-debug: 0.6.5 + broccoli-file-creator: 2.1.1 + broccoli-funnel: 3.0.8 + broccoli-merge-trees: 4.2.0 + chalk: 4.1.2 + ember-auto-import: 2.7.4(@glint/template@1.4.0)(webpack@5.92.1) + ember-cli-babel: 7.26.11 + ember-cli-get-component-path-option: 1.0.0 + ember-cli-is-package-missing: 1.0.0 + ember-cli-normalize-entity-name: 1.0.0 + ember-cli-path-utils: 1.0.0 + ember-cli-string-utils: 1.1.0 + ember-cli-typescript-blueprint-polyfill: 0.1.0 + ember-cli-version-checker: 5.1.2 + ember-router-generator: 2.0.0 + inflection: 1.13.4 + resolve: 1.22.8 + semver: 7.6.2 + silent-error: 1.1.1 + transitivePeerDependencies: + - '@babel/core' + - '@glint/template' + - supports-color + - webpack + dev: true + /ember-source@5.10.0(@glimmer/component@1.1.2)(@glint/template@1.4.0)(rsvp@4.8.5)(webpack@5.92.1): resolution: {integrity: sha512-auzkUAPtqYd4oP6ol4JLB1DMwEt7USZVwFKN+HuuvZkZGrDGADXBFJMYv3y9BPNrcW72DdEvvH4gYrxaoRMQLQ==} engines: {node: '>= 16.*'} @@ -9117,6 +9177,11 @@ packages: resolution: {integrity: sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==} dev: true + /inflection@1.13.4: + resolution: {integrity: sha512-6I/HUDeYFfuNCVS3td055BaXBwKYuzw7K3ExVMStBowKo9oOAMJIXIHvdyR3iboTCp1b+1i5DSkIZTcwIktuDw==} + engines: {'0': node >= 0.4.0} + dev: true + /inflection@2.0.1: resolution: {integrity: sha512-wzkZHqpb4eGrOKBl34xy3umnYHx8Si5R1U4fwmdxLo5gdH6mEK8gclckTj/qWqy4Je0bsDYe/qazZYuO7xe3XQ==} engines: {node: '>=14.0.0'}