├── .gitignore ├── Block └── ItemsWithPattern.php ├── LICENSE ├── Plugin └── Page │ └── ResultPlugin.php ├── README.md ├── composer.json ├── etc ├── frontend │ └── di.xml └── module.xml ├── registration.php └── view └── frontend ├── layout └── default.xml └── templates ├── defer-styles.phtml ├── items.phtml └── style.phtml /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /composer.lock 3 | /.idea 4 | -------------------------------------------------------------------------------- /Block/ItemsWithPattern.php: -------------------------------------------------------------------------------- 1 | pattern = $data['pattern'] ?? '%s'; 32 | } 33 | 34 | /** 35 | * Add item 36 | * 37 | * @param string $value 38 | */ 39 | public function addItem(string $value): void 40 | { 41 | $this->items[] = trim($value); 42 | } 43 | 44 | /** 45 | * Add multiple items 46 | * 47 | * @param array $items 48 | */ 49 | public function addItems(array $items): void 50 | { 51 | array_walk($items, [$this, 'addItem']); 52 | } 53 | 54 | /** 55 | * @return array 56 | */ 57 | public function getItems(): array 58 | { 59 | return $this->items; 60 | } 61 | 62 | public function hasItems(): bool 63 | { 64 | return !empty($this->items); 65 | } 66 | 67 | /** 68 | * Remove item by value 69 | * 70 | * @param string $value 71 | */ 72 | public function removeItem(string $value): void 73 | { 74 | $this->items = array_diff([$value], $this->items); 75 | } 76 | 77 | /** 78 | * Set pattern for rendering 79 | * 80 | * @param string $pattern 81 | */ 82 | public function setPattern(string $pattern): void 83 | { 84 | $this->pattern = $pattern; 85 | } 86 | 87 | /** 88 | * Render items with pattern 89 | * 90 | * @param string $pattern 91 | * @return string 92 | */ 93 | public function render(?string $pattern = null): string 94 | { 95 | $pattern = $pattern ?? $this->pattern; 96 | 97 | return implode('', array_map(function(string $value) use ($pattern) { 98 | return sprintf($pattern, $value); 99 | }, $this->items)); 100 | } 101 | 102 | /** 103 | * Only return if items are found 104 | * 105 | * @return string 106 | */ 107 | protected function _toHtml() 108 | { 109 | if (! $this->hasItems()) { 110 | return ''; 111 | } 112 | 113 | return parent::_toHtml(); 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /Plugin/Page/ResultPlugin.php: -------------------------------------------------------------------------------- 1 | request = $request; 29 | } 30 | 31 | /** 32 | * @param ResultInterface $subject 33 | * @param ResultInterface $result 34 | * @param ResponseInterface $response 35 | * @return ResultInterface 36 | */ 37 | public function afterRenderResult( 38 | ResultInterface $subject, 39 | ResultInterface $result, 40 | ResponseInterface $response 41 | ) 42 | { 43 | if (PHP_SAPI === 'cli' || $this->request->isXmlHttpRequest()) { 44 | return $result; 45 | } 46 | 47 | $html = $response->getBody(); 48 | $scripts = []; 49 | 50 | $startTag = ''); 70 | if ($end !== false) { 71 | $html = substr($html, 0, $end) . $scripts . substr($html, $end); 72 | } else { 73 | $html .= $scripts; 74 | } 75 | 76 | $response->setBody($html); 77 | 78 | return $result; 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Magento 2 - Lightspeed for Lighthouse optimizations 2 | 3 | Process your Google Lighthouse feedback using sane defaults. This module defines several sections where you can define 4 | common feedback from Google Lighthouse. 5 | 6 | ## Installation 7 | 8 | Run this in your Magento 2 project root; 9 | 10 | ```bash 11 | composer require elgentos/module-lightspeed 12 | php bin/magento module:enable Elgentos_Lightspeed 13 | php bin/magento setup:upgrade 14 | ``` 15 | 16 | ## Features 17 | 18 | ### Javascript handling 19 | Move all `script` tags before body end. This is the default after installing this module, no exceptions. 20 | 21 | ### Connection optimization (layout.xml) 22 | Allow modern browsers to use DNS prefetching and preconnecting. 23 | 24 | DNS prefetching only does a DNS lookup, preconnecting already connects to the remote server and does SSL handshake. 25 | Preconnecting is limited to a few connections which is defined in your browser, we have fallback to DNS-prefetching, 26 | but think before you add everything to `preconnect`. 27 | 28 | ### Fonts (layout.xml) 29 | Load external fonts to the head section. 30 | 31 | ### Styles (layout.xml) 32 | Inline CSS in the head or before body end for critical CSS. 33 | 34 | ### External CSS (layout.xml) 35 | We have several options for optimizing external CSS; 36 | * Directly in the head; 37 | * Before body end; 38 | * Defer till all other stuff is done. 39 | 40 | ### Javascript (layout.xml) 41 | Javascript via XML before body end via layout XML. 42 | 43 | ## Usage 44 | 45 | Preferred usage to keep everything together is to add a handle to `layout/default.xml`. 46 | You can also add controller specific rules, add them in the controller specific handles, for instance `layout/catalog_category_default.xml` 47 | 48 | You can also add specific rules and bind them to your module, instead to the theme. 49 | 50 | `app/design/frontend/your/theme/Magento_Theme/layout/default.xml` 51 | ```xml 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | ``` 60 | 61 | After that all default lighthouse feedback can go into `layout/default_lightspeed.xml` 62 | 63 | `app/design/frontend/your/theme/Magento_Theme/layout/default_lightspeed.xml` 64 | ```xml 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | www.google.com 74 | www.gstatic.com 75 | www.googleadservices.com 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | fonts.googleapis.com 85 | fonts.gstatic.com 86 | 87 | 88 | 89 | www.googletagmanager.com 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | https://fonts.googleapis.com/css?family=Font&display=swap 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | ``` 130 | 131 | ## Block quick references 132 | 133 | ### References HEAD 134 | 135 | * `lightspeed.head.dns-prefetch` 136 | * `lightspeed.head.preconnect` 137 | * `lightspeed.head.fonts` 138 | * `lightspeed.head.inline-styles` 139 | 140 | ### References (before body end) 141 | 142 | * `lightspeed.body.defer-styles` 143 | * `lightspeed.body.no-defer-styles` 144 | * `lightspeed.body.inline-styles` 145 | * `lightspeed.body.footer-js` 146 | 147 | ## Block code reference 148 | 149 | You can also use `\Elgentos\Lightspeed\Block\ItemsWithPattern` to add your own references. 150 | 151 | Public: 152 | * `addItem(string $value): void` 153 | * `addItems(array $values): void` 154 | * `getItems(): array` 155 | * `hasItems(): bool` 156 | * `removeItem(string $value): void` 157 | * `setPattern(string $pattern): void` 158 | * `render(): string` 159 | 160 | ### Custom block definition 161 | 162 | `default.xml` 163 | ```xml 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | ]]> 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | ``` 182 | 183 | 184 | ## Authors 185 | * [Gideon Overeem](@govereem) 186 | * [Jeroen Boersma](@Jeroen_Boersma) 187 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "elgentos/module-lightspeed", 3 | "description": "Magento 2 Lightspeed optimizations", 4 | "type": "magento2-module", 5 | "license": "Unlicense", 6 | "authors": [ 7 | { 8 | "name": "Gideon Overeem", 9 | "email": "gideon@elgentos.nl" 10 | }, 11 | { 12 | "name": "Jeroen Boersma", 13 | "email": "jeroen@elgentos.nl" 14 | } 15 | ], 16 | "minimum-stability": "stable", 17 | "require": { 18 | "php": ">=7.2", 19 | "magento/framework": "*" 20 | }, 21 | "autoload": { 22 | "psr-4": { 23 | "Elgentos\\Lightspeed\\": "" 24 | }, 25 | "files": [ 26 | "registration.php" 27 | ] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /etc/frontend/di.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /etc/module.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /registration.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | ]]> 10 | 11 | 12 | 13 | 14 | ]]> 15 | 16 | 17 | 18 | 19 | 20 | 21 | ]]> 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ]]> 30 | 31 | 32 | 33 | 34 | ]]> 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /view/frontend/templates/defer-styles.phtml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 32 | -------------------------------------------------------------------------------- /view/frontend/templates/items.phtml: -------------------------------------------------------------------------------- 1 | render(); 7 | -------------------------------------------------------------------------------- /view/frontend/templates/style.phtml: -------------------------------------------------------------------------------- 1 | 6 | 7 | --------------------------------------------------------------------------------