Skip to content

Commit 20b337a

Browse files
committed
Support Static Caching
1 parent 965af6a commit 20b337a

4 files changed

Lines changed: 350 additions & 306 deletions

File tree

Readme.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,8 @@ Add on for Statamic v3 to combine and minify all CSS and JS files found in the p
1212

1313
Install by composer: `composer require thoughtco/statamic-minify`
1414

15-
A new config file will be published, and you can modify the minification settings in `config/thoughtco/minify.php`.
15+
A new config file will be published, and you can modify the minification settings in `config/thoughtco/minify.php`.
16+
17+
## Static Caching
18+
19+
If you are using static caching, add `\Thoughtco\Minify\Replacers\MinifyReplacer::class` to your `statamic.static_caching.replacers` array.

src/Managers/MinifyManager.php

Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
<?php
2+
3+
namespace Thoughtco\Minify\Managers;
4+
5+
use Closure;
6+
use File;
7+
use MatthiasMullie\Minify;
8+
9+
class MinifyManager
10+
{
11+
public function parse(string $response)
12+
{
13+
// if disabled by config
14+
$shouldMinify = config('thoughtco.minify.minify_enabled');
15+
16+
// should minify is a closure
17+
if ($shouldMinify instanceof Closure) {
18+
if (! call_user_func($shouldMinify)) {
19+
return $response;
20+
}
21+
22+
// its a class
23+
} else {
24+
if (class_exists($shouldMinify)) {
25+
if (! app()->make($shouldMinify)->handle()) {
26+
return $response;
27+
}
28+
}
29+
}
30+
31+
// linkgroups as we match by media type
32+
$cssGroups = array();
33+
34+
// link groups to remove
35+
$linkElements = array();
36+
$groupedLinkElements = array();
37+
38+
// matches to remove
39+
$removeCSS = $removeCSSGroups = $removeJS = $removeJSGroups = array();
40+
41+
$this->ignoreUrls = config('thoughtco.minify.ignore_urls', []);
42+
43+
// are we minifying css ?
44+
if (in_array('css', config('thoughtco.minify.file_types', []))) {
45+
46+
// match link[href]
47+
preg_match_all('/<link([^>]+)href="([^"]+)"([^>]*)>/i', $response, $linkMatches, PREG_OFFSET_CAPTURE);
48+
if (count($linkMatches) > 0){
49+
50+
foreach ($linkMatches[0] as $i=>$match){
51+
52+
// ignore if IE blocks
53+
if (stripos($match[0], 'rel="stylesheet') !== FALSE && preg_match('/IE([^>]*)>/i', substr($response, $match[1] - 6, 7)) !== 1) {
54+
55+
preg_match('/media="([^"]+)"/i', $match[0], $mediaMatch);
56+
if (count($mediaMatch) > 1){
57+
58+
if (strpos($linkMatches[2][$i][0], '//') === FALSE) {
59+
60+
if ($this->shouldBeIgnored($linkMatches[2][$i][0])) {
61+
continue;
62+
}
63+
64+
// no grouping
65+
if (strpos($linkMatches[0][$i][0], 'data-group') === FALSE) {
66+
67+
$cssGroups[$mediaMatch[1]][] = $linkMatches[2][$i][0];
68+
$removeCSS[] = $match[0];
69+
70+
// grouping
71+
} else {
72+
73+
preg_match_all('/data-group="([^"]+)"/i', $linkMatches[0][$i][0], $groupMatch);
74+
75+
// make an array
76+
if (!isset($cssGroups[$groupMatch[1][0]][$mediaMatch[1]])){
77+
$cssGroups[$groupMatch[1][0]][$mediaMatch[1]] = array();
78+
}
79+
80+
$cssGroups[$groupMatch[1][0]][$mediaMatch[1]][] = $linkMatches[2][$i][0];
81+
$removeCSSGroups[$groupMatch[1][0]][] = $match[0];
82+
83+
}
84+
85+
}
86+
87+
}
88+
89+
}
90+
91+
}
92+
}
93+
94+
}
95+
96+
// are we minifying js ?
97+
if (in_array('js', config('thoughtco.minify.file_types', []))) {
98+
99+
// match script[src]
100+
preg_match_all('/<script([^>]+)src="([^"]+)"([^>]*)><\/script>/i', $response, $scriptMatches);
101+
if (count($scriptMatches) > 0) {
102+
foreach ($scriptMatches[0] as $i=>$match) {
103+
104+
if (strpos($scriptMatches[2][$i], '//') === FALSE && strpos($scriptMatches[2][$i], '://') === FALSE) {
105+
106+
if ($this->shouldBeIgnored($scriptMatches[2][$i])) {
107+
continue;
108+
}
109+
110+
// no grouping
111+
if (strpos($scriptMatches[0][$i], 'data-group') === FALSE) {
112+
113+
$linkElements[] = $scriptMatches[2][$i];
114+
$removeJS[] = $match;
115+
116+
// grouping
117+
} else {
118+
119+
preg_match_all('/data-group="([^"]+)"/i', $scriptMatches[0][$i], $groupMatch);
120+
121+
$groupedLinkElements[$groupMatch[1][0]][] = $scriptMatches[2][$i];
122+
$removeJSGroups[$groupMatch[1][0]][] = $match;
123+
124+
}
125+
126+
}
127+
128+
}
129+
}
130+
131+
}
132+
133+
// do we need to make min dir?
134+
$this->minPath = config('thoughtco.minify.min_path');
135+
$minPath = public_path($this->minPath);
136+
if (!File::isDirectory($minPath)){
137+
File::makeDirectory($minPath, $mode = 0777, true, true);
138+
}
139+
140+
$replacementCSS = array();
141+
$replacementCSSGrouped = array();
142+
143+
// loop over css groups
144+
foreach ($cssGroups as $media=>$files){
145+
146+
// styles may be grouped
147+
$filesNoSubgroups = array();
148+
foreach ($files as $index=>$file){
149+
if (is_array($file)){
150+
151+
$tag = $this->combineAndMinify($minPath, $file, 'css');
152+
if ($tag != ''){
153+
$replacementCSSGrouped[$media][] = '<link rel="stylesheet" type="text/css" href="'.$this->minPath.$tag['file'].'.css?'.$tag['version'].'" media="'.$index.'" />';
154+
}
155+
156+
} else {
157+
$filesNoSubgroups[] = $file;
158+
}
159+
160+
}
161+
162+
// or not
163+
$tag = $this->combineAndMinify($minPath, $filesNoSubgroups, 'css');
164+
if ($tag != ''){
165+
$replacementCSS[] = '<link rel="stylesheet" type="text/css" href="'.$this->minPath.$tag['file'].'.css?'.$tag['version'].'" media="'.$media.'" />';
166+
}
167+
168+
}
169+
170+
171+
// if we have css to replace
172+
if (count($replacementCSS) > 0){
173+
174+
// no grouping
175+
if (count($removeCSS) > 0){
176+
$response = str_replace($removeCSS[0], implode("\n\t", $replacementCSS), $response);
177+
}
178+
179+
// replace first instance of group with combined
180+
foreach ($removeCSSGroups as $index=>$group){
181+
$response = str_replace($group[0], implode("\n\t", $replacementCSSGrouped[$index]), $response);
182+
}
183+
184+
}
185+
186+
// loop over js
187+
if (count($linkElements) > 0){
188+
$tag = $this->combineAndMinify($minPath, $linkElements, 'js');
189+
if ($tag != ''){
190+
$response = str_replace($removeJS[0], '<script type="text/javascript" src="'.$this->minPath.$tag['file'].'.js?'.$tag['version'].'"></script>', $response);
191+
}
192+
}
193+
194+
// loop over grouped js
195+
if (count($groupedLinkElements) > 0){
196+
197+
foreach ($groupedLinkElements as $group=>$linkElements){
198+
199+
$tag = $this->combineAndMinify($minPath, $linkElements, 'js');
200+
if ($tag != ''){
201+
$response = str_replace($removeJSGroups[$group][0], '<script type="text/javascript" src="'.$this->minPath.$tag['file'].'.js?'.$tag['version'].'"></script>', $response);
202+
}
203+
204+
}
205+
206+
}
207+
208+
// remove everything
209+
foreach ($removeCSS as $el){
210+
$response = str_replace(array($el."\n", $el."\r", $el), '', $response);
211+
}
212+
213+
foreach ($removeCSSGroups as $index=>$els){
214+
foreach ($els as $el){
215+
$response = str_replace(array($el."\n", $el."\r", $el), '', $response);
216+
}
217+
}
218+
219+
foreach ($removeJS as $el){
220+
$response = str_replace(array($el."\n", $el."\r", $el), '', $response);
221+
}
222+
223+
foreach ($removeJSGroups as $index=>$els){
224+
foreach ($els as $el){
225+
$response = str_replace(array($el."\n", $el."\r", $el), '', $response);
226+
}
227+
}
228+
229+
return $response;
230+
}
231+
232+
// combine and minify files
233+
protected function combineAndMinify($minPath, $aFiles, $type)
234+
{
235+
236+
// get file last modified dates
237+
$aLastModifieds = array();
238+
foreach ($aFiles as $sFile){
239+
$aLastModifieds[] = File::lastModified(public_path($sFile));
240+
}
241+
242+
if (count($aLastModifieds) < 1) return;
243+
244+
// sort dates, newest first
245+
rsort($aLastModifieds);
246+
247+
// get array of filenames
248+
$vals = array_values($aFiles);
249+
250+
// alphabetic sort
251+
sort($vals);
252+
253+
// create an md5 of the filenames
254+
$md5files = md5(implode('', $vals));
255+
256+
// extension
257+
$extension = ($type == 'css' ? 'css' : 'js');
258+
259+
// create a unique tag, last modified file
260+
$iETag = $md5files;
261+
262+
// archive name
263+
$sMergedFilename = $minPath.'/'.$iETag.'.'.$extension;
264+
265+
// we already have this archive
266+
if (File::exists($sMergedFilename)) {
267+
268+
if (File::lastModified($sMergedFilename) >= $aLastModifieds[0]){
269+
return array('file' => $iETag, 'version' => $aLastModifieds[0]);
270+
}
271+
272+
}
273+
274+
// get merge code
275+
$sCode = '';
276+
$sCodeAfter = '';
277+
278+
// type?
279+
if ($type == 'js'){
280+
$minifier = new Minify\JS();
281+
} else {
282+
$minifier = new Minify\CSS();
283+
}
284+
285+
foreach ($aFiles as $sFile) {
286+
$minifier->add(public_path($sFile));
287+
}
288+
289+
$sCode = $minifier->minify();
290+
291+
// write file
292+
File::put($sMergedFilename, $sCode);
293+
294+
// get time
295+
$time = File::lastModified($sMergedFilename);
296+
297+
return array('file' => $iETag, 'version' => $time);
298+
}
299+
300+
protected function shouldBeIgnored($url)
301+
{
302+
$ignore = false;
303+
foreach ($this->ignoreUrls as $ignoreUrl) {
304+
if (preg_match($ignoreUrl, $url)) {
305+
$ignore = true;
306+
}
307+
}
308+
309+
return $ignore;
310+
}
311+
}

0 commit comments

Comments
 (0)