- 投稿日:2020-06-21T23:17:31+09:00
Promiseは何時呼ばれるのか?
Promiseは何時呼ばれるのか?
使う分には今まであまり意識してこなかったのですが、async/awaitを呼ぶことで処理がブロッキングされるのではないかというのを懸念していたのと、そもそもどのタイミングでPromiseのcallbackがされるのか気になったので今更ですが調べてみました。
Promiseが呼ばれる仕組みについては先にEventLoopとmicrotaskについて知る必要があります。先に結論から書くと以下の感じです。
- PromiseはEventLoop内のmicrotaskキューでFIFO実行される。
- Timer系の処理(setImmediateやsetTimeout)はmicrotaskが全て実行された後に実行される。(つまり、
setTimeout(fn, 0)
はmicrotaskを全て実行した後にfnを実行するという意味)- async/awaitはPromiseの箇所でsuspendしているに過ぎない(イベントループをブロッキングするかどうかはPromise内部の処理に依存する)、ジェネレータ文法(yield)やコルーチンの概念と同じ。
- NodeJS v12以降ではasync/awaitによるパフォーマンスの劣化は改善されているので、処理の実行順序の見やすさ的に積極的に書いて問題ない。(ただし、async/await関係なしに待たなくて良い処理に関してはレスポンスを返した後に実行すべしなのとI/O系のSyncメソッドは使わないほうが良い)
- ブラウザでもPromiseはqueueMicrotaskによって実装されている(そもそもPromise、async/awaitサポートされてないブラウザもまだ生き残っているのでトランスパイル必須だが)
NodeJSの場合
NodeJSはV8エンジン(Google ChromeのChromiumでも使われている)によるJavaScriptで動く実行環境です。
元々はC10K問題(サーバーのハードウェア性能は問題ないにもかかわらず、クライアントの同時接続数が多くなるとサービスの応答が遅くなる)を解決するバックエンド環境(アプリケーションサーバ)として開発されました。
C10K問題はハードウェア性能ではなく、OSの制限によって引き起こされるクライアント同時接続数の上限です。
- プロセス数の上限
- コンテキストスイッチ(切り替え)のコスト
- ファイルディスクリプタの上限
これらの問題を解決するためにNodeJSはシングルプロセス・シングルスレッドでリクエストを捌くという設計になっています。(実際にはマルチプロセスもマルチスレッドも作れるのですが根本的な設計思想はこれです。)
シングルプロセス・シングルスレッドで全部のリクエストを処理することでプロセス数の上限にもひっかからず、大量のマルチプロセス、マルチスレッドでのコンテキストスイッチも発生しない。
DBコネクションもマルチプロセス単位で都度接続するのでなく、シングルスレッド内で使いまわしをすることでファイルディスクリプタの上限にならない。
ただ、ファイルの読み書きに関してはシングルスレッドで行うと他の処理をブロッキングするほど重たいため、非同期でのI/Oをサポート・推奨しています。
シングルスレッドで飛んでくるリクエストを管理するための仕組みがイベントキュー(イベントループ)です。
従来の1リクエストにつき1プロセス立ち上がるマルチプロセス型のアプリケーションサーバに対し、
シングルプロセス、シングルスレッド内でイベントキューにリクエストを順次詰め込み、DBやファイルからのデータ取得を非同期に取得し、レスポンスを返すことを可能にしています。以上により、NodeJSはシングルプロセス・シングルスレッドによるイベントループでC10K問題を解決しています。
ただし、シングルスレッドならではの問題として
CPU負荷が高いループ処理などが発生するとイベントループをブロッキングしてしまい、全てのリクエストに遅延や最悪レスポンスが返って来ないほどの影響を与えてしまいます。さて、ようやくNodeJS上でPromiseがいつ呼ばれるのかという話しなのですが
setTimeout(() => console.log(1)); setImmediate(() => console.log(2)); process.nextTick(() => console.log(3)); Promise.resolve().then(() => console.log(4)); (() => console.log(5))();実行結果は次のようになります。
同期実行→nextTick→Promise(microtask)→setTimeout→setImmediate
の順番です。5 3 4 1 2
(() => console.log(5))()
のみ同期タスクで他は非同期タスクです。
同期タスクが一番最初なのは理解しやすいとして、非同期タスクの順番はどのように決まっているのでしょうか?
これはなぜなのかはもう少し踏み込んでイベントループについて見てみる必要があります。
イベントループはlivuvにより実装されていて、
NodeJSが起動すると以下のイベントループが初期化されます。
(ただし、nextTickQueue、microTaskQueueはNodeJS側で実装されている)まず、イベントループに入る前にもしくはイベントループの各フェーズの後にキューのタスクがある場合はキューが空になるまで実行されます。
(イベントループはシングルスレッドで複数のtaskを同時に処理することはできないため)
- nextTickQueueは全てのキューの中で最速に処理される→nextTickが実行される
- microTaskQueueはnextTickQueueが空になり次第、実行(Promisesオブジェクトのコールバックはここに所属)→Promiseが実行される
キューが解消されるとTimerフェーズからイベントループに入ります
- TimerフェーズでsetTimeoutが呼ばれる
- CheckフェーズでsetImmediateが必ず呼ばれる
なので
同期実行→nextTick→Promise(microtask)→setTimeout→setImmediate
となります。参考:Node.jsでのイベントループの仕組みとタイマーについて
先程シングルスレッドならではの問題点として、他の処理をブロッキングしてしまうという問題点をあげました。
次の例のようにasync functionだとしても裏側はシングルスレッドで動いているため、高負荷な処理を実行すると全てのリクエストをブロッキングしてしまいます。app.get('/compute-async', async function computeAsync(req, res) { log('computing async!'); const hash = crypto.createHash('sha256'); const asyncUpdate = async () => hash.update(randomString()); for (let i = 0; i < 10e6; i++) { await asyncUpdate(); } res.send(hash.digest('hex') + '\n'); });実は次のようにsetTimeoutを挟むことで他のリクエストをブロッキングすることなく、
高負荷な処理を継続することができます。
先程のイベントループが理解できていれば、Promise await(microtask)の間にsetTimeoutを挟むことでmicrotaskに大量の処理を全て詰め込んでから実行するのでなく、microtask→setTimeout→microtask→setTimeoutとインターバルを挟むようになるので他の処理をブロッキングするのを防ぐことができます。
(あくまでイベントループの仕様に基づいた回避策なのでそもそも重すぎるCPU処理の実行を直接サーバ上で行うのはNodeJSは向いていません)app.get('/compute-with-set-timeout', async function computeWSetTimeout (req, res) { log('computing async with setTimeout!'); function setTimeoutPromise(delay) { return new Promise((resolve) => { setTimeout(() => resolve(), delay); }); } const hash = crypto.createHash('sha256'); for (let i = 0; i < 10e6; i++) { hash.update(randomString()); await setTimeoutPromise(0); } log('done ' + req.url); res.send(hash.digest('hex') + '\n'); });参考:Node.js: How even quick async functions can block the Event-Loop, starve I/O
このようにNodeJSでは1つの処理がボトルネックになってサーバ全体のパフォーマンスを下げてしまうという危険があります。
後は同一プロセスで実行し続けるため、メモリリークが起きるとサーバの継続実行ができなくなってしまうのでデバッグツールや計測ツールでどこに問題があるのか調査する必要があります。さて、Promiseが何時実行されるかはわかりました。
(EventLoopの合間のmicrotaskキューで実行される)ではasync/awaitで実行した場合はどうなるでしょうか?
async/awaitの関数は次のような関数に変換されてV8上で実行されます。
suspendされて、microtaskキューの実行でsuspendの戻り値を返却し、
implicit_promiseをPromiseの結果として最終的に返します。後は懸念すべき点のasync/awaitを使った場合のパフォーマンス低下ですがNodeJS v12以降では素のPromiseとほぼ問題ないくらいの速度は出ているのでパフォーマンスを気にすることなく積極的にasync/awaitは使って良いレベルだと思います。
つまり、async/awaitそのものでパフォーマンス低下やイベントループがブロッキングされるということはないです。
(Promise内部の実装次第ではありえますが)参考:Faster async functions and promises
ブラウザの場合
ブラウザの場合もJavaScriptの実行フローはNodeJS同様EventLoopに基づいています。
PromiseやMutationObserverはmicrotaskで実行されます。setTimeout(() => console.log("0")); Promise.resolve() .then(() => console.log("1")); console.log("2");NodeJSのときと同様に
同期処理→Promise(microtask)→setTimeout(Timer)
の順番に実行されます。2 1 0もう一つ大事な点はDOMのイベントハンドリングやレンダリングを挟む場合は
すべてのmicrotaskは他のイベントハンドリングやレンダリングやTimer系処理の前に完了します。
(つまりイベントハンドリングしている最中にPromiseのネットワーク処理でデータが書き換わるなんてことはない)参考:イベントループ(event loop): microtask と macrotask
ChromeのWebWorkerを実装したJake氏のブログのほうがサンプル付きで実行例がわかりやすいかもしれません。
補足としてはブラウザ間の差異は解消され、FireFox、Safari、EdgeはすべてChromeと同じ挙動になっているのを確認しました。(2020/06/21現在)
元記事自体が2015年と古いため、古いブラウザでは差異があったのでしょう。参考:Tasks, microtasks, queues and schedules
ちなみに以上がわかると次記事の上級問題が解けます
参考:何問分かる?Promise に関するクイズ13問【解説付き】
- 投稿日:2020-06-21T23:16:11+09:00
React hooks 【シュチュエーション別 hooks利用方法と使い方まとめ】
モダンReactの代表になっているhooksですが、使いこなせるととても便利です。
今まで、クラスコンポーネントや
SFC(Stateless Functional Component)として高階コンポーネントやレンダープロップス、Recomposeなどを使っていた方は使い勝手の良さに驚くでしょう。また、これからReactを学習する人に向けてもhooksを使いこなすことはとても価値があり、
FC(Function Component)+hooksから入門するべきだと思います。
実際の現場でclassやSFCはレガシーであり、FC+hooksへの移行が進んでいるからです。復習がてら、どんなhooksがあるのか、どんな時にどう使うのかをまとめました。
前提として
React version 16.8.0 以降
hooksはFCでないと使用できないHooks.jsximport React, { useState, useEffect, useContext } from 'react' const hooks = () => { //関数コンポーネント内にhooksを書く return null }読み込みが必要ですがここから先ははしょりますので、上記のように読込んでください。
またはReact.hooksの名前
でも可能です。コンポーネント内で状態管理をしたい
useState
const hooks = () => { const [count, setCount] = useState(0) return( <> <div>{count}</div> <button onClick={() => setCount(count + 1)}>ボタン</button> </> ) }ステートフルな値と、それを変化させるための関数を分裂代入します。
上記は、ボタンを押すたびにsetCount
によって状態が変化して数字が増えていきます。また、
useState
の第一引数はinitialState(初期値)となっていて、ロジックが含まれている場合は、関数を渡すこともできます。const [count, setCount] = useState(()=> 1*2*3)オブジェクトの更新の仕方に注意
クラスコンポーネントの setState メソッドとは異なり、下記では正常にstateが更新されません。
const hooks = () => { const [user, setUser] = useState({ name: "test", age: "20" }) return ( <> <div>{user.name}</div> <button onClick={() => setUser({user.name = 'testNEW'}) }> ボタン </button> </> ) }consoleで
user
を出すとtestNEW
が出力されてしまいます。
正常にname
の値のみ変更するためには、下記のようにスプレッド構文と併用してマージさせるように書きます。const hooks = () => { return ( <button onClick={() => setUser({...user, ...{ name: "testNEW"}}) }> ボタン </button> ) }renderされた後に何か処理をしたい
useEffect
いままで
componentDidMount
をつかって、render後にAPIリクエストを行うといったアクションを取っていたと思いますが、useEffect
を使ってそれを実現できます。正確に言うと
componentDidMount
とcomponentDidUpdate
とcomponentWillUnmount
がまとまったものだと考えることができます。const hooks = () => { const [user, setUser] = useState({ name: "test", age: "20" }) //APIリクエストのサンプルです const sampleGetRequest = () => { return new Promise(resolve => { setTimeout(() => { resolve({ name: "NEW" }) }, 2000) }) } //render後に発火 useEffect(() => { sampleGetRequest().then(response => { setUser({ ...user, ...response }) }) }, []) return <div>{user.name}</div> }上記のコードを実行すると、renderされてから2秒後に
useStateで持っている、userが更新され、viewが書き換わります。第二引数について
第二引数を
引数なし、空の配列、値を入れた配列
で使い分けることによって、3種類の使い方ができます。引数なし
useEffect(() => {})コンポーネントがマウントされた後、更新された後に関わらず、毎回のレンダー時に処理を実行します。
componentDidMount と componentDidUpdateを併用しているのと似ています。空の配列
useEffect(() => {}, [])コンポーネントが初回にマウントされた後のみ処理を実行します。
componentDidMountを記述しているのと似ています。値を入れた配列
const [name, setName] = useState('test') const [age, setAge] = useState(25) useEffect(() => { console.log(`${name}は${age}才だ`) }, [count])配列内に入れた値を監視してくれます。
上記の例だと、配列の中に記入したcount
のデータの内容が書き換わった時に処理が実行されます。
なので、age
のデータ内容が更新されても何も起きず、count
が更新された時のみlogが出るようになります。無限ループに注意
引数を無し、配列に値を入れた引数の場合、無限ループになる可能性があります。
useEffectの説明の最初のコードの引数を無くしてみました。
これだと無限ループに陥って、処理を繰り返してしまいます。
レンダー => setUser({ ...user, ...response }) => レンダー => setUser({ ...user, ...response }) => レンダー => 続く...
const sampleGetRequest = () => { return new Promise(resolve => { setTimeout(() => { resolve({ name: "NEW" }) }, 2000) }) } useEffect(() => { sampleGetRequest().then(response => { setUser({ ...user, ...response }) }) })なぜなら
setUser({ ...user, ...response })
によってuser
の参照は毎回切れてしまうからですね。
{ name: "NEW", age: "20" }
から{ name: "NEW", age: "20" }
に変更されても、値は変わってないように見えて、参照は変わっているので、仮想DOMの差分を検知して再レンダーしてuseEffect発火という流れができてしまいます。
空の配列を引数に指定すれば治ります。
useEffect内で状態を変化させる際は気をつけましょう。もっとuseEffectについて詳し知りたい方はuseEffect完全ガイドをご覧ください。
コンポーネントツリー内で、値を共有したい
useContext
通常だとデータを親から子に渡す際、propsとしてバケツリレーで渡していく必要があります。
ですが、useContextを使えば、バケツリレーの必要がなく、一気に下層コンポーネントに値を渡すことができます。
多くの階層を経由していくつかの props を渡すことを避けたいときはうってつけです。
クラスコンポーネントでもコンテクストは使えましたが、それのhooks版という感じです。
下記は、APP.jsxからTitle.jsxまで一気に(Header.jsxを飛び越えて)値を渡している例です。App.jsximport React, { createContext } from "react" import Header from "./Header" //最上部のコンポーネントではクラスコンポーネントと同じように、 //値を共有するためのコンテクストをcreateContextで作成 export const PageMetaContext = createContext(""); const App = () => { const PageMeta = { title: "タイトル", description: "詳細です" }; return( //Header以下のツリーに共有できるようにプロバイダを使用、 //valueに値を設定することで、ツリー内のどの子コンポーネントにも渡せる(今回は PageMeta) <PageMetaContext.Provider value={PageMeta}> <Header /> </PageMetaContext.Provider> ) } export default App;値を使用する必要のないコンポーネントは何も特別なことはしなくてもいい
Header.jsximport React from "react" import Title from "./Title" const Header = () => ( <header> <Title /> </header> ) export default Header;親のプロバイダで設定された値を使いたいコンポーネントのみ
useContext
を使う
注意すべき点は、useContext
に渡す引数はコンテクストオブジェクト自体であることTitle.jsximport React, { useContext } from "react"; //ツリーの親で作成されたコンテキストを読み込む import { PageMetaContext } from "./App"; const Title = () => { //ここでuseContextを使う const { title, description } = useContext(PageMetaContext); return ( <> <h1>{title}</h1> <span>{description}</span> </> ) } export default Title;これで親で設定した
PageMeta
の中の文字列がviewに表示されます。useContext に渡す引数に注意
useContext
に渡す引数はコンテクストオブジェクト自体でなければいけません。上記の例だと、titleのみ使いたいからといって、
const title = useContext(PageMetaContext.title);にするとエラーが起きてしまうので気をつけましょう。
複数の複雑なstateを1つにして、templateをスッキリさせたい
useReducer
useReducerはuseStateと同じく、コンポーネント内で状態管理をするためのhookで、
useStateの状態管理をより堅牢であり、複雑なロジックが絡んだステートを更新するのに適しています。
Reduxに馴染みがあれば簡単ですが、初見だとわかりにくく使用するのにも気がひけるので順をおって説明します。下記のようなテキストボックスに入力するした値をリストにできるサンプルをつくりました。
2つのコンポーネントによって構成され、
下記はuseRecucerをもち、子コンポーネントからの入力によってリストをレンダーさせる親側のコンポーネントです。Todo.jsximport React, { useReducer } from "react" import InputArea from "./InputArea" const initialState = { input: "", items: [] } const reducer = (state, action) => { switch (action.type) { case "updateInput": return { ...state, input: action.payload }; case "resetInput": return { ...state, input: "" }; case "addItem": return { ...state, items: [...state.items, action.payload] }; case "removeItem": const filteredItems = state.items.filter(v => v.key !== action.payload); return { ...state, items: filteredItems }; case "resetItems": return initialState; default: throw new Error(); } } const Lists = () => { const [state, dispatch] = useReducer(reducer, initialState); return ( <div> <ul> {state.items.map((item, index) => ( <li key={index}> {item.title} <button onClick={() => dispatch({ type: "removeItem", payload: item.key }) } > 削除 </button> </li> ))} </ul> <InputArea input={state.input} dispatch={dispatch} /> </div> ) } export default ListsまずuseReducerの宣言部分ですが、
const [state, dispatch] = useReducer(reducer, initialState);
state
:ステートフルな値dispatch
:値を更新したいという旨をreducerに通知するための関数- useReducerの第1引数の
reducer
:値を更新するためのロジックが書いてある関数- useReducerの第2引数の
initialState
:値の初期値、関数でもOK。実際にレンダリングのために扱う値は、
state
の中身になります。値の更新は、
reducer
とaction
の2つの重要な役割によって行われます。reducer
const reducer = (state, action) => { switch (action.type) { case "updateInput": return { ...state, input: action.payload }; case "resetInput": return { ...state, input: "" }; case "addItem": return { ...state, items: [...state.items, action.payload] }; case "removeItem": const filteredItems = state.items.filter(v => v.key !== action.payload); return { ...state, items: filteredItems }; case "resetItems": return initialState; default: throw new Error(); } };reducer関数は、actionのtypeによって行う処理を分岐させるため、switch文で書きます。
引数の中身は
state
:現在のステートaction
:typeとpayloadが入ったオブジェクト
- type:
"updateInput"
のような処理をするための名前- pyload:action関数から渡させる任意の値
今回のreducerの中には下記のしたい処理が書いてあります。
- inputの値を更新
- inputの値を空にする
- 配列にinputの値を追加
- 配列から、keyが同じオブジェクトを削除
- inputと配列の内容を初期化
- typeがない時エラーを投げる
それぞれのtypeごとに実行される関数から返される値は、新しいステートになります。
それにより、新しいレンダーが行われます。
そして何を元にtypeがわかり、処理が振分けされるのかというと、action
によって行われます。action
dispatch({ type: "removeItem", payload: item.key })actionは簡単にいうと、reducerへの更新依頼です。
useReducerで宣言時に代入されたdispatch
を使うことで実現します。
{ type: "removeItem", payload: item.key }
はreducer内のactionで使うことができます。下位コンポーネントでstateの更新をさせる
下記は先ほどのTodo.jsxの子コンポーネントで、テキストボックスから値を入力し、stateを更新する役割を持っているコンポーネントです。
inputArea.jsximport React from "react"; const InputArea = ({ input, dispatch }) => { const hundleChange = event => { dispatch({ type: "updateInput", payload: event.currentTarget.value }); }; const addItem = () => { dispatch({ type: "addItem", payload: { title: input, key: new Date().getTime() + Math.random() } }); dispatch({ type: "resetInput" }); }; const resetItems = () => { dispatch({ type: "resetItems" }); }; return ( <div> <input type="text" onChange={e => hundleChange(e)} value={input} /> <button onClick={addItem}>追加</button> <button onClick={resetItems}>リセット</button> </div> ); }; export default InputArea;もし
useState
を使っている場合は親コンポーネントからコールバック関数を受け取り、発火させることでstateの更新ができましたが、
useReducer
を使っている場合は、dispatch
を渡し、子コンポーネント内でstate変更のactionを記述すれば良いので、stete更新のためのロジックが親コンポーネントに溜まらないので、見通しがよくなりますし、コンポーネントの役割が明確になります。
また、コンポーネントツリーが大きくなっている場合は、propsでなく、useContext
と組み合わせて使えば、小規模アプリならreduxを使わずに、useContext
+useReducer
でコードの肥大化を防ぐことができるでしょう。レンダーごとに計算を実行されないように処理をキャッシュしたい
useMomo
useMemoはメモ化された値を返します。
メモ化というのはプログラムの高速化のための最適化する技術の1つで、処理結果を保持しておき、あるトリガーがあるまで処理を行わずに保持してある値を返すことを言います。下記の例は
useMome
を使っていないコンポーネントで、
- テキストボックスの値を入力することで、stateの
input
が更新- ボタンを押すと、stateの
count
の三乗が計算され、consoleが出るというものです
import React, { useState, useMemo } from "react"; const Memo = () => { const [count, setCount] = useState(2); const [input, setInput] = useState(""); const newCount = () => { console.log("計算します"); return Math.pow(count, 3); }; return ( <div> <input type="text" value={input} onChange={e => setInput(e.target.value)} /> <p>{newCount()}</p> <button onClick={() => setCount(prev => prev + 1)}>increment</button> </div> ); }; export default Memo;上記だとonChangeが発火されるたびに、consoleに
計算します
が出力されます。
これは計算結果が変わることはないのにnewCount
関数が実行され、無駄な計算を繰り返していることになります。
今回の例は、そこまで複雑な計算ではないですが、もっと複雑になったり、他の処理も重なってくるとレンダリング速度のパフォーマンスの低下に繋がります。ここで
useMome
を使いますconst newCount = useMemo(() => { console.log("render"); return Math.pow(count, 3); }, [count]);これにより
newCount
はメモ化された値の入る変数になり、引数として配列に入っているcount
に変更があるまで処理は実行されません。
先ほどの問題は解消され、onChange
が発火して、stateのinput
が書き換わっても、再度計算されなくなりました。useMemoの第二引数の指定に注意
- 引数なし:全てのstate, propに依存します。結果的に不必要に処理が行われるためメモ化する意味がなくなります。
- 空配列[]:何にも依存しません。処理が行われるのはレンダー直後のみになります。
- 値を入れた配列:配列内の変数に変更があるたびに、処理が実行されます。複数入力可能です。useMemoを使う場合は、忘れないようにしましょう。
親からpropsとして渡される関数による無駄なレンダーを避けたい
useCallback
簡単にいうと
useMome
の関数版です。
useCallback
の場合はメモ化された関数を返します。
- 投稿日:2020-06-21T22:40:10+09:00
【JavaScript】値がundefined/nullの時だけtrue判定にする
値が
undefined
とnull
の時だけtrue
として判定したい(0
はfalse
判定にしたい)タイトルの通り、こんな場合どうするか。
[JavaScript] null とか undefined とか 0 とか 空文字('') とか false とかの判定についてに全て書いてあるが、このトピックの部分だけまとめてみた。
if (!value)
まず初めにこれを使っていたのだが、JSでは
0
はfalse
判定なので普通に間違い。勘違いして使っていたらバグの元になってしまう。
if (value === undefined || value === null)
これならOKだが、どこか野暮ったい。
この書き方が最も明示的で誰にもわかりやすいと思うが、もっとシンプルに書きたい場合もあるだろう。
if (!value && value !== 0)
2番目の書き方よりは短くなったが、少し分かりづらくなった気がする。
if (value == null)
この書き方だと
value
の値が0
の時はfalse
判定になる。厳密等価演算子ではなく等価演算子「==
」を使っているため、valueの値がnull
とundefined
の時は同様にtrue
として判定されるようだ。2、3番目の書き方よりも簡潔な条件式になった。厳密には次のようなカラクリらしい。
等価演算子は、2 つのオペランドが同じ型でないならばオペランドを変換して、それから厳密な比較を行います。両方のオペランドがオブジェクトならば、 JavaScript は内部参照を比較するので、オペランドがメモリ内の同じオブジェクトを参照するときに等しくなります。
ということで、
if (value == undefined)
でも同じ結果となるが、文字数的にはnullの方が簡潔になる。否定の時は
if (value != null)
でOK。(valueがnullとundefined以外の時にtrue判定となる)ESLintで引っかからないか気になったが、大丈夫だった(設定によっては指摘されるかも?)。
等価演算子「
==
」を使いたくなかったり、分かりやすさを求める場合は2、3番目の書き方が良さそうです。
- 投稿日:2020-06-21T22:36:29+09:00
WebでMIDIを記録して保存できるものをつくっておきました
概要
WebでMIDI入力を受け取って記録し、SMF(Standard MIDI File)の書き出しまで行えるものをつくりました。
動作には以下の条件が必要です。
・Web MIDI API対応のブラウザであること(Can I use)
・MIDI入力ができるデバイス等があることデモ
https://cagpie.net/web-midi-recording/
UIの作りがだいぶアレですが、
- 1)
Web MIDI API start
を押すとMIDIデバイスへアクセスを開始し、
- この状態から、繋いだMIDIデバイスから入力を送ると音が出ます(音が出る部分はソースコード的には
Sample
にあたります)- 2)
recording start
で記録を開始し、recording stop
で終了してSMFの書き出しが走ります。- 3)
play
で書き出したSMFの再生、またdownload
からSMFのダウンロードができます。
- playでの再生は、WebAudioAPIを用いてSMFを再生する PicoAudio.js を用いています。
ソースコード
https://github.com/cagpie/web-midi-recording
記録部分のみを本体としており、音がなる部分は本体に入っていません。
上記のデモはSample
の内容になります。MIDIを記録して保存する とは
例えば、
MIDIデバイスとして使用するピアノの真ん中の方の「ミ」の鍵盤を押したとします。
すると、0x90 0x40 0x7F
というMIDIメッセージが送られます。
(このデバイスの接続と、MIDIメッセージの受け取りにWeb MIDI APIを用いています)このイベント(MIDIメッセージ)を、押したタイミングと一緒に記録しておきます。
最終的に保存する際に、SMF形式にするのですが、
これは、イベントの内容と、それがいつ実行されるか(正確には前回のイベントからの差分時間)の情報が交互になっているだけです。
ざっくり言いましたが、本当はヘッダ情報なども含める必要があります。バイナリデータに起こし保存すれば、
DAWやMIDIシーケンスソフトで開いたりすることができます。おわり
おわりです。
Web×MIDI Web×音楽をもっと見たいです。参考
MIDIメッセージ一覧
https://www.g200kg.com/jp/docs/tech/midi.htmlSMF (Standard MIDI Files) の構造
https://sites.google.com/site/yyagisite/material/smfspec
- 投稿日:2020-06-21T22:31:11+09:00
iOS 13 以降ではウェブ上でセンサー値を扱う際にユーザからの許可が必要
はじめに
iOS 12 までは、Safari の設定からセンサー値を利用していいか権限を与えていました。
iOS 13 からは仕様が変わり、サイトごとにユーザーからセンサー値の権限を JavaScript で与える必要があります。
※ 永続化は不可能、セッション(?)ごとに許可を取る必要がある
ご覧の通り項目がなくなっていますね…(iOS 13.3.1 にて)DeviceMotionEvent.requestPermission(); DeviceOrientationEvent.requestPermission();このコードを実行するとセンサー値の権限を制御できるウィンドウが出てくるのですが
どうやらタップやクリックなどの意図的なユーザの行動から有効化の流れを作る必要があるみたいです…
iOS(特に 13 以降)でのモーションセンサー有効化 - http://dotnsf.blog.jp/archives/1076737232.html
こちらサイト様に有効化の手段は書いてあったのですが、他にも方法がないか検証してみたので共有しておきます。ユーザに確認を取ったが駄目 ? だったパターン
1. window.confirm
if (typeof DeviceMotionEvent.requestPermission === 'function' && sessionStorage.getItem('isPermission') === null) { const isPermission = confirm('このサイトでは、センサー値を扱います。'); if (isPermission) { const isDeviceOrientationEvent = await DeviceOrientationEvent.requestPermission(); const isDeviceMotionEvent = await DeviceMotionEvent.requestPermission(); // 許可したあとはまた許可が必要になるまでポップアップが出ないようにする if (isDeviceOrientationEvent && isDeviceMotionEvent) { sessionStorage.setItem('isPermission', 'true'); } } }これでは動きませんでした。
これを禁止にしている Apple さんの意図が正直よくわからない2. DOM でボタンを生成し、 JS 内で仮想クリックを行う
if (typeof DeviceMotionEvent.requestPermission === 'function' && sessionStorage.getItem('isPermission') === null) { const confirmElement = document.createElement('div'); confirmElement.style.display = 'none'; confirmElement.onclick = () => { const isDeviceOrientationEvent = await DeviceOrientationEvent.requestPermission(); const isDeviceMotionEvent = await DeviceMotionEvent.requestPermission(); // 許可したあとはまた許可が必要になるまで生成しないようにする if (isDeviceOrientationEvent && isDeviceMotionEvent) { sessionStorage.setItem('isPermission', 'true'); } document.body.removeChild(confirmElement); } document. body.appendChild(confirmElement); window.onload = confirmElement.click(); }ボタンをクリックしたイベントを呼び出して動作させればイケるんじゃないか ? と思いましたが駄目でした。
ブラウザ側でタップやクリックの動作も監視しており、それも同時に実行されてないと呼び出しできない仕様になってるんですかねえ3. 外部ライブラリのクリック時の動作メソッドを使う
if (typeof DeviceMotionEvent.requestPermission === 'function' && sessionStorage.getItem('isPermission') === null) { const tingleLinkElement = document.createElement('link'); tingleLinkElement.rel = 'stylesheet'; tingleLinkElement.href = 'https://cdnjs.cloudflare.com/ajax/libs/tingle/0.15.3/tingle.min.css'; document.head.appendChild(tingleLinkElement); tingleLinkElement.onload = () => { const tingleScriptElement = document.createElement('script'); tingleScriptElement.src = 'https://cdnjs.cloudflare.com/ajax/libs/tingle/0.15.3/tingle.min.js'; document.body.appendChild(tingleScriptElement); tingleScriptElement.onload = () => { const modal = new tingle.modal({ footer: true }); modal.setContent('<p>このサイトでは、センサー値を扱います。</p>'); modal.addFooterBtn('Cancel', 'tingle-btn tingle-btn--default tingle-btn--pull-right', () => modal.close()); modal.addFooterBtn('OK', 'tingle-btn tingle-btn--primary tingle-btn--pull-right', () => { const isDeviceOrientationEvent = await DeviceOrientationEvent.requestPermission(); const isDeviceMotionEvent = await DeviceMotionEvent.requestPermission(); // 許可したあとはまた許可が必要になるまでポップアップが出ないようにする if (isDeviceOrientationEvent && isDeviceMotionEvent) { sessionStorage.setItem('isPermission', 'true'); } modal.close(); }); modal.open(); }; }; }今回、Tingle.js というモーダルプラグインを使用した際にハマった点です。
クリックイベントも取ってるだろうし、これなら大丈夫っしょ!!って思って書きましたが駄目でした。
外部ライブラリを使いたい際は気をつけたほうが良いかもしれません。
毎回毎回、自作のコンポーネントを用意できるわけではないのでこの仕様は少し困ってしまいますね…結局うまく行ったパターン
ライブラリのメソッドは使わず addEventListener でクリックやタップ動作に対応した
if (typeof DeviceMotionEvent.requestPermission === 'function' && sessionStorage.getItem('isPermission') === null) { const tingleLinkElement = document.createElement('link'); tingleLinkElement.rel = 'stylesheet'; tingleLinkElement.href = 'https://cdnjs.cloudflare.com/ajax/libs/tingle/0.15.3/tingle.min.css'; document.head.appendChild(tingleLinkElement); tingleLinkElement.onload = () => { const tingleScriptElement = document.createElement('script'); tingleScriptElement.src = 'https://cdnjs.cloudflare.com/ajax/libs/tingle/0.15.3/tingle.min.js'; document.body.appendChild(tingleScriptElement); tingleScriptElement.onload = () => { const modal = new tingle.modal({ footer: true }); modal.setContent('<p>このサイトでは、センサー値を扱います。</p>'); modal.addFooterBtn('Cancel', 'tingle-btn tingle-btn--default tingle-btn--pull-right', () => modal.close()); modal.addFooterBtn('OK', 'tingle-btn tingle-btn--primary tingle-btn--pull-right', () => { modal.close(); }); document.querySelector('.tingle-btn.tingle-btn--primary.tingle-btn--pull-right').addEventListener('click', async () => { const isDeviceOrientationEvent = await DeviceOrientationEvent.requestPermission(); const isDeviceMotionEvent = await DeviceMotionEvent.requestPermission(); // 許可したあとはまた許可が必要になるまでポップアップが出ないようにする if (isDeviceOrientationEvent && isDeviceMotionEvent) { sessionStorage.setItem('isPermission', 'true'); } }); modal.open(); }; }; }動くことには動きましたが、なんか微妙に納得のいかない書き方に…
![]()
結論
ユーザからの動作であれば何でも良いわけではなく onclick か addEventListener を使って実装しなくてはいけない
セキュリティの観点からこういう風にサイトごとに許可を取るスタイルはしょうがないとは思うんだけど
メソッドを呼んでくれる基準がよくわからないから使う側としてはすごく困るなあと…
iOS のブラウザ(というか Safari )はこんな感じの謎独自機能と草案の機能の実装スピードをもう少し早くしてくれればなあ… と最近思うことが多いです。
正直、ブラウザに関しては Android ブラウザのほうが圧勝だなあと思いますね ?
- 投稿日:2020-06-21T22:19:53+09:00
Promiseとは・非同期処理時にPromiseを使うメリットとは
Promiseとは
非同期処理のコードを扱いやすくするもの。
Promiseを用いると、非同期処理のコールバックの扱いがより簡単になる。Promiseを使った実装・使わない実装
・Promiseを使わずに実装
//doWorkCallback関数の定義 const doWorkCallback = (callback) => { //非同期処理。2秒後に[1,2,3]を返す setTimeOut(() => { callback(undefined, [1,2,3]) }, 2000) } //doWork関数の実行 //第一引数:エラー時の返り値、第二引数:処理成功時の返り値 doWorkCallback((error, result) => { if(error){ return console.log(error) } console.log(result) })↓
2秒後に[1,2,3]が返る
または、2秒後にerrorが返る・Promiseを使って実装
//doWorkPromise関数の定義 const doWorkPromise = new Promise((resolve, reject) => { //非同期処理。2秒後に[1,2,3]を返す setTimeOut(() => { //処理成功時の処理をresolve関数として実装 resolve([1,2,3]) //エラー時の処理をreject関数として実装 reject('Things went wrong') } },2000) //doWorkPromise関数の実行 //成功時の処理を、「.then()」をつなげることによって記述 doWorkPromise.then((result) => { console.log('success', result) //エラー時の処理を、「.catch()」をつなげることによって記述 }).catch((error) => { console.log('Error',error) })↓
2秒後に「success, [1,2,3]」が返る
または、2秒後に「Error Things went wrong」が返るPromiseを使用するメリット
Promiseを使って実装すると・・・
①成功時の処理はresolve関数、エラー時の処理はreject関数と、2つの別々の関数て処理している為、何が起こっているかわかりやすい。
Promiseを使わないと: 関数1つで成功時とエラー時の処理を行うことになる、
コールバックの全ての呼び出しを調べてから、引数errorと引数result、どちらが提供されたか判断することになる。②処理結果によって呼び出される関数はresolve関数かreject関数かのどちらか一つ。
呼び出されるとPromiseは終了する為、後から実行結果の値や状態が上書きされることがない。Promiseを使わないと: コールバックが2回呼び出され、結果が変わるリスクがある。
Promise処理中におこっている事
Promiseが作成される
↓
Pending:非同期処理実行中。Promiseの結果は保留
↓
①fulfilled:処理成功
②rejected:処理失敗Promiseをチェインさせる
複数の非同期メソッドをつなげて処理を行う時、Promiseをチェインさせて書くことができる。
・Promiseチェインを使わず実装
//add関数の定義。Promiseを使った非同期処理とし、2秒後にa+bの結果を返す const add = (a, b) => { return new Promise ((resolve, reject) => { setTimeout(() => { resolve(a + b) }, 2000) }) } add(1, 2).then((sum) => { console.log(sum) add(sum, 5).then((sum2) => { //add関数で、「add(1,2)の結果」と「5」を処理する console.log(sum2) }).catch((e) => { console.log(e) }) }).catch((e) => { //add(1,2)のエラー処理 console.log(e) })↓
2秒後に「3」、さらに2秒後に「8」が返る・Promiseチェインを使って実装
//add関数の定義。Promiseを返し、2秒後にa+bの結果を返す const add = (a, b) => { return new Promise ((resolve, reject) => { setTimeout(() => { resolve(a + b) }, 2000) }) } add(1,1).then((sum) => { console.log(sum) return add(sum, 4) //add関数で、「add(1,1)の結果」と「4」を処理する。2つ目の非同期処理を returnで返すようにしている }).then((sum2) => { //2つ目の非同期処理を「.then()」で返す console.log(sum2) }).catch((e) => { console.log(e) })↓
2秒後に「2」、さらに2秒後に「6」が返る・Promiseチェインを使うメリット
①ネストを深くせずに、複数の非同期処理を繋げて行える
②エラー時の処理コードを重複して書かなくてすむ参考文献
「The complete Node.js Developer Course」
https://www.udemy.com/share/101WGiAEIedVpTTX4D/
- 投稿日:2020-06-21T22:03:18+09:00
プログラミング TypeScript:第 2 章 TypeScript 全体像
はじめに
TypeScript の学習のために「プログラミング TypeScript ―スケールする JavaScript アプリケーション開発」を購入しましたので、自身の学習のためにも本プログで数回に渡って、重要な部分に絞って紹介していければと思います。
学習用にサンプルプログラミングも Github で紹介されていますので、合わせて紹介していきます。
『プログラミングTypeScript』のリポジトリ本記事についての内容は以下を参照してください。
プログラミング TypeScript:第 2 章 TypeScript 全体像
- 投稿日:2020-06-21T21:58:31+09:00
TypeScript をはじめてみた!
はじめに
ArcGIS API for JavaScript を使用した簡単な Web マッピングアプリの開発に TypeScript を使用してみました。
ArcGIS API for JavaScript は、Google Map API と同じような Web マッピングアプリを開発するための API です。ArcGIS API for JavaScript は、Google Map とは異なり 空間検索などの GIS の機能やデータのビジュアライゼーションも豊富に対応しています。今回は、ArcGIS API for JavaScript で TypeScript を使用するための準備として、開発環境の構築や簡単なサンプルアプリを作成までの手順を紹介したいと思います。また、TypeScript を使用するメリットについても紹介します。
本記事についての内容は以下を参照してください。
TypeScript をはじめてみた!
- 投稿日:2020-06-21T21:51:34+09:00
【Vue】filtersでthisが使えない
Vueの
filters
でthis
が使えなかった話Vue.jsのfiltersで↓のようなコードを書いて実行しようとしたらエラーがでた。
最初はMixinとして使っていたためMixinでは
filters
は使えないのかと思い、普通にコンポーネント内のfilters
プロパティ内で使っても同じエラーがでたので調べることにした。ちなみに
filters
というのはVue固有のプロパティの一つで、ここに定義した関数をムスタッシュ構文内で例えば{{ <data> | oneFilter }}
のようにして使うと<data>
をoneFilter
で加工することができる。<template> <div id="app"> <p>{{ '選択してください' | processChoice }}</p> </div> </template> <script> new Vue({ el: '#app', computed: { getName (obj) { if (obj) { return obj.name } } }, filters: { processChoice(value) { if (this.getName) { return this.getName } return value } } }) </script>エラーメッセージCannot read property 'getName' of undefined...結論
先に結論を書いてしまうと、「Vueの
filters
プロパティではthis
(Vueインスタンス)にはアクセスできないようなので、computed
かmethods
を使いましょう」ということらしいです。
filters
で定義した関数内からthis
にアクセスできないので、undefined
エラーが出ているということですね。調べたこととか
1番目のリンクのissueに対する回答で、Evan You(Vue.jsの作者)がfiltersでは敢えてVueインスタンスにアクセスできないようににしていると言っています。filtersでは純粋なJSの関数を使うことしか現状できないようです。どうしても
this
を使う必要がある時は結論に書いたようにfilters
ではなく、computed
かmethods
に処理をまとめて書いてそれを使いましょう。(compoted
とmethods
もムスタッシュ構文に埋め込むことが可能です。)何人かの開発者がfilters内で
this
を使えるようにして欲しいと書いていますが、
作者が↓のように言っているのでVue3系でも同じ仕様になるような気がします。I'm absolutely convinced that filters must have a way access the context. The question's what core team is going to do about it?
Sorry but my opinion has not changed: filters should not, and will not have access to context. If you need context, use a method.
- 投稿日:2020-06-21T21:02:07+09:00
いろいろ試そう Parcel v2 (Beta)!
はじめに
去る 2020年6月19日、Parcel v2のベータバージョンがリリースされました。
? Parcel 2 beta 1: improved stability, tree shaking, source map performance, and more! ?
? Parcel 2 beta 1 has been released!
— Devon Govett (@devongovett) June 18, 2020
? Improved tree shaking
? 20x faster source map builds
#️⃣ Improved content hashing
? Resolver diagnostics
? More accurate bundle reports
? Tons of bugfixes!
? Read more: https://t.co/hMGtOKEOs3
? New website! https://t.co/SWaS5OvWIAこの記事では、Parcel v2の基本的な使い方から、いくつかのAltJS・ライブラリをParcelでコンパイルするテンプレートについて説明します。
なお、この記事の内容は
v2.0.0-beta.1
時点でのドキュメント、ソースコードをベースにしています。
また、無印のParcelを Parcel v1、新しいv2を Parcel v2と表記します。オフィシャルドキュメント
Parcel v1のドキュメントは https://parceljs.org/ でしたが、v2ではURLが代わり https://v2.parceljs.org/ (未完成)がドキュメントとして提供されています。
インストール
Parcel v1のパッケージは
parcel-bundler
という名前で公開されていましたが、Parcel v2からパッケージ名がparcel
となりました。また、現在のParcel v2はベータ版のため、バージョンを
@next
と指定してインストールします。$ yarn add -D parcel@next設定ファイル
Parcel v2では、
.parcelrc
という設定ファイルを使用できるようになりました。
.parcelrc
を指定しなかった場合は、デフォルトの設定(@parcel/config-default
)でバンドルされます。
.parcelrc
はParcelのプラグインを種類ごとに設定します。設定できるプラグインの種類は次のとおりです。
- Resolvers
- ファイルやパッケージ、アセットを解決するプラグイン
- Transformers
- ソースコードを変換(トランスパイル)するプラグイン
- 公式でBabelやTypeScript、Vue、Yamlなどのトランスフォーマが提供されている
- Bundlers
- アセットをバンドルするプラグイン
- Namers
- ファイルの命名やコンテントハッシュなどを付与するプラグイン
- Runtimes
- バンドラーフェーズのあとに最終的にバンドルに含まれるアセット(HMRのコードなど)を生成するプラグイン
- Packagers
- 異なるアセットタイプを単一のバンドルにマージするプラグイン
- Optimizers
- バンドルを最適化するプラグイン
- Reporters
- Parcelでのイベントを受け取ってロガーに出力したり、ログファイルを生成するプラグイン
- Validators
- ビルド後にソースコードの検証を行いエラーを表示するプラグイン
いずれのプラグインも自作が可能ですが、基本的な機能は公式パッケージとして公開されているものを使用すれば問題ないでしょう。
もし、個別の
.parcelrc
を設定する場合はデフォルトの設定に追加する形で定義するとよいでしょう。{ // デフォルトの設定を継承する "extends": "@parcel/config-default", "optimizers": { "*.{png,jpg}": ["@parcel/optimizer-awesome-images"] } }また、ある拡張子にプラグインを追加する際は、デフォルトの設定を引き継ぐよう
...
を最後に記載するようにしましょう。{ "extends": "@parcel/config-default", "transforers": { // デフォルト設定が引き継がれないためコンパイルできない "*.{ts,tsx}": [], // ... で extends で継承された設定を引き継ぐ "*.{ts,tsx}": ["..."], } }いろいろ試してみよう
ここからは、Parcel v2でいろいろなAltJS、ライブラリをバンドルする設定を見ていきましょう。
それぞれ、GitHubでソースコードを公開しています。また、公式のリポジトリにもいくつかのサンプルプロジェクトが用意されています。
Pug + Sass
テンプレートエンジンのPugとCSSプリプロセッサのSassを使用したサンプルです。
開発環境構築
サンプルリポジトリの開発環境構築の手順を紹介します。
ここでは、次の構成でファイルを配置しています。
それぞれのファイルの内容はサンプルリポジトリでご確認ください。. ├── src/ │ ├── index.pug │ └── styles.sass └── package.jsonPugとSassの設定はデフォルトで定義されているため、
.parcelrc
ファイルでの設定は必要ありません。
(Pugの設定・Sassの設定)インストールした依存関係は
parcel@next
と次のパッケージです。$ yarn add -D pug sass # reset.css $ yarn add reset-cssここまでできたら、Parcelで開発サーバーを起動します。
serve
コマンドは省略できます。$ yarn parcel serve src/index.pug
ポートを指定していない場合、
http://localhost:1234
で開発サーバーが起動します。あとは、Pugファイル、Sassファイルへ変更を加えるとHMRで表示中の内容が入れ替わります。
以上でPugとSassの開発環境ができました!かんたんですね。
まとめ
yarn add -D parcel@next pug sass
- PugとSassを記述
yarn parcel serve src/index.pug
- http://localhost:1234 を開く
余談
Parcelでは、Sassから
node_modules
へインストールされたパッケージをインポートする際、パッケージ名のプレフィックスとして~
を必要としません。
~package-name
の指定は本来、Webpackでのシンタックスのようで、ParcelのCLI上では次のようにレポートされます。具体的な理由まで書かれていて、とても親切なエラーメッセージですね!
React + CSS Modules
React(JSX)とCSS Modulesによるサンプルです。
開発環境構築
サンプルリポジトリの開発環境構築の手順を紹介します。
ここでは、次の構成でファイルを配置しています。
それぞれのファイルの内容はサンプルリポジトリでご確認ください。. ├── src/ │ ├── Hello/ │ │ ├── index.jsx │ │ └── index.modules.css │ ├── entry.jsx │ ├── index.html │ └── styles.css ├── .postcssrc └── package.jsonJSXやCSSのバンドルに関する設定はデフォルトで定義されているため、
.parcelrc
ファイルでの設定は必要ありません。
(JSXの設定)ただし、CSS Modulesを使用するためのPostCSSの設定が必要です。
ここでは、postcss-modules
パッケージを使用してCSS Modulesを実現しています。{ "modules": true, "plugins": [] }インストールした依存関係は
parcel@next
と次のパッケージです。$ yarn add react react-dom prop-types $ yarn add -D postcss-modules # reset.css $ yarn add reset-cssここまでできたら、Parcelで開発サーバーを起動します。
serveコマンドは省略できます。$ yarn parcel serve src/index.html
ポートを指定していない場合、
http://localhost:1234
で開発サーバーが起動します。あとは、JSXファイルやCSSファイルへ変更を加えるとHMRで表示中の内容が入れ替わります。
以上でReactとCSS Modulesの開発環境が構築できました。こちらもかんたんでしたね!
まとめ
yarn add -D parcel@next postcss-modules
yarn add react react-dom prop-types
.postcssrc
に設定を記述- Reactを記述
yarn parcel serve src/index.html
- http://localhost:1234 を開く
余談
今回はプレーンなCSSを使用したCSS Modulesでしたが、JSXファイルから参照するファイルを
.sass
や.styl
へ変更し、必要なパッケージ(sass
やstylus
)をインストールすれば、設定を変更することなくほかのCSSプリプロセッサを使用したCSS Modulesで開発ができます。Vue
残念ながら、Parcel v2 beta.1時点ではVueのトランスフォーマプラグインが開発されていません。
開発については次のIssue内で議論が行われています。
Parcel 2: Vue transformer · Issue #3364 · parcel-bundler/parcel
状況として、Parcel v2のコアチームにVueを使用するメンバーが少なくSFCの取り扱いについて議論しているようです。
開発が終了し、公開されれば次のように
.parcelrc
に記載すればVueもバンドルできるようになるのではないかなと思います。{ "extends": "@parcel/config-default", "transformers": { "*.vue": ["@parcel/transformer-vue"] } }TypeScript + ESLint
TypeScriptとESLintを使用したサンプルです。
開発環境構築
サンプルリポジトリの開発環境構築の手順を紹介します。
ここでは、次の構成でファイルを配置しています。
それぞれのファイルの内容はサンプルリポジトリでご確認ください。. ├── src/ │ └── index.ts ├── .eslintrc └── package.json └── tsconfig.jsonインストールした依存関係は
parcel@next
と次のパッケージです。yarn add -D typescript @parcel/validator-typescript @parcel/validator-eslint # ESLint yarn add -D eslint @typescript-eslint/parser @typescript-eslint/eslint-pluginTypeScriptの設定はデフォルトで定義されているため、
.parcelrc
ファイルでの必要はありませんが、型をチェックするには別途、バリデータプラグイン@parcel/validator-typescript
を追加する必要があります。
また、ESLintチェックもTypeScriptの型チェックと同様、バリデータプラグイン@parcel/validator-eslint
が必要です。バリデータプラグインを設定した
.parcelrc
ファイルは次のとおりです。{ "extends": "@parcel/config-default", "validators": { "*.{js,jsx,ts,tsx}": ["@parcel/validator-eslint", "@parcel/validator-typescript"] } }ここまでできたら、Parcelで
.ts
ファイルをビルドします。$ yarn parcel build src/index.ts
ソースコードに問題がなければビルドに成功しますが、エラーがあった場合は次のようにCLI上にエラーが表示されます。
なお、ESLintエラーのAuto Fixには対応していないようです。
まとめ
yarn add -D parcel@next typescript eslint @parcel/validator-typescript @parcel/validator-eslint
.eslintrc
にESLint設定を記述yarn parcel build src/index.ts
- エラーがあればCLI上に表示される
おわりに
Parcel v2でv1時代より柔軟にオプションを設定できるようになりましたが、設定不要でバンドルできる手軽さも健在です。
まだベータ版ですが、一般的な使い方をする分には申し分ない機能が提供されているなという印象でした。
ただ、新しいツールのため、調べられる情報や公開されているプラグインが少なく真に柔軟に使えるようになるにはまだまだ時間がかかるかなと思います。2019/8/14にα版が公開されてから10ヶ月、ついにParcel v2も正式リリースの日が近づいてきました。
これからのParcelの発展が楽しみです!
- 投稿日:2020-06-21T21:02:07+09:00
いろいろ試そう Parcel 2 (Beta)!
はじめに
去る 2020年6月19日、Parcel v2のベータバージョンがリリースされました。
? Parcel 2 beta 1: improved stability, tree shaking, source map performance, and more! ?
? Parcel 2 beta 1 has been released!
— Devon Govett (@devongovett) June 18, 2020
? Improved tree shaking
? 20x faster source map builds
#️⃣ Improved content hashing
? Resolver diagnostics
? More accurate bundle reports
? Tons of bugfixes!
? Read more: https://t.co/hMGtOKEOs3
? New website! https://t.co/SWaS5OvWIAこの記事では、Parcel v2の基本的な使い方から、いくつかのAltJS・ライブラリをParcelでコンパイルするテンプレートについて説明します。
なお、この記事の内容は
v2.0.0-beta.1
時点でのドキュメント、ソースコードをベースにしています。
また、無印のParcelを Parcel v1、新しいv2を Parcel v2と表記します。オフィシャルドキュメント
Parcel v1のドキュメントは https://parceljs.org/ でしたが、v2ではURLが代わり https://v2.parceljs.org/ (未完成)がドキュメントとして提供されています。
インストール
Parcel v1のパッケージは
parcel-bundler
という名前で公開されていましたが、Parcel v2からパッケージ名がparcel
となりました。また、現在のParcel v2はベータ版のため、バージョンを
@next
と指定してインストールします。$ yarn add -D parcel@next設定ファイル
Parcel v2では、
.parcelrc
という設定ファイルを使用できるようになりました。
.parcelrc
を指定しなかった場合は、デフォルトの設定(@parcel/config-default
)でバンドルされます。
.parcelrc
はParcelのプラグインを種類ごとに設定します。設定できるプラグインの種類は次のとおりです。
- Resolvers
- ファイルやパッケージ、アセットを解決するプラグイン
- Transformers
- ソースコードを変換(トランスパイル)するプラグイン
- 公式でBabelやTypeScript、Vue、Yamlなどのトランスフォーマが提供されている
- Bundlers
- アセットをバンドルするプラグイン
- Namers
- ファイルの命名やコンテントハッシュなどを付与するプラグイン
- Runtimes
- バンドラーフェーズのあとに最終的にバンドルに含まれるアセット(HMRのコードなど)を生成するプラグイン
- Packagers
- 異なるアセットタイプを単一のバンドルにマージするプラグイン
- Optimizers
- バンドルを最適化するプラグイン
- Reporters
- Parcelでのイベントを受け取ってロガーに出力したり、ログファイルを生成するプラグイン
- Validators
- ビルド後にソースコードの検証を行いエラーを表示するプラグイン
いずれのプラグインも自作が可能ですが、基本的な機能は公式パッケージとして公開されているものを使用すれば問題ないでしょう。
もし、個別の
.parcelrc
を設定する場合はデフォルトの設定に追加する形で定義するとよいでしょう。{ // デフォルトの設定を継承する "extends": "@parcel/config-default", "optimizers": { "*.{png,jpg}": ["@parcel/optimizer-awesome-images"] } }また、ある拡張子にプラグインを追加する際は、デフォルトの設定を引き継ぐよう
...
を最後に記載するようにしましょう。{ "extends": "@parcel/config-default", "transforers": { // デフォルト設定が引き継がれないためコンパイルできない "*.{ts,tsx}": [], // ... で extends で継承された設定を引き継ぐ "*.{ts,tsx}": ["..."], } }いろいろ試してみよう
ここからは、Parcel v2でいろいろなAltJS、ライブラリをバンドルする設定を見ていきましょう。
それぞれ、GitHubでソースコードを公開しています。また、公式のリポジトリにもいくつかのサンプルプロジェクトが用意されています。
Pug + Sass
テンプレートエンジンのPugとCSSプリプロセッサのSassを使用したサンプルです。
開発環境構築
サンプルリポジトリの開発環境構築の手順を紹介します。
ここでは、次の構成でファイルを配置しています。
それぞれのファイルの内容はサンプルリポジトリでご確認ください。. ├── src/ │ ├── index.pug │ └── styles.sass └── package.jsonPugとSassの設定はデフォルトで定義されているため、
.parcelrc
ファイルでの設定は必要ありません。
(Pugの設定・Sassの設定)インストールした依存関係は
parcel@next
と次のパッケージです。$ yarn add -D pug sass # reset.css $ yarn add reset-cssここまでできたら、Parcelで開発サーバーを起動します。
serve
コマンドは省略できます。$ yarn parcel serve src/index.pug
ポートを指定していない場合、
http://localhost:1234
で開発サーバーが起動します。あとは、Pugファイル、Sassファイルへ変更を加えるとHMRで表示中の内容が入れ替わります。
以上でPugとSassの開発環境ができました!かんたんですね。
まとめ
yarn add -D parcel@next pug sass
- PugとSassを記述
yarn parcel serve src/index.pug
- http://localhost:1234 を開く
余談
Parcelでは、Sassから
node_modules
へインストールされたパッケージをインポートする際、パッケージ名のプレフィックスとして~
を必要としません。
~package-name
の指定は本来、Webpackでのシンタックスのようで、ParcelのCLI上では次のようにレポートされます。具体的な理由まで書かれていて、とても親切なエラーメッセージですね!
React + CSS Modules
React(JSX)とCSS Modulesによるサンプルです。
開発環境構築
サンプルリポジトリの開発環境構築の手順を紹介します。
ここでは、次の構成でファイルを配置しています。
それぞれのファイルの内容はサンプルリポジトリでご確認ください。. ├── src/ │ ├── Hello/ │ │ ├── index.jsx │ │ └── index.modules.css │ ├── entry.jsx │ ├── index.html │ └── styles.css ├── .postcssrc └── package.jsonJSXやCSSのバンドルに関する設定はデフォルトで定義されているため、
.parcelrc
ファイルでの設定は必要ありません。
(JSXの設定)ただし、CSS Modulesを使用するためのPostCSSの設定が必要です。
ここでは、postcss-modules
パッケージを使用してCSS Modulesを実現しています。{ "modules": true, "plugins": [] }インストールした依存関係は
parcel@next
と次のパッケージです。$ yarn add react react-dom prop-types $ yarn add -D postcss-modules # reset.css $ yarn add reset-cssここまでできたら、Parcelで開発サーバーを起動します。
serveコマンドは省略できます。$ yarn parcel serve src/index.html
ポートを指定していない場合、
http://localhost:1234
で開発サーバーが起動します。あとは、JSXファイルやCSSファイルへ変更を加えるとHMRで表示中の内容が入れ替わります。
以上でReactとCSS Modulesの開発環境が構築できました。こちらもかんたんでしたね!
まとめ
yarn add -D parcel@next postcss-modules
yarn add react react-dom prop-types
.postcssrc
に設定を記述- Reactを記述
yarn parcel serve src/index.html
- http://localhost:1234 を開く
余談
今回はプレーンなCSSを使用したCSS Modulesでしたが、JSXファイルから参照するファイルを
.sass
や.styl
へ変更し、必要なパッケージ(sass
やstylus
)をインストールすれば、設定を変更することなくほかのCSSプリプロセッサを使用したCSS Modulesで開発ができます。Vue
残念ながら、Parcel v2 beta.1時点ではVueのトランスフォーマプラグインが開発されていません。
開発については次のIssue内で議論が行われています。
Parcel 2: Vue transformer · Issue #3364 · parcel-bundler/parcel
状況として、Parcel v2のコアチームにVueを使用するメンバーが少なくSFCの取り扱いについて議論しているようです。
開発が終了し、公開されれば次のように
.parcelrc
に記載すればVueもバンドルできるようになるのではないかなと思います。{ "extends": "@parcel/config-default", "transformers": { "*.vue": ["@parcel/transformer-vue"] } }TypeScript + ESLint
TypeScriptとESLintを使用したサンプルです。
開発環境構築
サンプルリポジトリの開発環境構築の手順を紹介します。
ここでは、次の構成でファイルを配置しています。
それぞれのファイルの内容はサンプルリポジトリでご確認ください。. ├── src/ │ └── index.ts ├── .eslintrc └── package.json └── tsconfig.jsonインストールした依存関係は
parcel@next
と次のパッケージです。yarn add -D typescript @parcel/validator-typescript @parcel/validator-eslint # ESLint yarn add -D eslint @typescript-eslint/parser @typescript-eslint/eslint-pluginTypeScriptの設定はデフォルトで定義されているため、
.parcelrc
ファイルでの必要はありませんが、型をチェックするには別途、バリデータプラグイン@parcel/validator-typescript
を追加する必要があります。
また、ESLintチェックもTypeScriptの型チェックと同様、バリデータプラグイン@parcel/validator-eslint
が必要です。バリデータプラグインを設定した
.parcelrc
ファイルは次のとおりです。{ "extends": "@parcel/config-default", "validators": { "*.{js,jsx,ts,tsx}": ["@parcel/validator-eslint", "@parcel/validator-typescript"] } }ここまでできたら、Parcelで
.ts
ファイルをビルドします。$ yarn parcel build src/index.ts
ソースコードに問題がなければビルドに成功しますが、エラーがあった場合は次のようにCLI上にエラーが表示されます。
なお、ESLintエラーのAuto Fixには対応していないようです。
まとめ
yarn add -D parcel@next typescript eslint @parcel/validator-typescript @parcel/validator-eslint
.eslintrc
にESLint設定を記述yarn parcel build src/index.ts
- エラーがあればCLI上に表示される
おわりに
Parcel v2でv1時代より柔軟にオプションを設定できるようになりましたが、設定不要でバンドルできる手軽さも健在です。
まだベータ版ですが、一般的な使い方をする分には申し分ない機能が提供されているなという印象でした。
ただ、新しいツールのため、調べられる情報や公開されているプラグインが少なく真に柔軟に使えるようになるにはまだまだ時間がかかるかなと思います。2019/8/14にα版が公開されてから10ヶ月、ついにParcel v2も正式リリースの日が近づいてきました。
これからのParcelの発展が楽しみです!
- 投稿日:2020-06-21T20:54:20+09:00
mongoDB, mongooseを使ってCRUDを実装する
CRUDとは
CRUD(クラッド)とは、ほとんど全てのコンピュータソフトウェアが持つ永続性[1]の4つの基本機能のイニシャルを並べた用語。その4つとは、Create(生成)、Read(読み取り)、Update(更新)、Delete(削除)である。
(Wikipediaより)
・CRUDそれぞれに対応するHTTPメソッド
・Create:Postメソッド
・Read:Getメソッド
・Update:Patchメソッド
・Delete:Deleteメソッド※CRUD処理の為にHTTPメソッドを実装した後は、ちゃんと動くか、Postmanを用いてテストすると良い。
<参考>『Postmanを使ってHTTPに送ったデータを確認する』(https://qiita.com/Molly95554907/items/e367e83129ea1173c317)CRUDの実装
const express = require('express') require('./db/mongoose') //別ファイル(mongooseの読み込み) const User = require('./models/user') //別ファイル(mongooseによるuserの定義の読み込み) const app = express() //process.env.PORTはherokuで動かす時のポート const port = process.env.PORT || 3000 //HTTPへ送ったデータを情報の読み取れるようにする app.use(express.json())Create
<公式ドキュメント>
・データをデータベースに送るapp.post('/users', (req, res) => { const user = new User(req.body) //リクエストで送ったデータをUserインスタンスに入力 user.save().then(() => { //新しく作成したUserインスタンスをDBに保存 res.status(201).send(user) //保存成功したら、HTTPステータス201と、保存したデータを返す }).catch((e) => { //保存失敗したら、HTTPステータス400とエラーメッセージを返す res.status(400).send(e) }) })<非同期処理(Promise)を用いた実装>
app.post('/users', async (req, res) => { //asyncで処理した関数はPromiseを返す const user = new User(req.body) //リクエストで送ったデータをUserインスタンスに入力 try { //保存成功したら、HTTPステータス201と、保存したデータを返す await user.save() res.status(201).send(user) } catch (e) { res.status(400).send(e) //保存失敗したら、HTTPステータス400とエラーメッセージを返す } })Read
<公式ドキュメント>
・データをデータベースからとってくるapp.get('/users', (req, res) => { User.find({}).then((users) => { //find({})で全てのデータを取得 res.send(users) //取得した全てのデータを返す }).catch((e) => { res.status(500).send() //取得失敗したら、HTTPステータス500を返す }) })<非同期処理(Promise)を用いた実装>
app.get('/users', async (req, res) => { try { const users = await User.find({}) //find({})で全てのデータを取得 res.send(users) //取得した全てのデータを返す } catch (e) { res.status(500).send() //取得失敗したら、HTTPステータス500を返す } })Updaet
・データの更新
<公式ドキュメント>
「API > Model」の項目から探す。
・findById
https://mongoosejs.com/docs/api/model.html#model_Model.findById
・findByIdAndUpdate
https://mongoosejs.com/docs/api/model.html#model_Model.findByIdAndUpdateapp.get('/users/:id', (req, res) => { const _id = req.params.id //「:id」に打ち込んだ値を取得 User.findById(_id).then((user) => { //「:id」に打ち込んだIDと同じドキュメントを探す if (!user) { return res.status(404).send() //「:id」に打ち込んだIDと同じドキュメントがなければ、HTTPステータス404を返す } res.send(user) //「:id」に打ち込んだIDと同じドキュメントが見つかればそれを返す }).catch((e) => { res.status(500).send() //取得失敗したら、HTTPステータス500を返す }) })<非同期処理(Promise)を用いた実装>
app.patch('/users/:id', async (req, res) => { const updates = Object.keys(req.body) //リクエストで送った更新データを取得 const allowedUpdates = ['name', 'email', 'password', 'age'] //updateできる項目を限定する const isValidOperation = updates.every((update) => allowedUpdates.includes(update)) //リクエストで送った更新データがupdateできる項目に当てはまっているか if (!isValidOperation) { return res.status(400).send({ error: 'Invalid updates!' }) //当てはまっていなければ、HTTPステータス404とエラーメッセージを返す } try { const user = await User.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true }) if (!user) { //「:id」に打ち込んだIDと同じドキュメントを探し、なければHTTPステータス404を返す return res.status(404).send() } res.send(user) //「:id」に打ち込んだIDと同じドキュメントが見つかればそれアップデートし返す } catch (e) { res.status(500).send(e) //取得失敗したら、HTTPステータス500を返す } })Delete
・データの削除
<公式ドキュメント>
findByIdAndDelete
https://mongoosejs.com/docs/api/model.html#model_Model.findOneAndDeleteapp.delete('/users/:id', async (req, res) => { try { const user = await User.findByIdAndDelete(req.params.id) //「:id」に打ち込んだIDと同じドキュメントを探し削除 if (!user) { return res.status(404).send() //「:id」に打ち込んだIDと同じドキュメントがなければHTTPステータス404を返す } res.send(user) //「:id」に打ち込んだIDと同じドキュメントが見つかればそれを削除し返す } catch (e) { res.status(500).send() //取得失敗したら、HTTPステータス500を返す } })
- 投稿日:2020-06-21T20:18:57+09:00
WebAssemblyとJavaScriptの最適化による処理速度を比較してみる
はじめに
前回のコメントで「サンプルが簡単すぎてCの最適化が早いのは当然」や「JavaScriptも最適化がある」と言ったことを教えてもらえたので再度比較してみました。
環境
- windows 10
- python:3.6.5
- emcc:1.39.16
- clang:11.0.0
実験ブラウザ
- Google Chrome:83.0.4103.106
純粋なループの比較
前回と同様にそれぞれの中でループをさせてみました。JavaScriptの最適化はprepackとClosure Compilerの2つを使いました。
C/C++ファイルの作成とコンパイル
前回と全く同じなので省略します。
htmlの作成する
前回とほぼ同じなので省略します。
JavaScriptの作成と最適化
最適化用JavaScriptの作成
今回は最適化するためにループ部分だけ別ファイルにします。
jsmod.jsfunction loop_func_ori(){ var count = 0; for(var i = 0; i < 100000000; i++){ count = count + 1; } return count; }最適化ツール(prepack)のインストール
npmを使用してprepackをインストールします。
npm install prepackインストールしたフォルダ内のnode_modulesにprepackが追加されています。
JavaScriptの最適化(prepack)
prepackコマンドを使用して最適化を行います。
>.\node_modules\.bin\prepack jsmod.js --out jsmod-prepa.js Prepacked source code written to jsmod-prepa.js.--outで指定したファイルに最適化後のJavaScriptができます。
jsmod-prepa.jsvar loop_func; (function () { var _$0 = this; var _1 = function () { var count = 0; for (var i = 0; i < 100000000; i++) { count = count + 1; } return count; }; _$0.loop_func = _1; }).call(this);JavaScriptの最適化(Closure Compiler)
Closure Compilerは公式から最適化を行います。
jsmod-clo.jsfunction loop_func_clo(){for(var a=0,b=0;1E8>b;b++)a+=1;return a};最適化しましたが、両方ともあまり処理が短縮されていないように感じます。
実行
サーバを再起動して上のhtmlを実行します。
回数 JavaScript JavaScript prepack最適化あり JavaScript Closure Compiler最適化あり C++最適化なし C++最適化01 C++最適化02 C++最適化03 1 84.914 82.774 82.875 426.709 0.0699 0.0949 0.0499 2 83.609 82.669 82.170 421.660 0.0449 0.0500 0.0550 3 54.994 55.779 55.080 422.435 0.0499 0.0350 0.0499 4 54.495 57.089 55.375 429.959 0.0450 0.2099 0.0599 5 54.455 55.075 55.044 421.464 0.0299 0.0399 0.0500 結果を見ると、予想通りJavaScriptの最適化はあまり早くなっていないことがわかりました。
それ以外は、前回と同様にC++の方が早く、C++は最適化を強くするたびに処理速度が速くなっています。
JavaScriptの最適化はC++の最適化とは異なり、抽象化の解消や関数の事前実行など若干使い方が異なるので早くならなかったのかなと思います。一応以下を最適化すれば処理時間が短くなりそうなのですが、最適化がいつまでたっても終わらないので諦めました。
jsmod2.jsfunction loop_func_ori(){ var count = 0; for(var i = 0; i < 100000000; i++){ count = count + 1; } return count; } var i = loop_func_ori()引数のある関数の比較
C++の引数のある関数をJavaScriptから呼び出した時の速度を試してみました。
C/C++ファイルの作成
引数のあるC++関数をJavaScriptから呼び出す方法は前に書いた独自のC ++関数の連携に引数を追加するだけです。
cmain.cpp#include <emscripten/emscripten.h> extern "C" { int EMSCRIPTEN_KEEPALIVE loop_func(int n) { int count = 0; while(true){ count = count + 1; if (n == 0){ break; } n = n - 1; } return count; } }wasmを呼び出すhtmlを作成する
htmlはほぼ同じなので省略します。
JavaScriptの作成と最適化
基本は先ほどと同じです。今回は関数の処理が変わるため、そこだけ変更しています。
各関数に与えている整数は1000000000にしています。jsmod.jsfunction loop_func_js_ori(n){ var count = 0; while(true){ count = count + 1 if (n == 0){ break; } n = n -1; } return count; }実行
サーバを再起動して上のhtmlを実行します。
回数 JavaScript JavaScript prepack最適化あり JavaScript Closure Compiler最適化あり C++最適化01 C++最適化02 C++最適化03 1 672.230 672.750 672.200 0.0600 0.0549 0.0999 2 669.505 675.559 674.194 0.0450 0.0499 0.0550 3 665.550 669.945 678.404 0.0550 0.0350 0.0450 4 670.449 678.560 698.879 0.0450 0.0550 0.0399 5 672.009 671.985 720.339 0.0550 0.0400 0.0350 結果を見ると単純なループと同じようにC++の方がJavaScriptより速いことがわかりました。これはループでもわかっていたため、単純にそうなんだーぐらいの感想でした。
おわりに
コメントを受けて他のパターンを試してみました。意外!と思えなかったので、個人的にはあまり面白くない結果でした。今回は、JavaScriptの最適化に不利な処理での比較になってしまったためJavaScriptの最適化はあまりすごくないイメージができてしまいましたが、大きなJavaScriptを最適化すれば意味のあるものになると思います。JavaScriptの最適化に有利な方法を思いついたら比較してみようと思います。実験の結果では前回と感想は変わりませんでした。
- 投稿日:2020-06-21T20:04:39+09:00
【Vue】学習開始2週目で覚える内容
2週目で学ぶべきこと
- v-model
- 名前付きslot
- スコープ付きslot
- 動的コンポーネント
- ライフサイクルフック
v-model
- v-modelディレクティブ:
formのinput要素
に対して、データバインディング
を行う際に使用する- データバインディング:データと表示を結びつけ、
双方向
に変更を反映させることApp.vue<template> <div> <!-- sampleオブジェクト内のanswerを参照する --> <input v-model="sample.answer"> <p>{sample.answer}</p> </div> </template> <script> export default { data() { return { //オブジェクト名:sample プロパティ名:answer sample: { answer: "Hello World!" } } } }; </script>名前付きslot
- slot:
親コンポーネント
から子コンポーネント
にテンプレートを差し込む機能◆ 親コンポーネント
App.vue<template> <div> <Child> <!-- 子コンポーネントの"slot name"で参照される --> <template v-slot:sample> <h1>親コンポーネントの表示</h1> </template> <!-- 子コンポーネントの"slot name"で参照される --> <template v-slot:answer> <!-- dataプロパティ参照 --> <p>{{word}}</p> </template> </Child> </div> </template> <script> export default { data() { return { word: "good morning!" } } };◆ 子コンポーネント
Child.vue<template> <div> <!-- 親コンポーネント"v-slot:sample"を参照する --> <slot name="sample"></slot> <hr> <p>Hello World!</p> <hr> <!-- 親コンポーネント"v-slot:answer"を参照する --> <slot name="answer"></slot> </div> </template>スコープ付きslot
子コンポーネントslotに渡されたprops
に、親コンポーネント
からアクセスすること◆ 子コンポーネント
Child.vue<template> <div> <!-- dataプロパティの"word"を、slotに設定する --> <!-- ※sampleは"任意の属性名"を設定する --> <slot name="sample" v-bind:sample="word"></slot> </div> </template> <script> export default { data() { return { word: "good morning!" } } };App.vue<template> <div> <Child> <!-- 子コンポーネントの v-bind sample="word" が参照される --> <!-- "slotProps"は任意の属性名"を設定する --> <template v-slot:sample="slotProps"> <!-- slotPropsは"template内"で使用可能 --> <h1>{{ slotProps }}</h1> </template> </Child> </div> </template>動的コンポーネント
コンポーネント間の切り替え
をスムーズに行う目的で使用する◆ 子コンポーネント
Child.vue<template> <p>Child</p> </template>◆ 親コンポーネント
App.vue<template> <div> <!-- is:"別のコンポーネントを参照する"属性 --> <component v-bind:is="sample"></component> </div> </template> <script> import Child from "./components/Child.vue"; export default { data() { return { //sample:属性名 default:値 sample: "default" }; }, components: { //子コンポーネントを参照する Child }ライフサイクルフック
- activated:生き続けたコンポーネントを
活性化
する際に、参照される- deactivated:生き続けたコンポーネントを
非活性化
する際に、参照される- destroyed:
Vueインスタンスが破棄
された際に、参照される- keep-alive:コンポーネントの内容を
保持したい時
に使用する◆ destroyedメソッド
Destroy.vue<script> export default { destroyed() { //Vueインスタンスが破棄された際に、出力される console.log("Hello World!"); } } </script>◆ keep-alive
Keepalive.vue<template> <div> <keep-alive> <!-- 保持したい"コンポーネント"を"keep-alive"で囲む --> <component v-bind:is="sample"></component> </keep-alive> </div> </template>◆ activated / deactivatedメソッド
Sample.vue<script> export default { activated() { //コンポーネントが"活性化状態"の時に出力される console.log("Hello World!"); }, deactivated() { //コンポーネントが"非活性化状態"の時に出力される console.log("Good morning"); } }; </script>参考文献
- 投稿日:2020-06-21T18:53:45+09:00
Web Crypto APIでJavaScriptによる暗号処理を行う(ECDH, AES)
こんにちわ。
JavaScriptによる暗号アルゴリズムの実装は幾つかありますが、今回はWeb Crypto APIというブラウザのネイティブ実装による暗号化を試したいと思います。
Web Crypto API
ネイティブ実装は実行パフォーマンス面で有利ですが関数や入出力などの"お作法"が複雑、逆にソフトウェア実装はパフォーマンスこそ及びませんが、その"お作法"が上手い具合に取り回しやすくなっている傾向があります。
ゆえに、ネイティブ実装のコーディングは少しややこしく感じますが、紐解いていくと実はそんなに難しい話ではないので、順を追って解説していきます。
はじめに
Web Crypto APIは、各種暗号処理(鍵生成/鍵交換/鍵導出/暗号化/復号/署名/検証...)をJavaScriptで安全に実行するためのAPIです。
Web Crypto APIの全機能は
window.crypto.subtle
オブジェクトに集約されており、いずれの関数もPromise
を返します。ただ
Math.random()
より暗号強度の高い乱数を取得できるgetRandomValues()
だけは例外で、関数はwindow.crypto
オブジェクトに存在し、戻り値はTypedArray
となります。window.crypto.getRandomValues() // => TypedArray window.crypto.subtle.xxx() // => PromiseWeb Crypto APIはいずれの機能も、使用するためのクラスは存在せず静的関数のみで構成され、都度パラメータを入力することで結果を得るかたちとなっています。
鍵オブジェクトは公開鍵/秘密鍵/共通鍵と種類を問わず、全て
CryptoKey
というオブジェクト型で管理されます。
このCryptoKey
は、後述のインポート/エクスポート関数を使用してバイナリデータとして入出力が可能です。Web Crypto APIはWebWorker内で使用可能です。
やること
今回は、楕円曲線暗号による鍵交換(ECDH)と、共通鍵暗号による暗号化(AES)を行います。
Web Crypto APIに実装されている楕円曲線は以下の通りです。
- P-256
- P-384
- P-521
2020年6月時点においては、いずれのブラウザもモンゴメリ曲線(Curve25519)やエドワーズ曲線(Ed25519)といった楕円曲線には対応していません。
Web Crypto APIに実装されている共通鍵暗号アルゴリズムは以下の通りです。
- AES-CTR
- AES-CBC
- AES-GCM
これらの実装状況から、ECDHはP-384を、AESはGCMを使用して暗号処理を実装していきます。
手順としては、以下のような流れになります。
楕円曲線による公開鍵と秘密鍵の生成
鍵生成function keyGen(){ const ec = { name: "ECDH", namedCurve: "P-384" }; const usage = ["deriveKey"]; return crypto.subtle.generateKey(ec, true, usage); }
generateKey()
で、公開鍵と秘密鍵の鍵ペアを生成します。戻り値は
publicKey
プロパティとprivateKey
プロパティにそれぞれCryptoKey
が入ったCryptoKeyPair
というオブジェクトになります。
ec
で、使用する暗号アルゴリズムと楕円曲線を指定します。
uaage
で、鍵の用途を指定します。
この鍵は鍵交換にのみ使うのでderiveKey
を設定します。第2引数のbooleanは、鍵のエクスポートを許可するかを指定します。
公開鍵は相手へ送るためにエクスポートしたいのでtrue
を設定します。公開鍵/秘密鍵のエクスポート
エクスポートasync function keyExport(key, isPub){ const encode = isPub ? "spki" : "pkcs8"; return new Uint8Array(await crypto.subtle.exportKey(encode, key)); }
exportKey()
で、CryptoKey
を各フォーマットのバイナリデータとして出力できます。戻り値は
ArrayBuffer
です。楕円曲線暗号の場合、秘密鍵はPKCS#8、公開鍵はSPKIでの出力に対応しています。
公開鍵/秘密鍵のインポート
インポートfunction keyImport(key, isPub){ const encode = isPub ? "spki" : "pkcs8"; const ec = { name: "ECDH", namedCurve: "P-384" }; const usage = isPub ? [] : ["deriveKey"]; return crypto.subtle.importKey(encode, key, ec, false, usage); }
importKey()
で、バイナリデータとして出力した鍵をCryptoKey
に戻せます。戻り値は
CryptoKey
です。使用する暗号アルゴリズム/楕円曲線/用途とエクスポート時のフォーマットは、それぞれ鍵生成/エクスポート時と同一の必要があります。
第4引数のbooleanはエクスポート許可ですが、一度インポートした鍵を再エクスポートするケースは考えにくいので
false
とします。ECDHによる共通鍵の導出
共通鍵導出function keyDerive(pub, priv){ const aes = { name: "AES-GCM", length: 256 }; const ec = { name: "ECDH", public: pub }; const usage = ["encrypt", "decrypt"]; return crypto.subtle.deriveKey(ec, priv, aes, false, usage); }
deriveKey()
で、公開鍵と秘密鍵を使用して共通鍵を導出します。
aes
で、導出した鍵を使用する暗号アルゴリズムを指定します。
ここでのlength
プロパティはAESの鍵長(bit数)となります。
ec
で、鍵導出を行う暗号アルゴリズムと公開鍵を指定します。
usage
は、導出した鍵を暗号化と復号に使うのでencrypt
とdecrypt
を設定します。第4引数のbooleanはエクスポート許可ですが、共通鍵がエクスポート出来てしまっては、せっかく安全性を保つために公開鍵で鍵交換した意味が台無しなので
false
とします。AESによるデータの暗号化
暗号化async function aesEncrypt(key, data){ const aes = { name: "AES-GCM", iv: crypto.getRandomValues(new Uint8Array(16)), tagLength: 128 }; const result = await crypto.subtle.encrypt(aes, key, data); const buffer = new Uint8Array(aes.iv.byteLength + result.byteLength); buffer.set(aes.iv, 0); buffer.set(new Uint8Array(result), aes.iv.byteLength); return buffer; }
encrypt()
でデータを暗号化します。入力データは
TypedArray
となります。
戻り値(暗号データ)はArrayBuffer
となります。初期ベクトル(IV)は
getRandomValues()
で16byteの乱数を取得します。
IVは公開しても問題ない代わりに、復号時も同じものが必要となるので、処理結果の暗号データの先頭に結合しています。AESによるデータの復号
復号async function aesDecrypt(key, data){ const aes = { name: "AES-GCM", iv: data.subarray(0, 16), tagLength: 128 }; return new Uint8Array(await crypto.subtle.decrypt(aes, key, data.subarray(16))); }
decrypt()
で、暗号データを復号します。入力データは
TypedArray
となります。
戻り値はArrayBuffer
となります。IVは暗号化時に暗号データの先頭に結合したので、今度は先頭16byteを
subarray()
で切り分けます。17byte以降が暗号データ本体なので、これも
subarray()
で切り分けます。試してみる
上記のラッパー関数を用いて実際に暗号化/復号を試してみます。
(async()=>{ const key1 = await keyGen(); const key2 = await keyGen(); const key1ExPub = await keyExport(key1.publicKey, true); const key1ExPriv = await keyExport(key1.privateKey, false); const key2ExPub = await keyExport(key2.publicKey, true); const key2ExPriv = await keyExport(key2.privateKey, false); console.log("--- Exported Keys ---"); for(const key of [key1ExPub, key1ExPriv, key2ExPub, key2ExPriv]){ console.log(key); } const key1ImPub = await keyImport(key1ExPub, true); const key1ImPriv = await keyImport(key1ExPriv, false); const key2ImPub = await keyImport(key2ExPub, true); const key2ImPriv = await keyImport(key2ExPriv, false); console.log("--- Imported Keys ---"); for(const key of [key1ImPub, key1ImPriv, key2ImPub, key2ImPriv]){ console.log(key); } const keyDe1Pub2Priv = await keyDerive(key1ImPub, key2ImPriv); const keyDe2Pub1Priv = await keyDerive(key2ImPub, key1ImPriv); console.log("--- Derived Keys ---"); for(const key of [keyDe1Pub2Priv, keyDe2Pub1Priv]){ console.log(key); } const raw = new TextEncoder().encode("hogehoge"); const enc = await aesEncrypt(keyDe1Pub2Priv, raw); const dec = await aesDecrypt(keyDe2Pub1Priv, enc); console.log("--- Results ---"); console.log(new TextDecoder().decode(raw)); console.log(enc); console.log(new TextDecoder().decode(dec)); })();無事に暗号化と復号が出来てると思います。
プレイグラウンド
(番外編)気になったこと
今回の記事とはあまり関係ないのですが、Web Crypto APIはCurve25519とEd25519には対応していないんだなぁと思って色々調べてるうちに疑問に思った事が...
楕円曲線 鍵交換(ECDH) 署名(ECDSA) モンゴメリ曲線(Curve25519) X25519 ??? エドワーズ曲線(Ed25519) ??? EdDSA モンゴメリ曲線を使用した鍵交換はX25519という仕様があり、エドワーズ曲線を使用した署名はEdDSAという仕様があります。
この2つの曲線は双有理同値という、超ざっくり解釈で"対"となる存在との事です。
(ここら辺は詳しくないので深追い言及は避けておきます)そして、上記テーブルの "???" の部分が問題で、以前 elliptic という楕円曲線暗号ライブラリを用いて鍵交換を実装した時に、たまたま間違えてEd25519鍵をECDH関数に入力してしまったら、実際に共通鍵を導出できてしまいました。
これは、仕様化されていないが対となる曲線だから出来た必然なのか、それともライブラリのバグなのか...
また、同じ理論でCurve25519を使用した署名も可能なのかも気になるところです。有識者の方がいらっしゃいましたら、ご教示お願い致します。
- 投稿日:2020-06-21T18:38:58+09:00
JavaScript for Automation (JXA)
java scriptでMacを自動化できる
アプリケーションの起動
function run(){ var app = Application("apps"); }引数を渡してiTuneで曲を再生
function run(argv){ if(argv.length == 2){ console.log(argv[0] + " : " + argv[1] + " を再生します"); var app = Application("iTunes"); app.playlists[argv[0]].tracks[argv[1]].play(); } else{ console.log("'プレイリスト' '曲名' を指定してください"); } }scriptの実行
実行
osascript -l JavaScript sample.js引数を渡して実行
osascript -l JavaScript sample.js testPlay hoge
- 投稿日:2020-06-21T18:03:40+09:00
javascript 数字の並び替えゲーム(自分なりの解説)
今回は、書籍ゲームで学ぶJavascript入門で紹介されている
15puzzle知識定着のために自分なりに解説をしてみます。15puzzleは数字を順番に並び替えていくゲームです。
(これは画像です)
15puzzle.html<body onload="init()"> <table id="table"></table> </body> </html>まず読み込んだら初期化のinit()が起動します。
body onload ="init()"の所です。15puzzle.html<body onload="init()"> <table id="table"></table> </body> </html>初期化(数タイルを表示させる)
初期化ですることは、
1~15までの数字を順番に表示をさせ、その後、順番をバラバラに表示させます。
このようなhtmlを出力できたらOKです。sample.html<table id="table"> <tr> <td class="tile">1</td> <td class="tile">2</td> <td class="tile">3</td> <td class="tile">4</td> </tr> <tr> <td class="tile">5</td> <td class="tile">6</td> <td class="tile">7</td> <td class="tile">8</td> </tr> <tr> <td class="tile">9</td> <td class="tile">10</td> <td class="tile">11</td> <td class="tile">12</td> </tr> <tr> <td class="tile">13</td> <td class="tile">14</td> <td class="tile">15</td> <td class="tile">16</td> </tr> </table>これをjsで段階的に書いてみると
var tr = document.createElement("tr")
~tdのfor処理4回分
最後にtrの閉じタグをつけるtr-repeat.jsvar table = docment.getElementById("table"); var tr = document.createElement("tr"); for (var j=0;j<4;j++){ var td = document.createElement("td"); td.textContent = j; td.className ="tile"; tr.appendChild(td); //trの最後の所にtdを追加。閉じタグにて } table.appendChild(tr);これだと0~3までしかできないので
trが4回分をつなげるとこうなります。tr-repeat.jsvar table = document.getElementById("table"); //子のtrが4回分 for (var i = 0;i<4;i++){ var tr = document.createElement("tr"); //孫のtdが4回分 for (var j=0;j<4;j++){ var td = document.createElement("td"); td.textContent = i*4+j;//0-15まで td.className ="tile"; tr.appendChild(td); //trの最後の所にtdを追加。閉じタグにて } //子の最後にtrの閉じタグを入れる table.appendChild(tr); }見た目的にこれでOKですが
パズル動作をするために取得した要素のプロパティを入れておく必要があります。
また値が0の所は、空白にしてパズルの入れ替えポイントにします。
また数字の入れ替え処理ができるようにclick関数を、onclickに設定します。
click関数は後ほど出ます。tr-repeat.jsvar table = document.getElementById("table"); //子のtrが4回分 for (var i = 0;i<4;i++){ var tr = document.createElement("tr"); //孫のtdが4回分 for (var j=0;j<4;j++){ var td = document.createElement("td"); td.className ="tile"; //値をindexとして var index = i*4+j; td.textContent = index; td.index = index; td.value = index; td.textContent = index == 0 ? "" : index; //三項演算子 td.onclick = click; //onclickにclick関数を設定 tr.appendChild(td); //trの最後の所にtdを追加。閉じタグにて } //子の最後にtrの閉じタグを入れる table.appendChild(tr); }三項演算子はif文の省略した書き方です。
index が0なら、""を。 0じゃないならindexを
「index == 0 ? "" :index;」その結果を
td.textContentに「代入=」しています。まだ未完成ですが並び替えを実行するための
click関数を考えます。click関数は並び替えを行いますが、
並びがどうなってるかjavascript側であらかじめ覚えてくれたほうが楽です。
click関数を呼び出したときに、「えーと、現在の並びがこうだから。。。」と取得してると効率が悪いです。(googleのスプレットシートとGASの処理で、何度もスプレットシートの値を取得するのでなく、
スプレット全体を配列で一旦全部記憶させて、配列からデータを探させるのと同じ考え方です。)15puzzle.js// グローバル変数(並びを入れとくやつ) var tiles = []; // 初期化関数 function init() { var table = document.getElementById("table"); for (var i = 0 ; i < 4 ; i++) { var tr = document.createElement("tr"); for (var j = 0 ; j < 4 ; j++) { var td = document.createElement("td"); td.className = "tile"; var index = i * 4 + j; td.index = index; td.value = index; td.textContent = index == 0 ? "" : index; td.onclick = click; tr.appendChild(td); //並びを覚えさせる tiles.push(td); } table.appendChild(tr); } }これでやっと並び替えclick処理を考えられます。
クリック処理
クリックをした時の処理は
「押した数字が隣が空白で、押した数字が空白に移動する」です。
移動の仕方は上下左右なので4パターンとなります。
後ほど記述するswap関数には、入れ替えの場所を引数で渡します。その4パターンのうち、どの処理を行うか選別してるようにします。
画像のようにindexの一般化から値を様子を見ます。click.jsfunction click(e) { //クリックした場所を取得 var i = e.srcElement.index; if (tiles[i - 4].value == 0) { swap(i, i - 4);//上移動の処理 } else if (tiles[i + 4].value == 0) { swap(i, i + 4);//下移動 } else if (tiles[i - 1].value == 0) { swap(i, i - 1);//左移動 } else if (tiles[i + 1].value == 0) { swap(i, i + 1);//右移動 } }これで良さそうに見えますが
i=1のときに、tiles[-3]の値を見ようとします。そんな値はないのでエラーが出ちゃいます。
なので、
上移動なら、まずクリックされたものが一番上でないこと。
下移動なら、まずクリックされたものが一番下でないこと。とする必要があります。click.jsfunction click(e) { var i = e.srcElement.index; if (i - 4 >= 0 && tiles[i - 4].value == 0) {//最上位ではなく、上の値が0なら上移動 swap(i, i - 4); } else if (i + 4 < 16 && tiles[i + 4].value == 0) { swap(i, i + 4); } else if (i % 4 != 0 && tiles[i - 1].value == 0) { swap(i, i - 1); } else if (i % 4 != 3 && tiles[i + 1].value == 0) { swap(i, i + 1); } }並び替えswap関数
並び替える場所を引数に渡せば、並び替えをする関数です。
textContentとvalueを入れ替えています。swap.jsfunction swap(i, j) { //一旦格納する var tmp = tiles[i].value; tiles[i].textContent = tiles[j].textContent; tiles[i].value = tiles[j].value; tiles[j].textContent = tmp; tiles[j].value = tmp; }バラバラに並び替える
ここまでのプログラムですと、順番に数字が並ぶだけです。
一応ゲームのため、あらかじめバラバラに並び替えておく必要があります。
今回はclickしたら、並び替えswapが発動するのでそれを使います。
適当なところをランダムに押すプログラムを置きます。random.jsfor(var i = 0 ;i<1000;i++){ click({srcElement:{index:Math.floor(Math.random()*16)}}) }0-15のランダムにとり、その位置のsrcElementをクリックされます。
click→swapまでたまたま行ったもので、並び替えが行われます。最後に
全体図はサンプルコードの配布になってしまうので控えます。
詳しくはこちらの書籍を購入お願いします。
- 投稿日:2020-06-21T17:17:15+09:00
Ajaxを使った非同期通信化実装STEP
1.jQueryを記述するFile作成
- app/assets/javascripts内にcomment.js(非同期通信を行うcontroller名.js)を作成。
2.イベントの発火を設定
- pictweetのcomment-formが送信された時、のイベント設定
comment.js$(function(){ $('#new_comment').on('submit', function(e) { e.preventDefault(); var formData = new FormData(this); var url = $(this).attr('action') $.ajax({ url: url, type: 'POST', data: formData, dataType: 'json', processData: false, contentType: false }) }) })
- フォームが送信された時、というイベントを設定したい場合は、form要素を取得してonメソッドを使う。
- フォームが送信される時、何も設定していない状態(デフォルトの状態)だとフォームを送信するための通信が行われるため、preventDefault()を使用してデフォルトのイベントを止めます。
- FormData
*new FormData(フォーム要素)とすることでFormDataを作成できます。 今回FormDataオブジェクトの引数はthisとなっていますが、イベントで設定したfunction内でthisを利用した場合は、イベントが発生したノード要素を指します。今回の場合は、new_commentというIDがついたフォームの情報を取得しています。
3.非同期通信で保存する
comment.js$(function(){ $('#new_comment').on('submit', function(e) { e.preventDefault(); var formData = new FormData(this); var url = $(this).attr('action') $.ajax({ url: url, type: 'POST', data: formData, dataType: 'json', processData: false, contentType: false }) }) })
- attrメソッド:要素が持つ指定属性の値を返します。今回はイベントが発生した要素のaction属性の値を取得しており、今回のaction属性にはフォームの送信先のurlの値が入っています。 これでリクエストを送信する先のURLを定義することができました。
- processDataオプション: デフォルトではtrueになっており、dataに指定したオブジェクトをクエリ文字列に変換する。
- contentTypeオプション: サーバにデータのファイル形式を伝えるヘッダ。こちらはデフォルトでは「text/xml」でコンテンツタイプをXMLとして返してきます。 ajaxのリクエストがFormDataのときはどちらの値も適切な状態で送ることが可能なため、falseにすることで設定が上書きされることを防ぎます。
4.コメントを保存し、respond_toを使用してHTMLとJSONの場合で処理を分ける
comments_controller.rbdef create @comment = Comment.create(comment_params) respond_to do |format| format.html { redirect_to tweet_path(params[:tweet_id]) } format.json end end
- ローカル変数commentは、スコープの関係でこの後のjbuilder側で使用できないので、インスタンス変数@commentに編集します
5. jbuilderを使用して、作成したメッセージをJSON形式で返す
views/comments/create.json.jbuilderjson.text @comment.text json.user_id @comment.user.id json.user_name @comment.user.nicknameこうすることによってJavaScriptファイルに返ってきたデータをjbuilderで定義したキーとバリューの形で呼び出して使うことができます。
6.返ってきたJSONを
done
メソッドで受取り、HTMLを作成するcomment.js$(function(){ function buildHTML(comment){ var html = `<p> <strong> <a href=/users/${comment.user_id}>${comment.user_name}</a> : </strong> ${comment.text} </p>` return html; } $('#new_comment').on('submit', function(e){ e.preventDefault(); var formData = new FormData(this); var url = $(this).attr('action'); $.ajax({ url: url, type: "POST", data: formData, dataType: 'json', processData: false, contentType: false }) .done(function(data){ var html = buildHTML(data); $('.comments').append(html); $('.textbox').val(''); $('.form__submit').prop('disabled', false); }) }) });
$('.form__submit').prop('disabled', false);
は、htmlの仕様でsubmitボタンを一度押したらdisabled属性という、ボタンが押せなくなる属性が追加されいるので、false
で解除。7.エラー時の処理を行う
comment.js.done(function(data){ var html = buildHTML(data); $('.comments').append(html); $('.textbox').val(''); $('.form__submit').prop('disabled', false); }) .fail(function(){ alert('error'); })
- 投稿日:2020-06-21T17:06:59+09:00
環境を汚さず(選ばず)Nuxtプロジェクトを作成しGitHub Pagesで公開するまでの一部始終
事前準備
docker がインストールされていることが前提です。
docker が入ってさえいれば Windows, Mac, Linux いずれでも同じように操作できるはずです。WindowsにDockerを導入する方法は以下にまとめています。
また、プログラムの編集にはVisualStudioCodeを使いますのでインストールしておいてください。
GitHubからクローンしたりプッシュしたりするのでGitHubのアカウントを用意し git もインストールしておいてください。
プロジェクト作成
プロジェクトを作る親フォルダに移動
(Windows の場合は例えば C:\dev を作ってコマンドプロンプトでcd \dev
で親フォルダとする dev に移動します。)
node.jsが入ったLinuxを起動するため以下のコマンドを実行します。
windowsdocker run --rm -itv %cd%:/app node:alpine shmac,linuxdocker run --rm -itv $PWD:/app node:alpine shdocker run は指定したイメージ(node:alpnine)からコンテナを作り、指定したコマンド(sh)を実行します。
--rm オプションは実行後にコンテナを削除します。これをつけないと docker run するごとにコンテナが増えていきディスクを圧迫します。
今回は作成したファイルはローカルに同期して残りますので、作業時以外コンテナは不要です。
-it は起動したコンテナに対して入力できるようにするためのオプションです。指定しないとコマンドが即終了してしまいます。
-v でローカルの$PWD or %cd%(カレントディレクトリ)とコンテナ内の /app を同期(ファイルやフォルダの内容がおなじになる)します。
コマンドが実行されると、Dockerコンテナ内のシェルに切り替わります。
Dockerコンテナ内/ #
/ は現在ルートディレクトリにいるということ、
# はルートユーザを表しています。
node:alphine イメージを使っているのでnode環境は既にあります。
以下のコマンドで同期している /app フォルダに移動して first_nuxt という名前のプロジェクトを作成します。Nuxtプロジェクト作成/ # cd /app && yarn create nuxt-app first_nuxt
yarn create next-app としてしまうとReactベースの Next.js のプロジェクトになってしまいますので間違わないように注意しましょう。
実行すると以下のように表示されます。
プロジェクト名入力yarn create v1.22.4 [1/4] Resolving packages... warning create-nuxt-app > sao > micromatch > snapdragon > source-map-resolve > resolve-url@0.2.1: https://github.com/lydell/resolve-url#deprecated warning create-nuxt-app > sao > micromatch > snapdragon > source-map-resolve > urix@0.1.0: Please see https://github.com/lydell/urix#deprecated [2/4] Fetching packages... [3/4] Linking dependencies... [4/4] Building fresh packages... success Installed "create-nuxt-app@3.0.0" with binaries: - create-nuxt-app create-nuxt-app v3.0.0 ✨ Generating Nuxt.js project in first_nuxt ? Project name (first_nuxt)プロジェクト名を聞かれていますが、コマンド実行時に指定している first_nuxt でいいので、そのまま Enter を押します。
言語選択? Choose programming language (Use arrow keys) ❯ JavaScript TypeScript使うプログラミング言語を聞かれています。今回は JavaScript にしますのでそのまま Enter を押します。
パッケージ管理選択? Choose the package manager (Use arrow keys) ❯ Yarn Npm使うパッケージ管理を聞かれています。今回は最初に Yarn を使っていますので、そのまま Enter を押します。
UIフレームワーク選択? Choose UI framework (Use arrow keys) ❯ None Ant Design Vue Bootstrap Vue Buefy Bulma Element Framevuerk iView Tachyons Tailwind CSS Vuesax Vuetify.js使う UIフレームワークを聞かれています。今回は自分でUIを作るので None(なし)にします。そのまま Enter を押します。
この辺りからはプロジェクト作成後に追加することもできますので、よくわからなければ None で大丈夫です。
モジュール選択? Choose Nuxt.js modules (Press <space> to select, <a> to toggle all, <i> to invert selection) ❯◯ Axios ◯ Progressive Web App (PWA) Support ◯ Content使うモジュールを聞かれています。
Axios はWebAPIを呼ぶときに使うものです。
PWA Support は Webサービスをスマートフォンのアプリのように使うようにするためのしくみです。
Content はブログ作成のためのモジュールです。
上下矢印キーで選択し、スペースキーを押すと選択/未選択を切り替えられます。
必要なら後から追加できるので、今回はそのまま Enter を押します。
チェックツール選択? Choose linting tools (Press <space> to select, <a> to toggle all, <i> to invert selection) ❯◯ ESLint ◯ Prettier ◯ Lint staged files ◯ StyleLint使うチェックツールを聞かれています。
今回はVSCodeの拡張機能でチェック等を行うので、そのまま Enter を押します。
テストツール選択? Choose test framework (Use arrow keys) ❯ None Jest AVA WebdriverIO使うテストフレームワークを聞かれています。
後から使いできるのでそのまま、Enter を押します。
レンダリング方式選択❯ Universal (SSR / Static) Single Page App使うレンダリング方式を聞かれています。
SSR(Server Side Rendering)というのはサーバサーイドでHTMLを組み立ててブラウザに返す方式です。
Single Page App(SPA)はブラウザ側でページを組み立てる方式です。
今回は静的ファイルを生成して GitHub Pages に置くのでどちらでもいけますが、そのまま Enter を押します。
開発ツール選択? Choose development tools (Press <space> to select, <a> to toggle all, <i> to invert selection) ❯◯ jsconfig.json (Recommended for VS Code) ◯ Semantic Pull Requests使う開発ツールを聞かれています。
今回はVSCodeを使うので、 jsconfig.json をスペースで選択して Enter を押します。
質問はこれで終わりです。しばらく待っていれば以下のような完了表示が出ます。
Nuxtプロジェクト作成完了? Choose development tools jsconfig.json (Recommended for VS Code) ? Successfully created project first_nuxt To get started: cd first_nuxt yarn dev To build & start for production: cd first_nuxt yarn build yarn start Done in 2842.73s.ls で生成されたフォルダ、ファイルを確認してみましょう。
生成されたファイル確認/app # ls first_nuxt
ローカルの first_nuxt フォルダにコンテナの first_nuxt 内と同様下記のファイルが作成されていることが確認できます。
生成されたファイル一覧README.md components layouts node_modules package.json plugins store assets jsconfig.json middleware nuxt.config.js pages static yarn.lockこれでプロジェクト作成は完了です。
開発モードで起動してみましょう。
まず、プロジェクトフォルダに移動します。
プロジェクトフォルダへ移動/app # cd first_nuxt
yarn dev コマンドを実行します。
開発サーバ起動/app/first_nuxt # yarn dev yarn run v1.22.4 $ nuxt ℹ NuxtJS collects completely anonymous data about usage. 12:06:56 This will help us improving Nuxt developer experience over the time. Read more on https://git.io/nuxt-telemetry ? Are you interested in participation? (Y/n)使用状況の匿名データ収集をしてもいいか聞かれますので、OKならY、NGならnを入力し、Enterを押します。
╭───────────────────────────────────────╮ │ │ │ Nuxt.js @ v2.13.0 │ │ │ │ ▸ Environment: development │ │ ▸ Rendering: server-side │ │ ▸ Target: server │ │ │ │ Listening: http://localhost:3000/ │ │ │ ╰───────────────────────────────────────╯ ℹ Preparing project for development 12:11:17 ℹ Initial build may take a while 12:11:17 ✔ Builder initialized 12:11:17 ✔ Nuxt files generated 12:11:17 ✔ Client Compiled successfully in 10.22s ✔ Server Compiled successfully in 10.14s ℹ Waiting for file changes 12:11:33 ℹ Memory usage: 125 MB (RSS: 211 MB) 12:11:33 ℹ Listening on: http://localhost:3000/ 12:11:33Listening on の行が表示されれば起動完了です。
コンテナ内の3000番ポートで動作しています。
ブラウザでの動作確認
起動した画面をブラウザで確認したいですよね。
でも今の状態だと、ブラウザで http://localhost:3000 にアクセスしても何も表示されません。
なぜかというと、コンテナ内の 3000 ポートは何も設定しないとローカル(PC)からアクセスできないようになっているからです。
ですので、いったんコンテナを終了して、設定を追加してもう一度コンテナを起動します。
Ctrl+Cを押して開発サーバを終了させて、 exit コマンドでコンテナから抜けます。/app/first_nuxt # exit
まず、作成したプロジェクトフォルダに移動します。
共通cd first_nuxt
以下のコマンドで開発サーバを起動します。
windowsdocker run --rm -itv %cd%:/app -p 80:3000 -e "HOST=0.0.0.0" -e "NUXT_TELEMETRY_DISABLED=1" -w /app node:alpine yarn devmax,linuxdocker run --rm -itv $PWD:/app -p 80:3000 -e "HOST=0.0.0.0" -e "NUXT_TELEMETRY_DISABLED=1" -w /app node:alpine yarn devいくつかオプションが増えていますが、意味は次の通りです。
-v %cd%:/app は先程と同じ記述ですが、first_nuxtがカレントフォルダなので、first_nuxt とコンテナ内の /app が同期するようなります。
-p 80:3000 とするとコンテナの3000番ポートがローカルの80番ポートにつながります。
-e "HOST=0.0.0.0" でコンテナの環境変数 HOST に 0.0.0.0 が設定され開発サーバがコンテナ以外のホスト(今回はローカル)からつながるようになります。
-e "NUXT_TELEMETRY_DISABLED=1" で使用状況の匿名データ収集について聞いてこなくなります。
-w でコンテナのカレントディレクトリをプロジェクトのディレクトリに設定しています。
sh を yarn dev に変えていきなり開発サーバを起動するようにします。これで、ブラウザを開いて http://localhost と入れればコンテナ内に起動したページを見ることができます。
ソース編集と自動更新の設定
これで開発できる状態になりました。
さっそく、表示されたトップページを変更してみます。first_nuxt フォルダを VisualStudioCode で開きます。
Windowsならフォルダ右クリックで Code で開くを選択します。
(VisualStudioCodeを起動してからフォルダを開くでもOKです)
トップページの編集
pages フォルダ内に index.vue というファイルがあります。
これがトップページの内容になります。<template> タグ内が表示内容を構成するHTMLです。(通常のHTMLだけではなくVueの記法が使えるようになっています)
<h1>タグ内にタイトルの文字 first_nuxt があるので、
「はじめてのNuxt」に変えてみましょう。
(ファイルを編集するとファイル名タブに●がつきます。保存すると消えます。)
変更の反映
WindowsならCtrl+S、MacならCommand+Sでファイルが保存できます。
保存すると、おそらくMacやLinux環境ならすぐにブラウザの内容が自動更新され文字が変わります。
Windows環境やうまく自動更新されない場合は、次の設定をしてみてください。
(Windowsで)自動更新が効かない場合の設定
nuxt.config.js ファイルを開き、末尾に以下の記述を追加して保存します。
変更前build: { } }変更後build: { }, watchers: { webpack: { poll: true } } }そしていったん docker コンテナから抜けて(CTRL+C)以下のコマンドで再度開発環境を起動します。
windowsdocker run --rm -itv %cd%:/app -p 80:3000 -e "HOST=0.0.0.0" -e "NUXT_TELEMETRY_DISABLED=1" -w /app node:alpine yarn devmax,linuxdocker run --rm -itv $PWD:/app -p 80:3000 -e "HOST=0.0.0.0" -e "NUXT_TELEMETRY_DISABLED=1" -w /app node:alpine yarn devこれでソースを変更して保存するとすぐにブラウザの表示内容が切り替わるようになります。
試しに、タイトルを「Nuxtはじめました」に変えてみましょう。
拡張機能を入れる
vueファイルを見やすくしたり、ミスを教えてくれたりする拡張機能を入れておきます。
検索欄に vetur と入れて Vetur という拡張機能を探します。
見つけたら install ボタンを押しまてインストールします。インストールできると以下のようにに内容が色分けして表示されます(シンタックスハイライトといいます)
余分なポートの通信を止める
ブラウザでF12を押し開発者ツールを出します。
そうすると、以下のようにエラーが出ているのが確認できると思います。
エラーを見ると localhost の 40791 ポートにアクセスしようとしてエラーになっています。
(このポート番号はプロジェクトごとにランダムで変わります。)
このポートも -p 80:3000 としたように Docker 内のポートにつなげないと通信できません。これは、Nuxtの自動ロード時に進捗率を表示するために使われているようです。
https://ja.nuxtjs.org/api/configuration-build/#indicator
エラーは出てほしくないのでこの設定をOFFにします。nuxt.config.js ファイルを開き、以下のように変更して保存します。
変更前build: { },変更後build: { indicator: false },もしくは、ONのままにしたい場合は、docker run 実行時に -p 40791:40791 のようにポートの通信を通すようにします。
github pages に公開する
まだトップページしかありませんが、動作はするので、これを Github pages にデプロイしてみましょう。
静的ファイルの生成
docker コンテナを一度終了して、以下のコマンドを実行します。
windowsdocker run --rm -itv %cd%:/app -e "NUXT_TELEMETRY_DISABLED=1" -w /app node:alpine yarn generatemax,linuxdocker run --rm -itv $PWD:/app -e "NUXT_TELEMETRY_DISABLED=1" -w /app node:alpine yarn generate実行結果yarn run v1.22.4 $ nuxt generate ℹ NuxtJS collects completely anonymous data about usage. 07:35:21 This will help us improving Nuxt developer experience over the time. Read more on https://git.io/nuxt-telemetry ? Are you interested in participation? No ℹ Production build 07:35:42 ℹ Bundling for server and client side 07:35:42 ℹ Target: static 07:35:42 ✔ Builder initialized 07:35:42 ✔ Nuxt files generated 07:35:42 ✔ Client Compiled successfully in 20.32s ✔ Server Compiled successfully in 601.69ms Hash: f6c77d51cae28b7be227 Version: webpack 4.43.0 Time: 20320ms Built at: 06/20/2020 7:36:19 AM Asset Size Chunks Chunk Names ../server/client.manifest.json 6.68 KiB [emitted] LICENSES 389 bytes [emitted] app.656ba39.js 51.3 KiB 0 [emitted] [immutable] app commons.app.4dd2efa.js 154 KiB 1 [emitted] [immutable] commons.app pages/index.573dd1d.js 2.85 KiB 2 [emitted] [immutable] pages/index runtime.b17d028.js 2.32 KiB 3 [emitted] [immutable] runtime + 2 hidden assets Entrypoint app = runtime.b17d028.js commons.app.4dd2efa.js app.656ba39.js Hash: 28f709d14cc9b922eefa Version: webpack 4.43.0 Time: 603ms Built at: 06/20/2020 7:36:19 AM Asset Size Chunks Chunk Names pages/index.js 11.9 KiB 1 [emitted] pages/index server.js 80.5 KiB 0 [emitted] app server.manifest.json 207 bytes [emitted] + 2 hidden assets Entrypoint app = server.js server.js.map ℹ Generating output directory: dist/ 07:36:19 ℹ Generating pages 07:36:19 ✔ Generated route "/" 07:36:19 ✔ Client-side fallback created: 200.html 07:36:20 Done in 72.08s.これらのファイルをレンタルサーバ等に置けばインターネット上に公開できます。
今回はこのファイルを GitHub Pages で公開します。
GitHub には GitHub Actions というコマンド実行機能がありますので、それを使って、push 時今の生成操作を自動実行して公開できるように設定していきます。
GitHub Actions 用ファイルの作成
FIRST_NUXT フォルダ直下に .github/workflows フォルダを作成します。
具体的には、nuxt.config.jsなど直下のファイルを選択した状態で
フォルダ作成ボタンを押します。
出てきたフォルダ名入力欄に .github/workflows を入力します。(先頭のピリオドを忘れないでください)
作成したworkflowフォルダを選択した状態で、ファイル作成ボタン
を押して、gh-pages.yml ファイルを作成します。
.github/workflows/gh-pages.ymlname: github pages on: push: branches: - master jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 - run: yarn generate - uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./dist
GitHubリポジトリの作成
下記の記事にGitHubのアカウント作成からプッシュするまでをまとめていますので参考にして、
リポジトリ作成まで(「ローカルにクローンする」の手前まで)進めてください。
ローカルのGit管理開始
ローカルで Git の初期化を行います。
first_nuxt フォルダ上で git init コマンドを実行します。
git初期化git init初期化したあと、VisualStudioCodeのソース管理
を見ると、
gitでまだコミットされていないファイルが列挙されてきます。
GitHubリポジトリをリモートリポジトリとしてセット
ローカルのgitリポジトリとGitHubのリポジトリを関連付けます。
リモートリポジトリ設定git remote add origin https://github.com/GitHubアカウント名/リポジトリ名.git※GitHubアカウント名、リポジトリ名はご自身で作成したものを指定します。
登録されたかどうかは
git remote -v
で確認できます。
コミットする
VisualStudioCodeでソース管理の上部分にコミットコメントを入力します。
今回は「初コミット」としています。
ステージされていないファイルをコミットするかの確認が出ます。全ファイルコミットするので Yes を押します。
以下の画面が出る場合はCancelボタンを押して、git config コマンドで名前とメールアドレスを設定してから再度コミットしてください。
名前、メールアドレス設定git config user.name あなたの名前 git config user.email GitHubに登録したメールアドレス
GitHub に push
2回目以降はPush先が記録されるので Push to ではなく Push で実行可能です。
GitHubにログインしていない場合は以下の画面が出ます。ログインします。
上向きの矢印が出ない状態になっていればPushされています。
動作確認
Pushすると GitHub Actions が実行され GitHub Pages にページが公開されているはずです。
公開先URLは以下の通りです。
https://GitHubのユーザ名.github.io/プロジェクト名(今回はfirst_nuxt)/例えば私が作ったURLは
https://github-japanese-user.github.io/first_nuxt/
になります。
もし、404エラーが表示される場合は正しくHTMLが登録されている確認します。
GitHub Pages に公開されているかを確認する
GitHubのリポジトリページに行き、Branchボタンを押して、gh-pages を選択します。
gh-pages というのが生成されたHTMLが格納されているブランチになります。gh-pages のファイル内に index.html があれば生成は成功しています。
次に、設定を確認します。
設定画面の下を見ていくと、GitHub Pages の設定部分があります。
ここの Source が gh-pages branch になっているか確認します。
また、緑で Your site・・・と表示されていればリンクをクリックすればサイトが表示されます。
もし、設定もあっていて、index.html も生成されているのに、リンクが表示されていない場合は、
一旦、Sourceの部分を master に切り替えて、https://GitHubのユーザ名.github.io/プロジェクト名(今回はfirst_nuxt)/README.md
がブラウザで表示できることを確認してから、
再度、Sourceの部分を gh-pages に切り替えるとうまくいくようです。
URLの調整
これで、完成のように見えますが、トップページの裏でエラーが発生しています。
F12で開発者ツールを出し、Networkタブを開くと、404エラーになっているファイルが見つかります。
これは、通常Nuxtで作ったプロジェクトはルートで動作する設定になっているためです。
https://GitHubのユーザ名.github.io/
が基準となっており、実際のルートとなる
https://GitHubのユーザ名.github.io/プロジェクト名(今回はfirst_nuxt)/
とズレがあるため、相対パスがおかしくなるのが原因です。最後にこれを解決する設定を行います。
VisualStudioCode で nuxt.config.js を開き、以下の通り変更します。
nuxt.config.js変更前link: [ { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' } ] }, /* ** Global CSS */nuxt.config.js変更後link: [ { rel: 'icon', type: 'image/x-icon', href: '/first_nuxt/favicon.ico' } ] }, router: { base: '/first_nuxt/' } /* ** Global CSS */linkのhrefの修正と、router: の追加です。
これで、相対パスのルートの設定が / から /first_nuxt/ に変更されます。
最終確認
変更したソースをコミットして、プッシュします。
変更内容はソース管理の nuxt.config.js を選択すると確認できます。
変更前と後の内容が左右に並び、変更した行がマークされています。
これをみて間違いがないかを確認できたら、コミットコメント
を入力して、コミットボタンを押します。
Push すると GitHub Action が動作しますので、GitHubのリポジトリページから Actions タブを開いて
動作が完了しているか確認してみましょう。
ブラウザでトップページをリロードして404エラーが消えていることを確認します。
長くなりましたが、以上ですべての作業が完了しました!
- 投稿日:2020-06-21T15:59:11+09:00
P5.js 日本語リファレンス(textFont)
このページでは「P5.js 日本語リファレンス」 の textFont関数を説明します。
textFont()
説明文
text() で描画される現在のフォントを設定します。
WEBGLモードのとき:loadFont() を介して読み込まれたフォントのみがサポートされます。
構文
textFont()
textFont(font, [size])
パラメタ
font
Object | String:loadFont() を介して読み込まれたフォント、またはWebセーフフォント(すべてのシステムで一般的に利用可能なフォント)を表す文字列size
Number:使用するフォントサイズ(オプション、デフォルト:12)戻り値
Object:現在のフォント
例1
let fontRegular, fontBold; function preload() { fontRegular = loadFont('assets/Inconsolata-Regular.ttf'); fontBold = loadFont('assets/Inconsolata-Bold.ttf'); } function setup() { createCanvas(300, 300); background(210); fill(0) .strokeWeight(0) .textSize(24); textFont(fontRegular); text('Font Style Normal', 10, 30); textFont(fontBold); text('Font Style Bold', 10, 70); }実行結果
https://editor.p5js.org/bit0101/sketches/t1EsW2OD_
著作権
p5.js was created by Lauren McCarthy and is developed by a community of collaborators, with support from the Processing Foundation and NYU ITP. Identity and graphic design by Jerel Johnson.
ライセンス
Creative Commons(CC BY-NC-SA 4.0) に従います。
- 投稿日:2020-06-21T15:50:48+09:00
P5.js 日本語リファレンス(text)
このページでは「P5.js 日本語リファレンス」 の text関数を説明します。
text()
説明文
画面にテキストを描画します。
パラメータで指定された位置に、画面の最初のパラメータで指定された情報を表示します。textFont() でフォントが設定されていないときはデフォルトのフォントが使用され、textSize() でフォントが設定されていないときはデフォルトのサイズが使用されます。 fill() を使用してテキストの色を変更できます。
stroke() および strokeWeight() を使用して、テキストのアウトラインを変更できます。テキストは、座標の左、右および中心に描画するオプションを提供する textAlign() に関連して表示されます。
x2およびy2パラメータはテキストを表示する長方形の領域を定義します。これらのパラメータを指定すると、現在のrectMode() 設定に基づいて解釈されます。指定された長方形に完全に収まらないテキストは画面に描画されません。
x2とy2が指定されていない場合、ベースラインの配置がデフォルトになります。つまり、テキストはxとyから上方に描画されます。WEBGL モードのとき:opentype / truetype フォントのみがサポートされています。 loadFont() を使用してフォントをロードする必要があります。(例2参照) 現在、stroke() は WEBGL モードでは効果がありません。
構文
テキスト(str, x, y, [x2], [y2])
パラメタ
str
String | Object | Array | Number | Boolean:表示される英数字記号x
Number:テキストのx座標y
Number:テキストのy座標x2
Number:デフォルトではテキストボックスの幅。詳細については rectMode() を参照してください(オプション)y2
Number:デフォルトではテキストボックスの高さ。詳細については rectMode() を参照してください(オプション)例1
function setup(){ createCanvas(300,300); let s = 'The quick brown fox jumped over the lazy dog.'; fill(0, 102, 183); text(s, 10, 10, 90, 80); //テキストボックス内でテキストを折り返します }実行結果
https://editor.p5js.org/bit0101/sketches/xw0xWCaqK
例2
let inconsolata; function preload() { inconsolata = loadFont('assets/Inconsolata-Regular.ttf'); } function setup() { createCanvas(300, 300, WEBGL); textFont(inconsolata); textSize(width / 3); textAlign(CENTER, CENTER); } function draw() { background(0); let time = millis() ; rotateX(time / 1000); rotateZ(time / 1234); text('inconsolata', 0, 0); }実行結果
https://editor.p5js.org/bit0101/sketches/05dmkLqex
著作権
p5.js was created by Lauren McCarthy and is developed by a community of collaborators, with support from the Processing Foundation and NYU ITP. Identity and graphic design by Jerel Johnson.
ライセンス
Creative Commons(CC BY-NC-SA 4.0) に従います。
- 投稿日:2020-06-21T15:49:06+09:00
P5.js 日本語リファレンス(p5.Font)
このページでは「P5.js 日本語リファレンス」 の p5.Font を説明します。
p5.Font
説明文
フォントの基本クラス
構文
new p5.Font([pInst])
パラメタ
- pInst
P5:p5インスタンス(オプション)Fields
- font
根本的なOpenTypeフォントの実装Methods
textBounds()
このフォントを使用して、指定されたテキスト文字列のタイトなバウンディングボックスを返します(現在は単一行のみをサポートしています)textToPoints()
指定されたテキストのパスをたどる点の配列を計算します著作権
p5.js was created by Lauren McCarthy and is developed by a community of collaborators, with support from the Processing Foundation and NYU ITP. Identity and graphic design by Jerel Johnson.
ライセンス
Creative Commons(CC BY-NC-SA 4.0) に従います。
- 投稿日:2020-06-21T15:46:20+09:00
P5.js 日本語リファレンス(loadFont)
このページでは「P5.js 日本語リファレンス」 の loadFont関数を説明します。
loadFont()
ファイルまたはURLからOpenTypeフォントファイル(.otf, .ttf)を読み込み、PFontオブジェクトを返します。このメソッドは非同期です。つまり、スケッチの次の行が実行される前に終了しない場合があります。
フォントへのパスは、スケッチでリンクするHTMLファイルからの相対パスである必要があります。ブラウザの組み込みセキュリティにより、URLまたはその他のリモートの場所からのフォントの読み込みがブロックされる場合があります。
preload() 内でloadFont() を呼び出すと、setup() および draw() が呼び出される前にロード操作が完了することが保証されます。
構文
loadFont(path, [callback], [onError])
パラメタ
path
String:ロードするファイルまたはURLの名前callback
Function:loadFont() の完了後に実行される関数(オプション)onError
Function:エラーが発生した場合に実行される関数(オプション)戻り値
p5.Font:p5.Fontオブジェクト
例1
let myFont; function preload() { myFont = loadFont('assets/Inconsolata-Regular.ttf'); } function setup() { createCanvas(300, 300); fill(' #ED225D'); textFont(myFont); textSize(28); text('Inconsolata', 10, 50); }実行結果
https://editor.p5js.org/bit0101/sketches/NWNLqM70C
例2
preload() の外で、オブジェクトを処理するためのコールバック関数を提供できます。
function setup() { // preload() でないことに注意 loadFont('assets / inconsolata.otf', drawText); } function drawText(font){ fill(' #ED225D'); textFont(font, 36); text('p5*js', 10, 50); }実行結果
https://editor.p5js.org/bit0101/sketches/6D-zSrbwm
著作権
p5.js was created by Lauren McCarthy and is developed by a community of collaborators, with support from the Processing Foundation and NYU ITP. Identity and graphic design by Jerel Johnson.
ライセンス
Creative Commons(CC BY-NC-SA 4.0) に従います。
- 投稿日:2020-06-21T15:38:13+09:00
診断アプリの作る方法(jQueryのみで実装)
概要
診断アプリを作りました。
こんな感じ
こちらの診断アプリをJS(jQuery)のみで実装したので記事をしました。まずは、ビューを用意する。
#wrapper .question %h1.title ソーシャルスタイル診断 %ul.questions %li.textBox %span.text Q1. 冷静で人からの指示は嫌い %label.yes %input.typeA.typeD{:name => "q01", :type => "radio"}>/ YES %label.no %input.typeB.typeC{:name => "q01", :type => "radio"}>/ NO %li.textBox %span.text Q2. 自分の話をすることを好む %label.yes %input.typeB.typeA{:name => "q02", :type => "radio"}>/ YES %label.no %input.typeC.typeD{:name => "q02", :type => "radio"}>/ NO %li.textBox %span.text Q3. 周りの意見を大事にする方だ %label.yes %input.typeC.typeD{:name => "q03", :type => "radio"}>/ YES %label.no %input.typeA.typeB{:name => "q03", :type => "radio"}>/ NO %li.textBox %span.text Q4. 情報を集めたり分析することが得意だ %label.yes %input.typeD.typeC{:name => "q04", :type => "radio"}>/ YES %label.no %input.typeA.typeB{:name => "q04", :type => "radio"}>/ NO %li.textBox %span.text Q5. 戦略立案や勝負事への興味が強い %label.yes %input.typeA.typeD{:name => "q05", :type => "radio"}>/ YES %label.no %input.typeB.typeC{:name => "q05", :type => "radio"}>/ NO %li.textBox %span.text Q6. ノリがよくムードメーカーだと言われる %label.yes %input.typeB.typeC{:name => "q06", :type => "radio"}>/ YES %label.no %input.typeA.typeD{:name => "q06", :type => "radio"}>/ NO %li.textBox %span.text Q7. 気配り上手、聞き上手と言われる %label.yes %input.typeC.typeD{:name => "q07", :type => "radio"}>/ YES %label.no %input.typeA.typeB{:name => "q07", :type => "radio"}>/ NO %li.textBox %span.text Q8. 感情を表に出すのは、苦手だ %label.yes %input.typeD.typeA{:name => "q08", :type => "radio"}>/ YES %label.no %input.typeC.typeB{:name => "q08", :type => "radio"}>/ NO %button 診断する .result.ResultA .ResultA__recommend あなたの性格は・・・・ .ResultA__Amenu .ResultA__Amenu__text 前進型・行動派 %p あなたは迅速かつ合理的に仕事を進めるタイプです。ビジネスライクな性格で、プロセスよりも結果を重視し、決断力に優れています。経営者に多いタイプに多いとされます。 = image_tag("close-up-face-fashion-fine-looking-450212.jpg", width: "306px",class: "image") .result.ResultB .ResultB__recommend あなたの性格は・・・・ .ResultB__Bmenu .ResultB__Bmenu__text 直感型・感覚派 %p あなたは、周りから注目されることを好むタイプです。ビジネス面では、自ら先頭に立って人を率いていく傾向にあります。 = image_tag("men-s-white-button-up-dress-shirt-708440.jpg", width: "306px",class: "image") .result.ResultC .ResultC__recommend あなたの性格は・・・・ .ResultC__Cmenu .ResultC__Cmenu__text 温和型・協調派 %p あなたは、どこにいてもみんなの調停役になるタイプです。周囲の気持ちに敏感で、自分の話をするよりも相手の話に耳を傾ける傾向にあります。 = image_tag("woman-wearing-teal-dress-sitting-on-chair-talking-to-man-2422280.jpg", width: "306px",class: "image") .result.ResultD .ResultD__recommend あなたの性格は・・・・ .ResultD__Dmenu .ResultD__Dmenu__text 分析型・思考派 %p あなたは、独特の価値観や雰囲気を持っていて、周囲に影響されにくいマイペースな人です。仕事においては、データの収集や分析に黙々と取り組みます。 = image_tag("person-using-a-laptop-3183131.jpg", width: "306px",class: "image")次にJS(jQuery)を用意する。
$(function(){ //ボタンがクリックされた時 $("button").on("click", function(){ //一度結果を非表示にする $(".result").hide(); //問題数を取得 var qNum = $("ul li").length; if( $("ul li input:checked").length < qNum ){ //全てチェックしていなかったらアラートを出す alert("未回答の問題があります"); } else { //チェックされているinputの数を取得 var typeANum = $(".typeA:checked").length, typeBNum = $(".typeB:checked").length, typeCNum = $(".typeC:checked").length, typeDNum = $(".typeD:checked").length, typeENum = $(".typeD:checked").length; if( typeANum >= typeBNum && typeANum >= typeCNum && typeANum >= typeDNum && typeANum >= typeENum) { $(".ResultA").fadeIn(); } else if( typeBNum >= typeANum && typeBNum >= typeCNum && typeBNum >= typeDNum && typeBNum >= typeENum) { $(".ResultB").fadeIn(); } else if( typeCNum >= typeANum && typeCNum >= typeBNum && typeCNum >= typeDNum && typeCNum >= typeENum) { $(".ResultC").fadeIn(); } else if( typeDNum >= typeBNum && typeDNum >= typeCNum && typeDNum >= typeANum && typeDNum >= typeENum) { $(".ResultD").fadeIn(); } else if( typeENum >= typeBNum && typeENum >= typeCNum && typeENum >= typeANum && typeENum >= typeDNum) { $(".ResultE").fadeIn(); } } }); });解説
ちょい説明します。
.result.ResultA .ResultA__recommend あなたの性格は・・・・ .ResultA__Amenu .ResultA__Amenu__text 前進型・行動派 %p あなたは迅速かつ合理的に仕事を進めるタイプです。ビジネスライクな性格で、プロセスよりも結果を重視し、決断力に優れています。経営者に多いタイプに多いとされます。 = image_tag("close-up-face-fashion-fine-looking-450212.jpg", width: "306px",class: "image")回答結果となる
.result.ResultA
は、CSSでdisplay: none;
により普段は非表示にしています。if( typeANum >= typeBNum && typeANum >= typeCNum && typeANum >= typeDNum && typeANum >= typeENum) { $(".ResultA").fadeIn();もし
.typeA
が.typeB
.typeC
.typeD
.typeE
よりinputの数(.length)が多い場合は、$(".ResultA").fadeIn();fadeInメソッドで、非表示にされていた
.result.ResultA
をフェードイン表示させます。
こんな感じで.typeB以下のif文が同じ要領で続きます。
これを応用すれば、色んな診断アプリを作れそうです。
補足
hamlからHTMLに変換する場合は、以下の記事を参考にしてください。
https://qiita.com/chezou/items/0e9bd4f9eb8314dc2aec#hamlhtml%E5%A4%89%E6%8F%9B
- 投稿日:2020-06-21T14:58:15+09:00
【Rails】ancestryを用いた多階層カテゴリー機能の実装『Bootstrap3でウィンドウ作ってみた編』
目標
開発環境
・Ruby: 2.5.7
・Rails: 5.2.4
・Vagrant: 2.2.7
・VirtualBox: 6.1
・OS: macOS Catalina前提
下記実装済み。
・Slim導入
・Bootstrap3導入
・Font Awesome導入
・ログイン機能実装
・投稿機能実装
・多対多のカテゴリー機能実装
・多階層カテゴリー機能実装(準備編)
・多階層カテゴリー機能実装(seed編)
・多階層カテゴリー機能実装(作成フォーム編)
・多階層カテゴリー機能実装(編集フォーム編)1.コントローラーを編集
homes_controller.rb# 追記 def category_window @children = Category.find(params[:parent_id]).children end【解説】
① Ajax通信で送られてきたパラメーターに対応するカテゴリーの、子カテゴリーを抽出し、インスタンス変数に代入する。
@children = Category.find(params[:parent_id]).children2.
json.jbuilderファイル
を作成・編集ターミナル$ touch app/views/homes/category_window.json.jbuildercategory_window.json.jbuilderjson.array! @children do |children| json.id children.id json.name children.name end【解説】
①
get_category_children
アクションで抽出したレコードを繰り返し処理し、配列を作成する。json.array! @children do |children|② 各IDと名前を
①
で作成した配列に格納する。json.id children.id json.name children.name◎ 親カテゴリー(ビジネス)にマウスが乗っている場合の返り値
[ { "id": 2, "name": "金融" }, { "id": 6, "name": "経済" }, { "id": 9, "name": "経営" }, { "id": 13, "name": "マーケティング" }, ]◎ 子カテゴリー(金融)にマウスが乗っている場合の返り値
[ { "id": 3, "name": "株" }, { "id": 4, "name": "為替" }, { "id": 5, "name": "税金" }, ]3.ルーティングを追加
routes.rb# 追記 get 'get_category/new', to: 'homes#category_window', defaults: { format: 'json' }4.ビューを編集
application.html.slimbody header nav.navbar.navbar-default.navbar-fixed-top .container-fluid ul.nav.navbar-nav.navbar-right li.dropdown role='presentation' a.dropdown-toggle data-toggle='dropdown' href='#' role='button' aria-expanded='false' i.fas.fa-list-ul span | カテゴリーから探す span.caret ul.dropdown-menu role='menu' li role='presentation' - Category.where(ancestry: nil).each do |parent| = link_to parent.name, root_path, id: "#{parent.id}", class: 'parent-category' br li role='presentation' class='children-list' br li role='presentation' class='grandchildren-list'【解説】
※Bootstrapの書き方については省略します。
① ancestryの値が
nil
、つまり親カテゴリーを全て抽出し、プルダウンメニューに表示する。- Category.where(ancestry: nil).each do |parent| = link_to parent.name, root_path, id: "#{parent.id}", class: 'parent-category'② 子カテゴリーを表示する場所を用意する。
li role='presentation' class='children-list'③ 孫カテゴリーを表示する場所を用意する。
li role='presentation' class='grandchildren-list'5.JavaScriptファイルを作成・編集
ターミナル$ touch app/assets/javascripts/category_window.jscategory_window.js$(function() { function buildChildHTML(children) { let html = ` <a class="children-category" id="${children.id}" href="/"> ${children.name} </a> `; return html; } $('.parent-category').on('mouseover', function() { let id = this.id; $('.children-category').remove(); $('.grandchildren-category').remove(); $.ajax({ type: 'GET', url: '/get_category/new', data: { parent_id: id, }, dataType: 'json', }).done(function(children) { children.forEach(function(child) { let html = buildChildHTML(child); $('.children-list').append(html); }); }); }); function buildGrandChildHTML(children) { let html = ` <a class="grandchildren-category" id="${children.id}" href="/"> ${children.name} </a> `; return html; } $(document).on('mouseover', '.children-category', function() { let id = this.id; $.ajax({ type: 'GET', url: '/get_category/new', data: { parent_id: id, }, dataType: 'json', }).done(function(children) { children.forEach(function(child) { let html = buildGrandChildHTML(child); $('.grandchildren-list').append(html); }); $(document).on('mouseover', '.children-category', function() { $('.grandchildren-category').remove(); }); }); }); });【解説】
① 子カテゴリーのHTMLを作成する。
function buildChildHTML(children) { let html = ` <a class="children-category" id="${children.id}" href="/"> ${children.name} </a> `; return html; }② どの親カテゴリーにマウスが乗っているかによって、子カテゴリーの表示内容を変更する。
$('.parent-category').on('mouseover', function() { let id = this.id; $('.children-category').remove(); $('.grandchildren-category').remove(); $.ajax({ type: 'GET', url: '/get_category/new', data: { parent_id: id, }, dataType: 'json', }).done(function(children) { children.forEach(function(child) { let html = buildChildHTML(child); $('.children-list').append(html); }); }); });◎ 親カテゴリーにマウスが乗った時に発火するイベントを作成する。
$('.parent-category').on('mouseover', function() {});◎
category_window.json.jbuilder
から送られてきたIDを、変数へ代入する。let id = this.id;◎ とりあえず子カテゴリー以下を削除しておく。
$('.children-category').remove(); $('.grandchildren-category').remove();◎ パラメーター(parent_id)に先ほど作成した変数を設定して、
category_window
アクションを非同期で実行する。$.ajax({ type: 'GET', url: '/get_category/new', data: { parent_id: id, }, dataType: 'json', })◎ Ajax通信が成功した場合は対応する子カテゴリーのHTMLを作成し、表示する。
.done(function(children) { children.forEach(function(child) { var html = buildChildHTML(child); $('.children-list').append(html); }); });③孫カテゴリーのHTMLを作成する。
function buildGrandChildHTML(children) { var html = ` <a class="grandchildren-category" id="${children.id}" href="/"> ${children.name} </a> `; return html; }④ どの子カテゴリーにマウスが乗っているかによって、孫カテゴリーの表示内容を変更する。(
③
とほぼ同じなので説明は省略)$(document).on('mouseover', '.children-category', function() { var id = this.id; $.ajax({ type: 'GET', url: '/get_category/new', data: { parent_id: id, }, dataType: 'json', }).done(function(children) { children.forEach(function(child) { var html = buildGrandChildHTML(child); $('.grandchildren-list').append(html); }); $(document).on('mouseover', '.children-category', function() { $('.grandchildren-category').remove(); }); }); });注意
turbolinks
を無効化しないとプルダウンメニューが非同期で動作しないので、必ず無効化しておきましょう。
- 投稿日:2020-06-21T10:19:19+09:00
ancestryを用いた多階層カテゴリー機能の実装『編集フォーム編』
目標
開発環境
・Ruby: 2.5.7
・Rails: 5.2.4
・Vagrant: 2.2.7
・VirtualBox: 6.1
・OS: macOS Catalina前提
下記実装済み。
・Slim導入
・Bootstrap3導入
・Font Awesome導入
・ログイン機能実装
・投稿機能実装
・多対多のカテゴリー機能実装
・多階層カテゴリー機能実装(準備編)
・多階層カテゴリー機能実装(seed編)
・多階層カテゴリー機能実装(作成フォーム編)実装
1.コントローラーを編集
books_controller.rbdef edit unless @book.user == current_user redirect_to books_path end @category_parent_array = Category.category_parent_array_create end def update if @book.update(book_params) book_categories = BookCategory.where(book_id: @book.id) book_categories.destroy_all BookCategory.maltilevel_category_create( @book, params[:parent_id], params[:children_id], params[:grandchildren_id] ) redirect_to @book else @category_parent_array = Category.category_parent_array_create render 'edit' end end【解説】
① 中間テーブルから編集する本に対応するレコードを全て抽出し、削除する。
book_categories = BookCategory.where(book_id: @book.id) book_categories.destroy_all2.ビューを編集
books/edit.html.slim/ 追記 .category-form = label_tag 'ジャンル' = select_tag 'parent_id', options_for_select(@category_parent_array), class: 'form-control', id: 'parent-category' i.fas.fa-chevron-down br注意
turbolinks
を無効化しないとセレクトボックスが非同期で動作しないので、必ず無効化しておきましょう。
- 投稿日:2020-06-21T10:19:19+09:00
【Rails】ancestryを用いた多階層カテゴリー機能の実装『編集フォーム編』
目標
開発環境
・Ruby: 2.5.7
・Rails: 5.2.4
・Vagrant: 2.2.7
・VirtualBox: 6.1
・OS: macOS Catalina前提
下記実装済み。
・Slim導入
・Bootstrap3導入
・Font Awesome導入
・ログイン機能実装
・投稿機能実装
・多対多のカテゴリー機能実装
・多階層カテゴリー機能実装(準備編)
・多階層カテゴリー機能実装(seed編)
・多階層カテゴリー機能実装(作成フォーム編)実装
1.コントローラーを編集
books_controller.rbdef edit unless @book.user == current_user redirect_to books_path end @category_parent_array = Category.category_parent_array_create end def update if @book.update(book_params) book_categories = BookCategory.where(book_id: @book.id) book_categories.destroy_all BookCategory.maltilevel_category_create( @book, params[:parent_id], params[:children_id], params[:grandchildren_id] ) redirect_to @book else @category_parent_array = Category.category_parent_array_create render 'edit' end end【解説】
① 中間テーブルから編集する本に対応するレコードを全て抽出し、削除する。
book_categories = BookCategory.where(book_id: @book.id) book_categories.destroy_all2.ビューを編集
books/edit.html.slim/ 追記 .category-form = label_tag 'ジャンル' = select_tag 'parent_id', options_for_select(@category_parent_array), class: 'form-control', id: 'parent-category' i.fas.fa-chevron-down br注意
turbolinks
を無効化しないとセレクトボックスが非同期で動作しないので、必ず無効化しておきましょう。続編
- 投稿日:2020-06-21T07:10:28+09:00
ESLint v7.3.0
前 v7.2.0 | 次 (2020-07-04 JST)
ESLint v7.3.0 has been released: https://t.co/cGcdAuYc9s
— ESLint (@geteslint) June 19, 2020ESLint
7.3.0
がリリースされました。小さな機能追加とバグ修正が含まれています。質問やバグ報告等ありましたら、お気軽にこちらまでお寄せください。
? 日本語 Issue 管理リポジトリ
? 日本語サポート チャット (招待リンク)
? 本家リポジトリ
? 本家サポート チャット (招待リンク)
[PR] ESLint は開発リソースを確保するための寄付を募っています。
応援してくださると嬉しいです。
✨ 本体への機能追加
特になし
? 新しいルール
no-promise-executor-return
? #12648
Promise
コンストラクタに渡す関数にて値を返すreturn
文を書くとエラーにするルールが追加されました。値を返すのではなくresolve()
を呼ぶ必要があります。例/* eslint no-promise-executor-return: error */ //✘ BAD new Promise((resolve, reject) => { if (someCondition) { return defaultResult; } getSomething((err, result) => { if (err) { reject(err); } else { resolve(result); } }); }); //✔ GOOD new Promise((resolve, reject) => { if (someCondition) { resolve(defaultResult); return; } getSomething((err, result) => { if (err) { reject(err); } else { resolve(result); } }); });no-unreachable-loop
? #12660
ループしないループ構文をエラーにするルールが追加されました。
例/* eslint no-unreachable-loop: error */ //✘ BAD for (let i = 0; i < arr.length; i++) { if (arr[i].name === myName) { doSomething(arr[i]); // break was supposed to be here } break; }? オプションが追加されたルール
特になし
- 投稿日:2020-06-21T02:21:21+09:00
call()を使うとthisをコントロールすることができる(ドラゴンボール編)
thisのコントロール
開眼!javascriptを読んでて
thisとcallの動きについて知ったことがあり、書きますその2。
(※from 6章の6.5より。)その1はこちら。
◼️グローバルなとこで使うthisはまじでグローバルthisレベル(thisに対する自分の理解感覚値)
関数とかの中でthis出てきたら「これ」の意味通り、その関数自体を指し示している、、、くらいの知識を持っていて、よくわかんなくなってもconsole.log()で中身見ればいいやくらいの理解度でした。
あとアロー関数の書き方くらいはわかるくらいの感じです!call()を使うとthisの生き方を決めれるぞ
どういう話かというと・・・
通常、「関数の中のthis」は、その関数自体を指し示します。
が、関数のcall()というものを使うと、任意の引数をthisの向き先に変えることができるぞ!というもの。みんな大好きドラゴンボールの孫悟飯に例えます。
孫悟飯は子供の頃、学者になりたかったのですが、ピッコロに出会い、修行を受け、戦闘能力が上がり戦闘民族としての才能を開花させていきます。サイヤ人襲来編あたりの話です。まず、「孫悟飯の情報を出力する無名関数」を作り、gohan変数に入れてみます。
test.js//gohan変数に、関数を入れる。 var gohan = function(a,b){ this.life = a; //御飯の人生は引数a this.sentouryoku = b; //御飯の戦闘力は引数b console.log(this.life,this.sentouryoku) //御飯の人生と戦闘力を表示。 };この関数ですが、受け取ったa、bの引数をもとに生活と戦闘力を決め、
gohan自体の情報を出力するというものです。
実際に、学者を目指してた子供自体の孫悟飯でやってみましょう。
初期のご飯の戦闘力は1ですね。ラディッツに切れた一発ぶちかましたあとがくんと戦闘力が下がるやつです。test.jsvar gohan = function(a,b){ this.life = a; this.sentouryoku = b; console.log(this.life,this.sentouryoku) }; gohan("学者",1); //この結果は 学者 1 となります。
で、call()関数。
ここまでの内容を前提として、call()のなかのthisの扱いについてです。
これは、関数.call(a,b,c)とすると、その関数のthisの向き先を、「関数自体」ではなく、「a」にする、、、というものです。わかりづらいので、とりあえず
孫悟飯の人生と戦闘力を、ピッコロに会うことで変えてみましょう。孫悟飯の人生をピッコロと会うことで変える
test.js//ピッコロオブジェクトを作る var picoro = {} //この関数自体は同じです。 var gohan = function(a,b){ this.life = a; this.sentouryoku = b; console.log(this.life,this.sentouryoku) }; //call関数。 gohan.call(picoro,"戦闘民族",2300); //結果は、、、戦闘民族 2300何が起きたのか?
gohan.call(picoro,"戦闘民族",2300);
この記述の解説です。
先ほど書いたように、call関数は
一つ目の引数(つまりピッコロ)、を、this(孫悟飯の人生)の向き先にします。孫悟飯は、出会った人の影響で人生が変わるわけなので、
つまりピッコロオブジェクトの中に、御飯の情報が入るわけです。
ここまで書いて少しこの例は違うなと気づき始めましたが、軌道修正も大変なのでこのまま行きます。ピッコロオブジェクトに対して、つまりピッコロの人生の影響を受けて
御飯の人生もピッコロと同じようなものになったということです。
何を言ってるのかわからなくなってきましたが、どうあれこの結果は
戦闘民族 2300 とこうなります!ちなみに、この記述の末尾に
console.log(picoro);
すると、きちんとピッコロオブジェクトの中身が出力されます。赤字のとこ。
ピッコロオブジェクトの中にきちんと御飯の人生が入ってますね。
やっぱりこの例はなんか違いますが、call()の動きはこういうものです!はい成功!捕捉
ちなみに、apply()
という関数も同じようなことができます。
違いとしては、applyでは、引数を配列で渡せる、というものです。gohan.call(picoro,"戦闘民族",2300);
のところを
gohan.apply(picoro,["戦闘民族",2300]);
とできるということです。
※結果は同じになります。・参考
call関数
- 投稿日:2020-06-21T01:16:35+09:00
Next.jsの環境変数の使い方の基本と、Vercelの環境変数との関係性について
Next.jsの公式サイトDefault Environment Variables を参考に、Next.jsの環境変数の取り扱い方法と、Vercel上の環境変数との関係性について調べ、ユースケースをまとめてみました。
Next.jsの環境変数の取り扱い方法について
後述の通り、環境変数の設定ファイルに
XXXXX=Y
の形で環境変数を定義しておけば、プログラム中でprocess.env.XXXXX
の形式で環境変数を参照できる。ただし、普通に環境変数を定義した場合、これらはブラウザ上からは見えないので(サーバーサイドで実行されるコードでのみ見える)、ブラウザ上で環境変数を使えるようにしたい場合(例えば、クライアントサイドでHTTP Requestを送信したい場合に環境変数を使いたい場合)には、環境変数名を
NEXT_PUBLIC_
で始めるようにすれば(ex.NEXT_PUBLIC_XXXXX
)、クライアントサイドのJavaScript上から参照できる。.env系
特徴
リポジトリに含めても良い環境変数を書く。
各ファイルの特徴
.env
- すべての環境のデフォルト設定となる。
.env.development
next dev
で起動した際に使われる。- .env に優先する。
.env.production
next start
で起動した際に使われる。- .env に優先する。
.env.local系
特徴
リポジトリに含めてはいけない環境変数を書く。(シークレット等)
リポジトリのトラッキング対象とはしない。各ファイルの特徴
.env.local
- 上記の.env系のすべて(.env, .env.developmentもしくは.env.production)に優先する。
.env.development.local
next dev
で起動した際に使われる。- .env, .env.development, .env.local に優先する。
.env.production.local
next start
で起動した際に使われる。- .env, .env.production, .env.local に優先する。
Next.jsでどこに何を記述すべきなのか
.env
- 環境に影響されない設定かつシークレット系ではない設定を記述。
- ex. リージョン名とか
- リポジトリでのトラッキング対象に含めるファイル。
.env.{ENV}
- 環境によって変わる設定かつシークレット系ではない設定を記述。
- ex. バケット名とか
- リポジトリでのトラッキング対象に含めるファイル。
.env.local
- 環境に影響されない設定かつシークレット系の設定を記述。
- ex. 環境に左右されない接続情報とか
- リポジトリでのトラッキング対象に含めてはいけないファイル。
- この情報をVercel上に管理する画面はないので、このファイルは使わずに冗長的に.env.{ENV}.localに書くのも一つの手。
.env.{ENV}.local
- 環境によって変わる設定かつシークレット系の設定を記述。
- ex. 環境ごとのアクセスキーとか
- リポジトリでのトラッキング対象に含めてはいけないファイル。
Next.jsの各環境変数ファイルの優先度
.env.{ENV}.local > .env.local > .env.{ENV} > .env となる。
つまり、全てのファイルに環境変数を定義した場合には、.env.{ENV}.localの値が使われることになる。
Vercel上の環境変数とVercelコマンドについて
Vercel上では、Next.jsで設定した環境変数ファイル(.env, .env.production, .env.development)がそのまま使用される。
- .env.developmentで設定された環境変数は、Previewで使用される。
.env.{ENV}.localで設定していた環境変数は、GUI上で、Production用、Preview/Development用にそれぞれ設定できるので、画面から普通にセットすればよい。
- ただし、環境によらないシークレットを保存する方法がVercelにはないので、.env.localの情報をVercelでは設定できない。
Vercel上でDevelopment環境変数を設定している場合、
vercel env pull [ファイル名。デフォルトは.env]
で、指定したファイル名、もしくは.envにdevelopment系の環境変数を引き抜くことができる。
- 上述の通り、.env.local を管理する方法がVercelにはないので、.local.envを使っている場合にはその内容もpullされてくることになる。
上記のNext.jsの環境変数のスタンダードとして、シークレット系は.localに書くのがいいので、もしVercel上に環境変数を設定していたら、
vercel env pull .env.local
で環境変数を引き抜きが良いかな・・・。参考