お知らせ

現在サイトのリニューアル作業中のため、全体的にページの表示が乱れています。

undefinedの判定方法が複数あるということでundefined判定の処理速度比較をしてみたのでその結果。

端的に言うと、hoge === undefinedtypeof hoge === 'undefined'の二方式がある。後者は原則考慮不要だが、言語仕様上存在しているので比較したが、現実的に見た場合、どちらで記述した場合でも処理速度に有意な差はないように感じた。

確認環境

Env Ver
Node.js 20.1.0
TypeScript 4.9.5
@swc/core 1.3.8

比較結果

hoge === undefinedの方が早く見えるが実行するタイミングで変わるので誤差の範疇だと思う。

方式 ms
hoge === undefined 4,514
typeof hoge === 'undefined' 4,515

確認コード

const tyof = (param?: string) => {
  return typeof param === 'undefined';
};

const undef = (param?: string) => {
  return param === undefined;
};

const tyStart = +new Date();
for (let i = 0; i < 10000000000; i++) {
  tyof();
}
console.log('typeof', +new Date() - tyStart);

const unStart = +new Date();
for (let i = 0; i < 10000000000; i++) {
  undef();
}
console.log('undefined', +new Date() - unStart);

TSから生成されたJS

"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
const tyof = (param)=>{
    return typeof param === 'undefined';
};
const undef = (param)=>{
    return param === undefined;
};
const tyStart = +new Date();
for(let i = 0; i < 10000000000; i++){
    tyof();
}
console.log('typeof', +new Date() - tyStart);
const unStart = +new Date();
for(let i = 0; i < 10000000000; i++){
    undef();
}
console.log('undefined', +new Date() - unStart);

あとがき

MDNを読む限りtypeof hoge === 'undefined'は該当変数が存在しない場合に有用なようであるが、TypeScriptで書いている場合、通常このようなコードが生まれることがなく、仮に起きるとした場合、次のようなコードになるため現実的に考慮する必要はない。なおMDNにも「こんなことはしないこと」と書いてあるので、一般的なコードでないことは客観的にも伺えるだろう。

(() => {
  const undefined = 123;
  const hoge = undefined;

  if (typeof hoge === 'undefined') {
    console.log('hoge is undefined');
  } else {
    console.log('hoge is not undefined');
  }
})();

上記コードの実行結果としてはhoge is not undefinedが出力される。

このコードの主な問題点

  1. const undefined = 123;というコードは予約語を変数名にしているため、混乱を招くコードであり、書かないことが好ましい
    1. MDNには予約語ではないとあるが、一般的には予約語の一つとして解釈して支障ないと考える
  2. このコードはESLintのeslint:recommendedで検知されるため、通常であれば書かれることはない
    1. no-shadow-restricted-namesに引っかかる

なお、このコードは例示のために即時実行関数形式で記述しているが、必要がない限りこの形式での実装は避けたほうが問題が少なくなると思う。これは不必要なネストが生まれたり、スコープの混乱を生むためである。

投稿日:
言語::MarkdownNode.js::Prettier

2023-07-05にPrettier 3.0がリリースされて大分経ったので個人的に気になったところのまとめ。Highlights~Breaking Changesの部分だけ書いてます。Other Changesまで書くと長すぎるので…(Other Changesはざっと見た感じ概ね可読性が向上する感じの内容だと思いましたが、行が動くので書き方によってはコードや、行ベースで動作しているCIの検査が壊れる可能性が場合によってはありそうです。)

変更点

気になったところしか書いてないのでちゃんと見たい人は本家のリリースノートを見てください

日本語と西洋文字の間に半角スペースを挿入しない様に

Stop inserting spaces between Chinese or Japanese and Western characters

恐らく日本人的にはこれが今回最大の変更点になると思います。かなり賛否両論があると思いますが、私は肯定派です。むしろ半角スペースが入るようになって以来、これが来る日をずっと待ちわびていた。

今までは全角文字と半角文字の間に半角スペースが挿入されていましたが、これがなくなります。これは可読性の観点では悪くない処理だとは思うのですが、ドキュメントとしてみた場合に検索性が非常に悪く個人的に好きではありませんでした。

<!-- 入力値 -->
漢字
Alphabetsひらがな12345カタカナ67890

<!-- Prettier 2.8 -->
漢字 Alphabets ひらがな 12345 カタカナ 67890

<!-- Prettier 3.0 -->
漢字Alphabetsひらがな12345カタカナ67890

なお、この変更には後方互換性がなく、今までのように半角スペースを挿入することはできなくなります。

また、新しい方式に既存のドキュメントを合わせる場合、Prettierが挿入したこの半角スペースを自動で綺麗に除去するのは難しいと思います。探せばあるかもしれませんが、恐らくtextlintの除去ルールでは不完全だと思います。私が試した限りでは見出しやリンクタイトル辺りでは上手く機能してくれませんでした。ただ、ある程度はtextlintが解決してくれるので、大まかにはtextlintを使い、細かい部分は手修正していくのが無難かなと思います。

設定ファイルでESM形式をサポート

https://prettier.io/blog/2023/07/05/3.0.0.html#support-config-files-in-esm-13130httpsgithubcomprettierprettierpull13130-by-fiskerhttpsgithubcomfisker

今まではCJS形式で書く必要があったconfigファイルでESM形式がサポートされたようです。但し拡張子が.jsである場合はpackage.json{"type": "module"}を指定する必要があります。

<!-- Prettier 2.8 -->
module.exports = {
  // ...
}

<!-- Prettier 3.0 -->
export default {
  // ...
};

trailingComma設定の初期値をes5からallに変更

https://prettier.io/blog/2023/07/05/3.0.0.html#change-the-default-value-for-trailingcomma-to-all-11479httpsgithubcomprettierprettierpull11479-by-fiskerhttpsgithubcomfisker-13143httpsgithubcomprettierprettierpull13143-by-sosukesuzukihttpsgithubcomsosukesuzuki

これは正直あまり良く解っていないのですが、Trailing Commasを読んだ感じ、恐らく関数の仮引数の最後にカンマが入るかどうかだと思われます。つまり以下のような感じです。

<!-- 入力値 -->
function HelloWorld(greeting = "hello", greeted = '"World"', silent = false, onMouseOver) {
}

<!-- trailingComma: es5 -->
function HelloWorld(
  greeting = "hello",
  greeted = '"World"',
  silent = false,
  onMouseOver // カンマが入らない
) {}

<!-- trailingComma: all -->
function HelloWorld(
  greeting = "hello",
  greeted = '"World"',
  silent = false,
  onMouseOver, // カンマが入る
) {}

上記の結果はこちらのPlaygroundで確認できます。(--trailing-commaの設定値をいじると比較できます)

個人的にはこの修正は余り歓迎できないですね…。というのも不要なカンマが入っていると引数の数を見間違えたり、ケアレスミスの原因になると思いますし、いたずらにコードが長くなるからです。

今まで深く考えたことがありませんでしたが、trailingCommanoneが例外がなく、統一感があり、コードが短くなり、ケアレスミスが減ると思うため無難だと思いました。noneにしている場合、単純に一番最後の要素にカンマが入らなくなります。

BabelパーサーからFlow構文のサポートを削除

https://prettier.io/blog/2023/07/05/3.0.0.html#remove-flow-syntax-support-from-babel-parser-14314httpsgithubcomprettierprettierpull14314-by-fiskerhttpsgithubcomfisker-thorn0httpsgithubcomthorn0

これによりPrettierの動作パフォーマンスが上がったようです。

デフォルトで.gitignoreのファイルを無視するように変更

https://prettier.io/blog/2023/07/05/3.0.0.html#ignore-gitignored-files-by-default-14731httpsgithubcomprettierprettierpull14731-by-fiskerhttpsgithubcomfisker

.prettierignoreではなく.gitignoreを見るようになったようです。これもなんとも微妙な変更だと思います…。どっちかといえば.eslintignoreを見てもらいたいような気もするような…。従前のようにしたい場合はCLIのオプションに--ignore-path=.prettierignoreを渡してやると行けるようです。

関連記事

Web開発をしていると取り敢えずHTTPリクエストが取れる雑なモックサーバーが欲しくなることがあるので、その作り方。

確認環境

Env Ver
OS Windows 11 Pro
WSL2 -
Distoribution Ubuntu 20.04.4 LTS
PHP 8.0.29
nginx 1.18.0

作り方

  1. Windows側の hosts127.0.0.1 mock-server.test を追加
  2. /etc/nginx/conf.d に以下の設定ファイルを置く
server {
  listen       80;
  client_max_body_size 100m;
  server_name  mock-server.test;
  access_log   /var/log/nginx/mock-server.access.log;
  error_log    /var/log/nginx/mock-server.error.log;


  location ~ ^/.*$ {
    rewrite ^/.*$ / break;
    # リクエストパスの確認用
    proxy_set_header X-Request-Path $request_uri;
    proxy_pass  http://127.0.0.1:8888;
  }
}
  1. sudo service nginx restart
  2. モックサーバーのコードを書いて適当な場所に置く
<?php

$server_json = json_encode($_SERVER);

file_put_contents('log', "$server_json\n", FILE_APPEND);
  1. php -S localhost:8888
  2. http://localhost:8888/hoge にアクセスしログファイルに追記されることを確認
  3. おわり

おまけ

$_SERVER の中身をフィルタしたい時

<?php

$ignoreKeys = [
    'DOCUMENT_ROOT',
    'REMOTE_ADDR',
    'REMOTE_PORT',
    'SERVER_SOFTWARE',
    'SERVER_PROTOCOL',
    'SERVER_NAME',
    'SERVER_PORT',
    'REQUEST_URI',
    'SCRIPT_NAME',
    'SCRIPT_FILENAME',
    'PHP_SELF',
    'HTTP_HOST',
    'HTTP_CONNECTION',
    'HTTP_UPGRADE_INSECURE_REQUESTS',
    'HTTP_USER_AGENT',
    'HTTP_ACCEPT',
    'HTTP_ACCEPT_ENCODING',
    'HTTP_ACCEPT_LANGUAGE',
    'HTTP_CACHE_CONTROL',
    'REQUEST_TIME_FLOAT',
    'REQUEST_TIME'
];

$buff = array_filter($_SERVER, function($key) use($ignoreKeys) {
    return !in_array($key, $ignoreKeys);
}, ARRAY_FILTER_USE_KEY);

$server_json = json_encode($buff);

file_put_contents('log', "$server_json\n", FILE_APPEND);

SCSS Moduleを使ったNext.jsと連携させてるStorybookを6.5から7.0に上げたときの手順とトラブルシューティング

環境移行内容

moduleのバージョンをbeforeからafterにアップデート

module before after
@storybook/addon-actions 6.5.13 7.0.24
@storybook/addon-essentials 6.5.13 7.0.24
@storybook/addon-links 6.5.13 7.0.24
@storybook/builder-webpack5 6.5.13 7.0.24
@storybook/manager-webpack5 6.5.13 6.5.16
@storybook/preset-scss 1.0.3 1.0.3
@storybook/react 6.5.13 7.0.24

やったこと

  1. storybook周りの最新化
npm i @storybook/xxx@latest && @storybook/yyy@latest ...
  1. sb devする
  2. エラーが出るので言われたとおりにnpx storybook@next automigrateを流す
  3. 適当に説明を読みながら進める
    1. 私のケースでは全部Yで問題ありませんでした
  4. storyファイルをs/storyName/name/で置換する
    1. なんか書式が変わったらしい
  5. .storybook/main.jsaddonsにある'@storybook/preset-scss'を削除
  6. ComponentStoryObjStoryObj に置換
  7. storybook devで起動確認
    1. storybook devで起動確認

トラブルシューティング

start-storybookが動かない

start-storybookが動かない

@storybook/cliを導入してstart-storybooksb devに置き換え

https://github.com/storybookjs/storybook/issues/18923#issuecomment-1214280920

sb devがコケる

sb devがコケる

書かれてる通りにnpx storybook@next automigrateを流す
何か色々変更点を教えてくれるので適宜読んで対処する

内容を読みつつ対処
内容を読みつつ対処

終わったらnpx storybook devを流せば起動します

Unexpected usage of "storyName" in "Example". Please use "name" instead.

storyNamenameに変える。

  export const Example: Story = {
-   storyName: 'ほげほげ',
+   name: 'ほげほげ',
  }

sass-loader が動かない

ModuleBuildError: Module build failed (from ./node_modules/sass-loader/dist/cjs.js):
SassError: expected "{".
  ╷
2 │       import API from "!../../../node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js";

.storybook/main.jsaddons'@storybook/preset-scss'がいたら消す

  module.exports = {
    stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'],
    addons: [
      '@storybook/addon-links',
      '@storybook/addon-essentials',
-     '@storybook/preset-scss',
    ],
    framework: {
      name: '@storybook/nextjs',
      options: {},
    },
    docs: {
      autodocs: true,
    },
  };

ゴミなので@storybook/preset-scssを消しておく

npm un @storybook/preset-scss

@storybook/nextjsに入ってるからなくて良くなったらしい

投稿日:
言語::JavaScriptジャンル::調査

関数の仮引数の書き方でオブジェクトの参照がどう変わるか気になったので試したメモ。別に仮引数でなくても分割代入なら何でも結果は同じになると思う。

これはプロジェクトのコーディング規約に定めがなく、無秩序な状態になっていると不具合を引き起こす因子になると思うので、どの方法を取るのか決めたほうが品質に寄与すると考える。個人的には引数由来であることが明示的であり、必ず参照を引きずるオブジェクト代入方式(後述)が好みだ。

結果

分割代入した結果プリミティブになると元オブジェクトとの参照が切れる

仮引数方式 参照
分割代入
スプレッド代入
オブジェクト代入

分割代入した結果、オブジェクトのままだと元オブジェクトとの参照が残る(Shallow copy)
再代入は参照が切れるので影響しない(再代入で参照アドレスが変わるためと思われる)

仮引数方式 参照
オブジェクト再代入
オブジェクト書き換え

確認コード

// 分割代入
const foo = ({ id, name }) => {
  id = 2;
  name = 'aaaa';
  console.log(id, name);
};

// スプレッド代入
const bar = ({ ...props }) => {
  props.id = 2;
  props.name = 'aaaa';
  console.log(props.id, props.name);
};

// オブジェクト代入
const baz = (props) => {
  props.id = 2;
  props.name = 'aaaa';
  console.log(props.id, props.name);
};

// オブジェクト再代入
const hoge = ({ id, name, dat }) => {
  id = 2;
  name = 'aaaa';
  dat = {
    value: 3,
  };
  console.log(id, name, dat);
};

// オブジェクト書き換え
const piyo = ({ id, name, dat }) => {
  id = 2;
  name = 'aaaa';
  dat.value = 3;
  console.log(id, name, dat);
};


const test1 = { id: 1, name: 'test' };
const test2 = { id: 1, name: 'test' };
const test3 = { id: 1, name: 'test' };
const test4 = { id: 1, name: 'test', dat: { value: 1 } };
const test5 = { id: 1, name: 'test', dat: { value: 1 } };


foo(test1);
console.log(test1);

bar(test2);
console.log(test2);

baz(test3);
console.log(test3);

hoge(test4);
console.log(test4);

piyo(test5);
console.log(test5);

結果

// 分割代入では呼び出し元が影響を受けていない
2 'aaaa'
{id: 1, name: 'test'}

// スプレッド代入では呼び出し元が影響を受けていない
2 'aaaa'
{id: 1, name: 'test'}

// オブジェクト代入では呼び出し元が影響を受ける
2 'aaaa'
{id: 2, name: 'aaaa'}

// オブジェクト再代入では呼び出し元が影響を受けていない
2 'aaaa' {value: 3}
{id: 1, name: 'test', dat: { value: 1}}

// オブジェクト書き換えでは呼び出し元が影響を受ける
2 'aaaa' {value: 3}
{id: 1, name: 'test', dat: { value: 3}}