- 投稿日:2020-09-09T23:30:15+09:00
FirebaseのCloud Firestoreと連携して掲示板を作る【nuxt.js】
FirebaseとNuxt.jsで掲示板を作りました。削除ボタンが機能しなかったり未完成の部分があります。
この記事は
- Firebaseを活用してみたい
- Nuxt.jsでアプリ作ってみたい
という人向けに書いてみました。
「こんな感じでやれば短時間でFirebaseと連携できます」的なものを記事にしました。作っていく際に参考にした記事と躓いた点も書いていきます。中途半端な出来ですが初心者向けに書いてみました!Firebaseとは何?
Firebaseはリアルタイムでデータ同期ができるモバイルプラットフォームです。Firebaseを使うとサーバを立てたり、管理や保守がいらなくなるのでフロントサイドの作成に集中できます。
NuxtJSとは何?
NuxtJSは、Vueファイルで記述できるフレームワークです。モジュール構造で拡張でき一部分から徐々に採用することが可能で、静的なページから複雑なwebアプリケーションまで、あらゆるものの作成に使用できます。
以下公式から抜粋
本質的に汎用性があり、さまざまなターゲット(サーバー、サーバーレス、または静的)をサポートし、サーバーサイドのレンダリングは切り替えることができます。
強力なモジュールエコシステムにより拡張可能で、REST や GraphQL エンドポイント、お気に入りの CMS や CSS フレームワークなどさまざまなものに簡単に接続できます。
NuxtJS は Vue.js プロジェクトのバックボーンであり、柔軟でありながら自信を持ってプロジェクトを構築するための構造を提供します。めちゃくちゃ簡易的な掲示板
できたもの
実際に利用できます!コメントしてみてください!
http://keiziban.tk/about/機能
名前、コメントの投稿がきる
作成手順
任意のフォルダーに移動して
VScode
のTerminalより
yarn
を使って以下のコマンドでNuxtプロジェクトを作成することができます。
yarn create nuxt-app cloudfireTest
を実行すると、UIフレームワークなどの機能が必要か聞かれるので選択していき、終了したら
yarn run dev
で起動させます。無事に終了したら以下のアドレスにアクセスして実行されているか見ます。
http://localhost:3000
続いて
yarn add firebaseでインストールて完了です。
今回にUIフレームワークはBootstrapVueを選択したので、こちらでどのようなスタイルになるのかを調べました。フォルダ構造
pages
pagesディレクトリに
.vue
ファイルを作成するとページのルーティングができます、index.vue
が/
になり、about.vue
が/about
でアクセスできますabout.vue<template> <div class="container"> <div class="jumbotron"> <div class="container"> <h1 class="display-3">掲示板</h1> </div> <div class="links"> <client-only placeholder="Loading..."> <Memo /> </client-only> </div> </div> </div> </template> <script> import Memo from '~/components/Memo.vue' export default { components: { Memo } } </script>以下の部分でComponents内に作る
Memo.vue
が呼び出されます。<client-only placeholder="Loading..."> <Memo /> </client-only>Components
何度も利用可能な
.vue
で構成されています。今回のFirebaseとの連携はこちらに書いていきます。まずはComponents
にMemo.vue
を追加。
Memo.vue<template> <div> <p> <b-form-input v-model="name" placeholder="名前"></b-form-input> <b-form-textarea input v-model="age" placeholder="コメント" id="button"></b-form-textarea> <b-button id="button" size="sm" variant="outline-success" v-on:click="post(); removetext()">投稿</b-button> </p> <ul v-for="(data, index) in allData" :key="data.id" class="menu-list" > <li> 名前:{{data.name}} <br> コメント:{{data.age}}<br> <b-button size="sm" variant="outline-danger" class="delete" @click="switchDelateAlarm(); getIndex(index)"> 削除 </b-button> </li> </ul> <div v-show="showDelateAlarm" id="overlay"> <div id="delateAlarm"> <p>コメントを削除します</p> <b-button size="sm" variant="outline-dark" v-on:click="closeModal"> 戻る </b-button> <b-button size="sm" variant="outline-danger" @click="switchDelateAlarm(); deleteItem(data.id)"> 削除 </b-button> </div> </div> </div> </template> <script> import firebase from "firebase/app"; import "firebase/firestore"; export default { components: {}, data(){ return{ db: {}, allData: [], name: '', age: '', showDelateAlarm: false, id: [], } }, methods: { init: () => { const config = { apiKey: "", authDomain: "", databaseURL: "", projectId: "", storageBucket: "", messagingSenderId: "", appId: "", measurementId: "" }; firebase.initializeApp(config); }, post: function(){ const testId = firebase.firestore().collection('memos').doc().id; //ユニークなIDを生成 const docRef = firebase.firestore().collection('memos').doc(testId); const setAda = docRef.set({ name: this.name, age: this.age, createdAt: new Date() }); this.get(); }, get: function(){ this.allData = []; firebase.firestore().collection('memos').get().then(snapshot => { snapshot.forEach(doc => { this.id = doc.id; console.log(this.id); const array = []; console.log(array); this.allData.push(doc.data()); }) }); }, getIndex: function() { console.log(firebase.firestore().collection('memos')); }, deleteItem: function(deleteId, getIndex) { const db = firebase.firestore(); db.collection("memos").doc(deleteId).delete().then(function() { console.log("Document successfully deleted!"); }).catch(function(error) { console.error("Error removing document: ", error); }); this.showDelateAlarm = false; this.get(); }, switchDelateAlarm: function() { this.showDelateAlarm = true }, closeModal: function(){ this.showDelateAlarm = false }, removetext: function() { this.name = ''; this.age = ''; }, }, mounted(){ this.init(); this.get(); }, } </script> <style> #delateAlarm{ z-index:2; width:50%; padding: 1em; background:#fff; } #overlay{ /* 要素を重ねた時の順番 */ z-index:1; /* 画面全体を覆う設定 */ position:fixed; top:0; left:0; width:100%; height:100%; background-color:rgba(0,0,0,0.5); /* 画面の中央に要素を表示させる設定 */ display: flex; align-items: center; justify-content: center; border-radius: 10%; } ul { display: block; margin-block-start: 1em; margin-block-end: 1em; margin-inline-start: 0px; margin-inline-end: 0px; padding: 0px; list-style: none; } #button { margin-top: 2px; } </style>Cloud Firestoreの設定
こちらの部分にファイヤーベースの設定→全般の下のほうにあるマイアプリ
からコピー&ペーストします。
Memo.vueconst config = { apiKey: "", authDomain: "", databaseURL: "", projectId: "", storageBucket: "", messagingSenderId: "", appId: "", measurementId: "" };今回はあらかじめCloud Firestoreのコレクションに
memos
作成しておきます。設定
以下は
post
の部分を抜粋しています。Memo.vuepost: function(){ const testId = firebase.firestore().collection('memos').doc().id; //ユニークなIDを生成 const docRef = firebase.firestore().collection('memos').doc(testId); const setAda = docRef.set({ name: this.name, age: this.age, createdAt: new Date() }); this.get(); },躓いた部分
削除ボタンの実装に躓きました。ボタンに紐づいたドキュメントidが取得できずボタンは配置されていますが、機能しません。
参考にした記事
- ざっくりFirestore + Vue.jsの使い方
- 【v2対応】Nuxt.jsとFirebaseを組み合わせて爆速でWebアプリケーションを構築する
- 【Nuxt.js】todoアプリを作成してみた①
- firestore, vue.jsでリアルタイム同期のチャットを実装してみる [チュートリアル形式]
- Vue.js おすすめライブラリ 21選(おまけ+1)
終わりに
読んでいただきありがとうございました、よくわからん部分もあったと思います。私自身わかっていない部分が多いですが、とにかく一言いってやりたいという方!是非こちらで絡んでやってください。
- 投稿日:2020-09-09T23:27:37+09:00
Expo x React Native DebuggerでUIデバッグ
概要
SimulatorでWeb開発でやるようなInspectを出来るようにする方法を書いておきます。
React Native Debuggerをインストール
caskで
react-native-debugger
をインストールします。$ brew update && brew cask install react-native-debuggerReact Native Debuggerの設定をExpo用に変更
React Native Debuggerを開いて、
Debugger
>Open Config File
を選択します。
.rndebuggerrc
という設定ファイルが開かれるので、defaultRNPackagerPorts
をExpo用に19001
に変更します。- defaultRNPackagerPorts: [8080] + defaultRNPackagerPorts: [19001],デバッグ方法
上記までが終わったら、一度expoを再起動します。
これしなくてエラーが出た
再起動したら、Simulatorを立ち上げます。
以下はiOS Simulatorで進めます。SimulatorでDebug JS RemotelyをONに
Simulator上で
Cmd + D
でメニューを開きます。
すると以下のようにメニューが出るので、Debug Remote JS
をタップします。React Native Debuggerを起動
React Native Debuggerを起動して、iOS Simulatorをリフレッシュすると以下のような小さいメニューが出るので、
Inspect
をタップするとWeb開発のようなInspectが出来るようになります。
- 投稿日:2020-09-09T23:20:55+09:00
Ajaxの非同期通信の考察
Ajaxの非同期通信の考察
同期通信では一度のデータ通信で全てのデータが更新されていた。
非同期通信では一部のデータのみでデータ処理を行なっている。何が違うのか。。。?
◆Railsの同期通信
URIもしくはPermitを元に、MVCでactionによる処理を行い、データ通信をしている。全体の更新を必要とする。
◆非同期通信
DOMでHTML(←階層構造)から作ったDOMツリー(←も階層構造)の先っちょにあるURLパラメーターを元に、XMLHttpRequestにより設定したリクエストとレスポンスの方法により、必要な一部分だけでデータ通信している。
全体を更新しなくても一部の処理を一部だけで行えるようになる!
◆ URIパラメーター達
・queryパラメーター
http://sample.jp/?name=kii
の?以降の情報。
HTMLの要素のことを表してる?・pathパラメーター
http://tweets.jp/tweets/1
のようなパラメーターで、「リソースを識別する場合」に使用する。
データベースでidとかをつけると使えるのかなってゆう感じまだはっきり理解してないので曖昧表現使ってます。(違ったら指摘お願いします。)
↑のやつをElementsかqueryのselectorで指定して、その部分のHTML要素のデータを単独でデータ通信していく。
◆XMLHttpRequest(以降XML)
これを使ってMVCで行なっていたようなデータ通信を作っていく。
ユーザーが見ている非同期通信の行えるwebサイトにはすでこれが組み込まれている?・open
非同期通信のリクエストを何のHTTPメソッドでどんなパスでどの方式で行うのかを決める。
XML.open(“HTTPメソッド, `パス`, true);trueだと非同期通信。
・responseType
レスポンスをどんなデータの形でしてもらうか決める。
XML.responseType = "json";これだとjsonの形式で返してくれる。
・send
この記述によって、この内容が組み込まれたwebサイトを見ているユーザー側から、リクエストの送信を実行する。
・・・最後に
まだはっきりと理解できてないので、今のところの認識で書いています。
言い回しの違いや、認識の間違いなどあれば指摘してくださると幸いです。
- 投稿日:2020-09-09T23:08:04+09:00
axios を async/await で書き直した時の課題をメモ
概要
request が deprecated になっており、トレンドも axios に移っているようだ。
なので、コードの置き換えを検討している。async/await にすると可読性があがるかもと試した内容を記載する。
結論
結論からいうと因果関係のある処理を async/await に置き換えると異常でも後続する処理が動くため微妙。
因果関係がある場合、論理的にも入れ子で正しいし、今回検証したパターンだと入れ子のままでいいと思う。サンプルコード: then()を使う
ユーザー認証をして得たトークンを元にリクエストをする例。
簡単すぎるコードでは検証にならないのでやや複雑なコードになっている。'use strict' const axios = require('axios'); axios( { url: 'https://httpbin.org/post?token=XXXXXXXX', // url: 'https://httpbin.org/status/500?token=XXXXXXXX', headers: { 'Content-Type': 'application/json' }, method: 'post', data: { id: 'john.doe', pass: '********' } } ).then(function (responseToken) { console.log(`Request URI: ${responseToken.config.url}`); const token = responseToken.data.args.token; console.log(token); axios( { url: 'https://httpbin.org/get?foo=bar', headers: { 'Authorization': 'Bearer ' + token }, method: 'get' } ).then(function (response) { console.log(`Request URI: ${response.config.url}`); console.log(`response body: ${JSON.stringify(response.data.args)}`); }).catch(function(error) { console.log(`Request URI: ${error.config.url}`); console.log(`${error.response.status} ${error.response.statusText}`); return `${error.response.status} ${error.response.statusText}` }) }).catch(function(error) { console.log(`Request URI: ${error.config.url}`); console.log(`${error.response.status} ${error.response.statusText}`); return `${error.response.status} ${error.response.statusText}` })正常時の実行結果
Request URI: https://httpbin.org/post?token=XXXXXXXX XXXXXXXX Request URI: https://httpbin.org/get?foo=bar response body: {"foo":"bar"}異常時の実行結果
入れ子なので token 取得に失敗したら、/get?foo=bar
の処理は実行はしない。期待通りの動作。Request URI: https://httpbin.org/status/500?token=XXXXXXXX 500 INTERNAL SERVER ERRORサンプルコード: await/asyncを使う
ちょっとわかりにくいと思うが、
await axios()
await axios()
と同じレベルでコードが並んでいる。入れ子でないので、前のリクエストの結果に依存する処理が増えても可読性が悪くはならない。ただ、異常系を考えると微妙。
'use strict' const axios = require('axios'); async function main() { const token = await axios( { // url: 'https://httpbin.org/post?token=XXXXXXXX', url: 'https://httpbin.org/status/500?token=XXXXXXXX', headers: { 'Content-Type': 'application/json' }, method: 'post', data: { id: 'john.doe', pass: '********' } } ).then(function(responseToken) { const token = responseToken.data.args.token; console.log(`Request URI: ${responseToken.config.url}`); console.log(`token: ${token}`); return token }).catch(function(error) { console.log(`Request URI: ${error.config.url}`); console.log(`${error.response.status} ${error.response.statusText}`); return {code: 1, detail: `${error.response.status} ${error.response.statusText}`} }) await axios( { url: 'https://httpbin.org/get?foo=bar', headers: { 'Authorization': 'Bearer ' + token }, method: 'get' } ).then(function(response) { console.log(`Request URI: ${response.config.url}`); console.log(`response body: ${JSON.stringify(response.data.args)}`); }).catch(function(error) { console.log(`Request URI: ${error.config.url}`); console.log(`${error.response.status} ${error.response.statusText}`); return `${error.response.status} ${error.response.statusText}` }) } try { main() } catch(e) { console.log(e) }正常時の実行結果
Request URI: https://httpbin.org/post?token=XXXXXXXX token: XXXXXXXX Request URI: https://httpbin.org/get?foo=bar response body: {"foo":"bar"}異常時の実行結果
token 取得に失敗したのに、後続の処理を実行してしまっている。Request URI: https://httpbin.org/status/500?token=XXXXXXXX 500 INTERNAL SERVER ERROR Request URI: https://httpbin.org/get?foo=bar response body: {"foo":"bar"}完了は待ってくれる が、正常も異常もお構いなし。
ならば、catch句で throw だと思ったが、Promise中のthrowは非推奨。そのうちNode.jsのプロセスを殺害するつもりだという。自プログラムでなく外部サービスの問題でクラッシュさせるのは微妙だ。参考1
次のように throw してみた結果。
throw {code: 1, detail: `${error.response.status} ${error.response.statusText}`}
Request URI: https://httpbin.org/status/500?token=XXXXXXXX 500 INTERNAL SERVER ERROR (node:885) UnhandledPromiseRejectionWarning: #<Object> (node:885) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 2) (node:885) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.参考2
次のように reject してみた結果。
Promise.reject(`${error.response.status} ${error.response.statusText}`)
Request URI: https://httpbin.org/status/500?token=XXXXXXXX 500 INTERNAL SERVER ERROR (node:852) UnhandledPromiseRejectionWarning: 500 INTERNAL SERVER ERROR (node:852) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 2) (node:852) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code. Request URI: https://httpbin.org/get?foo=bar response body: {"foo":"bar"}参考
- 投稿日:2020-09-09T21:23:57+09:00
【JavaScript】関数における、引数値を定数で定義することの利点
同じ変数を用いる複数の関数を実行したい場合、引数値を定数で定義すべき、という内容。
例)
//関数1 function intro(name, food) { console.log(`私の名前は${name}です。好物は${food}です。`); } //関数2 function lunch(food) { console.log(`今日の昼ごはんは${food}を食べます。`); }どちらの関数も、仮引数
food
を使っている。これらに対して、引数で定義し出力をする場合は、
intro("田中","カレー"); //仮引数(name,food)に引数を与え、関数の実行。 lunch("カレー"); //上記と同様。となる。
一方で、引数で定義するのではなく、定数で定義し出力する場合は、const name = "田中"; //定数nameに文字列"田中"を代入。 const food = "カレー"; //上記と同様。 intro(name,food); //仮引数(name,food)に定数(name,food)を与え、関数の実行。 intro(food); //上記と同様。となる。
一見、前者の方が記述が簡素で可読性に優れると思うが、メンテナンスの観点でいえば、後者の方が利便性に優れる。
仮に、food = "ラーメン";
に修正したい場合、前者は修正箇所が2つ、後者は定数定義1つで済む。同じ変数を用いる関数が増えれば増えるほど、後者の方がコード管理しやすいと考える。
- 投稿日:2020-09-09T21:10:01+09:00
jscodeshift with TypeScript
はじめに
以前、jscodeshitという
JavaScript
およびTypeScript
コードのリファクタリングツールの紹介記事を書きました。JavaScriptのリファクタリングツール「jscodeshift」の使い方
このツールは、ASTベースでコードを自在に変換でき、とても便利です。
jscodeshift
は、Transform File(変換処理を記述するファイル)として変換処理を書くのですが、これまでJavaScript
で変換処理を書くことが多かったです。しかし、Transform Fileは
TypeScript
で書くことが可能です。最近、
TypeScript
で変換処理を書いた際、その開発体験がとても良かったので紹介します。ASTベースのコーディングにTypeScript
の型推論や補完があると、開発が快適になることは想像しやすいでしょう。この記事で出てくるコードやファイルは以下のリポジトリで確認できます。
セットアップ
まずは、
jscodeshift
のTransform FileをTypeScript
で書く準備をします。ライブラリのインストール
必要なライブラリをインストールします。
typescript
の他に、@types/jscodeshift
をインストールします。$ npm i -D jscodeshift typescript @types/jscodeshifttsconfig.json
tsconfig.json
を作成します。設定はお好みで調整してください。$ npx tsc --initTypeScriptで変換処理を書く
セットアップが終わったら、
TypeScript
でTransform Fileを作成します。jscodeshift + TypeScriptの土台となる記述
jscodeshift
+TypeScript
でのコーディングの土台となる記述を書きます。
型推論により、型注釈を消すことはできますが、説明の便宜上書いています。import { Transform, FileInfo, API } from "jscodeshift"; const transform: Transform = ({ source }: FileInfo, { jscodeshift }: API) => { const j = jscodeshift; return j(source).toSource(); }; export default transform;あとは、補完が効くので、快適に開発を進めていくことができます。
変換処理
foo.bar()をfoo.baz()に変換
前回の記事でもシンプルな例として出した、
foo.bar()をfoo.baz()に変換
処理を書いていきます。といっても、主に利用する関数や値は型推論があるので、JavaScriptの時と比べても記述量は変わりません。規模が大きくなり、変換処理を関数として切り出す際などに型注釈を付与することになるでしょう。
対象のNodeを見つける
// ... const j = jscodeshift; return j(source) .find(j.CallExpression, { callee: { object: { name: "foo" }, property: { name: "bar" }, }, }) // ... }; export default transform;
find
関数にわたすAST NodeのTypeを選択する際も、エディターでの補完の恩恵を得ることができ、非常に便利です。Nodeを置き換える
// ... .find(j.CallExpression, { callee: { object: { name: "foo" }, property: { name: "bar" }, }, }) .replaceWith((path) => { // TODO }) // .... };別のAST Nodeへ置き換える
replaceWith
関数に渡ってくるpath
オブジェクトの型も推論されます。新しいNodeの作成
const j = jscodeshift; // ... .replaceWith((path) => { return j.callExpression( j.memberExpression(j.identifier("foo"), j.identifier("baz")), path.value.arguments ); }) // ... }; export default transform;置き換えるAST Nodeを作成する際に、利用する便利な
Builder
メソッド(e.g.j.memberExpression
)へ渡すことが可能な値も検査してくれます。
JavaScript
のときは、このあたりが特に苦労したので、とても助かります。成果物
最終的には以下のようなコードになりました。
transform.ts
import { Transform, FileInfo, API } from "jscodeshift"; const transform: Transform = ({ source }: FileInfo, { jscodeshift }: API) => { const j = jscodeshift; return j(source) .find(j.CallExpression, { callee: { object: { name: "foo" }, property: { name: "bar" }, }, }) .replaceWith((path) => { return j.callExpression( j.memberExpression(j.identifier("foo"), j.identifier("baz")), path.value.arguments ); }) .toSource(); }; export default transform;動作確認
jscodeshift
コマンドにTransform Fileと対象ファイルを渡して動作確認しましょう。
-p
はprintです。-d
はdry runです。
また、対象ファイルとしてTypeScript
ファイルを指定する場合は、--parser
オプションでts
を指定します。(tsx
も指定可能です)$ npx jscodeshift -t transform.ts index.ts --parser ts -p -d Processing 1 files... Spawning 1 workers... Running in dry mode, no files will be written! Sending 1 files to free worker... const foo: any = {}; foo.baz(); All done. Results: 0 errors 0 unmodified 0 skipped 1 ok Time elapsed: 0.855secondsテスト
最後に、テストについても触れておきます。
jscodeshift
のテストについては以下の記事を見ると良いです。今回は
jscodeshift
のテストおよびテストのfixture fileにTypeScript
ファイルを使う方法を紹介します。ライブラリのインストール
jest
とts-jest
をインストールします。$ npm i -D jest ts-jestjestのセットアップ
jest.config.js
のpreset
でts-jest
を指定します。module.exports = { preset: "ts-jest", };fixture fileとテストファイルの作成
jscodesfhit
のドキュメントで指定されているようなディレクトリ構造でfixture fileとテストファイルを作成します。今回は、
JavaScript
ファイルとTypeScript
ファイルのfixture fileを配置します。/transform.ts /__tests__/transform.test.js /__testfixtures__/foobar.input.ts /__testfixtures__/foobar.output.ts /__testfixtures__/foobar.input.js /__testfixtures__/foobar.output.js
__tests__/transform.test.js
を以下のように記述しました。
TypeScript
ファイルを変換する場合は、parser
オプションを指定する必要があるので、defineTest
関数で指定しています。// @ts-ignore import { defineTest } from "jscodeshift/dist/testUtils"; describe("test with .js file", () => { defineTest(__dirname, "transform", null, "foobar"); }); describe("test with .ts file", () => { defineTest(__dirname, "transform", null, "foobar", { parser: "ts" }); });$ npx jest > jest PASS __tests__/transform.ts test with .js file transform ✓ transforms correctly using "foobar" data (129 ms) test with .ts file transform ✓ transforms correctly using "foobar" data (10 ms) Test Suites: 1 passed, 1 total Tests: 2 passed, 2 total Snapshots: 0 total Time: 1.14 sまとめ
jscodeshift
のTransform FileをTypeScript
で書く方法と、TypeScript
ファイルでのテストの方法を紹介しました。ASTベースのプログラミングは、型の恩恵を強く受けることができ、
TypeScript
との相性が非常に良いです。今後は、
jscodeshift
を使う際は、TypeScript
で書くのが良さそうです。
- 投稿日:2020-09-09T21:06:00+09:00
AngularとCesium.jsを使って3Dモデルを動かすところまでやってみた
Cesium.jsをAngularアプリケーションの中で使うことになったので、知見を共有しようと思います。
割と基礎的な内容ですが、英語のドキュメントを読むのに辛い思いをしたので備忘録を兼ねて投稿します。願わくば誰かの役に立たんことを……。Cesium.jsとは?
Cesium.jsの説明についてはこちらの記事で基本的なところは解説しているので、省きます。
https://qiita.com/keijipoon/items/615ebaf7561a27d744f5準備
まずは新しいAngularのプロジェクトを作ります。
ng new cesium-project
プロジェクトに移動し、ng serveでサーバが起動しているかを確認します。
cd cesium-project
ng serve
確認したらサーバを停止し、AngularCLIからライブラリを追加。ここまでやったら準備は一旦完了です。
ng add angular-cesium
npmコマンドでマニュアルインストール可能だが、設定が面倒くさい。何かの理由でマニュアルインストールする場合は下記参照。
https://docs.angular-cesium.com/getting-started/installation地形と構造物の表示
さて、まずは地図の表示をしますので新規にコンポーネントを作成しましょう。
下記コマンドでmapコンポーネントを作成します。
cd src/app
ng g c map
map.component.htmlを以下のように書き換えます。
map.component.html<div id="cesiumContainer"></div>map.component.tsを以下のように書き換えます。
map.component.tsimport { Component, OnInit } from "@angular/core"; @Component({ selector: "app-map", templateUrl: "./map.component.html", styleUrls: ["./map.component.scss"], }) export class MapComponent implements OnInit { constructor() {} ngOnInit(): void { const viewer = new Cesium.Viewer("cesiumContainer", { terrainProvider: Cesium.createWorldTerrain(),//地形情報を表示するオプション }); scene.primitives.add(Cesium.createOsmBuildings());//OpenStreetMapというプロジェクトで作成された簡易なモデルを表示できる viewer.camera.flyTo({//目的の座標にカメラを向ける処理 destination: Cesium.Cartesian3.fromDegrees(139.767125, 35.681236, 1000), //経度,緯度,高さの順に指定 }); } }ここで、app.component.htmlを以下のように書き換えます。
app.component.html<router-outlet></router-outlet>
app-routing.module.tsも書き換えましょう。
app-routing.module.tsimport { NgModule } from "@angular/core"; import { Routes, RouterModule } from "@angular/router"; import { MapComponent } from "./map/map.component"; const routes: Routes = [{ path: "", component: MapComponent }]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule], }) export class AppRoutingModule {}ここまで来たら、再度ng serveでサーバを起動。
すると下記画像のように東京駅周辺の地図情報を3D表示することができました。
軌跡を表示する
map.component.tsのngOnInit()の中に下記の処理を追加します。
もちろんリストの中の値は適当です。map.component.ts//軌跡表示 viewer.entities.add({ polyline: { positions: Cesium.Cartesian3.fromDegreesArrayHeights([ 139.767125, 35.681236, 300, 139.768, 35.682, 300, 139.7685, 35.681, 300, 139.769, 35.683, 300, 139.768, 35.683, 300, ]), width: 5, material: Cesium.Color.RED, }, });保存して画面を確認します。赤いラインが表示されているのがわかります。
軌跡上を動くオブジェクトを表示する
map.component.tsのngOnInit()の中に下記の処理を追加します。
フリーで転がっていたドローンのモデル(.glb形式)をダウンロードし、src/assetsに入れました。map.component.ts//アニメーションの処理。 let czml = [ { //Cesium特有のczmlというJSON形式のデータを作る id: "document", name: "CZML", version: "1.0", clock: { //アニメーションのループ間隔を指定する interval: "2020-09-02T12:00:00Z/2020-09-02T12:00:08Z", //画面下部のタイムバーの始値と終値を定義する。ここにはフライト開始時刻と終了時刻を入れる currentTime: "2020-09-02T12:00:00Z", //フライト開始時刻を入れる multiplier: 1, //n倍速の指定。固定値にするかは要検討。 range: "LOOP_STOP" step: "SYSTEM_CLOCK_MULTIPLIER", }, }, { id: "drone", name: "drone", availability: "2020-09-02T12:00:00Z/2020-09-02T12:00:08Z", //ここにフライト開始時刻とフライト終了時刻を指定する position: { epoch: "2020-09-02T12:00:00Z", cartographicDegrees: [ //秒,経度,緯度,高さの順番 0, 139.767125, 35.681236, 300, 2, 139.768, 35.682, 300, 4, 139.7685, 35.681, 300, 6, 139.769, 35.683, 300, 8, 139.768, 35.683, 300, ], }, model: { gltf: "assets/drone.glb",//3Dモデルの指定 outlineColor: { rgba: [0, 0, 0, 255], }, }, }, ]; viewer.dataSources.add(Cesium.CzmlDataSource.load(czml));保存して画面を表示し、画面左下の再生ボタンをクリックするとアニメーションが行われるはずです。
総括
この界隈はあまり日本語のドキュメントがなく、苦労したので何かの役に立てば幸いです。
座標データを取得できるハードウェアなどと連携できれば、いろいろな可能性が見えてきそうです。参考にしたサイトのURLを張っておきます。
https://sandcastle.cesium.com
https://gis-oer.github.io/gitbook/book/materials/web_gis/Cesium/Cesium.html
- 投稿日:2020-09-09T20:07:01+09:00
アイデアの相互評価ができるidea streamerを作りたかったああああ
アイデアの評価むつかしい問題
最近世の中でアート思考だとかデザイン思考だとかがもてはやされるようになった気がします。
でも誰か知り合いが出したアイデアって、忖度なしで評価するの結構難しいと思うんですよね。
例えば上司のアイデアだったら「いいですね!」と言ってしまいそうですし、斬新なアイデアであっても、「こいつ生意気やんけ」といって評価を下げてしまう人もいるかもしれません。今回は「ひたすらアイデアを投稿しまくって」「それを知らない誰かに評価してもらえる」idea streamerというプラットフォームを作ってみました。
途中バグがあったり画面遷移がうまくいかなかったりで、超未完成ですが一旦振り返りの意味で投稿してみたいと思います。
「アイデアの相互評価」という意味で面白い仕組みだと思うので、何か刺激になればなと思います。アプリはこちらから入って、途中で画面遷移が切れるのでアイデア評価からはこちらからお願いします!
動作デモ
アイデアのブレスト
3分間の間にひたすらアイデアを投稿しまくっていきます。
Keywordに自分の好きな単語を入れておくと、「×〇〇」の部分がランダムで変わっていきます(技術不足で途中バグってます)。
普段交じり合わない組み合わせからアイデアを発想して投稿しまくる、という感じですね。アイデアを投稿しまくって知らん人に評価してもらうIdeaStreamerというプラットフォームを作りたかった
— canonno (@canonno_blog) September 9, 2020
途中バグってますが一回区切りでQiita書きます#protoout #アイデア思考 #idea #VueJS pic.twitter.com/QDcnUBVOtvアイデアの評価
アイデア出しが終わったら次は知らない人が投稿したアイデアの評価を行います (画面遷移の実装ができてません)。
DBにあるアイデアを見て面白い・斬新だな、などの観点で0~10の間で評価します。
現状自分のアイデアも見れるようになっていますが、そうすると自己評価になって意味がないので、他の人のアイデアだけ取り出す処理が必要そうですね。アイデア出しが終わったら次はアイデア評価になります。
— canonno (@canonno_blog) September 9, 2020
画面遷移の実装ができてないので手動で遷移しています。
他の人が出したアイデアに0~10の評価をつけて返します。
Postボタンを押したらアイデアが変わる実装もできてないので手動で変えていますboroboro#protoout #VueJS #アイデア思考 pic.twitter.com/r1gb8tYn1sアイデアのスコアの確認
こちらは全く実装できてません。
自分のアイデアがどう評価されたのかを確認できるようにしたいですね。自分が出したアイデアへの評価結果を確認したい。
— canonno (@canonno_blog) September 9, 2020
技術不足すぎて実装できませんでしたがそんな想定ですクヤシィィィイ#protoout #VueJS #アイデア思考 pic.twitter.com/IhPeEBbv5Qできたこととできなかったこと
実装できた
・アイデア出しする仕組み
・アイデア評価する画面
・全体の雰囲気実装できなかった
・アイデア評価の仕組み
・自分のアイデアのスコア確認
・全部0点にしちゃうなど悪ふざけ対策
・画像がデフォルト悔しい。。。
実装こまごま
yarnでいろいろ
今回は初めてNuxt.jsを使って実装してみました。
結構癖があるけれど使いこなせたら楽しそう。
備忘録的に記録しておきます。$ yarn create nuxt-app myNuxtTest Project name: myNuxtTest Programming language: JavaScript Package manager: Yarn UI framework: Bootstrap Nuxt.js modules: Axios, Progressive Web App (PWA), Content Linking tools: (Press <space> to select, <a> to toggle all, <i> to invert election) Testing framework: None Rendering mode: Universal (SSR / SSG) Deployment target: Static (Static/JAMStack hosting) Development tools: None $ cd myNuxtTestpagesの中に画面を作ったり、componentsにコンポを作り終わったら、
$ yarn devを実行後localhost:3000へアクセスし、画面を確認します。上手く動作していれば、
$ yarn generateでhtmlを生成し、distフォルダをデプロイ、という流れです。
generateしてフォルダ構造がどう変化するかをちゃんと把握していなかったため、画面遷移がうまくできなかったです。。。悔しい。Netlifyにデプロイしたのち、freenomで取得したドメインでURLを生成しました。
freenomは今回初めて使いましたがこんなにお手軽にドメインが借りれるのは驚きですね。タイマーがゼロになったら次へ
カウントダウンタイマーはこちらのサイトを参考にさせていただきました。
カウントがゼロになったら自動で画面遷移するようにしたかったものの、うまく実装ができず。。。
ひとまずはv-showで表示非表示を制御して、ユーザにクリックしてもらう作戦に変更しました。<template> <div> <div v-show="show"> <div id="timer"> <div class="timer"> <div class="time"> {{ formatTime }} </div> </div> </div> <h3> <input v-model="key" placeholder="KeyWord" class="key"> × {{reactant}} = <input v-model="idea" placeholder="Input Your Idea" class="idea"> </h3> <br> <h3> <button v-on:click='random' class="button--green">Pass</button> <button v-on:click='post' class="button--green">Post</button> </h3> </div> <div v-show="shownext"> <a href="ScoreIdea" target="_blank" rel="noopener noreferrer" class="button--green" > Next Step </a> </div> </div> </template>script部分はカウントダウンする部分の処理と、データをDBへPostする処理を実装しました。
vueのライフサイクルをまだ理解しておらず、createdやmountedが使いこなせていないです。。。
いやあこのあたりもちゃんと理解したい。<script> import firebase from "firebase/app"; import "firebase/firestore"; export default { components: {}, data(){ return{ db: {}, reactant: 'Click Pass', allData: ["文化","コロナ","夢","飲む","食べ物","おいしい","恋愛","決める","地面"], show:true, shownext:false, min: 2, sec: 59, timerOn: false, timerObj: null, } }, methods: { //初期化 init: () => { const config = { //firebaseの情報 }; // Initialize Firebase firebase.initializeApp(config); }, //カウントダウン処理 count: function() { if (this.sec <= 0 && this.min >= 1) { this.min --; this.sec = 59; } else if(this.sec <= 0 && this.min <= 0) { this.complete(); } else { this.sec --; } }, //カウントがゼロになった処理 complete: function() { clearInterval(this.timerObj) this.show = false; this.shownext = true; }, //ボタンを押したらランダムに表示 random: function(){ const rnd = Math.floor(Math.random() * this.allData.length); this.reactant = this.allData[rnd] }, //データ追加の処理 post: function(){ const testId = firebase.firestore().collection('test').doc().id; //ユニークなIDを生成 const docRef = firebase.firestore().collection('test').doc(testId); const setAda = docRef.set({ key: this.key, reactant:this.reactant, idea:this.idea }); this.random(); this.idea = ""; //window.location.href = "JoinTable"; }, //データ取得の処理 get: function(){ //this.allData = []; firebase.firestore().collection('test').get().then(snapshot => { snapshot.forEach(doc => { console.log(doc); this.allData.push(doc.data()); }) }); this.random(); } }, //インスタンスが生成したときの処理 created(){ let self = this; this.timerObj = setInterval(function() {self.count()}, 1000) this.timerOn = true; //timerがOFFであることを状態として保持 }, //常に動く処理 computed: { formatTime: function() { let timeStrings = [ this.min.toString(), this.sec.toString() ].map(function(str) { if (str.length < 2) { return "0" + str } else { return str } }) return timeStrings[0] + ":" + timeStrings[1] } }, //マウントされたときの処理 mounted(){ this.init(); this.get(); }, } </script>あとはデザイン部分ですね。
これはほとんどデフォルトのものを利用しています。<style> input{ text-align: center; } input.key{ width:160px; height:50px; } input.idea{ width:260px; height:50px; } #timer { display: flex; align-items: center; justify-content: center; } .time { font-size: 100px; } </style>さいごに
フレームワークの便利さを感じれるほどまだ慣れておらず、この辺りは経験積んでいくしかないですね。
この辺は精進していかなきゃなあと思います。
アイデア自体は悪くないと思うのに悔しい。。。最後までご覧になっていただきありがとうございました!
LGTMしていただけると励みになりますので、是非是非よろしくお願いします!
- 投稿日:2020-09-09T18:10:57+09:00
JavaScriptを学ぶ:スラスラ読めるJavaScript Chapter1
はじめに
@kuromailserverさんの『会津わろ法則』に参加。
その中で紹介された学習書の中でとりかかりやすく読破できそうな『スラスラ読めるJavaScript』をチョイス。
この投稿は本書を読んで自分なりのノートとしてまとめていく。注:本書では表なども使って丁寧に解説されているがそれを持ってこれないので他サイトのリンクを載せている。
文字の表示
プログラミングの第一歩、画面に文字を表示。
コンソールへの表示はconsole.logメソッド(メソッド=コンピューターに対する命令文)を使用。
console.log('表示させたい文字');
で表示させたい文字がコンソールに表示される。コンソールconsole.log('Hello');Hello
コンソールにHelloの文字が表示された。
演算子を使って計算
人が四則計算をする場合「+」「-」「×」「÷」の記号を使うが、JavaScriptでする場合は「+」「-」「」「/」の演算子*を使う。
左辺を50、右辺を2として順に計算してみる。
コンソールconsole.log(50 + 2); console.log(50 - 2); console.log(50 * 2); console.log(50 / 2);52
90
100
10
ちゃんと足し算、引き算、掛け算、割り算の計算結果が出た。
この他に「%」(割った余りを表示)「**」(べき乗計算を表示)がある。
上記と同じ値で計算してみる。コンソールconsole.log(50 % 2); console.log(50 ** 2);0
2500
参考:式と演算子
演算子には優先順位がある。
参考:演算子の優先順位表を見ると「+」「-」より「*」「/」「%」のほうが高く、最優先されるのが()を使ったグループ化。
試しにやってみるとこんな感じ。コンソールconsole.log(10 + 4 * 2); console.log(5 - 8 / 4); console.log(5 * (4 - 3));18
3
5
(「式と演算子」や「演算子の優先順位」みたいなのはPDF化してすぐに参照できるようにしておくのが良さげ。)
※負の数をあらわすとき
「-」の演算子は左側が数値以外だと「負の値」となる。
※整数と実数
JavaScriptでは整数と実数は同じNumber型のデータ。
整数と実数同士で計算もできるが、整数同士の計算のほうが圧倒的に早いとのこと。
先に進んだ時に実際に動作させて確認したい。変数を使って計算
値や文字列などをいれておける箱みたいなもの=「変数」。
let 変数名 = 値や文字列;
で新しく変数が作れる。
さっそくやってみましょう。コンソールlet num = 777; let word = 'リンゴ'これだけだとキチンとできたかどうかわからないので、
console.log()
で表示してみましょう。コンソールlet num = 777; let word = 'リンゴ' console.log(num); console.log(word);777
リンゴ
キチンとできていた。
let
は新しく作る際には必要だが、1度作った変数の中身を変えるときはいらない。変数の命名にはルールがある。
1:半角のアルファベット、アンダースコア、数字を組み合わせて作る
2:数字のみ、先頭が数字の命名はNG
3:予約語と同じ名前はNG(予約語と予約語、予約語と他の文字を合わせた場合はOK)
参考:予約語また、JavaScriptは変数、演算子、メソッドなどを識別する。
なので、変数名に演算子が入っていると変数として機能しない。
promptメソッド
を使うとユーザーから入力したものを扱える。
変数 = prompt('ダイアログボックスに表示したい文字列');
実際にやってみるとコンソールlet answer = prompt('文字を入力'); console.log(answer);ダイアログボックスが表示されるので「カエル」と入力。
すると
カエル
と入力したものが表示された。
次は2つの数値を入力してその合計が表示されるプログラムを実行する。
1つ目を「5」、2つ目を「3」と入力。
結果は「8」が表示されるはずだが……コンソールlet num = prompt('1つ目の数値'); let num2 = prompt('2つ目の数値'); console.log(num + num2);53
表示されたのは「53」。
「+」の演算子は左右どちらかが文字列だと「連結」という命令に変化。
promptメソッド
で入力すると入力したものはすべて文字列として扱われる。
なので「53」という結果が表示された。「連結」ではなく「足し算」として実行するには文字列を数値に変換する処理が必要。
それにはparseInt
という関数がある(関数については後のChapterで説明がある)。
parseInt
は指定したものを「整数」として返す。
先ほど入力したプログラムにparseInt
をいれれて、1つ目を「5」、2つ目を「3」と入力すると……コンソールlet num = prompt('1つ目の数値'); let num2 = prompt('2つ目の数値'); console.log(parseInt(num) + parseInt(num2));8
「5」と「3」を合計した「8」が表示された。
- 投稿日:2020-09-09T17:44:33+09:00
@babel/parser を console.log デバッグする
気軽に
console.log
デバッグがしたかったときのメモ。Prettier と違って Flow で書かれてるのでそのまま Node.js では動かないのでビルドする必要がある。
$ make watch
で変更を監視してビルドして、
/packages/babel-parser/lib/index.js
を見れば良い。なんか適当なファイルを作って
// tmp.js const { parse } = require("./lib/index.js"); const code = `const foo = "foo";`; const result = parse(code, { plugins: ["typescript"], errorRecovery: false }); console.log(JSON.stringify(result));とか好きな内容にして
$ node ./tmp.js
すればうごく。最初はビルドするのがめんどくさいから flow-node で実行しようと思ったけど、flow-node は ESM 対応がされていなくて @babel/parser のソースをそのまま実行できなかったので諦めてビルドすることにした。(ちなみに一瞬 flow-node の ESM 対応をしようとしたけど手元でいじって動かすには Flow をソースからビルドする必要があるっぽく(?)、OCaml 環境のセッティングが面倒くさかったのでやめてしまった。)
- 投稿日:2020-09-09T16:44:04+09:00
javascriptで関数をwrapする
javascriptで関数をwrapしたいときー
例えば、非同期処理の完了を待つまでなんかしたいときとかconst wrapper = function(func) { /*** なんか処理 ***/ let result = func(); /*** なんか処理 ***/ };async/awaitバージョン
const wrapper = async function(func) { /*** なんか処理 ***/ let result = await func(); /*** なんか処理 ***/ };普通ですね
つかうときー
// 引数を取らない関数の場合 wrapper(funcA) // 引数を取る関数の場合 ここが特殊! wrapper(funcB.bind(null, "unko"))
- 投稿日:2020-09-09T16:36:24+09:00
[JavaScript] 文字列のアルファベット大小文字考慮した辞書順ソート
アルファベット順、大文字小文字無視したソート
次のようなコードがよく紹介されていました。
var source = ['a', 'B', 'A', 'b', 'aa', 'Aa', 'AA', 'aA', 'aB', 'ab', 'Ab', 'AB'] source.sort((a, b) => { a = a.toLowerCase(); b = b.toLowerCase(); if (a < b) { return -1; } else if (a > b) { return 1; } return 0; }) console.log(source); // [ 'a', 'A', 'AA', 'aA', 'aa', 'Aa', 'aB', 'ab', 'Ab', 'AB', 'B', 'b' ]このコードだとある程度は文字列を並び替えられるのですが、大文字と小文字は同一視されているので、どちらかが優先されることがないです。
いわゆる辞書順を考えると、大文字を優先するか、小文字を優先するか、を判断したいところ。
アルファベット順、大文字、小文字、優先順位をつける。
次のようにします。
var source = ['a', 'B', 'A', 'b', 'aa', 'Aa', 'AA', 'aA', 'aB', 'ab', 'Ab', 'AB'] source.sort((a, b) => { const la = a.toLowerCase(); const lb = b.toLowerCase(); if (la < lb) { return -1; } else if (la > lb) { return 1; } else { for(let i = 0, l = a.length; i < l; i += 1) { if (a[i] < b[i]) { return -1; } if (b[i] < a[i]) { return 1 } } } }) console.log(source); // [ 'A', 'a', 'AA', 'Aa', 'aA', 'aa', 'AB', 'Ab', 'aB', 'ab', 'B', 'b' ]アルファベット順で、大小文字区別なしで同一文字の場合には、1文字ごとに並び順を判定するとよいです。
小文字優先にしたい場合は、for の内部の、-1 と 1 を入れ替えるとよいです。
// [ 'a', 'A', 'aa', 'aA', 'Aa', 'AA', 'ab', 'aB', 'Ab', 'AB', 'b', 'B' ]
追記:アルファベット順、大文字、小文字、優先順位をつける。
より効率よいコードを、コメント欄で @sugoroku_y さんに教えていただきました。ありがとうございます!
このように書いても同じ結果が出力されます。
var source = ['a', 'B', 'A', 'b', 'aa', 'Aa', 'AA', 'aA', 'aB', 'ab', 'Ab', 'AB'] source.sort((a, b) => { const la = a.toLowerCase(); const lb = b.toLowerCase(); if (la < lb) { return -1; } if (la > lb) { return 1; } if (a < b) { return -1; } if (b < a) { return 1 } }) console.log(source); // ["A", "a", "AA", "Aa", "aA", "aa", "AB", "Ab", "aB", "ab", "B", "b"] var source = ['a', 'B', 'A', 'b', 'aa', 'Aa', 'AA', 'aA', 'aB', 'ab', 'Ab', 'AB'] const sorted = source.map( s => ({o: s, l: s.toLowerCase()}) ).sort((a, b) => { if (a.l < b.l) return -1; if (a.l > b.l) return 1; if (a.o < b.o) return -1; if (a.o > b.o) return 1; return 0; }).map(e => e.o) console.log(sorted) // ["A", "a", "AA", "Aa", "aA", "aa", "AB", "Ab", "aB", "ab", "B", "b"]JS Bin で動かせるコードを載せました。
https://jsbin.com/busucecotu/2/edit?html,js,console
- 投稿日:2020-09-09T16:34:25+09:00
javascriptのfor文について
javascriptを触ってて
for文周りって色々あるけどfor以外使ったことなかったので調べてみました。for
まずはスタンダード?なfor文から。
const arr = ["banana", "apple", "orange"]; for (let i = 0; i < arr.length; i++) { console.log(arr[i]); }出力結果としては
banana apple orangeとなります。
for(初期値; 条件式; 増減値) {}
のように3つの引数を入れてあげれば出力できました。配列などでよく使用されるイメージです。
for-in
次はfor-inで、これはあまり使ったことがないのですがオブジェクトの中身などを取り出したいときに使用されるみたいです。
const obj = { name: "太郎", age: 15 } for(let key in obj) { console.log(key + ":" + obj[key]); }出力結果としては
name: 太郎 age: 15となります。
for (変数 in オブジェクト)とすれば
オブジェクトのプロパティの数だけ繰り返してくれます。for-of
for-ofは配列に対して使用できます。
const items = [1,2,3,4,5,6]; for (let item of items) { console.log(item); }出力結果としては
1 2 3 4 5 6となります。
for (変数 of 配列)とすれば配列の数だけ繰り返します。
for文のように条件式を入れないのでこっちの方がわかりやすい印象です。foreach
最後はforeachですがこれは配列に対してつかえます。
var items = [ { id: 1, name: "太郎" }, { id: 2, name: "健" } ]; items.forEach( function( value ) { console.log( value.name ); });配列の要素一つ一つに対して処理を実行できるので使いどころが多そうな印象です。
まとめ
今回はjavascriptのfor系を調べてみました。
同じようなものでも違いがあるので色々調べてみて使ってみたいと思います。
- 投稿日:2020-09-09T16:30:45+09:00
(JS)絶対にDeep Mergeを自力で実装しないでください
tl;dr
- JavaScriptでオブジェクトのdeep mergeをしたいならこの記事を読むか、あるいは最新版のlodashを使ってください
- 自力で実装したdeep mergeを使う場合は、引数にユーザーの入力(JSONなど)を使わないでください
deep merge? なにそれ
オブジェクトの引数を再帰的に設定する関数で、lodashでいえばmerge関数とかに相当。
プロトタイプ汚染の闇
JavaScriptのオブジェクトには99%プロトタイプが存在します。偉い人はオブジェクトを
Object.create(null)
で作ったりしますが、そういう人は稀です。プロトタイプの属性は、同名の属性が元のオブジェクトに存在しない場合のフォールバックとして使われます。たとえば、いつも使う
hasOwnProperty
は、実はObject
のプロトタイプ内に存在するのです。では、そのプロトタイプを書き換えられたらどうでしょうか。たとえば、
hasOwnProperty
が1
になったら、({}).hasOwnProperty("foo")
といったコードがエラーを出します。もし認証系のコードで「isAdmin属性が存在すれば管理者だ」をconst isAdmin = ({}).isAdmin;
のように実装していれば、立派な権限昇格です。これは「プロトタイプ汚染」(Prototype Pollution)と呼ばれます。そして、プロトタイプ汚染が怖いのは、deep mergeという簡単な操作で、引き起こせるからです。
Deep Merge クイックテスト
もし下のオブジェクトを空のオブジェクトにdeep mergeして、
Object.prototype.isAdmin
がセットされたら、プロトタイプ汚染成功です。(自分のコードでのみ試してください... 本番環境のAPIに投げるのはお勧めしません。){ "__proto__": {"isAdmin": 1}, "constructor": {"prototype": {"isAdmin": 1}} }安全なdeep merge
一番安全なのは、lodashのようなライブラリーを使うことです。なお、最新版を使うことをお勧めします。
それでも自力で実装したい場合は
__proto__
、constructor
、prototype
のキーが出てきたら無視する。- merge先の
hasOwnProperty
の結果がfalseの場合は、空のオブジェクトを作成する。(typeof merge先 === "object"
のみだと不十分。)のような安全策を取らないといけません。荒技としては
Object.freeze(Object.prototype)
で汚染をできないようにすることもできますが、あくまで荒技です。
- 投稿日:2020-09-09T09:48:30+09:00
[ServiceNow] サービスカタログに引数を渡したい
きっかけ
とあるサービスカタログに前のページで表示している値を引き継いで遷移したいという要望があり、URLから取得する方法を実装しました。
参考にした記事
https://jace.pro/post/2018-07-15-sp-set-variables-via-url/
プラットフォーム
NewYork
サンプル
以下のカタログ(レコードプロデューサー)のAsset No.フィールドに値を渡したい。
方法1 変数のDefault Valueを使用する
変数のDefault Valueに以下のスクリプトを書きます。
hoge.jsjavascript: (function(){ try{ return $sp.getParameter('var_asset_no') || ''; } catch(e){ return RP.getParameterValue('var_asset_no'); } })()以下の様にURLパラメータを渡してカタログを開きます。
https://(インスタンス名).service-now.com/(カタログページ)&var_asset_no=P1000023結果
方法2 マクロ型の変数を使用する
ウィジェットのクライアントスクリプトに以下のコードを書きます。
client_script.jsfunction($scope, $window) { var params = $window.location.href.split('?')[1]; var paramArr = params.toString().split('&'); paramArr.map(function(keyval) { var key = keyval.split('=')[0]; var value = keyval.split(key + '=').join(''); value = decodeURIComponent(value); try { $scope.page.g_form.setValue(key, value); } catch (e) { console.log('Error set params', e); } }); }以下の様にURLパラメータを渡してカタログを開く
https://(インスタンス名).service-now.com/(カタログページ)&category=hardware&cmdb_ci=P1000023結果
- 投稿日:2020-09-09T07:22:49+09:00
【JavaScript】配列の中にオブジェクトを追加する。
JSの初歩的な部分で躓いたので、備忘として記録する。
下記のとおり、科目(キー名subject)と点数(キー名points)を代入した変数testがあるとする。
let test = [{subject: 'sociology', points: 75}, {subject: 'english', points: 50}, {subject: 'biology', points: 85}];
この中に、点数95点をとった科目scienceを入れたい場合、ハッシュ名[キー] = 追加したい値;
では入れられない。
あくまでも、配列[]に追加したいわけなので、pushを使う。
test.push = ({subject: 'science', points: 95});
これで追加完了。ここから、points'95'だけを抽出したい場合は、
console.log(test[3]["points"]); ・・・配列testのインデックス番号[3]の['points']プロパティを出力、という意味。
まとめると下記のとおり。
※追記
下記の書き方も可能。
- 投稿日:2020-09-09T07:01:02+09:00
無料でSSR・ホスティング・API鯖を立てれるVercel。無料でホスティングするサンプル。Parcel・ReactでSPA。
Vercel
https://vercel.comバックナンバー
無料でSSR・ホスティング・API鯖を立てれるVercel。TypeScript・ExpressでAPI鯖を立てる。ソースコード
package.json{ "scripts": { "start": "rimraf local && parcel src/index.html -d local", "build": "rimraf public && parcel build src/index.html -d public --no-source-maps" }, "dependencies": { "connected-react-router": "^6.8.0", "history": "^5.0.0", "react": "^16.13.1", "react-dom": "^16.13.1", "react-redux": "^7.2.1", "react-router": "^5.2.0", "redux": "^4.0.5", "vercel": "^20.1.0" }, "devDependencies": { "parcel-bundler": "^1.12.4", "rimraf": "^3.0.2" } }src/index.html<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Vercelホスティング</title> </head> <body> <div id="app"></div> <script src="/index.js"></script> </body> </html>src/index.jsimport * as React from 'react'; import { render } from 'react-dom'; import { combineReducers } from 'redux'; import { Provider } from 'react-redux'; import { Route, Switch } from 'react-router'; import { ConnectedRouter, routerMiddleware, connectRouter, } from 'connected-react-router'; import { applyMiddleware, compose, createStore } from 'redux'; import { createBrowserHistory } from 'history'; const App = () => { return ( <> <h1>Hello world!</h1> </> ); }; const history = createBrowserHistory(); const store = createStore( combineReducers({ router: connectRouter(history), }), {}, compose(applyMiddleware(routerMiddleware(history))) ); render( <Provider store={store}> <ConnectedRouter history={history}> <Switch> <Route exact path={'/'} component={App} key="home" /> <Route exact path={'/sub'} component={App} key="sub" /> </Switch> </ConnectedRouter> </Provider>, document.getElementById('app') );vercel.json{ "routes": [ { "handle": "filesystem" }, { "src": "/.*", "dest": "/index.html" } ] }コマンド
ローカルで実行 ポート番号3000で鯖が立ち上がる
$ npx vercel dev
デプロイ
$ npx vercel --prodワイの成果物
https://qiita.com/yuzuru2/items/b5a34ad07d38ab1e7378
①コード共有サイト(SPA) React
https://code.itsumen.com②画像共有サイト(SPA) React
https://gazou.itsumen.com③チャット(SSR) Nuxt.js
https://nuxtchat.itsumen.com④チャット(SPA) React
https://chat4.itsumen.com⑤掲示板(SSR) Next.js
https://board.itsumen.com⑥掲示板(SPA) Vue
https://board.itsumen.com⑦レジの店員を呼ぶスマホアプリ(Android)
https://play.google.com/store/apps/details?id=com.itsumen.regi&hl=ja⑧ブログ(静的サイトジェネレータ) Hugo
https://yuzuru.itsumen.comワイのLINE: https://line.me/ti/p/-GXpQkyXAm
- 投稿日:2020-09-09T05:35:10+09:00
vue.js コンポーネントの書き方
Vue cliを使わないcdn版でのコンポーネントの書き方です。
いくつか書き方があり迷うので自分メモ的に投稿致します。
大きくわけてグローバル登録とローカル登録があります。コンポーネントの書き方(グローバル登録)
コンポーネント名はHTMLタグになるもの
テンプレート名は直接HTMLタグを書くか、HTML内に記述したx-templateの対応するidを設定//コンポーネント1 Vue.component('コンポーネント名A', { template: 'HTMLタグを記述、もしくはテンプレートのid', data: function() { return { number: 12 } } }) //コンポーネント2 Vue.component('コンポーネント名B', { template: 'HTMLタグを記述、もしくはテンプレートのid', data: function() { return { msg: 'hello!' } } }) //HTML内で表示したいブロック new Vue({ el: '#app' }) new Vue({ el: '#app2' })グローバルなので#app、#app2どちらのブロックでもすべてのコンポーネント名がHTMLタグとして使用できます。
<div id="app"> <コンポーネント名A></コンポーネント名A> </div> <div id="app2"> <コンポーネント名A></コンポーネント名A> <コンポーネント名B></コンポーネント名B> </div>コンポーネントの書き方(ローカル登録) 推奨
定義したコンポーネントの定数名を、new Vue内で使いたいel:に使いたいタグ名で登録する
//コンポーネントを定義 const 定数名 = { template: 'HTMLタグを記述、もしくはテンプレートのid', data: function() { return { msg: 'キーワードを入力してください', } } } new Vue({ el: 'HTML内の使いたいブロックをCSSセレクタで記述', components: { //上のelでマウントしたHTML内のブロックに //下の 'HTMLタグ名':定数名 を使います宣言 'search-component': Search } })[Vue warn]: Unknown custom element: - did you register the component correctly? For recursive components, make sure to provide the "name" option. - did you register the component correctly? For recursive components, make sure to provide the "name" option.
というエラーを吐かれたときはコンポート名が間違っていないか確認してください。
まとめ
基本的にローカル登録を推奨
多くの場合、グローバル登録は理想的ではありません。例えば Webpack のようなビルドシステムを利用しているときに、グローバルに登録した全てのコンポーネントは、たとえ使用しなくなっても、依然として最終ビルドに含まれてしまうことでしょう。これは、ユーザがダウンロードしなくてはならない JavaScript のファイルサイズを不要に増加させてしまいます。
このような場合に、コンポーネントを素の JavaScript オブジェクトとして定義することができます。ローカル登録手順まとめ
- constでコンポーネントを定義
- templateを作成(HTML内にx-templateかコンポーネントのtemplate:に直接記述)
※vue cli で使える.vueファイルだとファイル毎にコンポーネントをまとめて書くやり方が使える- template:でテンプレートを定義(直接記述でない場合x-templateのidを指定する)
- data、methodsなどオプションを定義
※dataはコンポーネント側に記述する(new Vueの方にdataを記述した場合反映されない感じ?)- new Vue で使いたいブロックをel:に、使いたいコンポーネントをcomponents:に
- el:複数登録不可、components:は複数登録可能
- components:に登録したコンポーネントだけがそのel:内で使える
- 投稿日:2020-09-09T02:38:45+09:00
gatsby入門 チュートリアルをこなす ソースプラグインの作成(1)
チュートリアルをこなす!
以前にgatsbyの基本のチュートリアルをこなしたのですが、まだチュートリアルが残っているので最後までやっていこうと思いました。
今回実施するgatsbyのチュートリアルはこちら
https://www.gatsbyjs.com/tutorial/plugin-and-theme-tutorials/
https://www.gatsbyjs.com/tutorial/source-plugin-tutorial/
早速やっていきましょう。Plugin & Theme Tutorials
https://www.gatsbyjs.com/tutorial/plugin-and-theme-tutorials/
ここではプラグインとテーマのチュートリアルの概略が記載されていました。
ざっくり言うと
プラグインは、Gatsby APIを実装するNode.jsパッケージ。
テーマは、事前設定された機能やデータソーシング、UIコードをギャツビーサイトに追加するプラグインの一種。
要はサイト構築に便利な物が作れまっせ(しかも共有出来まっせ)ってことで理解しました。
次行きましょう。Creating a Source Plugin
https://www.gatsbyjs.com/tutorial/source-plugin-tutorial/
ここでは独自のソースプラグインを作成するようです。
ソースプラグインについてはチュートリアルにこう書かれています。ソースプラグインは、任意のソースからのデータをGatsbyが処理できる形式に変換します。 Gatsbyサイトでは、いくつかのソースプラグインを使用して、興味深い方法でデータを組み合わせることができます。
つまり構築サイト内のソースからデータを抜き出して、良い感じのデータに変換できるってことかな?
とにかく次次!How to create a source plugin
なんだか色々な事を書いてあるけど英語よくわかんねぇから、とりあえず実技に進もう。
Set up an example site
以下コマンドでサンプルサイトを作成
gatsby new example-site https://github.com/gatsbyjs/gatsby-starter-hello-world
Set up a source plugin
以下コマンドでソースプラグインを作成
gatsby new source-plugin https://github.com/gatsbyjs/gatsby-starter-plugin
Install your plugin in the example site
サンプルサイトにソースプラグインをインストールします。
example-site/gatsby-config.jsを以下のように修正します。example-site/gatsby-config.jsmodule.exports = { /* Your site config here */ plugins: [require.resolve(`../source-plugin`)],←ここ修正 }example-siteを起動します。example-siteディレクトリに移動して以下を実行
gatsby develop
ロードされてる!
このログはsource-plugin/gatsby-node.jsに出力コマンドがあります。source-plugin/gatsby-node.jsexports.onPreInit = () => console.log("Loaded gatsby-starter-plugin")Source data and create nodes
source-plugin/gatsby-node.jsを以下のように書き換え
source-plugin/gatsby-node.js// constants for your GraphQL Post and Author types const POST_NODE_TYPE = `Post` exports.sourceNodes = async ({ actions, createContentDigest, createNodeId, getNodesByType, }) => { const { createNode } = actions const data = { posts: [ { id: 1, description: `Hello world!` }, { id: 2, description: `Second post!` }, ], } // loop through data and create Gatsby nodes data.posts.forEach(post => createNode({ ...post, id: createNodeId(`${POST_NODE_TYPE}-${post.id}`), parent: null, children: [], internal: { type: POST_NODE_TYPE, content: JSON.stringify(post), contentDigest: createContentDigest(post), }, }) ) return }再起動
graphqlを見ると
allPostという項目が増えています。
チュートリアル通りのクエリを実行するとこんな感じ。
うん。source-plugin/gatsby-node.jsに書かれたPost情報が記載されてる。
postもあるね。
なるほどね。サイトのディレクトリとファイルの内容だけでSQLみたいなクエリ作って取得するようにイメージしておこう。だめだ超眠い。
今回はここまで。
ありがとうございました。
gatsby 過去の作業履歴
gatsby入門 チュートリアルをこなす 0.開発環境をセットアップする
gatsby入門 チュートリアルをこなす 1. ギャツビービルディングブロックについて知る(1)
gatsby入門 チュートリアルをこなす 1. ギャツビービルディングブロックについて知る(2)
gatsby入門 チュートリアルをこなす 2. ギャツビーのスタイリングの概要
gatsby入門 チュートリアルをこなす 3. ネストされたレイアウトコンポーネントの作成
gatsby入門 チュートリアルをこなす 4. ギャツビーのデータ
gatsby入門 チュートリアルをこなす 5. ソースプラグインとクエリされたデータのレンダリング
gatsby入門 チュートリアルをこなす 6. 変圧器プラグイン※Transformer pluginsのgoogle翻訳
gatsby入門 チュートリアルをこなす 7. プログラムでデータからページを作成する
gatsby入門 チュートリアルをこなす 8. 公開するサイトの準備
gatsby入門 ブログ作ってサーバーにアップしてみる
- 投稿日:2020-09-09T02:17:22+09:00
Mac ショートカットコマンド
今回は超初心者が、jQuryのmapメソッドを学んだので、
処理内容を書いて行きます。mapメソッドの処理内容
sample.js//配列オブジェクト let names = [“aaa”,”bbb”,”ccc”]; //関数(コールバック関数) function disp(text) { console.log(text); } //mapメソッドで配列を呼び出し。 names.map(disp);処理内容
今回は、コンソール上にaaa,bbbと表示させるコードを書いました。
①
まず、配列オブジェクト「names」を、mapメソッドを使用して呼び出します。
②
マップメソッドの引数には、コールバック関数である関数「disp」を引数として
設定。
③
関数「disp」の仮変数「text」に呼び出し元である配列「names」の
各要素が入る。
イメージとしては、こんな感じになるのかな?
text = "aaa"
text = "bbb"
処理の回数は、変数の要素数分だけ行われる。
今回は、各要素を要素数分、コンソールに表示させて処理が終わる。まとめ
mapメソッドを使用すると、eachと違い配列の中身である要素だけを、
呼び出して処理ができる。
ざっくりとした処理イメージだと、
names(配列)
⬇︎
mapの引数に設定している関数を呼び出し
( names.map(disp)dispが呼び出し関数 )
⬇︎
呼び出されて関数の仮変数(仮引数)に、配列の中身(要素)が代入される
( 呼び出し元の配列オブジェクトnamesの中身が仮変数「text」に代入される )
⬇︎
配列の要素数だけ関数内の処理が行われる。
色々と、呼び方が間違っていたらすいませんm(_ _)m
- 投稿日:2020-09-09T02:17:22+09:00
jQuery mapメソッドについて
今回は超初心者が、jQuryのmapメソッドを学んだので、
処理内容を書いて行きます。mapメソッドの処理内容
sample.js//配列オブジェクト let names = [“aaa”,”bbb”,”ccc”]; //関数(コールバック関数) function disp(text) { console.log(text); } //mapメソッドで配列を呼び出し。 names.map(disp);処理内容
今回は、コンソール上にaaa,bbbと表示させるコードを書いました。
①
まず、配列オブジェクト「names」を、mapメソッドを使用して呼び出します。
②
マップメソッドの引数には、コールバック関数である関数「disp」を引数として
設定。
③
関数「disp」の仮変数「text」に呼び出し元である配列「names」の
各要素が入る。
イメージとしては、こんな感じになるのかな?
text = "aaa"
text = "bbb"
処理の回数は、変数の要素数分だけ行われる。
今回は、各要素を要素数分、コンソールに表示させて処理が終わる。まとめ
mapメソッドを使用すると、eachと違い配列の中身である要素だけを、
呼び出して処理ができる。
ざっくりとした処理イメージだと、
names(配列)
⬇︎
mapの引数に設定している関数を呼び出し
( names.map(disp)dispが呼び出し関数 )
⬇︎
呼び出されて関数の仮変数(仮引数)に、配列の中身(要素)が代入される
( 呼び出し元の配列オブジェクトnamesの中身が仮変数「text」に代入される )
⬇︎
配列の要素数だけ関数内の処理が行われる。
色々と、呼び方が間違っていたらすいませんm(_ _)m
- 投稿日:2020-09-09T00:26:41+09:00
JSエコシステムぶらり探訪(2): Node.jsとCommonJS modules
JSエコシステムの進化を語るにはNode.jsを避けて通ることはできません。Node.jsと、それ自身の持つモジュール機能について歴史的な背景を踏まえつつ説明します。
Node.js
Node.jsは非同期I/Oを備えたサーバーサイドJavaScriptのための実行環境として2009年に登場しました。1 現在はサーバーサイドJavaScriptだけではなく、JavaScriptのビルド環境として無くてはならないものになっています。
要するにNode.jsは、PerlスクリプトやRubyスクリプトと同じようにJavaScriptのコードを実行するための環境です。
main.jsconsole.log("Hello, world!");$ node main.js Hello, world!CommonJS modules (CJS)
CommonJS modulesはNode.jsで主に使われているモジュール形式です。CommonJSという規格群の一部として2009年に登場し、生まれて間もないNode.jsのモジュールシステムもすぐにCommonJSに適合しました。 2
後述するESM (ES Modules) で置き換えられつつあり、CJSのほうが伝統的な形式といえます。
util.jsvar private_value = 42; // exports.square = ... でもよい module.exports.square = function(x) { return x * x; };main.jsvar util = require('./util'); console.log(util.square(2)); // => 4 console.log(util.private_value); // => undefinedまた、
module.exports
に別の値を代入することもできます。この場合はexports
にだけ代入しても有効ではないので注意する必要があります。square.js// module.exportsへの代入は必須。exportsにも代入しておくと後々便利 module.exports = exports = function(x) { return x * x; }; // JavaScriptの関数はそれ自体オブジェクトなので、プロパティーに代入できる exports.version = "0.1.0";上の例では、
require
は関数を返します。main.jsvar square = require('./square'); console.log(square(2)); // => 4 console.log(square.version); // => "0.1.0"Node.jsにおいては最初に呼ばれるJavaScriptファイルもモジュールです。
main.jsvar x = 42; // global is Node.js equivalent of windows // (There's also globalThis usable in both environments) console.log(global.x); // => undefinedNode.jsのCJSの仕組み
CJSのrequireはNode.js (など、それぞれの処理系) が提供するプリミティブ関数ですが、その動作は比較的シンプルに理解できます。つまり、ファイルを発見して読み取り、関数に包んで
eval
する処理と考えることができます。3実際にevalされるときは以下のような関数の本体として扱われます。(つまり、
exports
,require
,module
,__filename
,__dirname
という5つのローカル変数があらかじめ存在するものとして扱われます。)(function(exports, require, module, __filename, __dirname) { // ファイルの中身 });
module
とexports
はあらかじめ以下のように初期化されたものと考えることができます。var module = {}, exports = {}; module.exports = exports; // その他、moduleの様々なプロパティーを設定
require
はmodule
オブジェクトをキャッシュしておき、eval終了後にmodule.exports
の値を戻り値として返すと考えることができます。これによりmodule.exports
とexports
の関係についても説明がつきます。モジュールの副作用とオブジェクトの同一性
以下のようなモジュールを考えます。
module1.jsconsole.log("Hello, world!"); var counter = 1; exports.fresh = function() { return counter++; };このモジュールには以下の特徴があります。
- モジュールのトップレベル処理に副作用がある。
- モジュールが状態を持っている。
このような場合、モジュールの同一性を気にする必要が出てきます。全く同じ内容の
module2.js
をコピーとして作成し、以下のようにmain.js
から呼び出してみます。main.jsvar module1 = require('./module1'); // => Hello, world! var module2 = require('./module2'); // => Hello, world! console.log(module1.fresh()); // => 1 console.log(module1.fresh()); // => 2 console.log(module2.fresh()); // => 1 console.log(module2.fresh()); // => 2 console.log(module1.fresh === module2.fresh); // => false両方の
console.log
が実行され、fresh
は別々にカウントされ、fresh
のオブジェクトとしての同一性もfalse
になりました。Node.jsでは、パスが同じものは同一モジュールになります4。先ほどの
main.js
を書き換えて./module1
を2回インポートするようにしてみます。main.jsvar module1a = require('./module1'); // => Hello, world! var module1b = require('./module1'); // (no output) console.log(module1a.fresh()); // => 1 console.log(module1a.fresh()); // => 2 console.log(module1b.fresh()); // => 3 console.log(module1b.fresh()); // => 4 console.log(module1a.fresh === module1b.fresh); // => true
console.log
は1回しか実行されず、fresh
は同じカウンタを使うようになり、2つのfresh
関数はオブジェクトとしても同一になりました。冠頭形モジュール
CJSのインポートは単なる
require
という関数であり、どこでも呼び出すことができます。これは一見すると便利で妥当な設計に見えますが、実際はNode.js以外の環境にモジュールシステムを移植するにあたってこの「どこでも呼び出せる」という性質が邪魔になってきます。そこで、以降で解説するモジュールシステムの理解を助けるために、本稿独自の用語として「(CJSの)冠頭形モジュール」という概念を導入します5。
定義. あるCommonJSモジュールが冠頭形である (is a prenex-form module) とは、以下を満たすことである。
- そのモジュールファイルはヘッダ部と本体に分けられる。 (ヘッダ部に続いて本体が来るものとする)
- ヘッダ部の各文は以下のいずれかの形式である。
var <変数名> = require(<文字列リテラル>);
let <変数名> = require(<文字列リテラル>);
const <変数名> = require(<文字列リテラル>);
require(<文字列リテラル>);
- 本体では
require
関数は使われていない。冠頭形であれば、モジュールファイルの中身を実際にevalしなくても、あらかじめ依存先モジュールを決定することができます。
冠頭形ではないものの例としては以下のようなものがあります6。
- 条件つきインポート
require
の引数が動的に決まるようなインポート- 当該モジュール読み込み時ではなく、あとで必要になってから行うインポート
まとめ
特に重要なのが以下の点です。
- Node.jsによって「ブラウザー以外のJavaScript実行環境」が大きな地位を獲得した。
- Node.jsによって、JavaScriptに優れたモジュールシステムがもたらされた。
このことがJavaScriptに2つの大きな課題をもたらしました:
- Webブラウザーもモジュールシステムの恩恵を受けられるようにすること。
- Node.jsとWebブラウザーの間のコードの相互運用性を高めること。
これらの課題がJavaScriptバンドラーの誕生、そして各種の新しいモジュールシステムの提案へとつながっていくと考えられます。が、次回はその前に、Node.jsのパッケージシステムについて扱います。
Wikipediaの記述によると、それ以前にもサーバーサイドJavaScriptの技術自体は存在していたようです。 ↩
根拠を探す余裕がなかったのでこのように書きましたが、実際のところNode.jsの初期のモジュールシステムをベースにしてCommonJSが生まれた可能性が高いと思います。 ↩
他に、ファイルの読み取りが同期的に行われる点、複数回requireしたときにキャッシュする仕組み、巡回参照の処理などを考える必要がある ↩
シンボリックリンクについては次回言及予定 ↩
これについて、より広く使われている名称があれば教えていただけるとありがたいです。 ↩
バンドラーによっては、ここに挙げたような例をうまく処理できてしまう場合もありますが、それでも一般的な場合を全てカバーするのは困難です。 ↩