Skip to content

Commit 1fbaa7a

Browse files
authored
feat: add styles.entries config for injecting global stylesheets (#929)
1 parent d6f8bc5 commit 1fbaa7a

3 files changed

Lines changed: 45 additions & 0 deletions

File tree

.changeset/rare-lemons-whisper.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@blinkk/root': patch
3+
---
4+
5+
feat: add `styles.entries` config for injecting global stylesheets (#929)

packages/root/src/core/config.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,25 @@ export interface RootUserConfig {
3333
exclude?: RegExp[];
3434
};
3535

36+
/**
37+
* Config for manually injecting stylesheet entries as
38+
* `<link rel="stylesheet">` tags.
39+
*/
40+
styles?: {
41+
/**
42+
* Project-root-relative stylesheet files to include on every rendered
43+
* page, e.g. `styles/index.css`.
44+
*
45+
* Entries are normalized as URL paths, so both `styles/index.css` and
46+
* `/styles/index.css` resolve to `/styles/index.css`.
47+
*
48+
* Manual entries are injected first, then auto-collected CSS
49+
* dependencies are merged after. Duplicate URLs are de-duped so each
50+
* stylesheet is only injected once.
51+
*/
52+
entries?: string[];
53+
};
54+
3655
/**
3756
* Config options for localization and internationalization.
3857
*/

packages/root/src/render/render.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,12 @@ export class Renderer {
227227
});
228228
}
229229

230+
// Merge user-configured stylesheet entries first. This allows global
231+
// entries to appear before auto-collected stylesheets.
232+
this.getConfiguredStyleEntries().forEach((styleEntry) => {
233+
cssDeps.add(styleEntry);
234+
});
235+
230236
// Parse the HTML for custom elements that are found within the project
231237
// and automatically inject the script deps for them.
232238
await this.collectElementDeps(mainHtml, jsDeps, cssDeps);
@@ -580,6 +586,16 @@ export class Renderer {
580586
return orderedSitemap;
581587
}
582588

589+
590+
private getConfiguredStyleEntries() {
591+
const styleEntries = this.rootConfig.styles?.entries || [];
592+
const basePath = this.rootConfig.base || '/';
593+
return styleEntries
594+
.map((entry) => entry.trim())
595+
.filter((entry) => entry)
596+
.map((entry) => normalizeStyleEntry(entry, basePath));
597+
}
598+
583599
private async renderHtml(html: string, options?: RenderHtmlOptions) {
584600
const htmlAttrs = options?.htmlAttrs || {};
585601
const headAttrs = options?.headAttrs || {};
@@ -860,6 +876,11 @@ function sortLocales(a: string, b: string) {
860876
return a.localeCompare(b);
861877
}
862878

879+
function normalizeStyleEntry(entry: string, basePath: string) {
880+
const normalizedEntry = normalizeUrlPath(entry.replace(/^\.\//, ''));
881+
return normalizeUrlPath(`${basePath}/${normalizedEntry}`);
882+
}
883+
863884
function guessContentType(ext: string): string {
864885
const normalized = ext.trim().toLowerCase().replace(/^\./, '');
865886
return CONTENT_TYPES[normalized] || 'application/octet-stream';

0 commit comments

Comments
 (0)