- 投稿日:2020-02-17T23:56:16+09:00
javascript基礎
変数
var 変数 → ES6から let 変数、const 変数配列
let list = [.....]
console.log(list)オブジェクト
let obj = {}for文 繰り返し
for(let i = obj. <回数 ; i += 1>) {
}function文
function 関数名(引数) {
} 引数はなくても()はいる
- 投稿日:2020-02-17T23:37:11+09:00
FormDataでオブジェクトの階層があるデータを送る
概要
multipart/form-data
形式で、JavaScriptからPostを投げる場合、FormDataを使うケースがあると思います。例えば、RailsのStrong Parametersに対応(Strong Parametersについては【Rails】requireとpermitメソッド参照)するなど、FormDataをオブジェクトの階層形式で送る場合の対応方法について記載します。
なお、FormDataの使い方についてはFormData は multipart/form-data で application/x-www-form-urlencoded は URLSearchParamsにてまとめられています。サンプルデータ
Json形式で下記の通り表現すると、parantObjectの配下にparamが存在するケースになります。
sample.json{ "parantObject" : { "name1": "value1" } }対応方法
以下の通り、FormDataに入れる変数名を
親オブジェクト名[子の変数名]
の形式で、登録することで親子関係の認識をします。sample.jsform.append("parantObject[name1]", "value1");その他参考
- 配列を送信したい場合、axoisにFormDataを使ってArrayデータを送信するが参考になります。ちなみにオブジェクト配下の配列項目を送りたいときは、
親オブジェクト名[子の配列変数名][]
で送信します。- axiosでFileオブジェクトを含むデータをFormDataに変換する共通処理で、オブジェクトをFormDataに変換するロジックが紹介されています。
- 投稿日:2020-02-17T22:42:54+09:00
SPAの知識ゼロの状態から、React+Reduxの開発環境構築までにやったこと
Webアプリケーションを開発する際に、「今ならSPA(Single Page Application)っしょ」となり、フレームワークの選定からやらせてもらう機会があったので、そのときにやったことを共有します。
前提
本記事作成時の筆者のスキル
- Webアプリケーションは作ったことがあるが、言語はJava
- SPA is 何?
- Javascriptはかじった程度の知識
フレームワークの選定
Googleトレンドや、実績からSPAのフレームワークを以下に絞りました。
- React
- AngularJS
- Vue.js
その時人気だったReactにしておけばいいだろと、Reactにしました。
※近年、Vue.jsが勢力を伸ばしてきているらしいです。React + α
Reactは様々なライブラリと組み合わせるのが普通のようです。
npm installでどんどんライブラリを追加していくことになりますが、ここでは開発手法に大きく関わるライブラリだけ。
- TypeScript:Javascriptに静的型付けをしてくれる、AltJS。正直これ初心者には辛い。
- Redux:stateの管理方法に大きく関わるライブラリ
参考記事:https://qiita.com/mpyw/items/a816c6380219b1d5a3bf- @reduxjs/toolkit:ReduxのActinとReducerをまとめて記述できるツール。Reduxチーム公式。
参考記事:https://qiita.com/Ouvill/items/a76e9cbce569d01f2931- redux-saga:タスクをうまいこと処理してくれる"ミドルウェア"という概念。APIリクエストなどの非同期処理で使用する。
参考記事:https://qiita.com/kuy/items/716affc808ebb3e1e8ac
※while(true)よりも、takeLatestをよく使いました。- Material-UI:見た目をいい感じにしてくれるライブラリ。これは無くてもよかったかも。
- react-intl:メッセージ管理や多言語対応をできるようにするライブラリ
公式ドキュメント:https://github.com/formatjs/react-intl/blob/master/docs/README.md執筆時点での最新バージョンはそれぞれ以下の通りです。あまり古くなっているようなら、参考にならないかもしれません。
プロダクト version React 16.12.0 Redux 4.0.5 TypeScript 3.7.5 redux-saga 1.1.3 Material-UI 4.9.2 react-intl 3.12.0 エディター
これも流行りなのでVSCodeを使用しました。
結果的に、nodeやgitのコマンドもターミナルのペインで実行できるので、かなり使いやすかったです。設定と拡張機能をチーム内で共有する
エディタの設定は、プロジェクト直下の.vscode/settings.jsonというファイルで共有できます。
拡張機能は、.vscode/extensions.jsonというファイルを置くことで、VSCode起動時にポップアップが出るようになります。
フォーマッター、保存時の自動フォーマットなどの設定は共有しておくと便利です。プロジェクトの作成
create-react-appというコマンドを使います。これを使うと起動スクリプトやwebpackのコンフィグなどが隠された状態で、カスタマイズできないので、すぐにejectというコマンドで素のnodeで扱えるようにします。
npx create-react-app my-app cd my-app npm run eject
プロジェクトの設定
ディレクトリ構成を考える
こんな感じになりしました。「これが正解!」というパターンは無いようなので、あくまで一例として。
ディレクトリ構成(クリックで開く)
<root> ├─public 画像ファイルなど。srcから見たとき、"/"がpublic直下 ├─build ビルドされたファイルが作成される先 ├─config create-react-appで勝手の作成される ├─scripts create-react-appで勝手の作成される+自作のnpmスクリプトを作る場合はここ ├─node_modules └─src src以外のファイルはほとんど触らない ├─i18n メッセージのyamlと、それを管理するJS ├─modules actionとreducerをまとめたもの ├─containers 1画面1コンテナという考え方で、コンテナコンポーネントを作成する。 ├─comopnents コンポーネント。画面ごとにサブディレクトリを切る ├─sagas 非同期処理を扱う処理 ├─scss .scssファイル └─utils 共通で使う処理。Validatorなどimportを絶対パス指定でできるようにする
これデフォルトじゃないんかい。。。と思いながら設定しました。
方法はいくつかあるようですが、TypeScriptを使っているのでtsconfigで設定できる方法を採用しました。
これをやらないと、"../../../../"地獄になります。いつものnpm i --save tsconfig-paths
tsconfig.json{ "compilerOptions": { "baseUrl": "src" ~~~後略~~~ }メッセージ管理方法を考える
yamlでメッセージ管理できるようにする
yaml-flat-loaderというライブラリを使用して、yamlをjsonとして読み込めるようにします。
いつものnpm i --save-dev yaml-flat-loader
webpackconfig.jsのrulesに追記{ test: /\.yml$/, use: [{ loader: 'json-loader' }, { loader: 'yaml-flat-loader' }] },メッセージのキーを補完できるようにする
yamlでメッセージ管理する弊害として、コピペがし難いこともあり、補完できるように一手間加えました。
メッセージのキーをオブジェクトとして参照できるようにすることで、メッセージキーの変更やキーの指定ミスがコンパイルエラーになるというメリットもあります。
react-intlの正しい使い方をしているなら、こちらの記事が参考になりますが、JSでデフォルトメッセージを定義して、各言語はyamlというのは面倒です。
メッセージはyamlファイルだけで完結したいので、yamlファイルのキーをjsonにします。スクリプトを作成し、messageKeysというオブジェクトが各ファイルから参照できるようにします。
ja.ymlに存在するキーをJSONにしています。
スクリプト詳細(クリックで開く)
まずは必要ライブラリのインストールnpm install --save-dev js-yamlscripts/messageGen.js'use strict'; var yaml = require('js-yaml'); var fs = require('fs'); // Get document, or throw exception on error try { let doc = yaml.safeLoad(fs.readFileSync('src/i18n/ja.yml', 'utf8')); let str = "export const messageKeys ="; str += JSON.stringify(keyToValue("", doc)); str += ';\n'; str += "export default messageKeys;" fs.writeFile("src/i18n/messageKeys.tsx", str, (err) => { if (err) { throw err; } }); } catch (e) { console.log(e); } function keyToValue(parentKey, param) { let ret = param; for (let [key, value] of Object.entries(param)) { if (typeof value === 'object') { keyToValue(parentKey + key.toString() + ".", value); } else if (typeof value === 'string') { ret[key] = parentKey + key; } } return ret; }package.json(抜粋)"scripts": { - "start": "node scripts/start.js", + "start": "node scripts/messageGen.js && node scripts/start.js", + "message": "node scripts/messageGen.js" },使用例import messageKeys from "src/i18n/messageKeys"; ~~~中略(コンポーネント内)~~~ const intl = useIntl(); intl.formattedMassage({ id: messageKeys.some.message.id });"npm run message"でsrc/i18n/ja.ymlに存在するキーをjsonにしてくれます。
※エディタ上で補完できるようにするには、messageKeys.jsを一度開く必要がありました。スタイリング方法を考える
こちらの記事を参考に、"1. クラス名によるスタイリング"と"3. CSS Modules"で悩みましたが、CSS(SCSS)によるスタイリングを採用しました。
理由は、
- 業務ロジックやイベント処理を記述しているコンポーネントファイルに、見た目のことまで書きたくない
- 普通のSCSSの方が書ける人が多い
- ぶっちゃけCSS Moduleよくわからなかった
あたりです。
ただし、これには以下のデメリットがあります。
- 普通にscssをimportすると、Reactはstyleタグを生成するので影響範囲は全体ということになる
- 上記動作により、複数回importされると無駄なstyleタグが増えることになる
よって、コンポーネントのrootとなる要素にclassを設定し、その下にネストしたSCSSを書くようにしました。
(結局は開発者の運用努力なので、改善すべき部分ではあります。)結び
まだまだ色々なことをやった(ような気がします)が、以上が開発開始時の"進め方"を決める段階で時間をかけて考えた部分になります。
これからReactの開発を始める際の参考になれば幸いです。
- 投稿日:2020-02-17T21:45:04+09:00
【React】フォーカスアウトするとカンマ区切りで表示される入力欄
経緯
- 数値を入力する項目がある
- 桁が大きくなると額がわからなくなる
- だからカンマ区切りで表示したい
- それを実現可能な要素はHTMLには無い
- input=text だと数値以外を入力できる
- input=number だとカンマを表示できない
- じゃあ作ろう
仕様
- input=textをベースとする
- 入力値は右寄せで表示する
- バリデーション機能を追加する
- 正の数、および負の数を入力可とする
- マイナスは先頭のみ入力可とする
- 小数点は一度のみ入力可とする
- 小数点の前には必ず数値の入力があるものとする
- 末尾が小数点で終わる場合を許容する
- 未入力、及びバリデーション通過時の背景色を #FFF とする
- バリデーション不通過時の背景色を #FFBEDA とする
- 入力欄にフォーカス時に入力した文字列をそのまま表示する
- 入力欄からフォーカスアウト時にバリデーションを通過した場合はカンマ区切りの文字列を表示する
- 入力欄からフォーカスアウト時にバリデーションが不通過となる場合は入力した文字列をそのまま表示する
import React, { useState } from 'react'; const InputText = ({ ...rest }) => { const [value, setValue] = useState(''); const [isFocus, setFocus] = useState(false); const handleFocus = () => setFocus(true); const handleBlur = () => setFocus(false); const handleChange = e => setValue(e.target.value); const isBlank = value === ''; const isValid = /^[-]?(\d+)[.]?(\d+)?$/.test(value); const displayValue = (() => { if (isFocus || !isValid) { return value; } if (isValid) { return (+value).toLocaleString(); } return ''; })(); const displayStyle = { textAlign: 'right', backgroundColor: isValid || isBlank ? '#FFF' : '#FFBEDA' } return ( <input {...rest} type="text" onFocus={handleFocus} onBlur={handleBlur} onChange={handleChange} value={displayValue} style={displayStyle} /> ); } export default InputText;
- 投稿日:2020-02-17T21:16:28+09:00
Chrome拡張機能でYoutube検索を楽にする
私はゲーム音楽を聞いていつも癒やされてます。
みんなで決めるゲーム音楽ベスト100のwikiを眺めて知らない曲をYoutubeで試聴したりするのですが、いちいち曲名をコピペして検索するのが面倒でした。
google翻訳の拡張機能を見てこんな風にYoutube検索できたら楽そうと言うことで作りました。前回と同じ部分は説明を省きます。
完成物
https://github.com/engabesi/context-search
やること
- コンテクストメニューに検索機能追加
- ポップアップボタン作成
作成&解説
作っていきます。chrome.*APIに関しては簡単な解説を入れます。
manifest.json
まずは
manifest.json
を作成します。manifest.json{ "manifest_version": 2, "version": "1.0", "name": "Context Youtube Search", "description": "Add Youtube Search to Context Menu", "icons": { "16": "icons/icon16.png", "48": "icons/icon48.png", "128": "icons/icon128.png" }, "content_scripts": [ { "matches": ["http://*/*", "https://*/*"], "css": ["searchStyles.css"], "js": ["search.js"] } ], "background": { "persistent": false, "scripts": ["background.js"] }, "permissions": ["contextMenus", "storage"], "options_page": "options/options.html" }今回新しく出てきた要素の簡易説明
詳細かつ正確に知りたい場合は公式Docへ
- content_scripts
matches
にマッチしたURLのページ内でcss
とjs
に記載したものを実行&適用- background
scripts
に記載したjsをbackgroundで実行するpersistent
をfalseにすると必要な時のみjsを実行する- trueにするとずっと動いてメモリを食い続ける
- 余程の理由がある場合を除きfalse
- permissions
- 使いたい
chrome.* API
をここで宣言tabs
に関しては宣言不要- options_page
- そのまんまoption_pageのhtml
ContextMenu
それではコンテクストメニューに自作項目を追加します。
manifestのbackgroundに記載したjsに書き込んでいきます。background.js// 以下の時に実行される。 // 最初のインストール時、拡張機能 or Chrome自体のバージョン更新時 chrome.runtime.onInstalled.addListener(() => { // コンテクストメニューの項目を作成、追加します。 // runtime.onInstalled内に記載し、無駄に実行されるのを防いでいます。 chrome.contextMenus.create({ id: "youtube_search_menu", title: "Youtubeで「%s」を検索", // 文字が選択されている時のみ表示されてほしいのでselectionを指定 contexts: ["selection"] }); }); // クリックイベント。 // 今回は項目が一つだけなので問題ありませんが複数項目を追加する場合、 // クリックされた項目のIDをチェックしてあげないといけません。 // createのoptionでclickイベントを紐付ける事も可能ですが、 // persistentをfalseにしている場合この書き方でないとエラーを吐いて動きません。 chrome.contextMenus.onClicked.addListener(info => { const query = info.selectionText; if (query.length > 300) { alert("文字が多すぎます。"); return; } const url = `https://www.youtube.com/results?search_query=${query}`; // urlを指定すると新しいタブが立ち上がりそこで指定したURLを開いてくれます。 chrome.tabs.create({ url: url }); });これだけです。
存在しないファイルがあるとエラーを吐く為、manifest.json
に記載したまだ作成していないファイルについては空ファイルを作ります。
後はインストールし、何か文字列を選択して右クリックするとYoutubeで検索の項目が追加されています。ポップアップボタン
クリック数を減らす為に文字を選択した際にポップアップでボタンが出るようにします。
content_scripts
に記載したjsに書きます。
ちょっと長いですが全文貼ります。search.js// localStorageと一緒 // localをsyncにすると別PCでもGoogleアカウントが同じであれば共有されます。 // getの第一引数で取りたい値を指定。default値もつけられます。 chrome.storage.local.get({ isPopupEnable: true }, result => { if (!result.isPopupEnable) return; let searchQuery = ""; const setSearchQuery = text => (searchQuery = text); const getSearchQuery = () => searchQuery; const popUpId = "cys-search"; const popUpIconClass = "cys-search-icon"; const createPopUpHtml = () => { const pop = document.createElement("div"); pop.id = popUpId; pop.style.position = "absolute"; const icon = document.createElement("div"); icon.className = popUpIconClass; pop.appendChild(icon); return pop; }; const popUp = createPopUpHtml(); const addPopupEvent = () => { popUp.addEventListener("mousedown", e => { e.preventDefault(); e.stopPropagation(); }); popUp.addEventListener("mouseup", e => { e.stopPropagation(); window.open( `https://www.youtube.com/results?search_query=${getSearchQuery()}` ); document.getElementById(popUpId).remove(); }); }; addPopupEvent(); const addDocumentEvent = () => { let [fromX, fromY] = [0, 0]; document.addEventListener("mousedown", e => { const popElm = document.getElementById(popUpId); if (popElm) popElm.remove(); [fromX, fromY] = [e.pageX, e.pageY]; }); document.addEventListener("mouseup", e => { // input, textarea, contentEditable属性が付与されている場合 // ボタンを表示しないようにする const activeElement = document.activeElement; if ( ["INPUT", "TEXTAREA"].includes(activeElement.tagName) || activeElement.attributes.getNamedItem("contentEditable") ) return; const selectionText = document.getSelection().toString(); if (selectionText === "" || selectionText.length > 300) return; const [clickedX, clickedY] = [e.pageX, e.pageY]; if (fromX === clickedX && fromY === clickedY) return; const popElm = document.getElementById(popUpId); if (popElm) return; setSearchQuery(selectionText); const posX = computePositionOffset(fromX, clickedX); const posY = computePositionOffset(fromY, clickedY); setElementPosition(popUp, posX, posY); document.body.appendChild(popUp); }); }; addDocumentEvent(); /** 移動方向からオフセットを設定 */ const computePositionOffset = (from, to) => (from > to ? to - 20 : to + 5); const setElementPosition = (element, x, y) => { element.style.left = `${x}px`; element.style.top = `${y}px`; }; });次にCSS
searchStyles.css#cys-search { background-color: rgb(245, 245, 245); box-sizing: content-box; cursor: pointer; height: 19px; width: 19px; z-index: 2147483647; border-width: 1px; border-style: solid; border-color: rgb(220, 220, 220); border-image: initial; border-radius: 5px; padding: 3px; } .cys-search-icon { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAADT0lEQVRoQ+1ZTWhUVxg9575JJjNESxMNptgoQ7BQVDCjpMsslToLlfusSAotpWs34sKNLsWVuhPBlQQdCC4UXboSFzFUEMpImhmjGLBYq4ZMYua9I2+SCfWn0fGNY67k27zNvd93zv3u/d757iUcNzqOHysEPncGVzIggGO7drWaubm29nS6bdqYFJPJFIAUwjBZzZAxswDKmp0tp8OwPDU9PRO2tMz0Xrv2koDiZPG9GbhkrddXqXQmPK839LxeI/WEwAZIG0j2AOgG0B7BrBNICGAKwKSkCZD3DXA/JCdMEIxVgmBsNJF44ufzwVJ+3yLw2Nr2MvAdgCzIvkDaTmATyFV1Aow3XHoh4J5HjkAaBXA7BRS68vmI9KItEoiAP5O2e+QBkrsBfBMPQcNnP5J0JZCGviJHakSqBEp793YHnnfAkL9jfvWXsxVC6awXBEMbh4cnOZLNtnRkMgMEDouMvi3LGb2AOUo3BJz8Z3z8Bh9Y21uRfiN5UOT65Qy+ho3SQ0kXEuQ5Fvft64cxR0DurJY+N6wM6TrC8ASL1u4HeQjAD25gX0R5C8Axlqw9GpI/EdjsEgEBd01EoGjteZADADa6RCAqnoF0nCXfv6T57fOtYwQecIHAVUnbQEaSII49lXQTxhQgbYXUz0/595YmsUAgqqnfA1gbBz2ApwAuQzptguC5jDkI8meQGdWvkz4Eyt8kj3Hc2hGSGQBff8isJcZUCYg8lbl48c6jXC49k0xmYcwggRyAdTH9vzk9ilc9xH+CjHTP6pgBXiNQ8/Vwz57OiuftDMlfDNmveeXaCHtePQNF358A0AkgHdPrOwnUfP5lbY8HDAr4NdpWMWNF06cBVKvQk4VVaY3pdEkCke/iwEBb0NGR9YyJSOwG2RUj5svqFhq3dpZk4iMaknftycUzsBSwQi63pjWV+hFRNoAdHylhQn0RBJzfQs4fYufLaKN/ZHPS6U35/B9N+5GVfL8hUoI1KREEZyg9a5qUKPl+Y8Sc9C+AmyALArY0U8y5Lafdb2i+gJbS7abe+WsV5y+2nL9ajGSv05e7Nd3u9PX6f5sPZx84/q+Dcu6Jqd4eddk/8tVLqNnj3/tK2WxA9cZbIVDvijV6vPMZeAUgiiBtP5u7QwAAAABJRU5ErkJggg==); background-size: 19px; width: 19px; height: 19px; }これで文字選択時にボタンがポップアップされ、クリックするとYoutubeの検索画面に飛ぶようになったはずです。
ただ残念なことにgoogle翻訳のアイコンボタンと被ってしまうことが多い…
offset値が違うからか完全に重なることはないので放置。オプションページ
拡張機能の設定画面を作ります。
ポップアップボタンのON/OFFが欲しいのでそれだけ作成。
見た目を捨ててさくっとhtmlとjsを作成。options/options.html<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Context Youtube Search Options</title> </head> <body> <h1>ポップアップのON/OFF</h1> <form> <div> <input type="radio" id="popupTrue" name="popup" value="true" /> <label for="popupTrue">ON</label> <input type="radio" id="popupFalse" name="popup" value="false" /> <label for="popupFalse">OFF</label> </div> <div><button type="submit">Save</button></div> </form> <script src="options.js"></script> </body> </html>options.jsconst form = document.querySelector("form"); form.addEventListener("submit", e => { saveOptions(); e.preventDefault(); }); const toBoolean = str => (str.toLowerCase() === "true" ? true : false); const saveOptions = () => { const isPopupEnable = toBoolean(form.popup.value); // setで保存 chrome.storage.local.set({ isPopupEnable: isPopupEnable }); }; const fetchOptions = () => { // getで取得 chrome.storage.local.get({ isPopupEnable: true }, result => { if (result.isPopupEnable) { form.popup[0].checked = true; form.popup[1].checked = false; } else { form.popup[0].checked = false; form.popup[1].checked = true; } }); }; fetchOptions();これでオプションページを作成できました。
まとめ
使ってるうちに検索するサイト(URL)を設定画面で追加できたら利便性上がるなって感じたのでそのうち機能追加します。
ゲーム音楽いいよ!聞いて気に入ったらOST買ってね!(発売されてないのも多いけど)
- 投稿日:2020-02-17T19:45:14+09:00
GASのV8では [JavaObject] が [Object] になる
概要
GAS で V8ランタイムが使えるようになった件について:(V8 Runtime Overview)
G Suite Services の FoobarApp 系オブジェクトは
Object.prototype.toString.call()
でタイプ名を取得すると
従来の Rhino では[object JavaObject]
が返ってきましたが・・・V8ランタイムに切り替えると
[object Object]
になります。確認
function foo(){ var objects = { calendar : CalendarApp.getDefaultCalendar(), folder : DriveApp.getFolderById( 'root' ), file : DriveApp.createFile( 'test', '' ), document : DocumentApp.create( 'test' ), spread : SpreadsheetApp.create( 'test' ) } for ( item in objects ){ console.log( item + ' : ' + Object.prototype.toString.call( objects[ item ] ) ); } /* --- Rhino --- calendar : [object JavaObject] folder : [object JavaObject] file : [object JavaObject] document : [object JavaObject] spread : [object JavaObject] --- V8 --- calendar : [object Object] folder : [object Object] file : [object Object] document : [object Object] spread : [object Object] */ }影響
[object Object]
になることで、従来使えなかったメソッドが使えたりします。
例えばプロパティ名の列挙に使うObject.getOwnPropertyNames()
:function bar(){ var obj = CalendarApp.getDefaultCalendar(); console.log( Object.getOwnPropertyNames( obj ).length ); /* --- Rhino --- TypeError: オブジェクト型の引数が必要ですが、object 型が返されました。 ↑ 何言ってんのお前 --- V8 --- 28.0 */ }注意
型名チェックに
=== '[object JavaObject]'
などとやっている場合は
挙動が変わるので修正が必要です。その他
従来の Rhino から V8 へ切り替える為のポイントがまとめられていますが
この件についてはどこにも記載を見つけられませんでした。少なくとも今は。Migrating scripts to the V8 runtime
(Qiita) GASがV8エンジンに対応したのでドキュメント読んだ以上
- 投稿日:2020-02-17T19:01:06+09:00
npm run tscした時にnpm ERR! missing script: tscと出た時の対処法
- 投稿日:2020-02-17T17:43:30+09:00
【JavaScript】第2引数でコールバックする関数をどうにかしたい
とりあげる課題とその問題点
今回取り上げるものは以下のようなコード
実例はkuromoji.js(A: T, B: VoidFunction) => voidkuromoji/example/load-node.js"use strict"; var kuromoji = require("../src/kuromoji"); var DIC_DIR = "dict/"; // Load dictionaries from file, and prepare tokenizer kuromoji.builder({ dicPath: DIC_DIR }).build(function (error, tokenizer) { // ここが問題のコールバック var path = tokenizer.tokenize("すもももももももものうち"); console.log(path); module.exports = tokenizer; }); // ここでtokenizerは使えないこのtokenizerはこのままではコールバック関数外では利用できない。
これはスコープの関係上当たり前だが、少々使いづらい。しかもコールバック地獄の原因なので使いたくない
というわけでこれを
testconst res = (await tokenizer) .tokenize('すもももももももものうち') .map(t => t.surface_form); expect(res).toEqual(['すもも', 'も', 'もも', 'も', 'もも', 'の', 'うち']);このようにfunctionalに書きたい。
解決策
new promiseする
typescriptimport kuromoji, { Tokenizer, IpadicFeatures, TokenizerBuilder } from 'kuromoji'; const builder: TokenizerBuilder<IpadicFeatures> = kuromoji.builder({ dicPath: 'node_modules/kuromoji/dict', }); export const tokenizer = new Promise<Tokenizer<IpadicFeatures>>(done => { builder.build((_err, tokenizer) => { done(tokenizer); }); });こうすると
testconst res = (await tokenizer) .tokenize('すもももももももものうち') // 呼べる .map(t => t.surface_form); expect(res).toEqual(['すもも', 'も', 'もも', 'も', 'もも', 'の', 'うち']);まとめ
async/await世代なので少し詰まった。
rxjsあたりを使うともっとスマートに書くことができると思う。
- 投稿日:2020-02-17T15:43:38+09:00
JavaScriptでリクエストパラメータをオブジェクトに変換する
コピペで使えます
function convParamToObj() { var search = location.search.substring(1); if (search) { return JSON.parse('{"' + decodeURI(search.replace(/&/g, "\",\"").replace(/=/g, "\":\"")) + '"}') } return {}; }それぞれの説明
var search = location.search.substring(1);location.search // ?hoge=hoge&fuga=fuga location.search.substring(1) // hoge=hoge&fuga=fugaリクエストパラメータを取得しています
return JSON.parse('{"' + decodeURI(search.replace(/&/g, "\",\"").replace(/=/g, "\":\"")) + '"}')ここは細かく確認します
search.replace(/&/g, "\",\"") // すべての & を "," に置換 search.replace(/=/g, "\":\"") // すべての = を ":" に置換 search.replace(/&/g, "\",\"").replace(/=/g, "\":\"")正規表現を用いて、リクエストパラメータを下記のように置換しています
hoge=hoge&fuga=fuga
hoge":"hoge","fuga":"fugadecodeURI(search.replace(/&/g, "\",\"").replace(/=/g, "\":\""))リクエストパラメータがエスケープされている場合、文字列をデコードするためにdecodeURIを使用しています
'{"' + decodeURI(search.replace(/&/g, "\",\"").replace(/=/g, "\":\"")) + '"}'{" と "} で先ほどの文字列を挟むとJSONの形になりますね!
hoge":"hoge","fuga":"fuga
{"hoge":"hoge","fuga":"fuga"}JSON.parse('{"' + decodeURI(search.replace(/&/g, "\",\"").replace(/=/g, "\":\"")) + '"}')最後にJSON.parseでJSONをオブジェクトに変換します
これでリクエストパラメータからオブジェクトへの変換ができました!
参考:
https://stackoverflow.com/questions/8648892/how-to-convert-url-parameters-to-a-javascript-object
- 投稿日:2020-02-17T15:30:46+09:00
正規表現と仲良くなろう!
正規表現とは??
正規表現とは、文字列の集合を一つの文字列で表現する方法の一つです。
この表現方法を利用すると、多くのデータの中から簡単に見つけたい文字列を検索できます。/^\d{3}-?\d{4}$/ ←この暗号解読できますか??
この記事を読み終えるころには読めるようになります!
正規表現初心者向けなので読める方、理解できる方はこの記事はおすすめしません。
今回使うメソッド&メタ文字
メソッド名 説明 match 文字列中で一致するものを検索し結果を配列で返します。 正規表現は メタ文字 を使い様々なパターンを検索することができます!
メタ 説明 ^ 入力の先頭にマッチします。 $ 文字列中で一致するものを検索し結果を配列で返す。 \ バックスラッシュ次の文字が特別なもので、文字通りには評価されないことを表します。 ? 直前の文字の 0 回か 1 回の出現にマッチします。{0,1}に相当します。 ではもう一度見てみましょう
JavaScriptlet postal_code = '123-4567'; let result = postal_code.match(/^\d{3}-?\d{4}$/); console.log(result) => Array ["124-5607"]お気づきの方もいらっしゃるかと思いますが正しい郵便番号かどうかを確認しています!
JavaScriptlet postal_code = '123-45'; let result = postal_code.match(/^\d{3}-?\d{4}$/); console.log(result) => nullざっくり解説
① // (の内側に正規表現を定義)
② /^\d/ (先頭の文字が数字)
(\とすることで特殊文字を使えるようになる\dは数字にマッチします。[0-9] に相当。)
③ /^\d{3}/ (先頭が数字で3桁)
④ /^\d{3}-?/ (ハイフンがあってもなくてもマッチ)
⑤ /^\d{3}-?\d{4}$/ ($は行末を表すのでこれで"4桁"になる)
JavaScriptlet postal_code = '1234567'; let result = postal_code .match(/^\d{3}-?\d{4}$/); console.log(result) => Array ["1234567"] 'なくてもマッチした' ------------------------------------------------- '末尾に$がないと' let postal_code = '123-45678'; let result = postal_code .match(/^\d{3}-?\d{4}/); console.log(result) => Array ["123-4567"] 'マッチしちゃってる' ------------------------------------------------- '末尾に$をつけると' let postal_code = '123-45678'; let result = postal_code .match(/^\d{3}-?\d{4}$/); console.log(result) =>null 'ちゃんとnullを返した!'その他にも沢山のパターンを作ることができますので気になる方は是非公式レファレンスで調べてみてください
さいごに
一応調べながらJavaScript Demo: RegExp Constructorを使い検証もしていますが初学者なので間違っているところあれば教えてください(優しめで。。)
- 投稿日:2020-02-17T15:22:51+09:00
【JavaScript】配列の取り出し・追加を行う4つのメソッド
.shift()メソッド
.shift()は0番目のインデックスの要素を削除して、後続のインデックスの値を0番目のインデックスにシフトし、削除した値を返すメソッド。
shiftconst arr = [100,200] console.log(arr.shift()) //配列から削除した100が返される console.log(arr) //配列に現在ある要素は[200] console.log(arr.shift()) //配列から削除した200が返される console.log(arr) //0番目のインデックスの要素を削除したため、配列は空 console.log(arr.shift()) //配列から削除する要素がないためundefinedになる実行結果
[ 200 ] 100 [] 200 [] undefined.unshift()メソッド
.unshift()は配列の最初に 1 つ以上の要素を追加し、追加後の要素数を返すメソッド。
unshiftconst arr = [100,200,300] console.log (arr.unshift('AAA'))//配列の最初にAAAが追加される console.log (arr)//追加後の要素数を返す console.log (arr.unshift('BBB','CCC'))//複数追加の場合、記載順に追加される console.log (arr)//追加後の要素数を返す実行後
4 [ 'AAA', 100, 200, 300 ] 6 [ 'BBB', 'CCC', 'AAA', 100, 200, 300 ].pop()メソッド
.pop()は配列から最後の要素を取り除き、その取り除いた要素を返すメソッド。
popconst arr = [100,200] console.log(arr.pop())//配列の最後の要素200が返される console.log(arr)//配列の最後の要素を削除したため、現在ある要素は[100] console.log(arr.pop())//配列の最後の要素100が返される console.log(arr);//配列の最後の要素を削除したため、配列の中身は空 console.log(arr.pop())//削除する要素がないためundefinedになる実行結果
200 [ 100 ] 100 [] undefined.push()メソッド
配列の最後に 1 つ以上の要素を追加し、追加後の配列の要素数を返すメソッド。
const arr = [100,200,300] console.log(arr.push('AAA'))//配列の最後に'AAA'が追加される console.log(arr)//追加後の配列の要素数を返す console.log(arr.push('BBB','CCC'))//複数追加の場合、記載順に追加される console.log(arr)//追加後の配列の要素数を返す実行結果
4 [ 100, 200, 300, 'AAA' ] 6 [ 100, 200, 300, 'AAA', 'BBB', 'CCC' ]
- 投稿日:2020-02-17T14:01:48+09:00
Lambda内からcloudfrontへのaxiosリクエストが403になる
ニッチなネタですが、同じくお困りの方がいらっしゃったらご参考にどうぞ
アーキテクチャ
cloudfront -> api gateway -> lambda(nuxt)この環境で
nuxt(axios) -> (out bounds) -> HTTP API(cloudfront)このようにaxiosを利用して外部のサービスで、そのサービスがcloudfrontを利用している場合、その通信は403となり失敗します。
原因
axiosでリクエストする際に付与しているヘッダー情報に原因がありました。
via
です。
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Viaこれはcloudfrontで付与されて、そのままapi gateway、lambdaへと引き継がれ、axiosのデフォルトヘッダーに設定されていました。
改善
cloudfrontでは多段構成を禁止する仕組みとしてこのviaを利用しているようで、このヘッダーを消すことで無事疎通が可能となりました。
nuxt(plugins)export default ({ $axios }: Context) => { $axios.defaults.headers.common = {} }※この例では初期設定値をすべて消しています。
- 投稿日:2020-02-17T11:42:28+09:00
Reactで画像をlazyloadしつつpreloadするコンポーネント
FirstViewにはいらんけど、スクロールして登場するまでにはなるべく存在してほしいみたいな画像があったので作ってみました。
正直こんな雑なやり方で合ってるのか分からんのでもっといい方法知ってる人がいたら教えてください頼みます。import * as React from 'react'; type Props = { imgUrl: string; alt: string; }; export default function LazyAndPreloadImage(props: Props) { const [loaded, setLoaded] = React.useState(false); React.useEffect(() => { // preload しておく const img = new Image(); img.src = props.imgUrl; // 読み込めたら img タグ描画する img.onload = () => { setLoaded(true); }; }, []); if (loaded) { return <img src={props.path} alt={props.alt} />; } // 雑に空divにしてるけど思い思いのplaceholder入れてください return <div></div>; }
- 投稿日:2020-02-17T10:54:19+09:00
EXCELファイルをバックアップし、DBに格納【Node.js Express】
エクセルファイルをアップロードしたら、バックアップを作成し、中身のデータをデータベースに登録するAPIを作りました。また似たような作業することになりそうなので、メモ。
Node.jsとExpress、データベースはMysqlを使用使うもの
1)multer
アップロードされたエクセルファイルを特定のファイルにコピーする。
※ただし、multipart/form-dataのみ
https://www.npmjs.com/package/multerインストール:
npm install --save multer
2)sheetjs
エクセルファイルの書き換え等ができる。
今回はエクセルファイルをJSONに変換するのに使用した。
https://www.npmjs.com/package/xlsxインストール:
npm install --save xlsx
実装
const express = require('express') const app = express() const models = require('../model/models') const xlsx = require('xlsx') const multer = require('multer') const storage = multer.diskStorage({ //保存先を指定 destination: function (req, file, cb) { cb(null, './uploads/excels') }, //ファイル名の先端に日付をつけて保存 filename: function (req, file, cb) { cb(null, new Date().toISOString() + file.originalname) } }) const fileFilter = (req, file, cb) => { //ファイルの拡張子を確認 とりあえず、excelとspredsheetを。 if (file.mimetype === 'application/vnd.ms-excel' || file.minetype === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') { cb(null, true) } else { cb(null, false) } } const upload = multer({ storage: storage, limits: { //アップロードできるファイル数 files: 1, //ファイルサイズ(2MB@) fieldSize: 2 * 1024 * 1024 }, fileFilter: fileFilter }) app.post('/upload' , upload.single('ファイル名'), async(req, res) => { try { const sheet = xlsx.readFile('./uploads/excels/' + req.file.filename, { cellDates: true }) const workingSheet = sheet.Sheets['シート名'] const data = xlsx.utils.sheet_to_json(workingSheet) var dataArray = [] //カラム名 var fieldNames = ['date', 'name', 'tel'] for (var i in data) { //String型を明記しないとエラーになったので。 //エクセルのカラム名?(カラム最上セルのテキスト)日本語なのがアレなので良い方法あれば教えて欲しいです。 //電話番号は -(ハイフン) を抜き取る。 var insertData = `(${JSON.stringify(data[i].日付)}, ${JSON.stringify(data[i].名前)}, , ${data[i].電話番号.replace(/-/g, '')})` dataArray.push(insertData) } const tableName = 'テーブル名' //毎回データを総入れ替えする仕様のため、全データ消す const areDeleted = await models.deleteAll(tableName) if (!areDeleted) return res.status(500).json({ message: 'エラーが発生!' }) //データを挿入 const areInserted = await models.insertData(tableName, fieldNames, dataArray) if (!areInserted) return res.status(500).json({ message: 'エラー発生!' }) return res.status(200).json({ message: '成功!' }) } catch (err) { console.log(err) return res.status(500).json({ message: 'エラーが発生!' }) } })モデル抜粋
model.jsconst mysql = require('mysql2') const models = {} // Setup database const connection = mysql.createConnection({ host: ホスト, user: ユーザー, password: パスワード, database: データベース名, port: ポート番号, multipleStatements: true //複数のコールを有効化 }) connection.connect((err) => { if (!err) console.log('接続成功') else console.log('接続失敗 : ' + JSON.stringify(err, undefined, 2)) }) models.deleteAll = (tableName) => { return new Promise((resolve, reject) => { connection.query(`DELETE FROM ${tableName}`, (err, results) => { console.log(results) if (err) return reject(err) else return resolve(results) }) }) } models.insertData = (tableName, fieldName, insertData) => { return new Promise((resolve, reject) => { connection.query(`INSERT INTO ${tableName} (${fieldName}) VALUES ${insertData}`, (err, results) => { console.log(results) if (err) return reject(err) else return resolve(results) }) }) } module.exports.models = models
- 投稿日:2020-02-17T10:38:57+09:00
プログラムの可読性を上げるための条件分岐を減らす方法7個
受託開発エンジニアでプロジェクトリーダーという立場になるにあたって品質保証について考えるようになりました。
品質と一口に言っても「内部品質」と「外部品質」の2点がありますが、エンジニアである以上、内部品質の向上が外部品質を上げる最適解だと思います。
※内部品質、外部品質についてはこちらを参照。
そして内部品質を上げるためにはアーキテクチャを考えることが大切ですが、既存プロジェクトの場合はアーキテクチャが
存在しないレベルでぐっちゃぐちゃ十分に検討されていないケースも多いです。そこで、一番手っ取り早くプログラムの可読性を上げる方法は、新規に書くコードをできるだけシンプルに実装することです。今回はその中でも個人的に使っている条件分岐を少なくする or 見やすくするテクニックについてまとめてみました。
条件分岐を減らす方法7個
今回の分岐を減らす方法ですが、基準としては
- 1メソッドあたりの循環的複雑度を下げる
- ネストを1段階浅くできる
のいずれかを満たしているという条件で考えています。
ガード節
まず、ネストを浅くする最もメジャーな方法としてガード節を思い浮かべた方も多いかと思います。
ガード節とは処理の対象外と条件を、関数の先頭でreturn/continueする方法のことで、その後の処理条件を限定することができるメリットがあります。
まず、悪い例から見ていくと、
sample.phppublic function hoge($a, $b) { $result = 0; if (isset($a)) { $result = 1; } else { if (isset($b)) { $result = 2; } } return $result; }あまりいい例ではありませんが、このようにifを重ねることによってネストが深くなるケースがあります。これをガード節を書き加えることによって以下のようになります。
sample.phppublic function hoge($a, $b) { if (isset($a)) return 1; if (isset($b)) return 2; return 0; }最初に
$a, $b
に値が入っていないかを判定することによって、不要なelseを各必要がなくなります。
条件分岐を減らしたいと考えたときに真っ先に検討する方法の1つです。bool判定
続いて true/false 判定をする際にも余計な条件分岐が書かれているケースも少なくありません。
具体的な悪いコードは以下の通りです。sample.phppublic function hoge($a) { if ($a === '') { return true; } return false; }基本的にtrue/falseを取得したい場合はその条件を変数を返すだけで事足ります。
sample.phppublic function hoge($a) { return $a === ''; }配列利用
続いて配列を使って条件分岐を減らす方法もあります。
個人的にはswitch文を使おうかなというケースには特に有効な方法になるかと思います。では、具体的な悪いコードを見ていきましょう。
sample.phppublic function hoge($x) { $a = 0; switch ($x) { case 0: $a = 1; break; case 1: $a = 3; break; case 2: $a = 5; break; } return $a; }決して悪い書き方ではありませんが、配列を使って以下のように書き換えることができます。
sample.phppublic function hoge($x) { $array = [ 0 => 1, 1 => 3, 2 => 5, ]; return $array[$x]; }こちらの方がシンプルでいいですね!
仮にswitch文に「defaultに入ったときは0を返す」といった条件があったときもnull合体演算子を使ってシンプルに書くことができます。
sample.phppublic function hoge($x) { $array = [ 0 => 1, 1 => 3, 2 => 5, ]; return $array[$x] ?? 0; // $xが0, 1, 2でないときは$array[$x]がnullのため0が返される }メソッド分割
続いて根本的な解決ではありませんがメソッドを分割して、1つのメソッドあたりの循環複雑度を減らす方法があります。
例えば以下のようなコードがあったとします。public function hoge($array) { $result = []; foreach ($array as $item) { if ($item) { array_push($item); } } return $result; }このif文を別メソッドに切り分けることでコードの可読性を上げることができます。
sample.phppublic function hoge($array) { $result = []; foreach ($array as $item) { $this->fuga($result, $item); } return $result; } private function fuga(&$result, $item) { if ($item) { array_push($item); } }正直この例ではあまりメリットが分かりづらいですが、例えば「ある特定の条件結果がtrue/falseによって処理を分ける」といったケースでは可読性が上がるケースがあります。
sample.php// 変更前 $result = ''; if ($condition) { if (...) { foreach (...) { // TODO } } } else { foreach ($array as $item) { // TODO } } // 変更後 $result = $condition ? $this->hoge() : $this->fuga();条件分岐を減らすテクニックというよりは、きちんとモジュール化しましょうという話なので少し毛色は違いますが立派なテクニックと言えるかと思います。
三項演算子の活用
人によって意見が分かれますが三項演算子を使うのもネストを浅くする1つの選択肢になります。
具体的に以下のコードを見ていきましょう。sample.php$result = 0; if ($condition) { $result = 1; } else { $result = 2; }三項演算子を使えば以下のように書くことが可能です。
sample.php$result = $condition ? 1 : 2;ただし三項演算子は読み手によってはわかりにくいと言われることも少なくありません。
したがって、三項演算子を使う場合にも複雑な処理を書かないことは頭に入れておく必要があるかと思います。型定義の実装
また、型定義が行える言語であるにも関わらず型を定義していないという場合もあるかもしれません。
例えば、PHPは7系から型定義ができますし、TypeScriptなどでもanyをできるだけ使わないようにすることで分岐を減らすことができます。sample.phppublic function hoge($id) { if (!is_int($id)) { throw new \Exception('id must be type int'); } $sql = "SELECT * FROM user WHERE user_id = {$id}"; ... }型定義は以下のように実装することができます。
sample.phppublic function hoge(int $id) { $sql = "SELECT * FROM user WHERE user_id = {$id}"; ... }型定義によってどれだけエラー発生が減らせるか?については以下のスライドも参考になります。
PHP7 で堅牢なコードを書く - 例外処理、表明プログラミング、契約による設計言語特有の演算子活用
最後に言語特有の演算子を活用したり言語特性を生かすことによって条件分岐を減らすことができます。
例えばPHPであれば、null合体演算子を活用して以下のようにコードを変更することができます。sample.php// 変更前 public function hoge($a) { $result = 0; if (!is_null($a)) { $result = $a; } return $result; } // 変更後 public function hoge($a) { return $a ?? 0; }他にもJavaScriptでは以下のように書くことができます。
sample.js// 変更前 const hoge = value => { let result = 0; if (!!value) { result = value; } return result; } // 変更後 const hoge = value => { return value || 0; }追記
@aliyome さんにコメントをいただきましたが、正しくnull判定を行うためには以下が正しいコードになります。sample.php// 変更後 const hoge = value => { return value ?? 0; }ただし、JavaScriptはundefinedもfalthyなため、nullやundefinedを一括で判定したい場合には論理OR演算子
||
を活用してください。このように言語特性を生かすことによってネストを浅くする方法がないか?は一度探してみてもいいのではないかと思います。
他にもこんなテクニックあるよ!という方は、ぜひ追記したいのでコメントをお願いします!
- 投稿日:2020-02-17T10:18:47+09:00
画像の代わりにaltのテキストを表示する
スマホの時だけ画像非表示にしてくださいってたまに言われます
本当はスマホ用の画像を用意した方がいいのですが時間や予算がまったくなかったりするときのための方法
元の状態
See the Pen
wvaGyLX by sphenisc (@sphenisc)
on CodePen.
完成版
See the Pen
ZEGWrgd by sphenisc (@sphenisc)
on CodePen.
imgタグには疑似要素を付けられないのでimgの上のh4にafterを付けてJSで imgのalt → afterのdata-label へと中身を移してからCSSのafterのcontentとして表示しています。
alt属性はそもそも画像が表示できない場合の代替テキストを書くための場所なのでSEO的にも問題なしJSなしでやりたい場合
See the Pen
jOPqzPd by sphenisc (@sphenisc)
on CodePen.
- 投稿日:2020-02-17T07:40:30+09:00
使えるようになって(いて)欲しいプログラミング言語
はじめに
私はすでに全く若手ではないのだが、個人的な意見として、また新たに入ってくる技術者に言っていることを書いてみる。勿論賛否あって良い。
何気に私はいわゆる組込み系の仕事をしているのだが、ソフトウェア・オリエンテッドであまり組込みっぽくない人種です。
使えるようになって(いて)欲しいプログラミング言語
概ね以下の3種類でそれぞれ1つずつ。
システム記述系
こういう括りが正しいかは別として、C/C++、Java、C#など。
プログラムの動きとか、コンピューターの動きとか、きちんと知るにはCが最適。アセンブラとは言わない。アセンブラでもいいけど、プロセッサごとに違うしね。概念は知ってると良い。
ただ、Cを実務で使うのはどうしても必要でなければ避けたいのが本音。スキルの差がモロに品質に直結するので。
後、オブジェクト指向しづらいし。それができるというのとはまた別。C++は好きだが、仕様がカオスになりすぎて完全にマニアの世界。Javaはできると実務でも使えて、オブジェクト指向にも慣れるので良いかもしれない。
C#も簡単で分かりやすかった。Windowsでしかいまいち使わないが。
スクリプト系
Ruby、Python、今更使わないがベテランはPerlか。何でも良い。何か使えれば考え方は大概一緒で応用が効くだろう。
スクリプト言語は超絶楽に何でもできる。製品開発だって個人的な業務効率化だっていける。なので、使えるようになっておくと様々な場面で重宝される。
JavaScript
というか、TypeScriptか。
クライアント系はもうこれ一択。組込み系だって最近はWebブラウザ内蔵してるし、UIやるには必需品。
デスクトップでも node.js で何でもできるし。
ただ、node.js はひとつのザ・ワールドになっていて全くライトな感じではないのがちょっと引っ掛かるが。
まとめ
上記は網羅的な形ではないが、上記の方針で取り組んでいれば、新たに何か学ばなくてはならなくなってもどこかのスキルがベースになって応用が効く。
どちらかというと言語そのものよりもライブラリやフレームワークのスキル習得のほうが重要になるが、それを使いこなす上での前提知識の習得に時間がかかるようでは心許ない所なので、ある程度応用が効く形でスキル習得していくのが良い。
その上で、技術は知識よりも実践(熟練)なので、沢山書く機会を作ることを意識してもらえれば、嬉しい。
- 投稿日:2020-02-17T00:33:21+09:00
一度入れたJavaScriptライブラリは二度とアップデートされることはない
以下はCloudflareによるレポート、JavaScript Libraries Are Almost Never Updated Once Installedの日本語訳です。
JavaScript Libraries Are Almost Never Updated Once Installed
Cloudflareは、WebページにJavaScriptやその他のフロントエンドリソースを配置するための一般的な方法である、CDNJSを支援しています。
今回我々は、CDNJSチームの許可を得て、CDNJSへのリクエストから匿名化・集約されたデータを収集し、インターネットでWebサイトがどのように構築されているかを分析することにしました。
今回のエントリでは、ひとつの疑問に焦点を当てています。
すなわち、いちどJavaScriptライブラリをサイトにインストールしたあとで、ライブラリは更新されるでしょうか?まずは地球上でもっとも人気のあるライブラリ、jQueryから始めましょう。
このチャートは、過去12か月間の、jQueryのバージョンごとのリクエスト数を表しています。2019年5月2日のリリース以降、バージョン3.4.1のリクエスト数は着実に増加しています。
しかし、古いバージョンが減少しているということはありません。
バージョン3.2.1は、チャートのはじめには一日あたり3600万のリクエストがありましたが、一年の終わりには2900万リクエストとなり、20%減少しています。
しかしこれは、平均的なWebサイトは2~4年しか保たないというリサーチの結果と一致しています。
バージョン3.4.1は着実に普及しつつありますが、しかしそれに伴って古いバージョンが減少するような傾向は全くありません。ちなみにCDNJSに入っているjQueryの最も古いバージョンは、2013年5月25日にリリースされた1.10.0です。
これはいまだに一日平均10万件のリクエストがあり、それらを使っているサイトの人気も依然として衰えていないどころか、さらに高まっています。jQueryだけの特異な現象でないことを示すために、別のプロジェクトTweenMaxも見てみましょう。
このパッケージは利用者がjQueryほど多くないため、データは一週間ごとの平均で平滑化されています。
バージョン1.20.4は1800万件のリクエストで始まり、1400万件で終わりました。
Webサイトの消滅割合と概ね同じ23%の減少です。
2.1.3の大きな成長は同時に、新バージョンのリリースが旧バージョンの人気にほとんど影響を与えないという明白な証拠を示しています。
2.1.3が2900万リクエストまで増加しても、旧バージョンへのリクエスト数は全く変わっていません。結果として言えることは、一度公開されたライブラリを使うWebサイトは永久に存在し続けるということです。
従って、Web基盤プラットフォームは、あらゆるWebをサポートするためには、どれほど古いライブラリでも無期限に提供し続けなければなりません。Cloudflareは、最新のWebにコントリビュートすることに非常に興味を持っています。
ぜひコメントで提案してください。コメント欄
「CDNJS依存関係を自動でバージョンアップするRenovate Botってのを作ってる。」
「古いライブラリ使ってる人に通知するとか。」
「Wappalyzerみたいなかんじでバージョンが古いのを一目でわかるようにするとか。」
「@major
みたいなセマンティックバージョンを提供するとか。」「マイナーバージョンで互換性なくすアプデしてくるライブラリを腐るほど見たからまあ無理だろ。」
「CDNが落ちてた時にローカルのライブラリを読ませてるんだけど、バージョン揃えるのが面倒。自動更新とかできない?」「ASP.NETだけどそういうのはある。」「jQueryにもあった気がする。」感想
要するに、継続的なんちゃらだとかリーンなんちゃらだとか言ってるのは業界の極一部でしかなく、大半のサイトは一度作ったらそれっきりということです。
実際そんな最前線を走り続けなければならないサイトなんてほとんどありませんからね。
GAFAMでも配信サイトでもない一般人のサイトが最新技術を駆使してます!なんて言われてもどうでもよくない?コメントで言っているセマンティックバージョンはこれのことです。
ざっくり言うと『互換性の無いバージョンアップはメジャーバージョンを上げろ』ですが、これが守られているライブラリなんて存在しないことは周知のとおりです。といってもさすがに今どきjQuery1は、さすがに古すぎますね。
jQuery2が出たのが2013年4月。
当時小学校に入った子供が既に中学生になっているほどの昔ですよ。あと今回見せてるのはjQueryとTweenMaxだけなので、VueとかReactとか最近のやつもちょっと見てみたかったですね。