nodejsのsemverまとめ

  • このエントリーをはてなブックマークに追加

semverの解釈がエコシステムによって異なったりする場合があるので、nodejs(npm)の場合を備忘録メモ。

3桁の数字で表現:x.y.z

  • 最初の桁はメジャーバージョン
  • 2桁目はマイナーバージョン
  • 3桁目はパッチバージョン

バージョンの上げ方

  • 互換性のないAPIを変更する場合、メジャーバージョンをアップ
  • 下位互換性のある方法で機能を追加する場合、マイナーバージョンをアップ
  • 下位互換性のあるバグ修正を行う場合、パッチバージョンがアップ

バージョン表記の記号

  • ^: 一番左にあるゼロ以外の数値を変更しない

    • ^1.2.3 => 1.2.3 <= version < 2.0.0
    • ^0.13.0 => 0.13.0 <= version < 0.14.0
    • 0.xの場合とそれ以外とでルールが異なるので注意。
  • ~: 一番右のバージョンがあがる

    • ~1.2.3 => 1.2.3 <= version < 1.3.0
    • ~1.2 => 1.2.0 <= version < 1.3.0, 1.2.xと同じ。
    • ~1 => 1 <= version < 2, 1.xと同じ。
  • = ピンポイントでそのバージョン

    • =1.2.3 => 1.2.3
  • - レンジ

    • 1.2.3 - 1.2.4 => 1.2.3 <= version <=1.2.4
    • 両端とも含みます
  • .xという表記もできるようだ

その他

  • 記号なしの場合は、ピンポイントでそのバージョン。(=と同じかな)
  • latest 最新のバージョン。(正確にはTag指定で、npmレポジトリにlatestというタグが存在するから)

複数指定

ルールが分かりづらい場合、分解して書いた方が分かりやすい

  • 1.2.3 - 1.2.5
  • >=1.2.3 <1.3.0
  • <1.0.0 || >2.3.4 || >=3.0.0 <4.0.0

確認する方法

npm semver calculator というツールでオンラインで検証できます。

参考情報

package.jsonでgithubを直接参照する

  • このエントリーをはてなブックマークに追加

通常は、semverを指定していますが、npmのtag, 他にもhttp(s)から始まるURL、GitのURLや、GithubのProject/Repo、Localファイルのパスを指定することができます。

package.json
{
"dependencies": {
"hexo-renderer-ejs": "^1.0.0",
"hexo-renderer-marked": "git+https://github.com/tamtam180/hexo-renderer-marked.git#feature/use-class-table-align"
}
}

npmのtag

"example1": "latest"
"example2": "next"

URL

"example": "http://example.com/"

Git Url

"example1": "git+https://github.com/tamtam180/hexo-renderer-marked.git#feature/use-class-table-align"
"example2": "git+ssh://github.com/tamtam180/hexo-renderer-marked.git#feature/use-class-table-align"

#でブランチやHash指定が可能。

git+ssh://git@github.com:npm/cli.git#v1.0.27
git+ssh://git@github.com:npm/cli#semver:^5.0
git+https://isaacs@github.com/npm/cli.git
git://github.com/npm/cli.git#v1.0.27

Github Url

"example1": "tamtam180/hexo-renderer-marked.git#feature\/use-class-table-align"

ブランチのスラッシュはエスケープが必要?

Localファイル

サンプル
../foo/bar
~/foo/bar
./foo/bar
/foo/bar

参考情報

ESLintのno-unused-varsを特定の箇所だけ無効にしたい

  • このエントリーをはてなブックマークに追加

例えば次のようなクラスを定義したい場合、processcontextが未使用なのでESLintにno-unused-varsの警告を受けます。このルールを部分的に無効にする方法にはいくつかあります。

Base
class Base {
constructor() {
}
execute() {
const context = {}
this.process(context)
}
// eslint-disable-next-line no-unused-vars
process(context) {
// override
}
}

特定の次の行を無効にする

eslint-disable-next-line

// eslint-disable-next-line no-unused-vars
const myVars = 1;

同じ行を無効にする

eslint-disable-line

const myVars = 1; // eslint-disable-line no-unused-vars

範囲を無効にする

/* eslint-disable no-unused-vars */
const myVars1 = 1;
const myVars2 = 1;
const myVars3 = 1;
/* eslint-enable no-unused-vars */

ファイル全体を対象に無効にする

ファイルの先頭に次のブロックコメントを記載します。

/* eslint no-unused-vars: 0 */

注意点として、/**/のコメント記法を利用することです。
また、指定している数字は、0=”off”, 1=”warn”, 2=”error”の意味です。

参考

ファイルをドロップする要素が子要素を持っているとDragLeaveが発生してしまう

  • このエントリーをはてなブックマークに追加

Drag and Dropを実装する時に、ドロップする要素が子要素を持っていると、DragLeaveが発生してしまう。
子要素(別要素)にEnterしたということは、対象の要素がLeaveしたということになるらしいです。

対策の方法は色々とありますが、カウンタを用意するのが一番簡単です。
ひとまず、Vue3+CompositionAPIでサンプルを。

template
<div :class="{enter: ddCounter>0}"
@dragenter="fileDragEnter"
@dragleave="fileDragLeave"
@dragover.prevent
@drop.prevent="fileDrop"
>
</div>
script
import { defineComponent, ref } from 'vue'
export default defineComponent({
setup() {
const ddCounter = ref(0)
const fileDragEnter = () => { ddCounter.value++ }
const fileDragLeave = () => { ddCounter.value-- }
const fileDrop = (event) => {
ddCounter.value = 0
}
return {
ddCounter,
fileDragEnter,
fileDragLeave,
fileDrop,
}
}
})
style
.enter {
border: 5px dotted powderblue;
}

サンプルコード ▶ Vue SFC Playground

参考

MySQLのJSONからboolean型のGenerated Columnを定義する時の注意点

  • このエントリーをはてなブックマークに追加

確認したバージョンは、MySQL8.0.25。
JSONのtrue/falseを参照してMySQLのboolean(tinyint)にする時、ついうっかりvarchar等と同じ感覚でjson_unquoteをするとエラーになる。

テスト用の定義

create table test1 (
id bigint not null,
data json not null,
name varchar(32) as (json_unquote(data->'$.name')) stored,
flag1 boolean as (json_unquote(data->'$.flag1')) stored, -- ※間違い
flag2 boolean as ((data->'$.flag2')) stored,
constraint pk_test1 primary key(id)
);

間違い

insert into test1 set id = 1, data = '{"name":"hello", "flag1": true, "flag2": true}';

flag1列でエラー。boolean列にjson_unquoteしているから。

ERROR 1366 (HY000): Incorrect integer value: 'true' for column 'flag1' at row 1

正しい

flag2はjson_unquoteをしていないので成功する。

insert into test1 set id = 1, data = '{"name":"hello", "flag2": true}';
insert into test1 set id = 2, data = '{"name":"hello", "flag2": false}';
select * from test1;
+----+-----------------------------------+-------+-------+-------+
| id | data | name | flag1 | flag2 |
+----+-----------------------------------+-------+-------+-------+
| 1 | {"name": "hello", "flag2": true} | hello | NULL | 1 |
| 2 | {"name": "hello", "flag2": false} | hello | NULL | 0 |
+----+-----------------------------------+-------+-------+-------+

型は何になっている??

MySQLにはpg_typeofみたいなものはないので、Selectの結果でTEMPテーブルを作って確認します。

create temporary table tmp1
select
(json_extract('{"name":"hello", "flag1": true}', '$.flag1')) as c1,
(json_unquote(json_extract('{"name":"hello", "flag1": true}', '$.flag1'))) as c2
;

desc tmp1;
+-------+----------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+----------+------+-----+---------+-------+
| c1 | json | YES | | NULL | NULL |
| c2 | longtext | YES | | NULL | NULL |
+-------+----------+------+-----+---------+-------+

select * from tmp1;
+------+------+
| c1 | c2 |
+------+------+
| true | true |
+------+------+

json_unquoteしたものはlongtextのようです。
つまり、jsonからtinyintへの変換は成功するが、longtextからtinyintへの変換は失敗するということになります。

確認してみます。

select cast(json_extract('{"name":"hello", "flag1": true}', '$.flag1') as signed) as x;
+------+
| x |
+------+
| 1 |
+------+
1 row in set (0.00 sec)

対して、

select cast(json_unquote(json_extract('{"name":"hello", "flag1": false}', '$.flag1')) as signed) as x;
+------+
| x |
+------+
| 0 |
+------+
1 row in set, 1 warning (0.00 sec)

駄目な上に警告がでています。

 show warnings;
+---------+------+--------------------------------------------+
| Level | Code | Message |
+---------+------+--------------------------------------------+
| Warning | 1292 | Truncated incorrect INTEGER value: 'false' |
+---------+------+--------------------------------------------+
1 row in set (0.00 sec)

検証の単純化

-- OK
select cast(true as signed);
select cast(cast('true' as json) as signed);

-- NG
select cast('true' as signed)

そんなわけで、JSONのbooleanを扱うときは、気をつけましょうということで。

MySQL8でOut of sort memoryが発生するようになった

  • このエントリーをはてなブックマークに追加

MySQL8.0.21から8.0.25にアップグレードした後、次のような単純なクエリを発行したらERROR 1038 (HY001): Out of sort memory, consider increasing server sort buffer sizeが発生するようになった。

SELECT *
FROM hoge_table
ORDER BY created_at DESC LIMIT 10

このテーブルは次のように、JSONカラムとそれを参照する大量のGeneratedカラムを持つテーブルです。

create table hoge_table(
id varchar(32) ascii not null,
data json not null,
c1 varchar(256) as (json_unquote(data->'$.c1')) stored,
c2 varchar(256) as (json_unquote(data->'$.c2')) stored,
c3 varchar(256) as (json_unquote(data->'$.c3')) stored,
.... 20カラムくらい,
constraint pk_hoge_table primary key (id)
) default charset utf8mb4 row_format=compressed key_block_size=4;

Bug Ticket:103225に情報があります。

対策

sort_buffer_sizeが256KBなので1Mに増やしました。innodb_sort_buffer_sizeではなくsort_buffer_sizeです。私は幸いにして、これで回避できました。

オンラインで変更

set global sort_buffer_size = 1*1024*1024;
# 変わらない場合は..
set @@sort_buffer_size = 1*1024*1024;

show variables like '%sort%';
/etc/mysql/mysql.conf.d/mysqld.cnf
[mysqld]
sort_buffer_size = 1M

参考

RubyのDatadog ClientからMetricsが記録されない場合がある

  • このエントリーをはてなブックマークに追加

dogstatsd-rubyを利用しています。
バージョン5から、Client側が内部バッファを持つようになった影響で、終了時には明示的にflushしないとバッファの内容が残ったままプログラムが終了し、Metricsが喪失します。

auto-closeの処理が入っていると思いきや、一瞬だけ入ったのですが、すぐにrevertされました。

This feature is introducing way too much complexity for applications using forks: since the auto-close feature use a global list to store the instances list, it could lead to a fork dying trying to close an instance which has been created before the fork.
It’s not wrong, but it’s complexifying the thinking process. It is better and important to manually close instances when they are not needed anymore (they create a socket and a thread, not closing the instance would leak these resources).

プログラムをForkする場合に、すごく複雑になるから。だそうです。

そんなわけで、自前で簡単に処理するように。

require "datadog/statsd"
datadog = Datadog::Statsd.new()
at_exit {
datadog.flush(sync: true) if datadog
}

v4と同じ挙動である、クライアントでバッファリングしないようにする事も可能です。その場合は明示的にflushする必要はありません。(大量にMetricsを送る場合は非推奨)

require "datadog/statsd"
datadog = Datadog::Statsd.new(single_thread: true, buffer_max_pool_size: 1)

参考

HTMLタグで属性が重複したときの挙動について

  • このエントリーをはてなブックマークに追加

次のタグのように属性が重複している場合、パーサーはどのように処理されるのだろうか。
もともと、Nokogiriを使ってパースしていて属性が取れないぞとなって調べたのでメモ。

<div id="s" class="a" class="b"></div>

結論から言うと、先に出現した属性が優先され、後続の重複した属性は無視されます。
Firefox, Chrome, Gumbo-parser, Nokogiri(sax-parser)などで確認しました。

仕様

WHATWGの仕様はこちらにあります。

There must never be two or more attributes on the same start tag whose names are an ASCII case-insensitive match for each other.

https://html.spec.whatwg.org/multipage/syntax.html

仕様的に、2回以上含めることはパースエラーとなる。

This error occurs if the parser encounters an attribute in a tag that already has an attribute with the same name. The parser ignores all such duplicate occurrences of the attribute.

https://html.spec.whatwg.org/#parse-error-duplicate-attribute

しかし、現実的にパースエラーにすると色々と困るので、エラーとして停止する/しないを選択することができ、殆どのブラウザはHTML(not XMLを解釈する時には、重複属性の時は最初の値を採用し、後続に出現した重複属性は無視をする、という選択をしているようです。

参考

Vue3でAタグをクリックした時に遷移させないで自前の処理をする

  • このエントリーをはてなブックマークに追加

Vue3にて、例えば、AタグをButton代わりに使う場合、こんな感じで書きたいわけですが、HashベースのRouterを使っている場合、#で遷移が発生してしまいます。

<a href="#" @click="linkClick" class="btn btn-outline-primary">link</a>

なのでこう書きます。

<a href="#" @click.prevent.stop="linkClick" class="btn btn-outline-primary">link</a>

stopでclickイベントの伝播が止まり、preventで既定のアクションを処理しない。
例えばformのsubmitをさせない場合はpreventを指定する(<form @submit.prevent="mySubmit">)

参考

Vue3でBootstrapとBootstrap Iconを利用する

  • このエントリーをはてなブックマークに追加
npm install --save bootstrap bootstrap-icons

bootstrapをcustomする

main.js
import 'bootstrap'
import '@/scss/custom.scss'

bootstrapの変数を上書きしてからbootstrapのscssをimportすると反映される。

scss/custom.scss
$body-bg: #000;
@import "node_modules/bootstrap/scss/bootstrap";

bootstrap iconsを利用する

main.js
import 'bootstrap-icons/font/bootstrap-icons.css'

iconsのcssをimportしてから、

App.vue
<i class="bi bi-file-bar-graph"></i>

参考