- 投稿日:2021-01-22T22:33:39+09:00
lerna + yarn workspaces で GitHub Packages の private repository を利用してみた(殴り書きメモ)
「サーバーとクライアントの共有部分をライブラリ化したい → でも非公開にしたい」という要望への解決方法の一つ。コードが JS/TS 一本で、リポジトリと所有権が一カ所で良ければ使いやすそう。
そもそも、公開パッケージとして分離できるならそれでいい話だし。
今回は
Windows
,nodist
,node v14
,yarn install v1.22.10
で試した。以下では、
github
のユーザ名をGITHUB_USERNAME
、github
のリポジトリ名をGITHUB_REPONAME
と記すことにする(コマンドをコピペして確認していないので細部に誤りがあると思います)。PACKAGE_NAME
はlib
,client
,server
のいずれかとする。
Github Actions
でpublish
する方法は試してないです。シンプルな競合対象としては、
npm link
やgit submodule
などでの結合だと思う。とりあえず、手元で試しに導入してみたのでメモを書き殴ります。
lerna
複数パッケージを一つのリポジトリで管理運用するためのツール。
コミットを元に、サブディレクトリ毎に
package
としてpublish
するなど。パッケージの依存関係を一括で解決する
lerna bootstrap
の機能については、Yarn Workspaces を利用することにした。Yarn Workspaces
複数のパッケージで依存関係を一括管理する機能。
依存関係の共有による最適化と、競合問題の回避を謳っている。
yarn
は使っていて、覚えることが少なそうなので採用。最終的なフォルダ構成
lib
,client
,server
という三つの構成を一つのリポジトリに入れて、client
とserver
からlib
を参照する構成を例として想定した。- GITHUB_REPONAME/ - packages/ - lib/ - package.json - client/ - package.json - server/ - package.json - .npmrc - lerna.json - package.json
https://github.com/GITHUB_USERNAME/GITHUB_REPONAME
にリポジトリが保存されて、
@GITHUB_USERNAME/lib
,@GITHUB_USERNAME/client
,@GITHUB_USERNAME/server
という三つのパッケージが作成され、npm
パッケージと同様に参照可能。リポジトリの用意
github
に新しいリポジトリを用意して、手元にgit clone
して、そこのフォルダで作業する。いつも通りgit clone https://github.com/GITHUB_USERNAME/GITHUB_REPONAME cd GITHUB_REPONAME
Yarn Workspaces
のためにpackages.json
の設定
"private": true
の設定と、"workspaces"
以下の設定を追加。packages.json{ // 略 "private": true, "workspaces": { "packages": [ "packages/*" ] },
lerna
のインストールインストール
lerna 公式のドキュメントには、いきなり
npx lerna init
せよと書かれているが、手元の環境ではgraceful-fs
関係のエラー(cb.apply is not a function
)が出て動作しなかったので、yarn global add lerna
してから使い始めた。yarn global add lerna yarn lerna init
lerna.json
の設定設定内容の概略は以下の通りと認識している
- 習わしに倣って
packages/
以下にソースコードを置くことにしたので、"packages": ["packages/*"]
yarn
を使うので"npmClient": "yarn"
yarn workspaces
を使うので"useWorkspaces": true
- パッケージ毎にバージョンを設定したいので
"version": "independent"
- LernaとYarn WorkspacesでMonorepo管理 に倣って command を追加したが、まだ十分に理解していない。
lerna publish
でmain
ブランチをGitHub Packages
にpublish
する。その際にはconventionalCommits
でコミットログが書かれているものとして扱うというそのままの認識で良いと思う。lerna.json{ "packages": ["packages/*"], "npmClient": "yarn", "useWorkspaces": true, "version": "independent", "command": { "publish": { "conventionalCommits": true, "message": "chore(release): publish", "registry": "https://npm.pkg.github.com", "allowBranch": "main" } } }全体で共通して利用する
npm
パッケージのインストール
Yarn Workspaces
を利用した。yarn workspaces
はyarn -W
で省略可能。-W
orworkspaces
) が付く以外はいつも通りにadd
/remove
すればいい。yarn -W -D add typescript @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint eslint-plugin-import lerna(良く分かってないメモ)この後、パッケージ毎にも
npm
パッケージをインストールするが、yarn workspaces
の場合、それらもhoisting(巻き上げ)
という機能によって、GITHUB_REPONAME/node_modules
以下に集約される模様。lerna
単体だと、パッケージ毎のものは個別に入るのかも。便利だけど、いかにもトラブりそうな機能で怖い。パッケージ毎の設定
パッケージ毎の
packages.json
を作成する。ディレクトリは
lerna
のコマンドで作成しても良いが、ここではyarn workspaces
を利用するので、普通にパッケージのディレクトリを作って、packages.json
を置けば良い。create-react-app
などでも良いはず。
packages.json
の 設定
name
にscope
を付ける(GitHub Packages 用)
name
は@GITHUB_USERNAME/PACKAGE_NAME
の用に、github
のユーザ名をscope
として付与する。これは、GitHub Packages
の為に必要。組織で利用する場合は org名 を(https://github.com/ の後ろと一致させる必要があるという認識)。
name
にGITHUB_REPONAME
も入れたかったのだけど、scope
についてはユーザ名と揃える必要があった。埋め込むならPACKAGE_NAME
に埋め込むことになりそう。
repository
とpublishConfig
パッケージのリポジトリと
publish
先をGitHub Packages
に設定する。デフォルトはnpmjs
なので不安なら、npmjs
からログアウトするなどしておくこと。リポジトリ全体を
private
にした環境でしか試行していないが、"access": "restricted
にしておくとprivate
になるのかも。
"private": true
を設定していると、packages
としてpublish
されないので、設定に不安がある間は"private": true
にしておくと良いかも。packages/PACKAGE_NAME/packages.json{ // 前略 "repository": { "type": "git", "url": "ssh://git@github.com/GITHUB_USERNAME/GITHUB_REPONAME.git", "directory": "packages/PACKAGE_NAME" }, "publishConfig": { "access": "restricted", "registry": "https://npm.pkg.github.com/" } // 後略 }依存関係
dependencies
,devDependencies
もいつも通り。編集してyarn
なり、必要に応じてyarn add
/yarn add -D
すればいい。パッケージ直下のnode_modules
ではなく、ルートのnode_modules
にインストールされる辺りの挙動が異なる。
lerna
だけを利用する場合は、この辺りが異なるはず。Yarn Workspaces
はお手軽そうだった。個別に入れたい場合は、nohoist オプション を利用する。
GitHub の認証情報を
.npmrc
に埋め込むGitHub の Settings Developer settings / Personal access tokens で
Personal access token
を発効する。名前は分かりやすい名前を付ければOK。
権限は、パッケージの作成・編集が必要なので
Select scopes
でrepo
,write:packages
,read:packages
,delete:packages
を付与した。パッケージの読み出しだけなど、利用者に応じて権限は調整すること。
作成するとトークンに対応したパスワードが表示される。以下ではそのパスワードを
TOKEN_SECRET
とする。ルートに、以下の内容の
.npmrc
を作成する。ユーザ名とシークレットは置き換えること。always-auth=true
は無くてもいけるかも。@GITHUB_USERNAME:registry=https://npm.pkg.github.com/ //npm.pkg.github.com/:_authToken=TOKEN_SECRET always-auth=true
- 一行目が、
@GITHUB_USERNAME
スコープのパッケージがGitHub Packages
にあることの設定- 二行目が、
GitHub Packages
の認証情報- 三行目は、動いた際のおまじないをそのまま残している
npm login
コマンドを利用する方法だと、C:\Users\username\.npmrc
(~/.npmrc
) に保存される。それでも大丈夫なはずなのだけど、試行錯誤の最中に消したので分からない。
TOKEN_SECRET
が流出するとリポジトリに無制限にアクセスされてしまうので、作成後は、.gitignore
に追加するのをお忘れ無く。.npmrc
libs
を作成する
npm
パッケージを作る要領で、libs
以下にコードを書いていく。共通設定はルートの
tsconfig.common.json
に記述して、lib
用の設定から読み込んだ。tsconfig.json
をまるっと貼っておく。packages/lib/tsconfog.json{ "extends": "../../tsconfig.common.json", "compilerOptions": { "outDir": "lib", "rootDir": "src", "baseUrl": "src", "module": "commonjs", "moduleResolution": "node", "declaration": true }, "include": ["src"] }
publish
する
git
コミットしておく。push
は自動でしてくれる。
yarn lerna publish minor
を実行すると、更新されているパッケージのX.Y.Z
のY
の部分がインクリメントされてパッケージ化される。バージョンはminor
のオプションを変えればコントロール出来るはず。上手く packages が
publish
された場合、https://github.com/GITHUB_USERNAME?tab=packages&repo_name=GITHUB_REPONAME
にパッケージができている。もしくは、https:://github.com/GITHUB_USERNAME/GITHUB_REPONAME
の右列にPackages
が増えている。エラーが出た場合、随時、エラーメッセージに対応する。大体、
C:\Users\username\AppData\Roaming\npm-cache\_logs
のログに書かれている内容に対応していけば解決出来た。記憶にあるトラブルシューティング
各パッケージの下に
LICENCE.md
なども置いておくとpublish
時にlerna
に怒られない。
org.couchdb.user' is not in the npm registry.
.npmrc
の記述間違いだったような…token
を作り直したような、試行錯誤をした記憶があるが、npm login
コマンドを使わずに.npmrc
を手動で作って解決したと思う。
This command requires you to be logged in.
.npmrc
の記述が間違っており、npmjs
を見に行こうとしていた- C:\users\username.npmrc を消して、パッケージ直下に .npmrc を作り直したなど
エラーが無かったのにパッケージができなかった。
"private": true
を指定していたのでパッケージされなかった。ログにはverbose stack Remove the 'private' field from the package.json to publish it.
などと出力されている。
npm package "PACKAGE_NAME" does not exist under owner "OTHER_SCOPE"
scope
の部分がGITHUB_USERNAME
と一致していなかった。
lib
を利用する
server
/client
でpackages.json
に依存関係を指定する。その際にパッケージ化されているバージョン名を指定しないと駄目みたい。packages/server/packages.json{ // 略 "dependencies": { "@GITHUB_USERNAME/lib": "^0.6.0", },後は、
npm
パッケージと同様にimport
なりrequire
なりで利用できるはず。packages/server/lib/hoge.tsimport { FooBar } from "@GITHUB_USERNAME/lib";
No matching version found for @GITHUB_USERNAME/lib@^0.8.0.
バージョン番号の不一致をチェックする。
Couldn't find package "@GITHUB_USERNAME/lib" on the "npm" registry.
.npmrc
の内容を見直すと良さそうだった。GitHub Packages
を見に行っていない可能性が大。Cloud Functions からも利用する
サーバ側で
Cloud Functions
を利用したかったので、.npmrc
をservers/.npmrc
にコピーした。他には特別な設定は必要なく、
firebase deploy --only functions
の実行時間が延びてしまったぐらいで、上手く参照してくれているとおもう。ライブラリの作り方が不味いなど
lerna
とyarn workspaces
には無関係のエラーは起こしていたが、それは別の問題。新規に git clone した後
yarn
を実行すると、全パッケージの依存関係を全て解決してくれる。node_modules
以下を消した場合なども同様。
npm
を使う場合は、yarn lerna bootstrap
を実行した後、パッケージ毎にnpm install
になるっぽい。参照したページ
感謝。
- Yarn Workspaces 公式
- lerna github
- lerna 公式
- 依存関係の扱い
- LernaとYarn WorkspacesでMonorepo管理
- How to use Firebase Cloud Functions and Yarn Workspaces
- Publishing and Installing Private GitHub Packages using Yarn and Lerna
- Yarn workspaces から Lerna に移行した
- yarn workspaceでmonorepo+TypeScript+Lint環境をつくる
- lernaでのmonorepoにおけるリリースフロー(Fixed/Independent)
- GitHub Packagesで社内用npmパッケージを配布して使ってみた
- Structuring a Firebase web project with Lerna
- モノレポについての誤解 - Misconceptions about Monorepos: Monorepo != Monolith を翻訳しました
- Lernaを使ってFirebase環境のためのモノレポ環境一式をカッコよく構築する
- 投稿日:2021-01-22T22:15:55+09:00
JSとLaravelでAPI通信する際に日付データを合わせる(unixtime <-> Date)
概要
サーバーさん => Sさん
フロントさん => Fさんあるときこんなやりとりを目撃しました。
Sさん < 日付のフォーマットは20200222で送ってください
Fさん < ここは2020/02/22で送ってください
Sさん < この場合は2020-02-22で送ってください
続く....そのとき第三者の私はこう思いました。
私 < Unixtimeでやりとりすれば、同じフォーマットでやりとりできるんじゃね?と...
やってみた。
javascript// Date -> UnixTime new Date().getTime() / 1000; // 1611320223.828 // UnixTime -> Date new Date(unixTime * 1000); // Fri Jan 22 2021 21:57:44 GMT+0900 (日本標準時)※バックエンドはLaravelを使用しています
php// Date -> UnixTime $date = new DateTime('now'); $date->format('U'); // "1611320663" // UnixTime -> Date $dateTime = new DateTime('@' . (int)$unixTime); $dateTime->setTimeZone(new DateTimeZone('Asia/Tokyo')); // 2021-01-22 22:04:23.0 Asia/Tokyo (+09:00)感想
一旦これでUnixtimeでやりとりできそう!
もっと良いやり方などがありましたら教えてください。
- 投稿日:2021-01-22T22:13:10+09:00
Twitch のウェブサイトを YouTube のシアターモード風に改造する
Twitch.tv の配信画面を YouTube のシアターモード(動画をウインドウ幅いっぱいに表示→その下に2カラムで概要とチャット欄)にしたくて無理やり弄った記録。久々の JavaScript + CSS。
もくじ
はじめに
巣篭もり生活で見たいアメドラも尽きて Twitch に辿り着きました。
作業中はセカンドモニターの端に表示して垂れ流し、表示しているモニターは 1440 x 2560 の縦配置なのでウインドウ幅はかなり狭い状態。
しかし、Twitch のPC版のウェブサイトはウインドウが横長または全画面表示にしていることを前提にしているようで、ウインドウ幅が狭いとちょっと見づらい。
Twitch の画面構成
Twitch の PC ブラウザ版のウェブサイトはレスポンシブ対応で、ウインドウ幅が小さくなるにつれサイドバー → チャット欄と順に非表示になる仕様。
シアターモードなんてのもありますが、YouTube とは違いチャット欄は動画右側に表示されたまま、概要欄とサイドバーが非表示になるタイプ。ちょっと想像と違う。
◆ シアターモードの比較 ← Twitch | YouTube →
代替手段
ながら見ならチャット欄はいらないだろって感じですが、表示されてなきゃないで寂しいので何とか表示したい。そこで。
モバイル版をPCで表示
PCブラウザでも www.twitch.tv ではなく m.twitch.tv にアクセスすればモバイル版を表示出来て、上から下への縦長レイアウトで動画の下にチャット欄を表示できる。んですが、モバイル向けなので PC で使うには不便な点が結構あります。
PC向きではない部分:
- モバイル版は左のサイドバーが無いのでザッピングにひと手間
- 初期状態がミュートで、新しいストリームを表示するたびに動画をクリックしなきゃダメ
- ニコ動風コメント表示やその他のエクステンションがオフになる
- チャット欄がモバイル向けの簡易版で、
- たまにランダムで視聴者に配られるギフトの対象外になる(たぶん)
- チャンネルポイントが貯まらない(たぶん)
- 予想・賭け機能(Channel Points Predictions)に参加できない
Multistre.am を使う
次にチャット欄がフル機能の状態で縦レイアウトにする方法として試したのが Multistre.am だったんですが、「Twitch.tv で見たほうがベストな体験が出来ますよ」的なスクリーンが表示されてストリームが止まってしまったり、過去の配信を選択して流すことが不可能だったり。。。で断念。
ブックマークレット
そんなこんなで、とりあえずモバイル版で落ち着いていたんですが、表示をモバイル版に切り替える時に使っていたブックマークレット
javascript: location.href = location.href.replace('https://www.', 'https://m.');
コレを、サイトのレイアウト弄る内容に変えれば・・・ ? と。
それがコチラ。
javascript: { let sidebarEl = document.getElementById('sideNav'); let sidebarWidthPx = sidebarEl.offsetWidth+'px'; let offsetTopStyle = 'calc( (100vw - '+sidebarWidthPx+') * 0.5625 )'; let transitionParam = ' 0.5s ease'; let playerEl = document.getElementsByClassName('persistent-player tw-elevation-0')[0]; playerEl.style.setProperty('width', '100%', 'important'); playerEl.style.setProperty('transition', 'width'+transitionParam, 'important'); let chatEl = document.getElementsByClassName('channel-root__right-column channel-root__right-column--expanded')[0]; chatEl.style.setProperty('bottom', '0px', 'important'); chatEl.style.setProperty('height', 'calc(100% - '+offsetTopStyle+')', 'important'); chatEl.style.setProperty('transition', 'bottom'+transitionParam+', height'+transitionParam, 'important'); let infoEl = document.getElementsByClassName('channel-root__info channel-root__info--with-chat')[0]; infoEl.style.setProperty('margin-top', offsetTopStyle, 'important'); infoEl.style.setProperty('transform', '', 'important'); infoEl.style.setProperty('transition', 'margin-top'+transitionParam+', transform'+transitionParam, 'important'); let buttonEl = document.getElementsByClassName('right-column__toggle-visibility toggle-visibility__right-column toggle-visibility__right-column--expanded tw-absolute tw-flex tw-flex-grow-0 tw-flex-shrink-0 tw-visible tw-z-above')[0]; buttonEl.style.setProperty('top', 'calc(1rem + '+offsetTopStyle+')', 'important'); buttonEl.style.setProperty('transition', 'top'+transitionParam, 'important'); }無事、YouTube 風のシアターモードに改造することが出来ました。
スタイルシートで transition を設定しておけばアニメーションまでつけてくれて、良い時代になりましたねー。
おわりに
ブックマークレット、既に死語の気もしますが便利ですよね。
JS / CSS を触ってた頃は「JavaScript じゃなくて Javascript だ!」勢が存在していた気もしますが、今は逆になってて不思議な気分です。彼らに何があったんでしょうか。
ともあれ最近 WebGL 面白そうだなーと思ってるので周辺技術を思い出すにはいい機会でした。
--
以上です。お疲れ様でした。
- 投稿日:2021-01-22T21:01:03+09:00
フレームワークとは
今回はフレームワークについて学習をしました。フレームワークとは何か、Ruby,PHP,JavaScript,CSSの代表的なフレームワークを公式サイトとURLと共に紹介します。
説明不足があると思いますので、気になったフレームワークを公式サイトで確認してみて下さい。フレームワークとは
「フレームワーク」という言葉には、「枠組み」という意味があり、アプリケーション開発においては、テンプレートの役割を持っています。テンプレートの機能は、開発を行う際に頻繁に必要とされる「基礎的な機能」をまとめて提供してくれるものです。
例えば、ログイン認証機能、新規投稿機能、検索機能などアプリケーション開発においてほとんど共通して使われる機能がパッケージ化されています。
そのため、フレームワークを用いることで開発を高速化させることが出来ます。
どれくらい便利なものかというと、ログイン認証を仮に自分で1から作成したとします。その場合、作らなければならないのが、ユーザーネーム、パスワードの欄を作り、それを認証、弾く機能、更にそれらを管理する画面など様々なところを作っていかなければなりません。しかし、フレームワークを使うことで、これらのベースとして使える骨組み部分がパッケージ化されているので、あとは自分好みに改良するだけで済みます。このパッケージのことをフレームワークと呼びます。もっと簡単に言えばコンピュータ言語を使用して何か開発したいと考えたとき、その手助けをしてくれる便利ツールの1つと考えておけば良いでしょう。
私がよくお世話になっているものだとRubyならRuby on Rails、CSSならBootstrapなどがあります。おすすめなフレームワーク
フレームワークには言語ごとにたくさんの種類があります。しかし、ここでは私が実際におすすめされた・よく聞くフレームワークを紹介していきます。
Rubyのオススメフレームワーク3種
- Ruby on Rails 公式サイト <https://rubyonrails.org/>
- Sinatra 公式サイト <http://sinatrarb.com/intro-ja.html>
- Padrino 公式サイト <http://padrinorb.com/>Ruby on Rails
「Rubyのフレームワークは?」と聞けば、ほとんど返ってくる答えが「Ruby on Rails」と言われるほど有名です。正直に言ってしまえば、Rubyでアプリ、システム開発を行いたいなら、Ruby on Railsを選択すれば間違いないでしょう。私自身一番最初に手をるけたのがRuby on Railsでした。また、素早くかつ効率的に開発するために、2つの大原則を設定しているのが特徴です。
それらの原則について私が紹介した記事がありますので、読んでみてください。
「DRY原則・CoC原則とは」
https://qiita.com/Shirosan10/items/84fe66955398e91f7136
このように徹底的に規約に従って行動することで、Webアプリ開発の高速化を目指します。Sinatra
Rubyで記述されているオープンソースのフレームワークになります。フレームワークの中では、最も少ないコードでWebアプリなどの開発が可能です。
そのため学習コストが少なくて済み、最も手軽にフレームワークを使用した開発を始めることができます。Padrino
先述の、Sinatraをベースに設計されたフレームワークになります。Sinatraにはなかった機能群があり、かつRuby on Railsよりも少ないコードでWebアプリ開発ができるので、初心者だけど、少々複雑なものを開発したい人向けです。
PHPのオススメフレームワーク3種
- Laravel 公式サイト <https://laravel.com/>
- CakePHP 公式サイト <https://cakephp.org/jp>
- FuelPHP 公式サイト <http://fuelphp.jp/>Laravel
数あるPHPのフレームワークで、Laravelは2011年に登場した最も若いフレームワークです。今現在注目度が高く、その理由は「Love beautiful code? We do too」という理念どおり、初心者であっても美しいコードを書ける点だと思います。コードが書きやすく、すぐに書き始めることができるので、PHPを習得した人が真っ先に使用できます。
CakePHP
PHPの中でも有名なフレームワークです。「ケーキを焼くように簡単に開発」を理念として開発されており、Ruby onRailsの影響も強く受けています。
特徴的なのは、MVC(モデル・ビュー・コントローラ)モデルと呼ばれる実装デザインに関する先駆けになったことです。
MVCモデルについて気になった方はこちらも読んでみてください。
拙いながらMVCについて図解を入れて解説しています。
「MVCモデルについて」
https://qiita.com/Shirosan10/items/d12416700f7e2329cb09FuelPHP
FuelPHPもLaravelと同じように、比較的最近登場したフレームワークです。そのため、ほかのPHPフレームワークの良いところ吸収しているので、使いやすいフレームワークです。
特徴としては、動作が軽量でフレームワークの規約が少ないため、自由にコードを記述出来ることが挙げられます。JavaScriptのオススメフレームワーク3種
- AngularJS 公式サイト <https://angularjs.org/>
- Vue.js 公式サイト <https://jp.vuejs.org/index.html>
- React 公式サイト <https://ja.reactjs.org/tutorial/tutorial.html>AngularJS
AngularJSはJavaScriptでできたWebフレームワークです。見た目の動きをきれいに作るために使用されます。
例えば、SPA(Single-page application)と呼ばれる、「ページを移動しなくてもなめらかにページ内のコンテンツが現れるような1ページで完結するアプリケーション」の開発を行う時などに採用されます。Vue.js
Vue.jsはAngularJSよりも後に開発されいます。そのため、他のフレームワークの良いところを組み込んだ設計になっています。
AngularJSよりも規約が少ないため、比較的自由にコードを記述出来、JavaScriptを学習したばかりの初心者におすすめされます。React
有名なSNS「Facebook」が中心となって開発したフレームワークです。React Nativeと呼ばれる、全く同じソースコードでiOSとAndoroidアプリ両方を動作させることが可能なため、モバイルアプリ開発に適しています。
CSSのオススメフレームワーク
Bootstrap 公式サイト <https://getbootstrap.com/>
Bootstrapは「Webデザインフレームワーク」と呼ばれ、CSS/JavaScriptから構成されています。Twitter社より提供されるフレームワークになるため、Twitterのようなフラットなデザインを簡単に作成することができます。クラスを指定するだけで簡単にレスポンシブデザインに対応したページを作ることが出来ます。
今回の記事は以上になります。
色々と拙い部分があるかと思います。
また、自分が実際に使っておらず調べたり、教科書に書いて有りそうな説明なってしまったものもあり、説明が淡々としすぎてしまいました。今後、自分で使いたいなと思ってメモしていたので理解が深まったら更新して行こうと思います。
間違っている点、理解が不完全な点があればご指摘いただければ幸いです。
- 投稿日:2021-01-22T20:57:39+09:00
JSXの中でif...else文を使って条件分岐する
Reactで開発をしていると、propsの値に応じて返すコンポーネントを変えたい場合が多々あります。
しかし、JSX内で実行できるのは式(expression)のみであり、if...else条件のような文(statement)は書くことができないため、複雑な分岐処理を書くためには工夫が必要になります。案1. 三項演算子を組み合わせる
三項演算子を使った式
A ? B : C
は、Aがfalseと評価された場合にCを返すだけなので、その後にCを起点として再び三項演算子の式を繋げることができます。
簡単な方法ですが、条件が複雑になると異様に読みづらくなります。const Fruit = props => { return ( <div> {props.fruit === 'apple' ? ( <Apple /> ) : props.fruit === 'orange' ? ( <Orange /> ) : ( <Banana /> )} </div> ); };案2. 即時関数を使う
関数はJSX内で実行できるため、関数内でif文を使って条件分岐することができます。
ただ即時関数は読み辛いので、こちらも個人的には好きではありません。const Fruit = props => { return ( <div> {(() => { if (props.fruit === 'apple') { return <Apple />; } else if (props.fruit === 'orange') { return <Orange />; } else { return <Banana />; } })()} </div> ); };案3. 条件分岐処理をJSXの外に出す
当然ですが、JSX以外の場所なら普通にJavaScriptが書けます。なので、return文の前で条件分岐の処理を書いてしまえば良いです。
コンポーネントが大きくなると、条件分岐処理とreturn文が離れてしまい読みづらくなることがありますが、多くの場合はこちらの方がシンプルに書くことができると思います。const Fruit = props => { let fruit; if (props.fruit === 'apple') { fruit = <Apple />; } else if (props.fruit === 'orange') { fruit = <Orange />; } else { fruit = <Banana />; } return <div>{fruit}</div>; };参考サイト
- 投稿日:2021-01-22T19:22:48+09:00
【新春企画】Web Developer Roadmap を真面目に勉強するまとめ【フロントエンド編】
前提
現在、組み込み系エンジニアの私が、今後はウェブ業界に挑戦するにあたって
「強みは技術力です!」
とかっこいいことがちゃんと言えるようになりたいと思いこの企画をはじめました。
いつでもアクセス可能のように備忘も兼ねて書きます。これまで
事前にこちらの記事を勉強済みです。
↓↓↓
Webエンジニアになりたい人に捧げる学習ロードマップ駆け出しフロントエンドエンジニアに求められるもの
1. HTML/CSSによるWebサイトコーディング経験(レスポンシブ対応含む)
2. JavaScriptの基礎構文理解
3. JavaScriptを使った動くUI(複数パターン)の実装経験
4. jQueryのプラグインやAjaxを使った実装経験
5. タスクランナー(gulp)やモジュールバンドラー(Webpack)の利用経験
6. Vue.jsもしくはReact.jsを使ったミニアプリの開発経験
7. 上記のスキルが証明できるポートフォリオサイトもしくはGitHubアカウントの提出記事の役割
この記事が本で言う【目次】という役割となりここから各項目へジャンプできるような構造になっています。
どのように進めるのか
すでにあるロードマップ通りに学習していきます。
今回真面目に勉強するロードマップがこちら!!
↓↓↓
Webエンジニアのためのロードマップ
// 高校の同級生
// 出席番号が1つ前
// サッカー部の友人
// ありがとうロードマップの存在目的の中にこのようなメッセージが。
The purpose of these roadmaps is to give you an idea about the landscape and to guide you if you are confused about what to learn next and not to encourage you to pick what is hip and trendy. You should grow some understanding of why one tool would be better suited for some cases than the other and remember hip and trendy never means best suited for the job.
「迷ったり、流行に流されたりしないように活用してね」
「適材適所でどんな手段がなぜ良いのかを理解し、流行っているものがベストとは限らないよ」と、どんな時代でも通用する本質を学べそうですね。
何を学ぶのか
ひとまずその中でも、フロントエンドのスキルについて勉強していきたいと思います。
[ ]←チェックボックスが全部[x]になっていくイメージ1.Internet[ ]
[ ] How does the internet work?
[ ] Please wait...
[ ] Please wait...2.HTML[ ]
[ ] Please wait...
[ ] Please wait...3.CSS[ ]
[ ] Please wait...
[ ] Please wait...4.Javascript[ ]
[ ] Please wait...
[ ] Please wait...
- 投稿日:2021-01-22T19:22:45+09:00
深さがバラバラの配列からスマートに値を取り出す。reduceで。[JavaScript]
導入
こんな配列と取り出したいデータの場所を表す配列があったとします
var arr=[ [['000', '001', '002'], ['010', '011', '012'], ['020', '021', '022']], [['100', '101', '102'], ['110', '111', '112'], ['120', '121', '122']], [['200', '201', '202'], ['210', '211', '212'], ['220', '221', '222']] ]; var pos =[0,2,2], pos1=[1,0,2], pos2=[2,1,0];多次元配列って呼ばれているやつです
データの場所を投げたら目的のデータが返ってくる関数が欲しいという設定です
この場合なら次の例が一番分かりやすいと思いますconst fx=x=>arr[x[0]][x[1]][x[2]]; console.log(fx(pos )); console.log(fx(pos1)); console.log(fx(pos2)); // >> '022' // >> '102' // >> '201'同じような状況で配列がこんな場合はどうでしょう
arr=[ [['000', '001'], '01', ['020', '021', ['0220', '0221']]], ['10', ['110', '111', '112'], '12'], [[['2000', '2001', '2002'], '201', ['2020', '2021']], ['210', '211']] ]; pos =[0,2,2,0]; pos1=[1,0]; pos2=[2,0,2,1];先程の関数に通してみます
console.log(fx(pos )); console.log(fx(pos1)); console.log(fx(pos2)); // >> ['0220', '0221'] // >> undefined // >> ['2020', '2021']場所によって深さが違うことが原因のようです
そこでこんな関数を作ってみましたconst fx_=x=>x.reduce((a,c)=>a[c],arr); console.log(fx_(pos )); console.log(fx_(pos1)); console.log(fx_(pos2)); // >> '0220' // >> '10' // >> '2021'便利。
解説
そもそもreduceとは
arr.reduce(callback[, initialValue]);
- arr : 処理をしたい配列
- callback : コールバック関数 処理をここに書く 引数は以下の4つ
- accumulator : 前の処理の戻り値 or 最初の要素 or initialValue
- currentValue : 現在処理されている配列の要素
- index : 現在処理している配列のインデックス 必須ではない
- array : 処理している配列 必須ではない
- initialValue : 一番最初の処理でaccumulatorとして渡される値 必須ではない
配列全部の要素を使って何か一つの値を出すのに向いてます
よく挙げられる配列の全要素の合計を出す例で動作を見てみますvar tmp=[1,2,3,4,5].reduce((a,c,i)=>{ console.log(a,c,i); return a+c; }); console.log(tmp); // >> 1, 2, 1 // >> 3, 3, 2 // >> 6, 4, 3 // >> 10, 5, 4 // >> 15注目して欲しいのがインデックス番号です
0からではなく1から始まっています
そして最初のaccumulatorに0番目の要素が使われています
initialValueを入れて実行してみますvar tmp=[1,2,3,4,5].reduce((a,c,i)=>{ console.log(a,c,i); return a+c; },0);// initialValue=0 console.log(tmp); // >> 0, 1, 0 ←処理が増えてる // >> 1, 2, 1 // >> 3, 3, 2 // >> 6, 4, 3 // >> 10, 5, 4 // >> 150, 1, 0の出力があります
つまりinitialValueがないと
本来initialValueとarr[0]で行われる処理がなくなる
ということです単純な数の演算ではない使い方をする場合は
恐らくこちらの方が都合が良いことが多いでしょう冒頭ではこの仕様を使って配列からデータを取り出しています
var arr=[ [['000', '001'], '01', ['020', '021', ['0220', '0221']]], ['10', ['110', '111', '112'], '12'], [[['2000', '2001', '2002'], '201', ['2020', '2021']], ['210', '211']] ]; var pos =[0,2,2,0]; const fx_=x=>x.reduce((a,c)=>{ console.log(a,c); return a[c]; },arr);// arrが最初のaccumilatorに渡される console.log(fx_(pos)); // >> [[["000","001"],"01",["020","021",["0220","0221"]…, 0 // >> [["000","001"],"01",["020","021",["0220","0221"]]], 2 // >> ["020","021",["0220","0221"]], 2 // >> ["0220","0221"], 0 // >> "0220"配列に一階層ずつ潜っていく様子がわかります
さいごに
そもそもこんな複雑な配列を作るなら連想配列を使うべきだったと思います
反省してます…最後まで読んでくださりありがとうございます。
- 投稿日:2021-01-22T15:11:31+09:00
Next.jsにCSS Modulesを適応させる(メタデータ設定も)
vercel社が提供するNext.js公式チュートリアル引用でございます。まるパクリです。
手順
① Next.jsのプロジェクトのルートにcomponentsフォルダを作成。
$ mkdir components② ①で作成した/components配下にlayout.jsを作成し以下を記述。
$ touch components/layout.jslayout.jsexport default function Layout({ children }) { return <div>{children}</div>; }③ pageディレクトリの任意のjsファイル内に以下を記述。
pages配下の任意のjsファイル〜〜〜 import Layout from "../../components/layout"; 〜〜〜 export default function FirstPost() { return ( <Layout> 〜〜〜 </Layout> ); } }④ プロジェクトのルートにstyleフsォルダを作成し、layout.module.cssを作成。
$ touch components/layout.module.css⑤ 適応させたいcssを記述。公式チュートリアルでのlayout.module.cssではあらかじめimgやheaderなどのよく使うスタイルを記述していました。このように機能ごとに分けてスタイルを記述しておくのは、cssmodulesではよくあることらしい・・・・。
layout.module.css.container { max-width: 36rem; padding: 0 1rem; margin: 3rem auto 6rem; } .header { display: flex; flex-direction: column; align-items: center; } .headerImage { width: 6rem; height: 6rem; } .headerHomeImage { width: 8rem; height: 8rem; } .backToHome { margin: 3rem 0 0; }⑥ components/layout.jsに以下を記述。Headタグ内でtwitterカードなどのメタデータを設定しています。
layout.jsimport Head from 'next/head' import styles from './layout.module.css' import utilStyles from '../styles/utils.module.css' import Link from 'next/link' const name = 'Your Name' export const siteTitle = 'Next.js Sample Website' export default function Layout({ children, home }) { return ( <div className={styles.container}> <Head> <link rel="icon" href="/favicon.ico" /> <meta name="description" content="Learn how to build a personal website using Next.js" /> <meta property="og:image" content={`https://og-image.now.sh/${encodeURI( siteTitle )}.png?theme=light&md=0&fontSize=75px&images=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Ffront%2Fassets%2Fdesign%2Fnextjs-black-logo.svg`} /> <meta name="og:title" content={siteTitle} /> <meta name="twitter:card" content="summary_large_image" /> </Head> <header className={styles.header}> {home ? ( <> <img src="/images/profile.jpg" className={`${styles.headerHomeImage} ${utilStyles.borderCircle}`} alt={name} /> <h1 className={utilStyles.heading2Xl}>{name}</h1> </> ) : ( <> <Link href="/"> <a> <img src="/images/profile.jpg" className={`${styles.headerImage} ${utilStyles.borderCircle}`} alt={name} /> </a> </Link> <h2 className={utilStyles.headingLg}> <Link href="/"> <a className={utilStyles.colorInherit}>{name}</a> </Link> </h2> </> )} </header> <main>{children}</main> {!home && ( <div className={styles.backToHome}> <Link href="/"> <a>← Back to home</a> </Link> </div> )} </div> ) }⑦ pages/index.jsに以下を記述。Layoutコンポーネントでrender要素を全てラップしています。
pages/index.jsimport Head from 'next/head' import Layout, { siteTitle } from '../components/layout' import utilStyles from '../styles/utils.module.css' export default function Home() { return ( <Layout home> <Head> <title>{siteTitle}</title> </Head> <section className={utilStyles.headingMd}> <p>[Your Self Introduction]</p> <p> (This is a sample website - you’ll be building a site like this on{' '} <a href="https://nextjs.org/learn">our Next.js tutorial</a>.) </p> </section> </Layout> ) }これで、Layoutコンポーネントの持つheaderやmainの要素を受け継ぐことができるはず!
終わり!
- 投稿日:2021-01-22T14:57:33+09:00
Googleスプレッドシートで郵便番号から住所を表示する関数 その4
概要
セルに郵便番号を入力することで、住所を表示してくれる関数があったらなぁと思って書いたプログラムが動かなくなっていたので、zipcloudさんのAPIを使って書き直してみたプログラムの出来がイマイチだった。
というわけで、zipcloudさんのAPIを使わずに自前でAPIを作成して書き直してみたもののプログラムの実行速度がアレだった。もうこうなったらサーバごと全部自前でやっちまえ!
ってな具合にAPIサーバ建てました。https://zipaddress.suisuinet.com/
ご興味がある方はどうぞ。
使い方は前回のものとほぼ同じです。使い方
下記をスクリプトエディタにコピペすれば関数を利用できるようになります。
=ZIP_ADDRESS("154-0004","address1")
みたいな感じ。利用できるデータの種類は次の通りです。
zipcode 郵便番号
prefcode 都道府県コード
address1 都道府県名
address2 市区町村名
address3 町域名function ZIP_ADDRESS(zip,part) { zip = zip.replace(/\-/g, ''); var response = UrlFetchApp.fetch('https://zipaddress.suisuinet.com/search.php?v=' + zip); var res = JSON.parse(response.getContentText()); if (res.status == 400) { return res.message; } if (res.results) { var address = res.results[0]; return (address[part]) ? address[part] : ""; } else { return "該当するデータがありません"; } }外部システムのデータを読み込むときに承認が必要と言われると思います。
そんなときはこちらの記事を参考に承認すれば大丈夫!
https://qiita.com/h-sto/items/fdde0905d0a4070d18fc
- 投稿日:2021-01-22T14:06:05+09:00
AR.jsのLocation Basedで遠くにモデルを無理やり表示させた話。
AR.jsのLocation Basedで無理やりモデルを表示したい
前回AR.jsを使用してLocation Basedで遠くにモデルを表示させたかった話を書きました。
おさらいするとAR.jsのLocation Basedは、1km以上遠方の位置を指定するとコンテンツが表示されません。
解決策としてvideoTexture: true;オプションを指定すると表示できますが、手持ちのiPhone(iOS14)では、うまく動かなかったので今回は無理やり表示させていきたいと思います。はじめに断っておきますが、根本的な解決策ではないです。
プラン
①の実際の位置を指定するとオブジェクトを表示させることができないので、③の自分の位置から①の角度と距離を求めて、②の表示位置を計算します。
目的地が複数ある場合には遠い位置ほど小さく表示させたいので、距離によりスケールを変えたいと思います。
便宜上①の地点を目的地。②を表示位置、③を現在地と呼ぶこととします。コード
githubにあげてみました。
javascriptも未熟ですが、ご容赦願います。ファイル等構成
同じ階層にindex.htmlと位置計算用のcalc.jsを配置しています。
index.html<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>LocationBased Sample</title> <script src="https://aframe.io/releases/1.0.4/aframe.min.js"></script> <script src="https://unpkg.com/aframe-look-at-component@0.8.0/dist/aframe-look-at-component.min.js"></script> <script src="https://raw.githack.com/AR-js-org/AR.js/master/aframe/build/aframe-ar-nft.js"></script> </head> <body style="margin: 0; overflow: hidden;"> <a-scene renderer="logarithmicDepthBuffer: true;" embedded loading-screen="enabled: false;" arjs="sourceType: webcam; debugUIEnabled: true;" > <a-camera gps-camera="gpsMinDistance:200;" rotation-reader></a-camera> </a-scene> <script src="./calc.js" type="module"></script> </body> </html>1.方角と距離を得る
これには、jsライブラリのgeodesyを使用します。
緯度経度から、距離等を求めることができます。素晴らしい?ありがとうgeodesy‼️。ここからはcalc.jsを作っていきます。
まずはimportします。calc.jsimport LatLon from 'https://cdn.jsdelivr.net/npm/geodesy@2.2.1/latlon-spherical.min.js';今回はクラスにしますので、定義を行います。
calc.jsexport class CalcVR { constructor() { this.distance = 0; this.bearing = 0; this.newPosition = [0, 0]; this.currentPosition = [0, 0]; this.objectSize = '0, 0, 0'; this.newDistance = 800; }distance: 距離
bearing : 方角
newPosition: 表示位置の緯度経度の配列。
currentPosition: 現在地。緯度経度の配列。
objectSize: 表示サイズ。
newDistance: 1km以内で、表示させる距離。今回は800mに設定。
AR.jsは、positionとかも単位はメートルです。距離と角度を求めます。
calc.jscalcDist(currentPosiArg, targetPosition) { const current = new LatLon(currentPosiArg[0], currentPosiArg[1]); const target = new LatLon(targetPosition[0], targetPosition[1]); this.distance = current.distanceTo(target); this.bearing = current.finalBearingTo(target) this.currentPosition = currentPosiArg; }引数
currentPosiArg: 現在地の緯度経度の配列。
targetPosition: 目的地の緯度経度の配列。まず現在地と目的地のgeodesyインスタンスを作成します。
距離は、distanceToで求めます。
角度は、finalBearingToで求めます。2.新しい表示位置を計算する。
calc.jscalcNewPosition(currentPosition, bearing, newTargetToDistance) { const current = new LatLon(currentPosition[0], currentPosition[1]); const calculatedlced = current.destinationPoint(newTargetToDistance, bearing); this.newPosition = [calculatedlced.latitude, calculatedlced.longitude]; }引数
currentPosiArg: 現在地の緯度経度の配列。
bearing: 先ほど求めた角度
newTargetToDistance: 先ほど設定した距離。newPositionに表示位置の緯度経度の配列が返ります。
3.サイズを計算する。
距離にあわせて表示サイズを決定します。
とはいえ、複数ある場合には表示位置に差がありません。遠いほど小さく、また遠いオブジェクトは手前のオブジェクトの後ろに表示させたいと思います。え〜雑ですが?、距離にあわせてサイズを決定するとともに、実際の距離の1000分の1の距離をベース距離に足して、オブジェクト間の奥行きを演出します。
もちろん足して1kmを超えると、意味がありません。ここはもっといい方法があると思うので、改善したいですね。
calc.jscalcSizeDist(distance) { if(distance <= 1000 && distance >= 500){ this.objectSize = '25 25 25'; this.newDistance = 800; }else if(distance > 1000 && distance <= 8000) { this.objectSize = '20 20 20'; this.newDistance = 800 + (distance/1000); }else if(distance > 8000 && distance <= 16000) { this.objectSize = '18 18 18'; this.newDistance = 800 + (distance/1000); }else if(distance > 16000 && distance <= 20000) { this.objectSize = '15 15 15'; this.newDistance = 800 + (distance/1000); }else if(distance > 20000) { this.objectSize = '10 10 10'; this.newDistance = 800 + (distance/1000); } }4.表示させます。
ここからは、AR.jsのサンプルのようにjsonで目的地を設定し、htmlに要素を追加していくことになります。
index.htmlの同階層にassetsディレクトリがあり、そこにgftlモデルがある前提です。calc.js// 目的地情報を追加。19個くらいまでは大丈夫そう。 function staticLoadPlaces() { return [ { name: 'Time Desk', modelName: 'timedesk.gltf', location: { lat: 43.062533, lng: 141.353638, } }, ]; } // 描画するため、a-sceneに追加。 function renderPlaces(places, pos) { let scene = document.querySelector('a-scene'); var crd = pos.coords; let cal = new CalcVR(); places.forEach((place) => { let latitude = place.location.lat; let longitude = place.location.lng; let name = place.name; let modelName = place.modelName; cal.calcDist([crd.latitude, crd.longitude], [latitude, longitude]); cal.calcNewPosition(cal.currentPosition, cal.bearing, cal.newDistance); cal.calcSizeDist(cal.distance); let model = document.createElement('a-entity'); model.setAttribute('look-at', '[gps-camera]'); model.setAttribute('gps-entity-place', `latitude: ${cal.newPosition[0]}; longitude: ${cal.newPosition[1]};`); model.setAttribute('gltf-model', `./assets/${modelName}`); model.setAttribute('animation-mixer', ''); model.setAttribute('scale', `${cal.objectSize}`); model.addEventListener('loaded', () => { window.dispatchEvent(new CustomEvent('gps-entity-place-loaded')) }); scene.appendChild(model); }); }以上です。
これで、geolocationで現在地を取得した時にrenderPlacesを走らせれば、どんなに遠くてもオブジェクトを表示させることができました。でも、根本的な解決にはなっていないので・・・・、色々と不備は出てくると思います。
何かの参考になれば、幸いです。5.コード全文
以下のコードでは、gltfモデルではなく赤いBOXを表示させます。
gltfモデルを使用する場合は、「4.表示させます」の例に書き換えてください。calc.jsimport LatLon from 'https://cdn.jsdelivr.net/npm/geodesy@2.2.1/latlon-spherical.min.js'; export class CalcVR { constructor() { this.distance = 0; this.bearing = 0; this.newPosition = [0, 0]; this.currentPosition = [0, 0]; this.objectSize = '0, 0, 0'; this.newDistance = 800; } // 距離と方角を計算 calcDist(currentPosiArg, targetPosition) { const current = new LatLon(currentPosiArg[0], currentPosiArg[1]); const target = new LatLon(targetPosition[0], targetPosition[1]); this.distance = current.distanceTo(target); this.bearing = current.finalBearingTo(target) this.currentPosition = currentPosiArg; } //表示位置を計算 calcNewPosition(currentPosition, bearing, newTargetToDistance) { const current = new LatLon(currentPosition[0], currentPosition[1]); const calculatedlced = current.destinationPoint(newTargetToDistance, bearing); this.newPosition = [calculatedlced.latitude, calculatedlced.longitude]; } // サイズを計算 calcSizeDist(distance) { if(distance <= 1000 && distance >= 500){ this.objectSize = '25 25 25'; this.newDistance = 800; }else if(distance > 1000 && distance <= 8000) { this.objectSize = '20 20 20'; this.newDistance = 800 + (distance/1000); }else if(distance > 8000 && distance <= 16000) { this.objectSize = '18 18 18'; this.newDistance = 800 + (distance/1000); }else if(distance > 16000 && distance <= 20000) { this.objectSize = '15 15 15'; this.newDistance = 800 + (distance/1000); }else if(distance > 20000) { this.objectSize = '10 10 10'; this.newDistance = 800 + (distance/1000); } } } window.onload = () => { navigator.geolocation.getCurrentPosition(success, error, options); }; // 目的地を設定 function staticLoadPlaces() { return [ { name: 'Time Desk', location: { lat: 43.062533, lng: 141.353638, } }, ]; } // 描画 function renderPlaces(places, pos) { let scene = document.querySelector('a-scene'); var crd = pos.coords; let cal = new CalcVR(); places.forEach((place) => { let latitude = place.location.lat; let longitude = place.location.lng; cal.calcDist([crd.latitude, crd.longitude], [latitude, longitude]); cal.calcNewPosition(cal.currentPosition, cal.bearing, cal.newDistance); cal.calcSizeDist(cal.distance); let model = document.createElement('a-box'); model.setAttribute('material', 'color: red'); model.setAttribute('gps-entity-place', `latitude: ${cal.newPosition[0]}; longitude: ${cal.newPosition[1]};`); model.setAttribute('scale', `${cal.objectSize}`); model.addEventListener('loaded', () => { window.dispatchEvent(new CustomEvent('gps-entity-place-loaded')) }); scene.appendChild(model); }); } var options = { enableHighAccuracy: true, timeout: 50000, maximumAge: 0 }; function success(pos) { let places = staticLoadPlaces(); renderPlaces(places, pos); } function error(err) { console.warn(`ERROR(${err.code}): ${err.message}`); alert('Unable to capture current location.'); }
- 投稿日:2021-01-22T13:38:33+09:00
Chrome拡張機能を作ってみよう③
前回はChrome拡張機能とローカルプログラムの連携までを書きました。
今回はその続きです。URLの監視
特定のURLにアクセスした場合にメッセージを表示するため
URLの監視を行います。監視を行うには
manifest.jsonのcontent_scriptsの項目にあるjsで行います。
今回ですと「message.js」です。message.js/* ■ URL監視 */ $(function() { try{ // background.jsにurlを送る chrome.runtime.sendMessage({method: 'checkurl', url: document.URL}, errorHandle(function(response1) { // 必要ないけどとりあえず受け取る var message = response1.data; })); }catch(e){ console.error(e); } }); /** * 例外をまとめて処理する */ function errorHandle(process) { return function(){ try { return process.apply(this, arguments); } catch (e) { chrome.browserAction.setIcon({path:"images/abnormal.png"}); console.error(e); } }; }message.jsから受け取るためbackground.jsを以下の内容に修正しています。
background.js/* 初期起動時の処理 */ // インストール時かバージョンアップ時 chrome.runtime.onInstalled.addListener(function() { initialize(); }); // ブラウザ起動時 chrome.runtime.onStartup.addListener(function() { initialize(); }); function initialize() { // ファイルダウンロード先 var dlFileName = "http://localhost/sample.txt"; // ファイルを取得 $.ajax({ url: dlFileName, type: "GET", dataType: 'binary', responseType:'arraybuffer', timeout: 500 }) // 成功時 .done(errorHandle(function (response) { var data = response; // ArrayBufferで取得するので var textfiledata = String.fromCharCode(...new Uint8Array(data)); console.log(textfiledata); // 渡す前にbase64エンコードしておく textfiledata = btoa(textfiledata); // ダウンロードした内容をprogramに連携する chrome.runtime.sendNativeMessage('put.message', {Action: "putmessage", Data: textfiledata}, errorHandle(function(response, thread){ // デコードしてJSON形式にする var getData = JSON.parse(decodeURIComponent(response)); // 受け取ったコードがエラーの場合 if(getData.Code != 0){ throw new Error('programでエラーが発生'); } // 受け取ったデータをlocalStorageに保存しておく localStorage.setItem('urllist', atob(getData.Data)); return true; })); // 成功した場合は拡張機能のアイコンを切り替える chrome.browserAction.setIcon({path:"images/success.png"}); // localStorageにステータスを保存 localStorage.setItem('Status', 'ok'); })) // 失敗時 .fail(errorHandle(function () { // localStorageにステータスを保存 localStorage.setItem('Status', 'ng'); })); return true; } /** * Chrome拡張全体用 */ chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) { switch (request.method) { // マニフェストファイルの内容を取得する case 'getManifest': var manifest = chrome.runtime.getManifest() sendResponse({data: manifest}) break; // URLを受け取る case 'checkurl': var url = request.url; // urllistの内容と一致するか確認する localStorage.setItem('match', 'URLが一致しません。'); var urllist = JSON.parse(localStorage.getItem('urllist')); for(var key of Object.keys(urllist)){ if(url == urllist[key]){ // 含んでいる場合 localStorage.setItem('match', 'URLが一致します。'); sendResponse({data: "URLが一致します。"}); break; } } break; case 'getApInfo': default: console.log('no method:' + request.method); break; } return true; }); /** * 例外をまとめて処理する */ function errorHandle(process) { return function(){ try { return process.apply(this, arguments); } catch (e) { chrome.browserAction.setIcon({path:"images/abnormal.png"}); console.error(e); } }; }あとはメッセージを表示するためpopup系を修正します。
popup.html<!DOCTYPE html> <html lang="ja"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta http-equiv="Pragma" content="no-cache"> <meta http-equiv="Cache-Control" content="no-cache"> <link href="../css/popup.css" rel="stylesheet"> <script src="../js/jquery-3.4.1.js" ></script> <script src="../js/popup.js" ></script> </head> <body id="body"> <div class="box"> <div class="header"> <h3 style="margin: 5px 0">動作状況</h3> </div> <div> <p id="status"></p> </div> <div> <p id="match"></p> </div> </div> <div class="footer"> <div id="productver"></div> </div> </body> </html>popup.js$(function() { /*** localStorageからステータスを取得。 ***/ var Status = localStorage.getItem('Status'); var match = localStorage.getItem('match'); if(Status == 'ok'){ $('#status').html('<b>正常</b>'); $('#match').html(match); }else{ $('#status').html('<b>異常</b>'); } // 特に意味はないけどマニフェストファイルのバージョン情報を取得する chrome.runtime.sendMessage({method: 'getManifest'}, function(response) { var manifest = response.data; $('#productver').text(manifest.name + ' ver.' + manifest.version); }); });
拡張機能の作成については以上になります。
次回はChrome拡張機能のインストールについて投稿します。
- 投稿日:2021-01-22T11:07:11+09:00
ブログラミング学習サイトDonblerに有料レッスンを投稿してみた
ブログラミング学習サイトDonbler
2021年1月にリリースされたDonblerというサービスが気になっている。このサイトどうやらエディタを内臓(Progateのような?)していてサイト内で完結するらしい。
Progateとなにが違うのか検証してみたところレッスンは公式ではなくユーザが投稿できてさらに有料にもできる仕様らしい。この点はUdemyと似ているがUdemyは動画のレッスンに対してDonblerはテキストベースのレッスンである。これはエンジニアにとっては嬉しい仕様なのではないだろうか。
今回はProgateとUdemyの融合体であるDonblerに試しに有料レッスンを投稿してみることにする。
今回投稿したレッスンのリンクを貼っておきます!興味のある方は買ってみてください!
VUEで使える様々なライブラリ紹介投稿方法
Donblerのレッスンの投稿方法は公式のnotionに書いてある。以下リンク。
Donbler公式Creator Wiki | レッスン投稿手順
こちらにある通り進めてみる。1.テンプレートダウンロード
https://github.com/donbler/lesson-template/releases
公式wikiにある上記URLから好みのテンプレートをダウンロードする。
テンプレートは2種類あるが内容は変わらないので自分の環境にあった方をダンウンロードした方がいいと思われる。2.投稿可能なレッスンの種類
現在Donblerでは以下3種類のレッスンが投稿できるそう
- HTML/CSS
- JS
- PHP
今回はこの中からHTML/CSS、JSを選んでVUEのライブラリを紹介するレッスンを作ってみる
3.setting.ymlの編集
setting.ymlでは
- タイトル
- 詳細
- ターゲット
- 各チャプターのタイトル
の4つを設定する。
ymlファイルなのでとても編集しやすかった。
また、文字数はどれくらいいけるのか気になったので検証してみたがタイトルは100文字程度らしい。
以下実際に投稿したsetting.ymlであるsetting.yml# レッスンのタイトルを入力してください title: vueで使える様々なライブラリ紹介 # レッスンの説明を入力してください description: |- このレッスンではvueで使えるライブラリを紹介しています!チャプター1ではvueの導入、そのあとのチャプターから様々なライブラリを紹介していきます! vueを触ったことがない人でも気軽に扱える内容になっています!ぜひ受講してしみてください! # レッスンの対象ユーザーを入力してください target: |- JavaScript初心者やフレームワークに興味がある人 # チャプターのタイトルを入力してください chapter: - VUEを導入してみよう! - vue-scroll-toで美しいページ内リンク! - vue-social-shareingでSNSの人気者! - vue-selectで簡単にセレクトボックス!4.実際にレッスンを作る
ここからは実際にレッスンの中身を作っていく。
公式では
- markdown
- サムネイル
- ソースコード
の順になっているが個人的には
- ソースコード
- markdown
- サムネイル
の手順の方が自分にはあっているのかなと感じたので後者の手順で解説していく。
4-1.ソースコード
自分が選択したテンプレートのHTML/JS/CSSにおいて少し注意することがあった。
普段ならHTMLにJSやCSSを不紐付ける必要があるがDonblerの場合それが不要らしい。投稿時かレッスンの実行時かに補正してくれているのであろう。少し不安だったのがCDNでのライブラリやフレームワークが使えるかどうかである。vueを使うにはnpmかyarnが主流だと思われるがDonblerではそれが使えない。となると残るはCDNである。
結果からいうとなんの問題もなくvueを導入することができた。これはなかなか拡張性があるのではないだろうか。また、当レッスンではvueの導入方法とライブラリ3種類の説明をしたレッスンを作成した。この時少しアドバイスがあるとすれば先ほども言った通りHTML/CSS/JSそれぞれリンクさせる必要はないと言ったが逆に編集しづらいという弱点もあった。しかし、これはレッスンの作成を投稿ページのプレビューでやってしまえばなんの問題もなくサクサク作ることができた。
最後にここが一番大事な要素だと思われるコメントアウトによるソースコード隠しである。
これはみていただいた方がよい。var app = new Vue({// :cut el: '#app',// :cut data: {// :cut message: 'Hello Vue!'// :cut }// :cut })// :cut以上のようにコメントアウトに:cutと入れることで見本には表示されるが受講時には非表示になるので問題を作る時やユーザーに入力してほしい部分には積極的に使っていきたい。
注意点があるとしたら答えには表示されるので2重で表示されない工夫が必要になる。具体例をあげると<div id="app"> <button v-scroll-to="'#element'">ここから</button> <div id='element'>ここにスクロール</div> </div>これは vue-scroll-toを使う時のHTMLである。
この時ユーザーにはv-scroll-to="'#element'"
だけをを入力してもらいたいがbutton
タグ丸ごと非表示にしてその下に見せておきたいコードを書くと答えで表示される時にダブって表示されるのであまりよろしくない。
この時は以下のように解決することができる<button v-scroll-to="'#element'" <!-- :cut --> >ここから</button> <div id='element'>ここにスクロール</div>上記のようにすることでピンポイントで非表示にすることができるようになる。
4-2.markdown
解説や問題等の作成は皆さまお馴染みmarkdownで作成することができる。この時気をつけることは特になかった。
4-3.サムネイル
サムネイルは登録しなくてもDonblerがデフォルトで設定してくれるらしいので無理して作る必要はなさそう。しかし、サムネイルはレッスンの購入数に直接影響があるので作れるのであれば作った方がいいと思われる。
5.有料レッスンの投稿
ここで有料レッスンの投稿手順について説明しておく。
5-1.有料レッスン投稿申請
有料レッスンを投稿するにはどうやら別サイトのアカウントを登録して審査を通す必要があった。
Donblerにログインした後、下記リンクから有料レッスン投稿申請が可能である。
https://donbler.com/userinfo/sales
この申請は個人情報を入力するくらいで特に難しい点はなかった。また、審査は入力後すぐに終わった。5-2.振込講座登録
振込口座を登録するフォームを入力する必要がある。当たり前といえば当たり前である。ただ、ここもフォームに入力して登録さえしてしまえば良いだけなので問題はなかった。
有料レッスン申請から振込口座登録まで数分で終わった。
特につまるところもなく登録できるので有料レッスンを投稿する可能性がある人は登録しておいて損はないかもしれない5-3.レッスン投稿
ここでやっと有料レッスンが投稿できる。
投稿ページは右上のアイコンからレッスンを投稿メニューを押すと飛べる。
ここにあるフォームでは
- レッスンレベル
- レッスンのカテゴリ
- テンプレートのsetting.yml
- 価格設定
- サムネイル
- テンプレートのcontens
の6つである。
5-3-1.レッスンのレベル
このフォームではレッスンのレベルを選択することができる。
今回のレッスンは何も知識ない人でも受講できるレッスンなので初心者向けとする。5-3-2.レッスンのカテゴリ
レッスンのカテゴリは3つあって
- HTML/CSS
- JS
- PHP
の3つである。
当レッスンはHTML/CSS/JSなので今回のカテゴリはhtmlcssとjsの2つである5-3-3.setting.yml
ここにはテンプレートで編集したsetting.ymlを投稿する。
何かしら問題があるときちんとエラーが出てくれるので助かる。5-3-4.価格設定
今回一番大事な価格設定である。
今回は価格は1000円に設定し、無料枠の設定もできるようなので設定してみた。
無料枠はとりあえず最大数の3に設定した。5-3-5.サムネイル
画像のサイズや比率の違いが気になって投稿してみたがどうやらきちんと整形してくれてるっぽい。
今回はフリー画像を使用した。5-3-6.contens
これがないと話にならない!
ここではテンプレートのcontentsファイル丸ごとを選択して投稿する。
ファイルを選択した後、間違えたファイルを投稿した場合のためか再度チェックしてくれるのでOKを押す。以上で投稿完了である。
6.レッスンの審査
どうやらDonblerにレッスンを投稿するには審査が必要らしい。
とりあえず審査が終わるまで待った後、無事公開された。以上で有料レッスンの投稿の流れは終了である。
7.終わり
今回Doonblerにレッスンを投稿してみたが、受講者側に立って考えた時Udemyのように動画レッスンもいいが自分の場合動画を見るよりテキストの方が早く進めるので自分にとっては嬉しい。また、環境構築不要で買った瞬間勉強ができるので初心者にはオススメのプログラミング学習サイトになりそうである。今はリリース後ということもありレッスン数はあまり多くないが今後増えて行くのではないかと思う。
今後Donblerで生計を立てるような亡者が現れて来ると思うとワクワクしてきた筆者であった。
- 投稿日:2021-01-22T10:12:07+09:00
Chrome拡張機能を作ってみよう②
前回はChrome拡張機能からファイルのダウンロードまでを投稿しました。
今回はその続きです。Chrome拡張機能とローカルプログラムの連携
Chrome拡張機能とローカルプログラムとの連携について
今回はローカルプログラムをvisual studioで作ったC#のプログラムとします。下準備
まずは連携するための下準備をします。
最初に連携するプログラムの配置場所を決めます。
今回は
c:\extensionSample\program
の配下に置くことにします。連携用JSONファイルを準備
場所に決まりはないですが連携用のjsonファイルをprogramフォルダの配下に配置するprogram.josn{ "name": "put.message", "description": "特定のURLでメッセージを表示する。", "path": "C:\\extensionSample\\program\\program.exe", "type": "stdio", "allowed_origins": [ "chrome-extension://gfoihpmphbkdehkldlglbbcgkcpdfmam/" ] }nameは連携に必要です。今回は「put.message」としています。
pathにはプログラムのパスを指定します。
allowed_originsは拡張機能のIDを指定します。
chromeのアドレスバーに「chrome://extensions」と入力して
前回追加した拡張機能のIDを記載します。レジストリに登録
次に、レジストリにjsonファイルを登録します。
コンピューター\HKEY_LOCAL_MACHINE\SOFTWARE\Google\Chrome\NativeMessagingHosts
にjsonファイルのnameに指定した「put.message」キーを作成する。
既定のデータにjsonファイルパスを指定する。
今回は「c:\extensionSample\program\program.josn」とします。
C#プログラム
では実際にC#のプログラムです。
(結構適当に書いています。ご容赦を。。。)using Microsoft.JScript; using System; using System.IO; using System.Runtime.Serialization; using System.Runtime.Serialization.Json; using System.Text; namespace program { /// <summary> /// ローカルプログラム /// </summary> class localprogram { private static bool NativeMessageFlg = true; private static readonly Encoding enc = Encoding.UTF8; /// <summary> /// メイン処理 /// </summary> static void Main(string[] args) { #if DEBUG // デバッグ時のみ、プロセスにアタッチ System.Diagnostics.Debugger.Launch(); #endif try { // 起動処理の確認(Chrome拡張機能 or その他(直接起動)) if (args.Length > 0 && args[0].IndexOf("chrome-extension") >= 0) { // Chrome拡張機能 NativeMessageFlg = true; } else { // その他(直接起動) NativeMessageFlg = false; } Request request = new Request(); if (NativeMessageFlg) { // Chrome拡張機能から取得 Stream stdin = Console.OpenStandardInput(); byte[] bytes = new byte[4]; stdin.Read(bytes, 0, 4); int length = BitConverter.ToInt32(bytes, 0); string input = ""; for (int i = 0; i < length; i++) { input += (char)stdin.ReadByte(); } stdin.Close(); using (var ms = new MemoryStream(enc.GetBytes(input))) { var jsonSer = new DataContractJsonSerializer(typeof(Request)); request = (Request)jsonSer.ReadObject(ms); } } // Chrome拡張機能から受け取った「Action」によって処理を分ける Response response = new Response(); int limit = 1024 * 1024 - 2; string stringText = string.Empty; switch (request.Action) { case "putmessage": string responsJson; response.Code = 0; response.Data = request.Data; responsJson = ConvertJson(response, typeof(Response)); string stringText1 = GlobalObject.encodeURIComponent(responsJson); while (stringText1.Length >= limit) { WriteString("\"" + stringText1.Substring(0, limit) + "\""); stringText1 = stringText1.Substring(limit); } WriteString("\"" + stringText1 + "\""); break; default: response.Code = 9; response.Data = "エラー"; responsJson = ConvertJson(response, typeof(Response)); string stringTextErr = GlobalObject.encodeURIComponent(responsJson); while (stringTextErr.Length >= limit) { WriteString("\"" + stringTextErr.Substring(0, limit) + "\""); stringTextErr = stringTextErr.Substring(limit); } WriteString("\"" + stringTextErr + "\""); break; } } catch (Exception ex) { } Environment.Exit(0); } /// <summary> /// オブジェクト→JSONへの変換 /// </summary> private static string ConvertJson(object obj, Type type) { using (var ms = new MemoryStream()) { var serializer = new DataContractJsonSerializer(type); serializer.WriteObject(ms, obj); return enc.GetString(ms.ToArray()); } } private static void WriteString(string stringData) { byte[] bytes = BitConverter.GetBytes(stringData.Length); Stream stdout = Console.OpenStandardOutput(); for (int i = 0; i < 4; i++) { stdout.WriteByte(bytes[i]); } byte[] b = Encoding.UTF8.GetBytes(stringData); stdout.Write(b, 0, b.Length); stdout.Flush(); stdout.Close(); } /// <summary> /// リクエストパラメータ /// </summary> [DataContract] struct Request { [DataMember(Name = "Action")] public string Action { get; set; } [DataMember(Name = "Data")] public string Data { get; set; } } /// <summary> /// レスポンス /// </summary> [DataContract] struct Response { [DataMember(Name = "Code")] public int Code { get; set; } [DataMember(Name = "Data")] public string Data { get; set; } } } }特にこれと言って注意点はないと思います。
System.Diagnostics.Debugger.Launch();
の箇所はデバックするときに便利なので付けています。ビルドしたらprogram.exeファイルを
C:\extensionSample\program\に配置します。次に「background.js」を修正します。
background.js/* 初期起動時の処理 */ // インストール時かバージョンアップ時 chrome.runtime.onInstalled.addListener(function() { initialize(); }); // ブラウザ起動時 chrome.runtime.onStartup.addListener(function() { initialize(); }); function initialize() { // ファイルダウンロード先 var dlFileName = "http://localhost/sample.txt"; // ファイルを取得 $.ajax({ url: dlFileName, type: "GET", dataType: 'binary', responseType:'arraybuffer', timeout: 500 }) // 成功時 .done(errorHandle(function (response) { var data = response; // ArrayBufferで取得するので var textfiledata = String.fromCharCode(...new Uint8Array(data)); console.log(textfiledata); // 渡す前にbase64エンコードしておく textfiledata = btoa(textfiledata); // ダウンロードした内容をprogramに連携する chrome.runtime.sendNativeMessage('put.message', {Action: "putmessage", Data: textfiledata}, errorHandle(function(response, thread){ // デコードしてJSON形式にする var getData = JSON.parse(decodeURIComponent(response)); // 受け取ったコードがエラーの場合 if(getData.Code != 0){ throw new Error('programでエラーが発生'); } // 受け取ったデータをlocalStorageに保存しておく localStorage.setItem('urllist', atob(getData.Data)); return true; })); // 成功した場合は拡張機能のアイコンを切り替える chrome.browserAction.setIcon({path:"images/success.png"}); // localStorageにステータスを保存 localStorage.setItem('Status', 'ok'); })) // 失敗時 .fail(errorHandle(function () { // localStorageにステータスを保存 localStorage.setItem('Status', 'ng'); })); return true; } /** * 例外をまとめて処理する */ function errorHandle(process) { return function(){ try { return process.apply(this, arguments); } catch (e) { chrome.browserAction.setIcon({path:"images/abnormal.png"}); console.error(e); } }; }chrome.runtime.sendNativeMessage([program.josnで指定したname],渡す値)
がローカルプログラム呼び出しの部分です。
データのやり取りはjson形式で行います。動かしてみる
Chromeブラウザのアドレスバーに「chrome://extensions」と入力し
拡張機能を更新する。バックグランドページを開いて、ApplicationのLocal Storageから対象の拡張機能IDを選択する。
urllistにsample.txtの内容が入っていれば成功です。
今回はここまで
次回はURL監視について書いていきます。(urlエンコードはいらなかったかな)
- 投稿日:2021-01-22T10:09:25+09:00
Chrome拡張機能を作ってみよう①
背景
Chromeの拡張機能を作ってみたが、どこかにアウトプットしないと絶対に忘れると思ったため
基本的に自分用メモなのでよくわからなかったらすみません。作るもの
① Chrome拡張機能からWeb上のファイルをダウンロードする。
② ダウンロードしたファイルをC#で作ったexeに連携する。
③ exeでファイル内容を受け取りChrome拡張機能に返す。
④ exeから受け取ったURLと同じURLを開いた場合にメッセージを表示する。
補足)Chrome拡張機能をChromeウェブストアを介さずにインストールさせる準備するもの
・テキストエディタ
・visual studio
・xampp(ファイルダウンロード用)Chrome拡張機能からファイルのダウンロード
今回はc:\extensionSample\extensions
というフォルダを作って、この中に作っていきます。extensionsの中はこんな感じです
[css]
popup.css - popup.htmlのスタイル
[images]
abnormal.png - Chrome拡張機能エラー時のアイコン
success.png - Chrome拡張機能正常時のアイコン
icon16.png
icon48.png
icon128png
[js]
background.js - 動作するファイル
jquery-3.4.1.js
jquery.binarytransport.js
popup.js - popup.htmlにて動作する
message.js - メッセージを表示するファイル
manifest.json - 設定ファイル
popup.html - Chrome拡張機能をクリックした際に表示する画面この構成で作っていきます。
まず、manifest.json_から
manifest.json{ "manifest_version": 2, "name": "message表示", "version": "1.0", "description" : "Chrome拡張機能のサンプル", "permissions": [ "nativeMessaging" ], "icons": { "16": "images/icon16.png", "48": "images/icon48.png", "128": "images/icon128.png" }, "browser_action" : { "default_icon" : "images/abnormal.png", "default_title" : "Chrome拡張機能のサンプル", "default_popup": "popup.html" }, "background": { "scripts": [ "js/jquery-3.4.1.js", "js/jquery.binarytransport.js", "js/background.js" ], "persistent": false }, "content_scripts": [ { "matches": [ "<all_urls>" ], "js": [ "js/jquery-3.4.1.js", "js/jquery.binarytransport.js", "js/message.js" ] } ] }permissionsにはコンピューターにインストールされたアプリケーションと拡張機能との間のメッセージ交換をするためnativeMessagingを指定します。
URLを監視するのでmatchesはall_urlsを指定します。
次にbackground.js
background.js/* 初期起動時の処理 */ // インストール時かバージョンアップ時 chrome.runtime.onInstalled.addListener(function() { initialize(); }); // ブラウザ起動時 chrome.runtime.onStartup.addListener(function() { initialize(); }); function initialize() { var dlFileName = "http://localhost/sample.txt"; $.ajax({ url: dlFileName, type: "GET", dataType: 'binary', responseType:'arraybuffer', timeout: 500 }) // 成功時 .done(errorHandle(function (response) { var data = response; // ArrayBufferで取得するので var textfiledata = String.fromCharCode(...new Uint8Array(data)); console.log(textfiledata); // 成功した場合は拡張機能のアイコンを切り替える chrome.browserAction.setIcon({path:"images/success.png"}); // localStorageにステータスを保存 localStorage.setItem('Status', 'ok'); })) // 失敗時 .fail(errorHandle(function () { // localStorageにステータスを保存 localStorage.setItem('Status', 'ng'); })); return true; } /** * 例外をまとめて処理する */ function errorHandle(process) { return function(){ try { return process.apply(this, arguments); } catch (e) { chrome.browserAction.setIcon({path:"images/abnormal.png"}); console.error(e); } }; }あとはpopup系です
popup.js$(function() { /*** localStorageからステータスを取得。 ***/ var Status = localStorage.getItem('Status'); if(Status == 'ok'){ $('#status').html('<b>正常</b>'); }else{ $('#status').html('<b>異常</b>'); } });popup.html<!DOCTYPE html> <html lang="ja"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta http-equiv="Pragma" content="no-cache"> <meta http-equiv="Cache-Control" content="no-cache"> <link href="../css/popup.css" rel="stylesheet"> <script src="../js/jquery-3.4.1.js" ></script> <script src="../js/popup.js" ></script> </head> <body id="body"> <div class="box"> <div class="header"> <h3 style="margin: 5px 0">動作状況</h3> </div> <div class="content"> <p id="status"></p> </div> </div> </body> </html>popup.cssbody { font-family: sans-serif; width: 300px; height: 180px; background-color: lightgray; } p { margin: 0px; } .box { border: solid 1px; width: 95%; height: 90%; padding: 5px; }message.jsはとりあえず空でファイルだけ作っておきます。
動かしてみよう
ここまで出来たら動かしてみよう
Chromeのアドレスバーに
chrome://extensionsを入力して拡張機能を表示する右上の「デベロッパー モード」をONにして「パッケージ化されていない拡張機能を読み込む」から
c:\extensionSample\extensionsを選択する。一覧に出てきたら、右上の「拡張機能」から確認してみます。
http://localhost/sample.txt
が存在すれば正常表示、無ければ異常表示になると思います。sample.txtの内容は以下のようにしています。
{"1" : "http://localhost/ap1/","2" : "http://localhost/ap2/"}
今回はここまで
次回はローカルプログラムとの連携について書こうかと思います。(確認してないけどerrorHandleはちゃんと動いているかな・・・)
- 投稿日:2021-01-22T08:49:06+09:00
【Typescript】 Error: Cannot find module 'express' の解決
あるファイルを読み込もうとしたら、エラーメッセージが出た。
node index.js internal/modules/cjs/loader.js:638 throw err; ^ Error: Cannot find module 'express' at Function.Module._resolveFilename (internal/modules/cjs/loader.js:636:15) at Function.Module._load (internal/modules/cjs/loader.js:562:25) at Module.require (internal/modules/cjs/loader.js:692:17) at require (internal/modules/cjs/helpers.js:25:18) at Object.<anonymous> (/home/ec2-user/environment/photo/realitycapture/reality.capture-nodejs-photo.to.3d/index.js:18:15) at Module._compile (internal/modules/cjs/loader.js:778:30) at Object.Module._extensions..js (internal/modules/cjs/loader.js:789:10) at Module.load (internal/modules/cjs/loader.js:653:32) at tryModuleLoad (internal/modules/cjs/loader.js:593:12) at Function.Module._load (internal/modules/cjs/loader.js:585:3)解決策
npm insatll expressexpressを入れましょう。以上。
- 投稿日:2021-01-22T04:58:37+09:00
TypeScript基礎の型を学んだので説明する
TypeScriptのインストール
npm install -g typescript型指定
Number: 数値
String: 文字
Boolean: true / false
Any: 全ての型
Void: 返り値がない
undefined: undefined
null: null配列
let list: number[] = [1, 2, 3];Tuple型
// 0番目にnumber、1番目にstringのみ入れることができる let tuple: [string, number] = ["myname", 3];Enum形
デフォルト値なし
// 型の定義 enum Family {Rick, Mike,Json}; // 使用するとインデックス番号が入る let Rick:number = Family.Rick; // 0 let Mike:number = Family.Rick; // 1 let Json:number = Family.Rick; // 2デフォルト値あり
// 型の定義 enum Ages {Rick = 21 , Mike = 22 , Json = 23}; // 使用するとデフォルト値が入る let Rick:number = Family.Rick; // 21 let Mike:number = Family.Rick; // 22 let Json:number = Family.Rick; // 23文字列はエラー
// エラー enum Hello {Rick = "Rick dayo" , Mike = "Mike dayo" , Json = 'Json dayo'};インデックスを指定してキーを取り出す
// 型の定義 enum Ages {Rick = 21 , Mike = 22 , Json = 23}; let Rick:string = Family[21]; // Rick let Mike:string = Family[22]; // Mike let Json:string = Family[23]; // JsonNever型
・returnが絶対に走らない場合、戻り値がない場合never型になる
・値は入れられない// errorを返す関数 const error = ():never => { throw new Error("エラー"); }voidとneverの違い
Void型
・戻り値undefinedを含む
・returnを省略したり、戻り値のないreturnをするとundefinedが返るNever型
・neverはそもそもreturnをしない。
・例外を投げる場合や無限ループをする場合など、戻り値が得られないときに使う
・undefinedも受け付けないので、戻り値の型にneverを指定した場合はreturnは書けない。onst void1 = () => {} const void2 = () => { return } const never = () => { while(true){} } let voidFunc1:never = void1() // エラー let voidFunc2:never = void2() // エラー let neverFunc:never = never() // OKObject型
・プリミティブ型ではないことを表現する型
// プリミティブ型を引数にとらないようにする関数 function viewObject(o: object): void { console.log(`myData: ${o}`); };インターフェース
オブジェクトが特定の構造が合っているかどうかチェックする。
interface Props { name: string; } function getMyname(props:Props): string { return `I'm ${props.name}`; }学習したUdemyの動画
【世界で7万人が受講】Understanding TypeScript - 2020年最新版
https://www.udemy.com/course/understanding-typescript-jp/
- 投稿日:2021-01-22T04:58:37+09:00
TypeScript基礎の型を学んだので記述しておく
TypeScriptのインストール
npm install -g typescript型指定
Number: 数値
String: 文字
Boolean: true / false
Any: 全ての型
Void: 返り値がない
undefined: undefined
null: null配列
let list: number[] = [1, 2, 3];Tuple型
// 0番目にnumber、1番目にstringのみ入れることができる let tuple: [string, number] = ["myname", 3];Enum形
デフォルト値なし
// 型の定義 enum Family {Rick, Mike,Json}; // 使用するとインデックス番号が入る let Rick:number = Family.Rick; // 0 let Mike:number = Family.Rick; // 1 let Json:number = Family.Rick; // 2デフォルト値あり
// 型の定義 enum Ages {Rick = 21 , Mike = 22 , Json = 23}; // 使用するとデフォルト値が入る let Rick:number = Family.Rick; // 21 let Mike:number = Family.Rick; // 22 let Json:number = Family.Rick; // 23文字列はエラー
// エラー enum Hello {Rick = "Rick dayo" , Mike = "Mike dayo" , Json = 'Json dayo'};インデックスを指定してキーを取り出す
// 型の定義 enum Ages {Rick = 21 , Mike = 22 , Json = 23}; let Rick:string = Family[21]; // Rick let Mike:string = Family[22]; // Mike let Json:string = Family[23]; // JsonNever型
・returnが絶対に走らない場合、戻り値がない場合never型になる
・値は入れられない// errorを返す関数 const error = ():never => { throw new Error("エラー"); }voidとneverの違い
Void型
・戻り値undefinedを含む
・returnを省略したり、戻り値のないreturnをするとundefinedが返るNever型
・neverはそもそもreturnをしない。
・例外を投げる場合や無限ループをする場合など、戻り値が得られないときに使う
・undefinedも受け付けないので、戻り値の型にneverを指定した場合はreturnは書けない。onst void1 = () => {} const void2 = () => { return } const never = () => { while(true){} } let voidFunc1:never = void1() // エラー let voidFunc2:never = void2() // エラー let neverFunc:never = never() // OKObject型
・プリミティブ型ではないことを表現する型
// プリミティブ型を引数にとらないようにする関数 function viewObject(o: object): void { console.log(`myData: ${o}`); };インターフェース
オブジェクトが特定の構造が合っているかどうかチェックする。
interface Props { name: string; } function getMyname(props:Props): string { return `I'm ${props.name}`; }学習したUdemyの動画
【世界で7万人が受講】Understanding TypeScript - 2020年最新版
https://www.udemy.com/course/understanding-typescript-jp/
- 投稿日:2021-01-22T04:58:37+09:00
UdemyでTypeScript学習メモ
TypeScriptのインストール
npm install -g typescript型指定
Number: 数値
String: 文字
Boolean: true / false
Any: 全ての型
Void: 返り値がない
undefined: undefined
null: null配列
let list: number[] = [1, 2, 3];Tuple型
// 0番目にnumber、1番目にstringのみ入れることができる let tuple: [string, number] = ["myname", 3];Enum形
デフォルト値なし
// 型の定義 enum Family {Rick, Mike,Json}; // 使用するとインデックス番号が入る let Rick:number = Family.Rick; // 0 let Mike:number = Family.Rick; // 1 let Json:number = Family.Rick; // 2デフォルト値あり
// 型の定義 enum Ages {Rick = 21 , Mike = 22 , Json = 23}; // 使用するとデフォルト値が入る let Rick:number = Family.Rick; // 21 let Mike:number = Family.Rick; // 22 let Json:number = Family.Rick; // 23文字列はエラー
// エラー enum Hello {Rick = "Rick dayo" , Mike = "Mike dayo" , Json = 'Json dayo'};インデックスを指定してキーを取り出す
// 型の定義 enum Ages {Rick = 21 , Mike = 22 , Json = 23}; let Rick:string = Family[21]; // Rick let Mike:string = Family[22]; // Mike let Json:string = Family[23]; // JsonNever型
・returnが絶対に走らない場合、戻り値がない場合never型になる
・値は入れられない// errorを返す関数 const error = ():never => { throw new Error("エラー"); }voidとneverの違い
Void型
・戻り値undefinedを含む
・returnを省略したり、戻り値のないreturnをするとundefinedが返るNever型
・neverはそもそもreturnをしない。
・例外を投げる場合や無限ループをする場合など、戻り値が得られないときに使う
・undefinedも受け付けないので、戻り値の型にneverを指定した場合はreturnは書けない。onst void1 = () => {} const void2 = () => { return } const never = () => { while(true){} } let voidFunc1:never = void1() // エラー let voidFunc2:never = void2() // エラー let neverFunc:never = never() // OKObject型
・プリミティブ型ではないことを表現する型
// プリミティブ型を引数にとらないようにする関数 function viewObject(o: object): void { console.log(`myData: ${o}`); };インターフェース
オブジェクトが特定の構造が合っているかどうかチェックする。
interface Props { name: string; } function getMyname(props:Props): string { return `I'm ${props.name}`; }学習したUdemyの動画
【世界で7万人が受講】Understanding TypeScript - 2020年最新版
https://www.udemy.com/course/understanding-typescript-jp/