vite-plugin-single-spa
Version:
Vite plugin to convert Vite-based projects to single-spa root or micro-frontend applications.
147 lines • 4.87 kB
JavaScript
/// <reference types="vite/client" />
import { createLinkElement, defaultFactoryOptions, getLogger, processCssPromises, setLogger, wireCssLinkElement } from "./css-helpers.js";
let observer;
let autoLinkEls = [];
const projectId = '{vpss:PROJECT_ID}';
const cssInjectedDictionary = '{vpss:CSS_MAP}';
const cssMap = JSON.parse(cssInjectedDictionary);
const cssFileMap = {};
let base = import.meta.env.BASE_URL;
base += base.endsWith('/') ? '' : '/';
let autoLinksMounted = false;
export function cssLifecycleFactory(entryPoint, options) {
const opts = {
...defaultFactoryOptions,
...options
};
setLogger(opts.logger);
const cssFileNames = cssMap[entryPoint] ?? [];
return {
bootstrap,
mount,
unmount
};
function bootstrap() {
if (!observer) {
observer = observeHead();
}
return Promise.resolve();
}
async function mount() {
if (cssFileNames.length > 0) {
const cssPromises = cssFileNames.map(css => mountCssFile(css, opts.loadTimeout));
await processCssPromises(cssPromises, opts);
}
mountAutoCss();
return Promise.resolve();
}
function unmount() {
if (cssFileNames.length > 0) {
for (let css of cssFileNames) {
unmountCssFile(css);
}
}
maybeUnmountAutoCss();
return Promise.resolve();
}
}
/**
* Mounts the specified CSS filename as a CSS link element in the HEAD element. The function returns a promise that
* resolves once the load event of the LINK element fires.
* @param cssFileName The CSS filename to be mounted.
*/
function mountCssFile(cssFileName, loadTimeout) {
return new Promise((rslv, _rjct) => {
let map = cssFileMap[cssFileName] ?? { count: 0 };
const firstTimeLoading = !map.linkElement;
if (map.count++ === 0 && !map.linkElement) {
map.linkElement = createLinkElement(base + cssFileName);
globalThis.document.head.appendChild(map.linkElement);
}
map.linkElement.disabled = false;
// The load event doesn't seem to fire for pre-existing elements that are merely re-enabled, even though a
// network request shows up in the Network tab of the browser's developer tools.
if (!firstTimeLoading) {
rslv({ status: 'ok' });
return;
}
cssFileMap[cssFileName] = map;
rslv(wireCssLinkElement(map.linkElement, cssFileName, projectId, loadTimeout));
});
}
/**
* Mounts all CSS link elements that have been inserted automatically by Vite's CSS splitting algorithm.
*/
function mountAutoCss() {
if (autoLinksMounted) {
return;
}
for (let el of autoLinkEls) {
el.disabled = false;
}
autoLinksMounted = true;
}
/**
* Unmounts the specified CSS filename from the HEAD element.
* @param cssFileName The CSS filename to be unmounted.
*/
function unmountCssFile(cssFileName) {
let map = cssFileMap[cssFileName];
const logger = getLogger();
if (!map) {
logger.warn('A request to unmount CSS file %s was made, but said file has no file map.', cssFileName);
return;
}
if (map.count <= 0) {
logger.warn('A request to unmount CSS file %s was made, but its count is already %d.', cssFileName, map.count);
return;
}
map.linkElement.disabled = --map.count === 0;
}
/**
* Potentially unmounts all CSS link elements that Vite's CSS splitting algorithm may have mounted.
*/
function maybeUnmountAutoCss() {
if (!autoLinksMounted) {
return;
}
for (let [_, v] of Object.entries(cssFileMap)) {
if (v.count > 0) {
return;
}
}
for (let el of autoLinkEls) {
el.disabled = true;
}
autoLinksMounted = false;
}
/**
* Determines if the given HTML node is a LINK element.
* @param el HTML node to test.
* @returns
*/
function isLinkElement(el) {
return el.nodeName === 'LINK';
}
/**
* Starts an observation process to identify any CSS link elements that Vite's CSS splitting algorithm may auto-insert.
* @returns The observer object that can be used to stop the observation process.
*/
function observeHead() {
const observer = new MutationObserver(mutations => {
mutations.forEach(m => {
if (m.addedNodes.length > 0) {
m.addedNodes.forEach(an => {
if (isLinkElement(an) && an.rel === 'stylesheet' && an.href.includes(`vpss(${projectId})`) && !an.getAttribute('data-vpss')) {
autoLinkEls.push(an);
}
});
}
});
});
observer.observe(globalThis?.document?.head, {
childList: true
});
return observer;
}
//# sourceMappingURL=multiMife-css.js.map