Panda Noir

JavaScript の限界を究めるブログでした。最近はいろんな分野を幅広めに書いてます。

1年間まいにち10コミット達成しました!

(本記事はネタ記事です)

いやー2020年も終わりですね。みなさんいかがお過ごしでしょうか?

僕は今年1月に「毎日10コミットをする」という抱負を立てていました。そしてなんとこのたび、毎日10コミットを達成しました!!

f:id:panda_noir:20201230162802j:plain

実際のアカウント

仕事がある日もお休みの日もがんばった甲斐がありました!!来年も達成できるようがんばりたいです!

というのはメチャクチャ嘘です。このコミットは単なる偽装で、実際には毎日コミットできていません…

毎日10コミット"したことにする"

毎日10コミット"していたように偽装する"トリックは、Git コマンドにあります。

Git には、コミットをする際に「コミット日時を指定するオプション」があります。

$ git commit --allow-empty -m 'fake commit' --date='2020-01-01 13:00:00 +0900'

たとえば、上のコマンドを叩くと、日付が現在時刻ではなく2020年1月1日のコミットが行われます。もうお分かりですね。あとは日付をずらしながらコミットを実行することで、毎日コミットしていたことにできます。スクリプトがこちらです。

#!/bin/bash
for i in {0..365}; do
  echo git commit --allow-empty \
    -m 'fake commit' \
    --date="`ruby -e "p Time.at(Time.new(2020,1,1,13,0,0,'+09:00').to_i + $i*24*60*60)"`"
done

(ここでは ruby を使って日付を生成していますが、dateに指定できるフォーマットになってさえいればなんでもOKです)

あとは、この嘘コミットが含まれたリポジトリを GitHub へ push して完了です。この偽装コミットはきちんと草に反映されます。

注意事項: 悪用厳禁

当たり前ですが、悪用は厳禁です。たとえば面接前日にこのスクリプトを実行して「毎日 GitHub へコミットをがんばっています!」とアピールしても、あなたも含めて誰も幸せになりません。ゼッタイにしないでください。

ちなみに: 一度に大量のコミットをpushすると草が反映されなくなる

上記の画像のあと、さらにコミットを行ったのですが、なぜかコミットが草に反映されなくなりました。一度に大量のコミットをしたせいで反映されなくなったのでしょうか…?

累計6000コミット(1日あたり17コミット)したはずですが、現時点では1日あたり7コミットで表示されています。僕の10コミットはどこへ行ったのでしょうか・・・?まあ、ごまかしてもダメだよということですかね。

プロパティが一つもないオブジェクト型

ちょっとした小ネタ。

type Empty1 = {}; // これはLinterに怒られる
type Empty2 = {[key in string | number | symbol]: never}; // こっちはOK

使用例

APIレスポンスの返り値の型など、JSON周りでの使用パターンが多そうです。

const json = fetch('https://example.com');
const response = JSON.parse(json) as Empty;

esbuild だと新しいJSXトランスフォームに対応できなそう

新しいJSXトランスフォームは若干引数が異なっています。

ReactDOM.render(<App/>, document.querySelector('#main'));
// ReactDOM.render(React.createElement(App, null), document.querySelector('#main'));

これが下のようになります。

ReactDOM.render(<App/>, document.querySelector('#main'));
// ReactDOM.render(_jsx(App, {}, null), document.querySelector('#main'));

第二引数が異なるようになってしまったので、単純な以下のようなことはできません。

$ npx esbuild src/*.tsx --bundle '--define:process.env.NODE_ENV="development"' --jsx-factory=_jsx --jsx-fragment=Fragment --inject:src/shim.ts --outdir=dist

引数の与え方が異なっているのですから、JSX ファクトリーをすり替えるだけでは当然うまくいきません。

現在の状況

そもそも、 import React from 'react';がなくしたいだけなら以下でもいけます。

// src/shim.ts
export * as React from 'react';
$ npx esbuild src/*.tsx --bundle '--define:process.env.NODE_ENV="development"' --inject:src/shim.ts --outdir=dist

ただ、これでは今回新しくJSXトランスフォームが導入された意義の半分も満たせていません。

現在はissueが立っており、そこで議論されている最中です。どうやらプラグインとしてのサポートとなりそうです。

esbuild はすぐに導入できてプレイグラウンドとしてかなり優秀なので、対応してくれることを祈っています。

デフォルト引数を悪用して変数宣言を減らす

const state = reactive({
  count: 42,
  flag: true,
  str: 'hello world',
  computed: computed(() => {
    const {count, flag, str} = state;
    return flag ? count : str;
  }),
});

このような Vue3 のコードがあったとします。このとき、わざわざconst文を使わず、デフォルト引数に収める荒業があります。

const state = reactive({
  count: 42,
  flag: true,
  str: 'hello world',
  computed: computed(({count, flag, str} = state) =>
    flag ? count : str
  ),
});

もちろん、今後引数が渡されるようになったら修正しなければなりません。しかし、その場合も TypeScript を使っていれば型エラーになってくれるので、そこまで大惨事を引き起こしはしないと思います。

最も、他のひとが読むときにコードの意図が全くわからなくなるので、業務コードに混ぜるのはやめましょう。

型レベルでPermutationする

こんな感じの型です

type Fuyu = Permutation<'あんたは' | 'ここで' | 'ふゆと' | '死ぬのよ'>;
// ['あんたは', 'ここで', 'ふゆと', '死ぬのよ'] | ['あんたは', 'ここで', '死ぬのよ', 'ふゆと'] | ['あんたは', 'ふゆと', 'ここで', '死ぬのよ'] | ...

※本記事ではTypeScript beta版の機能を使用しています

作り方

けっこう愚直に書いてます。そこまで説明することもないですね。

type Permutation<T, U = T> =
    U extends string ?
        Exclude<T, U> extends never ?
            [U] :
        [U, ...Permutation<Exclude<T, U>>] :
    [U];

ポイントはTとUの2変数を使っている点です。こうしないと、conditional types でバラされたあとに元々の型がわからなくなってしまい、Exclude<T, U>の部分が実現できません。

気をつけるべきはベータバージョンでしか動かないことです(2020年10月現在)。頑張れば下位バージョンでもできそうですが、まだ思いついていないです。

実例

type Permutation<T, U = T> =
    U extends string ?
        Exclude<T, U> extends never ?
            [U] :
        [U, ...Permutation<Exclude<T, U>>] :
    [U];

type ArrayToString<T extends string[]> =
  T extends [infer head, ...infer tail] ?
    head extends string ?
      tail extends string[] ?
        `${head}${ArrayToString<tail>}` :
      '' :
    '' :
  '';
type Fuyu = ArrayToString<Permutation<'あんたは' | 'ここで' | 'ふゆと' | '死ぬのよ'>>;
const str: Fuyu[] = ['あんたはここでふゆと死ぬのよ', 'あんたはふゆとここで死ぬのよ', 'ここであんたはふゆと死ぬのよ'];

const normalize = (str: Fuyu) => 'あんたはここでふゆと死ぬのよ' as const;

おまけ: 不要なものを除外する

上記の例では「死ぬのよここでふゆとあんたは」のようなものまで含んでしまっています。不要なものを除外するときはExcludeを使います。 

type Fuyu = Exclude<
    ArrayToString<
      Permutation<'あんたは' | 'ここで' | 'ふゆと' | '死ぬのよ'>
    >,
  '死ぬのよここでふゆとあんたは'>;