Skip to content
Open
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
17 changes: 16 additions & 1 deletion src/core/AssetFactory.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,28 @@ describe("AssestFactory", () => {
type: "css",
};

const element = assetFactory.create([asset]);
const element = assetFactory.create([asset], "head", "raffle-teasers");

expect(element).not.toBeNull();

expect(element).toMatchSnapshot();
});

it("wraps css in a @import layered by module (temporary fix)", () => {
const assetFactory = new AssetFactory();
const asset: AssetLink = {
src: "/src/",
type: "css",
};

const [element] = assetFactory.create([asset], "head", "raffle-teasers");

expect(element.tagName).toBe("STYLE");
expect(element.textContent).toBe(
'@import url("/src/") layer(bcmf.raffle-teasers);'
);
});

it("constructs multiple assets of different types", () => {
const assetFactory = new AssetFactory();
const asset: AssetLink = {
Expand Down
33 changes: 23 additions & 10 deletions src/core/AssetFactory.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { Asset, AssetLink, AssetPosition, AssetScript } from "~/core/Asset";

export type HtmlAsset = HTMLLinkElement | HTMLScriptElement;
export type HtmlAsset =
| HTMLLinkElement
| HTMLScriptElement
| HTMLStyleElement;
export default class AssetFactory {
private static buildTagScript(attr: AssetScript): HTMLScriptElement {
const script = document.createElement("script");
Expand All @@ -19,21 +22,31 @@ export default class AssetFactory {
return script;
}

private static buildTagLink(attr: AssetLink): HTMLLinkElement {
const link = document.createElement("link");
link.setAttribute("rel", "stylesheet");
link.setAttribute("href", attr.src);
if (attr.media) link.setAttribute("media", attr.media);
// TEMPORARY FIX: fragment CSS is injected via `@import ... layer(bcmf.<module>)`
// inside a <style> instead of a plain <link>, so every fragment's styles land
// in a cascade layer that ranks below the shell's own styles. This lets the
// shell (Tailwind v4) win over fragments (mix of Tailwind v3 and v4) without
// cross-pollution, while still sharing styles. Done at runtime here so already
// deployed fragments need no rebuild. Requires the shell to pre-declare the
// layer order first: `@layer bcmf, theme, base, components, utilities;`.
private static buildTagLink(
attr: AssetLink,
module: string
): HTMLStyleElement {
const style = document.createElement("style");
const media = attr.media ? ` ${attr.media}` : "";
style.textContent = `@import url("${attr.src}") layer(bcmf.${module})${media};`;

if (attr.priority)
link.setAttribute("data-priority", attr.priority.toString());
style.setAttribute("data-priority", attr.priority.toString());

return link;
return style;
}

public create(
assets: Asset[],
position: AssetPosition = "head",
module = "",
countryCode?: string
): HtmlAsset[] {
return (
Expand All @@ -58,11 +71,11 @@ export default class AssetFactory {
.map((entry) => {
switch (entry.type) {
case "css":
return AssetFactory.buildTagLink(entry);
return AssetFactory.buildTagLink(entry, module);
case "js":
return AssetFactory.buildTagScript(entry);
}
})
.filter((item): item is HTMLLinkElement | HTMLScriptElement => !!item);
.filter((item): item is HtmlAsset => !!item);
}
}
12 changes: 10 additions & 2 deletions src/core/ObservedModuleService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,16 @@ class ObservedModuleService implements ModuleService {
private injectAssets(): void {
if (this.assetsInjected) return;

const headAssets = this.assetFactory.create(this.config.assets, "head");
const bodyAssets = this.assetFactory.create(this.config.assets, "body");
const headAssets = this.assetFactory.create(
this.config.assets,
"head",
this.config.module,
);
const bodyAssets = this.assetFactory.create(
this.config.assets,
"body",
this.config.module,
);
headAssets.forEach((asset) => this.documentHead.appendChild(asset));
bodyAssets.forEach((asset) => this.documentBody.appendChild(asset));

Expand Down
12 changes: 10 additions & 2 deletions src/core/PageModuleService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,16 @@ class PageModuleService implements ModuleService {
this.config.pages.length === 0 ||
this.config.pages.some(this.matches)
) {
const headAssets = this.assetFactory.create(this.config.assets, "head");
const bodyAssets = this.assetFactory.create(this.config.assets, "body");
const headAssets = this.assetFactory.create(
this.config.assets,
"head",
this.config.module,
);
const bodyAssets = this.assetFactory.create(
this.config.assets,
"body",
this.config.module,
);

if (this.config.exposes) {
await forEachSeries(headAssets, async (asset) => {
Expand Down
36 changes: 16 additions & 20 deletions src/core/__snapshots__/AssetFactory.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,25 @@

exports[`AssestFactory > assumes default head position for assets if the asset does not specify 1`] = `
[
<link
href="/src/"
rel="stylesheet"
/>,
<style>
@import url("/src/") layer(bcmf.);
</style>,
]
`;

exports[`AssestFactory > constructs link asset 1`] = `
[
<link
href="/src/"
rel="stylesheet"
/>,
<style>
@import url("/src/") layer(bcmf.raffle-teasers);
</style>,
]
`;

exports[`AssestFactory > constructs multiple assets of different types 1`] = `
[
<link
href="/src/"
rel="stylesheet"
/>,
<style>
@import url("/src/") layer(bcmf.);
</style>,
<script
src="/src/"
type="text/javascript"
Expand All @@ -42,11 +39,11 @@ exports[`AssestFactory > constructs script asset 1`] = `

exports[`AssestFactory > creates list in order of asset priority 1`] = `
[
<link
<style
data-priority="2"
href="/src2/"
rel="stylesheet"
/>,
>
@import url("/src2/") layer(bcmf.);
</style>,
<script
data-priority="1"
src="/src/"
Expand All @@ -57,9 +54,8 @@ exports[`AssestFactory > creates list in order of asset priority 1`] = `

exports[`AssestFactory > ignores the asset if it does not match the position argument 1`] = `
[
<link
href="/src/"
rel="stylesheet"
/>,
<style>
@import url("/src/") layer(bcmf.);
</style>,
]
`;