import canUseDom from '../../_util/canUseDom';
import contains from './contains';
const APPEND_ORDER = 'data-vc-order';
const MARK_KEY = `vc-util-key`;
const containerCache = new Map<ContainerType, Node & ParentNode>();
export type ContainerType = Element | ShadowRoot;
export type Prepend = boolean | 'queue';
export type AppendType = 'prependQueue' | 'append' | 'prepend';
interface Options {
attachTo?: ContainerType;
csp?: { nonce?: string };
prepend?: Prepend;
mark?: string;
}
function getMark({ mark }: Options = {}) {
if (mark) {
return mark.startsWith('data-') ? mark : `data-${mark}`;
return MARK_KEY;
function getContainer(option: Options) {
if (option.attachTo) {
return option.attachTo;
const head = document.querySelector('head');
return head || document.body;
function getOrder(prepend?: Prepend): AppendType {
if (prepend === 'queue') {
return 'prependQueue';
return prepend ? 'prepend' : 'append';
/**
* Find style which inject by rc-util
*/
function findStyles(container: ContainerType) {
return Array.from((containerCache.get(container) || container).children).filter(
node => node.tagName === 'STYLE',
) as HTMLStyleElement[];
export function injectCSS(css: string, option: Options = {}) {
if (!canUseDom()) {
return null;
const { csp, prepend } = option;
const styleNode = document.createElement('style');
styleNode.setAttribute(APPEND_ORDER, getOrder(prepend));
if (csp?.nonce) {
styleNode.nonce = csp?.nonce;
styleNode.innerHTML = css;
const container = getContainer(option);
const { firstChild } = container;
if (prepend) {
// If is queue `prepend`, it will prepend first style and then append rest style
const existStyle = findStyles(container).filter(node =>
['prepend', 'prependQueue'].includes(node.getAttribute(APPEND_ORDER)),
);
if (existStyle.length) {
container.insertBefore(styleNode, existStyle[existStyle.length - 1].nextSibling);
return styleNode;
// Use `insertBefore` as `prepend`
container.insertBefore(styleNode, firstChild);
} else {
container.appendChild(styleNode);
function findExistNode(key: string, option: Options = {}) {
return findStyles(container).find(node => node.getAttribute(getMark(option)) === key);
export function removeCSS(key: string, option: Options = {}) {
const existNode = findExistNode(key, option);
if (existNode) {
container.removeChild(existNode);
* qiankun will inject `appendChild` to insert into other
function syncRealContainer(container: ContainerType, option: Options) {
const cachedRealContainer = containerCache.get(container);
// Find real container when not cached or cached container removed
if (!cachedRealContainer || !contains(document, cachedRealContainer)) {
const placeholderStyle = injectCSS('', option);
const { parentNode } = placeholderStyle;
containerCache.set(container, parentNode);
container.removeChild(placeholderStyle);
* manually clear container cache to avoid global cache in unit testes
export function clearContainerCache() {
containerCache.clear();
export function updateCSS(css: string, key: string, option: Options = {}) {
// Sync real parent
syncRealContainer(container, option);
if (option.csp?.nonce && existNode.nonce !== option.csp?.nonce) {
existNode.nonce = option.csp?.nonce;
if (existNode.innerHTML !== css) {
existNode.innerHTML = css;
return existNode;
const newNode = injectCSS(css, option);
newNode.setAttribute(getMark(option), key);
return newNode;