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(runtime): custom inject function #3028

Merged
merged 7 commits into from
Aug 31, 2023
Merged

Conversation

exside
Copy link
Contributor

@exside exside commented Aug 24, 2023

First of all, amazing work with UnoCSS, keep it up!

I'm liking runtime, but one thing I'm missing is the ability to control where the generated styles get injected into the DOM, right now they are basically even before the head tag. I'm using it in a context where i'd like to override existing classes from HTML markup I don't control via UnoCSS shortcuts, but I can't (without !important) because the styles from that platform are in the head (as usual) and single class selector's specificity is therefore higher than single class shortcuts from UnoCSS defined/injected earlier on.

image

So the change is relatively simple, it's just a user configurable function that receives the style element and the user can then inject that wherever they want. Default behaviour doesn't change. Layering logic is untouched, the function just controls where the first layer is injected.

One can now do the following:

	window.__unocss = {
		...,
		runtime: {
			inject: (styleElement) => document.head.append(styleElement),
		},
	};

which results in:

image

If you don't like the way it's implemented or where, let me know, glad to adapt to your liking. Just threw this together for myself, but thought it might be helpful for others too.

@netlify
Copy link

netlify bot commented Aug 24, 2023

Deploy Preview for unocss failed.

Name Link
🔨 Latest commit 9df4bc2
🔍 Latest deploy log https://app.netlify.com/sites/unocss/deploys/64f0a1c3b3c3b40008093d23

@chu121su12
Copy link
Collaborator

The insertion before <head> was done so that unocss styles can be overriden by other library/styles that usually appear in the <head> or even <body>.

Per your example,
image
The first <style> element can be overriden by unocss, especially if you are using reset/preflights.

You can get each layer's <style> element with the update() method and reposition them manually. See test example below.

const result = await runtime?.update()
const layers = [...(result?.getStyleElements().keys() ?? [])]
layers.forEach((layer) => {
const expected = `/* layer: ${layer} */`
expect(result?.css).toContain(expected)
const style = result?.getStyleElement(layer)
expect(style?.tagName).equals('STYLE')
expect(style?.innerHTML).toContain(expected)
})

@@ -161,15 +162,15 @@ export default function init(inlineConfig: RuntimeOptions = {}) {
styleElements.set(layer, styleElement)

if (previousLayer == null) {
html().prepend(styleElement)
inject(styleElement)
}
else {
const previousStyle = getStyleElement(previousLayer)
const parentNode = previousStyle.parentNode
if (parentNode)
parentNode.insertBefore(styleElement, previousStyle.nextSibling)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be handled. Maybe use previousStyle/previousLayer for context

Copy link
Contributor Author

@exside exside Aug 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I undestand, I'm simply replacing the hardcoded html().prepend() with an optional custom method that prepends/appends/inserts the style element(s) wherever the user needs it? What do you mean by "should be handled"? (I mean if a runtime user uses this config option he/she should know what it does IMHO 😉 )

Copy link
Contributor Author

@exside exside Aug 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah maybe i get what you mean: You think we should give users the ability to inject each layer separately wherever they want it?

If that is what you are proposing, I think this would maybe add unnecessary complexity, no? I didn't want to mess with the existing logic of how UnoCSS injects the different layers etc., just WHERE in the DOM it does all of that.

@@ -122,6 +122,7 @@ export default function init(inlineConfig: RuntimeOptions = {}) {

runtimeOptions.configResolved?.(userConfig, userConfigDefaults)
const uno = createGenerator(userConfig, userConfigDefaults)
const inject = (styleElement) => runtimeOptions.inject ? runtimeOptions.inject(styleElement) : html().prepend(styleElement)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add the type and some description

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added to RuntimeOptions interfarce

@@ -161,15 +162,15 @@ export default function init(inlineConfig: RuntimeOptions = {}) {
styleElements.set(layer, styleElement)

if (previousLayer == null) {
html().prepend(styleElement)
inject(styleElement)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may want to send layer for context

@exside
Copy link
Contributor Author

exside commented Aug 25, 2023

The insertion before was done so that unocss styles can be overriden by other library/styles that usually appear in the or even .

absolutely agree! This is the normal use case, but in mine I want to use Uno CSS within a third party vendor platform that does a lot of things i can't control, including markup & styles etc., and here the issue is that I DON'T want the platform's styles to override my utils (or shortcuts) defined in Uno, of course I can use !important everywhere, but we all know that's bad practice and leads to lots of other issues, so simply being able to control where Uno's style elements are placed within the DOM can solve that problem.

You can get each layer's <style> element with the update() method and reposition them manually. See test example below.

yes, but that doesn't matter if the third party code does not use @layer at all 😉

@exside exside requested a review from chu121su12 August 25, 2023 19:26
@antfu antfu changed the title Add ability to control where unocss runtime styles are injected feat(runtime): custom inject function Aug 28, 2023
Improve comment

Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>
@antfu antfu merged commit 77ea190 into unocss:main Aug 31, 2023
0 of 10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants