Motomichi Works Blog

その日学習したことについて書いている日記です。誰かの役に立ったらそれはそれで嬉しいです。

webpack4.xでjsをビルドする前にeslintで構文チェックをする

参考にさせて頂いたページ

eslint-loaderについて

.eslintrcについて

今日の環境

  • Windows10
  • node 8.11.1
  • npm 5.8.0
  • webpack 4.8.1

プロジェクトディレクトリを作成

mkdir webpack_4_eslint_introduction

npm init する

npm init

全てそのままenterを押してデフォルト値が設定されたpackage.jsonが生成されました。

グローバルにインストール

npm install -g webpack webpack-cli eslint

プロジェクトにインストール

npm install --save-dev eslint eslint-loader 

今回webpack_4_eslint_introductionディレクトリ内に作成するディレクトリ構造とファイル

  • .eslintrc
  • package.json
  • package-lock.json
  • webpack.config.js
  • node-modules/*
  • src/index.js
  • dist/bundle.js

webpack.config.jsの作成とその記述内容

module.exports = {
  mode: 'production',
  // entry point
  entry: './src/index.js',
  // 出力するパスは絶対パスで書きます
  output: {
    path: `${__dirname}/dist`,
    filename: 'bundle.js'
  },
  // webpack4はlordersではなくなりました
  module: {
    rules: [
      // 拡張子.jsのファイルに対する設定
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'eslint-loader',
          },
        ]
      },
    ]
  },
}

.eslintrcの作成とその記述内容

eslint:recommendedをextendして、セミコロンを忘れるとエラーになるルールを追加で設定しています。

{
    "extends": ["eslint:recommended"],
    "plugins": [],
    "parserOptions": {},
    "env": {"browser": true},
    "globals": {},
    "rules": {
        "semi": "error"
    }
}

./src/index.jsの作成とその記述内容

参考にさせていただいたページに倣って以下の通りです。
二行目は構文チェックでエラーになるようにセミコロンを忘れた状態です。

function hello(name) {
    document.body.textContent = "Hello, " + name + "!"
}

hello("World");

webpackでビルドをしてみる

webpackコマンドでビルドします。

webpack

以下のようなエラーが表示されました。
./src/index.js の二行目の55文字目に semi のルールによるエラーがあることがわかります。

ERROR in ./src/index.js

/Users/motomichi/Desktop/webpack_4_eslint_introduction/src/index.js
  2:55  error  Missing semicolon  semi

✖ 1 problem (1 error, 0 warnings)
  1 error, 0 warnings potentially fixable with the `--fix` option.

エラーが表示されたので./src/index.jsを修正する

以下のように修正してみます。

function hello(name) {
    document.body.textContent = "Hello, " + name + "!";
}

hello("World");

もう一度webpackでビルドをしてみる

webpack

エラーが出ずに、./dist/bundle.jsが書き出されました。

webpack4.xでscssファイルをコンパイルする

参考にさせて頂いたページ

設定全般

ExtractTextPluginをwebpack 4 で使用する方法について

今日の環境

  • Windows10
  • node 8.11.1
  • npm 5.8.0
  • webpack 4.8.1

プロジェクトディレクトリを作成

webpack_4_scss_introduction

npm init する

npm init

全てそのままenterを押してデフォルト値が設定されたpackage.jsonが生成されました。

グローバルにインストール

npm install -g webpack webpack-cli

プロジェクトにインストール

コマンド

npm install --save-dev webpack webpack-cli style-loader css-loader sass-loader node-sass postcss-loader autoprefixer extract-text-webpack-plugin@next

ここでのポイント

2018年05月09日現在ですが、webpack 4 を使用している場合はextract-text-webpack-plugin@nextをインストールします。

extract-text-webpack-pluginはwebpack 3で使用するプラグインなので以下のようなエラーが出ました。

(node:20569) DeprecationWarning: Tapable.plugin is deprecated. Use new API on `.hooks` instead
/Users/xxxxx/Desktop/webpack_4_scss_introduction/node_modules/webpack/lib/Chunk.js:712
        throw new Error(
        ^

Error: Chunk.entrypoints: Use Chunks.groupsIterable and filter by instanceof Entrypoint instead
    at Chunk.get (/Users/xxxxx/Desktop/webpack_4_scss_introduction/node_modules/webpack/lib/Chunk.js:712:9)
    at /Users/xxxxx/Desktop/webpack_4_scss_introduction/node_modules/extract-text-webpack-plugin/dist/index.js:176:47
    at Array.forEach (<anonymous>)
    at /Users/xxxxx/Desktop/webpack_4_scss_introduction/node_modules/extract-text-webpack-plugin/dist/index.js:171:18
    at AsyncSeriesHook.eval [as callAsync] (eval at create (/Users/xxxxx/Desktop/webpack_4_scss_introduction/node_modules/tapable/lib/HookCodeFactory.js:24:12), <anonymous>:7:1)
    at AsyncSeriesHook.lazyCompileHook [as _callAsync] (/Users/xxxxx/Desktop/webpack_4_scss_introduction/node_modules/tapable/lib/Hook.js:35:21)
    at Compilation.seal (/Users/xxxxx/Desktop/webpack_4_scss_introduction/node_modules/webpack/lib/Compilation.js:890:27)
    at hooks.make.callAsync.err (/Users/xxxxx/Desktop/webpack_4_scss_introduction/node_modules/webpack/lib/Compiler.js:481:17)
    at _err0 (eval at create (/Users/xxxxx/Desktop/webpack_4_scss_introduction/node_modules/tapable/lib/HookCodeFactory.js:24:12), <anonymous>:11:1)
    at _addModuleChain (/Users/xxxxx/Desktop/webpack_4_scss_introduction/node_modules/webpack/lib/Compilation.js:758:12)
    at processModuleDependencies.err (/Users/xxxxx/Desktop/webpack_4_scss_introduction/node_modules/webpack/lib/Compilation.js:697:9)
    at _combinedTickCallback (internal/process/next_tick.js:131:7)
    at process._tickCallback (internal/process/next_tick.js:180:9)

以下のページを参照して解決できました。

今回webpack_4_scss_introductionディレクトリ内に作成するディレクトリ構造とファイル

  • package.json
  • package-lock.json
  • webpack.config.js
  • node-modules/*
  • src/index.js
  • src/style.scss
  • dist/style.css

webpack.config.jsの作成とその記述内容

const ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
  mode: 'production',
  entry: './src/index.js',
  output: {
    path: `${__dirname}/dist`,
    filename: 'bundle.js',
  },
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: ExtractTextPlugin.extract({
          fallback: "style-loader",
          use: [
            // CSSをバンドルするための機能
            {
              loader: 'css-loader',
              options: {
                // CSS内のurl()メソッドの取り込みを禁止する
                url: false,
                // ソースマップの利用有無
                sourceMap: true,
                // 空白文字やコメントを削除する
                minimize: true,
                // Sass+PostCSSの場合は2を指定
                importLoaders: 2
              },
            },
            // PostCSSのための設定
            {
              loader: 'postcss-loader',
              options: {
                // PostCSS側でもソースマップを有効にする
                sourceMap: true,
                plugins: [
                  // Autoprefixerを有効化
                  // ベンダープレフィックスを自動付与する
                  require('autoprefixer')({grid: true})
                ]
              },
            },
            // Sassをバンドルするための機能
            {
              loader: 'sass-loader',
              options: {
                // ソースマップの利用有無
                sourceMap: true,
              }
            }
          ],
        })
      }
    ]
  },
  plugins: [
    new ExtractTextPlugin('style.css')
  ]
}

./src/index.jsの作成とその記述内容

import './style.scss';

./src/style.scssの作成とその記述内容

background-imageや、ベンダープレフィックスが意図通り出力されるのが確認できるように以下のように記述してみました。

.hoge {
  .foo {
    background-image: url(./hoge.png);
  }
  ::placeholder {
    color: gray;
  }
}

コンパイルする

以下のコマンドを実行します。

webpack

dist/style.cssが書き出されます。

ExtractTextPluginを使うとmodeをdevelopmentにしても、cssのsourceMapは出力されないけどなんでかわからない。 まぁcssはsourceMap無くてもいいか。

webpack4.x+vue.js 2.x+vue-loaderでビルド環境を構築する その0001 導入

参考にさせて頂いたページ

vue-loaderを使うときのwebpack.config.jsの記述内容について

.babelrcについて

包括的にwebpack 4 入門

包括的にもう一つ

今日の環境

  • Windows10
  • node 8.11.1
  • npm 5.8.0
  • webpack 4.8.1

プロジェクトディレクトリを作成

mkdir webpack_4_vue_introduction

npm init する

npm init

全てそのままenterを押してデフォルト値が設定されたpackage.jsonが生成されました。

グローバルにインストール

npm install -g webpack webpack-cli babel

プロジェクトにインストール

npm install --save-dev webpack webpack-cli vue-loader vue-template-compiler babel-core babel-loader babel-preset-env
npm install --save vue

今回webpack_4_vue_introductionディレクトリ内に作成するディレクトリ構造とファイル

  • .babelrc
  • index.html
  • package.json
  • package-lock.json
  • webpack.config.js
  • node-modules/*
  • src/index.js
  • src/App.vue
  • src/components/HelloWorld.vue
  • dist/bundle.js

webpack.config.jsの作成とその記述内容

const path = require('path');
const VueLoaderPlugin = require('vue-loader/lib/plugin');

module.exports = {
  // entry point
  entry: './src/index.js',
  // 出力するパスは絶対パスで書きます
  output: {
    path: `${__dirname}/dist`,
    filename: 'bundle.js'
  },
  // webpack4はlordersではなくなりました
  module: {
    rules: [
      // 拡張子.vueのファイルに対する設定
      {
        test: /\.vue$/,
        use: [
          {
            loader: "vue-loader"
          }
        ]
      },
      // 拡張子.jsのファイルに対する設定
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'babel-loader',
          },
        ]
      },
    ]
  },
  // デフォルトの設定値だけでは足りないことについて解決します
  resolve: {
    // モジュールを読み込むときに検索するディレクトリの設定
    modules: [path.join(__dirname, 'src'), 'node_modules'],
    // importするときに省略できる拡張子の設定
    extensions: ['.js', '.vue'],
    alias: {
      // 例えばmain.js内で `import Vue from 'vue';` と記述したときの`from vue`が表すファイルパスを指定
      'vue$': 'vue/dist/vue.esm.js'
    },
  },
  // プラグインを列挙
  plugins: [
    new VueLoaderPlugin()
  ],
}

./src/index.jsの作成とその記述内容

import Vue from 'vue';
import App from './App';

Vue.config.productionTip = false;

new Vue({
  el: '#app',
  template: '<App/>',
  components: { App },
});

./src/App.vueの作成とその記述内容

<template>
  <div id="app">
    <HelloWorld/>
  </div>
</template>

<script>
import HelloWorld from './components/HelloWorld'

export default {
  name: 'app',
  components: {
    HelloWorld
  }
}
</script>

./src/components/HelloWorld.vueの作成とその記述内容

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  data () {
    return {
      msg: 'Welcome to Your Vue.js App'
    }
  }
}
</script>

index.htmlの作成とその記述内容

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta charset="utf-8">
<title>vue-tutorial</title>
</head>
<body>
<div id="app">
</div>

<script type="text/javascript" src="dist/bundle.js"></script>
</body>
</html>

.babelrcの作成とその記述内容

{
  "presets": [
    [
      "env",
      {
        "targets": {
          "browsers": ["last 2 versions", "IE 11"]
        },
        "modules": false,
        "useBuiltIns": true
      }
    ]
  ]
}

watchしてコンパイルしてみる

webpack -d --watch

ここまでやってのpackage.jsonの内容

{
  "name": "webpack_4_vue",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "babel-core": "^6.26.3",
    "babel-loader": "^7.1.4",
    "babel-preset-env": "^1.6.1",
    "vue-loader": "^15.0.9",
    "vue-template-compiler": "^2.5.16",
    "webpack": "^4.8.1"
  },
  "dependencies": {
    "vue": "^2.5.16"
  }
}

ページを見てみる

index.htmlをブラウザで開いて確認します。

Welcome to Your Vue.js App という文字列が表示されました。

今日はここまで。

おまけ

ERROR in ./src/order_apply/components/PostcodeInput.vue?vue&type=template&id=716ac2cf
Module parse failed: Unexpected token (2:0)
You may need an appropriate loader to handle this file type.

上記のようなエラーが出るときは、

const VueLoaderPlugin = require('vue-loader/lib/plugin');

new VueLoaderPlugin()

が足りてないという場合もあります。

vue.js 2.x その0004-02 アコーディオンを作る(jQueryのslideToggleみたいなものを作る)

はじめに

以下の記事で書いたコードを少しマシな感じに書き直しました。

vue.js 2.x その0004 アコーディオンを作る(jQueryのslideToggleみたいなものを作る) - Motomichi Works Blog

サーバーサイドから動的な値がwindow.itemsで書き出される想定で書いています。

rootコンポーネントから渡された値を更新したい場合は、rootコンポーネントが持っているstateを$emitなりで更新して、子供に渡しなおすと良さそうです。

そのとき、this.item.textをwatchしたり、computedの中で使うなどしないと子供のコンポーネントの状態は更新されないと思います。たぶん。

HTMLファイルサンプル

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Vue.jsのアコーディオンサンプル</title>
<meta charset="utf-8">
</head>
<body>
<style>
.accordion{}
.accordion__trigger{}
.accordion__contents-outer-wrap{
  transition: max-height 2s;
  max-height: 0;
  overflow: hidden;
  background: #eeeeff;
}
.accordion__contents-inner-wrap{
  padding: 100px 10px;
}
</style>

<div id="accordion-app">
  <ul>
    <li is="my-component" v-for="(item, index) in state.items" :item="state.items[index]" :index="index">
    </li>
  </ul>
</div>

<!-- x-templateを定義 -->
<script type="text/x-template" id="my-component">
<li class="accordion">
  <button class="accordion__trigger" v-on:click="slideToggle()">
    accordion-trigger-{{index}}
  </button>
  <div class="accordion__contents-outer-wrap" v-bind:style="state.style">
    <div class="accordion__contents-inner-wrap" ref="contentsInner">
      text : {{ state.text }}<br>
      index : {{ index }}
    </div>
  </div>
</li>
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<script>
// サーバーから渡されたデータなどを想定
window.items = [
  { text: '何か個別に渡したいデータがあれば'},
  { text: '何か個別に渡したいデータがあれば'},
];

// my-componentを作成
Vue.component('my-component', {
  template: '#my-component',
  props: ['item', 'index'],
  data: function(){
    return {
      state: {
        isOpen: false,
        text: this.item.text,
        style: {},
      },
    };
  },
  methods: {
    slideToggle: function(){
      this.state.isOpen = !this.state.isOpen;
      this.setStyle(this.state.isOpen);
    },
    setStyle: function(isOpen){
      this.state.style = isOpen ? { 'maxHeight': this.$refs.contentsInner.clientHeight + 'px' } : {};
    },
  },
  mounted: function(){
    this.setStyle(this.state.isOpen);
  },
});

// VM
var vueApp = new Vue({
  el: '#accordion-app',
  data: {
    state: {
      items: window.items,
    },
  },
});
</script>
</body>
</html>

RSpec+Capybaraその0002 radioやcheckboxの状態を検証する

参考にさせて頂いたページ

RSpecについて

Capybaraについて

radioやcheckboxがCapybara::ElementNotFoundになる

ラジオボタンチェックボックスは、デザインを適用するときにdisplay:none;にしたり、position:absolute;のlabelタグの下に隠したりすることがよくあります。

Capybara::ElementNotFound のエラーが表示されてラジオボタンチェックボックスが見つけられない場合はinputタグが表示状態か非表示状態か、まずは思い込みを捨ててCSSをきちんと確認することが大切です。

checkedかどうかを検証する

表示されているradioやcheckboxがcheckedであることを検証する

expect(page).to have_checked_field('ラベル文字列')

display:none;のradioやcheckboxがcheckedであることを検証する

visible:falseを第二引数に渡すとdisplay:none;の要素がcheckedかを検証できます。

expect(page).to have_checked_field('ラベル文字列', visible:false)

checkedでないことを検証する

checkedでないということを検証するには、to_notがあります。

expect(page).to_not

フィールド自体が表示されているかを検証する

checkedかどうかは関係なく、要素が表示されているかどうかを検証します。

expect(page).to have_field('ラベル文字列')

RSpec+Capybaraその0001 インデックス

参考にさせて頂いたページ

RSpecについて

Capybaraについて

はじめに

RSpecとCapybaraを使い始めたのでハマったことなどを少しずつ足していこうと思います。

状態を検証する

radioとcheckbox

railsその0003-011 第九章、第十章完了

rails 5のチュートリアルをやってみる日記です。

今日はここまで読んだとか、ここまでやったとかそんな感じの日記です。

昨日と今日で第九章、第十章のコード書き終えました。

永続的ログイン、ユーザー情報の更新、ユーザーの削除などができるようになりました。

第八章あたりからか、ただコピペして、動くかとかテストがGREENになるかだけで精一杯な感じになりつつあるのでいよいよつらい。

一通りやり終えたらコピペしてきたコードを読んで理解しないと意味無いけど、まずは先に進みます。