「TypeScriptの型入門」で手を動かす - その9
本日は「TypeScriptの型入門」の「オブジェクト型再訪」と「asによるダウンキャスト」で手を動かします!
オブジェクト型再訪
- プロパティに対して修飾子をつけられる
?
とreadonly
?
- 省略可能を示す
- ?がついたプロパティの型は
指定した型 | undefined
となる。- 任意プロパティで取得できないことがあるため
undefined
とのunion型のほうがおすすめらしい
- ?をつけずに
型 | undefined
と指定した場合はプロパティ自体は存在してないと行けない- ?よりこっちの方がおすすめらしい
- ?はプロパティが無い場合に、本当に無いのか書き忘れなのかの区別ができない
- 後者(undefinedとのunion型)は書き忘れを防ぐことができる
- ?が必要なパターンとは?
- 本当に無くても良いオブジェクトを渡すのは、関数にオプションオブジェクトを渡すときくらい
- なるほど。
- 本当に無くても良いオブジェクトを渡すのは、関数にオプションオブジェクトを渡すときくらい
exactOptionalPropertyTypes
- オプショナルなプロパティの挙動の定義
- デフォルトは無効
- TypeScript4.4で追加
- 無効なとき
bar?: number
はbar?: number | undefined
とナオジ- barにundefinedを明示的に入れることができる
- オプションがONなら、undefinedはありえないので、プロパティが存在するか、値が入っているかの2択になる
in
演算子を用いて絞り込みができる
readonly
- 再代入できなくする
- constのプロパティ版と考えてよい
- 素のJavaScriptでもObjectプロパティ定義で、writable属性があるが、型システムに組み込むのは筋が悪いらしく、独自の方法を取っているらしい
- ただし、readonlyではない型を経由して書き換えることができるので、過信してはいけない
インデックスシグネチャ
[key: string]: number
の部分。任意のstring型のプロパティ名に対して、number型を持つという定義- undefinedになるプロパティを参照しても、コンパイルエラーにならない。
- 危険
- インデックスシグネチャはオブジェクトを辞書の用に使うときに使える仕組み
- でも、Mapがあるので、Mapを使った方がよい
interface MyObj { [key: string]: number }
関数シグネチャ
- オブジェクト型の記法で関数型を表現する方法がある
- 以下の
(arg: number):void
部分- 本来は
f : (arg:number) => void
みていな関数型をプロパティに付与するのが普通だと思う。
- 本来は
- ちょっと使い所はわからなかったが、関数にプロパティを付与した型を定義できる
interface Foo { (arg: number): void; } const f: Func = (arg: number) => { console.log(arg) }
newシグネチャ
- コンスタントであることを表すシグネチャ
new():T
の部分
interface Ctor<T> { new() : T }
asによるダウンキャスト
式 as 型
と書く- ダウンキャストとは派生型の値を部分型として扱う
string | number
型をnumber
型として扱うas
を使っても全く関係ない2つの値を変換できないstring
型の値をas number
でnumber型に代入なんてことはできない- できなかったんか。
any
型かunknown
型を経由すれば変換できる
as
はアップキャストもできる- 部分型を派生型として扱うってことかな。
as
を使わなくてもアップキャストはできるので、アップキャストでas
を使うのはやめたほうがよい。- 危険なダウンキャストとの見分けがつかなくなるから
- リテラル型をプリミティブ型に代入とかもアップキャストか
1
型をnumber型に代入
本日はここまで、次は「readonlyな配列とタプル」で手を動かします!
webpackの個人メモ
フロントエンドを開発しているときによく出てくるwebpack。
npmの記事がとても良くて、同じ著者の方の以下の2つの記事を動作確認しつつ、メモしていきたいと思います。
webpackがわからない
- 最近はViteが注目されている。
- Vue.jsのEvanさんが開発した、ビルドツール。
- webpackは現場では使われる
- フロントエンドの開発環境は複雑
- Frontend DepOpsという専門職ができている
- まじか
- Frontend DepOpsという専門職ができている
- webpackを知ることで、Viteのありがたみが理解できる
- 何できるかわからんから、ありがたみもわからんよね
webpackとは
- モジュールバンドラーのこと
- モジュールバンドラーとは
- 複数のモジュールの依存関係を解決して一つにまとめる「バンドリング」するもの
- モジュールとは
- 単体ではなくて組み合わせて使う個々のプログラムのこと
なぜモジュールをバンドリングするのか
- リクエストの回数をへらすため
- 一つのJSファイルにロジック書いたらよいのでは?
- 行数が多くなる
- 保守性が低くなる
- 再利用もしづらいよね
- 行数が多くなる
なので
- 開発するときは、なるべく機能をわけたい
- 実行するときは、なるべく機能をまとめたい
モジュールバンドラーは、これを解決してくれる。とのこと。
なるほど。そうですよね。
HTTP/2で通信のオーバーヘッドが小さくなったり、ブラウザ上でモジュールの依存関係を解消できるようになっても、依存関係解消のオーバーヘッドとかあるし、JSをまとめたものをフロントに返す、というのはなくならない気もするな。 (つまり、バンドラーは残り続ける、かもしれない)
なぜwebpackは複雑と言われるのか
Old JavaScript
- ES5以前
- モジュールを読み込むという概念ない
- 変数宣言も
var
のみなので、関数スコープ- 即時関数を多用
Current JavaScript
- ES2015(ES6)移行
const
とlet
が使える。ブロックスコープになったのかな- 変数の巻き上げ、宣言の上書きができないように
- モジュールの読み込みができるようになった(import)
npmとの違い
- npmはパッケージを管理するツール
- webpackはモジュールバンドラー
Gulpとの違い
- Gulpはタスクランナー
- タスクをいくつか定義していき、そのタスクにしたがって処理をする
- webpackより記述が独特で複雑
- webpackはバンドルに特化している
- バンドルに関してはwebpackがシンプルに記述できる
- 多機能なタスクランナーがなくても、大抵webpackでなんとかなる
色々やりたいならタスクランナーのGulpを使う。webpackで事足ればwebpackって感じなのかな。
Viteとの違い
- Viteはノーバンドルツール
- バンドルはする
- 開発時には依存関係の解決と多少のバンドルだけやる
- 多分、高速なのがメリット
- バンドラーの一種ではある
準備
- webpackのインストール
- webpackとwebpack-cli
- テストファイルの準備
- src/module/momdule.js
- src/index.js
- エントリーポイントはとりあえず
src/index.js
- エントリーポイントはとりあえず
$ npm init -y $ npm i -D webpack webpack-cli # webpackとwebpack-cliって何が違うんだ # src/module/module.js # src/index.js # を作成 $ node src/index.js (node:21806) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension. # moduleとして認識されない # package.jsonにtype=moduleを追加すると $ node src/index.js hello # 正常に出力 モジュールとして認識され依存関係が解決されて自動的に参照モジュールが読まれた # 一旦、type=moduleを削除
ビルド
- webpackコマンドでビルドできる
npx webpack
$ npx webpack $ cat dist/main.js (()=>{"use strict";console.log("hello")})(); # バンドルされているだけ。インライン展開ってかんじだな。 # foo()を2回よぶとどうなるんだ $ cat dist/main.js (()=>{"use strict";const o=()=>{console.log("hello")};o(),o()})(); # へー、デフォはminifyされたコードになる。 # -mode developmentを追加してwebpackを実行 $ npx webpack --mode development # めちゃクソ複雑なソースが出力される
毎回npx webpack --mode development
と打つのは面倒なので、scriptsに追加する。
当たり前かもだが、npx
はつけなくてよいね、そういえば。
"scripts": { "build": "webpack --mode development", "prod" : "webpack --mode production", },
これでnpm run build
でdevelopmentモードでビルドできる。
ソースマップ
- ビルド後のコードはビルド前のコードと違うので、元コードのどの部分かわかるようにビルド前後の対応関係を記したソースマップを出力できるようにする
- 同じディレクトリに
main.js.map
が出力される- ビルド後のファイル名が
main.js
だからかな。末尾が.map
になる
- ビルド後のファイル名が
"build": "webpack --mode development --devtool=source-map",
webpackでバインドするだけなら、これだけで完了。
まとめ
- js側は普通に
import
でモジュールを呼び出す webpack
コマンドを実行する- 開発時は
--mode development --devtool=soiurce-map
- プロダクションは
--mode production
- 開発時は
- 毎回オプションをつけるのが面倒ならscriptsに定義
npm run build
などで呼べるようにする
以下をやろうとおもったら追加設定が必要
- 出力先を変えたい
- cssや画像を扱いたい
- sassを使いたい
- TypeScriptをトランスパイルしたい
- ES5のソースでトランスパイルしたい
- Reactを使いたい
なるほどね。素のwebpackはバンドラーなので、依存関係を解決し1つのJSファイルにまとめるだけか。
webpack.config.js
- webpackでデフォルトで
src/index.js
をエントリーポイントとする - webpackの設定を行うのが
wepack.config.js
- カレントディレクトリのファイルを読む
- オプションで指定もできる
webpack --config ファイル名
module.exports = {}
entry
- webpackがビルドを始める開始点となるエントリーポイント
- デフォルトは
./src/index.js
context
- エントリポイントとローダーのベースとなるディレクトリ
- 絶対パス
- デフォルトはカレントディレクトリ
- コマンド実行時のディレクトリということかな、なんかそんなこともなさそう。パッケージ直下ぽい
__dirname
はnode.jsが提供するグローバル変数、絶対パスでディレクトリ名まで取得可能- webpack.config.jsに
console.log(__dirname)
書いてbuildしてみたら、たしかに絶対パスが出力された
- webpack.config.jsに
- contextを指定したら、entryも指定しないとビルドエラーになるな
- entryのデフォルト値を読まれないっぽい
output
- 出力先ディレクトリの設定
- path : ビルドしたファイルの出力先、絶対パス
- __dirname + "/dist" とか
- filename:出力ファイル名、指定しないと
main.js
./assets/js/main.js
のようにパス階層を指定できる.
clean:true
source
- ソースマップの設定
- package.jsonでもできるが、webcpack.config.jsでもできる
- package.jsonの
--devtool=source-map
の指定のこと
- package.jsonの
- webpack.config.jsに
devtool: "hidden-source-map"
と記載するdevtool: true
を指定するとエラーになるdevtool:false
と指定してもエラーにはならないがソースマップは出力されない
devtool
には色々細かい設定があるらしい。一旦無視
mode
- webpackコマンドのmodeオプションでしていしてたもの
mode: "development"
とすると、webpackコマンドオプション無しでもdevelopmentモードになる
なるほど。てか、ソースマップを出力する=developmentモードってわけじゃないのか。prodocutionモードでも、ソースマップを出力する設定してたら、出力されるね。
developmentモードで出力されるごちゃごちゃしたソースって何のためのロジックなんやろうか。
パスの書き方
- 環境によってパスの区切り文字が違う
path.join
などで連結する- webpack.config.jsもnodejsのコードなのでrequireして使える
-
const path = require('path')
path.resolve
で絶対パスを返せる
複数のファイル
- 複数のjsにバンドリングする
- エントリーポイントを複数していできる
entry : { main: "./indes.js", sub1: "./sub1.js" }, output : { filename: "[name].bundle.js" }
複数のページをまとめてバンドリングする
- これは、複数のエントリーポイントを1つのファイルにバンドリングする方法かな
- 試して見たところ、左のjsから順に連結されて、即時実行の無名関数でラップされる
- それぞれのエントリーポイントのJSは独立してないみたい。
- "./index.js"が実行されて"./sub1.js"が実行されるイメージ。一つの即時関数内。
entry : ["./index.js", "./sub1.js"]
エントリーポイントごとに1つのファイルにしたり、全部をまとめて1つにしたりできたんだね。
watchモード
- ファイルが変更されるのを監視して、自動でリビルドする
--watch
オプションをつけるnpx webpack --watch
- ファイルサイズが大きくなってもwatchモードではキャッシュが有向になって、差分ビルドされる
- `watchOptionsプロパティのignoredプロパティで監視対象から外すこともできる
optimization
- 最適化のデフォルト設定を上書きする
- 最初のうちは知らなくて良い
- じゃあいいか
minimize
- 圧縮を行うか
- truenにするとモードに関わらずminifyする。
- productionモードではデフォルトでtrueになっている
minimizer
- デフォルトの圧縮方法を設定する
- プラグインをインストールする必要がある
- TerserPluginとか
- どうやってインストールするんだ
npm install terser-webpack-plugin --save-dev
TerserPluginとは
- ES6+に対応したJSの圧縮ツール(mangler/compressorとあるがmanglerってなんだ)
uglify-es
という圧縮ツールがあるが、メンテされてないのとES6+に対応してないuglify-es
からフォークした
github.com
- これは、プラグインじゃなくてコマンドなのね。
- npm install terser
でインストールできる
optimization: { minimizer: [ new TerserPlugin({ parallel: true, terserOptions: { //色々ある } }), ], }
上記設定して(terserOptionsは指定していない)、手元で動かしてみたが、使われているのかどうかがよくわからんかったな。
以下から、「エピソード2」へ。
ローダーとは
- webpackは基本的にはjavascriptのデータしか扱うことはできない
- その他のデータをJavaScriptのオブジェクトにして、webpackで扱えるようにするのがローダー
- リソースごとにローダーが存在する
JavaScriptのオブジェクトにする、というのがいまいちイメージわかないけど。具体的な実装を見るとピンとくるかも。
ローダーおよびバンドルの注意点
- なんでもかんでもバンドルするのは好ましくない
- バンドルは諸刃の剣、通信回数は減らせるかもだが、データ容量は増える
- CSSや画像をバンドルすると、元の容量より変換後の方が多くなりがち
- 小さい画像ならよいかも
- 見極めるのは技術者の腕の見せ所
CSS
css-loader
- cssをjavascriptにバンドルする
style-loader
$ npm i -D style-loader css-loader
module
プロパティにルールを設定するmodule
- ローダーなどのモジュールを設定する
module.rules
- 各ローダーを設定するプロパティ
rules.test
- 正規表現などで該当するファイルを指定する
rules.use
- 使用するローダーを指定する
module: { rules: [ test: /\.css$/, use:['style-loader', 'css-lolader'] ] }
※¥と\を間違ったな。\が正しい。
- css-loaderでJS内にCSSの中身が文字列として保持される
- style-loaderでその文字列のCSSをHTMLにappendする。
- main-bundle.jsのscriptタグの下にsytleタグが動的に追加されてた
なるほど。cssをHTMLタグに記載しなくていいのか。これは知らなかった。
options
- 各ローダーのソースマップを含めるには
sourceMap
プロパティを設定する必要があるtrue
で出力false
または設定なし、で出力しない
- ローダーごとにoptionsプロパティをもたせる
use: [ "style-loader", { loader: "css-loader", options: { sourceMap: true } } ]
css-loader
でよく使うオプションとして、url
オプションがある- css内の
url()
の有効無効を設定する - デフォルトは
true
よく使うって、ことはurlをfalseにすることが多いってことかな。
options : { url: false }
url()ってなんだっけ
例
url(https://example.com/images/myImg.jpg); url(data:image/png;base64,iRxVB0…); url(myFont.woff); url(#IDofSVGpath); url('../images/bullet.jpg')
Sass
- 現在スタイルシートのコーディングにはほとんどSassを利用しているらしい
sass-loader
: SassをCSSに変換するローダー- コンパイルには
sass
モジュールが必要 npm i -D sass-loader node-sass
- module.rules[0].useにsass-loaderを追加
{ test: /\.(sass|scss|css)$/, use: ['style-loader', 'css-loader', 'sass-loader'] }
CSS内の画像をバンドル
- url()プロパティをtrueにするだけでは画像をバンドルできない
- base64形式に変換する必要がある
- webpack4ではurl-loaderが必要だった
- webpack5からは
type
プロパティにasset/inline
の指定でよくなった
- webpack.config.jsに画像を処理する設定を入れる
{ test: /\.(gif|png|jpg|svg)$/, type: "asset/inline" }
ローカル環境で動作確認できた。バンドルされたJSファイルはそこそこ大きそうだった。
バンドルする画像を分ける
- ローダーとバンドルの注意点
type
プロパティをassert/resource
にすると、画像は出力されるがバンドルはされない- どういうこと?
- 一意ぽいファイル名になって、cssのurlプロパティでもこのファイルを指定するようになっているぽい。なるほど
- どういうこと?
$ ls ./dist/ f1c205a6509fe15aa484.png index.html main-bundle.js main-bundle.js.map
- ファイルサイズに応じてバインドするか外だしにするかを切り替える
type
をasset
にする- module.rules[1].parser.dataUrlCondition.maxSizeを最大ファイル値にする。(100*1024=100kb)
プラグイン(Plugins)
- ローダーは、リソースをwebpackで扱えるようにしたもの。
- プラグインはローダーでは実現できない機能を提供するもの
mini-css-extract
- バンドルしたJSからスタイルシートの箇所をcssファイルとして出力する
- linkタグでcssファイルを読み込める
npm i -D mini-css-extract-plugin
- webpack.config.jsの先頭でMiniCssExtractPluginをrequireする
- loaderに
MiniCssExtractPlugin.loader
を指定する - plugins[0]に
MiniCssExtractPlugin
のインスタンスを渡す- オプションに
{ filename : './css/[name].css'}
を指定する
- オプションに
- JSから呼ばれているCSSを外だしできる
- index.htmlにはlinkタグでcssを指定しないといけない
これ、どっちが主流なんだろうか。cssファイルは分けた方が良い気もするが。
html-webpack-plugin
- dist以下にhtmlファイルを置くのではなくて、htmlファイルもビルドするプラグイン
- 対象とするhtmlファイルをビルドしてくれる
- そのときに、javascriptとcssの読み込み設定もしてくれる
// html-webpack-pluginの設定 new HtmlWebpackPlugin({ // 対象のテンプレートを設定 template: `${__dirname}/src/index.html`, // 書き出し先 filename: `${__dirname}/dist/index.html`, // ビルドしたjsファイルを読み込む場所。デフォルトはhead inject: 'body' }),
- templateを指定しなかったら、自動でファイルが作成されるらしい、どんなファイル?
- bodyが空のファイルだ。(bodyにjsの読み込み指定してれば、jsのscriptタグのみ)
copy-webpack-plugin
- 指定したファイルをそのままコピーして出力
- src -> distに出力するのに役立つ
new CopyWebpackPlugin({ patterns: [ { from: `${__dirname}/src/img/`, to: `${__dirname}/dist/img/`, } ] }),
module.rules[n].typeがasset/resource
のときは、cssから参照される画像はdist直下にコピーされてたが、どういう使い分けなんじゃろか。
htmlのimgタグにsrc属性で画像ファイルのパスを記載していても、dist以下にコピーされない気もするが。
試してみたが、やっぱりされないか。オプションがあるのかな。
imagemin-webpack-plugin
- ファイルを圧縮する
npm i -D imagemin-webpack-plugin
- 各ファイル形式に対応したパッケージもインストールする
npm i -D imagemin-pngquant
まずはpngだけためす
const ImageminWebpackPlugin = require('imagemin-webpack-plugin').default // defaultでimportするのが最初わからずエラーになった new ImageminWebpackPlugin({ test: /\.(png)$/i, pngquant: { quality: '10-20' } }),
おー、たしかに、dist以下の画像が圧縮されてた。不思議。CopyWebpackPluginとか使ってなかったらどうなるうだろう。
まとめ
長かったが、結構ためになったな。
「TypeScriptの型入門」で手を動かす - その8
本日は「intersection型(交差型)」で手を動かします。
intersection型(交差型)
- union型とついになる概念
- 2つの型T,Uに対して T & U とかくと、TでもありUでもある型を表す
- Tが持つプロパティ、Uが持つプロパティを両方持つ
- Union型の場合は代入はできても、どちらかの型としか認識されないので、現状の型がもつプロパティにしか参照できない
- Union型と組み合わせて使える
(Hoge | Piyo) & Fuga
は(Hoge & Fuga) | (Piyo & Fuga)
と同一視される- union型と同様にif文で型を絞り込みできる
union型を持つ関数との関係
- 関数型を含むunion型も定義できる
- 関数型とそれ以外の型のunion型の場合は
呼び出すことができない
- 関数じゃない可能性もあるからそりゃそう
- union型の構成要素が全部関数だったら呼べる?
- 呼べないこともある。
- 引数がunionで構成される関数に引数の型に一致している必要がある
- intersectiong型を保つ必要がある。(プリミティブはintersectionできないので無理)
- 「関数の引数の型が反変(contravariant)の位置にある」らしい。。。
本日はここまで、次は「オブジェクト型再訪」から。
「TypeScriptの型入門」で手を動かす - その7
本日は「TypeScriptの型入門」の「never型」で手をうごかします!
never型
- 「属する値が存在しない型」
- 部分型関係の一番下にある→任意の型の部分型となっている
- stringの部分型だし、numberの部分型でもある、ということかな
- でも属する値が存在しないので、never型の変数に代入不可
- any型のように危険な型ではない
- never型の値を作ることはできない
- switchのdefaultとか、リテラル型で分岐するとdefaultに絶対こないということがある
- 全体に通らないdefaultで値を返すとその値はnever型で表現できる
- 絶対にerrorをthrowするだけで、値を返さない関数も、返り値をnever型を指定できる
- never型を明示的につけないと、void型として推論されるらしい
今日はここまで、次は「intersection型(交差型)」です!
npmの個人メモ
フロントエンドを開発するときに絶対にでてくるnpm。
ちゃんとキャッチアップしたことがなかったので、ググってよく見られてそうな以下の記事を読みます。
Node.jsを使用するうえでnpmはちゃんと理解しておいた方がいいです。
とのこと。はい。理解します。
npmとは
- Node.jsのパッケージを管理するシステム
パッケージとは
- モジュールとは
- 他のプログラムから利用することを目的としたクラスや関数などのプログラム
- パッケージとは
- モジュールをまとめて機能するようにしたもの
- ライブラリとも呼ばれる
- npmの独自定義
- npmに存在するのはパッケージとモジュール
npmの必要性
- 以下の問題を解決するためにnpmが必要
- パッケージの依存関係
- パッケージの競合関係
- パッケージxで使用しているパッケージyが新しくインストールするパッケージzでも使われているとか
Yarn
- npmよりもインストールが速く、セキュリティが高い特徴がある
- npmと互換性があるパッケージ管理システム
- 筆者は改善されてきたnpmで十分かな、らしい
npmのインストール
- Node.jsをインストールすると同時にインストールされる
- Node.jsにはnvm(Node Version Manager)というNode.jsのバージョン管理システムで管理できる
- Node.jsは頻繁にバージョンアップされるからできるだけnvmを入れよう
Linux
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash # bashを開き直す $ tail -n 3 ~/.bashrc export NVM_DIR="$HOME/.config/nvm" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion # nvmのバージョン $ nvm --version 0.39.1
使い方
# nodejsのインストール $ nvm install 16.13.2 # インストールされているバージョンの一覧 $ nvm list -> v16.14.1 system default -> 16.14.1 (-> v16.14.1) iojs -> N/A (default) unstable -> N/A (default) node -> stable (-> v16.14.1) (default) stable -> 16.14 (-> v16.14.1) (default) lts/* -> lts/gallium (-> v16.14.1) lts/argon -> v4.9.1 (-> N/A) lts/boron -> v6.17.1 (-> N/A) lts/carbon -> v8.17.0 (-> N/A) lts/dubnium -> v10.24.1 (-> N/A) lts/erbium -> v12.22.10 (-> N/A) lts/fermium -> v14.19.0 (-> N/A) lts/gallium -> v16.14.1 # galiiumだけインストールされてるってことかな。 # リストアップされているのはLTSバージョンかな。 # バージョン切り替え $ nvm use 16.14.1 # アンインストール $ nvm uninstall 16.14.1 # nvmバージョン $ nvm --version # 利用しているnodeのバージョン? $ nvm version
npmの使い方
プロジェクトの作成
- npmはプロジェクト内でパッケージを管理するものなのでプロジェクトを作成する必要がある
初期化
npm init
でpackage.jsonを作成する- package.jsonを作成しないでもパッケージのインストールはできる
- ただしnpm管理にならないので恩恵にあずかれない
- 依存関係の管理ができないってことかな
- ただしnpm管理にならないので恩恵にあずかれない
- package.jsonが無いところで、
npm install xxx
したらインストールできた
# initで初期化(package.jsonを作成) # -y つけると全部初期値で作成 $ npm init -y Wrote to /home/xxxxx/npm_sample/package.json: { "name": "npm_sample", // パッケージの名前 "version": "1.0.0", "description": "", // npmリポジトリの説明欄 "main": "index.js", // メインファイル(一番最初に実行されるファイル) "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], // パッケージを公開したときの検索ワード "author": "", "license": "ISC" }
proxy
- proxyをかましたい場合に設定可能
# 設定の確認 $ npm config list ; node bin location = /home/xxxx/.config/nvm/versions/node/v16.14.1/bin/node ; cwd = /home/xxxx/npm_sample ; HOME = /home/xxxx ; Run `npm config ls -l` to show all defaults. # 詳細情報 $ npm config ls -l # configのset $ npm config set proxy [proxy url]
インストール
# ローカルインストール(install -> iでもよい) $ npm install typescript ⸨⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⸩ ⠴ idealTree:npm_sample: sill idealTree buildDeps added 1 package, and audited 2 packages in 24s found 0 vulnerabilities # バージョン指定のインストール # vueの最新は3.xだが、2.6.xを入れたい場合 $ npm info vue vue@3.2.31 | MIT | deps: 5 | versions: 370 # ...省略 dist-tags: csp: 1.0.28-csp latest: 3.2.31 legacy: 2.6.14 next: 3.2.31 # 2.6.14がlegacyらしい $ npm i vue@2.6.14 added 1 package, and audited 3 packages in 2s found 0 vulnerabilities $ cat package.json # 抜粋。vue2.6.14がインストールできた # --saveしなくてもdepencenciesに追加されるのね "dependencies": { "typescript": "^4.6.2", "vue": "^2.6.14" } # devDependenciesへの追加 # --save-dev または -D $ npm i -D webpack $ cat package.json # 抜粋。webpakがdevの方に追加された。 "devDependencies": { "webpack": "^5.70.0" }
package.jsonに設定されたパッケージのインストール
$ cd [ package.jsonがあるディレクトリ] $ npm install
npmのバージョンアップ
npm自信をグローバルインストールする
$ npm install -g npm
不思議だな。
パッケージ一覧
# 直接インストールしたもの npm list --depth=0と同じ # インストールしたパッケージが依存しているパッケージは表示されない $ npm list # 表示形式が違うバリエーションがある $ npm ls $ npm la $ npm ll # パッケージが依存しているパッケージも表示 $ npm list --depth=1 npm_sample@ /home/xxxx/npm_sample ├── typescript@4.6.2 ├── vue@2.6.13 └─┬ webpack@5.70.0 ├── @types/eslint-scope@3.7.3 ├── @types/estree@0.0.51 ├── @webassemblyjs/ast@1.11.1 ├── @webassemblyjs/wasm-edit@1.11.1 ... # グローバルインストールパッケージ一覧 $ npm list -g
パッケージ情報の検索/取得
# npmリポジトリから検索 $ npm search vue NAME | DESCRIPTION | AUTHOR | DATE | VERSION | KEYWORDS vue | The progressive… | =yyx990803… | 2022-02-12 | 3.2.31 | vue # パッケージ詳細情報の表示(info -> view or showのエイリアスあるどれも同じ) $ npm info vue # パッケージの最新バージョン $ npm info vue version 3.2.31 # インストール可能なパッケージ一覧 $ npm info vue versions [ '0.0.0', '0.6.0', '0.7.0', '0.7.1', '0.7.3', '0.7.4', ...
パッケージのアップデート
# アップデートの確認 $ npm outdated Package Current Wanted Latest Location Depended by @vue/cli 5.0.2 5.0.3 5.0.3 node_modules/@vue/cli npm_sample vue 2.6.14 2.6.14 3.2.31 node_modules/vue npm_sample # 多分wantedにあげた方がよい。vueのLatestはメジャーバージョンが上がって破壊的な変更が入っているので、wantedと一致してない # アップデート $ npm update @vue/cli # パッケージ名省略で全部アップデート $ npm update # npm updateで@vue/cliだけアップデートされた。wantedまで上がるということか $ npm outdated Package Current Wanted Latest Location Depended by vue 2.6.14 2.6.14 3.2.31 node_modules/vue npm_sample # でもpakcage.jsonに反映されないな。自動反映されないのか? $ cat package.json { "dependencies": { "vue": "^2.6.13" }, "devDependencies": { "@vue/cli": "^5.0.2", "typescript": "^4.6.2" } } # ^5.0.2 は5.0.3を含むから、別に更新しなくていいのか。
npm-check-updates
- 複数のパッケージのアップデータを便利できる
$ npm i -g npm-check-updates $ ncu Checking /home/xxxxx/npm_sample/package.json [====================] 3/3 100% vue ^2.6.13 → ^3.2.31 @vue/cli ^5.0.2 → ^5.0.3 # vue/cliの5.0.3はインストールされているはずだけどpackage.jsonベースの確認 # package.jsonへの反映 $ ncu -u Upgrading /home/xxxxx/npm_sample/package.json [====================] 3/3 100% vue ^2.6.13 → ^3.2.31 @vue/cli ^5.0.2 → ^5.0.3 Run npm install to install new versions. # vueと@vue/cliがバーアップされてしまった。 $ cat package.json { "dependencies": { "vue": "^3.2.31" }, "devDependencies": { "@vue/cli": "^5.0.3", "typescript": "^4.6.2" } } # vueは除外したいな $ ncu -u -x vue Upgrading /home/xxxx/npm_sample/package.json [====================] 2/2 100% @vue/cli ^5.0.2 → ^5.0.3 # ncu のパッケージ指定は正規表現も使える
ncuは以下で確認
アンインストール
# uninstallじゃなくて、remove, rm, r, un, unlink でも同じ $ npm uninstall @vue/cli $ npm uninstall -g @vue/cli # グローバル # package.jsonへの反映 # 試してみると、--saveを指定しなくてもpackage.jsonに反映される # npmのバージョンの問題かな $ npm un --save @vue/cli
ローカルインストールの罠
PATHを通す。
# グローバルインストールの場所 $ npm root -g /home/xxxx/.config/nvm/versions/node/v16.14.1/lib/node_modules # パッケージを探す探索パスは以下でも確認可能 # nodejsのグローバル変数を出力している # node -e でワンライナーを実行できるのね $ node -e "console.log(global.module.paths)" [ '/home/xxxx/npm_sample/node_modules', '/home/xxxx/node_modules', '/home/node_modules', '/node_modules' ] # 上から順にモジュールを検索するのか。一つ目はローカルだな。 # npmコマンドのインストール先 # グローバル $ npm bin -g /home/xxxx/.config/nvm/versions/node/v16.14.1/bin # ローカル。ローカルパッケージディレクトリにて $ npm bin /home/xxxx/npm_sample/node_modules/.bin # 以下にパスを通すとglobal.module.pathsに反映される $ export NODE_PATH=<PATH>
ローカルパッケージの実行
# ./node_modules/.bin への絶対パスを返す $ npm bin tsc /home/xxxx/npm_sample/node_modules/.bin $ ll ./node_modules/.bin/ lrwxrwxrwx 1 xxxx xxx 21 Mar 19 13:40 tsc -> ../typescript/bin/tsc lrwxrwxrwx 1 xxxx xxx 22 Mar 19 13:48 vue -> ../@vue/cli/bin/vue.js # .binには各node_modules以下のコマンドへのシンボリックリンクがある # 直接.bin以下を叩いてもよいが面倒なので、npxがあるよってのがこのあとの話
npx
./node_modules/.bin
からコマンドを探して実行してくれる- コマンドがインストールされてなくても探して実行してくれる
- え?npm installしてなくてもってことかな
$ npx webpack Need to install the following packages: webpack Ok to proceed? (y) y # インストールを求められるだけだな、npmのバージョンで違いがあるのかな # ローカルインストールされてれば以下のように実行可能 $ npx tsc -V Version 4.6.2 # urlからもnpxやnpm installできる? $ npx https://github.com/Microsoft/TypeScript.git npm ERR! could not determine executable to run # エラーになった。別のパッケージならできるのかな。 $ npm install https://github.com/Microsoft/TypeScript.git ⸨#########⠂⠂⠂⠂⠂⠂⠂⠂⠂⸩ ⠸ reify:typescript: sill audit bulk request { typescript: [ ' # なんか時間かかるので止めた
npxやnpm installでURLを指定するのは、あまり一般的ではないのかな。便利に使えるケースもあまり無い気もするし。公開されてない社内レポジトリのパッケージを使うとき、とかかしら。
package.json
- package.jsonとはプロジェクトの構成、構造を示した設計書のようなもの
- プロジェクト=パッケージ
name
- パッケージの名前
@
と/
の間にユーザーネーム(or組織名)をいれて名前空間とするscoped packages形式のネーミングも多い
version
- 決まりは無いが、大抵はセマンティックバージョニングに則ってる
description
- パッケージの説明、
npm search
で表示される
private
- パッケージを公開しない設定
- 開発時は「true」にした方が良い
main
- そのパッケージをインストールする際に開始となるファイル
bin
$ ll ./node_modules/.bin/ | grep tsc lrwxrwxrwx 1 xxxx xxxx 21 Mar 19 13:40 tsc -> ../typescript/bin/tsc $ cat ./node_modules/typescript/package.json | grep bin "bin": { "tsc": "./bin/tsc", "tsserver": "./bin/tsserver"
scripts
- コマンドのエイリアス
npm run xxxx
で実行- 慣習的に使われるエイリアスがある
- start: プログラムを実行する
- test : テストを実行する
- 名前の先頭に
pre
をつけるとついてないコマンドを実行する前にpre
がついたコマンドが実行されるpost
もある- preとかpostとかあまり使われてなさそう。見かけない。
dependencies
npm insall vue --save
でpackage.jsonのdependenciesに追記される- ローカル環境だと
--save
つけなくても追記される
- ローカル環境だと
npm 5.0.0
からのデフォルトの挙動らしい.自分のローカルは「8.5.0」、「5.0.0」が出たのはもう5年前らしい。
qiita.com
package-lock.json
もなんか最新バージョンと違うきもするが、あとで調べよう。
- dependenciesのバージョンに指定されている文字
^
: メジャーバージョンを固定(マイナー、パッチはあがる)~
:メジャー、マイナーバージョンを固定(パッチはあがる)- なし:全部一致
devDependencies
--save-dev
か-D
をつける- 開発やテスト時につかうパッケージを設定する
optionalDependencies
- インストールに失敗しても、他のインストールの処理が続行される
- なくても問題ないパッケージ
- あまり設定することない
- いつ設定するんだろうか
package-lock.json
- パッケージのバージョンをロックするファイル
- npm installで作成される
- npm installでインストールするたびに更新される?
- npm 8x だとそうでもないような挙動に見える、バージョン差分かな
- 複数人で開発するときにバージョンをあわせられる
- git管理する
- 人の手では更新しない
npm update
で更新される。(package.jsonは更新されないようだ)
- pakage-lock.jsonでインストールするには、
npm insltall
ではなくてnpm ci
を利用する?- npm 8xでは
npm install
でもpackage-lock.jsonを見てそうだった
- npm 8xでは
パッケージとモジュール
- npmでのモジュールとは
- npmのパッケージとは
まとめ
- 最初は全てパッケージ
- パッケージを他のパッケージインストールして、その使われ方によってパッケージとモジュールにわかれる
- 単体で使用するならパッケージ。読み込まれて使用されるならモジュール。
なんとなくわかったが。。JSでimport or requireされるJSはモジュールになる、ということかな。(基本はパッケージ)
パッケージの公開
- パッケージは以下からダウンロードしている
- npm installで通信している先
- 誰でもパッケージを登録することが可能
- アカウント作成が必要
- 削除したパッケージ名+バージョンは2度と登録するこはできない
npm
はNode Package Manager
の略ではなくて、npm is Not An Acronym
らしい。- バクロニムと呼ばれるものらしい。
試してみたいけど、なにもできないソースあげてもなぁ。
$ npm login # npmサイトにログインする。アカウント必要。ワンタイムパスも。 $ npm publish # 公開 $ npm logout # ログアウトは忘れずに # アップデート # package.jsonのバージョンを更新 $ npm publish # ログインしてからね # 削除 $ npm unpublish パッケージ名
「TypeScriptの型入門」で手を動かす - その6
本日は「TypeScriptの型入門」の「union型(合併型)」からです。
union型(合併型)
- ジェネリクスは型のある言語なら普通にある
- union型を持っている言語は多く無い
- TypeScriptはunion型のサポートに力を入れている
- union型は値が複数の型のどれかに当てはまるような型を表している
- 複数の型を
|
でつなぐstring | number
なら、stringまたはnumber- 確かに、javaもこういうふうにはかけないな、何か実現方法あるのかな
- 複数の型を
union型の絞り込み
type HogePiyo = Hoge | Piyo
で新しくunion型で定義した場合、そのままでは使いづらい- HogeかもしれないしPiyoかもしれないので、無い可能性があるプロパティは参照できない
Hoge | Piyo
のような型の値が与えられた場合、実行時に判定する必要がある- TypeScriptでは適切に型を絞り込んでくれる機能がある
- if分岐で
in
でプロパティがあるかを判定する
- if分岐で
- 余計なプロパティがあると、誤判定的な挙動をするので怖い
- 動かしてみるとHogePiyoにそもそも余計なプロパティを追加できなかったな
typeofを用いた絞込み
string === typeof value
のように絞り込みができる- あれ、これプリミティブ型以外だめだったりしないのかな..
- 関数の引数に対するバリデーションとか、ラインタイム依存の場合は駄目な気がする。
- あれ、これプリミティブ型以外だめだったりしないのかな..
nullチェック
- union型がよく使われるのは、nullableな値を扱いたい場合
- undefinedも
string | null
なら文字列の値があるかもしれないしnullかもしれないvalue != null
でチェックすると、型を絞り込んでくれる&&
や||
の短絡実行でも型検査してくれる
代数的データ型っぽいパターン
- プリミティブ型ならいい感じにunion型の絞り込みができるがオブジェクトはできない
- リテラル型とunion型を組み合わせることで代数的データ型(タグ付きunion)を再現するパターンが推奨
これ、なんか難しいんですよね。
- 値があるかもしれない無いかもしれないことを表すoption型をTSで表現
type Option<T> = Some<T> | None
- option型はsomeまたはnone、someだと値がある
- SomeとNoneは共通のtypeプロパティを持っている
- typeプロパティで someかnoneかを判定する
- TypeScriptプログラミングにおいて頻出
代数的データ型とは
- そもそも「代数」とは
- 特定の数の代わりとして用いられる文字・記号などのこと(wikipediaより)
- データ型を変数のように文字・記号で足したり引いたりできる型?
- 代数的データ型とは
- 「かつ」と「または」で表すことができる型
- 「または」の型はコンパイラによってもれなくチェックされる必要がる
うーむ。関数型言語で使われることが多い概念ぽいな。Haskellでの説明がちょこちょこ出てくる。
この記事では、以下と記載
以下の3つを合わせて代数的データ型と呼びます。 1. 列挙型 (他言語のenumに相当) 2. 直積型 (他言語のstructに相当) 3. 直和型 (他言語のunionに相当)
結局は、union型ならnumber | string
のように、numberかstringかどちらかの型である、というのを定義できて、オブジェクトの場合は、ランタイムで型の判定はできないから(型情報はランタイムでは残らない)、リテラル型でtypeプロパティとかを定義して、どの型なのかを擬似的に判定してるよ、ってことなのかな。
で、単純にtypeof
では判定できないので、擬似的に判定する作りが重くなっちゃてるが、TypeScriptの限界だよと、一旦この理解にしておこう。
union型オブジェクトのプロパティ
- オブジェクト同士のunion型を作った場合、プロパティアクセスの挙動は概ね期待通りの結果となる
HogePIyo
がHoge
とPiyo
を取るかたなら、それの共通プロパティfoo
だけにアクセス可能- また、
foo
プロパティの型は、Hoge
とPiyo
それぞれのfoo
プロパティの型のunion型になる
長かったー!
ちょこちょこ、なんかテンションだかモチベだか下がって、時間かかっちゃったけど、まぁまぁ理解できたかな。
長かったので、途中で分割しても良かったかも。(モチベ維持のために)
次は「never型」から手を動かします!
「TypeScriptの型入門」で手を動かす - その5
本日は「タプル型」から手を動かします。
タプル型
タプルとは?
pythonとかではa=(1,2)
みたいに、順序をもつ値の組みの認識。pythonではイミュータブルだと思うけど、タプルだからといって必ずしもイミュータブルというわけではないのかな。
TypeScriptのタプル型
- JavaScriptには無い概念である、タプル型がある。
- 配列をタプルの代わりとして用いている
[string, number]
のように型を定義する- TypeScriptがタプルと読んでいるのは配列なので、配列のメソッドで操作できる
- 配列のメソッドで操作すると、型に一致しない値を入れられてしまう
- TypeScriptの限界
- 配列のメソッドで操作すると、型に一致しない値を入れられてしまう
- 0要素のタプル型も作れる
- 配列メソッド操作じゃなければタプルのlengthは固定
- ただし、可変長のタプル型の宣言もできる
[number, ...string[]]
みたいな感じで、...配列型
を指定する[...string[], number]
ともできる。最後だけnumber...
が使えるのは1回だけ
- オプショナルな要素もある
[string, number?]
numberはあっても無くても良い[string?, number]
はだめ。オプショナルじゃない要素より後である必要がある
タプル型と可変長引数
- TypeScript3.0より、タプル型を関数の可変長引数の型に使えるようになった
type Args = [string, number, boolean]; const func (...args: Args) => arg[1]
- いまいちメリットがわからん。。(a:string, b:number, c:boolean)と同じではないのか
関数呼び出しのspreadとタプル型
- JSでは
...
を関数呼び出しのときにも使える - タプル型も同様に関数呼び出しでも使える
func(...args)
みたいな
タプル型と可変長引数とジェネリクス
- タプル型をとる型変数を用いることで、関数の引数列をジェネリクスで扱うことができる
U extends any[]
は 型引数Uはanyの部分型でなければならないという意味- string
本日はここまで、なんか、タプル型はいまいち理解が...
- スプレッド演算子
...
- 可変長引数の型を表すのに使える
- 関数の呼び出しでも使える
U extends any[]
型引数Uはany[]型の部分型である
とか、ところどころ理解はできるが、いまいちしっくりこないなぁって感じです。 慣れの問題でしょうか。
ちょっと、辛くなったので、さらっと飛ばして。
明日からは「union型(合併型)」で手を動かします!