├── .gitignore ├── LICENSE ├── README.md ├── composer.json └── src └── CriticalCssProcessor.php /.gitignore: -------------------------------------------------------------------------------- 1 | composer.phar 2 | /vendor/ 3 | composer.lock 4 | .idea 5 | 6 | # Commit your application's lock file http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file 7 | # You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file 8 | # composer.lock 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Jan Decavele 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Critical CSS 2 | 3 | PHP library for generating critical CSS. 4 | 5 | ![critical css banner](https://critical-css.jandc.io/images/banner.jpg) 6 | 7 | ## Features 8 | 9 | * PHP only, no Node.js required. 10 | * Automatically generated for each page 11 | * Manual control through `{% fold %}{% endfold %}` tags 12 | * Dynamically resolves CSS used on each page 13 | 14 | ## Site(s) using Critical CSS 15 | * https://www.farmaline.be/apotheek/ 16 | 17 | ## Installation 18 | 19 | ``composer require jandc/critical-css `` 20 | 21 | ##### Register the twig extension and create a wrapper instance with the critical CSS processor 22 | ```php 23 | use TwigWrapper\TwigWrapper; 24 | use CriticalCssProcessor\CriticalCssProcessor; 25 | 26 | $twigEnvironment->addExtension(new CSSFromHTMLExtractor\Twig\Extension()); 27 | $twigWrapper = new TwigWrapper($twigEnvironment, [new CriticalCssProcessor()]); 28 | ``` 29 | ##### Mark the regions of your templates with the provided blocks 30 | ```twig 31 | {% fold %} 32 |
33 | ... 34 |
35 | {% endfold %} 36 | ``` 37 | 38 | ##### Render your pages, using the twigwrapper 39 | ```php 40 | $twigWrapper->render('@templates/my/template.twig', ['foo'=>'bar']); 41 | ``` 42 | 43 | ## Available implementations 44 | 45 | * [Silex](https://github.com/JanDC/critical-css-silex) 46 | 47 | If you have your own implementation, please send a pull request to add it to this list. 48 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jandc/critical-css", 3 | "description": "Package wrapper and twig postprocessor class which uses the 'css from html extractor' library and twigwrapper to inject a document's head with critical css", 4 | "type": "library", 5 | "require": { 6 | "jandc/twig-wrapper": "^0.4", 7 | "jandc/css-from-html-extractor": "^1.1", 8 | "masterminds/html5": ">2.2", 9 | "doctrine/cache": ">1.6" 10 | }, 11 | "license": "MIT", 12 | "authors": [ 13 | { 14 | "name": "Jan Decavele", 15 | "email": "jandecavele@gmail.com" 16 | } 17 | ], 18 | "autoload": { 19 | "psr-4": { 20 | "CriticalCssProcessor\\": "src/" 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/CriticalCssProcessor.php: -------------------------------------------------------------------------------- 1 | fileCache = $fileCache; 24 | if (is_null($fileCache)) { 25 | $this->fileCache = new ArrayCache(); 26 | } 27 | 28 | } 29 | 30 | /** 31 | * @param string $rawHtml 32 | * 33 | * @param string $name Template name 34 | * @param array $context The context used to render the template 35 | * @param Twig_Environment|null $environment The twig environment used, useful for accessing 36 | * 37 | * @return string processedHtml 38 | */ 39 | public function process($rawHtml, $name = '', $context = [], $environment = null) 40 | { 41 | 42 | try { 43 | $html5 = new HTML5(); 44 | $document = $html5->loadHTML($rawHtml); 45 | 46 | /** @var ExtractorExtension $extractorExtension */ 47 | $extractorExtension = $environment->getExtension(ExtractorExtension::class); 48 | foreach ($document->getElementsByTagName('link') as $linkTag) { 49 | /** @var DOMElement $linkTag */ 50 | if ($linkTag->getAttribute('rel') == 'stylesheet') { 51 | $tokenisedStylesheet = explode('?', $linkTag->getAttribute('href')); 52 | $stylesheet = reset($tokenisedStylesheet); 53 | 54 | if ($content = $this->fileCache->fetch($stylesheet)) { 55 | $extractorExtension->addBaseRules($content); 56 | continue; 57 | } 58 | 59 | 60 | if (($content = @file_get_contents($stylesheet)) !== false) { 61 | $this->fileCache->save($stylesheet, $content); 62 | $extractorExtension->addBaseRules($content); 63 | continue; 64 | } 65 | if (($content = @file_get_contents($_SERVER['DOCUMENT_ROOT'] . $stylesheet)) !== false) { 66 | $this->fileCache->save($stylesheet, $content); 67 | $extractorExtension->addBaseRules($content); 68 | continue; 69 | } 70 | } 71 | } 72 | 73 | } catch (\Exception $exception) { 74 | error_log($exception->getMessage()); 75 | return $rawHtml; 76 | } 77 | 78 | try { 79 | $criticalCss = $extractorExtension->buildCriticalCssFromSnippets(); 80 | 81 | if (strlen($criticalCss) == 0) { 82 | return $rawHtml; 83 | } 84 | } catch (Twig_Error_Runtime $tew) { 85 | error_log($tew->getMessage()); 86 | return $rawHtml; 87 | } 88 | 89 | try { 90 | $headStyle = new DOMElement('style', $criticalCss); 91 | $document->getElementsByTagName('head')->item(0)->appendChild($headStyle); 92 | 93 | return $html5->saveHTML($document); 94 | } catch (\Exception $exception) { 95 | error_log($exception->getMessage()); 96 | return $rawHtml; 97 | } 98 | } 99 | } --------------------------------------------------------------------------------