I wrote a RegEx function to dynamically reorder the JS and CSS includes that are included by concrete5 via header_required (and a script to dynamically combine, minify, and gzip your theme JS files)

Permalink
Hello folks, I wrote a regex based function to take out the includes from the header and place them into the footer, all except for <style>, <link>, jQuery, and the VARS bit.

I would love feedback! Feel free to test and post your critiques back here. :-D

You will initially have some issues with some of your blocks not having
$(document).ready(function() {

wrapped around their script inits, but that is an easy fix. Just go into the view.php for the blocks that aren't working, and place $(document).ready(function() { at the beginning of the init (right after the <script> tag), and }); at the end (right before the </script> tag).

PART 1

Here is the function code:
function reorderJS($content) {
    preg_match('/((<\s*script)[^>]*(src){1}\s*\={1}[^>]*(jquery\.js|jquery\.min\.js)[^>]*>{1}\s*<\/\s*(script)\s*>{1})/', $content, $jQuery);
    echo $jQuery[0];
preg_match_all('/((<\s*(script\s*type="text\/javascript"\s*\>|meta|link|style[^>]*>|title>)[^>]*)(\s*\/>|<\/\s*(style|title|script)\s*>))/', $content, $matches);
    foreach ($matches[1] as $notJSTags) {
        $headerContent .= $notJSTags;
    };
    echo $headerContent;
    $nojQuery = preg_replace('/((<\s*script)[^>]*(src){1}\s*\={1}[^>]*(jquery\.js|jquery\.min\.js)[^>]*>{1}\s*<\/\s*(script)\s*>{1})/', '', $content);
    preg_match_all('/((<\s*(script)[^>]*)(src\s*=\s*){1}[^>]*>{1}(<\/\s*(script)\s*>))/', $nojQuery, $matches2);
    foreach ($matches2[1] as $scriptTags2) {
        $footerContent .= $scriptTags2;
    };
    return $footerContent;
}


To use it, put this function at the head of your header.php file.

then, replace:
Loader::element('header_required');


with:

ob_start();
        Loader::element('header_required');
        $footerJS = reorderJS(ob_get_clean());


The above will echo the required header stuff, and then the function sends all the other JS includes into the variable $footerJS.

In your footer.php, right before the
Loader::element('footer_required');


We'll echo the $footerJS variable, like so:

echo $footerJS;
Loader::element('footer_required');


Now we have taken all the JS includes out of the header, and put them into the footer.

PART 2

Place into your .htaccess file (it's in the root of your website) the following code:

# --compress text, html, javascript, css, xml--
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/xml
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE application/xhtml+xml
AddOutputFilterByType DEFLATE application/rss+xml
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/x-javascript
#--compress text, html, javascript, css, xml end--
# BEGIN EXPIRES
<IfModule mod_expires.c>
    ExpiresActive On
    ExpiresDefault "access plus 14 days"


This tells your server to compress everything before it sends it to the browser. You will gain up to 70% savings in file sizes.

PART 3

Aside from the above, we as theme developers need to watch out for updates and the like for all our theme's JS files, but we also need to make sure that they are as compact as possible for the live site.

I wrote another script that you simply drop into your /js/ folder, and it will automatically concatenate, minify, server side cache, and tell the server to gzip the contents. NOTE: This script requires jsmin.php to minify the JS.

Get jsmin.php from here:
https://github.com/rgrove/jsmin-php/...
and then drop it into your /js/ folder along with the following script.

Here is the code:

<?php
$s = $_SERVER;
//this bit sets up gzipping
if (substr_count($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip')) {
    ob_start("ob_gzhandler");
} else {
    ob_start();
}
//$JSfiles gets all the files with .js extentions
$JSfiles = glob("*.js");
//$time is set to a really far back date, so when we check against it, it always is less that the file creation date.
$time = mktime(0, 0, 0, 21, 5, 1980);
//$cache == the name of the file we write all the finished javascript to.  This is what the browser will see.
$cache = 'cache.js';
//here we are checking each file against $time.  For each file we get from $JSfiles, $fileTime is set to its modification date.


Copy and past the above code into a file called js.php, then include it in your footer.php like so:
echo $footerJS;
echo '<script type="text/javascript" src="' . $this->getThemePath() . '/js/js.php"></script>';
Loader::element('footer_required');


To order the scripts simply append a number to the file(1jquery.tools.min.js, 2cufon.yui.js, 3Nunito_400.font.js, ETC...).

NOTE: If you delete a file, my script won't know it, because it only checks the modified date of existing files. If you decide to delete a file after the script has built cache.js, simply delete cache.js and it will rebuild it with the correct files. Also, this script only builds a cache.js when the original files has been modified, so it should be much quicker than including all these scripts individually, and much less hassle than combining all these files into one file by hand.

PART 4

For dynamically minifying CSS and LESS files, see my how-to here:
http://www.concrete5.org/documentation/how-tos/developers/dynamical...

So far my test site is up to 80-85 on ySlow using these enhancements.

EDIT: You may need to change permissions on your JS and CSS folders, so my script can write to them.

EDIT 2: Added a better expiry header snippet into my .htaccess file example.

boomgraphics
 
boomgraphics replied on at Permalink Reply
boomgraphics
I found another useful htaccess code snippet. It is especially useful considering all the images in c5 are cached with a unique name that changes any time you make a change to the file.

Using this along with my other enhancements above, my Yslow score goes up to ~90.
<FilesMatch "\.(mov|otf|eot|ttf|ico|pdf|flv|jpg|jpeg|png|gif|js|css|swf)$">
Header set Expires "Thu, 15 Dect 2011 20:00:00 GMT"
</FilesMatch>


Just make sure to update the date when it expires. :-)
Charlie replied on at Permalink Best Answer Reply
Rather than setting a specific date you could use ExpiresDefault

e.g.
<FilesMatch "\.(mov|otf|eot|ttf|ico|pdf|flv|jpg|jpeg|png|gif|js|css|swf)$">
ExpiresDefault "access plus 2 months"
</FilesMatch>


You can set it for:

- years
- months
- weeks
- days
- hours
- minutes
- seconds

You may also want to set different expires periods dependent on the type of file your dealing with, in which case you can use the ExpiresByType and pass in each file type with a desired expiration period:

<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType text/css "access plus 1 year"
ExpiresByType image/gif "access plus 2 months"
</IfModule>
boomgraphics replied on at Permalink Reply
boomgraphics
Ah, perfect. I was wondering about whether there was a better way to set expires headers. Thanks!
wightwhale replied on at Permalink Reply
Hi I was trying your example on my website and I've broken a image slider block. I added the code listed at the top of your site wherever I found a script tag in the slider folder but it is still broken.

It looks like this now:

<script type="text/javascript">
$(document).ready(function() {
$(function(){

at the top of any script tag. Am I missing something in the setup? Any help would be greatly appreciated. (absurdinteractive.com) powerslider



Header file
<?php
function reorderJS($content) 
{
        preg_match('/((<\s*script)[^>]*(src){1}\s*\={1}[^>]*(jquery\.js|jquery\.min\.js)[^>]*>{1}\s*<\/\s*(script)\s*>{1})/', $content, $jQuery);
        echo $jQuery[0];
   preg_match_all('/((<\s*(script\s*type="text\/javascript"\s*\>|meta|link|style[^>]*>|title>)[^>]*)(\s*\/>|<\/\s*(style|title|script)\s*>))/', $content, $matches);
   foreach ($matches[1] as $notJSTags)
   {
           $headerContent .= $notJSTags;
   };
   echo $headerContent;
   $nojQuery = preg_replace('/((<\s*script)[^>]*(src){1}\s*\={1}[^>]*(jquery\.js|jquery\.min\.js)[^>]*>{1}\s*<\/\s*(script)\s*>{1})/', '', $content);
   preg_match_all('/((<\s*(script)[^>]*)(src\s*=\s*){1}[^>]*>{1}(<\/\s*(script)\s*>))/', $nojQuery, $matches2);
   foreach ($matches2[1] as $scriptTags2)
   {



footer file
<footer class="container">
       <?php  $a = new Area('Footer'); $a->display($c); ?>
    </footer>
    <div id="vivid">
        <a href="http://isitvivid.com"><strong>Website Design By Vivid in Jacksonville, Florida</strong></a>
    </div>
<!--/*<?php  Loader::element('footer_required'); ?>*/-->
<?php echo $footerJS;
Loader::element('footer_required'); ?>
</body>
</html>


view.php file
<?php   defined('C5_EXECUTE') or die("Access Denied."); ?>
<?php 
global $c;
?>
<?php  if ($c->isEditMode()) { }
else { ?>
<script type="text/javascript">
$(document).ready(function() {
   $(function(){
      $("#sliderContainer").cycle({ 
         fx: '<?php  echo $transitionType ?>',
         next: '#powerSliderNext',
         prev: '#powerSliderPrev',
         pager: '#powerSliderPagination',
         cleartypeNoBg: true,
boomgraphics replied on at Permalink Reply
boomgraphics
Ok I changed some things around. Let me know if this works. :-)

view.php

<?php defined('C5_EXECUTE') or die("Access Denied."); ?>
<?php 
$c = Page::getCurrentPage();
if (!$c->isEditMode()) { ?>
    <script type="text/javascript">
        $(document).ready(function() {
            $("#sliderContainer").cycle({ 
                fx: '<?php echo $transitionType ?>',
                next: '#powerSliderNext',
                prev: '#powerSliderPrev',
                pager: '#powerSliderPagination',
                cleartypeNoBg: true,
                timeout: <?php echo $slideDelay ?>000
            });
        });


footer.php

<footer class="container">
       <?php  $a = new Area('Footer'); $a->display($c); ?>
    </footer>
    <div id="vivid">
        <a href="http://isitvivid.com"><strong>Website Design By Vivid in Jacksonville, Florida</strong></a>
    </div>
<?php echo $footerJS;
Loader::element('footer_required'); ?>
</body>
</html>


header.php

<?php
function reorderJS($content) {
    preg_match('/((<\s*script)[^>]*(src){1}\s*\={1}[^>]*(jquery\.js|jquery\.min\.js)[^>]*>{1}\s*<\/\s*(script)\s*>{1})/', $content, $jQuery);
    echo $jQuery[0];
    preg_match_all('/((<\s*(script\s*type="text\/javascript"\s*\>|meta|link|style[^>]*>|title>)[^>]*)(\s*\/>|<\/\s*(style|title|script)\s*>))/', $content, $matches);
    foreach ($matches[1] as $notJSTags) {
        $headerContent .= $notJSTags;
    };
    echo $headerContent;
    $nojQuery = preg_replace('/((<\s*script)[^>]*(src){1}\s*\={1}[^>]*(jquery\.js|jquery\.min\.js)[^>]*>{1}\s*<\/\s*(script)\s*>{1})/', '', $content);
    preg_match_all('/((<\s*(script)[^>]*)(src\s*=\s*){1}[^>]*>{1}(<\/\s*(script)\s*>))/', $nojQuery, $matches2);
    foreach ($matches2[1] as $scriptTags2) {
        $footerContent .= $scriptTags2;
    };
    return $footerContent;
wightwhale replied on at Permalink Reply
Issue seems to be still happening, I think it's something to do with the script block section. It's only the powerslider which is breaking atm. Thanks so much for the help. :)http://www.absurdinteractive.com...
boomgraphics replied on at Permalink Reply
boomgraphics
Now it looks better but for some reason the $footerJS variable isn't printing out the javascript. I am checking over the code.
boomgraphics replied on at Permalink Reply
boomgraphics
I can't find the issue. If you look at the html output of your page you should have a bunch of js files at the bottom of the page. I have it working on my test server perfectly. The only thing I can think of is that the footer.php file is not being included as it should be. Perhaps the pagetypes have their own closing body and html tags instead of using the footer.php file. You'll have to troubleshoot this. Best way is to move the echo $footerJS; back into the header to make sure it is even outputting correctly. Then move it around the files to see where the issue is.
wightwhale replied on at Permalink Reply
Thanks so much! I'll give these things a try tomorrow and let you know how it goes. I did find the <head> and it's in the top.php file. I'll try and sort it out. The php and javascript are rather new to me so I'll have to give it a few tries.
wightwhale replied on at Permalink Reply
So it seems like wherever I put the footer it doesn't work unless I put it in the top.php file with the rest of the code. I tried to put it in my header.php file and my footer.php file but the only place it works is in the top.php file. Any ideas?


<?php
function reorderJS($content) {
    preg_match('/((<\s*script)[^>]*(src){1}\s*\={1}[^>]*(jquery\.js|jquery\.min\.js)[^>]*>{1}\s*<\/\s*(script)\s*>{1})/', $content, $jQuery);
    echo $jQuery[0];
    preg_match_all('/((<\s*(script\s*type="text\/javascript"\s*\>|meta|link|style[^>]*>|title>)[^>]*)(\s*\/>|<\/\s*(style|title|script)\s*>))/', $content, $matches);
    foreach ($matches[1] as $notJSTags) {
        $headerContent .= $notJSTags;
    };
    echo $headerContent;
    $nojQuery = preg_replace('/((<\s*script)[^>]*(src){1}\s*\={1}[^>]*(jquery\.js|jquery\.min\.js)[^>]*>{1}\s*<\/\s*(script)\s*>{1})/', '', $content);
    preg_match_all('/((<\s*(script)[^>]*)(src\s*=\s*){1}[^>]*>{1}(<\/\s*(script)\s*>))/', $nojQuery, $matches2);
    foreach ($matches2[1] as $scriptTags2) {
        $footerContent .= $scriptTags2;
    };
    return $footerContent;
wightwhale replied on at Permalink Reply
I also tried implementing your other optimizations and I'm having trouble getting your echo to link to the files. I'm getting text output at the top and bottom like so echo $footerJS; echo ''; Loader::element('footer_required');
boomgraphics replied on at Permalink Reply
boomgraphics
You are probably missing the <?php ?> tags.

I am not familiar with this theme's code structure, so I recommend you ask the theme developer (MichaelG) if he can make it work. I was going to buy the theme to see what was up, but $40 is too much for a test theme. :-P

Also, the JSMin and CSS part of my original post is for the theme developer to mess with. It doesn't work with the CMS js.

I recommend you insert the .htaccess code into your htaccess file, and use 12345j's optimizer script. It is a drop in set of files that may require less php and js knowledge than mine requires. :-)

https://github.com/12345j/Tinifier-Concrete5-Optimiser...

Put view.php in your top level libraries and tiny.php in your top level helpers folder.

You can view the full thread here:http://www.concrete5.org/community/forums/customizing_c5/concrete5-...
wightwhale replied on at Permalink Reply
Never mind the white around the images is my fault with the png 8

-----Original Message-----
From: Concrete5 Community [mailto:discussions@concretecms.com]
Sent: Wednesday, July 20, 2011 9:57 PM
To: willie@absurdinteractive.com
Subject: I wrote a RegEx function to dynamically reorder the JS and CSS includes that are included by concrete5 via header_required (and a script to dynamically combine, minify, and gzip your theme JS files): I wrote a RegEx function to dynamically reorder the JS
boomgraphics replied on at Permalink Reply
boomgraphics
Wait a sec. The header.php file isn't what I thought it was. You will have to edit your pagetype I think. Header.php seems to only have the header stuff. The actual <head> tag is somewhere else, probably included at the top of each pagetype.

Disregard my header.php edits. O_O
boomgraphics replied on at Permalink Reply
boomgraphics
Ok, so I think I may have figured it out. You need to find the <head> tag. It is either in the elements folder in another include, or it is at the top of each pagetype file.

I was confusing the <header> tag with the <head> tag, because I never use semantic html markup like that, and I also include all header content in one file, unlike this theme.

Go to each pagetype and paste my function in before the <html> tag. Then find the header_required which is inside the <head> tag, and replace it like I showed. The view.php edits and the footer.php edits I made are still good to go.

After that you should be all set. :-)
Mnkras replied on at Permalink Reply
Mnkras
I just did an overhaul of a site yesterday to add minifying, it uses a bunch of custom code,

http://rbari.org

http://gtmetrix.com/reports/rbari.org/6zmckoKw...

Next I have to remove css and use sprites :P.

and here is my .htaccess contents,
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/xml
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE application/xhtml+xml
AddOutputFilterByType DEFLATE application/rss+xml
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/x-javascript
ExpiresActive On
ExpiresDefault "access plus 1 week"
FileETag none