C++ ときどき ごはん、わりとてぃーぶれいく☆

USAGI.NETWORKのなかのひとのブログ。主にC++。

electron と node_modules と client-side library と electron-builder と symlink と .asar についての Windows での挙動についてのメモ

状況

  • Electron アプリを作っています。
  • yarn/npm で client-side 向けの library X (仮) を追加しています。
  • X は開発環境では yarn/npm により node_modules/X ディレクトリーに取り込まれています。
  • Electron アプリの client-side の static なファイル資源は public/lib/ ディレクトリーに配置したいお気持ちです。
  • 主なデプロイターゲットと開発環境は Windows です。

ここで、

となる場合に symlink を使うとどうなるのか、です。

symlink を使うとどうなるのか?

# zsh/arch/WSL2
# ; cmd 使いなら mklink -D を使い、 powershell 使いなら New-Item -ItemType SymbolicLink を使います。たぶん。
cd public/lib
ln -s ../../node_modules/X
file X
X: symbolic link to ../node_modules/X

直接 electron . → symlink もそのまま動作🙆‍♀️

  • 直接 electron . 的にアプリを起動する場合は symlink で作成したファイルまたはディレクトリーはElectronを介したアプリ client-side からの参照に対して通常のファイルと変わりない挙動を示します。たぶん、 NTFS と OS のファイルシステムに依存した挙動と思いますが、Windows開発環境で困る事は怒ら無いようです。

electron-builder → copy が .asar に入って動作🙆‍♀️

  • electron-builder でリリース用のパッケージを作成する場合は symlink で作成したファイルまたはディレクトリーは copy が .asar に入り、できあがった .exe での Electron を介したアプリ client-side からの参照に対して結果的には symlink を辿った先にあったファイルそのものと変わりない挙動を示します。

↑ここはこのメモの本質的な挙動確認の対象部分です。結果としてこの目的においては理想的な期待動作となる事がわかり安心が1つ増えました。 electron-builder を使うと symlink が相対パスを記したテキストファイルに化けてしまうとか何かしら問題が起こるのではないかと疑い、そのような挙動を示した場合には symlink で手軽に済ませるのはやめようと考えながらの確認でしたが、珍しく嬉しい誤算の期待動作に遭遇しました。のメモです。

winget へ choco または他の何かから可能な範囲で移行するメモ

winget を導入

f:id:USAGI-WRP:20200520104607p:plain

winget <- choco 移行例: Node.js

  1. 既存の別の何かで入れてある Node.js はそれぞれの方法で手アンインストール
    • sudo cuninst node ; choco の場合
    • 手導入してある場合も Windows の Apps & features から手アンインストール
  2. winget で導入
    • winget install node

winget install node の場合、 CUI でパッケージのダウンロードが進むとインストール経過は GUI のウィンドウが勝手に出てきて進みます。放っておくと(たぶん)成功で完了して CUI に制御が戻ります。

f:id:USAGI-WRP:20200520105632p:plain

f:id:USAGI-WRP:20200520110013p:plain

いまのところは残念ながら、途中で GUI のウィンドウが"しゃしゃり出て"来てしまうので winget install をバックグラウンドで実行させつつ「ながら」で別作業を並行みたいなかっこいい使い方はできません。

また、既にインストール済みか一切チェックしやがらない現代のパッケージマネージャーとしてはわりとおばかさんな仕様のため、 winget install node が成功した状態で winget install node するとまったく同様にインストールを繰り返します。これは依存パッケージの仕組みやある目的のためにまとめてあれこれを導入したい場合にすっごい無駄です。

おまけメモ

↑では突然winget installしましたが、教科書的な手順としては winget search node, winget show node など確認してから winget install node です。

f:id:USAGI-WRP:20200520104935p:plain

f:id:USAGI-WRP:20200520115440p:plain

所感としては「実用するにはまだはやいけれど期待はしたい」です。Issueがたくさんあがっていますし、パッケージマネージャーとしてのコア機能部分でまだ不足が多いので、現在はパッケージが充実したとしてもパッケージマネージャーとしての機能性からは choco の方が便利でいますぐ移行した方がよいとは言えません。来週には既にそうではなくなっているかもしれませんし、①年後には「そんなプロジェクトもそういえばありましたね、ハハッ」になっているかもしれません。

Electron アプリに electron-store で設定ファイル機能を追加する場合の example & tips 的なメモ

GitHub - sindresorhus/electron-store: Simple data persistence for your Electron app or module - Save and load user preferences, app state, cache, etc を使うメモです。

public/conf.js とか適当に作る例:

const path = require("path");
const store = require("electron-store");

const store_conf = {
 // https://www.npmjs.com/package/electron-store#schema
 schema: {
  // 'foo'でアクセスできる
  foo: {
   type: typeof 0,
   maximum: 100,
   minimum: 1,
   default: 50,
  },
  // 'bar'でアクセスできる
  bar: {
   type: typeof "",
   format: "url",
   default: "https://example.com/",
  },
  // 'window' でアクセスできる Object
  window: {
   type: typeof {},
   // Object は明示的に default: {} を与えておかないと子要素のdefaultがあっても取り出せないとか起こるので注意します
   default: {},
   // Object の子要素は properties で定義します
   properties: {
    // 'window.width' でアクセスできる
    width: {
     type: typeof 0,
     maximum: Number.MAX_SAFE_INTEGER,
     minimum: 480,
     default: 960,
    },
    // 'window.height' でアクセスできる
    height: {
     type: typeof 0,
     maximum: Number.MAX_SAFE_INTEGER,
     minimum: 270,
     default: 540,
    },
   },
  },
 },
 // https://www.npmjs.com/package/electron-store#migrations
 migrations: {},
};

module.exports = new store(store_conf);
  • Note:
    • schamaJSON で表現できるけれど、JSONで分離すると key にわざわざ " を付けなければならないとか Number.MAX_SAFE_INTEGERtypeof ''のような定義方法を使えない制限が生じて不便かもしれません。分離する場合も .js にしておくと便利が良さそうです。
    • schema に Object を入れ、その子要素へ default を設定したい場合は親となる Object で default: {} しておかないと子要素の default を取り出せなくなるので注意する必要があります。

public/conf.js を使う例( public/electron.js エントリーポイントを想定 ):

const electron = require("electron");
const app = electron.app;
const browser_window = electron.BrowserWindow;

const conf = require("./conf.js");

let main_window;

function createWindow() {
 app.allowRendererProcessReuse = false;

 main_window = new browser_window({
  width: conf.get("window.width"),
  height: conf.get("window.height"),
  webPreferences: { preload: `${__dirname}/preload.js` },
 });

 // (...abbr...; 後略)

Electron アプリに electron-localshortcut で非グローバルでMenuのアクセラレーターも使わないキーバインドを追加するメモ

準備:

実装例(public/electron.js):

const electron = require("electron");
const app = electron.app;
const browser_window = electron.BrowserWindow;

const local_shortcut = require("electron-localshortcut");

let main_window;

function createWindow() { 
  // (...abbr...; 中略)
}

// (...abbr...; 中略)

app.on("ready", () => {
 createWindow();
 local_shortcut.register(main_window, "F12", () =>
  main_window.webContents.toggleDevTools()
 );
});

// (...abbr...; 後略)

参考

  • javascript - Electron does not listen keydown event - Stack Overflow
    • Note: 今回のメモの直接の参考です。
  • Keyboard Shortcuts | Electron
    • Note: 公式情報として参考にはなるけれど electron-localshortcut を使う方法は紹介されていません。グローバルショートカット(ウィンドウが非アクティブでも反応する)、Menuのアクセラレーター機能(メニューに項目を作る必要が生じる)、レンダラープロセス側でにゃんにゃん(若干面倒&今回ケースの用途では直接レンダラープロセス側にコードをごりごりするのは微妙に美しくない気がしたので今回は↑の方法へ)

Electron アプリに i18next を追加して i18n/l10n 対応するメモ

基本的な使用の流れ

  1. yarn add i18next i18next-node-fs-backend
  2. public/i18n.js など適当に作り
  3. l10n 文字列を取り出したいソースで const i18n = require('./i18n.js') して
  4. 必要なら await i18n.changeLanguage('ja-JP') のように l10n 先を実行時に設定する仕込みをして
  5. i18n.t('hoge') とすると i18next で l10n した文字列を取り出せます
  6. i18next の options で渡す loadPathJSON{ "hoge": "ほげ" } のように key-value 的に l10n リソースを定義追加します。saveMissingtrue にしておけば addPath で与える missing 用のファイルへ l10n リソースが未定義で実行時に要求された文字列のリストが保存されます。

public/i18n.js

  • Note
    • ファイル名や配置はお好みで。
    • optionsConfiguration Options - i18next documentation を見て適当に。
    • fallbackLng を定義すると、 missing による add 動作先も fallbackLng になりました。未設定にしておけば missing による add は dev.missing.json のように addPath で与える {{lng}}dev になります。
const i18n = require("i18next");
const i18n_backend = require("i18next-node-fs-backend");
const path = require("path");
// const is_dev = require("electron-is-dev");

const options = {
 // debug: is_dev,
 lng: "en-US",
 initImmediate: false,
 backend: {
  loadPath: path.resolve(__dirname, "..", "i18n/{{lng}}.json"),
  addPath: path.resolve(__dirname, "..", "i18n/{{lng}}.missing.json"),
  jsonIndent: 2,
 },
 saveMissing: is_dev,
 // fallbackLng: "en-US",
 whiteList: [
  "en-US",
  "ja-JP",
  //, "ko-KR"
  //, "zh-CN"
  //, "zh-TW"
  //, "zh-HK"
 ],
};

i18n.use(i18n_backend);

if (!i18n.isInitialized) i18n.init(options);

module.exports = i18n;

i18n/ja-JP.json の例

  • Note: ファイル名と配置は i18next へ与える options で任意に設定できるのでお好みで。
{ "hoge": "ほげ" }

参考

Inkscape-1.0: 新規作成されるSVGドキュメントの背景色のデフォルト値を変える方法; ダークモードのテーミング&defaultドキュメント背景の設定方法のメモ

Inkscape-1.0 では Theme 機能が実装されたので、 Edit -> Preferences -> Interface -> Theme を設定するとダークモードに優しいテーミングができるようになりました。嬉しいです。しかし、闇の世界に住む私たちの目にInkscapeの画面で最も眩しくて困る部分はドキュメント本体の背景色です。白い画面は疲れます。真っ黒なのも黒線が完全に埋没してしまいますし、初期値は暗い灰色が好みです。これはいまのところ Preferences からではなく、ドキュメント新規作成時のテンプレートを直接変更する必要があります。

  • C:\Program Files\Inkscape\share\inkscape\templates (Windowsのデフォルトのインストールパスを用いている場合)

default.svg (InkscapeのUIをen-USで使用している場合) があるので、管理者権限の適当なテキストエディターで開いて svg/sodipodi:namedview[pagecolor]#FFFFFF からお好みの色へ変更して保存します。

GNU/Linuxでは /usr/share/inkscape/... とかそのあたりに templates があるかもしれません。 en-US 以外の言語をInkscapeのUIに設定している場合は default.ja.svg (日本語用) のように言語が付いたバージョンの templates/default.??.svg を編集します。

f:id:USAGI-WRP:20200506063817p:plain

心穏やかに闇のInkscapeを使えるようになります👍

参考

Blender: 正規表現でがさっと Vertex Group(s) をタゲって統合しつつ元は削除して置き換えられる Scripting: usagi/blender-merge-vertex-groups.py のメモ

前回のメモで使用した p2or/blender-merge-vertex-groups.py を元に:

  1. 正規表現 regex 設定で Vertex Group(s) をまとめてタゲれて
  2. 統合元になった Vertex Group(s) は削除する replace モードを True/False 設定できて
  3. 統合先を明示的な名付け to またはそれが未設定なら "+".join できて
  4. 削除と作成について簡単なログを吐いてくれる
  5. dryTrue/False して dry-run (実際には操作しないけど今の設定で実行するとこれこれが remove でこんな merge ができるよ、を表示だけ)してくれる (2020-04-14追記)

ように改造した Fork:

を作りました💪 便利ですよ✨

f:id:USAGI-WRP:20200413230555p:plain

f:id:USAGI-WRP:20200413230529p:plain

連続で複数パターンを操作する場合は add-on にするならちゃんとGUIを付けないと使いにくそうだし、 Scripting で regex, replace, to を逐次書き換えながら使ってね、でもとりあえずの便利には十分かなって思います。

(2020-04-14追記:↓dry-run)

f:id:USAGI-WRP:20200414011825p:plain