d.sunnyone.org
sunnyone.org

ページ

2020-07-05

emotionの内部構造(CSS-in-JSの裏側)

書いたスタイルがどうなるかという観点でemotion(emotion-js) v10の内部構造を調べたので図にした。styled-componentsもざっと見たけど、だいたい同じような感じ。




emotionをReactと組み合わせて使う場合、基本的には以下の動きをする(SSRが入るとちょっと違う)
  1. css`` あるいは styled.hoge`` が、SerializedStyles (ほぼ書いたとおりの状態のスタイルを表現するオブジェクト)を作る。ハッシュ+ラベルでnameがつく。
  2. ラップするための「jsx」関数か「styled」のコンポーネントが、React Contextを使ってEmotionCacheを探し出して、SerializedStylesをinsertStyles()する。
  3. EmotionCacheが、stylisでSerializedStylesを加工する(実際に使われるスタイルはここでできる)。
  4. stylisプラグインが、StyleSheetオブジェクトを経由して、containerに指定された要素(デフォルトではdocument.head)にstyle要素を挿入する。
  5. 「jsx」関数か「styled」コンポーネントが、スタイル適用された要素をclassNameを渡すことで作る。
createCache()でEmotionCacheは作れるので、CacheProviderと合わせると木の途中からdocument.head以外の場所、例えばiframeの向こう側にもstyleを挿入することができる。

Reactでない他の枠組みと統合するときは、以下を作ることになるかなという感じ。
  • cacheをつくるところ
  • (SerializedStyleを作るところ→css`` をそのまま使うのでもあり)
  • cacheを見つけてinsertするところ

2020-06-30

virt-install + cloud-initでUbuntu 20.04をインストールする

Ubuntu 20.04 (focal)のVMをいつもの通りvirt-installと--locationを使ってセットアップしていたら、WARNINGに遭遇したので、気になっていたcloud-initでインストールしてみたところうまくいったのでまとめる。
sudo virt-install (略) --location 'http://ftp.jaist.ac.jp/pub/Linux/ubuntu/dists/focal/main/installer-amd64/' --extra-args 'console=ttyS0,115200n8 serial'
WARNING  Using legacy d-i based installer, that has been deprecated and will be removed in the future. https://discourse.ubuntu.com/c/server

概要

Ubuntu 20.04 ServerのVM「cloudtest」をUbuntu Cloud Imagesのイメージを使って、virt-installでセットアップする。 ディスクは、"vg0"というLVM volume groupにroot用, swap用のvolumeを作成して利用する。

LVMでボリュームの作成

lvcreateでボリュームを作る。
$ sudo lvcreate -n cloudtest-root -L 8G vg0
$ sudo lvcreate -n cloudtest-swap -L 4G vg0

イメージのダウンロードと展開

cloud-images.ubuntu.comからイメージをダウンロードする。QCOW2 Image (v2)形式らしいので、qemu-img convertを使って、rawイメージに変換しつつLVM volumeに流し込む。
$ wget https://cloud-images.ubuntu.com/releases/20.04/release/ubuntu-20.04-server-cloudimg-amd64.img
$ sudo qemu-img convert ubuntu-20.04-server-cloudimg-amd64.img -O raw /dev/vg0/cloudtest-root

cloud-init用の設定ファイルの作成

Ubuntu Cloud Imagesでは、インストーラでの設定入力ではなく、cloud-initで初期設定を行う。 NoCloudデータソースでは、設定内容をイメージから読んでくれるので、それを使う。そのイメージのための設定ファイルをまず用意する。 (詳しくは第561回 ローカルインストール時もcloud-initを活用する:Ubuntu Weekly Recipeを参照)

 今回の主な設定内容は以下の通り。
  • ホスト名
  • ユーザー名・初期パスワード
  • パーティションテーブルの作成
  • ファイルシステムの作成
  • マウントポイントの設定
  • ネットワーク
(その他の例はCloud config examplesを参照)

ファイルにするとこんな感じ。 ここで注意すべきなのは、networkは別ファイルだということと、disk_setupに罠があるということ。

 examplesによく出てくるdisk_setupのlayout: Trueは ドキュメントを読むと、
The layout option specifies how partitions on the device are to be arranged. If layout is set to true, a single partition using all the space on the device will be created.
とのことだが、新しめの環境とmbrでは機能しないので、Trueと同じように100%の領域を使いたい場合は以下のように指定する。
disk_setup:
  /dev/vdb:
    table_type: 'mbr'
    layout:
      - [100]
    overwrite: False
詳しくはBug #1851438 “cloud-init disk_setup fails to partition disk for ...” : Bugs : cloud-initを参照。

設定ファイルが並ぶ感じになると思うので、gitで並べる用のテンプレートを作った。hosts/{hostname}以下にconfigファイルを書いて./build-images.shするとbuild/にできる。 https://github.com/sunnyone/cloud-init-configs

cloud-init用のイメージの作成

cloud-init用のイメージの作成にはcloud-localdsコマンドが便利なので、cloud-image-utilsパッケージをインストールして使う。
$ sudo apt install cloud-image-utils
$ cloud-localds --network-config network-config.yaml user-data.img user-data

virt-installの実行

これで準備できたので、virt-installを実行してVMを作る。
sudo virt-install \
    --name cloudtest --ram 512 --arch x86_64 --vcpus 1 \
    --os-type linux --os-variant ubuntu20.04 \
    --disk path=/dev/vg0/cloudtest-root \
    --disk path=/dev/vg0/cloudtest-swap \
    --disk path=$PWD/user-data.img,device=cdrom \
    --network bridge=br0,model=virtio \
    --graphics none --serial pty --console pty \
    --import
しばらくしたら設定が完了して、sshでログインできる。

cloud-init設定のデバッグ

インストーラと違って対話的でないので、うまくいかないときは積極的なデバッグが必要。 基本的にはログを見ると良くて、ログは以下の場所にある。
  • /var/log/cloud-init-output.log
  • /var/log/cloud-init.log
詳しくはFAQを参照。 イメージごときれいにしなおしても良いが、/var/lib/cloudをきれいにするともう1度走らせることができる。
$ sudo rm -rf /var/lib/cloud/*

cloud-init用のイメージのeject

最後にuser-data.imgを外してあげておしまい。
$ sudo virsh change-media cloudtest sda --eject --config

2020-06-16

AWS Lambda + TypeScript の構成メモ

AWS Lambda の関数を Node.js + TypeScriptで書くとき、どのような構成にすると便利かまとめたメモ。デプロイにはAWS SAMを使うことを想定。

ディレクトリ構造

src/ts/functions/(スタック名)/(関数名).ts をwebpackのエントリポイントとして、以下のようなディレクトリ構造にする。
src/
  templates/        # CloudFormationテンプレート
  ts/
    functions/
      (スタック名)/
        (関数名).ts # エントリポイント
    cli/
      (関数).ts     # コマンドラインでの動作確認
    lib/            # 共通コード群(AWS Lambdaのランタイムに依存しない部分)
      foo/
        bar.ts
スタック名があるのは、デプロイパッケージ(zip)をスタック単位にすることを想定。デプロイツールの都合でここは調整したほうがいいかも。

コマンドラインで動作確認できるようにする

AWSのリソースを操作する系のLambda関数を実装する場合、その処理をコマンドラインで実行できるようにしておくと便利。 cli/にコードを配置し、ts-nodeを使ってpackage.jsonの"scripts"で呼び出せるようにしておく。
  "scripts": {
    "cli:my-operation": "ts-node src/ts/cli/my-operation.ts",
  }
コードとしては、yargsでコマンドライン引数を解析して、関数に渡すだけのイメージ。
$ yarn cli:my-operation -a my-arg

webpack設定

node_modulesを持っていけば動くのだが、productionのみにしたりと面倒なので、webpackする。 例としては、こんな感じ。 ポイントは、externals: ["aws-sdk"]。標準のものやLambda Layerで提供しているものはここで除外する。ネイティブモジュールは、Lambda Layerで提供すると良いと思う。minimize: falseはコンソールで読む用。どっちでもいい。

tsconfig.json

Node v12のときはtarget: es2019。moduleはcommonjs。

source-map-supportを入れる

webpackするとスタックトレースが不便になるので、source-map-supportを入れる。
yarn add -D source-map-support @types/source-map-support
して、各エントリポイントファイルの先頭で
import "source-map-support/register";
する感じで。

必要に応じてnpm moduleにしてシェア

CloudFormation templateと、ビルドしたjsをnpm moduleにしてnpm registryにpublishしておくと、同一構成を作りやすくて便利。

2020-05-29

Drawio Desktop を使ってコマンドラインで図を画像に変換する

Webブラウザで使えるオープンソースのドローツールdraw.ioは便利なのだけど、図を描いたあと他フォーマットに変換のがちょっと面倒なので変換する話。

drawio-desktopをインストールする

draw.ioにはブラウザ版の他に、Electronで作られたdrawio-desktopというのがある。まずこれをインストールする。snapだと簡単。

# snap install drawio

drawioコマンドで変換する

drawio-desktopの実体であるdrawioコマンドには、コマンドラインオプションを指定すると指定フォーマットでエクスポートする機能がついているので、これを使って変換する。

$ drawio -x -f png *.drawio
シェルスクリプトにしておくと便利。ちなみにデフォルトはpdfらしい。

ヘルプを見るとこんな感じ。
$ drawio --help
Usage: drawio [options] [input file/folder]

Options:
  -V, --version                      output the version number
  -c, --create                       creates a new empty file if no file is passed
  -k, --check                        does not overwrite existing files
  -x, --export                       export the input file/folder based on the given options
  -r, --recursive                    for a folder input, recursively convert all files in sub-folders also
  -o, --output   specify the output file/folder. If omitted, the input file name is used for output with the
                                     specified format as extension
  -f, --format               if output file name extension is specified, this option is ignored (file type is determined from
                                     output extension, possible export formats are pdf, png, jpg, svg, vsdx) (default: "pdf")
  -q, --quality             output image quality for JPEG (default: 90)
  -t, --transparent                  set transparent background for PNG
  -e, --embed-diagram                includes a copy of the diagram (for PNG format only)
  -b, --border               sets the border width around the diagram (default: 0)
  -s, --scale                 scales the diagram size
  --width                     fits the generated image/pdf into the specified width, preserves aspect ratio.
  --height                   fits the generated image/pdf into the specified height, preserves aspect ratio.
  --crop                             crops PDF to diagram size
  -a, --all-pages                    export all pages (for PDF format only)
  -p, --page-index        selects a specific page, if not specified and the format is an image, the first page is selected
  -g, --page-range ..      selects a page range (for PDF format only)
  -h, --help                         display help for command

2020-02-27

yalcを使って開発中のnpmパッケージを手元で確認する

ライブラリとして分離したnpmパッケージを作るときは、シンプルなものであればyarn linkでローカルファイルを参照しながら動作確認ができるのだけど、依存によってはややこしい問題が起きたりする。

例えばReactを使うライブラリだとこんなエラーが起きたりする。
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.

そこで登場するのがyalcというツール。yarn linkはざっくり言うとライブラリ側のディレクトリへのシンボリックリンクをアプリ側のnode_modules以下に配置するのに対して、yalcを使うとローカル(~/.yalc)にpublishしてそこをpackage.jsonで見る形にできるので、npm registryにpublishされたものを使うのに近い状態にすることができる。

使い方

1. yalcのインストール

yalc自体をインストールする。
$ yarn global add yalc

2. publish

ライブラリ側でyalc publishする。
$ cd ~/proj/mylib
$ yarn build とか
$ yalc publish

3. パッケージ追加

アプリ側でyalc addする。
$ cd ~/proj/myapp
$ yalc add @myorg/mylib
$ yarn
これで使えるようになる。package.jsonにはこんな感じのエントリが追加されている。
    "@myorg/mylib": "file:.yalc/@myorg/mylib",

4. 内容更新

中身を変えたら publish と update をする。
$ cd ~/proj/mylib
$ yarn build とか
$ yalc publish
$ cd ~/proj/myapp
$ yalc update

5. yalc remove

yalc remove --allしておしまい。
$ cd ~/proj/myapp
$ yalc remove --all
$ yarn
詳しくはyalcのGitHub (https://github.com/whitecolor/yalc)を参照。

2020-02-12

tslint:disable を eslint-disable にするツールを雑に作った

TSLintからESLintに移行するには、
// tslint:disable-next-line:no-floating-promises
なんかのコメントを、
// eslint-disable-next-line @typescript-eslint/no-floating-promises
なんかに変える必要があるのだけど、ある程度機械的にできるんじゃないかと思って作った。
https://github.com/sunnyone/tslint-comment-to-eslint

使い方

使い方はこんな感じ。直接置き換えるのでgitなりできれいな状態にして実行を。
$ yarn global add tslint-comment-to-eslint
$ tslint-comment-to-eslint 'src/**/*.ts'

仕掛け

仕掛けとしては、tslint-to-eslint-config にMapが入っていたのでrequireして引いてきて、そのMapを使って正規表現置換で変えただけ。だからルール名は変わるけど、ルールの使い方が違っているとアウト(例えばmax-classes-per-fileをdisable-next-lineするには、TSLintだとそれを2回目のclassの行に使うけど、ESLintだと1行目に使う、とか)なので確認は必須。調整前にベースに使えればどうぞ。