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

feat: injector utility for the archetype and build config to override the one in X #1258

Merged
merged 2 commits into from
Aug 8, 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
50 changes: 50 additions & 0 deletions packages/x-archetype-utils/src/__tests__/css-injector.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { CssInjector } from '../css-injector/css-injector';
import { WindowWithInjector } from '../css-injector/css-injector.types';

describe('Test custom css injector', () => {
beforeEach(() => {
delete (window as WindowWithInjector).xCSSInjector;
});

it('reuses the same instance between initializations', () => {
const injector1 = new CssInjector();
const injector2 = new CssInjector();

expect(injector1 === injector2).toBe(true);
});

it('can be appended to the window under xCSSInjector', () => {
const injector = new CssInjector(true);
const windowCssInjector = (window as WindowWithInjector).xCSSInjector;

expect(injector === windowCssInjector).toBe(true);
});

it('can set the host element that will receive the styles', () => {
const injector = new CssInjector();
const domElement = document.createElement('div');

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore,
expect(injector.host).toBe(undefined);

injector.setHost(domElement);

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
expect(injector.host).toBe(domElement);
});

it('adds styles string to the set host', () => {
const injector = new CssInjector();
const domElement = document.createElement('div');
const styles = {
source: "* { background: 'red' }"
};

injector.setHost(domElement);
injector.addStyle(styles);

expect(domElement.getElementsByTagName('style')[0].textContent).toBe(styles.source);
});
});
10 changes: 10 additions & 0 deletions packages/x-archetype-utils/src/build/rollup/rollup.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export const rollupCssInjectorConfig = {
replace: {
// Replace X CSS injector by our custom one.
'addStyle(id, style);': 'window.xCSSInjector.addStyle(style);',
delimiters: ['', '']
},
styles: {
mode: ['inject', (varname: string) => `window.xCSSInjector.addStyle({ source: ${varname} });`]
}
};
88 changes: 88 additions & 0 deletions packages/x-archetype-utils/src/css-injector/css-injector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { WindowWithInjector, XCSSInjector } from './css-injector.types';

/**
* Instance of the injector that will be used across all the initializations.
*/
let instance: CssInjector | null = null;

/**
* Custom CSS injector that allows to inject styles into a host element.
*
* @public
*/
export class CssInjector implements XCSSInjector {
protected host!: Element | ShadowRoot;
protected stylesQueue: string[] = [];

/**
* Initializes the instance of the injector if it's not already initialized and sets it in the
* window object if it's required.
*
* @param setInWindow - Whether to set the injector instance in the window object.
*/
public constructor(setInWindow = true) {
if (!(instance instanceof CssInjector)) {
// eslint-disable-next-line @typescript-eslint/no-this-alias
instance = this;
}

if (setInWindow) {
this.setInWindow();
}

return instance;
}

/**
* Adds the styles to the host element.
*
* @param styles - The styles to be added.
*/
addStyle(styles: { source: string }): void {
this.stylesQueue.push(styles.source);
if (this.host) {
this.stylesQueue.forEach(styles => {
const styleTag = document.createElement('style');
styleTag.textContent = styles;
this.host.appendChild(styleTag);
});
this.stylesQueue = [];
}
}

/**
* Sets the host element.
*
* @param host - The host element.
*/
setHost(host: Element | ShadowRoot): void {
this.host = host;
}

/**
* Sets the injector instance in the window object.
*/
setInWindow(): void {
if (typeof window !== 'undefined' && instance) {
(window as WindowWithInjector).xCSSInjector = instance;
}
}

/**
* Checks if the injector instance is in the window object.
*
* @returns Whether the injector instance is in the window object.
*/
isInWindow(): boolean {
return typeof window === 'undefined'
? false
: (window as WindowWithInjector).xCSSInjector === instance;
}
}

/**
* Instance of the injector.
*
* @public
*/
export const cssInjector = new CssInjector(typeof window !== 'undefined');
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export interface XCSSInjector {
/** Function that will add the styles to the host. */
addStyle: (styles: { source: string }) => void;
/** Function setting the host for the injector. */
setHost: (el: Element | ShadowRoot) => void;
/** Set injector instance in the window object. */
setInWindow: () => void;
/** Check if the instance is set in the window object. */
isInWindow: () => boolean;
}

export type WindowWithInjector = Window & { xCSSInjector?: XCSSInjector };
3 changes: 3 additions & 0 deletions packages/x-archetype-utils/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
export * from './i18n/i18n.plugin';
export * from './i18n/i18n.types';
export * from './build/webpack/webpack.config';
export * from './build/rollup/rollup.config';
export * from './css-injector/css-injector';
export * from './css-injector/css-injector.types';