とろろこんぶろぐ

かけだしR&Dフロントエンジニアの小言

css-loader で ValidationError: CSS Loader Invalid Options

ValidationError: CSS Loader Invalid Options

nuxt を v2.9 にあげようとしたら css-loader の validation error が出てしまった。

 ERROR  in ./.nuxt/components/nuxt-loading.vue?vue&type=style&index=0&lang=css&

Module build failed (from ./node_modules/css-loader/dist/cjs.js):
ValidationError: CSS Loader Invalid Options

options should NOT have additional properties

    at validateOptions (/node_modules/schema-utils/src/validateOptions.js:32:11)
    at Object.loader (/node_modules/css-loader/dist/index.js:44:28)

 @ ./node_modules/vue-style-loader??ref--3-oneOf-1-0!./node_modules/css-loader/dist/cjs.js??ref--3-oneOf-1-1!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src??ref--3-oneOf-1-2!./node_modules/vue-loader/lib??vue-loader-options!./.nuxt/components/nuxt-loading.vue?vue&type=style&index=0&lang=css& 4:14-327 14:3-18:5 15:22-335
 @ ./.nuxt/components/nuxt-loading.vue?vue&type=style&index=0&lang=css&
 @ ./.nuxt/components/nuxt-loading.vue
 @ ./.nuxt/App.js
 @ ./.nuxt/index.js
 @ ./.nuxt/client.js
 @ multi eventsource-polyfill webpack-hot-middleware/client?reload=true&timeout=30000&ansiColors=&overlayStyles=&name=client&path=/__webpack_hmr/client ./.nuxt/client.js

上記エラーは css-loader に対するオプションで、未定義のものを設定してしまうと発生する。 そのため、webpack.config.js の css-loader の option で渡しているオブジェクトでキーをタイポしているようなケースで起こる。

しかし今回の場合は、nuxtなのでwebpack.config.jsのcss-loaderは内部で定義されていて変更を加えていない。

  css (options) {
    options.onlyLocals = this.onlyLocals
    const cssLoader = { loader: 'css-loader', options }

nuxt.js/style-loader.js at e7cc2757c3aea7076b82fc9c65700d48eab01586 · nuxt/nuxt.js · GitHub

おかしいと思ってnuxtのstyle-loaderを見てみると、 onlyLocals を設定していた。 つまりこいつが原因で落ちている。

webpackのcss-loaderを見てみると、 v3.0.0 で exportOnlyLocalsonlyLocals に変更されていた。

exportOnlyLocals option was remove in favor onlyLocals option

css-loader/CHANGELOG.md at 39f18f86df7404ffaedf49aa441d2a08a5e9c1e2 · webpack-contrib/css-loader · GitHub

つまり、nuxtのstyle-loaderはきちんと最新に追従できていることになる。

手元のlocal環境でcss-loaderが古いのかと思って見てみると、確かに css-loader@^2.1.1 が入っていた。

おかしいなと思って依存関係を調べると、犯人はstorybookだった。 現時点でのmasterである v5.1.11 では css-loader@^2.1.1 が指定されている。

storybook/package.json at v5.1.11 · storybookjs/storybook · GitHub

試しに v5.2.0-beta.40 を入れたらエラーは出なくなった。 v5.2.0 をはやくリリースしてほしい。

storybook/package.json at v5.2.0-beta.40 · storybookjs/storybook · GitHub

Next.js + AMP + typescript + styled-components ( + storybook)でやったことと気をつけること

Next.js + AMP + typescript + styled-components ( + storybook)でやったことと気をつけること

  • Next.jsでAMP化するときは、 pagesのコンポーネントに対して、nextAmpでwithAmpをimportし、withAmpで囲う。
import { withAmp, useAmp } from "next/amp";
  • withLayoutも使いたいときはそれも囲う
withAmp(withLayout(Index), { hybrid: true });
  • hybrid: trueをつけているときは、URLに ?amp=1 をつけているときだけAMP化されたHTMLが返る

  • getInitialProps はデータをfetchしてSSRしてくれるのでAMPで問題なく動く。

  • amp-customのscriptソースは、_document.tsxで内に記述する。 これは、理想的にはそれを使うコンポーネント内で指定したいが、複数呼び出すと複数head内にscriptが記述されてしまうのでよくない。

  • amp-customのstyleは、_documentのgetInitialPropsでstyled-componentsのServerStyleSheetからstylesheetを文字列に直し、inlineCssとしてSSR時にhtml内に吐き出すように書く。

  static getInitialProps({ renderPage }: { renderPage: any }) {
    const sheet = new ServerStyleSheet();
    const page = renderPage((App: any) => (props: any) =>
      sheet.collectStyles(<App {...props} />),
    );
    const stylesheets = [...sheet.getStyleElement()];
    const inlineCss = stylesheets.reduce(
      (inlineStyles, currentStylesheet) =>
        currentStylesheet
          ? `${inlineStyles}${
              (currentStylesheet.props as any).dangerouslySetInnerHTML.__html
            }`
          : inlineStyles,
      "",
    );
    return { ...page, inlineCss };
  }
          <style amp-custom="">{(this.props as any).inlineCss}</style>
  • ampのカスタムエレメント向けに型を用意する ampのカスタムエレメントやon属性はreact-domが型として未定義の情報なので、jsx.d.tsというような形で型情報を用意する。
declare namespace JSX {
  interface AmpHtml {
    children: React.ReactNode;
    amp?: any;
  }
  interface IntrinsicElements {
    html: AmpHtml;
  }
}

必要な型情報は追加していく。

  • ampでcssの擬似要素に content: "" は許容されてない。 iconなどはicon用の画像をコンポーネント別に用意する必要がある。

  • storybookで見る preview-head.html で headタグは足せるので、amp向けのscriptを追加は可能。ただし動作は安定しない。

  • yarn dev の developビルドだと__NEXT_DATAのようなnext.js向けのjavascriptがhtml内にinsertされるので注意。出力したHTMLをamp-validatorとかにかけてもエラーになる。yarn buildして閲覧したhtmlなら大丈夫。

  • amp-stateは_document.tsxで定義する。<script type="application/json"> はreactだと内部に記述するjsonjsonとして読み込まないので書くときには注意が必要。dangerouslySetInnerHtml を使うか、文字列で記載するようにする。 srcで指定するようにして、外部からjsonを取得するのが迷わないのでオススメ。

  • もしstyled-componentsを使っている場合は、on属性でイベントトリガーを記述する要素にstyleをあててはいけない。on要素がstyled-componentsのレンダリング時に(なぜか)破棄されてしまうので、直接div要素で指定する。

  • ampのstateを利用する際に [class] といった形式では記述できないので、 data-amp-bind-class といった data-amp-bind-xxx の形式を用いる。

  • amp-stateは、デバッグコンソールで AMP.printState() を呼び出すと確認できる。ただし、URLで #development=1 を指定しておかないと見れないので注意。

jest + typescript で 'describe' is not defined

問題

jest + typescript で、eslintが以下のエラーを出力した。

'describe' is not defined. eslint(no-undef)

typescriptだと @types/jest を入れろと言われるが、eslint ではそれだけではダメだった。

解決策

eslint-plugin-jest を入れる必要があった。

www.npmjs.com

READMEにある通り、以下のようにeslintrc.jsに追記する。

module.exports = {
  env: {
    ...
    "jest/globals": true,
  },
  ...
  plugins: [
    ...
    "jest",
  ],
  ...

結果

'describe' is not defined. は言われなくなった。

参考

eslintrc.js を載せておく。

module.exports = {
  env: {
    node: true,
    es6: true,
    'jest/globals': true,
  },
  extends: [
    "eslint:recommended",
    "plugin:prettier/recommended",
    "prettier/@typescript-eslint"
  ],
  parser: "@typescript-eslint/parser",
  parserOptions: {
    sourceType: "module"
  },
  plugins: [
    "@typescript-eslint",
    "jest",
    "prettier",
  ],
  rules: {
    "prettier/prettier": "warn",
    "@typescript-eslint/adjacent-overload-signatures": "error",
    "no-unused-vars": "off",
    "@typescript-eslint/no-unused-vars": "warn",
    "no-console": "off",
    "no-undef": "warn"
  }
};

JavaScript の Async/Await が Promise より優れている7つの理由【翻訳】

本記事は英記事を勝手に翻訳したものです。

Mostafa Gaafar さんの元記事 dev.to

概要

async/await は NodeJS7.6 で導入され、現在も全てのモダンブラウザでサポートされています。私はJSにおいて2017年から1番の変更だと思っています。もしそう思われない方がいたら、なぜ採用すべきかをサンプルとともに理由を以下に述べていきたいと思います。

Async/Await 101

Async/Await を聞いたことがない方々のために、簡単に概要を。

  • async/await は非同期なコードを書く新しい方法です。従来の非同期プログラミングの手法はコールバックかPromiseです。

  • async/await は単なる Promise のシンタックスシュガーです。コールバックで利用することはできません。

  • async/await は promise のようなもので、ノンブロッキングです。

  • async/await は同期的な見た目で非同期なコードを作ることができます。これが一番の特徴です。

シンタックス

ここで getJSON という promise を返す関数を定義し、promise はいくつかの JSON データを resolve します。これをログに出力し、 "done" を返します。

gist.github.com

そしてこちらが async/await を使ったコードです。

gist.github.com

ここには3つの違いがあります。

1. この関数の前には async キーワードが記述されており、 await キーワードは async 関数内でのみ使うことができます。どんな async 関数も暗黙的に promise を返し、promise で resolve される値が return で返されます。(このケースでは "done" が文字列で返されます。)

2. awaitasync 関数内ではないトップレベルで使うことができません。

gist.github.com

3. await getJSON() は console.log が getJSON() のpromiseのresolveを待ってから呼び出すことを暗黙的に意味しています。

なぜこれ(async/await)が良いのか?

1. 簡潔できれい

コードは書くことより読むことの方が多いです。上記のような恣意的な例だとしてもも、読みやすいコードになることは分かってもらえると思います。.then を書かずに非同期関数でレスポンスをハンドリングできます。上記の例では、無駄に data という変数を定義する必要もなくなります。ネストするコードを避けることもできます。小さな利点ですが、そのメリットは明らかだと思います。

2. エラーハンドリング

async/await は昔ながらの try/catch を使うので、同期、非同期で発生する両方のエラーをハンドリングすることができます。例えば promise では JSON.parse で fail しても promise 内で起きたエラーなので、 try/catch しないとエラーハンドリングできません。promise のエラーハンドリングを行うために多重に catch を定義する必要があるのです。

gist.github.com

async/await で同じコードを書くと、catch でどちらのエラーもハンドリング可能です。

gist.github.com

3. 条件式

あるデータを fetch して取得したデータの内容によっては、より詳細なデータを取得するコードを想像してみてください。

gist.github.com

頭痛がしてきそうなコードですね。6段のネストが発生していますし、返り値のデータの伝搬が必要になります。

以下が async/await でより読みやすく書き直した例になります。

gist.github.com

4. 即値

promise1 を呼び、その返り値を使って promise2 を呼び、それらの返り値を使って promise3 を呼ぶような状況があったとします。おおよそ以下のようなコードになるでしょう。

gist.github.com

もし promise3value1 が必要じゃないときは、もう少し簡単に書くことができます。Promise.all を使って value1 と value2 を包めばネストを避けることはできます。

gist.github.com

このアプローチは可読性のためにセマンティクスを犠牲にしています。ネストを避けるためだからといって value1 と value2 が配列である必要性はないはずです。 この同じロジックを async/await を使って簡易的で直感的なものに書き直してみます。

gist.github.com

5. エラーのスタック

複数のpromiseをチェーンして呼び出すコードを想像してください。このチェーン上のどこかでエラーがスローされます。

gist.github.com

このエラースタックはpromiseチェーンで返されますが、どこで発生したのか何の手がかりもありません。さらにこれはミスリーディングで、callAPromise という名前で定義されているとファイルや行数が出力されたとしても、そのエラーがどの関数で発生したのかわかりません。 async/await なら、どの関数でエラーが発生するかわかります。

gist.github.com

これはローカル環境で開発しているときの大きいメリットというわけではありませんが、サーバ上で発生したエラーログから挙動を把握する際に役に立つことでしょう。

6. デバッグ

async/await はより簡単にデバッグ可能です。promise のデバッグでは以下2つの苦しみがあります。

6-1. アロー関数内でブレークポイントをセットできない。

https://res.cloudinary.com/practicaldev/image/fetch/s--vpb-TU4B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/1260/1%2An_V4LaVdBOFgGCbmTR_VKA.png

6-2. ブレークポイント.then 内でセットしたらステップオーバーでデバッグすることはできます。ただし同期的に "ステップ" するので、 debugger は .then 内では動かすことができません。

async/await はアロー関数にする必要がないので、await で同期的に書いているコードを正確に "ステップ" することができます。

https://res.cloudinary.com/practicaldev/image/fetch/s--LyGaljw2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/1260/1%2AGWYd4eLrs0U96MkNNVB56A.png

7. 値でも await を使える

最後に、await は同期でも非同期でもどちらの式としても使うことができます。例えば、 Promise.resolve(5)await 5 と書くことができます。これはとても有用なことで、入力が同期か非同期か不明な関数を使うとしても、それに関係なく await を使って書くことができます。

アプリケーション内で、あるAPI呼び出しによって時刻を記録したいとします。このために関数を定義する際に以下のように書いたとします。

gist.github.com

全てのAPI呼び出しはpromiseを返しますが、同じ関数を同期関数内で時刻を記録するために使いたくなったらどうしますか?同期関数内でpromiseを返さなかったらエラーをスローしてしまいます。そのため makeRequest()Promise.resolve() で包む必要があります。

async/await を使えば、 await は promiseでも値でも安全に動作するので、心配する必要はありません。

gist.github.com

まとめ

async/await はここ数年でJavaScriptに追加された最大の革新的機能の一つです。これはpromiseのようなややこしいシンタックスを直感的なものに置き換えるものです。

懸念事項

async/await はシンタックスからは動作が明白なものではないため、少しの懸念があります。コールバックや .then を見た時の挙動は非同期なものとして理解しやすいですが、新しい記述の仕方(async/await)には慣れるまでに時間がかかることでしょう。C# ではasync/awaitを何年も前から用意されているので、より一層これに慣れ親しんでいる人たちはわかりづらく不便だと感じるかもしれません。


後記

async/await と promise を比較してメリットを述べている記事はあまり日本語で見かけなかったので、3、4日前にあがった記事を日本語訳した。 async/await はおおよそメジャーな書き方になってきたものの、 javascript 初心者からするとあまり理解しづらい部分もあると思うので、 promise との違いを分かってもらえれば嬉しい。 元記事はあくまで一所感であると思うし、ちょっと偏った意見だなと思うところもあるので注意してもらいたい。 また、翻訳が間違っているなど問題があれば指摘してほしい。

Vue.js で Drag & Drop 可能なソート機能な UI を作るには?

概要

Vue.js で drag & drop できる UI が作りたいときどのライブラリを入れてどう作ると良いか調べたのでまとめておく。

TL/DR

Vue.Draggable が一強。迷ったらこれを使っておけばよさそうではある。 ただ、だいたいのライブラリは SortableJS を使って作られており、Vue.Draggable も SortableJSの人たちが作ったものになっている。 無駄な機能を落として柔軟に作りたいなら SortableJS をそのまま使ったコンポーネントを自分で作った方がいい。

Vue.Draggable

Vue.js で言うといまはこれが一強だと思われる。 内部で Sortable.js を使っている。(まあ SortableJS の人たちが作っているし)

draggable コンポーネントで挟み込めば簡単に使うことができ、配下に v-for で繰り返されるコンポーネントを draggable にできる。

      <draggable
        :list="list"
        :disabled="!enabled"
        class="list-group"
        ghost-class="ghost"
        :move="checkMove"
        @start="dragging = true"
        @end="dragging = false"
      >
        <div
          class="list-group-item"
          v-for="element in list"
          :key="element.name"
        >
          {{ element.name }}
        </div>
      </draggable>

少し検索すれば使ってみた記事やサンプルをいくつかみることができるので、参考になるものがゴロゴロ転がっている。

ライブラリになってしまっていたり、配下で繰り返しにならないコンポーネントなどは、適用させるのがちょっと面倒そう。

github.com

その他のライブラリ

vue-sortable

v-sortable をコンポーネントにつければ配下のコンポーネントを draggable なコンポーネントにする、というアイデアは悪くないと思うが、現時点ですでに3年間メンテされていない。ちょっとこれは論外...。

github.com

vue-drag

2年メンテされてなさそうだし、こちらも個人で作ったものと思われる。考えるまでもなく論外。

github.com

element-ui-el-table-draggable

Element-UI の Table を draggable にする特化型のライブラリ。内部で Sortable に自由に option を与えられないのが残念。

github.com

Sortable.js をそのまま使う

ちなみに僕は SortableJS をそのまま利用しています。

github.com

以下のような sortable-wrapper.vue を用意する。

<template>
  <div class="wrapper">
    <slot></slot>
  </div>
</template>

<script>
import Sortable from 'sortablejs';

export default {
  props: {
    selector: {
      type: String,
    },
    option: {
      type: Object,
    },
  },
  data() {
    return {
      sortable: null,
    };
  },
  mounted() {
    // DOM がレンダリングされてから呼ぶために nextTick を使う
    this.$nextTick(this.refresh);
  },
  methods: {
    refresh() {
      if (this.sortable) {
        return;
      }
      if (this.$children.length === 0) {
        return;
      }
      const component = this.$children[0].$el.querySelector(this.selector);
      if (!component) {
        return;
      }
      const option = this.option || {};
      this.sortable = Sortable.create(component, option);
    },
  },
  watch: {
    option() {
      // 変更された option.disabled の内容を反映させる
      this.sortable.option('disabled', this.option.disabled);
    },
  },
};
</script>

以下のように wrapper で挟み込み、selector で class を指定すれば draggable にできるし、option で sortable.js に渡すオプションをそのまま指定できるので柔軟にできる。 wrapper 側の watch で option の更新もできる。

          <sortable-wrapper
            :selector="selector"
            :option="option"
          >
            <div 
              v-for="item in list" 
              :key="item.id"
              class="list" >
              {{ item.name }}
            </div>
          </sortable-wrapper>

気をつけること

ただし、sortableJS はあくまで 実DOM レベルで sortable にするライブラリなので、 vue の仮想DOM レベルでの要素との互換性を保つ必要がある。

sort されるイベントで Vue のデータを書き換えて、データの書き換えに応じて再レンダリングすれば基本的には問題ないはず。

しかしながら、場合によっては例えば以下のように onUpdate で DOM を書き換えるコードを記述するとともに、

...
        onUpdate: event => {
          // sortable.js は vue に対応してないので実DOMを更新する
          const fatherNode = event.from;
          const node = event.item;
          const position = event.newIndex;
          const refNode =
            position === 0
              ? fatherNode.children[0]
              : fatherNode.children[position - 1].nextSibling;
          fatherNode.insertBefore(node, refNode);
        },

vue で持っている data を更新も必要になってしまうことがありうるかもしれない。

          onEnd: ({ newIndex, oldIndex }) => {
           const rearranged = Array.from(targetArray);
            const target = rearranged.splice(oldIndex, 1)[0];
            rearranged.splice(newIndex, 0, target);
            this.targetArray = rearranged;
          },

まとめ

Vue.Draggable は便利なのでちゃっと使うには良さそう。 ただもう少し柔軟に作りたいなら sortable.js で自前の wrapper を用意する手もある、と思う。


Vue.js を勉強するなら 後悔しないためのVueコンポーネント設計

JDK11 で intelliJ でテストカバレッジが取れない?

Java で テストカバレッジを取ろうと思い、 IntelliJ でおもむろに Run 'All Tests' with Coverage したら、こういうエラーが各テストで発生した。

java.lang.ClassFormatError: Nest-host class_info_index 85 has bad constant type in class file app/models/Tuple$Two

ClassFormatError Nest-host class_info_index 16 has bad constant type in class file X on Java 11 · Issue #933 · powermock/powermock · GitHub

JDK11 だと動かない説があったが、 intelliJ 側の Update が必要だった。

無事 IntelliJ をアップデートし 2019.2 版にしたら動いた。

ちゃんとソフトウェアはアップデートしていかないとこういうことが起こるので気をつけような。