カテゴリー: HTML

  • 「SaCSS Special13 : SaCSS SUMMER FESTIVAL 第2弾 FRONTEND」でJSの話をしてきました。#SaCSS

    「SaCSS Special13 : SaCSS SUMMER FESTIVAL 第2弾 FRONTEND」でJSの話をしてきました。#SaCSS

    家のキーボードの「a」が反応しなくなりました。つらい。とろゆにです。

    先日、8/5に開催された、「SaCSS Special13 : SaCSS SUMMER FESTIVAL 第2弾 FRONTEND」にて登壇させて頂きました。

    「最近 SaCSS Special 続きで凄いなー」と観察してた頃、5月頃だったか、ハムさんから登壇しない?とお誘い頂きました。WordPress の話かなーと思っていたところ、JavaScriptの勉強の話をとのことで。何の冗談かと思いましたが僕なりに話せることもあるかなと思い、軽い気持ちでOKしました。

    ゆるふわな感じでとのことだったんですが、実際話す内容を考え出したら何を話して良いか全くわからず、何回かボツくらいました。

    そんなわけで、当日のスライドはこちらです。魔境感溢れる昨今のJS事情を僕なりにゆるふわにお伝えしたつもり。

    とりあえず、僕がJavaScriptに触れ出した頃とは状況がだいぶ変わっていて、ググって出てくるコードをコピペしても動かない、固有名詞がとにかく沢山出てくる、.js だけど JavaScript じゃないコードも沢山ある等、めちゃめちゃハイコンテクストに感じてます。そのあたりの文脈を少しでも整理出来たらと思っていろいろ書きました。

    ES6で変わったことを全部は紹介できないので、詳細はいまからはじめるECMAScript 6も見て頂ければいいのかなと。

    https://www.slideshare.net/1000ch/begin-ecmascript6

    また、こちらも参考になるかと。

    結局伝えたかったこと

    • みんな攻略本の無いダンジョンをさまよってる感じなので焦らない。ビビらない。
    • 普段コピペしているコードを読んでみる。
    • とりあえず、動くモノをつくってみる。
    • JSは猛烈に進化し続けるので、やりながら徐々に覚える。少しずつ実戦投入していく。
    • 仲間を作って見せ合いっこしたりする。
    • 1日30分でも週に1時間でも良いので、挫折しない程度にJavaScriptに触れる時間を作る。

    ここらへんが、言語や技術を勉強する上でとにかく大事だなと思っているので、それが伝われば良いなと思ってました。英語の勉強とか楽器とかにも通じる話かもしれません。(どちらもサボり気味ですが・・・)

    CoffeeScriptで、ライフゲームしてみた。」みたいな記事を書いたら滅茶苦茶JavaScript出来る人からいろいろツッコミをもらったりしました。そういうやりとりが楽しかったり、勉強になったりするので、公開するのはほんとに大切だと思います。

    僕は友人が勉強に作ったモノを改造して遊んだり近所の温泉のデータを収集してみたりしてゆるっとやってます。

    今回のをきっかけに何か書いてみたと言う人が居たら是非教えて下さい。喜んで見に行きます!

    イベントの感想

    他の登壇者の方々の話もいろいろ刺激的でした。個人的にはアップルップルの森田さんの「コーディングしながらデザインルールをドキュメント化してみた」が特にビビりました。マークアップエンジニアとしてそういう仕事のやり方があるのかーと考えさせられました。

    関口さんの、「おれの使うさいきょうえでぃた」は PHPStorm 使いとしてまだまだ知らない機能がいっぱいあるなーと思いました。3年くらい使っているのに知らないことばかりです。

    編集後記

    45分の予定が10分も押してしまいすみませんでした!ほんとはもう少し余裕を持ってやるはずだったんですが…

    スコープの広い話をすることの難しさを改めて感じました。なかなか謎の単語がずらずら並ぶ感じになってしまったので、また何かJSの勉強をしたくなったときにでも見返して頂ければ幸いです。

    スライドを作りながら改めて jQuery を調べたのですが、やっぱり使いどころを間違えなければ良いツールだなーと再認識しました。そういう意味でも生の JavaScript やブラウザのAPI、他のライブラリが解決しようとしている課題とかは知らないとだなぁと。温故知新。

    さいごに

    まさか、SaCSS に登壇する日が来るとは思いませんでした。業界入ったときの自分に言っても信じないでしょうね。不思議なモノです。これからも精進したいと思います。

    また、前日にa-blog cms 勉強会 in 札幌 2017にも参加させて頂きました。WordPress や Movable Type 、Drupal くらいしか制作で携わったことが無い僕にはなかなか新鮮な体験でした。他の CMS のアプローチの仕方を知るのは、いろんな気付きがありますね。

    Frontend Nagoya の話も聞けたので近いうちに偵察に行こうかなと思います。

     

    札幌楽しかったです。お酒も食べ物も美味しかったです。人生で初めて空を飛んでみましたが、意外に松本からは行きやすいんだなーと感じました。ひこうきすごい。また機会を作って遊びに行きたいです。そのときや別の場所でお会いしたときは、また遊んで下さいー!

  • Browsersync とローカルサーバーを連携させて自動リロードさせる。

    おかんが喜ぶらしいので書きます。

    Browsersync という node.js 製の開発ツールがあります。これを使うと、開発用のサーバーが(通常 localhost:3000)で立ち上がって、ファイルの変更時にブラウザを自動リロードしてくれたり、CSSとかを自動で差し替えてくれたり、 見ている全てのブラウザでスクロールの位置などを同期してくれたりととにかくいろいろ便利なツールです。Sass や Babel 等と組み合わせるととても便利だったりします。

    VCCWWocker や PHP のビルトインサーバーで WordPress や PHP の開発を行ったりしますが、そのときにも Browsersync 出来たら便利ですよね。(というか別にWordPressやPHP以外でも便利です)

    こんな感じで出来ます。何はともあれ、Browsersync をインストールします。

    $ npm install -D browser-sync

    そして、こんな感じで、server.js のようなファイルを、開発中のテーマの中に作ります。

    var browserSync = require("browser-sync");
    browserSync({
      proxy: 'vccw.dev',
      files: [
    	"./dist/styles/**/*.css",
    	"./dist/scripts/**/*.js",
    	"./**/*.php",
      ]
    });

    proxy に設定するのは、ローカルサーバーの hostname です。WP-CLI でローカルWEBサーバーを立てて開発などをしているときは、localhost:8080 等にします。

    files には、監視するファイルを入れます。指定したファイルに変更があったときにブラウザが自動でリロードしたり、CSSファイルの場合は、動的に差し替えてくれます。JS や CSS をビルドしたりする際は、出力されたものだけを指定しておくのがベターです。

    そして、server.js を起動すると、Browsersync が起動して、いろいろ便利にやってくれます。

    $ node server.js

    package.json に

    {
      "scripts": {
        "serve": "node ./server.js"
      }
    }

    等と書いておくと、

    $ npm run serve

    みたいな感じで使えるので、よりスマートかなーと思います。

    glup と組み合わせる場合

    gulp を使っている場合はこんな感じになります。

    var browserSync = require("browser-sync");
    var gulp = require('gulp');
    var sass = require('gulp-sass');
    
    gulp.task('sass', function(){
      gulp.src('./src/styles/*.scss')
        .pipe(sass())
        .pipe(gulp.dest('./dist/styles'));
    });
    
    gulp.task('browserSync', function () {
      browserSync({
        proxy: 'vccw.dev',
        files: [
    	    "./dist/styles/**/*.css",
    	    "./dist/scripts/**/*.js",
    	    "./**/*.php",
        ]
      });
    });
    
    gulp.task('watch', function(){
      gulp.watch('./src/styles/*.scss', ['sass']);
    });
    
    gulp.task('default', ['browserSync', 'sass', 'watch']);

    gulp で sass の自動ビルドをやってる場合などは、こんな感じで使えば良いんじゃ無いかなと思います。

  • Vue + Vuex を使ってみた感想と、Redux との比較

    Vue + Vuex を使ってみた感想と、Redux との比較

    いいにくの日ですね。肉食べたいです。

    React + Redux にはよくお世話になっている昨今なのですが、React 以外も扱いたいなと思ったこと、そもそも Flux に対する理解が浅いんじゃ無いか?ということで、Vue.js + Vuex をちょっと勉強してみました。

    つくったもの:https://github.com/torounit/vuex-todo

    ここら辺をいろいろ参考にしました。

    Redux と Vuex の違い。Reducer と Mutation の違い。

    flow

    両方とも Flux パターンであるため、基本的な考え方は変わりません。ただ、Redux で Reducer が担って役割が、Mutation というモノが処理することになります。

    Redux では ActionType に応じて Reducer が State を変更という流れですが、Vuex の場合は Action 側から MutationType を指定して値を投げるという流れになります。

    複数の State の変更が同時に変化するような場合だと、Action から 複数の Mutation をそれぞれ呼び出すということになります。Redux だと、ある Action に対して複数の Reducer が紐付くという格好になるので、ここら辺の考え方が逆転しています。

    Vuexのドキュメントを参照すると結構解りやすいです。 Actions · GitBook

    actions: {
      checkout ({ commit, state }, products) {
        // save the items currently in the cart
        const savedCartItems = [...state.cart.added]
        // send out checkout request, and optimistically
        // clear the cart
        commit(types.CHECKOUT_REQUEST)
        // the shop API accepts a success callback and a failure callback
        shop.buyProducts(
          products,
          // handle success
          () => commit(types.CHECKOUT_SUCCESS),
          // handle failure
          () => commit(types.CHECKOUT_FAILURE, savedCartItems)
        )
      }
    }

    一つの Action がどういった変更を起こすのか等が局所的になるので、結構解りやすい気がします。

    また、Mutation に渡される state ですが、単純に、現在の State オブジェクトそのものです。なので当然参照渡しです。これを直接弄ります。

    mutations: {
      increment (state, n) {
        state.count += n
      }
    }

    テストの書きやすさ等には結構影響してきそうです。

    Vue.js の デバッガーが強力

    Chrome の拡張機能で Vue.js devtools がかなり強力でした。

    Vuex にも対応していて、過去の State の状態に戻したりなどシンプルながら必要な機能がそろっている感じです。GIF アニメを撮ったので、そちらをどうぞ。
    vue-debugger

     

    感想

    正直、Flux への理解が浅いまま Redux を触っていたので、いろいろと Vuex を触る上で逆に苦労したりしましたが、なんとなく Flux ってこういうモノかというのがつかめてきた気がします。

    最初は、Riot.js + RiotControl をやってみようと思ったのですが、RiotControl のソースコードがめちゃくちゃシンプル(20行)で、逆にどうして良いか解らなかったんですが、さっき改めて読んでみたら解った気になってきたのでこっちでも遊んで見ようと思います。

    Vue.js 自体は、ドキュメントが整備されていてローカライズもされているので割と取っつきやすいですし、デザイナーさんとかと組んだときとかに結構便利そうな気がしました。特にCSS周りは React だとお世辞にも良いとは言いがたい状況なので、なかなか良い感じです。また、公式のデバッグツールや vue-cli 等のツールがそろってるのも結構便利ですね。

    やっぱり自分で手を動かしていろいろ書いていくのは学びがあって良いですね。

  • JavaScript 祭りでキレてきました。#jsfes

    JavaScript 祭りでキレてきました。#jsfes

    https://twitter.com/todays_mitsui/status/787198459862364160

    https://twitter.com/motchi0214/status/787197871749668864

    https://twitter.com/todays_mitsui/status/787201187472736256

    どうしてこうなった。。。

    どうも。先日開催されました、秋のJavaScript祭 in mixi 〜秋のJavaScript収穫祭〜 にて、「『ホームページ屋さん』とJavaScriptの未来。」というセッションをさせて頂きました。動画も公開されるようなのでそちらもどうぞ。

    わりとゆったり聞いてもらおうかなと思ってました。本当です。

    初対面の方もたくさん居たのですが、その人たちからもキレてると言われていたのでそうなんでしょう。緊張って恐ろしいです。

    しゃべり出すまでは全然普通だったんですけどね。カメラを意識してしまったせいでしょうか。それとも普段スベるところで若干笑って頂いて調子に乗ってしまったのでしょうか。そもそも1時間前に現地に到着し、オシャレな喫茶店でコーヒーを飲んでから会場に向かうなど、普段の僕が絶対やらないようなことしてたあたりから何かが狂っていた気がします。

    まぁ、結局キレ芸キレ芸って煽りまくったあげくに自分でキレ芸した岡本君が全部悪いと思います。

    普段からキレてる人だとか、カンニング竹山みたいな人って思われるのはさすがにどうかなと思ったので、WordCamp Kansai 2016 で登壇させて頂いたときの動画がありますので、ご参照頂ければと思います。

     

    当日できなかった写経のはなしとか。

    やっぱりTodoアプリなどの写経をしたりするのは一番手軽に出来ることかなとは思います。

    実際に手を動かして書いてみるって大事で、思わぬところで躓いたりするもんです。

    そして、とりあえず動くようになったら、ちょっとした機能を付けてみたり、テストを書いてみたりなど、自分なりにいろいろ足してみるってのを僕はよくやってます。

    また、エンジニアのGithubを眺めると習作として作ったものが上がっていたりします。知り合いのエンジニアのを写経してみて、解らないところを本人に聞いてみたりするとかも良いんじゃ無いかなーと。そういうやりとりって結構楽しいんですよね。

    その際、スターを付けてあげるとちょっとポイント高いです。

    あと習作として作ってみたモノに頼んでも無いのにコードレビューされたり、「俺も作ってみたー」って言われていきなりすごいコードを見せられたりして「ぐぬぬ・・・」ってなりながら楽しかったのを覚えてます。

    というわけで、僕のGithub アカウントはこちらになります。https://github.com/torounit

     

    さいごに

    つらいって話たくさんした気がしますけど、カッコいいモノとか作りたいなーってのは有るので、そういう楽しさとかを原動力に学んでいくのが個人的には良いのかなと思っていたりします。

    スタッフの皆様・ご来場の皆様のおかげでとても楽しかったです。準備などお疲れ様でした。ありがとうございました!

    また、酔っ払いの僕に夜中まで付き合って下さった皆様、本当にありがとうございました!!!

  • 別に $ npm install -g gulp しなくても大丈夫って話。

    別に $ npm install -g gulp しなくても大丈夫って話。

    $ npm install -g gulp-cli

    みたいにグローバルにモジュールをインストールさせる風潮ってどうなんだよ!みたいな話をしてまして。

    僕自身もいろいろそれには非常に違和感を持っていたり、そもそも node.js のバージョンを変更したときとか、グローバルに入っているモジュールの面倒も見ないといけないことが非常に面倒くさいんですよね。grunt-cli も gulp-cli も bower も入れたくないし、可能な限りプロジェクトの node_modules で完結させたいんですよね。バージョンアップとかも面倒ですし。

    そんなわけで、いろいろ試してみたんですが、

    $ ./node_modules/.bin/gulp hoge

    とかやるのは、実際面倒くさいし、現在のディレクトリの位置次第で変わったり。

    $ $(npm bin)/gulp hoge

    とかやるのも最初の奴よりはマシですが、面倒。bashrcとかを使って、./node_modules/.bin にパスを・・・というのは流石に論外。Windows の人はどうすんねんとか言い出すとキリは無いわけで。

    というわけでいろいろ試してみたんですが、どうやら npm 2系以上からは、npm scripts にサブコマンドを渡せるようになっていたようです。だいぶ前から出来たんですね…

    つまりどういうことかというと、package.json の “scripts” のところに、以下のように書いておきます。

    {
      "scripts": {
        "gulp": "gulp"
      }
    }

    そうすると、以下のコマンドで gulp を走らせることが出来ます。

    $ npm run gulp

    そして、それに加えて、gulp で定義したサブコマンドなども渡せるようです。

    $ npm run gulp build

    grunt 等の他のタスクランナーなどでも同等のことは出来るはずです。これで面倒くさいグローバルなモジュールの管理をしなくて済みますね!!! バージョンの不整合などに悩まされる心配も無さそうです。

    とりあえず、gulp をお使いの皆さんは今すぐ package.json に1行足しましょう。

    npm scripts を使うと、npm install したあとに勝手に CSS や JS をビルドしたりいろいろ便利なことが出来るので面白いですよー。

    参考: npm で依存もタスクも一元化する – Qiita

    コメント欄の議論も含めてなかなか濃いですね。

  • WordPress等のCMSがjQuery等を出力するとき、browserifyで上手いことやる。

    最近、jsでrequireが無いと生きれない身体になってしまいました。

    CoffeeScriptから卒業して、ES6でコードを書くようにもしています。ES6のClass構文とかアロー関数が無いと人生辛くなってきました。

    そんなわけで自分はbrowserifyを使ってコードを書いてます。requireを良い感じにコンパイルして1つのファイルにまとめてくれる奴です。

    そんなときに結構困るのがwp_head()や、wp_footer()からscriptタグでjQueryunderscore.jsBackbone.js等の有名処のライブラリが出力される場合があることです。

    functions.phpで無理矢理消しても良いのですがそれは筋が悪そうなので、browserify-shimを使って指定されたライブラリはグローバル変数のものを使うようにします。

    そのコードがこんな感じ。

    gist: https://gist.github.com/torounit/0da591b62d03187b4641

    package.json

    {
      "browserify-shim": {
        "jquery": "global:jQuery",
        "underscore": "global:_",
        "backbone": "global:Backbone"
      },
      "devDependencies": {
        "babelify": "^6.1.3",
        "browserify": "^11.0.0",
        "browserify-shim": "^3.8.10",
        "debowerify": "^1.3.1",
        "gulp": "^3.9.0",
        "gulp-notify": "^2.2.0",
        "vinyl-buffer": "^1.0.0",
        "vinyl-source-stream": "^1.1.0",
        "watchify": "^3.3.0"
      },
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "author": "Toro_Unit",
      "license": "ISC"
    }
    

    gulpfile.js

    'use strict';
    
    // ==================================
    //
    // Config.
    //
    // ==================================
    
    var config = {
        browserify: {
            bundleOption: {
                cache: {}, packageCache: {}, fullPaths: false, //for watchfy.
                debug: true,
                entries: './src/scripts/app.js',
                extensions: ['js', 'jsx'],
            },
            dest: './dist/scripts/',
            filename: 'app.js'
    
        }
    }
    
    // ==================================
    //
    // Load modules.
    //
    // ==================================
    
    var source = require('vinyl-source-stream');
    var browserify = require('browserify');
    var watchify = require('watchify');
    var gulp = require('gulp');
    var notify = require("gulp-notify");
    
    var handleErrors = function() {
        var args = Array.prototype.slice.call(arguments);
        notify.onError({
            title: "Compile Error",
            message: "<%= error.message %>"
        }).apply(this, args);
        this.emit('end');
    };
    
    // ==================================
    //
    // Compile JavaScripts.
    //
    // ==================================
    
    gulp.task('setWatch', function () {
        global.isWatching = true;
    });
    
    gulp.task('browserify', function () {
    
        var b = browserify(config.browserify.bundleOption)
            .transform('babelify')
            .transform("browserify-shim")
            .transform("debowerify");
    
        var bundle = function () {
            b.bundle().on('error', handleErrors)
                .pipe(source(config.browserify.filename))
                .pipe(gulp.dest(config.browserify.dest));
        };
        if (global.isWatching) {
            var bundler = watchify(b);
            bundler.on('update', bundle);
        }
        bundle();
    });
    
    gulp.task('watchify', ['setWatch', 'browserify']);
    

    タスク

    $ gulp browserify //コンパイル
    $ gulp watchify //ファイルの変更を監視して自動コンパイル

    といった格好になります。

    browserify-shim

    browserifyには、transformというAPIがありまして、ここにプラグインを突っ込むことが出来ます。

    アプリケーションコード上のJSから呼び出せるのは通常、node_modulesにインストールされているものか、ファイル名で指定されたJSのみですが、require(‘jquery’) のようなコードを書いたときに、node_modulesではなく、グローバル変数のjQueryを呼んでくることが出来るようになります。

    package.jsonにbrowserify-shimという項目を作ってこんな感じで指定すれば動作します。

    {
      "browserify-shim": {
        "jquery": "global:jQuery",
        "underscore": "global:_",
        "backbone": "global:Backbone"
      }
    }

    debowerify

    debowerifyは、bowerでインストールしたライブラリをrequireやimportできるようにするプラグインです。

    最近オワコンと言われているbowerですが、jQueryプラグインなどをnpmでインストールして持ってくると、そこからnode_modules内のjqueryを取ってきてしまうと言う問題があります。shimの方の設定が悪いのかどうなのか解らないのですが、そんなわけでその手のものは仕方ないのでbowerで持ってきてrequire(import)します。

    npmだけで管理はしたいのでどうにかしたいところです。最悪、jQueryプラグインも普通にscriptタグで読み込ませるコトもたまにやります。

     

    まとめ

    とりあえずこれで、ライブラリの多重インストールを避けつつインストールが出来ます。後はテーマ側で

    $js_path = get_template_directory_uri() . 'dist/scripts/app.js';
    wp_enqueue_script( 'app', $js_path, array( 'underscore', 'jquery' ), '1.0.0', true );
    

    とかやればとりあえずは終了です。

    まぁ、WordPressのunderscore.jsとかBackbone.jsバージョンが古い問題とかもあるんですけどね。。。

     

  • git pushしたときにFTPしたりgruntタスクを実行する。

    FTPしか使えないレンタルサーバーでの案件でGitを入れようとするとき、ソースの管理って結構大変だったりします。なので、git pushしたときに自動でFTPすると、非常に便利でした。

    このgrunt-githooksと、grunt-ftpushを使ってこんなGruntfileを作ります。

    module.exports = (grunt) ->
    
    
      grunt.loadNpmTasks "grunt-ftpush"
      grunt.loadNpmTasks "grunt-githooks"
    
    
      grunt.initConfig
    
        ftpush: {
          build: {
            auth: {
              host: 'example.com',
              port: 21,
              authKey: 'pass'
            },
            src: '',
            dest: '/path/to/dir',
            exclusions: ['.*','node_modules/*','.sass-cache/']
            keep: [],
            simple: true,
            useList: false
          }
        }
    
        githooks: {
          options: {
            dest: '.git/hooks',
            hashbang: '#!/bin/bash',
            template: './node_modules/grunt-githooks/templates/shell.hb',
            startMarker: '## GRUNT-GRUNTHOOKS START',
            endMarker: '## GRUNT-GRUNTHOOKS END'
          },
          setup: {
            'pre-push': 'deploy'
          }
        }
    
      grunt.registerTask 'deploy', ["ftpush"]

    これを設定したら、以下のコマンドを実行します。

    $ grunt githooks

    これで.git/hooks/pre-pushが作成され、pushしたときにgrunt deployが実行されるようになります。grunt deployにこのコードだとFTPしか設定してありませんが、サーバーにアップロードする前にCSSやJSのコンパイル・結合・圧縮などを実行するとより、使い勝手がいいと思います。

    例:https://github.com/torounit/torounit2015/blob/master/Gruntfile.coffee

    サーバーによってはftpushではなく、grunt-rsync等を使ったほうが良い場合も多いですが基本はこんな感じで、push時にサーバーと同期を取る事ができます。

    やっぱりいろいろ自動化しておくと便利で良いですね。ファイルの同期は手動でやるとだいたいろくな事が無いので、自動化の威力がいろいろ感じられて良いかなと思います。

    note

    • gitにsourcetreeを使う場合、環境変数が読まれないので、command not found: grunt とか言われる場合があります。その場合、githooksのテンプレートをいじって、bashrcとかをexportすると良い感じになります。

     

  • とあるサイトの高速化についてフロントエンドでやったことまとめ。

    業務で携わっている案件なのですが、アクセス数の急増が見込まれるイベントがありまして。準備期間も少なく、バックエンド側でできることがほぼないという状況でサイトを落とさないようにがんばる!というお仕事でした。レガシーソースてんこ盛り。CSSプリプロセッサとか何それ状態。

    そこで実施した対策のまとめです。サーバー・アプリケーション・サイトの構成によって、効果の大小はありますが、比較的効果があったと思われるものをつらつらと。

    リクエストの削減とファイルサイズの最適化

    まず一番最初に考えなければいけないのがリクエスト数です。すごいおおざっぱに言うと、WEBサーバー(ApacheとかNginxとか)への負荷は、PV数×リクエスト数です。PVがそんなに無くてもそのページのリクエストがめちゃくちゃ多いとそれだけでかなりの負荷になります。リクエストを半分にできれば2倍の人数がさばけるってことに、すげーおおざっぱに言うとなります。

    ファイルサイズについてはそりゃ当然、軽ければ軽いほど良いわけで。

    Lazy Loadを導入して画像を遅延ロード

    Lazy Loadは、画像を遅延ロードするプラグインです。画像がウィンドウの描画範囲に現れるまでロードしないというやつです。

    <img width="160" height="45" src="dummy.png" class="lazy" data-original="http://example.com/example.jpg" alt="" />
    
    <script src="//code.jquery.com/jquery-1.11.0.min.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/jquery.lazyload/1.9.1/jquery.lazyload.min.js"></script>
    
    <script>
    $(function() {
        $("img.lazy").lazyload();
    });
    </script>

    追記:当然、何でもかんでもLazy Loadにして良いかという話ではないです。HTMLの仕様的にどうだとか、JSが無効だと見れなくなるだとか。個人的には、緊急回避の手段かなと思ってます。

    CSS Spriteで画像を結合

    メンテナンス性とのトレードオフな部分もあるんですが、それでも可能な限りCSS Spriteにするとそれなりにリクエストを削れます。

    また、同じような画像だと、トータルの画像サイズも下がる場合があります。

    CSSやJSも結合

    当然、CSSやJSも結合できるものはとにかく結合。grunt-contrib-cssmingrunt-contrib-uglifyとかを使うと、それなりにメンテナンスせいを保ったまま、圧縮と結合ができるのでなかなか具合が良いです。

    画像の圧縮

    画像の圧縮も当然ですが、grunt-contrib-imageminや、 grunt-imageなどを使って圧縮するのが手軽です。

    ただ、JPEGは元から圧縮されている関係上、この手の方法だとあまり効果が無い場合があります。ファイルサイズが大きいものは、Photoshopで地道に目視で圧縮しました。品質を60%くらいでもそんなに見た目変わらないので、やるなら思い切って削ると良いかなと。

    CSS / JS / 画像などの配信の最適化

    ファイルサイズを削ったり、リクエストを削ったりってのは、CSSの書き方とか静的リソースの作り方そのものの話でしたけど、これをいかにして高速に配信するかというのも結構大事です。

    静的リソース用サーバー立てる

    おいおい、フロントエンドなのかそれは、と言われたらそりゃそうなんですけど。

    静的リソース用サーバーとアプリケーションサーバーに求められるものは当然別です。画像なんて一度アップロードしたら更新しないなんてことはざらにありますし。でもHTMLはそれなりに更新がありますし、CMSとかが入ってるとまた大変な話。

    サーバーからいじれたら、nginxをサーバーのフロントエンドに立てて、直接nginxから見に行くようにするとかいろいろあるんですが、そういうわけにもいかなかったわけで。今回は事情によりApache。

    Amazon S3とか活用するのも有りだと思います。というかたぶんそっちの方がイマドキだし、絶対早い。

    Keep-Aliveを有効に

    普通に、HTTPでリクエストが飛ぶと、TCPハンドシェイクという動作が発生します。1リクエストごとに、これが発生してしまうのですが、Keep-Aliveを設定しておくと、一定時間ハンドシェイクを省略できるというやつです。

    細かいこと言い出すときりがないので何ともいえないんですが、リクエスト数が多ければデフォルト値でもそれなりに有効な気がします。

    <ifModule mod_headers.c>
    Header set Connection keep-alive
    </ifModule>

    サーバーによっては、.htaccessに上記の記述で有効になります。まぁ.htaccessでやる話でもない気はしますけどね。

    Expiresヘッダの設定

    Expireはファイルの有効期限です。この有効期限内であれば、ブラウザのキャッシュを利用してくれます。これが未設定だと1週間くらいで失効してしまうのですが、これを適切に設定することでリクエストをそれなりに削れます。

    今回の案件は、7割か8割くらいはリピーターだったりで初見さんが少なかったのもあって、これもそれなりに大きな効果があったのかなと思います。

    最近では、.htaccessで設定できることが多いです。拡張子ごとに設定できます。以下のコードだと、とりあえずCSSとJS、画像は1ヶ月キャッシュを有効にしてます。

    ExpiresActive On
    ExpiresByType image/gif "access plus 2592000 seconds"
    ExpiresByType image/png "access plus 2592000 seconds"
    ExpiresByType image/jpeg "access plus 2592000 seconds"
    ExpiresByType image/x-icon "access plus 2592000 seconds"
    ExpiresByType text/css "access plus 2592000 seconds"
    ExpiresByType application/x-javascript "access plus 2592000 seconds"
    ExpiresDefault "modification plus 1 week"

    CDNを活用する

    jQueryだとかfontawesomeとか有名どころのライブラリは、CDNから呼びましょう。キャッシュがあれば、当然それを使ってくれます。cdnjs.comなんかは有名ですね。

    また、一つのホスト(origin)への同時接続数はブラウザの標準設定で6つまでなので、そのぶん他のリソースをロードできます。

    まとめ

    フロントエンドの高速化といってもやることめちゃくちゃ多いし、幅広いなーというのが印象でした。これ以外にもサーバーにサブドメインを複数割り当てて、一度にロードするファイル数を水増ししてみるとかいろいろやってみました。

    結果としてとりあえず、Lazy Loadはかなり強力な手段だなと思いました。リクエストが発生しなければそもそもサーバーに負荷はかからないので。細かい画像やバナーも多かったので、リクエストがこれだけでかなりの数削減できました。

    結局フロントエンドチューニングに銀の弾丸はやっぱり存在しないし、システム構成等でもだいぶ変わってくる部分は大きいと思います。

    レガシーなソースでもgruntとか入れるメリットはでかいなぁとも感じました。

    やっぱり、基本的には「塵も積もれば山となる」的発想で立ち向かうしかないのかなと。そもそも画像やCSSにしたって一つ一つのサイズはたいしたことないという事を考えると、その「塵が積もって」サイトを遅くしてるんですからね。

    参考

  • Gruntでbrowserify使ってCoffeescriptをコンパイルする。

    なんかCoffeescriptをGruntでコンパイルして、concatしたりとかだるいなーってずーっと思ってました。
    Classとかを他のファイルから参照しようとするとwindow.classNameとか@classNameとかにしないといけないですし、ファイル名順に結合するので、アルファベット順で後ろにくるクラスを読もうとするとエラー吐いたり、いまいち依存関係が見えなかったり。

    小規模な開発であれば問題ないのですが、Coffeescriptで大量にClassを作ったりすると、結構大きな問題になってきます。かといってrequire.jsとか面倒くさい。

    というわけで、最近流行のbrowserifyを使ってみました。

    npmとbrowserifyを使ったクライアントサイドのウェブアプリ開発
    Browserify: それはrequire()を使うための魔法の杖

    ざっくり言うと、ブラウザで、node.jsのようなrequireが使えるようになるツールです。

    たとえば、以下のようなHoge.coffeeがあるとすると

    class Hoge
        piyo: ->
    
    module.exports = Hoge
    

    以下のコードで、読み込んでくることができるという代物です。

    Hoge = require("./Hoge")
    hoge = new Hoge()
    

    debowerifyとかをtransformに指定しておくと、bowerでインストールしたモジュールなんかも取ってこれるので、大変素敵です。

    $ = require("jquery")

    それをgruntでファイル監視して自動化するようにしたのがこちらです。

    module.exports = function(grunt) {
      grunt.initConfig({
        dir: {
          coffee: 'coffee',
          javascripts: 'javascripts'
        },
        esteWatch: {
          options: {
            dirs: [ 'coffee/**' ],
            extensions: ['coffee'],
          }
          coffee: function(filepath) {
            return ["browserify"];
          }
        },
        browserify: {
          dist: {
            files: {
              'javascripts/all.js': ['coffee/all.coffee']
            },
            options: {
              transform: ['coffeeify', "debowerify"],
              browserifyOptions: {
                extensions: ['.coffee']
              }
            }
          }
        }
      });
      grunt.loadNpmTasks('grunt-browserify');
      grunt.registerTask('default', ['esteWatch']);
    };

    transformにcoffeeifyを設定しておくと、CoffeeScriptでもコンパイルできるようになります。
    browserifyOptionsのextensionsに.coffeeを指定しておくと、CoffeeScriptのファイルでも拡張子を省略してrequireできました。

    名前空間も汚染しないし、依存するClass等は明示的になるし、読み込み順に左右されないJSを書くことができるようになります。その代わり、$ = require(“jquery”)等は各ファイルで行う必要があります。実行時には一度しか読み込まれないようなので、パフォーマンス的にもよろしいです。

    GruntでBowerしたり、Coffeeしたり、ConcatしてJSを書いている人にはホントに便利です。

  • grunt-bower-concatでbowerを超便利に使う。

    bowerを使ってjsをパッケージ管理したかったんです。
    でも普通に使うと、bower_componentsから、ファイルを動かしたり、scriptタグをたくさん書いたり、いろいろめんどくさいです。

    そんなときに、grunt-bower-concatがものすごく便利です。

    このタスクを回すと、bower_componentsの中のjsを取り出し、結合して一つのjsにまとめてくれます。
    また、その際、依存関係を解決しながら読み込み順などを設定して結合します。

    jsをインストールする際、

    bower install hoge
    grunt bower_concat

    とするだけで、JavaScriptのインストールが完了します。scriptタグを書いたりしなくても大丈夫。非常にbowerが手軽に扱えます。PHPのComposerくらいお手軽につかえます。

    インストール

    npm install grunt-bower-concat --save-dev

    にて、モジュールをインストール後、Gruntfileに

    bower_concat:
      all:
        dest: 'assets/js/lib.js'
        exclude: [
            'jquery',
            'modernizr'
        ]
        bowerOptions:
          relative: false

    と、設定を書きます。destは、吐き出しファイル名、excludeには、除外するライブラリを記述します。bowerOptionsは正直よくわかりません。そのほかのオプションは、Githubを参照してください。

    Gruntfileはこんな感じになります。
    https://gist.github.com/torounit/10652847

    最新版がbowerで配布されるようなモジュールなどを扱うときは死ぬほど便利です。作成されたjsをgrunt-contrib-uglifyなどで、圧縮したりするのも楽勝です。

    ただ、CSSには対応していません。
    また、bower.jsonのmainが記述されていないものもだめだったりします。まぁ、主要なライブラリは結構ちゃんと動くので。。。。。

  • CoffeeScriptで、ライフゲームしてみた。

    50行で作る、HTML5+JavaScriptで『ラングトンのアリ』の簡単プログラミング! – あのねノート。を読んで、無性にライフゲームが書きたくなったので、やりました。

    さすがにJavaScriptで書くのは何番煎じどころの話じゃないので、CoffeeScriptのClassで書いてみました。

    [gist]https://gist.github.com/torounit/6613937[/gist]

    jQueryと、これをコンパイルしたjsを読み込んで、HTMLに適当にCanvasを書いてあげれば動くはずです。
    CoffeeScriptでjQueryプラグインを書く参考になったりするかもしれません。

    Canvas使うと、さくさくと動くものが作れて、楽しいですね。

    こんな感じで動きます。

    セル・オートマトン等はルールも難しくないので、案外さくっと作れるわりに、出来上がると案外楽しいので、遊びついでにやってみるってのもいいもんですね。

  • Nivo Sliderのエフェクトを動的に変更したよ!!!!

    Nivo Sliderといえば、もう定番のコンテンツスライダーのjQueryプラグインですよね。エフェクトもたくさんあるし、使い方も簡単ですし。

    そのエフェクトなんですけど、

    「左の矢印と右の矢印で動きを変えたい!!!!!!」

    って思ったことないですか?戻るボタンで左にスライドとか、次へボタンで右にとか。まぁ、別に僕も人から言われて「確かに!」って思ったんですけど。。。。。。

    そんなの機能としてあるでしょ!とか呑気に思っていたら、どうやらそんなものはないようなので、JQueryをガリガリ書く羽目になったわけで。そのコードをさらしたいと思います。

    [js]
    $(window).load(function() {
    var $slider = $(‘#slider’);
    $slider.nivoSlider({
    effect: "sliceUpRight",
    afterChange: function(){
    $slider.find("img").attr("data-transition","sliceUpLeft");
    }
    });

    $(".nivo-prevNav").on( "click", function(){
    $slider.find("img").attr("data-transition","sliceUpRight");
    });
    $(".nivo-nextNav").on( "click", function(){
    $slider.find("img").attr("data-transition","sliceUpLeft");
    });
    });
    [/js]

    Nivo Slider 3.2で動作を確認しています。
    Nivo Sliderはスライドする要素にdata-transitionを設定しておくと、そこに設定されているエフェクトでその要素にスライドします。その値を書き換えているだけという、なんとも単純な作りです。

    普段何気なく使っているプラグインもソースを眺めてみると、意外にあんなこともこんなこともできるんだ!って発見がありますよねぇ~。