-->

Node.js

[Node.js][TypeScript] プライベートなモジュールの共有でモジュールを作ってみたんだけど、これだとソースコードが1つのファイルにしないといけないことになってしまう・・・

普通は、クラスごととかにわけて複数ファイルで構成されているもんだろうから、それをやってみる。

例として、複数ファイルで構成されているモジュールをcommonという名前で作る。

commonディレクトリを作って、そこでnpm initを実行して、package.jsonを適当に作る。
このモジュールは公開しないので念のためprivateフィールドをtrueで追加する。TypeScriptの場合、typesフィールドを追加して、モジュールの型情報ファイルを指定する。

{
  "name": "common",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "types": "index.d.ts",
  "private": true,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

つぎにTypeScriptの準備。

tsc --initを実行して、tsconfig.jsonを作成する。tsconfig.jsoでdeclarationをtrueにするとtsc実行時に型情報ファイルも生成されるようになるので指定する。

複数のソースファイル、Hoge1.ts、Hoge2.tsを用意する。

Hoge1.ts

export class Hoge1 {
    public name:string = "hoge1";
}

Hoge2.ts

export class Hoge2 {
    public name:string = "hoge2";
}

export class Hoge3 {
    public name:string = "hoge3";
}

package.jsonでmainとtypesに1つのファイルしか指定できないんで、index.tsでHoge1.ts、Hoge2.tsをインポートしてそれをエクスポートするようにする。型情報ファイルは、tsc実行で出力するように設定したのでそれを使う。

index.ts

export { Hoge1 } from "./Hoge1";            // Hoge1のクラスHoge1をエクスポートする。
export * from "./Hoge2";                    // Hoge2でエクスポートしているものを全部エクスポート
export { Hoge1 as Hoge4} from "./Hoge1";    // Hoge1のクラスHoge1をHoge4としてエクスポート

これで、tscを実行して、index.js、index.d.ts 生成する。

これでモジュールのほうは完成。

次は使う方だ。

commonディレクトリと同列(隣)にtestと言うディレクトリを作って、そこを使うことにする。 npm inittsc --initでpackage.json、tsconfig.jsonを作成する。

npm install ../common --save

を実行して、commonモジュールをインストールする。testプロジェクトのnode_modulesフォルダにシンボリックリンクされる。

commonモジュールを使うコードはこんな感じになる。

test.ts

import * as t from "common";

const hoge1 = new t.Hoge1();
const hoge2 = new t.Hoge2();
const hoge3 = new t.Hoge3();
const hoge4 = new t.Hoge4();

console.log(hoge1.name);    // "hoge1"と表示
console.log(hoge2.name);    // "hoge2"と表示
console.log(hoge3.name);    // "hoge3"と表示
console.log(hoge4.name);    // "hoge1"と表示

[Node.js][TypeScript] プライベートなモジュールの共有でpackage.jsonにcommonが絶対パスで保存されるって書いているけど、今のバージョンnpm(v6.1.0))では相対パスになっている。





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

CSVを読むためのモジュールをインストールする。

npm install csv-parse @types/csv-parse -S

サンプルはこんな感じだ。えーっと、ソースにコメントを書いたので説明は省く。あと、オプション指定できる項 の細かい説明はここに詳しく書いてある。

import csvParse from "csv-parse";
import csvParseSync from "csv-parse/lib/sync";

// csvファイルの内容を文字列で用意する。
// 本当はfs.readFileでcsvファイルを読み込み
const csvstr = '"hoge1","hoge2"\n1,3';

//// 非同期処理
// とりあえず、パースする。
// 配列(1レコード)の配列で返される。
csvParse(csvstr, {}, (err, recodes)=>{
    // [ [ 'hoge1', 'hoge2' ], [ '1', '3' ] ] と表示される。
    console.log(recodes);
});

// 1行目がフィールド名の場合
// 1行目のフィールド名がkeyになっているオブジェクトの配列になる。
csvParse(csvstr, { columns:true }, (err, recodes)=>{
    // [ { hoge1: '1', hoge2: '3' } ] と表示される。
    console.log(recodes);
});

// 1行目がフィールド名でkeyを独自に付ける場合
// オプションのnocolumnsでフィールド名(オブジェクトのkeyになる)を指定する。
// 1行目がフィールド名なので2行目からパーするするようにfromオプションで2を指定している。
// 結果、columnsで指定したフィールド名がkeyのオブジェクトの配列になる。
csvParse(csvstr, { columns:["field1", "field2"], from:2 }, (err, recodes)=>{
    // [ { field1: '1', field2: '3' } ] と表示される。
    console.log(recodes);
    // hoge1:1 と表示される。
    console.log(`hoge1:${recodes[0]["field1"]}`);
    // hoge2:3 と表示される。
    console.log(`hoge2:${recodes[0]["field2"]}`);
});


///// 同期処理
// 1行目がフィールド名でkeyを独自に付ける場合
try {
    const recodes = csvParseSync(csvstr, { columns:["field1", "field2"], from:2 });
    // [ { field1: '1', field2: '3' } ] と表示される。
    console.log(recodes);
} catch(err) {
    console.log(err);
}





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

メニューを変更してみる。

アップしたソースからメインプロセスのソースを一部あげる。

const template:any = [
    {
        label:"テスト",
        submenu:[
            { label:"テスト1", click:()=> {
                dialog.showMessageBox({message:"テスト1クリック!"});
            }},
            {
                label:"サブメニュー",
                submenu:[
                    {
                        label:"テスト2",
                        click:()=>{
                            dialog.showMessageBox({message:"テスト2クリック!"});
                        },
                        accelerator:"CmdOrCtrl+Shift+G"
                    },
                ]
            }
        ]
    },
    {
        label: "編集",
        submenu: [
            {role: "undo", label:"元に戻す"},
            {role: "redo", label:"やり直し"},
            {type: "separator"},
            {role: "cut", label:"切り取り"},
            {role: "copy", label:"コピー"},
            {role: "paste", label:"貼り付け"},
            {role: "pasteandmatchstyle", label:"ペースしてスタイルを合わせる"},
            {role: "delete", label:"削除"},
            {role: "selectall", label:"すべて選択"}
        ]
    },

    <<省略>>
}

// ウィンドウの作成準備ができたときの処理を追加
app.on("ready", ()=>{
    // メニューを設定する
    const menu = Menu.buildFromTemplate(template)
    Menu.setApplicationMenu(menu)

    createWindow();
});

メニューのテンプレートを用意する。
配列でトップメニュー(と言うのかな?)のオブジェクトを用意する。これが、Windowsだとウインドウ上部に表示されるメニューになる。
002

それぞれのオブジェクト内にsubmenuでさらにメニューのオブジェクトを用意するとメニューをクリックした時に出てくるメニューになる。

メニューオブジェクトのプロパティやメソッドは、ここに詳しく書かれている。ここでは最低限必要そうなのを書いておく。

  • label:メニューの表示名
  • click:クリックした時に呼ばれるメソッド
  • accelerator:ショートカットキーの定義
  • submenu:サブメニュー
  • type:メニューのタイプ。"separator"、"checkbox"とか。
  • role:メニューの動作を定義。標準的なメニュー項目と動作が用意されているのでそれを指定する。どんなのがあるかは、ここに書いてある。clickメソッドが定義されている場合は無視される。

appのreadyイベントでMenu.buildFromTemplateを使ってテンプレートからメニューを作って、Menu.setApplicationMenuでメニューを設定する。

ポップアップメニュー

レンダラープロセスでメニューを用意しておいて、contextmenuイベントで表示する。レンダラープロセスのソースを次に示しておく。

import * as React from "react";
import {remote} from "electron";
import * as fs from "fs";
import * as electron from "electron";

const dialog = remote.dialog;
const Menu = remote.Menu;
const MenuItem = remote.MenuItem;

interface AppState {
    text?:string;
}

export class App extends React.Component<{}, AppState>  {
    private contextMenu:electron.Menu;

    constructor(props:any) {
        super(props);
        this.state = {
            text:""
        };

        // コンテキストメニューの準備(1)
        this.contextMenu = new Menu();
        this.contextMenu.append(new MenuItem({label:"テスト1", click:()=>{dialog.showMessageBox({message:"コンテキストメニュー:テスト1クリック"})}}));
        this.contextMenu.append(new MenuItem({label:"テスト2", click:()=>{dialog.showMessageBox({message:"コンテキストメニュー:テスト2クリック"})}}));

        this.onContextMenu = this.onContextMenu.bind(this);
    }

    render() {
        return <div>
                <span>Hello World</span>
            </div>;
    }

    onContextMenu(e:PointerEvent) {
        e.preventDefault();
        // ポップアップメニューを出す(2)
        ///// ↓ Electron 2.0.0でインターフェースが変わった。
        this.contextMenu.popup({window:remote.getCurrentWindow()});
    }

    componentDidMount() {
        document.addEventListener("contextmenu", this.onContextMenu);
    }

    componentWillUnmount() {
        document.removeEventListener("contextmenu", this.onContextMenu);
    }
}

ソースの(1)の部分でポップアップメニューを用意して、(2)の部分でポップアップメニューを表示している。



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

fs.watchでファイルの変更監視できる。

import * as fs from "fs";

fs.watch("./", {persistent:true, recursive:false}, (eventType, filename)=>{
    console.log(`${eventType}:${filename}`);
});

第1引数に監視するファイルまたはディレクトリ名を指定する。

第2引数にオプション。

  • persistent ファイルの監視を続けるかどうかを指定:trueで続ける。falseだと1度変更されたら終了。
  • recursive サブディレクトリも監視するかどうか。サブディレクトリも監視できるのは、macOSとWindowsのみ対応。

第3引数は、eventTypeとfilenameが渡される関数を指定する。eventTypeは"rename"か"change"でfilenameはイベントのきっかけになったファイル名。


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

前々からTypeScriptの型定義ファイル(のモジュール?)って--save-dev(-D)オプションを付けてpackage.jsonのdevDependenciesに記録するんじゃなかろーかと思っているんだが、いろんなページ見ていると--save(-S)でpackage.jsonに記録しているんだよねー。

で、MicrosoftのVisual Studio Codeで多分使っているだろーと思って、公開されているpackage.jsonを見てみると、devDependenciesに記録されているんだよね。やっぱり、--save-dev(-D)を付けるので正解のようだ。(たぶん)


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

↑このページのトップヘ