Skip to content

fix: handle multiple url()#237

Merged
layershifter merged 2 commits into
microsoft:mainfrom
layershifter:fix/handle-multiple-urls
Oct 13, 2022
Merged

fix: handle multiple url()#237
layershifter merged 2 commits into
microsoft:mainfrom
layershifter:fix/handle-multiple-urls

Conversation

@layershifter

@layershifter layershifter commented Oct 3, 2022

Copy link
Copy Markdown
Member

Fixes #238. See the bug for the issue description in detail.

The main change is that we use tokenize from stylis instead of regexps. This is more robust and allows to parse multiple url() expressions.

@griffel/babel-preset and @griffel/webpack-extraction-plugin were updated/adjusted to handle this scenario.

@github-actions

github-actions Bot commented Oct 3, 2022

Copy link
Copy Markdown
Contributor

📊 Bundle size report

🤖 This report was generated against 28641c5bc7616ffe3ec327b0de67625bbd9c727c

@@ -0,0 +1,3 @@
export function isAssetUrl(value: string): boolean {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I intentionally didn't cover this by tests directly as it will be changed/removed in #234.

// Quotes in URL are optional, so we can also normalize them
// https://www.w3.org/TR/CSS2/syndata.html#value-def-uri
const url = result.url.replace(/['|"](.+)['|"]/, '$1');
return tokenize(ruleValue).reduce((result, token, index, array) => {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Instead of a regex I am using tokenize() from stylis so we can parse all url() entries properly.

Comment on lines +36 to +61
const quasis: t.TemplateElement[] = [];
const expressions: t.Identifier[] = [];

let acc = '';

for (let i = 0, l = tokens.length; i < l; i++) {
for (; i < l; i++) {
acc += tokens[i];

if (tokens[i] === 'url') {
const url = tokens[i + 1].slice(1, -1);

if (isAssetUrl(url)) {
quasis.push(t.templateElement({ raw: acc + '(' }, false));
expressions.push(getAssetIdentifier(url));

acc = ')';
i++;

break;
}
}
}
}

quasis.push(t.templateElement({ raw: acc }, true));

@layershifter layershifter Oct 3, 2022

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It's not the loop that makes me proud, but it works ¯_(ツ)_/¯ (opened for suggestions).

Based on tokens ('.foo', '{', 'url', '(image.png)', '}') we need to create:

  • N TemplateElements (t.templateElement({ raw: '.foo{url(' }, false), t.templateElement({ raw: ')}' }, true))
  • N-1 expressions (t.identifier('image.png'))

N & N-1 are a requirement as it's how Babel manages template literals...

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I can't think of much better....

but I don't quite understand why you need the second nested loop, once you find the url token you break out of the inner loop and the outer one continues where the inner one left off anyway ??

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

but I don't quite understand why you need the second nested loop, once you find the url token you break out of the inner loop and the outer one continues where the inner one left off anyway ??

Good catch, it's a leftover from the previous attempt. Removed.

Comment on lines -38 to -42
{
title: 'assets import handling',
fixture: path.resolve(fixturesDir, 'assets', 'code.ts'),
outputFixture: path.resolve(fixturesDir, 'assets', 'output.ts'),
},

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Moved into a separate group.

Comment on lines +124 to +152
const expressionPaths = literalPath.get('expressions');

expressionPaths.map(expressionPath => {

@layershifter layershifter Oct 3, 2022

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The inner part is 100% percent the same: now we parse all expressions instead of only first.

@layershifter
layershifter marked this pull request as ready for review October 3, 2022 15:50
@layershifter
layershifter requested a review from a team as a code owner October 3, 2022 15:50
function buildTemplateLiteralFromValue(value: string): t.TemplateLiteral {
const tokens = tokenize(value);

const quasis: t.TemplateElement[] = [];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

what are quasis ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It's how Babel names string in template literals i.e. foo from foo${bar}:

image

https://babeljs.io/docs/en/babel-types#templateliteral

acc += tokens[i];

if (tokens[i] === 'url') {
const url = tokens[i + 1].slice(1, -1);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

why ignore the first and last characters of a url ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm going by your example of tokens: ('.foo', '{', 'url', 'image.png', '}')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The first and last characters are parens. url(image.png) tokenizes to ["url", "(image.png)"]

@layershifter
layershifter force-pushed the fix/handle-multiple-urls branch from 7fa5811 to a3f91c9 Compare October 13, 2022 07:10

@ling1726 ling1726 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I have doubts about the ugly nested loop in the preset, but otherwise LGTM

@layershifter
layershifter merged commit 32d9b0b into microsoft:main Oct 13, 2022
@layershifter
layershifter deleted the fix/handle-multiple-urls branch October 13, 2022 07:50
layershifter added a commit to layershifter/griffel that referenced this pull request Apr 16, 2026
Bump svgo 3.2.0 → 3.3.3 (fixes Dependabot microsoft#237, Billion Laughs DoS)
and serve-handler 6.1.6 → 6.1.7 which brings minimatch 3.1.2 → 3.1.5
(fixes Dependabot microsoft#230, ReDoS via GLOBSTAR).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
layershifter added a commit that referenced this pull request Apr 16, 2026
* chore: bump svgo and serve-handler to fix security advisories

Bump svgo 3.2.0 → 3.3.3 (fixes Dependabot #237, Billion Laughs DoS)
and serve-handler 6.1.6 → 6.1.7 which brings minimatch 3.1.2 → 3.1.5
(fixes Dependabot #230, ReDoS via GLOBSTAR).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: dedupe sax

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.

babel-preset: does not handle multiple url()

3 participants