├── .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 | 
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 | }
--------------------------------------------------------------------------------