diff --git a/common/framework/Formatter.php b/common/framework/Formatter.php index fc9f9136e..90204a609 100644 --- a/common/framework/Formatter.php +++ b/common/framework/Formatter.php @@ -328,6 +328,8 @@ class Formatter */ public static function concatCSS($source_filename, $target_filename, $add_comment = true, &$imported_list = []) { + $charsets = []; + $imported_urls = []; $result = ''; if (!is_array($source_filename)) @@ -353,10 +355,11 @@ class Formatter // Convert all paths in LESS and SCSS imports, too. $dirname = dirname($filename); $import_type = ends_with('.scss', $filename) ? 'scss' : 'normal'; - $content = preg_replace_callback('/@import\s+([^\r\n]+);(?=[\r\n$])/', function($matches) use($dirname, $filename, $target_filename, $import_type, &$imported_list) { + $content = preg_replace_callback('/@import\s+([^\r\n]+);(?=[\r\n$])/', function($matches) use($dirname, $filename, $target_filename, $import_type, &$imported_list, &$imported_urls) { if (preg_match('!^url\([\'"]?((?:https?:)?//[^()\'"]+)!i', $matches[1], $urlmatches)) { - return '@import url("' . escape_dqstr($urlmatches[1]) . '");'; + $imported_urls[] = $urlmatches[1]; + return ''; } $import_content = ''; $import_files = array_map(function($str) use($dirname, $filename, $import_type) { @@ -403,7 +406,7 @@ class Formatter { if (preg_match('!^(https?:)?//!i', $import_filename)) { - $import_content .= '@import url("' . escape_dqstr($import_filename) . '");'; + $imported_urls[] = $import_filename; } elseif (file_exists($import_filename)) { @@ -434,6 +437,12 @@ class Formatter }, $content); unset($path_converter); + // Extract all @charset declarations. + $content = preg_replace_callback('/@charset\s+(["\'a-z0-9_-]+);[\r\n]*/i', function($matches) use (&$charsets) { + $charsets[] = trim($matches[1], '"\''); + return ''; + }, $content); + // Wrap the content in a media query if there is one. if ($media !== null) { @@ -452,6 +461,20 @@ class Formatter } } + // Place all @charset and @import statements at the beginning. + if (count($imported_urls)) + { + $imports = implode("\n", array_map(function($url) { + return '@import url("' . escape_dqstr($url) . '");'; + }, $imported_urls)); + $result = $imports . "\n" . $result; + } + if (count($charsets)) + { + $charset = '@charset "' . escape_dqstr(array_first($charsets)) . '";'; + $result = $charset . "\n" . $result; + } + return $result; } diff --git a/tests/_data/formatter/concat.source2.css b/tests/_data/formatter/concat.source2.css index 13ffd0fbc..0389b6bd3 100644 --- a/tests/_data/formatter/concat.source2.css +++ b/tests/_data/formatter/concat.source2.css @@ -1,4 +1,4 @@ -@charset "UTF-8"; +@charset "CP949"; @import url(concat.source3.css); @import url(//fonts.googleapis.com/earlyaccess/nanumgothic.css); @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+KR&display=swap'); diff --git a/tests/_data/formatter/concat.target1.css b/tests/_data/formatter/concat.target1.css index 91f2f365e..b772aebf3 100644 --- a/tests/_data/formatter/concat.target1.css +++ b/tests/_data/formatter/concat.target1.css @@ -1,19 +1,20 @@ +@charset "UTF-8"; +@import url("//fonts.googleapis.com/earlyaccess/nanumgothic.css"); +@import url("https://fonts.googleapis.com/css2?family=Noto+Sans+KR&display=swap"); /* Original file: tests/_data/formatter/concat.source1.css */ -@charset "UTF-8"; .rhymix { background: url("../_data/formatter/foo/bar.jpg"); } /* Original file: tests/_data/formatter/concat.source2.css */ -@charset "UTF-8"; .imported { background-image: url("../_data/_data/formatter/test.jpg"); font-family: sans-serif; } -@import url("//fonts.googleapis.com/earlyaccess/nanumgothic.css"); -@import url("https://fonts.googleapis.com/css2?family=Noto+Sans+KR&display=swap"); + + .wordpress { border-radius: 4px; } diff --git a/tests/_data/formatter/concat.target2.css b/tests/_data/formatter/concat.target2.css index 198329c07..6cf6e2d1c 100644 --- a/tests/_data/formatter/concat.target2.css +++ b/tests/_data/formatter/concat.target2.css @@ -1,8 +1,10 @@ +@charset "UTF-8"; +@import url("//fonts.googleapis.com/earlyaccess/nanumgothic.css"); +@import url("https://fonts.googleapis.com/css2?family=Noto+Sans+KR&display=swap"); /* Original file: tests/_data/formatter/concat.source1.css */ @media screen and (max-width: 640px) { -@charset "UTF-8"; .rhymix { background: url("../_data/formatter/foo/bar.jpg"); } @@ -11,13 +13,12 @@ /* Original file: tests/_data/formatter/concat.source2.css */ -@charset "UTF-8"; .imported { background-image: url("../_data/_data/formatter/test.jpg"); font-family: sans-serif; } -@import url("//fonts.googleapis.com/earlyaccess/nanumgothic.css"); -@import url("https://fonts.googleapis.com/css2?family=Noto+Sans+KR&display=swap"); + + .wordpress { border-radius: 4px; } diff --git a/tests/_data/formatter/scss.target1.css b/tests/_data/formatter/scss.target1.css index 590ccd975..d315a16bd 100644 --- a/tests/_data/formatter/scss.target1.css +++ b/tests/_data/formatter/scss.target1.css @@ -1,7 +1,7 @@ @charset "UTF-8"; -/* Original file: tests/_data/formatter/scss.source1.scss */ @import url("//fonts.googleapis.com/css?family=Raleway:700,400"); @import url("https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,400;0,700;1,400;1,700&display=swap"); +/* Original file: tests/_data/formatter/scss.source1.scss */ /* Original file: tests/_data/formatter/scss.source2.scss */ .inc1 { margin: 5px;