20210122のJavaScriptに関する記事は18件です。

lerna + yarn workspaces で GitHub Packages の private repository を利用してみた(殴り書きメモ)

「サーバーとクライアントの共有部分をライブラリ化したい → でも非公開にしたい」という要望への解決方法の一つ。コードが JS/TS 一本で、リポジトリと所有権が一カ所で良ければ使いやすそう。

そもそも、公開パッケージとして分離できるならそれでいい話だし。

今回は Windows, nodist, node v14, yarn install v1.22.10 で試した。

以下では、github のユーザ名を GITHUB_USERNAMEgithub のリポジトリ名を GITHUB_REPONAME と記すことにする(コマンドをコピペして確認していないので細部に誤りがあると思います)。PACKAGE_NAMElib, client, server のいずれかとする。

Github Actionspublish する方法は試してないです。

シンプルな競合対象としては、npm linkgit submodule などでの結合だと思う。

とりあえず、手元で試しに導入してみたのでメモを書き殴ります。

lerna

複数パッケージを一つのリポジトリで管理運用するためのツール。

コミットを元に、サブディレクトリ毎に package として publish するなど。

パッケージの依存関係を一括で解決する lerna bootstrap の機能については、Yarn Workspaces を利用することにした。

Yarn Workspaces

複数のパッケージで依存関係を一括管理する機能。

依存関係の共有による最適化と、競合問題の回避を謳っている。

yarn は使っていて、覚えることが少なそうなので採用。

最終的なフォルダ構成

lib, client, server という三つの構成を一つのリポジトリに入れて、clientserver から 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 publishmain ブランチを GitHub Packagespublish する。その際には 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 workspacesyarn -W で省略可能。-W or workspaces) が付く以外はいつも通りに 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 の 設定

namescope を付ける(GitHub Packages 用)

name@GITHUB_USERNAME/PACKAGE_NAME の用に、github のユーザ名を scope として付与する。これは、GitHub Packages の為に必要。組織で利用する場合は org名 を(https://github.com/ の後ろと一致させる必要があるという認識)。

nameGITHUB_REPONAME も入れたかったのだけど、scope についてはユーザ名と揃える必要があった。埋め込むなら PACKAGE_NAME に埋め込むことになりそう。

repositorypublishConfig

パッケージのリポジトリと 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 tokensPersonal access token を発効する。

名前は分かりやすい名前を付ければOK。

権限は、パッケージの作成・編集が必要なので Select scopesrepo, 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.ZY の部分がインクリメントされてパッケージ化される。バージョンは 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 / clientpackages.json に依存関係を指定する。その際にパッケージ化されているバージョン名を指定しないと駄目みたい。

packages/server/packages.json
{
  // 
  "dependencies": {
    "@GITHUB_USERNAME/lib": "^0.6.0",
  },

後は、npm パッケージと同様に import なり require なりで利用できるはず。

packages/server/lib/hoge.ts
import { 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 を利用したかったので、.npmrcservers/.npmrc にコピーした。

他には特別な設定は必要なく、firebase deploy --only functions の実行時間が延びてしまったぐらいで、上手く参照してくれているとおもう。

ライブラリの作り方が不味いなど lernayarn workspaces には無関係のエラーは起こしていたが、それは別の問題。

新規に git clone した後

yarn を実行すると、全パッケージの依存関係を全て解決してくれる。node_modules 以下を消した場合なども同様。

npm を使う場合は、yarn lerna bootstrap を実行した後、パッケージ毎に npm install になるっぽい。

参照したページ

感謝。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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でやりとりできそう!
もっと良いやり方などがありましたら教えてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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)に参加できない

◆ Twitch モバイル版

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 面白そうだなーと思ってるので周辺技術を思い出すにはいい機会でした。

--

以上です。お疲れ様でした。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

フレームワークとは

今回はフレームワークについて学習をしました。フレームワークとは何か、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/d12416700f7e2329cb09

FuelPHP

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のようなフラットなデザインを簡単に作成することができます。クラスを指定するだけで簡単にレスポンシブデザインに対応したページを作ることが出来ます。

今回の記事は以上になります。
色々と拙い部分があるかと思います。
また、自分が実際に使っておらず調べたり、教科書に書いて有りそうな説明なってしまったものもあり、説明が淡々としすぎてしまいました。今後、自分で使いたいなと思ってメモしていたので理解が深まったら更新して行こうと思います。
間違っている点、理解が不完全な点があればご指摘いただければ幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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>;
};

参考サイト

https://stackoverflow.com/questions/40477245/is-it-possible-to-use-if-else-statement-in-react-render-function:embed

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【新春企画】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...

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

深さがバラバラの配列からスマートに値を取り出す。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とは

Array.prototype.reduce() MDN

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
// >> 15

0, 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"

配列に一階層ずつ潜っていく様子がわかります

さいごに

そもそもこんな複雑な配列を作るなら連想配列を使うべきだったと思います
反省してます…

最後まで読んでくださりありがとうございます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Next.jsにCSS Modulesを適応させる(メタデータ設定も)

vercel社が提供するNext.js公式チュートリアル引用でございます。まるパクリです。

手順

① Next.jsのプロジェクトのルートにcomponentsフォルダを作成。

$ mkdir components

② ①で作成した/components配下にlayout.jsを作成し以下を記述。

$ touch components/layout.js
layout.js
export 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.js
import 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.js
import 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 - youll be building a site like this on{' '}
          <a href="https://nextjs.org/learn">our Next.js tutorial</a>.)
        </p>
      </section>
    </Layout>
  )
}

これで、Layoutコンポーネントの持つheaderやmainの要素を受け継ぐことができるはず!
終わり!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AR.jsのLocation Basedで遠くにモデルを無理やり表示させた話。

AR.jsのLocation Basedで無理やりモデルを表示したい

前回AR.jsを使用してLocation Basedで遠くにモデルを表示させたかった話を書きました。

おさらいするとAR.jsのLocation Basedは、1km以上遠方の位置を指定するとコンテンツが表示されません。
解決策としてvideoTexture: true;オプションを指定すると表示できますが、手持ちのiPhone(iOS14)では、うまく動かなかったので今回は無理やり表示させていきたいと思います。

はじめに断っておきますが、根本的な解決策ではないです。

プラン

d0-94bb-5e761af7b59c.png

①の実際の位置を指定するとオブジェクトを表示させることができないので、③の自分の位置から①の角度と距離を求めて、②の表示位置を計算します。

目的地が複数ある場合には遠い位置ほど小さく表示させたいので、距離によりスケールを変えたいと思います。
便宜上の地点を目的地表示位置現在地と呼ぶこととします。

コード

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.js
import LatLon from 'https://cdn.jsdelivr.net/npm/geodesy@2.2.1/latlon-spherical.min.js';

今回はクラスにしますので、定義を行います。

calc.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;
    }

distance: 距離
bearing : 方角
newPosition: 表示位置の緯度経度の配列。
currentPosition: 現在地。緯度経度の配列。
objectSize: 表示サイズ。
newDistance: 1km以内で、表示させる距離。今回は800mに設定。
AR.jsは、positionとかも単位はメートルです。

距離と角度を求めます。

calc.js
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;
    }

引数
currentPosiArg: 現在地の緯度経度の配列。
targetPosition: 目的地の緯度経度の配列。

まず現在地と目的地のgeodesyインスタンスを作成します。
距離は、distanceToで求めます。
角度は、finalBearingToで求めます。

2.新しい表示位置を計算する。

calc.js
calcNewPosition(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.js
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);
        }
    }

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.js
import 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.');
 }

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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拡張機能のインストールについて投稿します。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ブログラミング学習サイトDonblerに有料レッスンを投稿してみた

ブログラミング学習サイトDonbler

2021年1月にリリースされたDonblerというサービスが気になっている。このサイトどうやらエディタを内臓(Progateのような?)していてサイト内で完結するらしい。
Progateとなにが違うのか検証してみたところレッスンは公式ではなくユーザが投稿できてさらに有料にもできる仕様らしい。この点はUdemyと似ているがUdemyは動画のレッスンに対してDonblerはテキストベースのレッスンである。これはエンジニアにとっては嬉しい仕様なのではないだろうか。
今回はProgateとUdemyの融合体であるDonblerに試しに有料レッスンを投稿してみることにする。
スクリーンショット 2021-01-21 11.38.37(2).png

今回投稿したレッスンのリンクを貼っておきます!興味のある方は買ってみてください!
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.実際にレッスンを作る

ここからは実際にレッスンの中身を作っていく。
公式では

  1. markdown
  2. サムネイル
  3. ソースコード

の順になっているが個人的には

  1. ソースコード
  2. markdown
  3. サムネイル

の手順の方が自分にはあっているのかなと感じたので後者の手順で解説していく。

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を押す。

以上で投稿完了である。

今回のレッスンの投稿確認ページを参考に添付しておく。
スクショ.png

6.レッスンの審査

どうやらDonblerにレッスンを投稿するには審査が必要らしい。
とりあえず審査が終わるまで待った後、無事公開された。

以上で有料レッスンの投稿の流れは終了である。

7.終わり

今回Doonblerにレッスンを投稿してみたが、受講者側に立って考えた時Udemyのように動画レッスンもいいが自分の場合動画を見るよりテキストの方が早く進めるので自分にとっては嬉しい。また、環境構築不要で買った瞬間勉強ができるので初心者にはオススメのプログラミング学習サイトになりそうである。今はリリース後ということもありレッスン数はあまり多くないが今後増えて行くのではないかと思う。
今後Donblerで生計を立てるような亡者が現れて来ると思うとワクワクしてきた筆者であった。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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」とします。
image.png

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エンコードはいらなかったかな)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.css
body {
  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はちゃんと動いているかな・・・)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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 express

expressを入れましょう。以上。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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]; // Json

Never型

・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() // OK 

Object型

・プリミティブ型ではないことを表現する型

// プリミティブ型を引数にとらないようにする関数
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/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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]; // Json

Never型

・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() // OK 

Object型

・プリミティブ型ではないことを表現する型

// プリミティブ型を引数にとらないようにする関数
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/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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]; // Json

Never型

・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() // OK 

Object型

・プリミティブ型ではないことを表現する型

// プリミティブ型を引数にとらないようにする関数
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/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む