22
33namespace Bugo \Sass ;
44
5+ use Generator ;
56use Symfony \Component \Process \Process ;
67
78use function array_merge ;
1718use function json_decode ;
1819use function json_encode ;
1920use function parse_url ;
21+ use function preg_match ;
2022use function pathinfo ;
2123use function realpath ;
2224use function strtolower ;
@@ -28,6 +30,10 @@ class Compiler implements CompilerInterface
2830{
2931 protected array $ options = [];
3032
33+ protected static ?Process $ cachedProcess = null ;
34+
35+ protected static ?array $ cachedCommand = null ;
36+
3137 public function __construct (protected ?string $ bridgePath = null , protected ?string $ nodePath = null )
3238 {
3339 $ this ->bridgePath = $ bridgePath ?? __DIR__ . '/../bin/bridge.js ' ;
@@ -88,8 +94,8 @@ public function compileFileAndSave(string $inputPath, string $outputPath, array
8894 throw new Exception ("Source file not found: $ inputPath " );
8995 }
9096
91- $ inputMtime = filemtime ($ inputPath );
92- $ outputMtime = file_exists ($ outputPath ) ? filemtime ($ outputPath ) : 0 ;
97+ $ inputMtime = $ this -> getFileMtime ($ inputPath );
98+ $ outputMtime = file_exists ($ outputPath ) ? $ this -> getFileMtime ($ outputPath ) : 0 ;
9399
94100 if ($ inputMtime > $ outputMtime ) {
95101 if (! empty ($ options ['sourceMap ' ]) && empty ($ options ['sourceMapPath ' ])) {
@@ -105,6 +111,45 @@ public function compileFileAndSave(string $inputPath, string $outputPath, array
105111 return false ;
106112 }
107113
114+ protected function getFileMtime (string $ path ): int
115+ {
116+ return filemtime ($ path );
117+ }
118+
119+ public function compileStringAsGenerator (string $ source , array $ options = []): Generator
120+ {
121+ if (trim ($ source ) === '' ) {
122+ yield '' ;
123+ return ;
124+ }
125+
126+ $ options = array_merge ($ this ->options , $ options );
127+
128+ if (strlen ($ source ) > 1024 * 1024 ) {
129+ $ options ['streamResult ' ] = true ;
130+ }
131+
132+ $ payload = [
133+ 'source ' => $ source ,
134+ 'options ' => $ options ,
135+ 'url ' => $ options ['url ' ] ?? null ,
136+ ];
137+
138+ $ result = $ this ->runCompile ($ payload );
139+
140+ if (isset ($ result ['isStreamed ' ]) && $ result ['isStreamed ' ]) {
141+ foreach ($ result ['chunks ' ] as $ chunk ) {
142+ yield $ chunk ;
143+ }
144+ } else {
145+ yield $ result ['css ' ] ?? '' ;
146+ }
147+
148+ if (! empty ($ result ['sourceMap ' ])) {
149+ yield $ this ->processSourceMap ($ result ['sourceMap ' ], $ options );
150+ }
151+ }
152+
108153 protected function compileSource (string $ source , array $ options ): string
109154 {
110155 $ payload = [
@@ -113,14 +158,21 @@ protected function compileSource(string $source, array $options): string
113158 'url ' => $ options ['url ' ] ?? null ,
114159 ];
115160
116- return $ this ->runCompile ($ payload );
161+ $ data = $ this ->runCompile ($ payload );
162+ $ css = $ data ['css ' ] ?? '' ;
163+
164+ if (! empty ($ data ['sourceMap ' ])) {
165+ $ css .= $ this ->processSourceMap ($ data ['sourceMap ' ], $ options );
166+ }
167+
168+ return $ css ;
117169 }
118170
119- protected function runCompile (array $ payload ): string
171+ protected function runCompile (array $ payload ): array
120172 {
121173 $ cmd = [$ this ->nodePath , $ this ->bridgePath , '--stdin ' ];
122174
123- $ process = $ this ->createProcess ($ cmd );
175+ $ process = $ this ->getOrCreateProcess ($ cmd );
124176 $ process ->setInput (json_encode ($ payload ));
125177 $ process ->run ();
126178
@@ -139,13 +191,7 @@ protected function runCompile(array $payload): string
139191 throw new Exception ('Sass parsing error: ' . $ data ['error ' ]);
140192 }
141193
142- $ css = $ data ['css ' ] ?? '' ;
143-
144- if (! empty ($ data ['sourceMap ' ])) {
145- $ css .= $ this ->processSourceMap ($ data ['sourceMap ' ], $ payload ['options ' ]);
146- }
147-
148- return $ css ;
194+ return $ data ;
149195 }
150196
151197 protected function processSourceMap (array $ sourceMap , array $ options ): string
@@ -159,7 +205,7 @@ protected function processSourceMap(array $sourceMap, array $options): string
159205
160206 $ mapFile = (string ) $ options ['sourceMapPath ' ];
161207
162- $ isUrl = filter_var ($ mapFile , FILTER_VALIDATE_URL ) !== false ;
208+ $ isUrl = filter_var ($ mapFile , FILTER_VALIDATE_URL ) !== false && preg_match ( ' /^https?:/ ' , $ mapFile ) ;
163209 if ($ isUrl ) {
164210 $ sourceMappingUrl = $ mapFile ;
165211 } else {
@@ -196,6 +242,25 @@ protected function readFile(string $path): string|false
196242 return file_get_contents ($ path );
197243 }
198244
245+ protected function getOrCreateProcess (array $ command ): Process
246+ {
247+ if (self ::$ cachedProcess !== null && self ::$ cachedCommand === $ command ) {
248+ if (self ::$ cachedProcess ->isRunning ()) {
249+ return self ::$ cachedProcess ;
250+ }
251+
252+ self ::$ cachedProcess = null ;
253+ self ::$ cachedCommand = null ;
254+ }
255+
256+ $ process = $ this ->createProcess ($ command );
257+
258+ self ::$ cachedProcess = $ process ;
259+ self ::$ cachedCommand = $ command ;
260+
261+ return $ process ;
262+ }
263+
199264 protected function createProcess (array $ command ): Process
200265 {
201266 return new Process ($ command );
@@ -216,6 +281,7 @@ protected function findNode(): string
216281 foreach ($ candidates as $ node ) {
217282 $ process = $ this ->createProcess ([$ node , '--version ' ]);
218283 $ process ->run ();
284+
219285 if ($ process ->isSuccessful ()) {
220286 return $ node ;
221287 }
0 commit comments