├── .gitignore
├── .env
├── src
├── registration.php
├── etc
│ └── module.xml
├── view
│ └── frontend
│ │ ├── layout
│ │ ├── default_head_blocks.xml
│ │ └── default.xml
│ │ └── web
│ │ └── js
│ │ └── meanbee_csspreload.js
└── Block
│ └── Head
│ └── Preload.php
├── composer.json
├── LICENSE
├── README.md
└── docker-compose.yml
/.gitignore:
--------------------------------------------------------------------------------
1 | /magento
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | COMPOSE_PROJECT_NAME=m2_meanbee_csspreload
2 |
3 | PROJECT_HOSTNAME=m2-meanbee-csspreload.dev.docker
4 | PROJECT_CERT=dev.docker
5 | PROJECT_SRC_VOLUME=/extensions/Meanbee_CSSPreload
6 |
--------------------------------------------------------------------------------
/src/registration.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/view/frontend/layout/default_head_blocks.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/view/frontend/layout/default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | ]]>
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "meanbee/magento2-csspreload",
3 | "description": "Magento 2 module that allows loading of stylesheets with the preload attribute",
4 | "type": "magento2-module",
5 | "version": "1.2.1",
6 | "license": [
7 | "MIT"
8 | ],
9 | "repositories": {
10 | "magento": {
11 | "type": "composer",
12 | "url": "https://repo.magento.com/"
13 | }
14 | },
15 | "require": {
16 | "magento/framework": "^101.0.0",
17 | "psr/log": "~1.0"
18 | },
19 | "authors": [
20 | {
21 | "name": "Darren Belding",
22 | "email": "darren.belding@meanbee.com"
23 | }
24 | ],
25 | "autoload": {
26 | "files": [
27 | "src/registration.php"
28 | ],
29 | "psr-4": {
30 | "Meanbee\\CSSPreload\\": "/src"
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2017 Meanbee
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | 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, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Magento 2 CSS Preload
2 |
3 | Simple module that allows for asynchronous CSS loading in Magento 2
4 |
5 | ## Installation
6 |
7 | 1. Go to your Magento root directory
8 | 2. Run ```composer require meanbee/magento2-csspreload```
9 |
10 | ## Development
11 |
12 | ### Setting up a development environment
13 |
14 | A Docker development environment is included with the project:
15 |
16 | ```
17 | docker-compose run --rm cli magento-extension-installer Meanbee_CSSPreload
18 | docker-compose up -d
19 | ```
20 |
21 |
22 | ## Usage
23 |
24 | To add assets to the block, provide the `assets` argument:
25 |
26 | ```
27 |
28 |
29 |
30 | -
31 |
- css/filename.css
32 | -
33 |
- attribute
34 | - value
35 |
36 |
37 |
38 |
39 |
40 | ```
41 |
42 | To modify the template of the generated `` tags, provide a `link_template` argument, e.g.:
43 |
44 | ```
45 |
46 |
47 | ]]>
48 |
49 |
50 | ```
51 |
52 | There are two variables that will be substituted: `:path:`, which will be replaced by the asset path, and `:attributes:` that will contain your `attributes` of your `assets` as HTML attributes.
--------------------------------------------------------------------------------
/src/Block/Head/Preload.php:
--------------------------------------------------------------------------------
1 | getAssets();
31 |
32 | if (empty($assets)) {
33 | return "\n\n";
34 | }
35 |
36 | if (!$this->hasLinkTemplate()) {
37 | return "\n\n";
38 | }
39 |
40 | foreach ($assets as $asset) {
41 | $attributesHtml = sprintf('%s="%s"', $asset['attributes']['name'], $asset['attributes']['value']);
42 | $assetUrl = $this->_assetRepo->getUrl($asset['path']);
43 | $html .= $this->renderLinkTemplate($assetUrl, $attributesHtml);
44 | }
45 |
46 | return $html;
47 | }
48 |
49 | /**
50 | * @param string $assetUrl
51 | * @param string $additionalAttributes
52 | * @return string
53 | */
54 | private function renderLinkTemplate($assetUrl, $additionalAttributes)
55 | {
56 | return str_replace(
57 | [self::PATTERN_URL, self::PATTERN_ATTRS],
58 | [$assetUrl, $additionalAttributes],
59 | $this->getLinkTemplate()
60 | );
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "2"
2 | services:
3 | varnish:
4 | image: meanbee/magento2-varnish:latest
5 | hostname: ${PROJECT_HOSTNAME}
6 | ports:
7 | - 80
8 | environment:
9 | VIRTUAL_HOST: ${PROJECT_HOSTNAME}
10 | VIRTUAL_PORT: 80
11 | HTTPS_METHOD: noredirect
12 | CERT_NAME: ${PROJECT_CERT}
13 | links:
14 | - web
15 |
16 | web:
17 | image: meanbee/magento2-nginx:1.9
18 | hostname: web.${PROJECT_HOSTNAME}
19 | ports:
20 | - 80
21 | volumes_from:
22 | - magento
23 | links:
24 | - fpm
25 |
26 | fpm:
27 | image: meanbee/magento2-php:7.0-fpm
28 | hostname: fpm.${PROJECT_HOSTNAME}
29 | ports:
30 | - 9000
31 | volumes_from:
32 | - magento
33 | environment:
34 | ENABLE_SENDMAIL: "true"
35 | PHP_ENABLE_XDEBUG:
36 | links:
37 | - db
38 |
39 | cron:
40 | image: meanbee/magento2-php:7.0-cli
41 | hostname: cron.${PROJECT_HOSTNAME}
42 | command: run-cron
43 | volumes_from:
44 | - magento
45 | environment:
46 | ENABLE_SENDMAIL: "true"
47 | links:
48 | - db
49 |
50 | cli:
51 | image: meanbee/magento2-php:7.0-cli
52 | volumes_from:
53 | - magento
54 | environment:
55 | COMPOSER_HOME: /root/.composer
56 | COMPOSER_ALLOW_SUPERUSER: 1
57 | M2SETUP_INSTALL_DB: "true"
58 | M2SETUP_VERSION: 2.2.*
59 | M2SETUP_USE_SAMPLE_DATA: "true"
60 | M2SETUP_DB_HOST: db
61 | M2SETUP_DB_NAME: magento2
62 | M2SETUP_DB_USER: magento2
63 | M2SETUP_DB_PASSWORD: magento2
64 | M2SETUP_BASE_URL: https://${PROJECT_HOSTNAME}/
65 | M2SETUP_BACKEND_FRONTNAME: admin
66 | M2SETUP_ADMIN_FIRSTNAME: Admin
67 | M2SETUP_ADMIN_LASTNAME: User
68 | M2SETUP_ADMIN_EMAIL: admin@example.com
69 | M2SETUP_ADMIN_USER: admin
70 | M2SETUP_ADMIN_PASSWORD: password123
71 | links:
72 | - db
73 |
74 | db:
75 | image: mariadb:10
76 | ports:
77 | - 3306
78 | volumes:
79 | - dbdata:/var/lib/mysql
80 | environment:
81 | MYSQL_ROOT_PASSWORD: magento2
82 | MYSQL_USER: magento2
83 | MYSQL_PASSWORD: magento2
84 | MYSQL_DATABASE: magento2
85 | TERM: dumb
86 |
87 | magento:
88 | image: meanbee/magento2-data:2.2-sample
89 | volumes:
90 | - .:${PROJECT_SRC_VOLUME}
91 | environment:
92 | SYNC_DESTINATION: ${PROJECT_SRC_VOLUME}/magento
93 | privileged: true
94 |
95 | volumes:
96 | dbdata:
97 | # Database tables
98 |
--------------------------------------------------------------------------------
/src/view/frontend/web/js/meanbee_csspreload.js:
--------------------------------------------------------------------------------
1 | /*! loadCSS. [c]2017 Filament Group, Inc. MIT License */
2 | (function(w){
3 | "use strict";
4 | /* exported loadCSS */
5 | var loadCSS = function( href, before, media ){
6 | // Arguments explained:
7 | // `href` [REQUIRED] is the URL for your CSS file.
8 | // `before` [OPTIONAL] is the element the script should use as a reference for injecting our stylesheet before
9 | // By default, loadCSS attempts to inject the link after the last stylesheet or script in the DOM. However, you might desire a more specific location in your document.
10 | // `media` [OPTIONAL] is the media type or query of the stylesheet. By default it will be 'all'
11 | var doc = w.document;
12 | var ss = doc.createElement( "link" );
13 | var ref;
14 | if( before ){
15 | ref = before;
16 | }
17 | else {
18 | var refs = ( doc.body || doc.getElementsByTagName( "head" )[ 0 ] ).childNodes;
19 | ref = refs[ refs.length - 1];
20 | }
21 |
22 | var sheets = doc.styleSheets;
23 | ss.rel = "stylesheet";
24 | ss.href = href;
25 | // temporarily set media to something inapplicable to ensure it'll fetch without blocking render
26 | ss.media = "only x";
27 |
28 | // wait until body is defined before injecting link. This ensures a non-blocking load in IE11.
29 | function ready( cb ){
30 | if( doc.body ){
31 | return cb();
32 | }
33 | setTimeout(function(){
34 | ready( cb );
35 | });
36 | }
37 | // Inject link
38 | // Note: the ternary preserves the existing behavior of "before" argument, but we could choose to change the argument to "after" in a later release and standardize on ref.nextSibling for all refs
39 | // Note: `insertBefore` is used instead of `appendChild`, for safety re: http://www.paulirish.com/2011/surefire-dom-element-insertion/
40 | ready( function(){
41 | ref.parentNode.insertBefore( ss, ( before ? ref : ref.nextSibling ) );
42 | });
43 | // A method (exposed on return object for external use) that mimics onload by polling document.styleSheets until it includes the new sheet.
44 | var onloadcssdefined = function( cb ){
45 | var resolvedHref = ss.href;
46 | var i = sheets.length;
47 | while( i-- ){
48 | if( sheets[ i ].href === resolvedHref ){
49 | return cb();
50 | }
51 | }
52 | setTimeout(function() {
53 | onloadcssdefined( cb );
54 | });
55 | };
56 |
57 | function loadCB(){
58 | if( ss.addEventListener ){
59 | ss.removeEventListener( "load", loadCB );
60 | }
61 | ss.media = media || "all";
62 | }
63 |
64 | // once loaded, set link's media back to `all` so that the stylesheet applies once it loads
65 | if( ss.addEventListener ){
66 | ss.addEventListener( "load", loadCB);
67 | }
68 | ss.onloadcssdefined = onloadcssdefined;
69 | onloadcssdefined( loadCB );
70 | return ss;
71 | };
72 | // commonjs
73 | if( typeof exports !== "undefined" ){
74 | exports.loadCSS = loadCSS;
75 | }
76 | else {
77 | w.loadCSS = loadCSS;
78 | }
79 | }( typeof global !== "undefined" ? global : this ));
80 |
81 | /*! loadCSS. [c]2017 Filament Group, Inc. MIT License */
82 | /* This file is meant as a standalone workflow for
83 | - testing support for link[rel=preload]
84 | - enabling async CSS loading in browsers that do not support rel=preload
85 | - applying rel preload css once loaded, whether supported or not.
86 | */
87 | (function( w ){
88 | "use strict";
89 | // rel=preload support test
90 | if( !w.loadCSS ){
91 | w.loadCSS = function(){};
92 | }
93 | // define on the loadCSS obj
94 | var rp = loadCSS.relpreload = {};
95 | // rel=preload feature support test
96 | // runs once and returns a function for compat purposes
97 | rp.support = (function(){
98 | var ret;
99 | try {
100 | ret = w.document.createElement( "link" ).relList.supports( "preload" );
101 | } catch (e) {
102 | ret = false;
103 | }
104 | return function(){
105 | return ret;
106 | };
107 | })();
108 |
109 | // if preload isn't supported, get an asynchronous load by using a non-matching media attribute
110 | // then change that media back to its intended value on load
111 | rp.bindMediaToggle = function( link ){
112 | // remember existing media attr for ultimate state, or default to 'all'
113 | var finalMedia = link.media || "all";
114 |
115 | function enableStylesheet(){
116 | link.media = finalMedia;
117 | }
118 |
119 | // bind load handlers to enable media
120 | if( link.addEventListener ){
121 | link.addEventListener( "load", enableStylesheet );
122 | } else if( link.attachEvent ){
123 | link.attachEvent( "onload", enableStylesheet );
124 | }
125 |
126 | // Set rel and non-applicable media type to start an async request
127 | // note: timeout allows this to happen async to let rendering continue in IE
128 | setTimeout(function(){
129 | link.rel = "stylesheet";
130 | link.media = "only x";
131 | });
132 | // also enable media after 3 seconds,
133 | // which will catch very old browsers (android 2.x, old firefox) that don't support onload on link
134 | setTimeout( enableStylesheet, 3000 );
135 | };
136 |
137 | // loop through link elements in DOM
138 | rp.poly = function(){
139 | // double check this to prevent external calls from running
140 | if( rp.support() ){
141 | return;
142 | }
143 | var links = w.document.getElementsByTagName( "link" );
144 | for( var i = 0; i < links.length; i++ ){
145 | var link = links[ i ];
146 | // qualify links to those with rel=preload and as=style attrs
147 | if( link.rel === "preload" && link.getAttribute( "as" ) === "style" && !link.getAttribute( "data-loadcss" ) ){
148 | // prevent rerunning on link
149 | link.setAttribute( "data-loadcss", true );
150 | // bind listeners to toggle media back
151 | rp.bindMediaToggle( link );
152 | }
153 | }
154 | };
155 |
156 | // if unsupported, run the polyfill
157 | if( !rp.support() ){
158 | // run once at least
159 | rp.poly();
160 |
161 | // rerun poly on an interval until onload
162 | var run = w.setInterval( rp.poly, 500 );
163 | if( w.addEventListener ){
164 | w.addEventListener( "load", function(){
165 | rp.poly();
166 | w.clearInterval( run );
167 | } );
168 | } else if( w.attachEvent ){
169 | w.attachEvent( "onload", function(){
170 | rp.poly();
171 | w.clearInterval( run );
172 | } );
173 | }
174 | }
175 |
176 |
177 | // commonjs
178 | if( typeof exports !== "undefined" ){
179 | exports.loadCSS = loadCSS;
180 | }
181 | else {
182 | w.loadCSS = loadCSS;
183 | }
184 | }( typeof global !== "undefined" ? global : this ) );
185 |
--------------------------------------------------------------------------------