Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: hardcodes export-application-global #273

Merged
merged 1 commit into from
Jul 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
Pixelik marked this conversation as resolved.
Show resolved Hide resolved
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'
7 changes: 5 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",
Pixelik marked this conversation as resolved.
Show resolved Hide resolved
"@ember/test-helpers": "^2.9.3",
"@embroider/test-setup": "^1.8.3",
"@tsconfig/ember": "^1.1.0",
Expand Down Expand Up @@ -103,6 +103,9 @@
"typescript": "^4.9.4",
"webpack": "^5.76.0"
},
"peerDependencies": {
"@ember/string": "^3.1.1"
},
"engines": {
"node": "14.* || 16.* || >= 18"
},
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')

Check warning on line 46 in tests/unit/initializers/export-application-global-test.ts

View workflow job for this annotation

GitHub Actions / org / Lint and tests

Unexpected any. Specify a different type
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