Skip to content

Commit 45fe238

Browse files
committed
Merge remote-tracking branch '38977/wip_less_source_maps' into novcommpr-2
2 parents f643af0 + 08e05b3 commit 45fe238

File tree

5 files changed

+160
-33
lines changed

5 files changed

+160
-33
lines changed

lib/internal/Magento/Framework/Css/PreProcessor/Adapter/Less/Processor.php

+23-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
*/
66
namespace Magento\Framework\Css\PreProcessor\Adapter\Less;
77

8+
use Magento\Framework\App\Filesystem\DirectoryList;
9+
use Magento\Framework\App\ObjectManager;
810
use Magento\Framework\App\State;
911
use Magento\Framework\Css\PreProcessor\File\Temporary;
1012
use Magento\Framework\Phrase;
@@ -16,6 +18,8 @@
1618

1719
/**
1820
* Class Processor
21+
*
22+
* Process LESS files into CSS
1923
*/
2024
class Processor implements ContentProcessorInterface
2125
{
@@ -39,24 +43,32 @@ class Processor implements ContentProcessorInterface
3943
*/
4044
private $temporaryFile;
4145

46+
/**
47+
* @var DirectoryList
48+
*/
49+
private DirectoryList $directoryList;
50+
4251
/**
4352
* Constructor
4453
*
4554
* @param LoggerInterface $logger
4655
* @param State $appState
4756
* @param Source $assetSource
4857
* @param Temporary $temporaryFile
58+
* @param ?DirectoryList $directoryList
4959
*/
5060
public function __construct(
5161
LoggerInterface $logger,
5262
State $appState,
5363
Source $assetSource,
54-
Temporary $temporaryFile
64+
Temporary $temporaryFile,
65+
?DirectoryList $directoryList = null,
5566
) {
5667
$this->logger = $logger;
5768
$this->appState = $appState;
5869
$this->assetSource = $assetSource;
5970
$this->temporaryFile = $temporaryFile;
71+
$this->directoryList = $directoryList ?: ObjectManager::getInstance()->get(DirectoryList::class);
6072
}
6173

6274
/**
@@ -66,10 +78,19 @@ public function processContent(File $asset)
6678
{
6779
$path = $asset->getPath();
6880
try {
81+
$mode = $this->appState->getMode();
82+
$sourceMapBasePath = sprintf(
83+
'%s/pub/',
84+
$this->directoryList->getPath(DirectoryList::TEMPLATE_MINIFICATION_DIR),
85+
);
86+
6987
$parser = new \Less_Parser(
7088
[
7189
'relativeUrls' => false,
72-
'compress' => $this->appState->getMode() !== State::MODE_DEVELOPER
90+
'compress' => $mode !== State::MODE_DEVELOPER,
91+
'sourceMap' => $mode === State::MODE_DEVELOPER,
92+
'sourceMapRootpath' => '/',
93+
'sourceMapBasepath' => $sourceMapBasePath,
7394
]
7495
);
7596

lib/internal/Magento/Framework/Css/Test/Unit/PreProcessor/Adapter/Less/ProcessorTest.php

+102-12
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
namespace Magento\Framework\Css\Test\Unit\PreProcessor\Adapter\Less;
99

10+
use Magento\Framework\App\Filesystem\DirectoryList;
1011
use Magento\Framework\App\State;
1112
use Magento\Framework\Css\PreProcessor\Adapter\Less\Processor;
1213
use Magento\Framework\Css\PreProcessor\File\Temporary;
@@ -18,15 +19,15 @@
1819

1920
class ProcessorTest extends TestCase
2021
{
21-
const TEST_CONTENT = 'test-content';
22+
private const TEST_CONTENT = 'test-content';
2223

23-
const ASSET_PATH = 'test-path';
24+
private const ASSET_PATH = 'test-path';
2425

25-
const TMP_PATH_LESS = '_file/test.less';
26+
private const TMP_PATH_LESS = '_file/test.less';
27+
private const TMP_PATH_CSS_PRODUCTION = '_file/test-production.css';
28+
private const TMP_PATH_CSS_DEVELOPER = '_file/test-developer.css';
2629

27-
const TMP_PATH_CSS = '_file/test.css';
28-
29-
const ERROR_MESSAGE = 'Test exception';
30+
private const ERROR_MESSAGE = 'Test exception';
3031

3132
/**
3233
* @var Processor
@@ -52,6 +53,10 @@ class ProcessorTest extends TestCase
5253
* @var Temporary|MockObject
5354
*/
5455
private $temporaryFileMock;
56+
/**
57+
* @var DirectoryList|MockObject
58+
*/
59+
private $directoryListMock;
5560

5661
/**
5762
* Set up
@@ -69,12 +74,16 @@ protected function setUp(): void
6974
$this->temporaryFileMock = $this->getMockBuilder(Temporary::class)
7075
->disableOriginalConstructor()
7176
->getMock();
77+
$this->directoryListMock = $this->getMockBuilder(DirectoryList::class)
78+
->disableOriginalConstructor()
79+
->getMock();
7280

7381
$this->processor = new Processor(
7482
$this->loggerMock,
7583
$this->appStateMock,
7684
$this->assetSourceMock,
77-
$this->temporaryFileMock
85+
$this->temporaryFileMock,
86+
$this->directoryListMock,
7887
);
7988
}
8089

@@ -89,7 +98,7 @@ public function testProcessContentException()
8998

9099
$this->appStateMock->expects(self::once())
91100
->method('getMode')
92-
->willReturn(State::MODE_DEVELOPER);
101+
->willReturn(State::MODE_PRODUCTION);
93102

94103
$this->assetSourceMock->expects(self::once())
95104
->method('getContent')
@@ -120,7 +129,7 @@ public function testProcessContentEmpty()
120129

121130
$this->appStateMock->expects(self::once())
122131
->method('getMode')
123-
->willReturn(State::MODE_DEVELOPER);
132+
->willReturn(State::MODE_PRODUCTION);
124133

125134
$this->assetSourceMock->expects(self::once())
126135
->method('getContent')
@@ -141,12 +150,55 @@ public function testProcessContentEmpty()
141150
}
142151

143152
/**
144-
* Test for processContent method (not empty content)
153+
* Test for processContent method in production mode (not empty content)
145154
*/
146155
public function testProcessContentNotEmpty()
147156
{
148157
$assetMock = $this->getAssetMock();
149158

159+
$this->appStateMock->expects(self::once())
160+
->method('getMode')
161+
->willReturn(State::MODE_PRODUCTION);
162+
163+
$this->assetSourceMock->expects(self::once())
164+
->method('getContent')
165+
->with($assetMock)
166+
->willReturn(self::TEST_CONTENT);
167+
168+
$this->temporaryFileMock->expects(self::once())
169+
->method('createFile')
170+
->with(self::ASSET_PATH, self::TEST_CONTENT)
171+
->willReturn(__DIR__ . '/' . self::TMP_PATH_LESS);
172+
173+
$assetMock->expects(self::once())
174+
->method('getPath')
175+
->willReturn(self::ASSET_PATH);
176+
177+
$this->loggerMock->expects(self::never())
178+
->method('critical');
179+
180+
$clearSymbol = ["\n", "\r", "\t", ' '];
181+
self::assertEquals(
182+
trim(str_replace(
183+
$clearSymbol,
184+
'',
185+
file_get_contents(__DIR__ . '/' . self::TMP_PATH_CSS_PRODUCTION)
186+
)),
187+
trim(str_replace(
188+
$clearSymbol,
189+
'',
190+
$this->processor->processContent($assetMock)
191+
))
192+
);
193+
}
194+
195+
/**
196+
* Test for processContent method in developer mode (not empty content)
197+
*/
198+
public function testProcessContentNotEmptyInDeveloperMode()
199+
{
200+
$assetMock = $this->getAssetMock();
201+
150202
$this->appStateMock->expects(self::once())
151203
->method('getMode')
152204
->willReturn(State::MODE_DEVELOPER);
@@ -170,8 +222,16 @@ public function testProcessContentNotEmpty()
170222

171223
$clearSymbol = ["\n", "\r", "\t", ' '];
172224
self::assertEquals(
173-
trim(str_replace($clearSymbol, '', file_get_contents(__DIR__ . '/' . self::TMP_PATH_CSS))),
174-
trim(str_replace($clearSymbol, '', $this->processor->processContent($assetMock)))
225+
trim(str_replace(
226+
$clearSymbol,
227+
'',
228+
file_get_contents(__DIR__ . '/' . self::TMP_PATH_CSS_DEVELOPER)
229+
)),
230+
trim(str_replace(
231+
$clearSymbol,
232+
'',
233+
$this->normalizeInlineSourceMap($this->processor->processContent($assetMock))
234+
))
175235
);
176236
}
177237

@@ -186,4 +246,34 @@ private function getAssetMock()
186246

187247
return $assetMock;
188248
}
249+
250+
/**
251+
* - find json part of sourcemap
252+
* - url decode it
253+
* - replace \/ with / in source filenames
254+
* - remove absolute path in filename, make it a relative path
255+
*/
256+
private function normalizeInlineSourceMap(string $css): string
257+
{
258+
$regexBegin = 'sourceMappingURL=data:application/json,';
259+
$regexEnd = '*/';
260+
$regex = '@' . preg_quote($regexBegin, '@') . '([^\*]+)' . preg_quote($regexEnd, '@') . '@';
261+
262+
if (preg_match($regex, $css, $matches) === 1) {
263+
$inlineSourceMapJson = $matches[1];
264+
$inlineSourceMapJson = urldecode($inlineSourceMapJson);
265+
$inlineSourceMapJson = json_decode($inlineSourceMapJson, true, 512, JSON_UNESCAPED_SLASHES);
266+
267+
$relativeFilenames = [];
268+
foreach ($inlineSourceMapJson['sources'] as $filename) {
269+
$relativeFilenames[] = str_replace(sprintf('%s/', BP), '', $filename);
270+
}
271+
$inlineSourceMapJson['sources'] = $relativeFilenames;
272+
$inlineSourceMapJson = json_encode($inlineSourceMapJson, JSON_UNESCAPED_SLASHES);
273+
274+
$css = preg_replace($regex, sprintf('%s%s%s', $regexBegin, $inlineSourceMapJson, $regexEnd), $css);
275+
}
276+
277+
return $css;
278+
}
189279
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* Copyright © Magento, Inc. All rights reserved.
3+
* See COPYING.txt for license details.
4+
*/
5+
body {
6+
background: #333333;
7+
color: #454545;
8+
}
9+
a {
10+
color: #ff9900;
11+
}
12+
h1,
13+
h2,
14+
h3,
15+
h4,
16+
h5,
17+
h6 {
18+
color: #333333;
19+
}
20+
/*#sourceMappingURL=data:application/json,{"version":3,"sources":["lib/internal/Magento/Framework/Css/Test/Unit/PreProcessor/Adapter/Less/_file/test.less"],"names":[],"mappings":";;;;AASA;EACE,mBAAA;EACA,cAAA;;AAEF;EACE,cAAA;;AAEF;AAAI;AAAI;AAAI;AAAI;AAAI;EAClB,cAAA"}*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
body {
2+
background: #333333;
3+
color: #454545
4+
}
5+
a {
6+
color: #ff9900
7+
}
8+
h1,
9+
h2,
10+
h3,
11+
h4,
12+
h5,
13+
h6 {
14+
color: #333333
15+
}

lib/internal/Magento/Framework/Css/Test/Unit/PreProcessor/Adapter/Less/_file/test.css

-19
This file was deleted.

0 commit comments

Comments
 (0)