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

【2025年の崖】経産省の「DXレポート2」が刊行されたので読んでみた

「2025年の崖」というキャッチーな用語などおよそ政府の刊行物とは思えないほどキレのある文面で話題になった経済産業省のDXレポート(@2018年)ですが、昨年の暮れに「DXレポート2」が刊行されていたのでそのレポートです

なお、DXレポートについてご存知ない方のために抜粋をすると、DXレポートはこういうものです

「2018 年に公開した DX レポートにおいては、複雑化・ブラックボックス化した既存システムを解消できず DX が実現できない場合、デジタル競争の敗者になってしまうだけでなく、多額の経済損失が生じるとして警鐘を鳴らし(2025 年の崖)、この問題に対応するため、2025 年までに集中的にシステム刷新を実施する必要があると指摘した」

結構「2025年の崖」っていう言葉が話題になったんですよね。
昨年の12月29日に刊行されたDXレポート2もとても面白かったので、ITに携わる人はぜひ知っておいて良いことだと思ったのでQiitaに載せさせていただきました

各ユーザー企業におけるIT活用の指針に加えて、ベンダー企業のあるべき姿などかなり突っ込んだ内容となっており、前回にもましてキレのある文章で読み応えバッチリでした。「2020年の崖」に引くも劣らない名言揃いでしたので、章ごとにまとめていきたいと思います。

オリジナル

経産省のHPにあります。なるべく内容を損なわないようにしましたが、ぜひソースを当たっていただくといいと思います。

https://www.meti.go.jp/press/2020/12/20201228004/20201228004.html

エグゼクティブサマリ

それでは、まずは冒頭の「エグゼクティブサマリ」から追っていきます。
「エグゼクティブサマリ」という名前に負けず中身も迫真に迫るものがありました

まず、2018年のDXレポートでDXによる変革の警鐘を鳴らしたにも関わらずなかなか取り組みが進まないことを受けて下記のように断じます。

  • 実に全体の9割以上の企業が DX にまったく取り組めていない(DX 未着手企業)レベルか、散発的な実施に留まっている(DX 途上企業)状況であることが明らかになった。
  • 我が国企業全体における DX への取り組みは全く不十分なレベルにあると認識せざるを得ない

と断じます。

そして、結構大企業に勤めている人はニヤリとしてしまうかもしれませんが、それに対してこのようにコメントします

  • DX =「レガシーシステムの刷新」などの本質ではない解釈が是となっていた
  • DX の本質とは単にレガシーなシステムを刷新すると言ったことに留まるのではなく、事業環境の変化に迅速に適応する能力を身につけること、そしてその中で企業文化変革することにあると考えられる

そして、コロナ禍に言及した後、このようにサマリーを締めくくっています。

  • 人々の固定観念が変化している今こそ「2025年の壁」問題の対処に向けて、企業文化を変革するある意味絶好(最後)の機会である

いや、コロナ禍に言及して「これが絶好で最後の機会」という部分が迫真に迫るものがありますね。それでは全体構成を紹介の後、本文を細かく見ていきます。

全体構成と読みどころ

ppt形式のサマリーとWord形式のレポートがあるのですが、全体の構成はこのようになっております(pptのサマリから転載)。

スクリーンショット 2021-01-13 20.55.17.jpg

個人的には、読みどころは下記だと思いました。

  • コロナ禍で表出した本質的な課題
  • 企業の目指すべき事業変革の方向性
  • ベンダー企業の目指すべき変革の方向性
  • 企業の経営・戦略の変革の方向性について、コロナ禍を契機に企業が直ちに取り組むべきもの
  • DX を進めるための短期的、中長期的な対応
  • 変革を加速するための政府の取組

それでは、それぞれについて抜粋する形で紹介していきます。

2章:コロナ禍で表出した本質的な課題

1章はこれまでの部分で説明したので2章からの紹介です。結構大きな題目を掲げて「コロナ禍で表出した本質的な課題」とありますが、いったい何なのでしょうか
まず、2020年を下記のように振り返ります。

  • 2020 年初頭からの新型コロナウイルスの世界的な感染拡大により、企業は「感染拡大を防ぎ顧客・従業員の生命を守りながら、いかに事業を継続するか」という対応を否応なしに求められることとなった

そして、テレワークの増加や新しいデジタル技術を活用した楽しみが人々の中で広まりつつあることを踏まえて
「人々は新たな価値の重要性に気付き、コロナ禍において新しいサービスを大いに利用し、順応している」
と国民を評価します。
しかし、それに追いつける企業と追いつけない企業がいることを記載した上でこのように断じます。

「ビジネスにおける価値創出の中心は急速にデジタル空間へ移行しており、今すぐ企業文化を刷新しビジネスを変革できない企業は、デジタル競争の敗者としての道を歩むことになるであろう
「そして、デジタル技術によるサービスを提供するベンダー企業も、受託開発型の既存のビジネスモデルではこのような変革に対応できないことを認識すべき」

これ政府の刊行物ぽくないですよね、?? そのように断じたのち、目指すべき方向についてテーマが移ります。

3章:企業の目指すべき方向性

3章は「デジタル企業の姿と産業の変革」という章で、ユーザ企業とベンダー企業がそれぞれ何を目指すべきかということを短期、中期長期の視点から分析しています。そして、前段でこのように名言が飛び出します。

  • ビジネスにおける価値創出の源泉はデジタルの領域に移行しつつあり、この流れはコロナ禍が終息した後も元には戻らない
  • 周囲の環境が変わっているにもかかわらず、これまで続けてきた業務形態やビジネスモデルは所与のものであるという固定観念に囚われてしまうと、抜本的な変革を実現することはできない

そして、ベンダー企業の目指すべき方向に章は進みます。

ベンダー企業の目指すべき方向性

  • 価値創造型のビジネスにおいては、ユーザー企業は絶えず変化する顧客のニーズに対応するために自社の IT システムを迅速に更新し続ける必要がある。そのためには、最もニーズの高い機能を迅速に開発し,フィードバックしながら変化に迅速に対応できるアジャイル型に開発を変革しなければ変化の速さに対応できない
  • 従来のウォーターフォール開発による受託開発型のビジネスに固執するベンダー企業は、今後ユーザー企業のニーズ・スピード感に応えられなくなる

そして問題点を指摘した後に目指すべき方向を論じます。

  • 顧客や社会の課題を正確にとらえるために、ベンダー企業はユーザー企業と DX を一体的に推進する共創的パートナーとなっていくことが求められる

なぜなら、その心は、

「米国では、システム開発をユーザー企業で行う等、ベンダー企業との分野の境目がなくなる形で変化が加速している。しかし、わが国では IT 人材がベンダー企業に偏り、雇用環境も米国とは異なる」ためです。したがって「デジタル社会における将来のベンダー企業には、顧客企業と自社の DX をともに進めていくことが求められる」からです。

以上のことはppt形式サマリーのP9を見れば綺麗にまとまっていました。

スクリーンショット 2021-01-13 21.13.54.jpg

そして次にユーザー企業を含む全体の話です。ユーザー企業はどうすればいいのでしょうか。

企業の経営・戦略の変革の方向性

短期、中長期にわけて章立てがありましたが、まずは短期の部分です。これは比較的内容が複雑なのでサマリにまとまっているものを転載させていただきます。政府刊行物のため転載が自由ということですので。

スクリーンショット 2021-01-13 21.23.38.jpg

スクリーンショット 2021-01-13 21.24.24.jpg

以下、各ポイントについての詳細です

DX推進に向けた関係者間の共通理解の形成

まず、DX推進に向けた関係者間の共通理解の形成が短期的にしなければならないことですよと言っているわけですが、これは前提として下記の2点があることを踏まえて

  • DX の推進にあたっては、経営層、事業部門、IT 部門が協働してビジネス変革に向けたコンセプトを描いていく必要がある
  • DX を推進する関係者の間で基礎的な共通理解を初めに形成することが必要

具体的には下記の方向を示しています。

経営層の課題をデータとデジタル技術を活用していかに解決していくかという視点に対しては、経営層や事業部門がアイデアを提示し、デジタルを活用することで可能となるまったく新たなビジネスを模索するという視点に対してはIT 部門がアイデアを提示し、仮説検証のプロセスを推進していくこと

そして最後にとても(!)いいことが書いてあります。

関係者間での協働を促すためにも、アジャイルマインド(俊敏に適応し続ける精神)や、心理的安全性を確保すること(失敗を恐れない・失敗を減点としないマインドを大切にする雰囲気づくり)が求められる

アジャイルマインドで心理的安全、いいですよね。。!

CIO/CDXO の役割・権限等の明確化

その他、短期的にやらないといけないこととしてCIO/CDXO の役割・権限等の明確化もあります。これは抜粋だけで。

  • CIO/CDXO がどのような役割・権限を担うべきか明確にした上で、これに基づき、DX を推進するための適切な人材が配置されるようにするべき
  • 適切なリーダーシップが欠如していると IT 部門が事業部門の現行業務の支援に留まり、業務プロセスが個別最適で縦割りとなってしまうため、DX の目標である事業変革を妨げる
  • デジタル化に係る投資を行うためには、事業部門の業務プロセスの見直しを含めた IT 投資の効率化にとどまらず、場合によっては不要となる業務プロセスと対応する IT システムの廃止・廃棄にまでつなげることが必要

なるほど。

遠隔でのコラボレーションを可能とするインフラ整備

短期的にやること3つ目です。

  • 新型コロナウイルスの感染を防止しながら事業を継続するためのツールとして、リモートワークを実現する IT インフラの整備が急速に進んでいる
  • こうした遠隔でのコラボレーションを可能とするインフラは感染防止の観点にとどまらず、今後のイノベーション創出のインフラとなる可能性がある

業務プロセスの再設計

4つ目。

  • 社会や企業においてこれまで当たり前のこととされていた業務プロセスの中には、前例を踏襲しているだけで実は見直しによって効率化可能なものや、過去の検討の結果積み重ねられてきた個別ルールによりかえって非効率となっているものが潜んでいる可能性がある
  • 「人が作業することを前提とした業務プロセス」を、デジタルを前提とし、かつ顧客起点で見直しを行うことにより大幅な生産性向上や新たな価値創造が期待

最後の部分もポイントです。

  • 業務プロセスの見直しを一度実施したとしても、そこで見直しの活動を停止してしまえば業務プロセスがレガシー化してしまう
  • 業務プロセスが顧客への価値創出に寄与しているか否かという視点をもち、恒常的な見直しが求められる

いいこと言いますよね。

中長期的な対応

以上が短期的な対応で、中長期的な対応についてです。まずはppt形式のサマリーを転載させていただきます。これを見れば概ねわかると思います。

スクリーンショット 2021-01-13 21.41.04.jpg

スクリーンショット 2021-01-13 21.41.28.jpg

スクリーンショット 2021-01-13 21.41.42.jpg

スクリーンショット 2021-01-13 21.41.48.jpg

スクリーンショット 2021-01-13 21.41.55.jpg

長期的に実施することについてもポイントを抜粋していきます。

デジタルプラットフォームの形成

中長期的にやらなければならないこととして、まずデジタルプラットフォームの形成があると言っています。

  • 企業は、協調領域については自前主義を排し、経営トップのリーダーシップの下、業務プロセスの標準化を進めることで SaaS、パッケージソフトウェアを活用し、貴重な IT 投資の予算や従事する人材の投入を抑制すべき
  • IT 投資の効果を高めるために、業界内の他社と協調領域を形成して共通プラットフォーム化することも検討すべき
  • 共通プラットフォームは、特定業界における協調領域をプラットフォーム化した業界プラットフォームや、特定の地域における社会課題の解決のための地域プラットフォーム等が想定
  • こうした共通プラットフォームによって生み出される個社を超えたつながりは、社会課題の迅速な解決と、新たな価値の提供を可能とするため、デジタル社会の重要な基盤となる

変化対応力の高い IT システムを構築するために

中長期的にやらなければならないことの2つ目は、変化対応力の高いIT システムを構築するということのようです。

  • デジタル時代の特徴として、顧客や社会との接点(Engagement)を通して顧客や社会の課題を発見し、解決することで新たな価値提案を行うためのシステム、すなわち、SoE(Systems of Engagement)の領域が広がっている
  • スモールスタートで迅速に仮説としての製品・サービスを市場に提示し、データドリブンで仮説の検証を実施するとともに、その結果を用いて製品・サービスの改善へとつなげる、というサイクルを繰り返すことで、より良い価値提案が可能となる
  • SoE の領域において、大規模ソフトウェアを外部に開発委託することは、これまでの受発注形態では対応が困難
  • 大規模なソフトウェア開発を一括発注し長期間をかけて開発するのではなく、アジャイルな開発体制を社内に構築し、市場の変化をとらえながら小規模な開発を繰り返すべき

ベンダー企業の事業変革とユーザー企業とベンダー企業との新たな関係

中長期の3つ目です。

  • 今後、大規模な受託開発は減少していく
  • 今後、ユーザー企業において DX が進展すると、受託開発の開発規模や案件数が減少するとともに、アジャイル開発による内製が主流になる
  • しかし、内製化する過程で必要となるアジャイル開発の考え方や、クラウドネイティブな開発技術等について、ユーザー企業の内部人材ではすぐに対応できないことが多いため、ベンダー企業が内製開発へ移行するための支援や、伴走しながらスキル移転することに対するニーズが高まる
  • ベンダー企業はこうした事業機会を顧客企業への客先常駐ビジネスとするのではなく、対等なパートナーシップを体現できる拠点において、ユーザー企業とアジャイルの考え方を共有_しながらチームの能力を育て(共育)、内製開発を協力して実践する(共創)べき

ジョブ型人事制度の拡大とDX 人材の確保

中長期4つ目。

  • DX を推進するために必要となる人材については(外部のベンダー企業に任せるのではなく)企業が自ら確保するべき
  • DX の推進においては、企業が市場に対して提案する価値を現実のシステムへと落とし込む技術者の役割が極めて重要
  • 副業・兼業を行いやすくし、人材流動や、社員が多様な価値観と触れる環境を整えることも重要

以上までが4章です。5章の政府の取り組みについてです。

政府の政策の方向性

5章の政府の取り組みについてです。非常に多くの良い取り組みをしてくれているんだと感じました。抜粋はしませんが、ぜひオリジナルでご一読いただくといいと思いました。

以下政府の実施内容です

共通理解形成のためのポイント集の策定,CIO/CDXO の役割再定義,DX 成功パターンの策定,DX 推進状況の把握,デジタルプラットフォームの形成,産業変革の制度的支援,ユーザー企業とベンダー企業の共創の推進,デジタル技術を活用するビジネスモデル変革の支援,研究開発に対する支援,DX 人材確保のためのリスキル・流動化環境の整備ということです。

結構いいことやってるんだなと思いました

最後に

DXレポート自身は6章目もあり、そこでは2018年のDXレポートでの指摘とその後の政策展開を振り返っていますが、今回はDXレポート2の振り返りなのでこちらは割愛させていただきます。最後に1パラグラムだけ6章から抜粋し、終わりたいと思います。

企業の行動変容が進まない理由は、生活習慣病のアナロジーで理解が可能である。誰しも、一般論としてメタボリックシンドロームの状態よりも痩せていたほうが良いことは理解している上、生活習慣病のリスクについても理解しているが、自分自身は健康だと信じている。企業の DX についても同様で、DX が必要だと理解はしていながらも、行動を変容できていない企業は多い

最後はメタボに掛けてわかりやすく説明いただきました。


この記事が役に立ったと思ったらLGTMお願いいたします:thumbsup:

個人的にはこの流れだと、Python、JavaScript、クラウドがいま以上に熱くなると思いました。

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

JavaScriptにおけるvar, letはスコープの仕組みが違う

JavaScriptにおけるvar, letはスコープの仕組みが違う

JavaScriptにおいて使われているvar, let, constの違いについて気になって調べてみたのでメモとしてここに投稿します。

const

constは定数である。一度代入した値を変えることがないときに使う。

varとletで宣言された変数の違い。

これら二つの違いはスコープの仕組み

var

varにより宣言された変数は関数スコープを持つ。そのため関数内であればとこからでもその変数にアクセスできる。

let

letにより宣言された変数は、ブロックスコープを持つ。そのためアクセスできる範囲が宣言されたブロック内に制限されている。

let.js
if () {
  let a = 'abc';
}
console.log(a); // 宣言されたブロックの外なのでエラーとなる。

letを使うことのメリット

ブロックスコープを持ちアクセスできる範囲が制限されているため、宣言されたブロック内の外側で使用されてバグにつながるということがなくなる。


参考にしたサイト
JavaScriptの変数宣言はletにすべきか 『入門JavaScriptプログラミング』から解説

この記事の内容に間違いなどありましたらご指摘していただけると幸いです。

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

lodashのtoNumberについて

lodashtoNumberの仕様がよく分からなくなったので、いろいろ実験してみました。
結論から言うと、undefinedの時だけ注意が必要そうです。

引数 引数(コード) 結果
文字の'0' '0' 0
空文字 '' 0
半角スペース ' ' 0
全角スペース ' ' 0
null null 0
undefined undefined NaN
数字ではない文字 'hoge' NaN

おまけ

以下で確認しました。

console.log('toNumber');
let vari = '0';
console.log(vari + '->' + _.toNumber(vari));
vari = '';
console.log(vari + '->' + _.toNumber(vari));
vari = ' ';
console.log(vari + '->' + _.toNumber(vari));
vari = ' ';
console.log(vari + '->' + _.toNumber(vari));
vari = null;
console.log(vari + '->' + _.toNumber(vari));
vari = undefined;
console.log(vari + '->' + _.toNumber(vari));
vari = 'hoge';
console.log(vari + '->' + _.toNumber(vari));
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

"Hello World"でみるプログラミング言語

はじめに

プログラミングを学ぶものなら誰しも通る道、Hello world。今回はそんなHello worldを出力するためのプログラムで、5つのプログラミング言語を比較していきます。皆さんが使っていなそうな言語を選びました。が、あくまでも独断と偏見によって決定されています。

1.C

いわずと知れた言語ですね。

#include <stdio.h>
int main(void) {
    printf("Hello world");
}

2.Python

簡潔に書けることが利点ですね。

print("Hello world")

3.Ruby

日本生まれの言語ですね。

puts "Hello world"

4.FORTRAN

最初の高水準言語として有名ですね。まだ息してたのか...

program hello
  print *, 'Hello world'
end program hello

5. PHP

HTML/CSSと一緒に使われたりしますね。

<?php
  echo 'Hello world';
?>

終わりに

以上です。いかがでしたでしょうか?Rubyはやっぱり短かったですね。それが長所ですよね。PHPechoてエコーって読むんですね。初めて知りました。若輩者ゆえ、もしかしたら間違いがあるかもしれないので、その時はコメントなんかで教えてください。

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

自前のCognitoをAmplifyでJavaScriptアプリに組み込む

はじめに

この記事は、

  • 既に作成済みのCognitoをJavaScriptアプリに組み込みたいがどうすればいいかわからない人
  • アプリとCognitoはもう作ってしまったのでなんとかドッキングさせたい人
  • AmplifyCLIで簡単にCognitoの認証機能が作成できて喜んだのも束の間、使用できない機能が多いので自前のCognitoを使いたくなったがAmplifyのライブラリを使用してアプリに組み込めるのかわからず調査中の人

などに向けて、アプリ開発者目線で書かれたものです。
Cognitoの作成・設定方法やAmplifyCLIの使い方は取り扱いませんのでご注意ください。

「AmplifyとCognitoすごく便利だと聞いたことあるけどまだ試したことない」という人や、「自前のアプリにCognitoでサクッと認証機能を実装したい」という人はAmplify公式ドキュメントのチュートリアルをまずは見てみることをオススメします。
驚くほど簡単に認証機能付きアプリが出来上がる感動体験ができますよ?

概要

結論から言うと、自前のCognitoをAmplifyのライブラリを使ってJavaScriptアプリに組み込むことは可能です。
aws-amplifyライブラリのconfigureメソッドを使用して設定を上書きするとよいです。

import Amplify from 'aws-amplify'
Amplify.configure()

以降は、私が実際に試したVue.js製のアプリケーションへの組み込みを例に手順を紹介したいと思います。

環境

  • node.js 14.15.3
  • vue 2.6.11
  • aws-amplify 3.3.7
  • aws-amplify-vue 2.1.3

手順

Amplifyのライブラリをインストールする

コマンドプロンプト等で下記コマンドを実行

npm install aws-amplify aws-amplify-vue --save

Amplify設定用ファイルを作成する

plugins配下にamplify.jsファイルを作成し、設定を上書きする記述を書く

amplify.js
import Vue from 'vue'
import Amplify, * as AmplifyModules from 'aws-amplify'
import { AmplifyPlugin } from 'aws-amplify-vue'

Amplify.configure({
  Auth: {
    identityPoolId: '1.CognitoIDプールの識別ID',
    region: 'Cognitoの作成リージョン', 
    userPoolId: '2.Cognitoユーザプールの識別ID', 
    userPoolWebClientId: '3.CognitoのアプリクライアントID', 
    oauth: {
      domain: '4.Cognitoユーザプールで設定されているドメイン',
      // 5.Cognitoユーザプールで許可されているスコープ
      scope: ['openid', 'aws.cognito.signin.user.admin'],
      redirectSignIn: '6.サインイン後に遷移するURL',
      redirectSignOut: '7.サインアウト後に遷移するURL',
      responseType: 'code' // とりあえずcodeにしておくとリフレッシュトークンが生成される様子
    }
  }
})

Vue.use(Amplify)
Vue.use(AmplifyPlugin, AmplifyModules)

AWSコンソールにログインし、使用するCognitoの設定を写していけばOKです。
どこに記載があるのか見つけにくいものもあるので、順に記載個所を紹介します。

1. CognitoIDプールの識別ID

スクリーンショット 2021-01-13 173650.jpg
1番見つけにくいかもしれません。
Cognitoのトップページまたはユーザプール表示時に画面左上に出ている「フェデレーティッドアイデンティティ」からIDプールの画面に遷移し、使用するIDプールを選択すると画像左側にあるサイドメニューが現れます。「サンプルコード」を選択し表示されたサンプル内にIDがあります。
(画像マスクが中途半端ですが、ap-northeast-1:xxxxx..とリージョン名から設定してください)

2. Cognitoユーザプールの識別ID

スクリーンショット 2021-01-13 172833.jpg
地味に見つけづらいです。
Cognitoのトップページからユーザプールに遷移し、サイドメニュー1番上「全般設定」の1番上にあります。こちらもリージョン名の部分から設定してください。

3. CognitoのWebClientID

スクリーンショット 2021-01-13 173321.png
ユーザプール「全般設定>アプリクライアント」で発行したものです。

4. Cognitoユーザプールで設定されているドメイン

スクリーンショット 2021-01-13 174550.jpg
ユーザプールの「アプリの統合>ドメイン名」で自分で設定したものです。
ドメインなので、https://は除いて.comまでを設定します。

5. Cognitoユーザプールで許可されているスコープ

6. サインイン後に遷移するURL

7. サインアウト後に遷移するURL

スクリーンショット 2021-01-13 174751.jpg
ユーザプールの「アプリの統合>アプリクライアントの設定」から写します。
コールバックURL・サインアウトURLはCognitoの設定とAmplifyの設定が異なると認証時にエラーが出るのでご注意ください。

Amplify設定用ファイルを読み込む

作成したamplify.jsをmain.jsで読み込ませる

main.js
import './plugins/amplify'

設定は完了です!
あとはAmplifyのドキュメントを参考に、ログイン・ログアウト始め好きなメソッドを使用することができます。

認証を使用したいコンポーネントでAmplifyライブラリを読み込み使用する

下記の例ではAzureADを使用してシングルサインオンをさせていますが、シンプルにユーザ名とパスワードを使用したAuth.signInメソッド等でも使用できると思います。

Login.vue
<template>
  <div>
    <button type="button" @click="login">
      サインイン
    </button>
  </div>
</template>

<script>
import { Auth } from 'aws-amplify'

export default {
  name: 'Login',
  methods: {
    async login () {
      Auth.federatedSignIn({ provider: 'AzureADProvider' })
        .then(value => {
        })
        .catch(e => {
          console.log(e)
        })
    }
  }
}
</script>

参考リンク

https://docs.amplify.aws/lib/auth/start/q/platform/js#re-use-existing-authentication-resource
https://aws.amazon.com/jp/about-aws/whats-new/2020/10/use-existing-cognito-user-pools-identity-pools-for-amplify-project/
https://day-journal.com/memo/try-024/

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

Next.jsをSSRでFirebaseデプロイ

はじめに

next.jsをSSR(サーバーサイドレンダリング)でデプロイをしようと思い、
思いのほか、手間取ったので、ここでアウトプットしておきまーす!

next.jsをSSG(static generation)静的ビルドする場合はnext exportでoutディレクトリで良かったのですが、

SSRする場合は、cloud functionsをつかって、サイトアクセス時にHTMLを生成しないといけないようです!

デプロイまで

まずいつも通り

firebaseのコンソールでプロジェクトを作成しておいてください!

npx create-next-app your app
cd your app
//firebaseをグローバルインストールしていない方は
npm install -g firebase-tools
firebase login
firebase init 
//ここではhostingとfunctionsのみ選択そして既存のプロジェクトIDを選択

buildディレクトリ設定

module.exports = {
  distDir: "./.next",
};

今回はserver-side-renderingなのでbuildファイルは、.nextディレクトリになります!

firebase周りの設定

.firebaserc
{
  "projects": {
    "default": "your-projectId"
  }
}
firebase.json
{
  "hosting": {
    "public": "public",
    "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
    "rewrites": [
      {
        "source": "**",
        "function": "nextjsFunc"
      }
    ]
  },
  "functions": {
    "source": "./functions/",
    "predeploy": [
      "npm --prefix \"$PROJECT_DIR\" install",
      "npm --prefix \"$PROJECT_DIR\" run build"
    ],
    "runtime": "nodejs10"
  }
}

functionsのパスを設定
predeployでデプロイ前installとbuildをデフォルトで行うようにする

cloud functions関数作成

functions/index.js
const { https } = require("firebase-functions");
const { default: next } = require("next");

const isDev = process.env.NODE_ENV !== "production";
const nextjsDistDir = require("./next.config.js").distDir;

const nextjsServer = next({
  dev: isDev,
  conf: {
    distDir: nextjsDistDir,
  },
});
const nextjsHandle = nextjsServer.getRequestHandler();

exports.nextjsFunc = https.onRequest((req, res) => {
  return nextjsServer.prepare().then(() => nextjsHandle(req, res));
});

これで

firebase deploy --only functions,hosting

サーバーサイドレンダリングのデプロイが完了

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

[Oracle Cloud] Oracle Content and ExperienceのフォルダUIを、他のアプリケーションに埋め込み表示する方法

はじめに

Oracle Content and Experience (以降OCE)は、APIファーストなアーキテクチャで、マルチチャネルでのコンテンツ配信を実現するインテリジェントなコンテンツ管理プラットフォームです。

この記事では、OCEのフォルダに保管される資料一式を、他のWebアプリケーションに埋め込み表示する方法について紹介します。

この記事は、2020年12月時点での最新バージョン(20.4.3)を元に作成されてます

1. 事前準備

1.1 利用環境

準備する環境は以下の通りです

  • Webブラウザ
  • Webサーバ(今回はMacOSでApacheを起動し、ローカルのWebサーバとして利用)
  • OCEインスタンス

1.2 Webサーバのセットアップと起動

今回はMacOSのApacheを利用しますが、Webブラウザでhttp://<ホスト名orIPアドレス>/xxx.htmlなどのURLが表示できれば、なんでもOKです。
私は、以下の記事を参考に、MacOSにApacheをローカルのWebサーバとしてセットアップ&起動しました

1.3 OCEインスタンスの作成

Oracle Cloud Infrastructure(OCI)の管理コンソールを利用し、OCEインスタンスを作成します。以下のチュートリアルを参考に作成します

なお、OCEインスタンスは、Oracle Cloudの$300無料クレジット付きの30日間無料トライアル環境で作成できます。もしOracle Cloudの環境をお持ちでない場合は、これを機に取得してみてはいかがでしょう?

1.4 埋め込み表示用フォルダの作成

OCEインスタンスにサインインし、埋め込み表示で利用するフォルダを作成します。また、作成したフォルダのID(GUID)をメモします
フォルダのIDは、フォルダのUIを埋め込み表示の設定で利用します

  1. OCEインスタンスにサインインします。URLの形式は以下の通りです
    https://myocedev-mytenancy.cec.ocp.oraclecloud.com/documents/home

  2. 左ナビゲーションメニューのドキュメントをクリックします

  3. 画面右上部の作成をクリックします
    emb001.jpg

  4. 「名前」埋め込み用フォルダ と入力し、作成 をクリックします
    emb010.jpg

  5. 前の手順で作成した埋め込み用フォルダを開き、いくつかのファイルをアップロードします

  6. フォルダを表示しているURLから、フォルダのGUIDをメモします。フォルダのGUIDは、https://myocedev-mytenancy.cec.ocp.oraclecloud.com/documents/folder/Fxxxxxxxxx/_埋め込み用フォルダ/updatedFxxxxxxxxxです(スクリーンショットの赤線部分です)
    emb002.png

1.5 OCEインスタンスの埋込み表示設定を有効化する

OCEの初期設定では、埋め込み表示は「無効化」に設定されてます。ここでは、システム設定より埋め込み表示を有効化します。さらに、埋込み表示を許可するドメインに「localhost」を追加します

  1. 左ナビゲーションメニューの ADMINISTRATION:システム をクリックします

  2. 「システム」の右隣のプルダウンメニューより、セキュリティを選択します

  3. 「他のドメイン内のOracle Content and Experienceの埋込みコンテンツを表示できます:」許可を選択します

  4. 「許可されるドメイン:」に、localhostを追加します
    emb003.jpg
    Webサーバを稼働させる環境にあわせて、ドメインを登録してください

  5. 保存をクリックします

2. OCEのフォルダを埋め込み表示するHTMLファイルを作成する

ローカルサーバ(今回の場合はApache)で表示するHTMLファイルを作成し、Apacheのドキュメントルートに配置します

2.1 説明

OCEは、iframe要素の作成を簡素化し、フレームで実行されているコードとの通信を管理するためのJavaScript API(Embed UI API V2)を提供します。詳細は以下のドキュメントをご確認ください

今回は、指定したフォルダを埋め込み表示する documentsView コンポーネントを利用します。その他にも以下のコンポーネントが提供されています。

コンポーネント 概要説明
assetView 指定したリポジトリのアセット一覧を埋め込み表示
assetViewer 指定したアセットを埋め込み表示
contentItemEditor 指定したコンテンツ・アイテムの編集UIを埋め込み表示
documentsView 指定したフォルダを埋め込み表示
documentViewer 指定したドキュメントを埋め込み表示
conversationsList 指定した会話一覧を埋め込み表示
conversationView 指定した会話を埋め込み表示

それぞれのコンポーネントについては、以下リファレンスをご確認ください

2.2 HTMLファイルの作成

Webサーバに配置するHTMLファイルを作成します。エディタを起動し、以下のコードをコピー&ペーストします

test.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>OCE Embed UI</title>
  <script type="text/javascript" src="https://static.oracle.com/cdn/cec/api/oracle-ce-ui-2.1.js"></script>
  <style>
  #oce-frame-1 {
    border: solid 2px black;
    width: 800px;
    height: 600px;
  }
  </style>
</head>
<body>
  <h1>Oracle Content and Experience - Embed Web UI v2 サンプル</h1>
  <p>この下にOCEのフォルダUIを表示します</p>

</body>

<script type="text/javascript">
  OracleCEUI.oceUrl = "https://<OCEInstance>-<CloudAccount>.cec.ocp.oraclecloud.com"

  var options = {
      documentsView: {
        id: '<埋め込み表示用フォルダのGUID>'
      }
    }

  var fe = OracleCEUI.documentsView.createFrame(options);
  document.body.appendChild(fe);
</script>
</html>

2.3 HTMLファイルの説明と編集

OCEの埋込み表示用APIは、シンプルなJavaScriptファイルoracle-ce-ui-2.1.jsを介してアクセスします。JavaScriptファイルは、<script>タグを利用し、オラクルのコンテンツ配信ネットワーク(CDN)より取得します

  <script type="text/javascript" src="https://static.oracle.com/cdn/cec/api/oracle-ce-ui-2.1.js"></script>

次に、OracleCEUI.oceUrl="https://..."で指定するOCEインスタンスのURLを、利用中のものに書き換えます。
(以下書き換え例)

  OracleCEUI.oceUrl = "https://myocedev-mytenancy.cec.ocp.oraclecloud.com"

続けて、id: 'xxxx'で指定するフォルダのGUIDを、埋め込み表示用に作成したフォルダのGUIDに書き換えます
(以下書き換え例)

  var options = {
      documentsView: {
        id: 'F626526AA0D6EF29B5696AB739306AAC50E02C94D3C8'
      }
    }

ファイル名をtest.htmlとして保存します。

Webサーバのドキュメントルートにtest.htmlを配置します。MacOSの場合は、/Library/WebServer/Documents/です
emb004.jpg

3. 動作確認

  1. Webブラウザで http://localhost/test.html を開きます

  2. (OCEにサインインしていないWebブラウザを利用した場合は)ポップアップウィンドウが開き、OCEへのサインイン画面が表示されます。OCEにサインインします
    ポップアップ・ウィンドウがブロックされている場合は、一時的に許可してください

  3. OCEへのサインインが完了すると、OCEのフォルダが埋め込み表示されたtest.htmlが表示されます
    emb005.png

4. 埋め込み表示オプションを構成する

前の手順で作成した埋め込み表示フォルダは、新規ファイルのアップロード、フォルダ内のファイルの表示やダウンロードができません。これらの操作を可能にするには、options オブジェクトを構成します

4.1 ファイルのプレビュー、ダウンロード、新規ファイルのアップロードを有効にする

エディタでtest.htmlを開き、optionsオブジェクトを以下の通りに変更し、保存します

  var options = {
      documentsView: {
          "header": {
              "upload": true    //アップロードメニューを有効化
          },
          "actions": {
              "open": {
                  "file": true  //ファイルの表示を有効化
              },
              "download": true  //ファイルのダウンロードを有効化
          },
          id: 'F626526AA0D6EF29B5696AB739306AAC50E02C94D3C8'
      }
    }

Webブラウザでhttp://localhost/test.htmlを表示します
emb006.png

  • 右上に「アップロード」メニューが表示されます。また、ドラッグ&ドロップでのファイルアップロードができるようになります
  • ファイルを選択すると「表示」「ダウンロード」メニューが表示されます。また、ファイルの右クリックメニューにも「表示」「ダウンロード」が表示されます。

4.2 UIコンフィギュレータの紹介

UIコンフィギュレータは、埋め込み表示オプションの定義を支援する機能です。UIコンフィギュレータは、開発者→埋込み可能なUI→UIコンフィギュレータより利用できます。

OCEの画面上で埋め込み表示オプションを設定し、他のWebアプリケーションに含めるコードをコピーして貼り付けることができます。

スクリーンショットの右パネル「Options」で有効化するメニューを設定し、「Source」でコードを取得します
emb008.jpg
emb009.jpg

UIコンフィギュレータの使い方は、こちらのマニュアルを参考にしてください

以上でこのチュートリアルは終了です。ありがとうございました

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

カスタムデータ属性について

カスタムデータ属性とは?

HTML5で新しく導入された、html要素に、カスタムデータと呼ばれる、独自の属性を指定する属性。
オリジナルの属性も作成できる。
カスタムデータ属性の名前は常にdataから始まる。
各種スクリプトで動的に使用可能であり、主にJavaScriptやjQueryで値を取得するときに使用されることが多い。

<div class="tweet" data-genre="movie">
属性名:data-id
属性値:tweet.id

本来的に、HTMLのclass属性の目的は、データを格納するためではなく、開発時にCSS等のスタイル情報を割り当てるため。
しかし、要素への情報が増えるたびに、その都度新しいクラスを追加する必要が出てきてしまう。
そうなると、JavaScriptで実際に必要な情報を取り出すことが難しくなる。
そのような背景のもと、HTML5で新たに導入された。

カスタムデータ属性のルール

HTMLでカスタムデータ属性を設定するルールは次のようなものがあります。

  • data以降に属性名を指定する
  • 属性名に使用できるのは、文字、数字、-(ハイフン)、.(ドット)、_(アンダースコア)のみで、大文字は使用できない
  • 属性値は数字も文字列も使用できるが、慣例的に小文字の使用が大半
  • class名を属性名として格納することはできない(データ属性を扱うのは、他に適切なHTML要素や属性がない場合だけに限るべきだから)

JavaScriptでの使い方

データ属性の取得

<div id="tweet" data-genre="movie"></div>
<script>
    var result = document.getElementById("tweet");
    var dataset = result.dataset;
    console.log(result);
</script>

実行結果

movie

データ属性の変更

<div id="tweet" data-genre="movie"></div>
<script>
    var result = document.getElementById("tweet");
    result.dataset.genre = 'movie';
    console.log(result);
</script>

実行結果

movie

jQueryでの使い方

データ属性の取得

対象要素.data( 属性名 )のように、引数へdata属性名を指定する。

<div class="tweet" data-genre="movie"></div>
<script>
    const result = $(".tweet").data('genre');
    console.log(result);
</script>

実行結果

movie

データ属性の変更

<div class="tweet" data-genre="movie"></div>
<script>
    const result = $(".tweet").data('genre', 'movie');
    console.log(result);
    const result = $(".tweet").data('genre', 'music');
    console.log(result);
</script>

実行結果

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

innerHTML

innerHTML について

innerHTMLを使用すると、HTML要素の取得や書き換えを行うことができます。
例えば、以下のように指定した要素の文字列を取得・操作することができます。

html
<div class="contents" id="apple">りんご</div>
javascript
const apple = document.getElementById("apple")

console.log(apple)
// => <div class="contents" id="apple">りんご</div>

console.log(apple.innerHTML)
// => りんご

apple.innerHTML = "青リンゴ"
console.log(apple.innerHTML)
// => 青リンゴ

apple.innerHTML = "青リンゴ"の意味は
apple.innerHTML へ 青リンゴ を定義するというようなもの。これにより
console.log(apple.innerHTML)を実行すると青リンゴが出力される

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

ReactでonClick={() =>がわからない

Reactでこういうコードがありました。

<Chip
 label={"ラベル"}
 onClick={() => handleClick("ラベル")}
/>

それで、これが解せなかった。

onClick={() =>

結論、今の僕の認識では、ユーザーがブラウザでChipをクリックすると、

() => handleClick("ラベル")

これで定義したhandleClickが実行されるという認識において、

MDNのアロー関数に書いてある仕様とは違うのかな?と思いました。

アロー関数は学びました

【JavaScript】アロー関数式を学ぶついでにthisも復習する話

このqiitaで書かれれる、

3行でまとめ
・JavaScriptのES6でアロー関数式という、今までとは違った関数の書き方が追加された
・アロー関数式は既存の関数式より文字数が短くなるだけではなく、宣言時のthisを束縛して不変のものにするという効果を持っている
・ほとんどの場面ではアロー関数式を使うほうがわかりやすくなるが、thisを束縛されて困る場面もあるので要注意!

というところも納得しました。

これを読んだ後でも、所謂アロー関数式の説明では、 onClick={() => handleClick("ラベル")}の動きはよく理解できなかった。

自分で試して、

var arguments = [1, 2, 3];
var arr = () => arguments[0];

 // 1
console.log( arr() )

だとコンソールに1が出てくるけど、

var arguments = [1, 2, 3];

 // () => arguments[0]
console.log( () => arguments[0] )

これだとコンソールに() => arguments[0]が出てくるということを見ても、

ブラウザからのクリック動作がまさに、onClick={() => handleClick("ラベル")}で言うところの、関数実行()になるものと判断しました。

そして、これはこういうもので、アロー関数のイロハとは違うと認識しています。

間違ってたら詳しく教えてください٩( 'ω' )و

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

無駄に柔軟性がある FizzBuzz を作ってみた

多くのプログラマが通る有名な問題 FizzBuzz を無駄に極限まで柔軟性がある実装にしてみた
※ 最短部門ならちらほら見かけるが最柔軟部門はあんまり見かけないので

  • 方針
    • Java11
    • 標準ライブラリを積極的に使用
    • 最終的に Builder Pattern っぽいものになる

一般的な解

色々工夫すればもっと短くなるが教科書に載るような解答ならこんな感じでしょうか?

public class FizzBuzz {
    public static void main(String[] args) {
        for (int i = 1; i <= 100; i++) {
            if (i % 15 == 0) {
                System.out.println("FizzBuzz");
            } else if (i % 3 == 0) {
                System.out.println("Fizz");
            } else if (i % 5 == 0) {
                System.out.println("Buzz");
            } else {
                System.out.println(i);
            }
        }
    }
}

output (見やすくするように一行にまとめています)

1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz ...

魔改造開始

「3: Fizz」「5: Buzz」といったペアを外部化

  • Map 形式で保存
  • Stream で各数字の判定&文字列を結合を行う
Map<Integer, String> map = new HashMap<>();
map.put(3, "Fizz");
map.put(5, "Buzz");

for (int i = 1; i <= 100; i++) {
    final int current = i; // このあと消えます
    String result = map.entrySet().stream()
            .filter(v -> current % v.getKey() == 0)
            .map(Entry::getValue)
            .reduce("", (left, right) -> left + right);
    System.out.println(result.isBlank() ? i : result);
}

別メソッドに分離

  • 結果をリストとして返すメソッドに分離
  • ついでに開始と終了値もカスタマイズ可能に
public static List<String> fizzBuzz(int start, int end, Map<Integer, String> map) {
    return IntStream.rangeClosed(start, end).mapToObj(i -> {
        String result = map.entrySet().stream()
                .filter(v -> i % v.getKey() == 0)
                .map(Entry::getValue)
                .reduce("", (left, right) -> left + right);
        return result.isBlank() ? String.valueOf(i) : result;
    }).collect(Collectors.toList());
}

public static void main(String[] args) {
    Map<Integer, String> map = new HashMap<>();
    map.put(3, "Fizz");
    map.put(5, "Buzz");

    fizzBuzz(1, 100, map).forEach(System.out::println);
}

Builder pattern っぽいものにする

  • 一般的に Builder pattern における build メソッドはある独立したクラスを返すべきだが、ここでは List<String> を返すとする
    • ちゃんとやるなら多分「FizzBuzzBuilder」と「FizzBuzz」の 2 クラスに分けることになるかと
  • javadoc は割愛
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.*;

public class FizzBuzz {
    private Map<Integer, String> map = new HashMap<>();
    private int start = 1;
    private int end = 100;

    public List<String> build() {
        return IntStream.rangeClosed(start, end).mapToObj(i -> {
            String result = map.entrySet().stream()
                    .filter(v -> i % v.getKey() == 0)
                    .map(Entry::getValue)
                    .reduce("", (left, right) -> left + right);
            return result.isBlank() ? String.valueOf(i) : result;
        }).collect(Collectors.toList());
    }

    public FizzBuzz addPair(int value, String text) {
        map.put(value, text);
        return this;
    }

    public FizzBuzz start(int start) {
        this.start = start;
        return this;
    }

    public FizzBuzz end(int end) {
        this.end = end;
        return this;
    }

    public static void main(String[] args) {
        new FizzBuzz()
                .start(1)
                .end(20)
                .addPair(3, "Fizz")
                .addPair(5, "Buzz")
                .build()
                .forEach(System.out::println);
    }
}

試運転

  • せっかくなので互いに素の 3, 5, 11, 13 でテスト
  • 最小公倍数は 2145 なので 2130 ~ 2150 あたりを出力する
new FizzBuzz()
        .start(2130)
        .end(2150)
        .addPair(3, "Fizz")
        .addPair(5, "Buzz")
        .addPair(11, "Foo")
        .addPair(13, "Bar")
        .build()
        .forEach(System.out::println);

結果

FizzBuzz
2131
Bar
Fizz
Foo
Buzz
Fizz
2137
2138
Fizz
Buzz
2141
Fizz
2143
2144
FizzBuzzFooBar
2146
2147
Fizz
2149
Buzz

おまけ:JavaScript version

JavaScript のパラダイムをよくわかっていない人が作るとこうなる
本職の方が作るものと乖離しそうなのであくまで参考

(() => {
    class FizzBuzz {
        constructor() {
            this._from = 1;
            this._to = 100;
            this._pair = [];
        }
        build() {
            const convert = i => this._pair
                .filter(divisor => i % divisor.value == 0)
                .reduce((left, right) => left + right.text, "") || i;
            return [...Array(this._to - this._from + 1).keys()]
                .map(i => i + this._from)
                .map(convert);
        }
        addPair(value, text) {
            this._pair.push({value: value, text: text});
            return this;
        }
        from(from) {
            this._from = from;
            return this;
        }
        to(to) {
            this._to = to;
            return this;
        }
    };
    new FizzBuzz()
        .from(2130)
        .to(2150)
        .addPair(3, "Fizz")
        .addPair(5, "Buzz")
        .addPair(11, "Foo")
        .addPair(13, "Bar")
        .build()
        .forEach(result => console.log(result));
})();
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptのthisの挙動を完全に理解した話

はじめに

今回はJavaScriptのthisの挙動について改めてまとめていきたいと思います。

このthisの挙動については多くのエンジニアが頭を悩ませていると思います。

何を隠そう、私もその一人です。

多くの時間を捧げたおかげで、ある程度thisの挙動について理解できた気がするので記事にまとめていきたいと思います。

「完全に理解した」と書いてありますが、自分の勘違いである可能性も十分にあリます。コメント欄で優しく指摘して頂ければ幸いです。

thisの挙動を頑張って理解していきましょう。頑張りましょう。

thisとは

JavaScriptにおけるthisを一言で説明すると、関数の実行コンテキストのオブジェクト自身への参照が格納された暗黙の引数であるということができます。

以下の記事が参考になりました。

JavaScript の this を理解する多分一番分かりやすい説明

PythonやRubyなどの一般の言語においては、メソッドの実行においてその実行コンテキストのオブジェクトがthisselfとして渡されています。

Pythonの場合

Pythonの場合、実行コンテキストのオブジェクトへの参照をselfという引数で明示的に受け取ります。

class UnReact:
    def __init__(self, name):
        self.name = name

    def some_function(self):
        print(self.name)


organization = UnReact('unreact')
print(organization.name)

Pythonではクラスからインスタンスを作成した場合、そのインスタンス自身への参照をクラス内のメソッドの第一引数に受け取ります。

変数名は何でも構いませんが、一般的にはselfという変数名で受け取ります。

organization.nameという形でインスタンスの属性へアクセスした際にunreactという文字列にアクセスできるということは、コンストラクタ関数内の処理self.name = nameのselfがorganaization自身を表しているということを示しています。

このように、Pythonではメソッドの実行においてその実行コンテキストのオブジェクトをselfという形で明示的に受け取ります。

実行コンテキストのオブジェクトがメソッドの第一引数に格納されるという性質を確かめるため、以下のコードを実行してください。

class UnReact:
    def __init__(self, name):
        self.name = name


def some_function(this):
    print(this.name)


organization = UnReact('unreact')
some_function(organization)  # unreact
UnReact.some_function = some_function
organization.some_function()  # unreact

unreact
unreact

some_function(organization)の例では、インスタンスであるorganizationがそのままthisとして使用されています。

また、UnReact.some_function = some_functionの後にorganization.some_function()をした例では、引数にインスタンスを設定していないにも関わらずインスンタンスであるorganaizationsome_functionの第一引数thisに渡されていることが確認されます。

つまり、Pythonのメソッドの実行においては実行コンテキストのオブジェクト、今回の例では.の前のorganization自身がthisとして渡されていることが確認できます。

Pythonはその設計哲学に「Explicit is better than implicit」を掲げているため、他の言語(RubyやJava)では暗黙の引数として渡されるthisをクラスのメソッドの第一引数として明示的に記述することが求められます。

JavaScriptでは、このthisが暗黙の内に渡されているだけです。

このことを頭に入れて、実際の具体例に進んでいきましょう。

thisのパターン4種類

それでは具体的にthisの4つのパターンについて見ていきましょう。

[パターン1] new演算子をつけて呼び出したとき

JavaScriptにおいてクラスの生成に用いられるnew演算子ですが、このnew演算子を用いたときは、new演算子により新規生成されるオブジェクトがthisとして渡されることになります。

というかJavaScriptにおいて、厳密な意味でのクラス定義は存在せず、ただのオブジェクトを生成するシンタックスシュガーです。

とりあえずその話は気にせずに、new演算子が使用されたときのthisの挙動について実際のコードを用いて解説していきます。

const unreact = function () {
  console.log("Hello UnReact, this is", this);
};

const obj = new unreact();

Hello UnReact, this is unreact {}

クラス定義に使用されるnew演算子ですが、実は任意の関数に実行することができます。

new演算子の挙動ですが、これは非常に難解なので以下の記事に目を通すことをおすすめします。

さすがにそろそろ JavaScript の new (あと継承も)について理解したいと思っているあなたに。

new演算子の挙動をざっくり説明すると、先にnew unreactが実行されて、new演算子によりunreactのプロトタイプオブジェクト、この場合はfunctionのプロトタイプのため空のオブジェクト{}がコピーされて新規にオブジェクトが作られます。

その後にnew unreact()()の部分が実行されて、空のオブジェクト{}を実行コンテキストにしてfunctionの中身が実行されることになります。

今回は関数宣言をunreactに代入しているので、thisにはunreact {}が格納されており、new unreact()()により実行されるために結果が出力されています。

つまり、new演算子をつけて呼び出したときには、thisにはその新規生成されるオブジェクトがかくのう されることになります。

もう少し分かりやすく説明するために、以下のようにコードを書き換えました。

const unreact = function (name) {
  console.log("Hello UnReact, this is", this); //Hello UnReact, this is unreact {}
  this.name = name;
  console.log(this.name); //UnReact
  console.log(this); //unreact { name: 'UnReact' }
};

const obj = new unreact("UnReact");

functionの中のコードはnew unreact("UnReact")("UnReact")が実行されて段階で実行されます。

new unreactの部分でfunctionのプロトタイプオブジェクトである{}:空のオブジェクトが新規作成されて渡されることになります。

その空のオブジェクトを実行コンテキストにして、functionの中身が実行されます。つまり、this.name = nameの部分は{}.name = 'UnReact'というコードが実行されることになるため、オブジェクトの中身が{ name: 'UnReact' }になります。

ここまでで、new演算子を用いた際のthisの挙動についての解説は終わりです。

[パターン2] メソッドとして実行したとき

これは最もシンプルなパターンですが、JavaScriptにおいてthisがメソッド内で使用された場合は、そのthisはメソッドが所属するオブジェクトになります。

そもそもJavaScriptにおいてメソッドとはオブジェクトのプロパティに存在する関数のことであり、それ以上でもそれ以下でもありません。

そのメソッド内でthisを使用した際に、そのthisにメソッドが所属するオブジェクト自体が格納されるというのは、他の言語と共通する当然の挙動であるといえるでしょう。

const unreact = {
  name: "UnReact",
  greet: function () {
    console.log("Hello", this);
  },
};
unreact.greet();

unreact.greetの部分で、オブジェクトのプロパティであるfunctionにアクセスして、()を用いることでその関数を実行しています。

メソッド内におけるthisはそのメソッドが所属するオブジェクト、つまりはunreactオブジェクト自体を表すため、上記のような挙動になります。

Hello { name: 'UnReact', greet: [Function: greet] }

[パターン3] 1,2以外の関数

thisの4つもパターンと書いたんですが、このパターン3に至っては2つに場合分けされます。「いやもうそれ4つじゃないやん!」と言いたくなる気持ちも分かりますが、ぐっとこらえて読んで下さい。

というのもES5から追加されたStrictモードにより、グローバル環境へのthisへのアクセスの挙動が分岐するからです。

StrictモードはJavaScriptの古来から続くヤバい挙動を防ぐためにES5から追加されました。そのStrictモードによりグローバル変数thisへのアクセスが禁止されてしまったのです。

冷静に考えて、グローバル変数thisへ簡単にアクセスできてしまう初期の実装が悪いのですけれども、このStrictモードにより余計にthisが分かりにくくなってしまったのは事実だと思います。

ここでは一旦Strictモードが無い場合と有る場合に分けてthisの挙動を確認していきましょう。

1,2以外の関数のthisの挙動 [非Strictモード]

それでは最初に非Strictモードにおける挙動をみていきましょう。

こちらの方はJavaScriptの基本設計に沿った挙動をするため、今までの話の延長線上にあります。

というかこの基本設計がヤバかったため、Strictモードが導入されて禁止されることになったんですが。

メソッド内のthisや、new演算子を用いた際のthis以外の関数内で使われるthisは実行コンテキストがグローバルオブジェクトになります。これは処理系によっても異なっていて、Node.jsならglobalオブジェクトであり、ブラウザであればWindowオブジェクトになります。

thisは実行環境のオブジェクトであるという基本原則が守られており、分かりやすいですよね。

以下のコードで具体例をみていきましょう。

const unreact = function (name) {
  this.name = name;
};
unreact("UnReact");
console.log(name);

UnReact

この仕様は設計方針的には正しいですが、こんなに簡単に汚染が起きてしまうのは良くないですよね。

ちなみに以下のようにすればグローバル環境の汚染が起きてしまったことが確認できます。

const unreact = function (name) {
  this.name = name;
};
unreact("UnReact");
console.log(name);
console.log(global);

...
setTimeout: [Function],
console: [Getter],
name: 'UnReact' }

このように、メソッドやnew演算子が無い関数の中で呼び出されるthisはグローバルオブジェクトになります。

1,2以外の関数のthisの挙動 [Strictモード]

それでは次にStrictモードにおける挙動を見てみましょう。

Strictモードとは、ES5から追加されたJavaScriptの危険な挙動を回避するためのものでしたね。

Strictモードにおいて、関数定義の中で呼ばれるthisはグローバルオブジェクトではなくundefinedになります。

const unreact = function () {
  console.log(this)
};
unreact();

undefined

この挙動は安全ではありますが、thisは実行コンテキストのオブジェクトであるという基本原則から外れてしまっていますね。

この辺りはStrictモードによる例外としてしっかりと抑えておきましょう。

apply, callによる呼び出し

以下の記事を参考にしました。

JavaScriptの「this」は「4種類」??

callやapplyはthisを呼び出し側から任意のオブジェクトに指定して関数を実行する方法です。

第一引数に渡したオブジェクトをthisに強制的に束縛して、callやapplyを実行した関数を実行します。

言葉で説明しても何がなんだか分からないと思うので、実際にコードで解説していきます。

const unreact = {
  name: "UnReact",
  say: function () {
    console.log(this.name);
  },
};

const react = {
  name: "React",
};

unreact.say.call(react);  //React
unreact.say.apply(react);  //React

React
React

上記の例では、unreactオブジェクトのsayメソッドを、reactオブジェクトを主体として呼び出しています。

callメソッドとapplyメソッドは、第一引数に実行の主体となるオブジェクトを渡し、第2引数にメソッドの引数を渡します。

この例ではunreact.sayメソッドを実行する際に、主体をreactオブジェクトに切り替えて実行しています。

unreactオブジェクトのsayメソッドの中で用いられるthisは、実行コンテキストがreactオブジェクトに切り替えられているので、この場合はreactオブジェクト自身が格納されることになります。

もう少し分かりやすく説明するために、引数にオブジェクトを直接渡してみましょう。

const unreact = {
  say: function (organization) {
    this.organization = organization;
    console.log(this);
  },
};

unreact.say.call({ name: "React" }, "UnReact");
unreact.say.apply({ name: "React" }, ["UnReact"]);

{ name: 'React', organization: 'UnReact' }
{ name: 'React', organization: 'UnReact' }

上記の例では、{ name: "React" }を実行コンテキストとして、unreactオブジェクトのsayメソッドを呼び出しています。

callやapplyの第2引数は、sayメソッドの引数organaizationに代入されます。それによりthis.organization = organization;の部分が{ name: "React" }.organaization = "UnReact"になるため、上記のような結果になります。

callとapplyの違いは、第2引数のとり方です。applyは第2引数に配列をとり、その配列の中身が引数として渡されますが、callは第2引数以降がそのまま引数として渡されます。

thisの問題点を回避しよう

ここまでで、4つのパターンのthisの挙動が理解できたと思います。

それでは次に、このthisの挙動が引き起こす問題とその回避方法についてまとめていきます。

よくあるthisの挙動により起こる問題は、クラスのメソッドの中で新たに関数を定義した際のthisの挙動でしょうか。

クラスのメソッドで関数を定義した場合

結論から書きますと、クラスのメソッドで関数を定義して、その中でthisを使おうとした場合、そのthisはグローバルオブジェクトになります。

Strictモードにおいてはグローバルオブジェクトのthisへのアクセスは禁止されているので、undefinedが返ってくることになります。

下記のコードです。

class Organization {
  constructor(name) {
    this.name = name;
  }
  say() {
    const hello = function () {
      console.log(`Hello ${this.name}`);
    };
    hello();
  }
}

const Company = new Organization("UnReact");
Company.say();

TypeError: Cannot read property 'name' of undefined

上記の例において、sayメソッド内で新たに定義したhelloという関数はただの関数であり、オブジェクトの実行コンテキストにありません。そのため、非Strictモードではthisはグローバルオブジェクトになるのですが、今回はStrictモードで実行しているためthisにundefinedが格納されています。

そのundefinedに対してプロパティアクセスを実行しているため、TypeErrorが発生しています。

この問題を回避するためにいくつかの方法が考えられます。

  • callやapplyを使ってthisを強制的に指定する
  • bindを使って関数にthisを束縛する
  • thisの値を一時変数に避難させる
  • アロー関数式を使用する

各々の方法について解説していきます。

callやapplyを使用してthisを強制的に指定する

callやapplyを使ってthisを指定してみましょう。

以下のコードです。

class Organization {
  constructor(name) {
    this.name = name;
  }
  say() {
    const hello = function () {
      console.log(`Hello ${this.name}`);
    };
    hello.call(this);
  }
}

const Company = new Organization("UnReact");
Company.say();

hello()で実行していた部分をhello.call(this)とすることで、thisを主体としてhello関数を実行しています。

この場合のthisはメソッドの中のthisなので、実行コンテキストがオブジェクト自身、この場合はCompanyインスタンス自身になります。

applyを使用しても同様の挙動になります。

bindを使って関数にthisを束縛する

それでは次にbindを使って関数にthisを束縛する方法をみていきましょう。

class Organization {
  constructor(name) {
    this.name = name;
  }
  say() {
    const hello = function () {
      console.log(`Hello ${this.name}`);
    };
    const bindHello = hello.bind(this);
    bindHello();
  }
}

const Company = new Organization("UnReact");
Company.say();

Hello UnReact

const bindHello = hello.bind(this)を使用することでthisをhello関数に束縛しています。このthisはメソッドの中のthisであるため、実行コンテキストがオブジェクト自身、つまりCompanyインスタンスになっています。

そのため、Company.say()を実行するとthisがCompany自身となり、this.nameにUnReactが格納されています。

thisの値を一時変数に避難させる

次はthisの値を一時変数に避難させる方法です。

メソッド内でのthisは実行コンテキストがオブジェクトになっているため、このthisを一時変数_thisに避難させて、この_thisをメソッド内で定義した関数内で使用します。

class Organization {
  constructor(name) {
    this.name = name;
  }
  say() {
    const _this = this;
    const hello = function () {
      console.log(`Hello ${_this.name}`);
    };
    hello();
  }
}

const Company = new Organization("UnReact");
Company.say();

Hello UnReact

const _this = thisの部分で、実行コンテキストがオブジェクト自身であるthisを一時変数_thisに格納しています。

この_thisをhello関数の中で使用することで、thisの挙動を制御しています。

アロー関数式を使用する

最後はアロー関数式です。

メソッド内で定義する関数のみをアロー関数式で定義する場合と、メソッド自身もアロー関数式で定義する場合があります。

この2つを場合分けして解説します。

メソッド内で定義する関数のみをアロー関数式で定義する

アロー関数式で関数を定義すると、functionで関数を宣言した場合とは異なる挙動をします。

以下のコードです。

class Organization {
  constructor(name) {
    this.name = name;
  }
  say() {
    const hello = () => {
      console.log(`Hello ${this.name}`);
    };
    hello();
  }
}

const Company = new Organization("UnReact");
Company.say();

Hello UnReact

上記のコードでは、hello関数をアロー関数式で定義しています。

アロー関数式はES2015から追加された書き方です。そのため、一般的なオブジェクト指向言語、PythonやRubyなどに慣れ親しんだ開発者向けに配慮されています。

具体的には、アロー関数式を使用した場合は暗黙の引数としてのthisを持ちません。

つまり、上記の例ではhello関数は暗黙の引数thisを持ちません。その代わり、外のスコープのthis、この場合はメソッド内のthisが使用されることになります。

そのため、上記のようにhello関数の中のthisはCompany自体となっています。

基本的には、このアロー関数式を使った方法でthisの問題を回避することになります。

メソッド自身もアロー関数式で定義する場合

最後はメソッド自身もアロー関数式で定義する場合です。

以下のコードになります。

class Organization {
  constructor(name) {
    this.name = name;
  }
  say = () => {
    const hello = () => {
      console.log(`Hello ${this.name}`);
    };
    hello();
  };
}

const Company = new Organization("UnReact");
Company.say();

Hello UnReact

メソッド自身もアロー関数式で定義したパターンです。

本来なら、say関数内のthisは実行コンテキストがグローバルになるので、Strictモードでundefinedになるはずです。

しかし、クラスのインスタンスのメソッドとしてアロー関数式が定義された場合には、例外的にthisの一時変数による移し替えが発生します。

それにより、メソッド自身もアロー関数式で定義した場合も、メソッドをfunctionで定義した場合と同様の挙動をします。

終わりに

今回の記事はここまでになります。

ここまで読んで頂きありがとうございました。

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

【GAS】監査ログの取得(外部ドメインへの転送設定有効化を検知)

やりたいこと

  • Gmailの転送設定で外部ドメインへの転送を有効化したアカウントをSlackに通知させたい。
  • 情報漏洩やうっかり外部ドメインへの転送設定を検知、早期に対処するため。
  • 監査ログのチェックは1日毎で行う。
  • 監査ログのイベント名で「email_forwarding_out_of_domain」に一致したもののみSlackに通知する。

事前準備

Admin SDKのReportsを使うためにはサービスを追加しておく必要がある。

サービス > Admin SDK API の順に選択
バージョンは「reports_v1」、IDに「AdminReports」を入力して追加
image.png

コード

sample.gs
var slackWebhookUrl = 'Slack Webhook URL';

function AuditLog() {

  var now = new Date();

  var oneDayAgo = new Date(now.getTime() - 1 * 24 * 60 * 60 * 1000); 

  var startTime = oneDayAgo.toISOString();

  var endTime = now.toISOString();

  var page;

  page = AdminReports.Activities.list('all', 'user_accounts', {
      startTime: startTime,
      endTime: endTime,
      maxResults: 5,
      pageToken: pageToken
    });//page = AdminReports.Activities.list('all', 'user_accounts', {

  var items = page.items;

  if (items){

    for (var i = 0; i < items.length; i++){

      var item = items[i];

      if (item.events[0].name == "email_forwarding_out_of_domain"){

        var text = "Gmailの転送設定有効化を検知しました。 " + item.id.time + " " + item.actor.email + " " + item.events[0].name + " " + item.events[0].parameters;
        postSlack(text);
      }//if (item.events[0].name == "email_forwarding_out_of_domain"){

    }//for (var i = 0; i < items.length; i++){

  }//if (items){

}//function AuditLog() {

function postSlack(text){
  var data = {
    "text": text
  }//var data = {
  var options = {
    'method' : 'post',
    'contentType': 'application/json',
    'payload' : JSON.stringify(data)
  };//var options = {

  Logger.log(options);
  UrlFetchApp.fetch(slackWebhookUrl, options);
}//function postSlack(text){
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

江頭ゲーム

ランダム関数で江頭を出現するかしないかを選択

偶数だったら江頭出現でそこからいくつか用意した江頭の写真を選択して表示

奇数だったら何もない画面に戻る

function setEgasira() {
    const Body = document.getElementById("www");
    const img_Egasira = document.createElement('div');
    const checkEgasira = Math.floor(Math.random() * 4);
    if (checkEgasira % 2 ===0) {
        if (checkEgasira === 0) {
            img_Egasira.innerHTML = "<img src='./img/egasira-1.jpg' alt='img_Egasira' width='250px' height=150px' />";
        } else if (checkEgasira === 2) {
            img_Egasira.innerHTML = "<img src='./img/egasira-2.jpg' alt='img_Egasira' width='250px' height=150px' />";
        }
        Body.appendChild(img_Egasira);
    } else {
        www.innerHTML=""
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

パスワード変更ロジック

中にあるconsole.logはjsでは出力することが出来ない
reactになれがリアルタイムで反映することできる、、、はず...?

const Form = document.getElementsByTagName("form");
let PasswordValue = "aaaaa";
const nowPassword = document.getElementById("now_password");
nowPassword.textContent = "現在のパスワードは"+ PasswordValue;


function setPassword() {
    const confirmPassword = document.getElementById("confirm_password").value
    const newPassword = document.getElementById("new_password").value
    console.log(confirmPassword)
    console.log(newPassword)
    alert(PasswordValue)
    if (PasswordValue===confirmPassword) {
        if (PasswordValue != newPassword) {
            let PasswordValue = newPassword;
            alert(PasswordValue)
        }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ビンゴシートを作成

マスは二次元配列で作成(過去のオセロゲームを参考)
真ん中はfree
ページ更新するたびに中身を更新
bの列には1~15
iの列には16~31
nの列には31~45
gの列には46~60
oの列には61~75
を入れるようにする

const sheet =document.getElementById("field")

function newSheet() {
    for (var x = 0; x < 6; x++){
        const tr = document.createElement("tr")
        sheet.appendChild(tr)

        for (var y = 0; y < 5; y++){
            var td = document.createElement("td");
            if (x === 0) {
                switch (y){
                case 0:
                    td.textContent ="B"
                    break;
                case 1:
                    td.textContent = "I"
                    break;
                case 2:
                    td.textContent = "N"
                    break;
                case 3:
                    td.textContent = "G"
                    break;
                case 4:
                    td.textContent = "O"
                    break;
                }
            } else {
                switch (y){
                    case 0:
                        td.textContent = (Math.floor(Math.random() * 15 + 1))
                        break;
                    case 1:
                        td.textContent = (Math.floor(Math.random() * 15 + 16))
                        break;
                    case 2:
                        td.textContent = (Math.floor(Math.random() * 15 + 31))
                        if (x === 3) {
                            td.textContent = "free"
                        }
                        break;
                    case 3:
                        td.textContent = (Math.floor(Math.random() * 15 + 46))
                        break;
                    case 4:
                        td.textContent = (Math.floor(Math.random() * 15 + 61))
                        break;
                    }
            }
               tr.appendChild(td)
        }
    }

}

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

JavaScriptでPythonのenumerate関数を実行するには

はじめに

Node.jsを使ったWebアプリの開発にて、forEachでリストを回していたのですが、
急遽インデックスの情報も必要になりました。

そこで次のようなソースコードを書いたものの、「もっといい方法がある」と仲間に指摘されました。
実際に試したところ非常に便利だったことと、Pythonのenumerate関数に近いものを感じたので、
for文でインデックスとバリューを同時に取得する方法として、記事に残します。

ary = ["bar", "foo", "hoge"]

let i = 0 
ary.forEach(function( value ) {
     console.log(i, value );
     i++
});
// 0 bar
// 1 foo
// 2 hoge

実行環境

  • Node.js v12.16.3

検証環境

  • Paiza.io(Python 3.8.2)
  • Paiza.io(Node.js v12.18.3)

そもそもenumerate関数とは

引数に指定したリストの、インデックスとバリューを取得する関数です。

ary = ["bar", "foo", "hoge"]
for i, item in enumerate(ary):
    print(i, item)
#  0 bar
#  1 foo
#  2 hoge

for文でインデックスとバリューを同時に取得する

forEachは、第二引数にインデックスを指定できる

forEach関数において 第一引数はリスト、第二引数はインデックスを取ります。

このインデックスは、ループ回数が増加するごとに自動的にインクリメントされるため、
i++i += 1と記述する必要はありません。

ary = ["bar", "foo", "hoge"]
ary.forEach(function(value, i) {
     console.log(i, value);
});
// 0 bar
// 1 foo
// 2 hoge

for inを使う

for inはリストのインデックスを取得します。
次のようなソースコードにより、インデックスとバリューを取り出します。

ary = ["bar", "foo", "hoge"]

for (i in ary){
    console.log(i, ary[i])
}
// 0 bar
// 1 foo
// 2 hoge

一方で、for inでは次のように予期しない挙動を示す場合もあるので、
利用には注意が必要そうです。

var data = [ 'apple', 'orange', 'banana'];
//配列オブジェクトにhogeメソッドを追加
Array.prototype.hoge = function(){}
  for (var key in data){
   console.log(data[key]);
  }
// apple
// orange
// banana
// function(){}

参考

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

文章を?????な帰国子女っぽくしてくれるクソアプリを作った

文章をかっこよくしたいことってありますよね?
かっこいい文章と言えば、アメリカ帰りの帰国子女やイケてる外資系コンサル社員がニューヨークの風をまとわせて書く、ルー大柴のような英語まじりの文です。
ついでに英文のスタイルも変えて?????な表記にできれば、平凡な文字列も鮮やかに生まれ変わるでしょう。

しかし、単語を翻訳してスタイルの変更を適用するのはなかなか手間です。そこで、一連の変換を自動化する ??????? ??? を作ってみました。

https://notra.herokuapp.com/
CEFA7764-71D4-4809-A6A7-8E78FA0EE2F4.jpeg

概要

おおまかにはこのアプリは以下の流れで動いています。

  1. 入力文を受け取る
  2. 形態素解析を適用
  3. 名詞を Google Cloud の Translation API で翻訳
  4. 英語部分のUnicodeをいろんなパターンに変換して表示

フロントエンド

フレームワーク

WebフレームワークはReactを使いました。その上で、UIフレームワークにAnt Designを使いました。Ant Designはアリババが提供するオープンソースのReact用UIフレームワークで、以下のように書くだけでコンポーネントのUIをいい感じにしてくれます。

import { Tabs, Typography } from "antd";

import { TabContent } from "../../types";

const { Paragraph } = Typography;

const { TabPane } = Tabs;

type StyleTabsProps = {
  options: TabContent[];
  onChange: (key: string) => void;
};

export const StyleTabs = (props: StyleTabsProps) => {
  const tabOptions = props.options.map((op) => (
    <TabPane tab={op.tabName} key={op.key}>
      <Paragraph className="Paragraph" copyable>
        {op.value}
      </Paragraph>
    </TabPane>
  ));
  return <Tabs onChange={props.onChange}>{tabOptions}</Tabs>;
};

Unicode変換

Fancy -> ?????のような変換を行うために、Fancifyというパッケージを使わせていただきました。

以下は実装の一部です。スタイルによっては小文字しかなかったり大文字しかなかったりするため、それに対応してアルファベットの大小を変えています。

const SentenceTransformer = (sentence: str): TabContent[] => {
  const sets = [
    { style: "circled", letter: "both" },
    { style: "negative circled", letter: "upper" },
    { style: "fullwidth", letter: "both" },
    { style: "math bold", letter: "both" },
    { style: "math bold fraktur", letter: "both" },
    { style: "math bold italic", letter: "both" },
    { style: "math bold script", letter: "both" },
    { style: "math double struck", letter: "lower" },
    { style: "math mono", letter: "both" },
    { style: "math sans", letter: "both" },
    { style: "math sans bold", letter: "both" },
    { style: "math sans italic", letter: "both" },
    { style: "math sans bold italic", letter: "both" },
    { style: "parenthesized", letter: "both" },
    { style: "regional indicator", letter: "upper" },
    { style: "squared", letter: "upper" },
    { style: "negative squared", letter: "upper" },
  ] as const;

  const transformedSentences: TabContent[] = sets.map((x, index) => {
    let tabName = "Tab";
    let casedSentence = sentence;
    if (x.letter === "lower") {
      tabName = tabName.toLowerCase();
      casedSentence = sentence.toLowerCase();
    } else if (x.letter === "upper") {
      tabName = tabName.toUpperCase();
      casedSentence = sentence.toUpperCase();
    }
    const key = index + 1;
    return {
      tabName: fancify({ input: tabName + key, set: x.style }),
      value: fancify({
        input: casedSentence,
        set: x.style,
      }),
      key: key,
    };
  });

  return transformedSentences;
};

バックエンド

バックエンドのAPIサーバーにはExpressを使いました。Expressにはexpress-generatorというスケルトン生成ツールがありますが、これが生成してくれるのはJavaScriptのファイル群です。今回はTypeScriptを使いたかったので似たようなものがないか探したところ、express-generator-typescriptというまさに求めていたものがあったので使わせていただきました。こちらは本家よりもさらにいろいろ生成してくれます(今回はクソアプリを作るだけなのでやや過剰感もありましたが……)。

形態素解析

形態素解析はkuromoji.jsのラッパーであるkuromojinを使わせていただきました。
TinySegmenterは分かち書きソフトウェアであり形態素解析による品詞推定はできないので、名詞を見つける必要がある今回の用途には適しませんでした)。

以下は形態素解析で名詞を取り出す部分のスニペットです。

tokenize(text).then((tokenized) => {
  const nouns = tokenized
    .filter((token) => token.pos === "名詞")
    .map((x) => x.surface_form);
}

翻訳

名詞だけ変換できればいいので和英辞書を使うことも考えたのですが、あまりいい辞書がネット上に見つからなかったことと、辞書にない語の扱いをローマ字変換するだけよりは機械に無理やり変換させたほうがおもしろそうだな、と思い機械翻訳を使いました。

最初は月に100万文字まで無料のWatson Language Translatorを使おうかと考えていたのですが、機械翻訳APIの選定時はまだバックエンドを用意せずフロントエンドだけで頑張ろうとしていた(最終的に形態素解析の辞書の重さがネックになりバックエンドを用意することにしました)ため、CORSに対応していないWatson Language Translatorは使えずGoogle CloudのTranslation APIを使うことにしました。こちらは月50万文字まで無料ですが、まあたぶん大丈夫でしょう。

インフラやリポジトリ構成

URLを見ればわかりますがデプロイ先にはherokuを使いました。どうせなら使ったことのないサービスを使ってみたかったのですが、サービスをDockerコンテナ化していて、コンテナのデプロイを無料でさっとできそうなのがherokuくらいだったので今回もお世話になりました。

プロジェクト管理はlernaを使ってモノレポにしてみました。以下のディレクトリ構造のclientディレクトリにReactのコード、serverにexpressのコード、sharedに共通の型ファイルなどが置いてあります。

.
├── Dockerfile
├── README.md
├── heroku.yml
├── lerna.json
├── package-lock.json
├── package.json
└── packages
    ├── client
    ├── server
    └── shared

おわりに

年末年始あまりにも暇だったので始めたのですが、結果的にいろんな技術やツールに触れて楽しかったです。ぜひ遊んでみてください。

https://notra.herokuapp.com/

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