「TypeScriptの型入門」で手を動かす - その9

本日は「TypeScriptの型入門」の「オブジェクト型再訪」と「asによるダウンキャスト」で手を動かします!

qiita.com

オブジェクト型再訪

  • プロパティに対して修飾子をつけられる
    • ?readonly

?

  • 省略可能を示す
  • ?がついたプロパティの型は指定した型 | undefinedとなる。
    • 任意プロパティで取得できないことがあるため

undefinedとのunion型のほうがおすすめらしい

  • ?をつけずに型 | undefinedと指定した場合はプロパティ自体は存在してないと行けない
    • ?よりこっちの方がおすすめらしい
    • ?はプロパティが無い場合に、本当に無いのか書き忘れなのかの区別ができない
    • 後者(undefinedとのunion型)は書き忘れを防ぐことができる
  • ?が必要なパターンとは?
    • 本当に無くても良いオブジェクトを渡すのは、関数にオプションオブジェクトを渡すときくらい
      • なるほど。

exactOptionalPropertyTypes

  • オプショナルなプロパティの挙動の定義
    • デフォルトは無効
    • TypeScript4.4で追加
  • 無効なとき
    • bar?: numberbar?: 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つの記事を動作確認しつつ、メモしていきたいと思います。

zenn.dev

zenn.dev

webpackがわからない

  • 最近はViteが注目されている。
    • Vue.jsのEvanさんが開発した、ビルドツール。
  • webpackは現場では使われる
  • フロントエンドの開発環境は複雑
    • Frontend DepOpsという専門職ができている
      • まじか
  • webpackを知ることで、Viteのありがたみが理解できる
    • 何できるかわからんから、ありがたみもわからんよね

webpackとは

  • モジュールバンドラーのこと
  • モジュールバンドラーとは
    • 複数のモジュールの依存関係を解決して一つにまとめる「バンドリング」するもの
  • モジュールとは
    • 単体ではなくて組み合わせて使う個々のプログラムのこと

なぜモジュールをバンドリングするのか

  • リクエストの回数をへらすため
    • リクエストが増えると画面の表示速度が落ちる
    • HTTP/1.1では一度に処理できるリクエストの数が限られている
      • HTTP/2ではそうでもなかったような
    • scriptタグで複数のモジュールを読むと
      • 読み込む順番に気をつける必要がある
      • リクエスト回数が増える
  • 一つのJSファイルにロジック書いたらよいのでは?
    • 行数が多くなる
      • 保守性が低くなる
      • 再利用もしづらいよね

なので

  • 開発するときは、なるべく機能をわけたい
  • 実行するときは、なるべく機能をまとめたい

モジュールバンドラーは、これを解決してくれる。とのこと。

なるほど。そうですよね。

HTTP/2で通信のオーバーヘッドが小さくなったり、ブラウザ上でモジュールの依存関係を解消できるようになっても、依存関係解消のオーバーヘッドとかあるし、JSをまとめたものをフロントに返す、というのはなくならない気もするな。 (つまり、バンドラーは残り続ける、かもしれない)

なぜwebpackは複雑と言われるのか

Old JavaScript

  • ES5以前
  • モジュールを読み込むという概念ない
  • 変数宣言もvarのみなので、関数スコープ
    • 即時関数を多用

Current JavaScript

  • ES2015(ES6)移行
  • constletが使える。ブロックスコープになったのかな
    • 変数の巻き上げ、宣言の上書きができないように
  • モジュールの読み込みができるようになった(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してみたら、たしかに絶対パスが出力された
  • contextを指定したら、entryも指定しないとビルドエラーになるな
    • entryのデフォルト値を読まれないっぽい

output

  • 出力先ディレクトリの設定
    • path : ビルドしたファイルの出力先、絶対パス
    • __dirname + "/dist" とか
    • filename:出力ファイル名、指定しないとmain.js
      • ./assets/js/main.jsのようにパス階層を指定できる.
  • clean:true
    • 出力フォルダ内のファイルを全て削除してから出力
    • keepプロパティ
      • 削除したくないディレクトリ・ファイルを指定
      • cleanプロパティをbooleanじゃなくてオブジェクトにしてkeepを渡す

source

  • ソースマップの設定
  • package.jsonでもできるが、webcpack.config.jsでもできる
    • package.json--devtool=source-mapの指定のこと
  • 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からフォークした

wp-works.com

github.com - これは、プラグインじゃなくてコマンドなのね。 - npm install terserでインストールできる

optimization: {
  minimizer: [
    new TerserPlugin({
      parallel: true,
      terserOptions: {
          //色々ある
      }
    }),
  ],
}

上記設定して(terserOptionsは指定していない)、手元で動かしてみたが、使われているのかどうかがよくわからんかったな。

以下から、「エピソード2」へ。

ローダーとは

  • webpackは基本的にはjavascriptのデータしか扱うことはできない
  • その他のデータをJavaScriptのオブジェクトにして、webpackで扱えるようにするのがローダー
  • リソースごとにローダーが存在する

JavaScriptのオブジェクトにする、というのがいまいちイメージわかないけど。具体的な実装を見るとピンとくるかも。

ローダーおよびバンドルの注意点

  • なんでもかんでもバンドルするのは好ましくない
  • バンドルは諸刃の剣、通信回数は減らせるかもだが、データ容量は増える
    • CSSや画像をバンドルすると、元の容量より変換後の方が多くなりがち
    • 小さい画像ならよいかも
  • 見極めるのは技術者の腕の見せ所

CSS

$ 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()ってなんだっけ

  • CSS関数、ファイルを含めるために使用する
  • 絶対URL, 相対URL, データURIのいずれかを指定可能
  • 相対URLの場合はCSSファイルからの相対パス

url(https://example.com/images/myImg.jpg);
url(data:image/png;base64,iRxVB0…);
url(myFont.woff);
url(#IDofSVGpath);
url('../images/bullet.jpg')

developer.mozilla.org

Sass

  • 現在スタイルシートのコーディングにはほとんどSassを利用しているらしい
  • sass-loader: SassをCSSに変換するローダー
  • コンパイルにはsassモジュールが必要
  • npm i -D sass-loader node-sass
  • module.rules[0].useにsass-loaderを追加
    • 後ろから順に実行されるので後ろに追加
    • sass→css変換、cssをJS変換、CSSをHTMLにappendする処理をJSに追加
           {
               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ファイルはそこそこ大きそうだった。

バンドルする画像を分ける

  • ローダーとバンドルの注意点
    • なんでもかんでもバンドルするのは好ましくない
    • base64エンコードした場合、容量は約1.33倍になる
  • typeプロパティをassert/resourceにすると、画像は出力されるがバンドルはされない
    • どういうこと?
      • 一意ぽいファイル名になって、cssのurlプロパティでもこのファイルを指定するようになっているぽい。なるほど
$ ls ./dist/
f1c205a6509fe15aa484.png  index.html  main-bundle.js  main-bundle.js.map
  • ファイルサイズに応じてバインドするか外だしにするかを切り替える
    • typeassetにする
    • 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ファイルをビルドしてくれる
    • そのときに、javascriptcssの読み込み設定もしてくれる
    // 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とか使ってなかったらどうなるうだろう。

まとめ

長かったが、結構ためになったな。

  • webpackは基本的にはバンドルするだけ
    • 複数のjsファイルを一つにまとめる
  • jsやcssを最適化(minifyとか)するにはminimizerを指定する
    • optimization.minimizer[]に指定する
      • TerserPlugin() // jsのminify
  • JS以外を扱う場合はローダーを設定する
    • jsにcssを埋め込む(css-loader)
    • jsに埋め込まれたcssを動的にhtmlから参照できるようにする(style-loader)
    • sassをcssに変換する(sass-loader)
    • css内の画像をバンドルする(url-loaderやtype: asset)
  • ローダーでできない変換処理はプラグインを使う
    • cssを別ファイルに出力する(mini-css-extract)
    • htmlをsrcからdistに出力する(html-webpack-plugin)
    • srcのファイルをdistにコピーする(copy-webpack-plugin)
    • 画像を圧縮する(imagemin-webpack-plugin)

「TypeScriptの型入門」で手を動かす - その8

本日は「intersection型(交差型)」で手を動かします。

qiita.com

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型」で手をうごかします!

qiita.com

never型

  • 「属する値が存在しない型」
  • 部分型関係の一番下にある→任意の型の部分型となっている
    • stringの部分型だし、numberの部分型でもある、ということかな
    • でも属する値が存在しないので、never型の変数に代入不可
  • any型のように危険な型ではない
  • never型の値を作ることはできない
  • switchのdefaultとか、リテラル型で分岐するとdefaultに絶対こないということがある
    • 全体に通らないdefaultで値を返すとその値はnever型で表現できる
  • 絶対にerrorをthrowするだけで、値を返さない関数も、返り値をnever型を指定できる
    • never型を明示的につけないと、void型として推論されるらしい

プレイグラウンドで手を動かす

今日はここまで、次は「intersection型(交差型)」です!

npmの個人メモ

フロントエンドを開発するときに絶対にでてくるnpm。

ちゃんとキャッチアップしたことがなかったので、ググってよく見られてそうな以下の記事を読みます。

zenn.dev

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

github.com

使い方

# 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管理にならないので恩恵にあずかれない
      • 依存関係の管理ができないってことかな
  • 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は以下で確認

github.com

アンインストール

# 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形式のネーミングも多い
    • @types/reactとか@babel/coreとか@vue/cliとか
    • node_modules以下だと、@vueとかでディレクトリが切れてそれ以下にcliとかのディレクトリがある。
    • @vueで一つのリポジトリになってて、モノレポになっているんだと思う

version

  • 決まりは無いが、大抵はセマンティックバージョニングに則ってる

semver.org

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でのモジュールとは
    • node_modulesディレクトリに存在し、rquire()関数で読み込んで使用されるもの
    • 大抵はpackage.jsonで管理され、mainフィールドを持っているパッケージ
  • npmのパッケージとは
    • package.jsonで管理しているプロジェクトのこと
    • あるパッケージAにインストールされて、そのパッケージAから読み込まれれてたら、それはモジュール
      • そのパッケージA内でコマンドラインなどで単体に使用される場合はパッケージ

まとめ

  • 最初は全てパッケージ
  • パッケージを他のパッケージインストールして、その使われ方によってパッケージとモジュールにわかれる
  • 単体で使用するならパッケージ。読み込まれて使用されるならモジュール。

なんとなくわかったが。。JSでimport or requireされるJSはモジュールになる、ということかな。(基本はパッケージ)

パッケージの公開

  • パッケージは以下からダウンロードしている
    • npm installで通信している先
  • 誰でもパッケージを登録することが可能
    • アカウント作成が必要
  • 削除したパッケージ名+バージョンは2度と登録するこはできない
  • npmNode Package Managerの略ではなくて、npm is Not An Acronymらしい。
    • バクロニムと呼ばれるものらしい。

www.npmjs.com

試してみたいけど、なにもできないソースあげてもなぁ。

$ npm login # npmサイトにログインする。アカウント必要。ワンタイムパスも。
$ npm publish # 公開
$ npm logout # ログアウトは忘れずに

# アップデート
# package.jsonのバージョンを更新
$ npm publish # ログインしてからね

# 削除
$ npm unpublish パッケージ名

「TypeScriptの型入門」で手を動かす - その6

本日は「TypeScriptの型入門」の「union型(合併型)」からです。

qiita.com

union型(合併型)

  • ジェネリクスは型のある言語なら普通にある
  • union型を持っている言語は多く無い
    • TypeScriptはunion型のサポートに力を入れている
  • union型は値が複数の型のどれかに当てはまるような型を表している
    • 複数の型を|でつなぐ
      • string | number なら、stringまたはnumber
      • 確かに、javaもこういうふうにはかけないな、何か実現方法あるのかな

union型の絞り込み

  • type HogePiyo = Hoge | Piyoで新しくunion型で定義した場合、そのままでは使いづらい
  • HogeかもしれないしPiyoかもしれないので、無い可能性があるプロパティは参照できない
  • Hoge | Piyoのような型の値が与えられた場合、実行時に判定する必要がある
  • TypeScriptでは適切に型を絞り込んでくれる機能がある
    • if分岐でinでプロパティがあるかを判定する
  • 余計なプロパティがあると、誤判定的な挙動をするので怖い
    • 動かしてみると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より)
    • データ型を変数のように文字・記号で足したり引いたりできる型?
  • 代数的データ型とは
    • 「かつ」と「または」で表すことができる型
    • 「または」の型はコンパイラによってもれなくチェックされる必要がる

zenn.dev

うーむ。関数型言語で使われることが多い概念ぽいな。Haskellでの説明がちょこちょこ出てくる。

qiita.com

この記事では、以下と記載

以下の3つを合わせて代数的データ型と呼びます。
1. 列挙型 (他言語のenumに相当)
2. 直積型 (他言語のstructに相当)
3. 直和型 (他言語のunionに相当)

結局は、union型ならnumber | stringのように、numberかstringかどちらかの型である、というのを定義できて、オブジェクトの場合は、ランタイムで型の判定はできないから(型情報はランタイムでは残らない)、リテラル型でtypeプロパティとかを定義して、どの型なのかを擬似的に判定してるよ、ってことなのかな。

で、単純にtypeofでは判定できないので、擬似的に判定する作りが重くなっちゃてるが、TypeScriptの限界だよと、一旦この理解にしておこう。

union型オブジェクトのプロパティ

  • オブジェクト同士のunion型を作った場合、プロパティアクセスの挙動は概ね期待通りの結果となる
    • HogePIyoHogePiyoを取るかたなら、それの共通プロパティfooだけにアクセス可能
    • また、fooプロパティの型は、HogePiyoそれぞれのfooプロパティの型のunion型になる

プレイグラウンドで手を動かす

長かったー!

ちょこちょこ、なんかテンションだかモチベだか下がって、時間かかっちゃったけど、まぁまぁ理解できたかな。

長かったので、途中で分割しても良かったかも。(モチベ維持のために)

次は「never型」から手を動かします!

「TypeScriptの型入門」で手を動かす - その5

本日は「タプル型」から手を動かします。

qiita.com

タプル型

タプルとは?

pythonとかではa=(1,2)みたいに、順序をもつ値の組みの認識。pythonではイミュータブルだと思うけど、タプルだからといって必ずしもイミュータブルというわけではないのかな。

ja.wikipedia.org

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などの配列型、タプル型は全部any[]の部分型

プレイグラウンドで手を動かす

本日はここまで、なんか、タプル型はいまいち理解が...

  • スプレッド演算子...
    • 可変長引数の型を表すのに使える
    • 関数の呼び出しでも使える
  • U extends any[]型引数Uはany[]型の部分型である

とか、ところどころ理解はできるが、いまいちしっくりこないなぁって感じです。 慣れの問題でしょうか。

ちょっと、辛くなったので、さらっと飛ばして。

明日からは「union型(合併型)」で手を動かします!