feat: manager to support single instances with nested npm installations
BREAKING CHANGE: add singleton-manager
This commit is contained in:
parent
612ad1f19f
commit
7f49f2c6a6
51 changed files with 1335 additions and 0 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -30,3 +30,5 @@ local.log
|
||||||
|
|
||||||
## browserstack
|
## browserstack
|
||||||
browserstack.err
|
browserstack.err
|
||||||
|
|
||||||
|
!packages/singleton-manager/demo/**/node_modules
|
||||||
|
|
|
||||||
302
packages/singleton-manager/README.md
Normal file
302
packages/singleton-manager/README.md
Normal file
|
|
@ -0,0 +1,302 @@
|
||||||
|
# Singleton Manager
|
||||||
|
|
||||||
|
A singleton manager provides a way to make sure a singleton instance loaded from multiple file locations stays a singleton.
|
||||||
|
Primarily useful if two major version of a package with a singleton is used.
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm i --save singleton-manager
|
||||||
|
```
|
||||||
|
|
||||||
|
⚠️ You need to make SURE that only ONE version of `singleton-manager` is installed. For how see [Non Goals](#non-goals).
|
||||||
|
|
||||||
|
### Example Singleton Users
|
||||||
|
|
||||||
|
Use the same singleton for both versions (as we don't use any of the breaking features)
|
||||||
|
|
||||||
|
```js
|
||||||
|
// managed-my-singleton.js
|
||||||
|
import { singletonManager } from 'singleton-manager';
|
||||||
|
import { mySingleton } from 'my-singleton'; // is available as 1.x and 2.x via node resolution
|
||||||
|
|
||||||
|
singletonManager.set('my-singleton::index.js::1.x', mySingleton);
|
||||||
|
singletonManager.set('my-singleton::index.js::2.x', mySingleton);
|
||||||
|
```
|
||||||
|
|
||||||
|
OR create a special compatible version of the singleton
|
||||||
|
|
||||||
|
```js
|
||||||
|
// managed-my-singleton.js
|
||||||
|
import { singletonManager } from 'singleton-manager';
|
||||||
|
import { MySingleton } from 'my-singleton'; // is available as 1.x and 2.x via node resolution
|
||||||
|
|
||||||
|
class CompatibleSingleton extends MySingleton {
|
||||||
|
// add forward or backward compatibility code
|
||||||
|
}
|
||||||
|
const compatibleSingleton = new CompatibleSingleton();
|
||||||
|
|
||||||
|
singletonManager.set('my-singleton::index.js::1.x', compatibleSingleton);
|
||||||
|
singletonManager.set('my-singleton::index.js::2.x', compatibleSingleton);
|
||||||
|
```
|
||||||
|
|
||||||
|
AND in you App then you need to load the above code BEFORE loading the singleton or any feature using it.
|
||||||
|
|
||||||
|
```js
|
||||||
|
import './managed-my-singleton.js';
|
||||||
|
|
||||||
|
import { mySingleton } from 'my-singleton'; // will no always be what is "defined" in managed-my-singleton.js
|
||||||
|
```
|
||||||
|
|
||||||
|
### Warning
|
||||||
|
|
||||||
|
Overriding version is an App level concern hence components or "features" are not allowed to use it.
|
||||||
|
If you try to call multiple times for the same key then it will throw an error.
|
||||||
|
|
||||||
|
```js
|
||||||
|
// on app level
|
||||||
|
singletonManager.set('my-singleton/index.js::1.x', compatibleSingleton);
|
||||||
|
|
||||||
|
// somewhere in a dependency
|
||||||
|
singletonManager.set('my-singleton/index.js::1.x', compatibleSingleton);
|
||||||
|
|
||||||
|
// will throw an error that it is already defined
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example Singleton Maintainers
|
||||||
|
|
||||||
|
If you are a maintainer of a singleton be sure to check if a singleton manager version is set.
|
||||||
|
If that is the case return it instead of your default instance.
|
||||||
|
|
||||||
|
It could look something like this:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// my-singleton.js
|
||||||
|
import { singletonManager } from 'singleton-manager';
|
||||||
|
import { MySingleton } from './src/MySingleton.js';
|
||||||
|
|
||||||
|
export const overlays =
|
||||||
|
singletonManager.get('my-singleton/my-singleton.js::1.x') || new MySingleton();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Convention Singleton Key
|
||||||
|
|
||||||
|
The key for a singleton needs to be "unique" for the package.
|
||||||
|
Hence the following convention helps maintaining this.
|
||||||
|
|
||||||
|
As a key use the `<package>::<unique-variable>::<semver-range>`.
|
||||||
|
|
||||||
|
Examples Do:
|
||||||
|
|
||||||
|
- `overlays::overlays::1.x` - instance created in index.js
|
||||||
|
- `@scope/overlays::overlays::1.x` - with scope
|
||||||
|
- `overlays::overlays::1.x` - version 1.x.x (> 1.0.0 you do 1.x, 2.x)
|
||||||
|
- `overlays::overlays::2.x` - version 2.x.x (> 1.0.0 you do 1.x, 2.x)
|
||||||
|
- `overlays::overlays::0.10.x` - version 0.10.x (< 1.0.0 you do 0.1.x, 0.2.x)
|
||||||
|
|
||||||
|
Examples Don't:
|
||||||
|
|
||||||
|
- `overlays` - too generic
|
||||||
|
- `overlays::overlays` - you should include a version
|
||||||
|
- `overlays::1.x` - you should include a package name & unique var
|
||||||
|
- `./index.js::1.x` - it should start with a package name
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Singleton Manager Rationale
|
||||||
|
|
||||||
|
We have an app with 2 pages.
|
||||||
|
|
||||||
|
- page-a uses overlays 1.x
|
||||||
|
- page-b uses overlays 2.x (gets installed nested)
|
||||||
|
|
||||||
|
```txt
|
||||||
|
my-app (node_modules)
|
||||||
|
├── overlays (1.x)
|
||||||
|
├── page-a
|
||||||
|
│ └── page-a.js
|
||||||
|
└── page-b
|
||||||
|
├── node_modules
|
||||||
|
│ └── overlays (2.x)
|
||||||
|
└── page-b.js
|
||||||
|
```
|
||||||
|
|
||||||
|
The tough part in this case is the OverlaysManager within the overlays package as it needs to be a singleton.
|
||||||
|
|
||||||
|
It starts of simplified like this
|
||||||
|
|
||||||
|
```js
|
||||||
|
export class OverlaysManager {
|
||||||
|
name = 'OverlayManager 1.x';
|
||||||
|
blockBody = false;
|
||||||
|
constructor() {
|
||||||
|
this._setupBlocker();
|
||||||
|
}
|
||||||
|
_setupBlocker() {
|
||||||
|
/* ... */
|
||||||
|
}
|
||||||
|
block() {
|
||||||
|
this.blockBody = true; // ...
|
||||||
|
}
|
||||||
|
|
||||||
|
unBlock() {
|
||||||
|
this.blockBody = false; // ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example A (fail)
|
||||||
|
|
||||||
|
See it "fail" e.g. 2 separate OverlaysManager are at work and are "fighting" over the way to block the body.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run start:fail
|
||||||
|
```
|
||||||
|
|
||||||
|
Steps to reproduce:
|
||||||
|
|
||||||
|
1. Page A click on block
|
||||||
|
2. Page B => "Blocked: false" (even when hitting the refresh button)
|
||||||
|
|
||||||
|
See [the code](./demo/fail/demo-app.js).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Example B (singleton manager)
|
||||||
|
|
||||||
|
The breaking change in `OverlayManager` was renaming of 2 function (which has been deprecated before).
|
||||||
|
|
||||||
|
- `block()` => `blockingBody()`
|
||||||
|
- `unBlock()` => `unBlockingBody()`
|
||||||
|
|
||||||
|
knowing that we can create a Manager that is compatible with both via
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { OverlaysManager } from 'overlays';
|
||||||
|
|
||||||
|
class CompatibleOverlaysManager extends OverlaysManager {
|
||||||
|
blockingBody() {
|
||||||
|
this.block();
|
||||||
|
}
|
||||||
|
unBlockingBody() {
|
||||||
|
this.unBlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
all that is left is a to "override" the default instance of the "users"
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { singletonManager } from 'singleton-manager';
|
||||||
|
|
||||||
|
const compatibleOverlaysManager = new CompatibleOverlaysManager();
|
||||||
|
singletonManager.set('overlays::overlays::1.x', compatibleOverlaysManager);
|
||||||
|
singletonManager.set('overlays::overlays::2.x', compatibleOverlaysManager);
|
||||||
|
```
|
||||||
|
|
||||||
|
See it in action
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run start:singleton
|
||||||
|
```
|
||||||
|
|
||||||
|
and [the code](./demo/singleton/demo-app.js).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Example C (singleton and complex patching on app level)
|
||||||
|
|
||||||
|
The breaking change in `OverlayManager` was converting a property to a function and a rename of a function.
|
||||||
|
|
||||||
|
- `blockBody` => `_blockBody`
|
||||||
|
- `block()` => `blockBody()`
|
||||||
|
- `unBlock()` => `unBlockBody()`
|
||||||
|
|
||||||
|
e.g. what is impossible to make compatible with a single instance is to have `blockBody` act as a property for 1.x and as a function `blockBody()` for 2.x.
|
||||||
|
|
||||||
|
So how do we solve it then?
|
||||||
|
|
||||||
|
We will make 2 separate instances of the `OverlayManager`.
|
||||||
|
|
||||||
|
```js
|
||||||
|
compatibleManager1 = new CompatibleManager1(); // 1.x
|
||||||
|
compatibleManager2 = new CompatibleManager2(); // 2.x
|
||||||
|
console.log(typeof compatibleManager1.blockBody); // Boolean
|
||||||
|
console.log(typeof compatibleManager2.blockBody); // Function
|
||||||
|
|
||||||
|
// and override
|
||||||
|
singletonManager.set('overlays::overlays::1.x', compatibleManager1);
|
||||||
|
singletonManager.set('overlays::overlays::2.x', compatibleManager2);
|
||||||
|
```
|
||||||
|
|
||||||
|
and they are "compatible" to each other because they sync the important data to each other.
|
||||||
|
e.g. even though there are 2 instances there is only `one` dom element inserted which both can write to.
|
||||||
|
When syncing data only the initiator will update the dom.
|
||||||
|
This makes sure even though functions and data is separate it will be always consistent.
|
||||||
|
|
||||||
|
See it in action
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run start:singleton-complex
|
||||||
|
```
|
||||||
|
|
||||||
|
and [the code](./demo/singleton-complex/demo-app.js).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How does it work?
|
||||||
|
|
||||||
|
As a user you can override what the import of `overlays/instance.js` provides.
|
||||||
|
You do this via a singletonManager and a "magic" string.
|
||||||
|
|
||||||
|
- Reason be that you can target ranges of versions
|
||||||
|
|
||||||
|
```js
|
||||||
|
singletonManager.set('overlays::overlays::1.x', compatibleManager1);
|
||||||
|
singletonManager.set('overlays::overlays::2.x', compatibleManager2);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Potential Improvements
|
||||||
|
|
||||||
|
Potentially we could have "range", "exacts version" and symbol for unique filename.
|
||||||
|
So you can override with increasing specificity.
|
||||||
|
If you have a use case for that please open an issue.
|
||||||
|
|
||||||
|
## Non Goals
|
||||||
|
|
||||||
|
Making sure that there are only 2 major versions of a specific packages.
|
||||||
|
npm is not meant to handle it - and it never will
|
||||||
|
|
||||||
|
```txt
|
||||||
|
my-app
|
||||||
|
├─┬ feat-a@x
|
||||||
|
│ └── foo@2.x
|
||||||
|
├─┬ feat-a@x
|
||||||
|
│ └── foo@2.x
|
||||||
|
└── foo@1.x
|
||||||
|
```
|
||||||
|
|
||||||
|
dedupe works by moving dependencies up the tree
|
||||||
|
|
||||||
|
```txt
|
||||||
|
// this app
|
||||||
|
my-app
|
||||||
|
my-app/node_modules/feat-a/node_modules/foo
|
||||||
|
my-app/node_modules/foo
|
||||||
|
|
||||||
|
// can become if versions match
|
||||||
|
my-app
|
||||||
|
my-app/node_modules/foo
|
||||||
|
```
|
||||||
|
|
||||||
|
in there `feat-a` will grab the version of it's "parent" because of the node resolution system.
|
||||||
|
If however the versions do not match or there is no "common" folder to move it up to then it needs to be "duplicated" by npm/yarn.
|
||||||
|
|
||||||
|
Only by using a more controlled way like
|
||||||
|
|
||||||
|
- [import-maps](https://github.com/WICG/import-maps)
|
||||||
|
- [yarn resolutions](https://classic.yarnpkg.com/en/docs/selective-version-resolutions/)
|
||||||
|
|
||||||
|
you can "hard" code it to the same versions.
|
||||||
91
packages/singleton-manager/demo/fail/demo-app.js
Normal file
91
packages/singleton-manager/demo/fail/demo-app.js
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
import { LitElement, css, html } from 'lit-element';
|
||||||
|
|
||||||
|
import 'page-a/page-a.js';
|
||||||
|
import 'page-b/page-b.js';
|
||||||
|
|
||||||
|
class DemoApp extends LitElement {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.page = 'A';
|
||||||
|
}
|
||||||
|
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
page: { type: String },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles() {
|
||||||
|
return css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
max-width: 680px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
padding: 0 10px 10px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border: none;
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
background: #0069ed;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: center;
|
||||||
|
transition: background 250ms ease-in-out, transform 150ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover,
|
||||||
|
button:focus {
|
||||||
|
background: #0053ba;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:focus {
|
||||||
|
outline: 1px solid #fff;
|
||||||
|
outline-offset: -4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:active {
|
||||||
|
transform: scale(0.99);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.active {
|
||||||
|
background: #33a43f;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<h1>Demo App</h1>
|
||||||
|
<nav>
|
||||||
|
<button
|
||||||
|
class=${this.page === 'A' ? 'active' : ''}
|
||||||
|
@click=${() => {
|
||||||
|
this.page = 'A';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Page A
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class=${this.page === 'B' ? 'active' : ''}
|
||||||
|
@click=${() => {
|
||||||
|
this.page = 'B';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Page B
|
||||||
|
</button>
|
||||||
|
</nav>
|
||||||
|
${this.page === 'A' ? html` <page-a></page-a> ` : html` <page-b></page-b> `}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('demo-app', DemoApp);
|
||||||
11
packages/singleton-manager/demo/fail/index.html
Normal file
11
packages/singleton-manager/demo/fail/index.html
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<demo-app>Loading App...</demo-app>
|
||||||
|
<script type="module" src="./demo-app.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
29
packages/singleton-manager/demo/fail/node_modules/overlays/index.js
generated
vendored
Normal file
29
packages/singleton-manager/demo/fail/node_modules/overlays/index.js
generated
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
export class OverlaysManager {
|
||||||
|
name = 'OverlayManager 1.x';
|
||||||
|
|
||||||
|
blockBody = false;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this._setupBlocker();
|
||||||
|
}
|
||||||
|
|
||||||
|
_setupBlocker() {
|
||||||
|
const blocker = document.createElement('div');
|
||||||
|
blocker.setAttribute('style', 'border: 2px solid #8d0606; margin: 10px; padding: 10px; width: 140px; text-align: center;');
|
||||||
|
blocker.innerText = `Blocker for ${this.name}`;
|
||||||
|
|
||||||
|
document.body.appendChild(blocker);
|
||||||
|
|
||||||
|
this.blocker = blocker;
|
||||||
|
}
|
||||||
|
|
||||||
|
block() {
|
||||||
|
this.blockBody = true;
|
||||||
|
this.blocker.style.backgroundColor = '#ff6161';
|
||||||
|
}
|
||||||
|
|
||||||
|
unBlock() {
|
||||||
|
this.blockBody = false;
|
||||||
|
this.blocker.style.backgroundColor = 'transparent';
|
||||||
|
}
|
||||||
|
}
|
||||||
3
packages/singleton-manager/demo/fail/node_modules/overlays/instance.js
generated
vendored
Normal file
3
packages/singleton-manager/demo/fail/node_modules/overlays/instance.js
generated
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
import { OverlaysManager } from './index.js';
|
||||||
|
|
||||||
|
export const overlays = new OverlaysManager();
|
||||||
4
packages/singleton-manager/demo/fail/node_modules/overlays/package.json
generated
vendored
Normal file
4
packages/singleton-manager/demo/fail/node_modules/overlays/package.json
generated
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"name": "overlays",
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
7
packages/singleton-manager/demo/fail/node_modules/page-a/package.json
generated
vendored
Normal file
7
packages/singleton-manager/demo/fail/node_modules/page-a/package.json
generated
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"name": "page-a",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"overlays": "^1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
34
packages/singleton-manager/demo/fail/node_modules/page-a/page-a.js
generated
vendored
Normal file
34
packages/singleton-manager/demo/fail/node_modules/page-a/page-a.js
generated
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { LitElement, html, css } from 'lit-element';
|
||||||
|
import { overlays } from 'overlays/instance.js';
|
||||||
|
|
||||||
|
export class PageA extends LitElement {
|
||||||
|
static get styles() {
|
||||||
|
return css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
padding: 10px;
|
||||||
|
border: 2px solid #ccc;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<h3>I am page A</h3>
|
||||||
|
<p>Overlays Status:</p>
|
||||||
|
<p>Name: ${overlays.name}</p>
|
||||||
|
<p>Blocked: ${overlays.blockBody}</p>
|
||||||
|
<button @click=${() => {
|
||||||
|
overlays.block(); this.requestUpdate();
|
||||||
|
}}>block</button>
|
||||||
|
<button @click=${() => {
|
||||||
|
overlays.unBlock(); this.requestUpdate();
|
||||||
|
}}>un-block</button>
|
||||||
|
<button @click=${() => {
|
||||||
|
this.requestUpdate();
|
||||||
|
}}>refresh</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('page-a', PageA);
|
||||||
29
packages/singleton-manager/demo/fail/node_modules/page-b/node_modules/overlays/index.js
generated
vendored
Normal file
29
packages/singleton-manager/demo/fail/node_modules/page-b/node_modules/overlays/index.js
generated
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
export class OverlaysManager {
|
||||||
|
name = 'OverlayManager 2.x';
|
||||||
|
|
||||||
|
_blockBody = false;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this._setupBlocker();
|
||||||
|
}
|
||||||
|
|
||||||
|
_setupBlocker() {
|
||||||
|
const blocker = document.createElement('div');
|
||||||
|
blocker.setAttribute('style', 'border: 2px solid #8d0606; margin: 10px; padding: 10px; width: 140px; text-align: center;');
|
||||||
|
blocker.innerText = `Blocker for ${this.name}`;
|
||||||
|
|
||||||
|
document.body.appendChild(blocker);
|
||||||
|
|
||||||
|
this.blocker = blocker;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockBody() {
|
||||||
|
this._blockBody = true;
|
||||||
|
this.blocker.style.backgroundColor = '#ff6161';
|
||||||
|
}
|
||||||
|
|
||||||
|
unBlockBody() {
|
||||||
|
this._blockBody = false;
|
||||||
|
this.blocker.style.backgroundColor = 'transparent';
|
||||||
|
}
|
||||||
|
}
|
||||||
3
packages/singleton-manager/demo/fail/node_modules/page-b/node_modules/overlays/instance.js
generated
vendored
Normal file
3
packages/singleton-manager/demo/fail/node_modules/page-b/node_modules/overlays/instance.js
generated
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
import { OverlaysManager } from './index.js';
|
||||||
|
|
||||||
|
export const overlays = new OverlaysManager();
|
||||||
4
packages/singleton-manager/demo/fail/node_modules/page-b/node_modules/overlays/package.json
generated
vendored
Normal file
4
packages/singleton-manager/demo/fail/node_modules/page-b/node_modules/overlays/package.json
generated
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"name": "overlays",
|
||||||
|
"version": "2.0.0"
|
||||||
|
}
|
||||||
7
packages/singleton-manager/demo/fail/node_modules/page-b/package.json
generated
vendored
Normal file
7
packages/singleton-manager/demo/fail/node_modules/page-b/package.json
generated
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"name": "page-b",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"overlays": "^2.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
34
packages/singleton-manager/demo/fail/node_modules/page-b/page-b.js
generated
vendored
Normal file
34
packages/singleton-manager/demo/fail/node_modules/page-b/page-b.js
generated
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { LitElement, html, css } from 'lit-element';
|
||||||
|
import { overlays } from 'overlays/instance.js';
|
||||||
|
|
||||||
|
export class PageB extends LitElement {
|
||||||
|
static get styles() {
|
||||||
|
return css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
padding: 10px;
|
||||||
|
border: 2px solid #ccc;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<h3>I am page B</h3>
|
||||||
|
<p>Overlays Status:</p>
|
||||||
|
<p>Name: ${overlays.name}</p>
|
||||||
|
<p>Blocked: ${overlays._blockBody}</p>
|
||||||
|
<button @click=${() => {
|
||||||
|
overlays.blockBody(); this.requestUpdate();
|
||||||
|
}}>block</button>
|
||||||
|
<button @click=${() => {
|
||||||
|
overlays.unBlockBody(); this.requestUpdate();
|
||||||
|
}}>un-block</button>
|
||||||
|
<button @click=${() => {
|
||||||
|
this.requestUpdate();
|
||||||
|
}}>refresh</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('page-b', PageB);
|
||||||
8
packages/singleton-manager/demo/fail/package.json
Normal file
8
packages/singleton-manager/demo/fail/package.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"name": "fail-demo-app",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"page-a": "^1.0.0",
|
||||||
|
"page-b": "^1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
6
packages/singleton-manager/demo/fail/server.js
Normal file
6
packages/singleton-manager/demo/fail/server.js
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
module.exports = {
|
||||||
|
rootDir: '../../',
|
||||||
|
appIndex: 'packages/singleton-manager/demo/fail/index.html',
|
||||||
|
nodeResolve: true,
|
||||||
|
open: true,
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
import { LitElement, css, html } from 'lit-element';
|
||||||
|
|
||||||
|
import './overlayCompatibility.js';
|
||||||
|
|
||||||
|
import 'page-a/page-a.js';
|
||||||
|
import 'page-b/page-b.js';
|
||||||
|
|
||||||
|
class DemoApp extends LitElement {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.page = 'A';
|
||||||
|
}
|
||||||
|
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
page: { type: String },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles() {
|
||||||
|
return css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
max-width: 680px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
padding: 0 10px 10px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border: none;
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
background: #0069ed;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: center;
|
||||||
|
transition: background 250ms ease-in-out, transform 150ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover,
|
||||||
|
button:focus {
|
||||||
|
background: #0053ba;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:focus {
|
||||||
|
outline: 1px solid #fff;
|
||||||
|
outline-offset: -4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:active {
|
||||||
|
transform: scale(0.99);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.active {
|
||||||
|
background: #33a43f;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<h1>Demo App</h1>
|
||||||
|
<nav>
|
||||||
|
<button
|
||||||
|
class=${this.page === 'A' ? 'active' : ''}
|
||||||
|
@click=${() => {
|
||||||
|
this.page = 'A';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Page A
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class=${this.page === 'B' ? 'active' : ''}
|
||||||
|
@click=${() => {
|
||||||
|
this.page = 'B';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Page B
|
||||||
|
</button>
|
||||||
|
</nav>
|
||||||
|
${this.page === 'A' ? html` <page-a></page-a> ` : html` <page-b></page-b> `}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('demo-app', DemoApp);
|
||||||
11
packages/singleton-manager/demo/singleton-complex/index.html
Normal file
11
packages/singleton-manager/demo/singleton-complex/index.html
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<demo-app>Loading App...</demo-app>
|
||||||
|
<script type="module" src="./demo-app.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
29
packages/singleton-manager/demo/singleton-complex/node_modules/overlays/index.js
generated
vendored
Normal file
29
packages/singleton-manager/demo/singleton-complex/node_modules/overlays/index.js
generated
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
export class OverlaysManager {
|
||||||
|
name = 'OverlayManager 1.x';
|
||||||
|
|
||||||
|
blockBody = false;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this._setupBlocker();
|
||||||
|
}
|
||||||
|
|
||||||
|
_setupBlocker() {
|
||||||
|
const blocker = document.createElement('div');
|
||||||
|
blocker.setAttribute('style', 'border: 2px solid #8d0606; margin: 10px; padding: 10px; width: 140px; text-align: center;');
|
||||||
|
blocker.innerText = `Blocker for ${this.name}`;
|
||||||
|
|
||||||
|
document.body.appendChild(blocker);
|
||||||
|
|
||||||
|
this.blocker = blocker;
|
||||||
|
}
|
||||||
|
|
||||||
|
block() {
|
||||||
|
this.blockBody = true;
|
||||||
|
this.blocker.style.backgroundColor = '#ff6161';
|
||||||
|
}
|
||||||
|
|
||||||
|
unBlock() {
|
||||||
|
this.blockBody = false;
|
||||||
|
this.blocker.style.backgroundColor = 'transparent';
|
||||||
|
}
|
||||||
|
}
|
||||||
4
packages/singleton-manager/demo/singleton-complex/node_modules/overlays/instance.js
generated
vendored
Normal file
4
packages/singleton-manager/demo/singleton-complex/node_modules/overlays/instance.js
generated
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { OverlaysManager } from './index.js';
|
||||||
|
import { singletonManager } from '../../../../index.js';
|
||||||
|
|
||||||
|
export const overlays = singletonManager.get('overlays::overlays::1.x') || new OverlaysManager();
|
||||||
4
packages/singleton-manager/demo/singleton-complex/node_modules/overlays/package.json
generated
vendored
Normal file
4
packages/singleton-manager/demo/singleton-complex/node_modules/overlays/package.json
generated
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"name": "overlays",
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
7
packages/singleton-manager/demo/singleton-complex/node_modules/page-a/package.json
generated
vendored
Normal file
7
packages/singleton-manager/demo/singleton-complex/node_modules/page-a/package.json
generated
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"name": "page-a",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"overlays": "^1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
35
packages/singleton-manager/demo/singleton-complex/node_modules/page-a/page-a.js
generated
vendored
Normal file
35
packages/singleton-manager/demo/singleton-complex/node_modules/page-a/page-a.js
generated
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { LitElement, html, css } from 'lit-element';
|
||||||
|
import { overlays } from 'overlays/instance.js';
|
||||||
|
|
||||||
|
export class PageA extends LitElement {
|
||||||
|
static get styles() {
|
||||||
|
return css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
padding: 10px;
|
||||||
|
border: 2px solid #ccc;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<h3>I am page A</h3>
|
||||||
|
<p>Overlays Status:</p>
|
||||||
|
<p>Name: ${overlays.name}</p>
|
||||||
|
<p>Blocked: ${overlays.blockBody}</p>
|
||||||
|
<button @click=${() => {
|
||||||
|
overlays.block(); this.requestUpdate();
|
||||||
|
}}>block</button>
|
||||||
|
<button @click=${() => {
|
||||||
|
overlays.unBlock(); this.requestUpdate();
|
||||||
|
}}>un-block</button>
|
||||||
|
<button @click=${() => {
|
||||||
|
this.requestUpdate();
|
||||||
|
}}>refresh</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
customElements.define('page-a', PageA);
|
||||||
29
packages/singleton-manager/demo/singleton-complex/node_modules/page-b/node_modules/overlays/index.js
generated
vendored
Normal file
29
packages/singleton-manager/demo/singleton-complex/node_modules/page-b/node_modules/overlays/index.js
generated
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
export class OverlaysManager {
|
||||||
|
name = 'OverlayManager 2.x';
|
||||||
|
|
||||||
|
_blockBody = false;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this._setupBlocker();
|
||||||
|
}
|
||||||
|
|
||||||
|
_setupBlocker() {
|
||||||
|
const blocker = document.createElement('div');
|
||||||
|
blocker.setAttribute('style', 'border: 2px solid #8d0606; margin: 10px; padding: 10px; width: 140px; text-align: center;');
|
||||||
|
blocker.innerText = `Blocker for ${this.name}`;
|
||||||
|
|
||||||
|
document.body.appendChild(blocker);
|
||||||
|
|
||||||
|
this.blocker = blocker;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockBody() {
|
||||||
|
this._blockBody = true;
|
||||||
|
this.blocker.style.backgroundColor = '#ff6161';
|
||||||
|
}
|
||||||
|
|
||||||
|
unBlockBody() {
|
||||||
|
this._blockBody = false;
|
||||||
|
this.blocker.style.backgroundColor = 'transparent';
|
||||||
|
}
|
||||||
|
}
|
||||||
4
packages/singleton-manager/demo/singleton-complex/node_modules/page-b/node_modules/overlays/instance.js
generated
vendored
Normal file
4
packages/singleton-manager/demo/singleton-complex/node_modules/page-b/node_modules/overlays/instance.js
generated
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { OverlaysManager } from './index.js';
|
||||||
|
import { singletonManager } from '../../../../../../index.js';
|
||||||
|
|
||||||
|
export const overlays = singletonManager.get('overlays::overlays::2.x') || new OverlaysManager();
|
||||||
4
packages/singleton-manager/demo/singleton-complex/node_modules/page-b/node_modules/overlays/package.json
generated
vendored
Normal file
4
packages/singleton-manager/demo/singleton-complex/node_modules/page-b/node_modules/overlays/package.json
generated
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"name": "overlays",
|
||||||
|
"version": "2.0.0"
|
||||||
|
}
|
||||||
7
packages/singleton-manager/demo/singleton-complex/node_modules/page-b/package.json
generated
vendored
Normal file
7
packages/singleton-manager/demo/singleton-complex/node_modules/page-b/package.json
generated
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"name": "page-b",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"overlays": "^2.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
49
packages/singleton-manager/demo/singleton-complex/node_modules/page-b/page-b.js
generated
vendored
Normal file
49
packages/singleton-manager/demo/singleton-complex/node_modules/page-b/page-b.js
generated
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
import { LitElement, html, css } from 'lit-element';
|
||||||
|
import { overlays } from 'overlays/instance.js';
|
||||||
|
|
||||||
|
export class PageB extends LitElement {
|
||||||
|
getInstance(sym, fallback) {
|
||||||
|
const ev = new CustomEvent('request-instance', {
|
||||||
|
detail: { key: sym },
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
composed: true,
|
||||||
|
});
|
||||||
|
this.dispatchEvent(ev);
|
||||||
|
return ev.detail.instance || fallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles() {
|
||||||
|
return css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
padding: 10px;
|
||||||
|
border: 2px solid #ccc;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<h3>I am page B</h3>
|
||||||
|
<p>Overlays Status:</p>
|
||||||
|
<p>Name: ${overlays.name}</p>
|
||||||
|
<p>Blocked: ${overlays._blockBody}</p>
|
||||||
|
<button @click=${() => {
|
||||||
|
overlays.blockBody(); this.requestUpdate();
|
||||||
|
}}>block</button>
|
||||||
|
<button @click=${() => {
|
||||||
|
overlays.unBlockBody(); this.requestUpdate();
|
||||||
|
}}>un-block</button>
|
||||||
|
<button @click=${() => {
|
||||||
|
this.requestUpdate();
|
||||||
|
}}>refresh</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('page-b', PageB);
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
import { OverlaysManager } from 'overlays';
|
||||||
|
import { singletonManager } from '../../index.js';
|
||||||
|
import { OverlaysManager as OverlaysManager2 } from './node_modules/page-b/node_modules/overlays/index.js';
|
||||||
|
|
||||||
|
let compatibleManager1;
|
||||||
|
let compatibleManager2;
|
||||||
|
|
||||||
|
const blocker = document.createElement('div');
|
||||||
|
blocker.setAttribute(
|
||||||
|
'style',
|
||||||
|
'border: 2px solid #8d0606; margin: 10px; padding: 10px; width: 140px; text-align: center;',
|
||||||
|
);
|
||||||
|
blocker.innerText = `Shared Blocker for App`;
|
||||||
|
document.body.appendChild(blocker);
|
||||||
|
|
||||||
|
class CompatibleManager1 extends OverlaysManager {
|
||||||
|
name = 'Compatible1 from App';
|
||||||
|
|
||||||
|
block(sync = true) {
|
||||||
|
super.block();
|
||||||
|
if (sync) {
|
||||||
|
compatibleManager2.blockBody(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unBlock(sync = true) {
|
||||||
|
super.unBlock();
|
||||||
|
if (sync) {
|
||||||
|
compatibleManager2.unBlockBody(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_setupBlocker() {
|
||||||
|
this.blocker = blocker;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CompatibleManager2 extends OverlaysManager2 {
|
||||||
|
name = 'Compatible2 from App';
|
||||||
|
|
||||||
|
blockBody(sync = true) {
|
||||||
|
super.blockBody();
|
||||||
|
if (sync) {
|
||||||
|
compatibleManager1.block();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unBlockBody(sync = true) {
|
||||||
|
super.unBlockBody();
|
||||||
|
if (sync) {
|
||||||
|
compatibleManager1.unBlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_setupBlocker() {
|
||||||
|
this.blocker = blocker;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compatibleManager1 = new CompatibleManager1();
|
||||||
|
compatibleManager2 = new CompatibleManager2();
|
||||||
|
|
||||||
|
singletonManager.set('overlays::overlays::1.x', compatibleManager1);
|
||||||
|
singletonManager.set('overlays::overlays::2.x', compatibleManager2);
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"name": "fail-demo-app",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"page-a": "^1.0.0",
|
||||||
|
"page-b": "^1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
module.exports = {
|
||||||
|
rootDir: '../../',
|
||||||
|
appIndex: 'packages/singleton-manager/demo/singleton-complex/index.html',
|
||||||
|
nodeResolve: true,
|
||||||
|
open: true,
|
||||||
|
};
|
||||||
93
packages/singleton-manager/demo/singleton/demo-app.js
Normal file
93
packages/singleton-manager/demo/singleton/demo-app.js
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
import { LitElement, css, html } from 'lit-element';
|
||||||
|
|
||||||
|
import './overlayCompatibility.js';
|
||||||
|
|
||||||
|
import 'page-a/page-a.js';
|
||||||
|
import 'page-b/page-b.js';
|
||||||
|
|
||||||
|
class DemoApp extends LitElement {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.page = 'A';
|
||||||
|
}
|
||||||
|
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
page: { type: String },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles() {
|
||||||
|
return css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
max-width: 680px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
padding: 0 10px 10px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border: none;
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
background: #0069ed;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: center;
|
||||||
|
transition: background 250ms ease-in-out, transform 150ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover,
|
||||||
|
button:focus {
|
||||||
|
background: #0053ba;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:focus {
|
||||||
|
outline: 1px solid #fff;
|
||||||
|
outline-offset: -4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:active {
|
||||||
|
transform: scale(0.99);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.active {
|
||||||
|
background: #33a43f;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<h1>Demo App</h1>
|
||||||
|
<nav>
|
||||||
|
<button
|
||||||
|
class=${this.page === 'A' ? 'active' : ''}
|
||||||
|
@click=${() => {
|
||||||
|
this.page = 'A';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Page A
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class=${this.page === 'B' ? 'active' : ''}
|
||||||
|
@click=${() => {
|
||||||
|
this.page = 'B';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Page B
|
||||||
|
</button>
|
||||||
|
</nav>
|
||||||
|
${this.page === 'A' ? html` <page-a></page-a> ` : html` <page-b></page-b> `}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('demo-app', DemoApp);
|
||||||
11
packages/singleton-manager/demo/singleton/index.html
Normal file
11
packages/singleton-manager/demo/singleton/index.html
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<demo-app>Loading App...</demo-app>
|
||||||
|
<script type="module" src="./demo-app.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
29
packages/singleton-manager/demo/singleton/node_modules/overlays/index.js
generated
vendored
Normal file
29
packages/singleton-manager/demo/singleton/node_modules/overlays/index.js
generated
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
export class OverlaysManager {
|
||||||
|
name = 'OverlayManager 1.x';
|
||||||
|
|
||||||
|
blockBody = false;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this._setupBlocker();
|
||||||
|
}
|
||||||
|
|
||||||
|
_setupBlocker() {
|
||||||
|
const blocker = document.createElement('div');
|
||||||
|
blocker.setAttribute('style', 'border: 2px solid #8d0606; margin: 10px; padding: 10px; width: 140px; text-align: center;');
|
||||||
|
blocker.innerText = `Blocker for ${this.name}`;
|
||||||
|
|
||||||
|
document.body.appendChild(blocker);
|
||||||
|
|
||||||
|
this.blocker = blocker;
|
||||||
|
}
|
||||||
|
|
||||||
|
block() {
|
||||||
|
this.blockBody = true;
|
||||||
|
this.blocker.style.backgroundColor = '#ff6161';
|
||||||
|
}
|
||||||
|
|
||||||
|
unBlock() {
|
||||||
|
this.blockBody = false;
|
||||||
|
this.blocker.style.backgroundColor = 'transparent';
|
||||||
|
}
|
||||||
|
}
|
||||||
4
packages/singleton-manager/demo/singleton/node_modules/overlays/instance.js
generated
vendored
Normal file
4
packages/singleton-manager/demo/singleton/node_modules/overlays/instance.js
generated
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { OverlaysManager } from './index.js';
|
||||||
|
import { singletonManager } from '../../../../index.js';
|
||||||
|
|
||||||
|
export const overlays = singletonManager.get('overlays::overlays::1.x') || new OverlaysManager();
|
||||||
4
packages/singleton-manager/demo/singleton/node_modules/overlays/package.json
generated
vendored
Normal file
4
packages/singleton-manager/demo/singleton/node_modules/overlays/package.json
generated
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"name": "overlays",
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
7
packages/singleton-manager/demo/singleton/node_modules/page-a/package.json
generated
vendored
Normal file
7
packages/singleton-manager/demo/singleton/node_modules/page-a/package.json
generated
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"name": "page-a",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"overlays": "^1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
34
packages/singleton-manager/demo/singleton/node_modules/page-a/page-a.js
generated
vendored
Normal file
34
packages/singleton-manager/demo/singleton/node_modules/page-a/page-a.js
generated
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { LitElement, html, css } from 'lit-element';
|
||||||
|
import { overlays } from 'overlays/instance.js';
|
||||||
|
|
||||||
|
export class PageA extends LitElement {
|
||||||
|
static get styles() {
|
||||||
|
return css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
padding: 10px;
|
||||||
|
border: 2px solid #ccc;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<h3>I am page A</h3>
|
||||||
|
<p>Overlays Status:</p>
|
||||||
|
<p>Name: ${overlays.name}</p>
|
||||||
|
<p>Blocked: ${overlays.blockBody}</p>
|
||||||
|
<button @click=${() => {
|
||||||
|
overlays.block(); this.requestUpdate();
|
||||||
|
}}>block</button>
|
||||||
|
<button @click=${() => {
|
||||||
|
overlays.unBlock(); this.requestUpdate();
|
||||||
|
}}>un-block</button>
|
||||||
|
<button @click=${() => {
|
||||||
|
this.requestUpdate();
|
||||||
|
}}>refresh</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('page-a', PageA);
|
||||||
29
packages/singleton-manager/demo/singleton/node_modules/page-b/node_modules/overlays/index.js
generated
vendored
Normal file
29
packages/singleton-manager/demo/singleton/node_modules/page-b/node_modules/overlays/index.js
generated
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
export class OverlaysManager {
|
||||||
|
name = 'OverlayManager 2.x';
|
||||||
|
|
||||||
|
blockBody = false;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this._setupBlocker();
|
||||||
|
}
|
||||||
|
|
||||||
|
_setupBlocker() {
|
||||||
|
const blocker = document.createElement('div');
|
||||||
|
blocker.setAttribute('style', 'border: 2px solid #8d0606; margin: 10px; padding: 10px; width: 140px; text-align: center;');
|
||||||
|
blocker.innerText = `Blocker for ${this.name}`;
|
||||||
|
|
||||||
|
document.body.appendChild(blocker);
|
||||||
|
|
||||||
|
this.blocker = blocker;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockingBody() {
|
||||||
|
this.blockBody = true;
|
||||||
|
this.blocker.style.backgroundColor = '#ff6161';
|
||||||
|
}
|
||||||
|
|
||||||
|
unBlockingBody() {
|
||||||
|
this.blockBody = false;
|
||||||
|
this.blocker.style.backgroundColor = 'transparent';
|
||||||
|
}
|
||||||
|
}
|
||||||
4
packages/singleton-manager/demo/singleton/node_modules/page-b/node_modules/overlays/instance.js
generated
vendored
Normal file
4
packages/singleton-manager/demo/singleton/node_modules/page-b/node_modules/overlays/instance.js
generated
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { OverlaysManager } from './index.js';
|
||||||
|
import { singletonManager } from '../../../../../../index.js';
|
||||||
|
|
||||||
|
export const overlays = singletonManager.get('overlays::overlays::2.x') || new OverlaysManager();
|
||||||
4
packages/singleton-manager/demo/singleton/node_modules/page-b/node_modules/overlays/package.json
generated
vendored
Normal file
4
packages/singleton-manager/demo/singleton/node_modules/page-b/node_modules/overlays/package.json
generated
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"name": "overlays",
|
||||||
|
"version": "2.0.0"
|
||||||
|
}
|
||||||
7
packages/singleton-manager/demo/singleton/node_modules/page-b/package.json
generated
vendored
Normal file
7
packages/singleton-manager/demo/singleton/node_modules/page-b/package.json
generated
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"name": "page-b",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"overlays": "^2.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
34
packages/singleton-manager/demo/singleton/node_modules/page-b/page-b.js
generated
vendored
Normal file
34
packages/singleton-manager/demo/singleton/node_modules/page-b/page-b.js
generated
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { LitElement, html, css } from 'lit-element';
|
||||||
|
import { overlays } from 'overlays/instance.js';
|
||||||
|
|
||||||
|
export class PageB extends LitElement {
|
||||||
|
static get styles() {
|
||||||
|
return css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
padding: 10px;
|
||||||
|
border: 2px solid #ccc;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<h3>I am page B</h3>
|
||||||
|
<p>Overlays Status:</p>
|
||||||
|
<p>Name: ${overlays.name}</p>
|
||||||
|
<p>Blocked: ${overlays.blockBody}</p>
|
||||||
|
<button @click=${() => {
|
||||||
|
overlays.blockingBody(); this.requestUpdate();
|
||||||
|
}}>block</button>
|
||||||
|
<button @click=${() => {
|
||||||
|
overlays.unBlockingBody(); this.requestUpdate();
|
||||||
|
}}>un-block</button>
|
||||||
|
<button @click=${() => {
|
||||||
|
this.requestUpdate();
|
||||||
|
}}>refresh</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('page-b', PageB);
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { OverlaysManager } from 'overlays';
|
||||||
|
import { singletonManager } from '../../index.js';
|
||||||
|
|
||||||
|
class CompatibleManager extends OverlaysManager {
|
||||||
|
name = 'Compatible from App';
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.blocker.innerText = `Blocker for ${this.name}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockingBody() {
|
||||||
|
this.block();
|
||||||
|
}
|
||||||
|
|
||||||
|
unBlockingBody() {
|
||||||
|
this.unBlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const compatibleManager = new CompatibleManager();
|
||||||
|
|
||||||
|
singletonManager.set('overlays::overlays::1.x', compatibleManager);
|
||||||
|
singletonManager.set('overlays::overlays::2.x', compatibleManager);
|
||||||
8
packages/singleton-manager/demo/singleton/package.json
Normal file
8
packages/singleton-manager/demo/singleton/package.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"name": "fail-demo-app",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"page-a": "^1.0.0",
|
||||||
|
"page-b": "^1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
6
packages/singleton-manager/demo/singleton/server.js
Normal file
6
packages/singleton-manager/demo/singleton/server.js
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
module.exports = {
|
||||||
|
rootDir: '../../',
|
||||||
|
appIndex: 'packages/singleton-manager/demo/singleton/index.html',
|
||||||
|
nodeResolve: true,
|
||||||
|
open: true,
|
||||||
|
};
|
||||||
5
packages/singleton-manager/index.js
Normal file
5
packages/singleton-manager/index.js
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { SingletonManagerClass } from './src/SingletonManagerClass.js';
|
||||||
|
|
||||||
|
export { SingletonManagerClass } from './src/SingletonManagerClass.js';
|
||||||
|
|
||||||
|
export const singletonManager = new SingletonManagerClass();
|
||||||
38
packages/singleton-manager/package.json
Normal file
38
packages/singleton-manager/package.json
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
{
|
||||||
|
"name": "singleton-manager",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"description": "Manage singletons across multiple major versions so they converge to a single instance",
|
||||||
|
"author": "ing-bank",
|
||||||
|
"homepage": "https://github.com/ing-bank/lion/",
|
||||||
|
"license": "MIT",
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/ing-bank/lion.git",
|
||||||
|
"directory": "packages/singleton-manager"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"prepublishOnly": "../../scripts/npm-prepublish.js",
|
||||||
|
"start:fail": "es-dev-server -c demo/fail/server.js",
|
||||||
|
"start:singleton": "es-dev-server -c demo/singleton/server.js",
|
||||||
|
"start:singleton-complex": "es-dev-server -c demo/singleton-complex/server.js",
|
||||||
|
"test": "cd ../../ && yarn test:browser --grep \"packages/singleton-manager/test/**/*.test.js\"",
|
||||||
|
"test:watch": "cd ../../ && yarn test:browser:watch --grep \"packages/singleton-manager/test/**/*.test.js\""
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"lion",
|
||||||
|
"singleton-manager"
|
||||||
|
],
|
||||||
|
"main": "index.js",
|
||||||
|
"module": "index.js",
|
||||||
|
"sideEffects": false,
|
||||||
|
"files": [
|
||||||
|
"docs",
|
||||||
|
"src",
|
||||||
|
"test",
|
||||||
|
"test-helpers",
|
||||||
|
"*.js"
|
||||||
|
]
|
||||||
|
}
|
||||||
20
packages/singleton-manager/src/SingletonManagerClass.js
Normal file
20
packages/singleton-manager/src/SingletonManagerClass.js
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
export class SingletonManagerClass {
|
||||||
|
constructor() {
|
||||||
|
this._map = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
set(key, value) {
|
||||||
|
if (this.has(key)) {
|
||||||
|
throw new Error(`The key "${key}" is already defined and can not be overridden.`);
|
||||||
|
}
|
||||||
|
this._map.set(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
get(key) {
|
||||||
|
return this._map.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
has(key) {
|
||||||
|
return this._map.has(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { expect } from '@open-wc/testing';
|
||||||
|
|
||||||
|
import { SingletonManagerClass } from '../src/SingletonManagerClass.js';
|
||||||
|
|
||||||
|
describe('SingletonManagerClass', () => {
|
||||||
|
it('returns undefined and has false if not set', async () => {
|
||||||
|
const mngr = new SingletonManagerClass();
|
||||||
|
expect(mngr.get('overlays/overlays.js::0.13.x')).to.be.undefined;
|
||||||
|
expect(mngr.has('overlays/overlays.js::0.13.x')).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('return value and has true if set', () => {
|
||||||
|
const mngr = new SingletonManagerClass();
|
||||||
|
mngr.set('overlays/overlays.js::0.13.x', 'is-set');
|
||||||
|
expect(mngr.get('overlays/overlays.js::0.13.x')).to.equal('is-set');
|
||||||
|
expect(mngr.has('overlays/overlays.js::0.13.x')).to.be.true;
|
||||||
|
// make sure non set values are still correct
|
||||||
|
expect(mngr.get('overlays/overlays.js::0.14.x')).to.be.undefined;
|
||||||
|
expect(mngr.has('overlays/overlays.js::0.14.x')).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws if an app tries to set an existing key again', () => {
|
||||||
|
const mngr = new SingletonManagerClass();
|
||||||
|
mngr.set('overlays/overlays.js::0.13.x', 'is-set');
|
||||||
|
expect(() => {
|
||||||
|
mngr.set('overlays/overlays.js::0.13.x', 'new-set');
|
||||||
|
}).to.throw(
|
||||||
|
'The key "overlays/overlays.js::0.13.x" is already defined and can not be overridden.',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
15
packages/singleton-manager/test/singleton-demo.test.js
Normal file
15
packages/singleton-manager/test/singleton-demo.test.js
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { expect } from '@open-wc/testing';
|
||||||
|
|
||||||
|
import '../demo/singleton/overlayCompatibility.js';
|
||||||
|
|
||||||
|
import { overlays } from '../demo/singleton/node_modules/overlays/instance.js';
|
||||||
|
import { overlays as overlays2 } from '../demo/singleton/node_modules/page-b/node_modules/overlays/instance.js';
|
||||||
|
|
||||||
|
describe('singleton-demo', () => {
|
||||||
|
it('uses the compatibility overrides', async () => {
|
||||||
|
// Note: we can not test how it would work without applying the compatibility layer
|
||||||
|
// as it is a global side effect and there is only one karma instance running.
|
||||||
|
expect(overlays.name).to.equal('Compatible from App');
|
||||||
|
expect(overlays2.name).to.equal('Compatible from App');
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue