diff --git a/packages/next/build/webpack/loaders/css-loader/src/utils.js b/packages/next/build/webpack/loaders/css-loader/src/utils.js index d3e1198e5fcf..0ab83b2fd9ec 100644 --- a/packages/next/build/webpack/loaders/css-loader/src/utils.js +++ b/packages/next/build/webpack/loaders/css-loader/src/utils.js @@ -43,6 +43,10 @@ function normalizePath(file) { return path.sep === '\\' ? file.replace(/\\/g, '/') : file } +function fixedEncodeURIComponent(str) { + return str.replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16)}`) +} + function normalizeUrl(url, isStringValue) { let normalizedUrl = url @@ -51,10 +55,28 @@ function normalizeUrl(url, isStringValue) { } if (matchNativeWin32Path.test(url)) { - return decodeURIComponent(normalizedUrl) + try { + normalizedUrl = decodeURIComponent(normalizedUrl) + } catch (error) { + // Ignores invalid and broken URLs and try to resolve them as is + } + + return normalizedUrl + } + + normalizedUrl = unescape(normalizedUrl) + + if (isDataUrl(url)) { + return fixedEncodeURIComponent(normalizedUrl) + } + + try { + normalizedUrl = decodeURI(normalizedUrl) + } catch (error) { + // Ignores invalid and broken URLs and try to resolve them as is } - return decodeURIComponent(unescape(normalizedUrl)) + return normalizedUrl } function requestify(url, rootContext) { diff --git a/test/integration/css-fixtures/data-url/pages/index.js b/test/integration/css-fixtures/data-url/pages/index.js new file mode 100644 index 000000000000..2a5dc77aec1b --- /dev/null +++ b/test/integration/css-fixtures/data-url/pages/index.js @@ -0,0 +1,9 @@ +import { className } from './index.module.css' + +export default function Home() { + return ( +
+ Background +
+ ) +} diff --git a/test/integration/css-fixtures/data-url/pages/index.module.css b/test/integration/css-fixtures/data-url/pages/index.module.css new file mode 100644 index 000000000000..fb722e140e34 --- /dev/null +++ b/test/integration/css-fixtures/data-url/pages/index.module.css @@ -0,0 +1,4 @@ +.className { + background: url('data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20649%2087.5%22%20xmlns%3Av%3D%22https%3A%2F%2Fvecta.io%2Fnano%22%3E%3Cpath%20d%3D%22M0%200h649v87.5H0z%22%20fill%3D%22%230b3647%22%2F%3E%3Cpath%20d%3D%22M0%204.03h649v76.22H0z%22%20fill%3D%22%23104f68%22%2F%3E%3Cpath%20d%3D%22M0%200h649v8.07H0z%22%20fill%3D%22%231a6184%22%2F%3E%3C%2Fsvg%3E') + 0 0 no-repeat; +} diff --git a/test/integration/css/test/index.test.js b/test/integration/css/test/index.test.js index 4b3345cac2df..09cef727dd03 100644 --- a/test/integration/css/test/index.test.js +++ b/test/integration/css/test/index.test.js @@ -1852,4 +1852,26 @@ describe('CSS Support', () => { } }) }) + + describe('Data URLs', () => { + const workDir = join(fixturesDir, 'data-url') + + it('should compile successfully', async () => { + await remove(join(workDir, '.next')) + const { code } = await nextBuild(workDir) + expect(code).toBe(0) + }) + + it('should have emitted expected files', async () => { + const cssFolder = join(workDir, '.next/static/css') + const files = await readdir(cssFolder) + const cssFiles = files.filter((f) => /\.css$/.test(f)) + + expect(cssFiles.length).toBe(1) + const cssContent = await readFile(join(cssFolder, cssFiles[0]), 'utf8') + expect(cssContent.replace(/\/\*.*?\*\//g, '').trim()).toMatch( + /background:url\("data:[^"]+"\)/ + ) + }) + }) })