ふんばりフロントエンジニアのブログ

新米フロントエンジニアの備忘録です。ふんばり温泉タオル欲しい...

fetchが定義されていませんとかのエラーはpolyfillかCDNで対処しよう

今回はさらっとIEのエラー対処について。

jsonを扱う際にfetchを使ってデータを取得しているのですが、どうやらIEだとpromiseやfetchが使えないようなんですね。

ということで、まず簡単なCDNから。

<script src="https://cdnjs.cloudflare.com/ajax/libs/es6-promise/3.3.1/es6-promise.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/2.0.4/fetch.min.js"></script>

これらを読み込むと、IEでもしっかりfetchとpromiseを利用することができます。

そして、もう一つの方法がpromiseとfetchのpolyfillを読み込む、という方法。

こちらはみんな大好き「npm install」でパッケージをインストールします。

npm install fetch-polyfill  es6-promise

で、jsではこのようにimportしてあげれば大丈夫です。

import "es6-promise/auto";
import "fetch-polyfill";

promiseを変数名に定義しないといけない場合もあるようですが今回はこのような記述でなんとかなりました。

IEもうやだ。。。

AMP対応でインラインCSS書き出しをなんとか自動化した話 〜PHP編〜

今回は前回の続き、ということでPHPファイルについてお話していこうと思います。

hubarifront.hatenablog.com


前回お話したgulpfileで重要だったのは、PHPコマンドを叩くタスクでしたね。

そしてこのタスクにはコマンドライン引数として変更したファイル名を渡していました。

では、その叩かれているPHPファイルがどのような処理を行なっているのか、見ていきましょう!


前回に引き続き、ファイルの階層構造はこちらです。

f:id:ma1129nm:20190121000820p:plain

で、まずはじめに見ていただきたいのがテンプレートとなるindex_template.htmlファイルです。

<!doctype html>
<html amp lang="ja">
<head>
  <meta charset="utf-8">
  <title>サンプルテキスト</title>
 
  <script type="application/ld+json">
[
  {
    "@context": "http://schema.org",
    "@type": "BreadcrumbList",
    "itemListElement": [
      {
        "@type": "ListItem",
        "position": 1,
        "item": {
          "@id": "https://sample.jp/",
          "name": "テキスト"
        }
      }
    ]
  }
]
</script>
  <style amp-custom>
    /*inline css*/
  </style>
  <style amp-boilerplate>body {
    -webkit-animation: -amp-start 8s steps(1, end) 0s 1 normal both;
    -moz-animation: -amp-start 8s steps(1, end) 0s 1 normal both;
    -ms-animation: -amp-start 8s steps(1, end) 0s 1 normal both;
    animation: -amp-start 8s steps(1, end) 0s 1 normal both
  }

  @-webkit-keyframes -amp-start {
    from {
      visibility: hidden
    }
    to {
      visibility: visible
    }
  }

  @-moz-keyframes -amp-start {
    from {
      visibility: hidden
    }
    to {
      visibility: visible
    }
  }

  @-ms-keyframes -amp-start {
    from {
      visibility: hidden
    }
    to {
      visibility: visible
    }
  }

  @-o-keyframes -amp-start {
    from {
      visibility: hidden
    }
    to {
      visibility: visible
    }
  }

  @keyframes -amp-start {
    from {
      visibility: hidden
    }
    to {
      visibility: visible
    }
  }</style>
  <noscript>
    <style amp-boilerplate>body {
      -webkit-animation: none;
      -moz-animation: none;
      -ms-animation: none;
      animation: none
    }</style>
  </noscript>
</head>
<body>

</body>
</html>

はい、ご覧の通りかなり長ったらしいですがこれはほとんどAMPのデフォルトで記載しなければならないものばかり。

多分ここまではAMPのドキュメントを参照してもらえれば理解していただけると思います。

www.ampproject.org

さて、ここで肝心なのは次の部分。

<style amp-custom>
    /*inline css*/
  </style>

本来ならCSSを記載しなければならないstyleタグの中に「inline css」とコメントアウトが入っています。

これがトリガーになっていて、最終的にはこのコメントアウトコンパイルされたCSSに置換されるようになる、という想定です。

そしてHTMLファイルも編集するのはこのテンプレートファイルで、HTMLとCSSを混ぜ合わせた別のHTMLファイル、ここで言えばindex.htmlが出力されるということになります。


これは前回も貼りましたがイメージはこんな感じです。

f:id:ma1129nm:20190120234338p:plain


前置きが少し長くなってしまいましたが、ようやく本題のPHPファイルを見てみましょう。

<?php
//コマンドライン引数を取得 -> php replace.php _index_template.html
$cmdtemplate = $argv[1]; //_index_template.html
$cmdhtml = explode("_",$cmdtemplate)[1];//index

//templateディレクトリからファイル名を取得するための配列
$templatelist = array();

//第一引数に指定したファイルで第二引数に指定したファイルを置換
function replace($template,$html) {
    $css = file_get_contents("./css/style.css"); //scssからコンパイルされたCSS
    $replacing = file_get_contents("./templates/".$template);
    $replaced = str_replace("/*inline css*/",$css,$replacing);
    file_put_contents("./".$html.".html",$replaced);
}

foreach(glob('templates/*') as $file){
    if(is_file($file)){
        array_push($templatelist, htmlspecialchars($file));
    }
}

if( strpos($cmdtemplate,"html") !== true ){
    replace($cmdtemplate,$cmdhtml);
} else {
    foreach ($templatelist as $list){
        $t = explode("/",$list)[1];
        $h = explode("_",$t)[1];
        replace($t,$h);
    }
}

では、ポイントごとにコードの解説をしていきたいと思います。

まずは変数の宣言からいきましょう。

<?php
//コマンドライン引数を取得 -> php replace.php _index_template.html
$cmdtemplate = $argv[1]; //_index_template.html
$cmdhtml = explode("_",$cmdtemplate)[1];//index

//templateディレクトリからファイル名を取得するための配列
$templatelist = array();

ここでは、コマンドライン引数の取得をし、.htmlを除いた名前を取得しています。

コマンドライン引数は「$argv」で取れるらしいですね。

で、次に宣言している配列はindex_template.htmlなどのテンプレートファイルが入っているtemplatesディレクトリからファイル名を取得するための変数です。

合計AMPページが3ページほどあったのでこのような配列を宣言していますが1ページだけなら不要です。

では次はメインの関数であるreplaceを見てみましょう。

//第一引数に指定したファイルで第二引数に指定したファイルを置換
function replace($template,$html) {
    $css = file_get_contents("./css/style.css"); //SCSSからコンパイルされたCSS
    $replacing = file_get_contents("./templates/".$template);
    $replaced = str_replace("/*inline css*/",$css,$replacing);
    file_put_contents("./".$html.".html",$replaced);
}


このreplace関数は二つの引数があり、コメントアウトにも書いていますが、第一引数のファイルで第二引数に指定したファイルを置換するという関数です。

まず、コンパイルされたCSSを「file_get_contens」で取得し、変数に入れます。

そして対象となるテンプレートファイル(index_template.html)を取得し、$repacingに入れていますね。

で、その$replacingの中で「/*inline css*/」とコメントアウトされている部分を先ほどの$cssで置換し、$replacedに入れます。

この時点でindex_template.htmlにはCSSが入っていますが、このままだと「/*inline css*/」が消えてしまうので「file_put_contents」をするのは出力先であるindex.htmlにするわけです。

あとはこのreplace関数をどのように実行させるかの条件ですね。


if( strpos($cmdtemplate,"scss") !== true ){
    replace($cmdtemplate,$cmdhtml);
} else {
    foreach ($templatelist as $list){
        $t = explode("/",$list)[1];
        $h = explode("_",$t)[1];
        replace($t,$h);
    }
}

とりあえず、複数ページの想定なので共通のSCSSなどがあるわけです。

もちろんそのSCSSを編集した場合は全部のHTMLファイルに更新をかけたいのでtemplateファイルを格納している$templatewをforeachで回してreplace関数を全テンプレートファイルにかけています。

しかし、HTMLだけを編集した場合に全部のHTMLファイルに更新をかける必要はありませんよね。

そのため、コマンドライン引数、つまり変更したファイルにscssという文字がなければコマンドライン引数のファイルのみを置換する、という分岐をしています。

これでなんとか置換することができました。

実際にはこのような形に…

f:id:ma1129nm:20190127124731p:plain

ここまで来るのに結構時間がかかってしまいましたが、PHPのお勉強としてはかなり有意義だったような気がします。

またgulpでコマンドを打ったり、イベントを取得するということも始めてだったので今後色々使っていきたいですね!


ちょっとまとまりがない記事になってしまったかもしれませんが、逐一確認して修正していきます(汗

AMP対応でインラインCSS書き出しをなんとか自動化した話 〜gulpfile編〜

明けましておめでとうございます!

なかなか年末まで案件が終わらずブログが更新できませんでした…泣

 

さて、そして年末まで取り組んでいた案件というのが今回のテーマであるAMP対応です。

AMPとは簡単にいってしまえば、Webページを高速化するための枠組みですね。

AMP独自のタグやコンポーネントを使うことで簡単にスライダーやカルーセルを実現できるので便利ではありますが、結構制約が多いんですよね…

 

たとえば、自分でJSを書くことができなかったりするのでちょっと動作を変えたいな〜とかいうときはCSSやAMPのコンポーネントをなんとか組み合わせてやらなきゃいけないわけです。

今回で言えば、カルーセルにスライダーのページネーションを組み込む、的なことをやろうと思ったのですがなかなかうまく行かず…
 

つまりコーディングする前に綿密に実現可能かどうかの調査が必要になります。
 

さて、色々な制約があるAMPなのですがこれらの制約の中でも「一番やだなー」って思ったものがインラインCSSです。

 

実はAMPではリクエストを減らすために、CSSの外部ファイル読み込みができないんですね。

そしてこのCSSはどこに書いていいわけでもなの中に記述しないといけないと…

 
まあチクチク書いていけばなんとかなりますが、プレフィクスもつけたいし最終的には圧縮したいわけですよ…

そしてCSSをいじっただけでリロードとかもしてもらいたいわけですよ…

gulpを使っている私ですが、せっかくいつも使っているタスクが利用できないのはかなりもどかしい…
 

ということで!

いつも通りの環境でコーディングできるように試行錯誤しながらインラインCSSを自動化した話をお届けしたいと思います。

 
今回インラインCSSの書き出しに使ったものでいつもと異なるのはPHP

別に言語はなんでもいいと思いますが、とりあえず勉強中という事もあってPHPを使うことにいたしました。


ではこのPHPがどのようにして役に立つのか、流れをご紹介していきたいと思います。


まず、ざっくりした図から見ていきましょう!

f:id:ma1129nm:20190120234338p:plain


このように、元となる「index_template.html」ファイルにコンパイルされたCSSを取り込んで、最終的にHTMLファイルとして吐き出すというのが大まかな流れ。

SCSSのコンパイルに関してはいくらでも参考になる記事があると思いますのでここでは割愛します。


では、肝心のtemplateからHTMLファイルを生成するまでにどのようなファイルが必要になるのか、主役は二つのファイルです。

  • gulpfile.js

...ご存知の通り、gulpを実行するためのファイル。

...templateとHTMLファイルの中身を入れ替えるためのファイル。


では、gulpfile.jsから内容を見ていきましょう。

const gulp = require('gulp');
const autoprefixer = require('gulp-autoprefixer');
const plumber = require('gulp-plumber');
const sass = require('gulp-sass');
const sourcemaps = require('gulp-sourcemaps');
const sync = require('browser-sync').create();
const ps = require('child_process').exec; //コマンドを打てるプラグイン 重要!!
let changeFileName;
const srcDir = './src';
const distDir = './';

gulp.task('replace', function () {
    var command = `php replace.php ${changeFileName}`;
    ps(command);
});

gulp.task('serve', () => {
    sync.init({
        server: './',
        port: 3333,
        notify: false
    });
    gulp.watch(['./templates/**/*.html',`${srcDir}/css/style.css`], (event) => {
        changeFileName = event.path.split('/').pop();
    });
    gulp.watch([`${srcDir}/**/*.scss`], ['sass', sync.reload]);
    gulp.watch(['./templates/**/*.html',`${distDir}/css/style.css`], ['replace',sync.reload]);
});

gulp.task('sass', () => {
    return gulp.src(`${srcDir}/css/style.scss`)
        .pipe(plumber())
        .pipe(sass({
            outputStyle: 'compressed'
        }).on('error', sass.logError))
        .pipe(sourcemaps.init())
        .pipe(autoprefixer({
            browsers: ['last 2 versions', 'ie >= 11', 'iOS >= 10', 'Android >= 4.4'],
        }))
        .pipe(sourcemaps.write('.'))
        .pipe(gulp.dest(`${distDir}/css/`));
});

さて、このgulpfile.jsで見慣れないタスクはreplaceというタスクでしょう。

このタスクはが行なっている処理はいたって単純でPHPコマンドを動かすだけ。

ただ、「${changeFileName}」というコマンドライン引数を渡していることに注目して下さい。

この${changeFileName}という引数はserveタスクを見てもらえるとわかるように、watchで監視しているHTMLファイルかSCSSファイルに変更があった場合に、そのファイル名を代入するための変数です。

言い忘れてましたが、ファイルの階層構造はこのような感じです。

f:id:ma1129nm:20190121000820p:plain

変更のあったファイルを取るのは「event.path」なのでこのプロジェクトのルートからパスが取れてしまうわけです。

そのため、index_template.htmlに変更があった場合「templates/index_template.html」というパス名が取得できるんですね。

それをsplitで分割して完全にファイル名だけを取れるようにしている、というのが一番上のwatchの中で行なっている処理となっています。

で、この取得したパス名を変数に入れてそのままコマンドライン引数として渡しているのですが、次に気になるのは引数が渡されているreplace.phpですよね。

このまま書き続けてもいいのですが、なんとなく長くなりそうなのでPHPファイルに関してはまた次にお話ししようと思います。

 

 

 

 

 

gulpで「TypeError: args.cb is not a function」が出たらこれが原因

最近Webpackが流行っていますが、やっぱ馴染み慣れたgulpを使ってしまう私がいます…

今回はたまに出くわしていた「TypeError: args.cb is not a function」というエラーの対処法について一つ。

こちら、実際にエラーが出ていても動くものなんですがなんか怖いですし、エラー出るのがまず嫌なんですよね...

私はwatchのタスクを動かすときにこのエラーが出ていたのですが、おそらくこのページを見ている方も同じなのではないでしょうか?

そしてこのエラーの原因はエラー内容にも現れているように「args」にあります。

はい、argsというのはみなさん大好きな配列ですね。(白目)

私がエラーを出していた時のコードはこちらになります。

gulp.watch(['./**/*.html'], [sync.reload]);
gulp.watch(['./src/css/**/*.scss'], ['sass', sync.reload]);
gulp.watch(['./src/js/**/*.js'], ['js', sync.reload]);

こちら、何が問題かというと一番最初の監視対象である「html」のところ。
第一引数に関しては問題ないのですが、問題があるのは第二引数のタスクです。
上記のコードでは括弧でreloadさせるようにしていますが、括弧で囲っていいのは動かしたいタスクが二つ以上ある時のみのよう。
以下のように、タスクが一つの時は括弧で囲まないようにするとエラーは出なくなりました。

gulp.watch(['./**/*.html'], sync.reload);
gulp.watch(['./src/css/**/*.scss'], ['sass', sync.reload]);
gulp.watch(['./src/js/**/*.js'], ['js', sync.reload]);

特に大したことない情報かもしれませんがお役に立てれば幸いです^^

Google Analyticsでオプトアウトした時はこうなるよ

こんにちは〜〜

今回はGoogle Analyticsでオプトアウトするときの設定をご紹介します。

Google Analyticsは今や多くのWebサイトで活用されているアクセス解析ツールです。

そしてオプトアウト、というのはGoogla Analyticsにサイト閲覧者の情報を送らないようにするための設定のこと。

つまりユーザーが「自分の情報を提供したくないから、アクセス解析に入れないで!」というのがGoogle Analyticsでいう所のオプトアウトです。

しっかりドキュメントを見れば載っている事なので参考にするまでもないことかもしれませんが一応、残しておきます。

Google Analyticsのオプトアウト方法はドキュメントに載ってる!

f:id:ma1129nm:20181022140339p:plain

はい、という事でGoogle Analyticsでオプトアウトをするための方法はドキュメントにしっかり記載してくれています↓

高度な設定 - ウェブ トラッキング(analytics.js)  |  ウェブ向けアナリティクス(analytics.js)  |  Google Developers

このように、windowプロパティに「ga-disable-トラッキングID」を追加し、その値を「true」にする事でオプトアウトの設定が完了します。

ただ、これだけではそのページでだけオプトアウトが設定されることになり、他のページに飛んでしまった時やブラウザを閉じてまたアクセスしてきた時にこのwindowプロパティは消えてしまいます。

じゃあcookieを使うか、と考えながらドキュメントをスクロールして見てみると...
f:id:ma1129nm:20181022140811p:plain

これまた親切に、cookieを使ったオプトアウトの例を書いてくれています。

ただ、cookieの設定は適宜変える必要がありますね。

そして注意点としてタグマネージャー のjsだけでなく、しっかりanalytics.jsも読み込んであげてください。

つまりこのような感じになりますね。

<!-- Google Analytics -->
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

ga('create', 'UA-XXX-YY', 'auto');
ga('send', 'pageview');
</script>
<!-- End Google Analytics -->

<!-- Google Tag Manager -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-KXPVFMN"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','XXX-YYYY');</script>
<!-- End Google Tag Manager -->

そして、このGoogle Analyticsを読み込む前に先ほどのwindowプロパティを変更する必要があります。

そうしないとオプトアウトされないので注意しましょう。

これらの設定ができ、ページを確認して↓のようになっていればオプトアウトは完了です。
f:id:ma1129nm:20181022141643p:plain

このログはGoogle Analyticsデバッグ用jsを読み込むことで吐き出すことができます。

開発段階でのみ使用することを推奨されており、かなり便利なもの。

デバッグ  |  ウェブ向けアナリティクス(analytics.js)  |  Google Developers

このように、analytics.jsをanalytics_debug.jsとするだけでデバッグモードになるのでぜひ利用してみてくださいね。

Gmailで送信済みメールの色が紫色になる原因はこいつだった

仕事ではGmailを使ってやりとりをすることが多いのですが、最近結構返信メールが長くなって送信済みメールを見るとなにやら署名の部分が紫色に…

相手側には黒色で見えている、との口コミもありましたがどうやら相手側にも紫色で見えてしまっているよう...

「これはまずい」ということで検証してみたところ、原因はこいつでした。

f:id:ma1129nm:20181019163816p:plain

Gmailの設定にある「スレッド表示」です。

スレッド表示をONにしていると、返信メールなどを一つにまとめてくれて便利です。

しかし、こいつをOFFにすることで紫問題から逃れることができました...

スレッド表示をONにしたままでもユーザースタイルシートを使うことで色を変えることはできるようですがプラグイン入れるのもなあ...と思ったので今回はこのように対処しました。

同じ現象の方はお試しあれ!

なぜだ…Cookieが上手く操作できない!!domainやpathを指定した時詰まったこと

こんにちは、今回はCookieJavaScriptで扱った際の備忘録です。

Cookieはサーバーから付与される「key」と「value」を持ったデータのことで、Sessionとは違い「ブラウザ側」に保存されるものです。

そのため、フロントエンドであるJavaScriptでも扱うことができるんですね。

そして今回初めてCookieを扱ってオプトアウトに取り組んだのですが、これがなかなかつまづいてしまいまして...

簡単にではありますが、詰まったところと対処法をお伝えしようと思います!

 

Cookieとは

Cookieとは、サーバーから付与される「key」と...(以下省略)
と、そんなことはさておきまして、Cookiはブラウザ側に保存され、サーバーサイドでももちろん、そしてJavaScriptでも扱うことができるもの。
そして今回取り組んだのはオプトアウト、というcookieなどによるトラッキングをかけないようにするといった内容でした。
私は当初cookieではなく、「WebStorage」を利用しようとしていたんですね。
WebStorageの種類は二つあり、sessionStorageとlocalStorageの二つ。
sessionStorageはブラウザを閉じると消えますが、localStorageはブラウザを閉じても消えることはありません。
ここまでは有効期限を指定したcookieと同じですよね。
ただ、localStorageにはcookieには絶対にある有効期限がないんです!
つまり消さない限りずっと残っているということに...
操作の仕方も簡単だったので、「これ使ってみよう!」と思いポチポチ進めていたのですが、重大なことに気がつきます。
それがlocalStorageは「シングルドメイン限定」である、ということ。
つまりクロスドメインで利用することができないんですね。
今回のオプトアウトはクロスドメインだったので、「あ、もうダメだ」と。
ということで、一度は捨てたCookieに舞い戻ってきたわけです。
少々Cookieとは違う内容が入ってしまいましたが、今回詰まったとこをご紹介していきましょう!

なぜだ…サブドメインCookieが渡されてない!

はい、ということでサブドメインCookieが渡されていなかったんですね。
これはちょっと調べると出てきたのですが、↓のようにするといいとのこと。

document.cookie = 'trFlag = 1; domain = .domain.com; max-age = ${maxAge}';

そう、domain指定のところで「.」をメインドメインの前に付けるということです。
しかし私の方ではこれがあまり上手くいかず…
ということで、pathも指定してみました。

document.cookie = 'trFlag = 1; domain = .domain.com; path = /; max-age = ${maxAge}';

私の方ではこのようにすることでCookieの引き渡しが上手くいったのですが、なんかモヤモヤしてます笑
なんか上手くいかなかった方は試してみてください!

なぜだ…Cookieが削除できない!

こちらはセットしたCookieが削除できない、というお悩みでした。
jQueryプラグインを利用するとdeleteメソッドがあるようですが、今回はネイティブなJavaScriptで作業をしていたので、基本の削除方法「上書き」で削除を行なっておりました。
ここで私の適当さが出てしまったんです...
当初書いていたコードは次のようなコードです。

if(document.cookie.indexOf('trFlag') < 0){
    document.cookie = `trFlag = 1; domain = .domain.com; path = / ;max-age = ${maxAge}`;
  } else {
    document.cookie = 'trFlag = 1; max-age = 0';
  }

とりあえずmax-ageを0にすれば削除できるだろう、そう思っていました。
しかし、何度やっても削除できず。
pathとかdomain指定してると削除できない!という記事も見つけて「まじかよ」となっていた矢先、全部書かなきゃダメじゃね?となり、書いたのがこちら。

if(document.cookie.indexOf('trFlag') < 0){
    document.cookie = `trFlag = 1; domain = .domain.com; path = / ;max-age = ${maxAge}`;
  } else {
    document.cookie = 'trFlag = 1; domain = .creadorz.com; path = /;max-age = 0';
  }

これで上手く削除できるようになりました。
面倒くさがらず、全て指定してあげるべきでしたね...
以上、今回詰まったCookieについてでした!