Skip to content

Commit

Permalink
chore: incorporates export-application-global & removes dependency
Browse files Browse the repository at this point in the history
- declares export-application-global internally instead of using external archived dependency
  • Loading branch information
Pixelik committed Jul 3, 2023
1 parent b56d8cf commit 45018ba
Show file tree
Hide file tree
Showing 10 changed files with 230 additions and 43 deletions.
33 changes: 20 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,12 @@
[![CI](https://github.com/DazzlingFugu/ember-cli-embedded/actions/workflows/ci.yml/badge.svg)](https://github.com/DazzlingFugu/ember-cli-embedded/actions/workflows/ci.yml) [![Ember Observer Score](https://emberobserver.com/badges/ember-cli-embedded.svg)](https://emberobserver.com/addons/ember-cli-embedded)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)


⚠️ This addon depends on [ember-export-application-global](https://github.com/ember-cli/ember-export-application-global)
to get your application globally exposed, but it's deprecated.

Makes it easier to embed your Ember application in another (non-Ember) app.

This addon gives you more control over how and when your Ember app will boot and also allows how to add/override some configuration so that the Ember app can boot with some context-dependent config.

We found it especially useful, for example, when migrating an existing app to Ember part by part.


## Compatibility

* Ember.js v3.28 or above
Expand All @@ -37,15 +32,23 @@ In your `config/environment.js`, add the following config to the `ENV`:

```js
let ENV = {
...
...,
modulePrefix: 'my-app-name',

embedded: {
delegateStart: true,
config: { // optional
// Default values for the config passed at boot
},
},
...
};

/*
* 1. If you leave this flag undefined, you will have to start your app with `MyAppName.start(...)`
* 2. If you set this flag to `SomeOtherAppName` (String), you will have to start your app with `SomeOtherAppName.start(...)`
* 3. If you set this flag to `false` (Boolean), you will NOT be able to start your app with `.start(...)` at all
*/
exportApplicationGlobal: 'SomeOtherAppName'
}
```

Doing so will make your application hold until you manually start it. (read on to learn more)
Expand All @@ -57,10 +60,11 @@ Doing so will make your application hold until you manually start it. (read on t

### Start your app

In your JS code, execute `MyApp.start(/* optionalConfig */)` to resume the boot of your application. As per the example, it takes an optional configuration as its first argument.
In your JS code, execute `MyAppName.start(/* optionalConfig */)` to resume the boot of your application. As per the example, it takes an optional configuration as its first argument.

Remember:
Your app __will not start__ unless you call `MyApp.start(/* optionalConfig */)` method.
### Attention :warning:
1. Your app __will not start__ unless you call `MyAppName.start(/* optionalConfig */)` method.
2. Calling `MyAppName.start(...)` will __not work__ if you've set `exportApplicationGlobal: false` in `your config/environment.js`


### Access the config from your application
Expand All @@ -71,14 +75,15 @@ Consider the following `config/environment.js` file:

```js
let ENV = {
...
...,
modulePrefix: 'my-app',
embedded: {
config: {
option1: 'value-1',
},
},
...
};
}
```

And the application is started that way:
Expand Down Expand Up @@ -137,6 +142,8 @@ Consider the following `config/environment.js` file:
rootElement: `#some-element`,
},

modulePrefix: 'my-app',

embedded: {
config: {
option1: 'value-1',
Expand Down
31 changes: 8 additions & 23 deletions addon/initializers/embedded.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,11 @@
import Application from '@ember/application'
import { deprecate } from '@ember/debug'

interface ObjectConfig {
delegateStart?:
| undefined
| boolean

config?:
| undefined
| Record<string, never> // empty object `{}`
| Record<string, unknown>
}

type NullishConfig =
| null
| undefined

type DeprecatedBooleanConfig = boolean

type GivenConfig =
| NullishConfig
| DeprecatedBooleanConfig
| ObjectConfig
import {
ObjectConfig,
NullishConfig,
DeprecatedBooleanConfig,
GivenConfig
} from '../../types'

function configIsNullish(config: GivenConfig): config is NullishConfig {
return config === null || config === undefined
Expand Down Expand Up @@ -97,6 +81,7 @@ function normalizeConfig(userConfig: GivenConfig): ObjectConfig {
}

export function initialize(application: Application): void {

const env = application.resolveRegistration('config:environment') as { embedded?: GivenConfig }
const embeddedConfig: ObjectConfig = normalizeConfig(env.embedded)

Expand Down Expand Up @@ -126,5 +111,5 @@ export function initialize(application: Application): void {
export default {
name: 'ember-cli-embedded',
after: 'export-application-global',
initialize,
initialize
}
49 changes: 49 additions & 0 deletions addon/initializers/export-application-global.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import Application from '@ember/application'
import { classify } from '@ember/string'

export function initialize(application: Application): void {
const env = application.resolveRegistration('config:environment') as {
embedded?: {
delegateStart: boolean
},
exportApplicationGlobal: boolean | string,
modulePrefix: string
}

const mustExportApplicationGlobal = env.embedded?.delegateStart === true && env.exportApplicationGlobal !== false

if (mustExportApplicationGlobal) {
let theGlobal

if (typeof window !== 'undefined') {
theGlobal = window
} else if (typeof global !== 'undefined') {
theGlobal = global
} else if (typeof self !== 'undefined') {
theGlobal = self
} else {
return
}

const value = env.exportApplicationGlobal

let globalName

if (typeof value === 'string') {
globalName = value
} else {
globalName = classify(env.modulePrefix)
}

// @ts-ignore: until there's a way to access a dynamic propertyName of window in TS ?
if (!theGlobal[globalName]) {
// @ts-ignore: until there's a way to set a dynamic propertyName on the window in TS ?
theGlobal[globalName] = application
}
}
}

export default {
name: 'export-application-global',
initialize
}
1 change: 1 addition & 0 deletions app/initializers/export-application-global.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default, initialize } from 'ember-cli-embedded/initializers/export-application-global'
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@
"dependencies": {
"ember-cli-babel": "^7.26.11",
"ember-cli-htmlbars": "^6.1.1",
"ember-cli-typescript": "^5.2.1",
"ember-export-application-global": "^2.0.1"
"ember-cli-typescript": "^5.2.1"
},
"devDependencies": {
"@ember/optional-features": "^2.0.0",
"@ember/string": "^3.1.1",
"@ember/test-helpers": "^2.9.3",
"@embroider/test-setup": "^1.8.3",
"@tsconfig/ember": "^1.1.0",
Expand Down
Empty file removed tests/unit/.gitkeep
Empty file.
121 changes: 121 additions & 0 deletions tests/unit/initializers/export-application-global-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import Application from '@ember/application'
import { initialize } from 'dummy/initializers/export-application-global'
import { module, test } from 'qunit'
import Resolver from 'ember-resolver'
import { classify } from '@ember/string'
import { run } from '@ember/runloop'

type TestApplication = Application & {
// Public types are currently incomplete, these 2 properties exist:
// https://github.com/emberjs/ember.js/blob/v3.26.1/packages/@ember/application/lib/application.js#L376-L377
_booted: boolean
_readinessDeferrals: number
}

// How an app would look like with our Initializer `embedded`
interface EmbeddedApp extends TestApplication {
start?: (config?: Record<string, unknown>) => void
}

interface Context {
TestApplication: typeof Application
application: EmbeddedApp
}

module('Unit | Initializer | export-application-global', function (hooks) {
hooks.beforeEach(function (this: Context) {
this.TestApplication = class TestApplication extends Application {
modulePrefix = 'whatever'
}

this.TestApplication.initializer({
name: 'export application global initializer',
initialize,
})

// @ts-ignore: temporarily required as public types are incomplete
this.application = this.TestApplication.create({
autoboot: false,
Resolver
})

this.application.register('config:environment', {})
})

hooks.afterEach(function (this: Context) {
const config:any = this.application.resolveRegistration('config:environment')
const exportedApplicationGlobal:string = classify(config.modulePrefix)
// @ts-ignore: because TS doesn't like window[dynamicPropertyName]
delete window[exportedApplicationGlobal]
run(this.application, 'destroy')
})

// @ts-ignore: because QUnit is not set up with TS propertly and does not like .each()
test.each('it adds expected application global to window if config.embedded.delegateStart is true', [
['something-random', 'SomethingRandom'],
['something_more-random', 'SomethingMoreRandom'],
['something-', 'Something'],
['something', 'Something']
], async function (this: Context, assert: Record<string, unknown>, testData: Array<Array<string>>) {
const [modulePrefix, exportedApplicationGlobal] = testData

this.application.register('config:environment', {
modulePrefix,
embedded: {
delegateStart: true
}
})

await this.application.boot()

// @ts-ignore: because TS doesn't like modulePrefix
assert.strictEqual(classify(modulePrefix), exportedApplicationGlobal, 'it "classifies" module prefix')

// @ts-ignore: because TS doesn't like window[dynamicPropertyName]
assert.deepEqual(window[exportedApplicationGlobal], this.application, 'it creates expected application global on window')
})

test('it does not add application global to window if config.embedded.delegateStart is not true', async function (this: Context, assert) {
this.application.register('config:environment', {
modulePrefix: 'something-random'
})

await this.application.boot()

// @ts-ignore: because TS doesn't like window[dynamicPropertyName]
assert.notOk(window.SomethingRandom)
})

test('it does not create application global on window if config.exportApplicationGlobal is false', async function (this: Context, assert) {
this.application.register('config:environment', {
modulePrefix: 'something-random',
embedded: {
delegateStart: true
},
exportApplicationGlobal: false
})

await this.application.boot()

// @ts-ignore: because TS doesn't like window[dynamicPropertyName]
assert.notOk(window.SomethingRandom)
})

test('it adds application global to window using value of config.exportApplicationGlobal, if it is a String', async function (this: Context, assert) {
this.application.register('config:environment', {
modulePrefix: 'something-random',
embedded: {
delegateStart: true
},
exportApplicationGlobal: 'SomethingElse'
})

await this.application.boot()

// @ts-ignore: because TS doesn't like window.PropertyName ?
assert.deepEqual(window.SomethingElse, this.application, 'name set in config is used for exported application global, instead of original module prefix')

// @ts-ignore: because TS doesn't like window.PropertyName ?
assert.notOk(window.SomethingRandom, 'original module prefix is not used in exported application global')
})
})
1 change: 1 addition & 0 deletions types/dummy/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
declare module 'dummy/app'
declare module 'dummy/initializers/embedded'
declare module 'dummy/initializers/export-application-global'
declare module 'dummy/instance-initializers/embedded'
21 changes: 21 additions & 0 deletions types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export interface ObjectConfig {
delegateStart?:
| undefined
| boolean

config?:
| undefined
| Record<string, never> // empty object `{}`
| Record<string, unknown>
}

export type NullishConfig =
| null
| undefined

export type DeprecatedBooleanConfig = boolean

export type GivenConfig =
| NullishConfig
| DeprecatedBooleanConfig
| ObjectConfig
12 changes: 7 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1022,6 +1022,13 @@
mkdirp "^1.0.4"
silent-error "^1.1.1"

"@ember/string@^3.1.1":
version "3.1.1"
resolved "https://registry.yarnpkg.com/@ember/string/-/string-3.1.1.tgz#0a5ac0d1e4925259e41d5c8d55ef616117d47ff0"
integrity sha512-UbXJ+k3QOrYN4SRPHgXCqYIJ+yWWUg1+vr0H4DhdQPTy8LJfyqwZ2tc5uqpSSnEXE+/1KopHBE5J8GDagAg5cg==
dependencies:
ember-cli-babel "^7.26.6"

"@ember/test-helpers@^2.9.3":
version "2.9.3"
resolved "https://registry.yarnpkg.com/@ember/test-helpers/-/test-helpers-2.9.3.tgz#c2a9d6ab1c367af92cf1a334f97eb19b8e06e6e1"
Expand Down Expand Up @@ -4098,11 +4105,6 @@ ember-disable-prototype-extensions@^1.1.3:
resolved "https://registry.yarnpkg.com/ember-disable-prototype-extensions/-/ember-disable-prototype-extensions-1.1.3.tgz#1969135217654b5e278f9fe2d9d4e49b5720329e"
integrity sha512-SB9NcZ27OtoUk+gfalsc3QU17+54OoqR668qHcuvHByk4KAhGxCKlkm9EBlKJcGr7yceOOAJqohTcCEBqfRw9g==

ember-export-application-global@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/ember-export-application-global/-/ember-export-application-global-2.0.1.tgz#b120a70e322ab208defc9e2daebe8d0dfc2dcd46"
integrity sha512-B7wiurPgsxsSGzJuPFkpBWnaeuCu2PGpG2BjyrfA1VcL7//o+5RSnZqiCEY326y7qmxb2GoCgo0ft03KBU0rRw==

ember-load-initializers@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ember-load-initializers/-/ember-load-initializers-2.1.2.tgz#8a47a656c1f64f9b10cecdb4e22a9d52ad9c7efa"
Expand Down

0 comments on commit 45018ba

Please sign in to comment.