Skip to content

Commit 30a43ec

Browse files
committed
fix #3410: quote asset references in url tokens
1 parent a579bd8 commit 30a43ec

7 files changed

Lines changed: 88 additions & 21 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
.foo{font:16px Menlo}
1818
```
1919

20+
* Fix bundling CSS with asset names containing spaces ([#3410](https://github.com/evanw/esbuild/issues/3410))
21+
22+
Assets referenced via CSS `url()` tokens may cause esbuild to generate invalid output when bundling if the file name contains spaces (e.g. `url(image 2.png)`). With this release, esbuild will now quote all bundled asset references in `url()` tokens to avoid this problem. This only affects assets loaded using the `file` and `copy` loaders.
23+
2024
* Update to Unicode 15.1.0
2125

2226
The character tables that determine which characters form valid JavaScript identifiers have been updated from Unicode version 15.0.0 to the newly-released Unicode version 15.1.0. I'm not putting an example in the release notes because all of the new characters will likely just show up as little squares since fonts haven't been updated yet. But you can read https://www.unicode.org/versions/Unicode15.1.0/#Summary for more information about the changes.

internal/bundler_tests/bundler_css_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2617,3 +2617,37 @@ func TestCSSCaseInsensitivity(t *testing.T) {
26172617
},
26182618
})
26192619
}
2620+
2621+
func TestCSSAssetPathsWithSpacesBundle(t *testing.T) {
2622+
css_suite.expectBundled(t, bundled{
2623+
files: map[string]string{
2624+
"/entry.css": `
2625+
a {
2626+
background: url(foo.copy);
2627+
background: url(foo.file);
2628+
}
2629+
2630+
/*! The URLs for "foo 2" files must have quotes in the final CSS */
2631+
b {
2632+
background: url('foo 2.copy');
2633+
background: url('foo 2.file');
2634+
}
2635+
`,
2636+
"/foo.file": `...`,
2637+
"/foo.copy": `...`,
2638+
"/foo 2.file": `...`,
2639+
"/foo 2.copy": `...`,
2640+
},
2641+
entryPaths: []string{"/entry.css"},
2642+
options: config.Options{
2643+
Mode: config.ModeBundle,
2644+
AbsOutputFile: "/out.css",
2645+
MinifySyntax: true,
2646+
ExtensionToLoader: map[string]config.Loader{
2647+
".css": config.LoaderCSS,
2648+
".file": config.LoaderFile,
2649+
".copy": config.LoaderCopy,
2650+
},
2651+
},
2652+
})
2653+
}

internal/bundler_tests/snapshots/snapshots_css.txt

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,28 @@ body {
6363
color: blue;
6464
}
6565

66+
================================================================================
67+
TestCSSAssetPathsWithSpacesBundle
68+
---------- /foo-AKINYSFH.copy ----------
69+
...
70+
---------- /foo-AKINYSFH.file ----------
71+
...
72+
---------- /foo 2-AKINYSFH.copy ----------
73+
...
74+
---------- /foo 2-AKINYSFH.file ----------
75+
...
76+
---------- /out.css ----------
77+
/* entry.css */
78+
a {
79+
background: url("./foo-AKINYSFH.copy");
80+
background: url("./foo-AKINYSFH.file");
81+
}
82+
/*! The URLs for "foo 2" files must have quotes in the final CSS */
83+
b {
84+
background: url("./foo 2-AKINYSFH.copy");
85+
background: url("./foo 2-AKINYSFH.file");
86+
}
87+
6688
================================================================================
6789
TestCSSAtImport
6890
---------- /out.css ----------
@@ -1659,7 +1681,7 @@ TestCSSCaseInsensitivity
16591681
body {
16601682
BACKGROUND-color: red;
16611683
width: 50Px;
1662-
background-IMAGE: url(./image-AKINYSFH.png);
1684+
background-IMAGE: url("./image-AKINYSFH.png");
16631685
}
16641686
}
16651687
}
@@ -2214,12 +2236,12 @@ This is some data.
22142236
---------- /out/entry.css ----------
22152237
/* one.css */
22162238
a {
2217-
background: url(./example-GDKWWYFY.data);
2239+
background: url("./example-GDKWWYFY.data");
22182240
}
22192241

22202242
/* two.css */
22212243
b {
2222-
background: url(./example-GDKWWYFY.data);
2244+
background: url("./example-GDKWWYFY.data");
22232245
}
22242246

22252247
/* entry.css */

internal/bundler_tests/snapshots/snapshots_default.txt

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3148,8 +3148,8 @@ tNjAtOTVMMTU1IDEwMGwtNDcuNSA0Ny4\
31483148
1IiBmaWxsPSJub25lIiBzdHJva2U9IiM\
31493149
xOTE5MTkiIHN0cm9rZS13aWR0aD0iMjQ\
31503150
iLz4KPC9zdmc+Cg==");
3151-
cursor: url(./x-TZ25B4WH.file);
3152-
cursor: url(./x-UF3O47Y3.copy);
3151+
cursor: url("./x-TZ25B4WH.file");
3152+
cursor: url("./x-UF3O47Y3.copy");
31533153
cursor: url("data:text/plain;c\
31543154
harset=utf-8,...lots of long dat\
31553155
a...lots of long data...");
@@ -4110,10 +4110,10 @@ a {
41104110
background: url(data:image/svg+xml,<svg/>);
41114111
}
41124112
b {
4113-
background: url(./file-NVISQQTV.file);
4113+
background: url("./file-NVISQQTV.file");
41144114
}
41154115
c {
4116-
background: url(./copy-O3Y5SCJE.copy);
4116+
background: url("./copy-O3Y5SCJE.copy");
41174117
}
41184118
d {
41194119
background: url(extern.png);
@@ -4357,10 +4357,10 @@ d {
43574357
"entryPoint": "project/entry.css",
43584358
"inputs": {
43594359
"project/entry.css": {
4360-
"bytesInOutput": 183
4360+
"bytesInOutput": 187
43614361
}
43624362
},
4363-
"bytes": 230
4363+
"bytes": 234
43644364
}
43654365
}
43664366
}
@@ -4394,7 +4394,7 @@ import("./3333333333333333333333333333333333333333333333333333333333333333333333
43944394
---------- /out/bytesInOutput should be at least 99.css ----------
43954395
/* project/bytesInOutput should be at least 99.css */
43964396
a {
4397-
background: url(./444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444-55DNWN2R.file);
4397+
background: url("./444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444-55DNWN2R.file");
43984398
}
43994399
---------- metafile.json ----------
44004400
{
@@ -4561,10 +4561,10 @@ a {
45614561
"entryPoint": "project/bytesInOutput should be at least 99.css",
45624562
"inputs": {
45634563
"project/bytesInOutput should be at least 99.css": {
4564-
"bytesInOutput": 142
4564+
"bytesInOutput": 144
45654565
}
45664566
},
4567-
"bytes": 196
4567+
"bytes": 198
45684568
}
45694569
}
45704570
}

internal/bundler_tests/snapshots/snapshots_loader.txt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ console.log(x);
311311
---------- /out/src/entry.css ----------
312312
/* Users/user/project/src/entry.css */
313313
body {
314-
background: url(../assets/some.file);
314+
background: url("../assets/some.file");
315315
}
316316

317317
================================================================================
@@ -321,7 +321,7 @@ stuff
321321
---------- /out/src/entry.css ----------
322322
/* Users/user/project/src/entry.css */
323323
body {
324-
background: url(../some-BYATPJRB.file);
324+
background: url("../some-BYATPJRB.file");
325325
}
326326

327327
================================================================================
@@ -666,15 +666,15 @@ x
666666
---------- /out/entries/entry.css ----------
667667
/* src/shared/common.css */
668668
div {
669-
background: url(../common-LSAMBFUD.png);
669+
background: url("../common-LSAMBFUD.png");
670670
}
671671

672672
/* src/entries/entry.css */
673673

674674
---------- /out/entries/other/entry.css ----------
675675
/* src/shared/common.css */
676676
div {
677-
background: url(../../common-LSAMBFUD.png);
677+
background: url("../../common-LSAMBFUD.png");
678678
}
679679

680680
/* src/entries/other/entry.css */
@@ -704,7 +704,7 @@ x
704704
---------- /out/entries/entry.css ----------
705705
/* src/entries/entry.css */
706706
div {
707-
background: url(https://example.com/images/image-LSAMBFUD.png);
707+
background: url("https://example.com/images/image-LSAMBFUD.png");
708708
}
709709

710710
================================================================================
@@ -725,7 +725,7 @@ x
725725
---------- /out/entries/entry.css ----------
726726
/* src/entries/entry.css */
727727
div {
728-
background: url(https://example.com/image-LSAMBFUD.png);
728+
background: url("https://example.com/image-LSAMBFUD.png");
729729
}
730730

731731
================================================================================
@@ -746,7 +746,7 @@ x
746746
---------- /out/entries/entry.css ----------
747747
/* src/entries/entry.css */
748748
div {
749-
background: url(../images/image-LSAMBFUD.png);
749+
background: url("../images/image-LSAMBFUD.png");
750750
}
751751

752752
================================================================================
@@ -767,7 +767,7 @@ x
767767
---------- /out/entries/entry.css ----------
768768
/* src/entries/entry.css */
769769
div {
770-
background: url(../image-LSAMBFUD.png);
770+
background: url("../image-LSAMBFUD.png");
771771
}
772772

773773
================================================================================

internal/css_printer/css_printer.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1025,6 +1025,13 @@ func (p *printer) printTokens(tokens []css_ast.Token, opts printTokensOpts) bool
10251025
var flags printQuotedFlags
10261026
if record.Flags.Has(ast.ContainsUniqueKey) {
10271027
flags |= printQuotedNoWrap
1028+
1029+
// If the caller will be substituting a path here later using string
1030+
// substitution, then we can't be sure that it will form a valid URL
1031+
// token when unquoted (e.g. it may contain spaces). So we need to
1032+
// quote the unique key here just in case. For more info see this
1033+
// issue: https://github.com/evanw/esbuild/issues/3410
1034+
tryToAvoidQuote = false
10281035
} else if p.options.LineLimit > 0 && p.currentLineLength()+len(text) >= p.options.LineLimit {
10291036
tryToAvoidQuote = false
10301037
}

scripts/js-api-tests.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -945,7 +945,7 @@ export {
945945
assert.strictEqual(value.outputFiles, void 0)
946946
assert.strictEqual(await readFileAsync(output, 'utf8'), `/* scripts/.js-api-tests/fileLoaderCSS/in.css */
947947
body {
948-
background: url(https://www.example.com/assets/data-BYATPJRB.bin);
948+
background: url("https://www.example.com/assets/data-BYATPJRB.bin");
949949
}
950950
`)
951951
},

0 commit comments

Comments
 (0)