dackdive's blog

新米webエンジニアによる技術ブログ。JavaScript(React), Salesforce, Python など

ctrlp.vimでファイルを検索するときのウィンドウサイズを変更する

前回に続き、ctrlp.vim についてのメモ。

ctrlp.vim でファイルを検索するとき、そのままだと候補の一覧がウィンドウ下部にmax10件ぐらいしか表示されない。
ここのサイズを変更したい。

f:id:dackdive:20170407003231p:plain

設定方法を調べてみたところ、この Issue に書いてあった。

Not enough results · Issue #187 · kien/ctrlp.vim

まず、Issue には

:help g:ctrlp_max_height

で調べてみろとあるのでヘルプを見てみるとわかるが、 g:ctrlp_max_height 自体は別の変数 g:ctrlp_match_window に統合されたのでこちらを設定する。
g:ctrlp_match_window は Example にあるように

let g:ctrlp_match_window = 'bottom,order:btt,min:1,max:10,results:10'

のように、bottommin/max などいくつか設定できるパラメータがあり、カンマ区切りで指定する。

指定できるパラメータは以下の通り。

パラメータ 意味 デフォルト
top/bottom ウィンドウを上下どちらに表示するか bottom
btt/ttb 結果の並び順
ttb: top to bottom
btt: bottom to top
(具体的にどう変わるのかよくわかってない)
btt
min, max ウィンドウの最小高さと最大高さ min:1
max:10
results 結果を最大何件表示するか max height と同期

というわけで、表示する件数を増やしたい場合は max だけ指定すれば良い。

自分は

let g:ctrlp_match_window = 'max:30'

とした。

ctrlp.vimでnode_modulesなどのフォルダを除外する

ちょいメモ。

VimAtomVSCode のような Ctrl + P によるファイル検索ができるようにする ctrlp.vim というプラグイン
便利なんだけど、node_modules があるプロジェクトで使うと起動が遅かったり node_modules 内のファイルまで検索対象になってしまっていたので
特定のフォルダを対象から除外する方法が調べてみたら、Issue にあった。

.vimrc に

let g:ctrlp_custom_ignore = 'node_modules\|DS_Store\|git'

と書くだけでよかった。

JIRAの課題フィルタで一度に表示する課題数(ページサイズ)を変更する

ちょいメモ。

課題フィルタで一度に 50 件ずつしか結果が表示できないのを変更したい。

(画像は https://ja.confluence.atlassian.com/jiracoreserver070/searching-for-issues-777015942.html より引用)

と思って調べてみたところ、
Expanding Issue Filter to show more than 50 items - Atlassian Answers
ユーザプロファイルで簡単に変更できるらしい。

JIRA 右上のアバターアイコンから「プロファイル」を選択し、

ユーザー設定 > ページ サイズ

を変更する。

f:id:dackdive:20170322160614p:plain

これだけ。

WEB+DB PRESS Vol.97のReactサンプルアプリをReduxで書き直した

特集の React 記事を読んでいます。
React/Redux でのクライアントサイドルーティングや SSR(サーバーサイドレンダリング)について学びたく、とりあえずサンプルコードが flux を使って書かれていたのを Redux を使う形に書き直した。

(PR は #1

いじらなかった components/ 以下のコンポーネントと containers/ でファイル名の命名ルールが揃っていなかったり、container はもう少し presentational な役割と切り離した方がいいんじゃないかという気がするが
本来学びたかったことのための土台はとりあえずできた。
正直 flux の書き方になっている action や store を順次 Redux の action や reducer に置き換えていくだけで移行自体に面白味はなかった。

メモ

React Router の基本的な使い方として、

<Route path="notes/:id/edit" component={NoteEdit} />

とすると、コンポーネント側では URL 中のパラメータを this.props.params.id のようにして取得できる。


main.js

<Route path="/" component={Dashboard}>
  <Route path="notes/:id/edit" component={NoteEdit} />
</Route>

Dashboard.js

<div className="page-Dashboard-main" role="form">
  {this.props.children ? React.cloneElement(this.props.children, { note }) : null}
</div>

としておくと、<NoteEdit />Dashboard から note を作って props として渡すことができる。
この目的で子コンポーネントにする方法は今まで知らなかった。

webpackでReact/Reduxを本番環境用にビルド(production build)する

はじめに

React に限らず、JavaScript を使ってると本番環境用のビルド(以後、production build)として圧縮や難読化を行う必要があります。
webpack にはそのためのプラグインとして UglifyJsPlugin があるのは知っていましたが、
単純にこのプラグインを適用しただけだとブラウザで以下のような warnings が出ていることがわかりました。

f:id:dackdive:20170307045306p:plain

  • Warning: It looks like you're using a minified copy of the development build of React. When deploying React apps to production, make sure to use the production build which skips development warnings and is faster. See https://fb.me/react-minification for more details.
  • You are currently using minified code outside of NODE_ENV === 'production'. This means that you are running a slower development build of Redux. You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify or DefinePlugin for webpack (http://stackoverflow.com/questions/30030031) to ensure you have the correct code for your production build.

そのため、このエラーが表示されなくなるような、正しい production build 用の webpack の構成を一度調べてみたいと思います。

加えて、UglifyJsPlugin を使っていても redux-logger のログが出力されており、これは本番環境では出力されないことが望ましいので
そのような切り替えを行う方法についても一緒に調べてみます。

※ webpack v1を対象としていますが、分かる範囲で v2 との違いについても言及しています。


サンプル(GitHub

先に、今回の調査を踏まえたサンプルリポジトリはこちらです。

(この記事を書いたときの最新コミットはbdff7ec


やること1:webpack の各種プラグインを導入&設定する

使用する webpack のプラグインは以下です。


DefinePlugin で process.env.NODE_ENV を置換する

DefinePlugin はビルド実行時、コード中に埋め込まれたグローバル変数プラグインで定義した値に置換してくれるプラグインです。
たとえば

// webpack.config.js
plugins: [
  new webpack.DefinePlugin({
    PRODUCTION: JSON.stringify(true),
  }),
],

という定義があった場合、コード中の

if (PRODUCTION) {
  console.log('production');
}

if (true) {
  console.log('production');
}

に置き換えられます。

React などのライブラリには

if (process.env.NODE_ENV === 'development') {
  // only in development
}

のような処理が埋め込まれており、DefinePlugin でこの部分を置換します。

// webpack.config.js
plugins: [
  new webpack.DefinePlugin({
    'process.env': {
      NODE_ENV: JSON.stringify(process.env.NODE_ENV),
    },
  }),
],

これの何が良いかと言うと、次に紹介する UglifyJsPlugin と組み合わせることで到達しないコードが削除され、ファイルサイズの圧縮につながります。
※到達しないコード = if (false) { ... } のような部分のこと


UglifyJsPlugin を production build 時のみ適用する

冒頭でも紹介した UglifyJsPlugin も webpack.config.js に追加します。
これは開発時には OFF になってほしいので、たとえば NODE_ENV=production を指定した時のみ有効になるよう、以下のようにします。

// webpack.config.js
const PRODUCTION = process.env.NODE_ENV === 'production';

plugins: [
  new webpack.DefinePlugin({
    'process.env': {
      NODE_ENV: JSON.stringify(process.env.NODE_ENV),
    },
  }),
  ...(
    PRODUCTION ? [
      new webpack.optimize.UglifyJsPlugin({
        compress: {
          warnings: false,
        },
      }),
    ] : []
  ),
]    

UglifyJsPlugin のオプションですが、{ compress: { warning: false } } を指定しないとビルド時に

WARNING in bundle.js from UglifyJs
Condition always false [../~/react/lib/React.js:32,0]
Dropping unreachable code [../~/react/lib/React.js:32,0]
...

のような warning がずらっと出力されるので、指定しておきます。

また、参考にした create-react-app の webpack.config.prod.js ではそれ以外のオプションも色々と指定しているようですが、README に記載がないのとほとんどがデフォルト値のようなのでとりあえずこれでいいかと。


DedupePlugin, OccurenceOrderPlugin も production build 時のみ適用する

最後に、DedupePlugin と OccurrenceOrderPlugin という 2 つのプラグインも導入します。
DedupePlugin は重複するモジュールはひとつにまとめてくれるプラグイン、OccurenceOrderPlugin は利用頻度の高いモジュールのにをなるべく短い ID を降ることでファイルサイズの圧縮を図るプラグインという理解です。
先程の UglifyJsPlugin に続けてプラグインを定義します。

// webpack.config.js
const PRODUCTION = process.env.NODE_ENV === 'production';

plugins: [
  new webpack.DefinePlugin({
    'process.env': {
      NODE_ENV: JSON.stringify(process.env.NODE_ENV),
    },
  }),
  ...(
    PRODUCTION ? [
      new webpack.optimize.UglifyJsPlugin({
        compress: {
          warnings: false,
        },
      }),
      new webpack.optimize.OccurrenceOrderPlugin(),
      new webpack.optimize.DedupePlugin(),
    ] : []
  ),
]    

DedupePlugin については 公式ドキュメント

Note: Don’t use it in watch mode. Only for production builds.

という記載があるので production build 時のみ適用すべきですが、OccurrenceOrderPlugin については特にそういう記述はないので常に有効でもいいのかもしれません。


補足:webpack v2 との差分

webpack v1 -> v2 の migration guide を読むと、今回紹介したプラグインで関係している変更は以下のあたりでしょうか。

  • UglifyJsPlugin
    • { compress: { warnings: false } } はデフォルトになったので指定不要に(参考
    • sourceMap がデフォルトで出力されなくなったので、出力したい場合は明示的に { sourceMap: true } を追加(参考
  • OccurrenceOrderPlugin
    • 指定しなくても有効になったので、記載不要(参考
  • DedupePlugin
    • こちらも webpack2 では不要(参考


やること2:NODE_ENV = production のときは redux-logger が読み込まれないようにする

webpack の設定が終わったので、続いて Redux まわりの設定を。
redux-logger が production build 時には読み込まれないようにします。

これは README にも記載がありますが、webpack のときと同様に process.env.NODE_ENV の値を使って
process.env.NODE_ENV === 'development' のときだけ redux-logger がインポートされるようにします。

reducer の Hot Module Replacement の設定も考慮すると、store の設定は以下のようになります。

// store/configureStore.js

/* eslint-disable global-require */
import { createStore, applyMiddleware } from 'redux';
import rootReducer from '../reducers';

// eslint-disable-next-line no-underscore-dangle
const __PRODUCTION__ = process.env.NODE_ENV === 'production';

export default function configureStore(initialState) {
  const middlewares = [];
  if (!__PRODUCTION__) {
    const createLogger = require('redux-logger');
    middlewares.push(createLogger());
  }
  const store = createStore(
    rootReducer,
    initialState,
    applyMiddleware(...middlewares),
  );

  if (!__PRODUCTION__ && module.hot) {
    module.hot.accept('../reducers', () => {
      const newRootReducer = require('../reducers').default;
      store.replaceReducer(newRootReducer);
    });
  }

  return store;
}

これで production build 時には redux-logger がインポートされなくなりました。


おまけと TODO

冒頭で述べたブラウザの warning について

それぞれ ReactDOM および Redux 内で定義されていました。

NODE_ENV = production じゃない状態で minify されているかどうか」の判定方法、面白いですね。
(ダミーの function を定義し、function.name が関数名そのものと一致するかどうか判定し、一致しない = minify されている)

webpack の -p オプション

production build は webpack のデフォルトのオプションとしてもいくらかサポートされてるようです。
参考:https://webpack.github.io/docs/cli.html#production-shortcut-p

$ webpack -p

$ webpack --optimize-minimize --optimize-occurrence-order

というオプションをつけるのと同じで、それぞれのオプションについては https://github.com/webpack/docs/wiki/optimization を参照。

TODO

production build 時には console.log も除去したい。こちらを使えばできる?


リファレンス

webpack.config.js について

create-react-app の webpack.config.prod.js が参考になるほか、日本語記事では

Redux の configureStore.js について

この記事が参考になりました。
Practical Redux, Part 3: Project Planning and Setup · Mark's Dev Blog

ただ、記事では Redux Devtools などの設定も行っていますが、今回はそこまで手が出せなかった...

Inside Frontend #1に参加したメモ

こちらのイベントに行ってきました。

AMA(休憩時間中の質問コーナー)という取り組みが非常に良かったです。 以下、話を聞きながら手元でメモしてた内容。


Web over ServiceWorker by @jxck__

  • Capability against
    • Native/Low API
    • Installable
    • Performance
    • Permission
    • Offline
    • etc
  • The Extensible Web Manifesto : https://extensiblewebmanifesto.org/
  • Service Worker
    • Service Worker is API for Offline Support
    • Service Worker is Platform for New Capability
  • Proxy としての Service Worker
    • データのfetchなどを一旦 ServiceWorker がフック。キャッシュにあればそちらを利用し、なければ実際にfetchしてキャッシュに突っ込んでおく
  • Pusher としての Service Worker
  • TaskManager としての Service Worker
    • オフライン時に行われたアクションを覚えておき、回線が復活したときにサーバー側に同期する
    • onsync !== on-line/offline
  • ブラウザやタブが開いてなくても使える
  • 中級者向け Service Worker Tutorial | blog.jxck.io : https://blog.jxck.io/entries/2016-04-24/service-worker-tutorial.html
Foreign Fetch
  • 3rd Party が自分たちのサービス(JS)とそれに付随するService Workerを提供できるようにする仕組み
  • サブドメインを分けることで SW の責務を分けることができるようになった
  • Navigation Preload
    • fetch 時に Service-Worker-Navigation-Preload: true でリクエストすると、SW の起動を待たずに fetch できる


AMA: Progressive Web Apps

PWA化することによって改善される問題・PWA化のタイミング · Issue #4 · insidefrontend/issue1-ama
  • PWA 化のタイミングについては難しいが、SW は「使える環境であればより便利になるが、なくても普通に動く」ように作るべき
ChromeのブラウザPush通知 / Progressive Web Apps · Issue #12 · insidefrontend/issue1-ama
  • いきなり通知送る実装にしていることにも問題あり。Slack とかはうまくやっている
  • SW はドメイン単位で ON/OFF されるので、通知うざいからって無効化されると今後通知以外の実装を行ったときも使ってもらえない
  • 「再エンゲージとかにこだわる必要ない」はあくまであなたのユースケースであることに注意。それから、今の Web はユースケースよりも Capability を重視している
    • 例:エンゲージを高めるために SW を実現する(ユースケース)、SW というものを実現すると何かできることが増えるはず(Capability)


Web フロントエンドにおけるコンポーネント化のアプローチ by @1000ch

  • CSS のスタイルガイド
    • プロジェクトUIのドキュメントとして
    • デザイナーとの協業手段として
      • 理想:プロジェクトUIの整理や再利用のため
      • 現実:メンテされない
    • -> 何を解決すればいいのか本質的に見えてなかった
  • コンポーネントとはいったいなんなのか
  • コンポーネントの価値
    • デザインにおいてはUIの一貫性・統一性
    • ソフトウェアにおいては再利用性
  • これまでの問題点
  • これまでのワークフロー:
    • (Design) デザインデータを参考にする→デザインデータを更新する→
    • (Dev)HTML+CSSで再現する→スタイルガイドを更新する
    • → 誰がメンテナンスする問題
  • 協働のためのデザイン思考の再構築 : could : http://www.yasuhisa.com/could/article/ddd-ooux-job/
  • CSS のスコープ問題
    • HTML&CSS にはスコープがない
    • スコープが欲しい
      • CSS Modules, CSS in JSといった技術的に解決する方法と、Shadow DOM という仕様で解決する方法


アメブロ2016: 実録、アメブロフロントリニューアル275日


karmaを使ったSPA向けのE2Eテスト技法 by @kyo_ago

  • はじめに:「E2E テスト」より「Integration テスト」のほうが言葉としては適切
  • 伝えたいこと:SPA のテストにWebDriverは向かない
  • WebDriver
    • Selenium から発展したツール
    • ブラウザ
    • 2004年から始まる歴史あるツール
  • なぜ向かないか
    • あくまで画面を遷移していく「Webサイト」のためのツールだから
    • Driver によっては Dev Tools と競合(Dev Tools を同時に使えない状況でテストはつらい)
    • ターミナルだと console.log(obj) しても [Object] としか表示されなくてつらい
  • SPA の Integration テストには Karma
    • 元は AngularJS 用に開発されたが、現在は汎用的なツール
    • Protractor とは違う(Protractor はWebDriver 系)
  • Karma?
    • Unit テストがブラウザ上で動く
    • ver 1.0.0 で追加された customContextFile
    • HTMl に Unit テストを
  • Karma v1.0.0から登場したcustomContextFile。Unit テストをHTMLに埋め込むことができるので、実際のサイト上でテストを走らせるかのように書ける。ただしlocalhost
  • localhost 問題をどうするか
  • WebDriverは受け入れ用のテストツール、karmaは開発者向けのテストツール
  • おまけ:E2Eテストの指すもの
    • E2Eは対象範囲の話で、目的の話はしていない
    • 検証のため?開発のため?


AMA: React.js

自分が質問した内容。Redux でつらいこと2点

どこまでが Redux state で、どこまでが React の State か悩みます
  1. ほとんど Redux state に寄せているが、チェックボックスラジオボタンの状態ぐらいだと React state で良いかも。ただその判断基準は明確でないので、そのせいで判断に困ることが多くなるようであれば思い切って Redux に全部寄せるのはあり。
ビジネスロジックをどこに閉じ込めてますか?
  1. Redux とは関係なく扱うリソースごとに Model みたいなのを定義している。Model を Redux に乗せている、というイメージ


JSON Schema in Web Frontend by @pirosikick

終わった後に質問してみた:
react-jsonschema-form で逆にできないこと(どうしてもカスタマイズできないこと)はありますか?
A. バリデーションエラー時のメッセージがカスタマイズできない(多言語化できない?)ので、かなり無理矢理実装することになる。


参加できなかったセッションの資料

データビジュアライゼーションの作り方

shimizu’s Blocks - bl.ocks.org:D3 のサンプル集として参考になりそう

react-lightning-design-systemをVisualforceで使う

件名の通りですが少しハマりどころがあったのでメモ。
普通に react-lightning-design-system を使おうとすると SVG アイコン使用時に Unsafe attempt to load URL エラーが表示されます。

Unsafe attempt to load URL https://zakiyama-dev-ed.my.salesforce.com/assets/icons/utility-sprite/svg/symbols.svg#event from frame with URL https://zakiyama-dev-ed--c.ap2.visual.force.com/apex/LDSTest. Domains, protocols and ports must match.

いわゆる CORS: Cross Origin Resource Sharing、つまり salesforce.comvisual.force.com という異なるオリジンでリソースのやり取りをしようとしているのが原因です。
Issue にもあります。

これを回避するためにはコメントにもあるように、util.setAssetRoot(path) というメソッドを使います。
また setAssetRoot に渡す静的リソースへのパスは、Visualforce 側で

{!URLFOR($Resource.slds)}

を使い取得します。

Visualforce ページと、React 側のエントリーポイントとなる index.js は以下のようになります。

<apex:page showHeader="false" standardStyleSheets="false" docType="html-5.0">
<html>
  <head>
    <meta charset="utf-8" />
    <apex:stylesheet value="{!URLFOR($Resource.slds,'assets/styles/salesforce-lightning-design-system.min.css')}" />
  </head>
  <body>
    <div id="root"></div>

    <apex:includeScript value="{!URLFOR($Resource.app, 'bundle.js')}" />
    <script type="text/javascript">
      App.init(document.getElementById('root'), '{!URLFOR($Resource.slds)}/assets');
    </script>
  </body>
</html>
</apex:page>
// src/scripts/index.js
import React from 'react';
import { render } from 'react-dom';
import { util } from 'react-lightning-design-system';

import App from './components/App';
import '../stylesheets/index.scss';

export const init = function(el, assetRoot) {
  util.setAssetRoot(assetRoot);
  console.log('Set asset root as ', util.getAssetRoot());
  render(<App />, el);
};

この例では、webpack.config.js の設定でビルドした JavaScriptApp というライブラリ名でエクスポートしてます(参考

また、初期化用関数の引数として渡してますが

window.assetRoot = '{!URLFOR($Resource.slds)}/assets';

のように window 関数に直接変数を生やして index.js 側で参照するという方法もあります。


余談:<script> タグと <apex:includeScript> タグの読み込み順序に関して

上の例では、<apex:includeScript> による JS 読み込みが続く <script> タグ内のスクリプトより先に実行される必要がありますが
https://developer.salesforce.com/docs/atlas.ja-jp.204.0.pages.meta/pages/pages_compref_includeScript.htm
を見ると、loadOnReady を明示的に true にしない限り <apex:includeScript> の方がすぐに読み込まれるようです。
(すぐに、というのが曖昧ですが、一旦こちらの読み込みが先行すると考えておいて良さそうです)


Spring'17 で追加された <apex:slds> を使う場合

Spring'17 から Visualforce ページでも、静的リソースにアップロードすることなくプラットフォームから提供される SLDS が使えるようになります。
参考:Visualforce ページでの Lightning Design System の使用

これを使う場合、先ほどのコードは

 <html>
   <head>
     <meta charset="utf-8" />
-    <apex:stylesheet value="{!URLFOR($Resource.slds,'assets/styles/salesforce-lightning-design-system.min.css')}" />
+    <apex:slds />
   </head>
   <body>
     <div id="root"></div>

     <apex:includeScript value="{!URLFOR($Resource.app, 'bundle.js')}" />
     <script type="text/javascript">
-      App.init(root, '{!URLFOR($Resource.slds)}/assets');
+      App.init(root, '{!URLFOR($Asset.SLDS)}/assets');
     </script>
   </body>
 </html>

というように、静的リソースから SLDS を読み込んでいた部分を <apex:slds /> に置き換え、さらにパス部分は $Asset.SLDS という変数を使って取得するようにします。

(2017/02/20追記)

f:id:dackdive:20170220103817p:plain

バージョンは最新バージョンの 2.2.1 でなく 2.1.3 のよう。
あと読み込んでいるリソースが salesforce-lightning-design-system-vf.min.css なのも気になる。
(追記ここまで)


さらに、applyBodyTag または applyHtmlTag が false だった場合

リリースノートおよび開発者ガイドの該当ページによると、
Using the Lightning Design System | Visualforce Developer Guide | Salesforce Developers

if you set applyBodyTag or applyHtmlTag to false, you must include the scoping class slds-scope

とあるので、

-  <body>
+  <body class="slds-scope">

などとする必要があります。
(そういう意味で、上の例では applyBodyTag および applyHtmlTag を false にしていないのに <html> や <body> を使っているのは適切ではないですね)


おまけ:<apex:slds> の既知のバグ

検証中、<apex:slds> を使うと最初の Unsafe attempt to load URL エラーが発生することがわかりました。
どうやら $Asset.SLDS の URL が salesforce.com になっており、調べてみると StackExchange にありました。バグのようです。

近日中に修正されるようなので1週間後ぐらいに確認してみることとします。

(2017/02/20追記)
今日見たら直ってました。
StackExchange のコメントに更新はなく、Known Issues にもないので、動きを確認した限りですが。
(追記ここまで)


リポジトリ