Strategic Web Design : ITpro の連載記事「技術者視点のユーザビリティ考」の第30回 JavaScriptの動作を軽くするための工夫で紹介されていた動的に js ファイルを統合する仕組み Supercharged Javascript が、中々興味深い。
要は指定された js ファイルへのアクセスを PHP に送り、結合して送り返すのだが、これが中々良さそうだ。
多少、修正して当サイトにも実装してみたので、自分用にメモ。
時間が有るときに WordPress 用のプラグインとして利用できるようにしてリリースするかも。
js ファイルの動的結合
まずは、js ファイルを結合する PHP。
元記事のままだと、別ディレクトリの js ファイルを指定できなさそうなので、以下のように修正した。
<?php
$cache = dirname($_SERVER['SCRIPT_FILENAME']).'/cache'; // $cache is cache dir
ini_set("zlib.output_compression", "Off"); // Disable zlib compression, if present, for duration of this script.
header("Content-Type: text/javascript; charset=UTF-8"); // Set the content type header
header("Cache-Control: must-revalidate"); // Set the cache control header (http 1.1 browsers MUST revalidate -- always)
header('Expires: '.gmdate("D, d M Y H:i:s", time() + (60 * 60 * 24 * 365)).' GMT'); // Set the Expires header (1 year)
// Here we are going to extract the filename list.
if (isset($_GET['files'])) {
$fileList = str_replace(" ", "", trim(urldecode($_GET['files'])));
} else {
$expl = explode("/",$HTTP_SERVER_VARS["REQUEST_URI"]);
$fileList = trim(urldecode($expl[count($expl)-1]));
}
$orgFileNames = explode(",",$fileList); // $fileNames now is an array of the requested file names.
// Go through each of the files and get its last modified time so we
// can send a last-modified header so caching works properly
$newestFile = 0;
$ii=0;
$longFilename = ''; // This is generated for the Hash
$fileNames = Array();
for ($i=0; ($i < count($orgFileNames)); $i++) {
$orgFileNames[$i] = trim($orgFileNames[$i]); // Get rid of whitespace
if (preg_match('/^\/.*\.js$/i', $orgFileNames[$i])) {
$orgFileNames[$i] = dirname($_SERVER['SCRIPT_FILENAME']).$orgFileNames[$i];
}
if (preg_match('/\.js$/i',$orgFileNames[$i])) { // Allow only files ending in .js in the list.
$fileNames[$ii++]=$orgFileNames[$i]; // Valid file name, so go ahead and use it.
$longFilename .= $orgFileNames[$i]; // Build our LONG file name for the hash.
$lastMod = @filemtime($orgFileNames[$i]); // Get file last modified time
if ($lastMod > $newestFile) { // Is this the newest file?
$newestFile = $lastMod; // Yup, so mark it.
}
}
}
// Begin *BROWSER* Cache Control
//
$fileHash = md5($longFilename); // This generates a key from the collective file names
$hash = $fileHash . '-'.$newestFile; // This appends the newest file date to the key.
// Get all the headers the browser sent us.
if (function_exists('getallheaders')) {
$headers = getallheaders();
} elseif (function_exists('apache_response_headers')) {
$headers = apache_response_headers();
}
if (ereg($hash, $headers['If-None-Match'])) { // Look for a hash match
header('Last-Modified: '.gmdate('D, d M Y H:i:s', $newestFile).' GMT', true, 304);
die();
}
// We are still alive so save the hash+latest modified time in the e-tag.
header("ETag: \"{$hash}\"");
// If there's no change we'll send a cache control header and die.
if (isset($headers['If-Modified-Since'])) {
if ($newestFile <= strtotime($headers['If-Modified-Since'])) {
// No change so send a 304 header and terminate
header('Last-Modified: '.gmdate('D, d M Y H:i:s', $newestFile).' GMT', true, 304);
die();
}
}
// Set the last modified date as the date of the NEWEST file in the list.
header('Last-Modified: '.gmdate('D, d M Y H:i:s', $newestFile).' GMT');
// Begin File System Cache Control
//
$fp = @fopen("$cache/$fileHash.txt","r");
if ($fp) {
if ($newestFile>@filemtime("$cache/$fileHash.txt")) { fclose($fp); $fp=false;}
}
if (!$fp) {
$buffer='';
for ($i=0; ($i < count($fileNames)); $i++) {
$buffer .= @file_get_contents($fileNames[$i])."\n\n";
}
// We've created our concatenated file so first we'll save it as
// plain text for non gzip enabled browsers.
$fp = @fopen("$cache/$fileHash.txt","w");
@fwrite($fp,$buffer);
@fclose($fp);
// Now we'll compress the file (maximum compression) and save
// the compressed version.
$fp = @fopen("$cache/$fileHash.gz","w");
$buffer = gzencode($buffer, 9, FORCE_GZIP);
@fwrite($fp,$buffer);
@fclose($fp);
}
// Begin Output
//
if (strstr($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip')) {
// Browser can handle gzip data so send it the gzip version.
header ("Content-Encoding: gzip");
header ("Content-Length: " . filesize("$cache/$fileHash.gz"));
readfile("$cache/$fileHash.gz");
} else {
// Browser can not handle gzip so send it plain text version.
header ("Content-Length: " . filesize("$cache/$fileHash.txt"));
readfile("$cache/$fileHash.txt");
}
?>
これに scripts.php とでも名前をつけて保存しておきましょう。
.htaccess は、こんな感じで修正しておけばおっけ。
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_URI} ",+.*\.js$"
RewriteRule . /scripts.php?files=%{REQUEST_URI} [L]
</IfModule>
これで、以下のようにカンマ区切りで js ファイルを列挙してやれば指定された js ファイルをすべて結合した一つのファイルとして返してくれます。
<script type="text/javascript" src="/wp-includes/js/jquery/jquery-1.2.2.min.js, /wp-includes/js/jquery/jquery.plugins.js, /wp-includes/js/quick-comments.js"></script>
WordPress への応用
さて WordPress では標準の javascript ロード基準が無いため、各プラグインが各々好きなように javascript をセットしています。
※調べたら、Wordpress 用の javascript ローダーがありました。
wp_enqueue_scriptで外部JavaScriptの読み込みをスマートに at WordPress.ex-libris.jp
そんな訳で、読み込んでる javascript ファイルが半端無い数になって重くなってしまっているサイトもあります。
自動で対応できると嬉しいので、ウチでは以下のようにしました。
修正するのは、使用しているテーマの中の functions.php。
以下の記述を追加すれば、上手いこと行くはず。
<?php
function _start() {
ob_start();
}
function _end_flush() {
$content = ob_get_contents();
ob_end_clean();
$content = preg_replace('/<!--[^>]*-->/i', '', preg_replace('/[\r\n]/i', '', $content));
$content = preg_replace('/(<[^>]*\/>)/i', "\n".'$1'."\n", $content);
$content = preg_replace('/(<script[^>]*>.*?<\/script>)/i', "\n".'$1'."\n", $content);
$contents = explode("\n", $content);
$re_siteurl = '/^'.preg_quote(preg_replace('/^(https?:\/\/[^\/]*\/)/', '$1', get_settings('siteurl').'/'), '/').'/i';
$inner_js = '';
$outer_js = '';
$inline_js = '';
for ($i=0; ($i < count($contents)); $i++) {
$contents[$i] = trim($contents[$i]);
if ($contents[$i] != '') {
if (preg_match('/<script [^>]*><\/script>/i', $contents[$i])) {
$outer = true;
preg_match_all('/(src|type|charset)=["\'](.*?)["\']/i', $contents[$i], $out, PREG_SET_ORDER);
for ($j=0; ($j < count($out)); $j++) {
if (strtolower($out[$j][1]) == 'src') {
$out[$j][2] = preg_replace('/^'.preg_quote(get_settings('siteurl').'/', '/').'(.*\.js)[\?]?[^\?]*$/i', '/$1', $out[$j][2]);
if (!preg_match('/^https?:\/\//i', $out[$j][2]) && preg_match('/\.js$/i', $out[$j][2])) {
if ($inner_js != '') {$inner_js .= ", ";}
$inner_js .= $out[$j][2];
$outer = false;
}
}
}
unset($out);
if ($outer) {$outer_js .= $contents[$i]."\n";}
} elseif (preg_match_all('/<script [^>]*>(.*?)<\/script>/i', $contents[$i], $out, PREG_SET_ORDER)) {
$inline_js .= $out[0][1]."\n";
} else {
echo $contents[$i]."\n";
}
}
}
if ($inner_js != '') {
echo '<script type="text/javascript" src="'.$inner_js.'"></script>'."\n";
}
if ($outer_js != '') {
echo $outer_js;
}
if ($inline_js != '') {
echo '<script type="text/javascript">//<![CDATA['."\n";
echo $inline_js;
echo '// ]]></script>'."\n";
}
}
add_action('wp_head', '_start', 0);
add_action('wp_head', '_end_flush', 100000);
add_action('wp_footer', '_start', 0);
add_action('wp_footer', '_end_flush', 100000);
?>
どんな感じになるかは、当サイトのソースでも眺めてみてください。
つぶやく
こんにちは。
最近この記事を見るのが日課になってます。
で、実験的にWPを使って試してみたのですが・・・
質問が一つあります。
Javascript のファイル名が、.js.php になってる場合でも結合されるのですが、問題が出るのでしょうか?
結合はソースで確認できるのですが、呼び出せてないようです。
kohaku さん、どもです。
動的結合している方の PHP で、 .js なファイルしか読み込んでないので、結合されません。
.php なファイルは、結合されないように functions.php を修正しないとダメですね。
ありがとうございます。
動作しない謎が解けました。
phpならgzipで圧縮対象になるようで、K2というテーマで使われているjavascriptはphpになってます。
残念です。。
PHP の fopen() 関数は、http://hogehoge〜 なモノも開けるので、動的結合している方の PHP を修正すれば対応できそうですけどね。
こんにちは。
質問させて下さい。
scripts.phpを作成してWordpressの.htaccessなどが置いてあるフォルダ直下に保存しました。
.htaccessは以下のように修正しました。
header.phpのスクリプト読み込み部分を以下のように修正しました。
src="http://localhost/***/wordpressフォルダ名/jquery.js, http://localhost/***/wordpressフォルダ名/common.js"これで実行してみたのですが、cacheフォルダには何も作成されませんでした。
http://codezine.jp/article/detail/971?p=1
に元記事の日本語訳されたものがあるのですが、こちらの方はうまく動きました。
これを修正して
scripts.phpを元記事の方の内容にして、.htaccessは上記と同じにして、header.phpを以下のように修正しました。
src="http://localhost/***/wordpressフォルダ名/scripts/jquery.js, common.js"これで実行すると、うまく動きました。
でも
src="http://localhost/***/wordpressフォルダ名/jquery.js, common.js"とすると、cacheフォルダには何も作成されませんでした。
scriptsの文字列を入れる必要はないと思うのですが、よくわかりませんでした。
次に
scripts.phpを、をかもとさんの内容に戻しまして、.htaccessは上記と同じにして、header.phpを以下のようにしました。
src="http://localhost/***/wordpressフォルダ名/scripts/jquery.js, common.js"こうすると、common.jsの中身だけがcacheフォルダに出力されました。
src="http://localhost/***/wordpressフォルダ名/scripts/jquery.js, http://localhost/***/wordpressフォルダ名/scripts/common.js"src="http://localhost/***/wordpressフォルダ名/jquery.js, common.js"にしても、cacheフォルダには何も作成されませんでした。
何かおかしい点があれば、教えて下さい。
よろしくお願いします。
Woprdpress2.6とXAMPPでローカル環境で試しています。
バニラさん、はじめまして。
元記事のままだと、動的結合したい JavaScript は、すべて scripts.php と同じディレクトリに無ければいけません。
私が修正した scripts.php は、様々なフォルダに分散している JavaScript を動的結合できるように修正したものです。
なので、指定は
src="http://localhost/***/wordpressフォルダ名/jquery.js,/***/wordpressフォルダ名/common.js"としてください。
動的結合したい JavaScript をすべて scripts.php と同一ディレクトリの中に入れておくのであれば、元記事のとおりで何も問題はありません。
お返事ありがとうございます。
src="http://localhost/***/wordpressフォルダ名/jquery.js,/***/wordpressフォルダ名/common.js"としても、cacheフォルダには何も作成されませんでした。
Wordpressフォルダ直下に scripts.php、jquery.js、common.js、cacheフォルダを置いてテストしているのですがうまくいきません。
試しに、scripts.phpと scriptsとして拡張子に何も付けないものを両方置いて
src="http://localhost/***/wordpressフォルダ名/scripts/jquery.js, common.js"こうすると、common.jsの中身だけがcacheフォルダに出力されました。
最初のコメントの「common.jsの中身だけがcacheフォルダに出力された」時も、scripts.phpと scriptsとして拡張子に何も付けないものを両方置いていたようです。
どうやら、こちらでは scripts.php だけでは、うまく動いてくれないみたいです。
scripts.phpと scriptsとして拡張子に何も付けないものを両方置いて
src="http://localhost/***/wordpressフォルダ名/scripts/jquery.js,/***/wordpressフォルダ名/scripts/common.js"とすると、cacheフォルダに、空の ***.txt と ***.gz が作成されました。
まずは指定した jsファイルを結合したいので functions.php には追加していないのですが問題ありますでしょうか?
よろしくお願いします。
バニラさん、どもです。
インストールした WordPress の URL が、http://localhost/***/wordpressフォルダ名/ であれば、前述したように
src="http://localhost/***/wordpressフォルダ名/jquery.js,/***/wordpressフォルダ名/common.js"と記述すれば、良いはずです。
scripts と言うディレクトリは無いんですよね?
ローカルの環境は Windows でしょうか?それとも Linux?Apache, PHP のバージョンは?
ひょっとすると、環境固有の問題があるのかもしれません。
元記事の scripts.php で動作するのであれば、そちらを利用していただいた方が良いと思います。
ご報告いただいたエラーメッセージの件は、こちらでテスト中のモノが表示されているようです。
コメントからは削除させていただきました。
こんばんは。
インストールしてある WordPress の URL は、http://localhost/***/wordpressフォルダ名/ です。
scripts と言うディレクトリはありません。
ローカルの環境は WindowsXP SP2 です。
XAMPP v1.6.6a
Apache v2.2.8
PHP v5.2.5
MySQL v5.0.51a
環境固有の問題でしょうか?
よろしくお願いします。
バニラさん、どもです。
と言うことであれば、13行目を
$expl = explode("\",$HTTP_SERVER_VARS["REQUEST_URI"]);に書き換えれば、動作するかもしれません。
UNIX 系では、ディレクトリの区切り文字は "/(スラッシュ)" ですが、Windows 系ではディレクトリの区切り文字は "\(バックスラッシュ or \記号)" なので。
ただし、この修正をした scripts.php をレンタルサーバなどの UNIX 系のOSに持っていくときは、また "/(スラッシュ)" に戻してやる必要があります。
なんにせよ、私が例示したスクリプトは UNIX 系のサーバでしか確認していないので、他にも問題があるかもしれません。
ローカルにテスト用環境を整えるなら、実運用を行うサーバと同じ OS, Apache, PHP, MySQL を用意した方が良いかもしれません。
書き換えてもダメでした。
元記事の方でも
$expl = explode("/",$HTTP_SERVER_VARS["REQUEST_URI"]);で動いているので、スラッシュは問題ないみたいですね。
時間があるときにVMwareで構築して、UNIX 系でテストしてみます。
ありがとうございました。
複数の JS をまとめてくれて便利なのですが
$out = str_replace(array("rn", "r", "n", "t", ' ', ' ', ' '), '', $out);のような感じで、改行やスペースを取り除きたいのですが
元記事のソースの場合、どうやって修正すれば可能でしょうか?
よろしくお願いします。
バニラさん、どもです。
ソースを読めば分かると思いますが、「// ファイルシステムのキャッシュ制御を行う」という所で、$buffer という変数にファイルの内容を読み込んだ後、fwrite() 関数で書き出していますね。
fwrite() 関数で書き出す前に、$buffer 変数の中身を加工してやれば良いんじゃないでしょうか?
こんばんは。
うまく出来ました。 :D
重ね重ねありがとうございました。
バニラ さん、どもです。
とりあえず、うまくいくようになって何よりです。
また何かありましたら、お気軽にどうぞ。
やさしい言葉ありがとうございます。
デザイナーでスクリプトは苦手なのですが、PHPは独学でコツコツやっております。
xrea(広告なしバージョンです)で試してみたところ、何も作成されずうまくいきませんでした。
うーん、つまづいてばかりでヘコみます。
‘jquery.js’、’common.js’、’jquery.js,common.js’ としたファイルを置いて試したところ
cacheフォルダに common.js の内容だけが作成されました。
",+.*\.js$"を
".*\.js$"に変更して
src="http://***/wordpressフォルダ名/jquery.js"にすると、空のキャッシュが作成されました。
最悪なことに元記事の方で試したところ、何も作成されずうまくいきませんでした。
八方塞になってしまいました。
xreaの環境は
Apache v1.3.37
PHP v5.2.5
MySQL v5.1.22-rc
何かおかしい点があれば教えて下さい。
よろしくお願いします。
バニラさん、どもです。
サーバは XREA と言うことですが、ひょっとすると PHP がセーフモードで動作していることも原因かもしれません。
回避方法は、下記リンクあたりが参考になるかと思います。
http://minobu.in/wp/archives/63
具体的には、.htaccess に、以下の記述を加えればうまくいくかも。
# 未検証ですので、これで本当に上手くいくかは分かりませんが
こんばんは。
xreaはセーフモードです。
加えてみましたが、ダメでした。
‘jquery.js,common.js’と名前をつなげたファイルを置いたところ
scripts.php は動作して、cacheフォルダに common.js の内容だけが出力されているので
セーフモードが問題ではないようです。
バニラさん、どもです。
重要なことを忘れていました。
この scripts.php は、js ファイルの実際のパスを取得する所で手抜きをしている(26〜28行目)ので、scripts.php が www ルートに配置されていないと正常に動作しません。
"wordpressフォルダ名" というパスに設置したいのであれば、26〜28行目を修正しないと正常に動作しないと思います。
# ちょうど1年前に書いたエントリなんで、記憶が曖昧な部分がありますね。
# ちゃんとソースを読み返せばいいんですが
色々と26〜28行目などに手を加えてみたのですが
力量が足りず、うまくいきません。
お時間が空いている時にでも修正してもらえないでしょうか?
よろしくお願いします。
こちらで試して疑問に思っていることがあります。
htaccess から正規表現で末尾に js が付いているファイルがリクエストされた時に scripts.php を実行するといった流れだと思うのですが
src="http://***/wordpressフォルダ名/jquery.js,config.js"‘jquery.js,config.js’というファイルを用意しておくと、ファイルが存在してリクエストが送られてcacheが作成されるのですが
‘jquery.js,config.js’というファイルを削除しておくと、ファイルが存在しないのでリクエストが送られなくてcacheが作成されないと考えています。
src="http://***/wordpressフォルダ名/jquery.js,config.js"という書き方をしていると、’jquery.js’、’config.js’の単体のファイルが存在しないことになっているので、これが問題だと思うのですが
これを別々のファイルとして認識させて実行しているといった、htaccess の書き方はあるのでしょうか?
元記事の方では、’jquery.js,config.js’がリクエストされる前に scripts.php を実行して回避していますよね?
的外れな意見かもしれませんが・・・。
バニラさん、どもです。
www ルートに scripts.php を設置しても正常に動作しないでしょうか?
例としてhttp://example.com/というサイトで、このスクリプトを使用するためには、
まず、scripts.php をhttp://example.com/scripts.phpでアクセスできる場所(wwwルート)に配置してください。
その上で、http://example.com/wordpressフォルダ名/jquery.jsとhttp://example.com/wordpressフォルダ名/common.jsという2つの JavaScript を結合する場合は、本文にも書いてあるとおり
と記述すれば、正常に動作するはずです。
どうしても www ルートにscripts.phpを置けないのであれば、前回のコメントでも申し上げているとおり、このスクリプトの修正が必要です。
realpath() 関数等を駆使して、ファイルの絶対パスを取得するようにしてやってください。
残念ながら、こちらではこのスクリプトを改修している時間はありません。
そこまでのサポートはできかねますので、申し訳ありませんがご自分で修正してご利用ください。
一つだけ、気になったことがあるので
正確には正規表現で、途中に「,」が少なくとも一つあり、末尾が「.js」で終わるファイルがリクエストされた時にscripts.phpを実行です。
# 本文では「RewriteCond %{REQUEST_URI} ",+.*.js$"」と記述してあります。
こんばんは。
無理なお願いをして申し訳ありませんでした。
スクリプトの修正を試してみます。
ありがとうございました。
バニラさん、どもです。
このスクリプトを汎用的な環境で使えるように改修するというのは面白い試みだと思うのですが、残念ながら現状ではそこまで手が回りません。
オンラインの PHP マニュアル( http://www.php.net/manual/ja/ )はかなり充実していますし、サンプルコードは探せばいくらでも出てきます。
PHPの勉強だと思って、スクリプトの修正にトライしてみてください。
こんにちは。
まずは元記事のスクリプトを修正して動作させることが出来ました。
元記事の方でパスの中に含まれている scripts の文字列をなくして動作させる為に試行錯誤しています。
RewriteCond %{REQUEST_URI} ",+.*\.js$"RewriteRule . /wordpressフォルダ名/scripts.php?files=%{REQUEST_URI} [L]
と返事を頂いたのですが、
src="http://***/wordpressフォルダ名/jquery.js,config.js"と書いている場合、ファイルがリクエストされて、scripts.phpを実行されると思うのですが実行されません。
デバッグしてみても、scripts.php にもたどり着けていません。
過去のコメントにも書いてますが、「jquery.js,config.js」というファイルを作成しておくと scripts.php が実行されます。
RewriteCond %{REQUEST_FILENAME} !-fファイルが存在しない場合という意味らしいのですが、これを追加してもダメでした。
RewriteLogでデバッグが出来ると思うのですが、htaccess に追加したところ Server error になってしまいました。
「jquery.js,config.js」が存在しない場合でも、scripts.php が実行されるようにしたいのですが可能でしょうか?
をかもとさんの環境ではキチンと動作しているので、xrea 固有の問題なのでしょうか?
php_flag allow_url_fopen onphp_flag allow_url_include on
php_flag register_globals on
などを、htaccess に追加してもダメでした。
htaccess のデバッグの仕方がわからないので、どこをどう直していいのか見当もつかなくなっていまして困っています。
よろしくお願いします。
バニラさん、どもです。
.htaccess の設定については、人に説明できるほど詳しくはありません。
マニュアル等を参照してみてください。
http://japache.infoscience.co.jp/japanese_1_3_6/manual/mod/mod_rewrite.html
お役に立てず、申し訳有りません。
お返事ありがとうございます。
教えて頂いたURIは、一応チェックしています。
をかもとさんの環境は さくらのレンタルサーバ ですよね?
さくらのレンタルサーバもビジネスプロ以外はセーフモードで動いていると思うのですが
どのプランで運用しているか教えて頂いてもよろしいでしょうか?
お試し期間でxreaとどう違うのがテストしてみたいと考えています。
よろしくお願いします。
XAMPPのローカル環境でも、ファイルがリクエストされていませんでした。
htaccessの設定ではなくて、php.ini や httpd.conf の設定かもしれません。
間違ったこと書いていました。
ビジネスプロ以外はモジュールモードですね、すいません。
バニラさん、どもです。
このサイトは、さくらのスタンダードプランで動作しています。
PHP は CGI モードで動作しています。
さくらを試されるのでしたら、「さくらのレンタルサーバ非公式FAQ ( http://faq.sakuratan.com/ ) 」は、結構役立つ情報がまとめてありますよ。
そちらも、参照してみてください。
こんばんは。
さくらのスタンダードプランで試してみましたが、ダメでした。
“.*\.js$” と js を1つにするとキャッシュは作成されました。
js を複数にすると作成されないですね。
www ルートに scripts.php を設置しています。
こちらのやり方が何かまずい気がします。
別のアプローチで試してみます。
ありがとうございました。
バニラさん、どもです。
さくらのスタンダードプランでもダメでしたか。うーん。
ちょっと時間のある時にじっくりと調べてみますね。