コピペコードで快適生活

明日使えるソースを自分のために

composerで使えるライブラリを作ってみた

composerで使うライブラリを作ったことがなかったので、試しに作ってみた。

作ってみる

ソースはこんなかんじで
https://github.com/kinosuke01/convertible-romaji

作るときのコマンドメモ

# コンテナ起動してログイン
docker compose up
docker compose exec php /bin/bash

# composer.pharのインストール
curl -s http://getcomposer.org/installer | php

# composer.jsonの内容に従って
# ライブラリをインストール&autoload設定
php composer.phar install

# autoloadの更新だけ
composer dump-autoload

# テスト実行
vendor/bin/phpunit ./tests/

# composer.jsonのチェック
php composer.phar validate

# タグうち
git tag v0.0.1
git push origin v0.0.1

packagistに登録する

namaspace

よくわかってなかったのでメモ。

  • ライブラリの名前空間ベンダー名\ライブラリ名\クラス が一般的っぽい。

  • 定義の書き方

namespace Kinosuke01\ConvertibleRomaji;
class ConvertibleRomaji
{
// 略
}

// Rubyで書くならこんな
// module Kinosuke01::ConvertibleRomaji
//   class ConvertibleRomaji
//   end
// end
  • 呼び出し方
use Kinosuke01\ConvertibleRomaji\ConvertibleRomaji
// ↑は以下の略式
// use Kinosuke01\ConvertibleRomaji\ConvertibleRomaji as ConvertibleRomaji

// クラスが名前空間なしで使えるようになる。
$romaji = new ConvertibleRomaji('irohani');

作ったライブラリを使ってみる

docker-composer.yml作る

services:
  php:
    image: webdevops/php-apache-dev:7.4
    volumes:
      - "./:/var/app"
    ports:
      - "80:80"
    tty: true

コンテナ起動 & ログイン

docker compose up
docker compose exec php /bin/bash

composer.json作る

{
    "require": {
        "kinosuke01/convertible-romaji": "0.0.*"
    }
}

composer installする

# コンテナ内で実行
composer install

test.php作る

<?php
require 'vendor/autoload.php';

use Kinosuke01\ConvertibleRomaji\ConvertibleRomaji;

$romaji = new ConvertibleRomaji('irohanihoheto');
echo $romaji->toHiragana(); // いろはにほへと
echo $romaji->toKatakana(); // イロハニホヘト

test.phpを実行

# コンテナ内で実行
php test.php
# => いろはにほへとイロハニホヘト

CSSをあてる要素をどう分割するか

設計手法

責務をどのように分割するか、名前をつけるか という点でいくつかの代表的なアプローチがある。

  • OOCSS
  • BEM
  • SMACSS
  • FLOCSS

SMACSS

以下のように責務を分割する。

  • Base
    • ベースとなるデフォルトのスタイルを定義する
  • Layout
    • ページを構成するブロック要素のスタイルを定義する
    • プレフィックスとして layout-l- が使われる。
  • Module
    • 再利用可能なスタイルを定義する
  • State
    • レイアウトやモジュールの状態を表すスタイル
    • 例 is-active, is-enable
  • Theme
    • 色、形状、レイアウト、書体などの見た目や振る舞いを表すスタイル
    • プレフィックスとして theme- が使われる。

https://murashun.jp/article/programming/css/css-design.html#chapter-5

FLOCSS(フロックス)

以下のように責務を分割する。

  • Foundation
    • スタイルの初期化を行う
    • 例: reset.css
  • Layout
    • ページを構成するブロック要素のスタイルを定義する
    • 例: header, main, sidebar, footerなど
  • Object
    • Component
      • 使い回せる要素を定義する。
      • 例: btn要素
    • Project
      • プロジェクト固有のスタイルを定義する。
      • (よくわからない)
    • Utility
      • Component,Project以外の色々
      • 例: clearfix, .mbs { margin-bottom: 10px; } など

要素名にはプレフィックスを付けることが推奨されている

Component - .c-*
Project - .p-*
Utility - .u-*

参考:https://github.com/hiloki/flocss

tips

子孫セレクタは少ない方が高速

CSSは右から左に読み込まれるため、子孫セレクタの多いこっちより

.wrapper .main .list .row

少ないこっちのほうが、探し出す要素の数が少ないため高速

.list-row

idやclassには要素名をつけないほうが高速

li.list より .li-listのほうが高速

責務で要素の名前をつける

スタイルの内容やコンテンツ名で名前をつけない

# 赤じゃなくなったら、class名を全部書き換える必要あり。
.coler-red

# 見積もり表以外でも使いたくなったとき、class名を全部書き換える必要あり。
.mitsumori-table

隙間をとるマージンについて

  • 上下の要素間の隙間を空ける時は、margin-bottom にだけ適用する
  • 左右の要素間の隙間を空ける時は、margin-right にだけ適用する

参考:https://qiita.com/tera_shin/items/af90aeba49f93c76bd9e

ReactHooksを使ってみる

最近のReactでは関数コンポーネント&ReactHooksを使うのが主流らしい。
キャッチアップしたところ、state管理がコンポーネントからキレイに分離できてよさそうだった。

index.tsx

import * as React from "react";
import * as ReactDOM from "react-dom";
import Omikuji from "./Omikuji";

let element: JSX.Element = <Omikuji name="kinosuke01"/>;
let dom: HTMLElement = document.getElementById('root');
ReactDOM.render(element, dom);

Omikuji.tsx

import React from 'react';
import useOmikuji from './useOmikuji';

interface Props {
  name: string,
};

interface State {
};

export default function Omikuji(props: Props) {
  // state管理を行う処理を、関数コンポーネントに組み込む。
  let [result, updateResult] = useOmikuji();

  let message: string = '今日の運勢を占います!';
  let buttonText: string = '占う!';
  if (result) {
    message = `${props.name}さんの今日の運勢は「${result}」です!!`;
    buttonText = 'もう一度占う!';
  }

  return (
    <div>
      <p>{message}</p>
      <button onClick={() => updateResult()}>{buttonText}</button>
    </div>
  )
};

useOmikuji.ts

import { useState, useEffect } from 'react';

//
// 関数コンポーネントにstate管理を組み込む関数。
// state管理をコンポーネントから分離できる。
// 複数のコンポーネントで使い回すことができる。
// setResultやuseEffectを使うことで実現。
//
export default function useOmikuji(): [string, Function] {
  // result -> state.result
  // setResult -> setState({result: xxx});
  // に対応するイメージ
  // useStateの引数は、対象stateの初期値
  const [result, setResult] = useState(null);

  function predict(): string {
    let i: number = Math.floor(Math.random() * 10 % 5);
    let list: Array<string> = ['大吉', '吉', '中吉', '小吉', '凶'];
    return list[i];
  }

  function updateResult(): void {
    setResult('占い中...');
    setTimeout(() => {
      let predictResult: string = predict();
      setResult(predictResult);
    }, 1000);
  }

  // useEffectには、
  // componentDidMount, componentDidUpdate の
  // タイミングで呼びたい関数を渡す
  useEffect(() => {
    console.log(`「${result}」を取得しました。`);

    // 戻り値には、componentWillUnmountの
    // タイミングで呼びたい関数を渡す
    return (): void => {
      console.log('さよなら');
      return;
    };
  });

  return [result, updateResult];
}

参考

React+TypeScript+webpackの環境構築

必要なライブラリのインストール

ビルドツールをインストールする

npm install --save-dev webpack webpack-cli webpack-dev-server typescript ts-loader

React関連のライブラリをインストールする。 ビルド後のファイルに含めるので--saveとする。

npm install --save react react-dom @types/react @types/react-dom
  • react-dom: ReactオブジェクトをDOMに反映するライブラリ
  • @types/react: Reactに関する型定義
  • @types/react-dom: react-domに関する型定義

scriptsの追加

${APP_DIR}/package.jsonにscriptsを追加

{
  "scripts": {
    "build:prod": "webpack --mode production",
    "build": "webpack",
    "watch": "webpack --watch",
    "start": "webpack serve"
  },
  # 略
}

TypeScriptの設定を行う

${APP_DIR}/tsconfig.json に以下を書き出す。

{
  "compilerOptions": {
    "sourceMap": true,
    "target": "ES5",    // ECMAScript 5に変換
    "module": "ES2015", // ES Modulesとして出力
    "jsx": "react",     // JSXの書式を有効に設定

    // 非相対パスを指定したとき、インポート対象をnode_modulesから探す
    "moduleResolution": "node", 

    // targetに含まれていないライブラリを組み込む
    "lib": [
      "ES2020",
      "DOM"
    ]
  }
}

webpackの設定を行う

${APP_DIR}/webpack.config.js に以下を書き出す。

const path = require('path')

module.exports = {
  mode: 'development',

  entry: {
    index: path.join(__dirname, '/src/index.tsx'),
  },

  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].js',
  },

  module: {
    rules: [
      {
        test: /\.tsx?$/,          // 拡張子 .ts と .tsx のファイルを
        use: 'ts-loader',         // ts-loaderでトランスパイルする
        exclude: /node_modules/,  // ただし外部ライブラリは除く
      },
    ],
  },

  // 拡張子指定なしでimportできるおまじない
  resolve: {
    extensions: [".ts", ".tsx", ".js", ".json"]
  },

  // ES5向けの指定(webpack 5以上で必要)
  target: ["web", "es5"],

  devServer: {
    host: '0.0.0.0',
    port: 8080,
  },
};

TypeScriptでReactのコードを書いてみる

${APP_DIR}/src/index.tsx

import * as React from "react";
import * as ReactDOM from "react-dom";
import { ShoppingList } from "./ShoppingList";

let element: JSX.Element = <ShoppingList name="weekend"/>;
let dom: HTMLElement = document.getElementById('root');
ReactDOM.render(element, dom);

${APP_DIR}/src/ShoppingList.tsx

import * as React from "react";

interface Props {
  name: string,
};

interface State {
};

export class ShoppingList extends React.Component<Props, State> {
  render() {
    return (
      <div className="shopping-list">
        <h1>Shopping List for {this.props.name}</h1>
        <ul>
          <li>Instagram</li>
          <li>WhatsApp</li>
          <li>Oculus</li>
        </ul>
      </div>
    );
  }
};

${APP_DIR}/index.html

<!DOCTYPE html>
<html>
<head></head>
<body>
  <div id="root"></div>
  <script type="text/javascript" src="./dist/index.js"></script>
</body>
</html>

ビルドなどをする

# ビルド
npm run build

# 開発サーバ立ち上げる
npm run start

補足

いつの間にか、webpackDevServerの起動方法が変わっていた。 webpack-dev-serverwebpack serve

参考

TypeScriptのコードをJestでテストする

やり方には、ts-jestを使う方法と、babelを使う方法がある。
babelを使うやり方は、https://kinosuke.hatenablog.jp/entry/2020/01/29/115640 と同じアプローチ。preset-envをpreset-typescriptにするだけ。ただし、型チェックはできない。
今回は、型チェックもやってほしいので、ts-jestを使う方法を試してみる。

ライブラリのインストール

npm install --save-dev jest ts-jest @types/jest
  • @types/jest
    • jestの型を定義
  • ts-jest
    • Jest用のTypeScriptプリプロセッサ
    • JestがTypeScriptをトランスパイルできるようになる

Jestを設定する

# 設定ファイルを作成する
npx jest --init

${APP_DIR}/jest.config.js に以下を書き込む。

module.exports = {
  // Jestの検索対象となるパス
  roots: [
    "<rootDir>"
  ],
  // テストコードを書いたファイルを特定するための条件
  "testMatch": [
    "**/__tests__/**/*.+(ts|tsx|js)",
    "**/?(*.)+(spec|test).+(ts|tsx|js)"
  ],
  // ts/tsxファイルに対してts-jestを使うよう設定
  "transform": {
    "^.+\\.(ts|tsx)$": "ts-jest"
  },
}

テストスクリプトを準備する

${APP_DIR}/package.json に以下を追加

{
  "scripts": {
    # 略
    "test": "jest",
    "test:w": "jest --watch",
    "test:coverage": "jest --coverage"
  },
  # 略
}

テストコードを書いてみる

src/Animal.ts にテスト対象コードを書く。

export class Animal {
  animaltype: string;
  name: string;

  constructor(animalType: string, name: string) {
    this.animaltype = animalType;
    this.name = name;
  }

  say(): string {
    let content: string = "`Hello. I am ${this.name} of ${this.animaltype}.`";
    return content;
  }
}

test/Animal.test.ts にテストコードを書く。

import { Animal } from '../src/Animal';

describe('say', () => {
  it("`Hello`を含む文字列を返すこと", () => {
    let animal = new Animal('cat', 'popuko');
    let reg = /Hello/;
    let result = animal.say();
    expect(reg.test(result)).toEqual(true);
  })
})

テストを実行する

# 全部をテストする
npm run test test/.

# ファイル名を指定してテストする
npm run test test/Animal.test.ts

補足

テスト時に以下の警告が出た。
importで問題がある場合は、esModuleInteropを使うとよさそうですね、とのこと。

ts-jest[config] (WARN) message TS151001: If you have issues related to imports, you should consider setting `esModuleInterop` to `true` in your TypeScript configuration file (usually `tsconfig.json`). See https://blogs.msdn.microsoft.com/typescript/2018/01/31/announcing-typescript-2-7/#easier-ecmascript-module-interoperability for more information.

毎回警告が出るのは気になるので、tsconfig.jsonに以下を追加。

{
  "compilerOptions": {
    # 略
    "esModuleInterop": true
  }
}

esModuleInteropって何がいいの?

// esModuleInterop無効の場合
// CommonJSのモジュールは、requireで読み込まないとだめ。
`const moment = require('moment');`

// esModuleInterop有効な場合
// CommonJSのモジュールもES Modulesと同じ書き方で読み込めるようになる!
import moment from 'moment'

参考

TypeScript+webpack環境を作る - ts-loader使う版

https://kinosuke.hatenablog.jp/entry/2021/07/23/165039 のやりなおし。 babelはTypeScriptからJavaScriptへの変換だけをやってくれるだけで、肝心の型チェックはやってくれなかったので、ts-loaderを使ってやってみる。

必要なライブラリをインストール

npm install --save-dev webpack webpack-cli typescript ts-loader
  • webpack:
    • モジュールバンドラー。
    • JavaScript等の複数のファイルを1つにまとめる。
    • loaderを組み込むことで、まとめる前に変換処理(ES2015→ES5など)を加えることができる。
  • webpack-cli:
    • webpackのcli
  • ts-loader:
    • webpackにTypeScriptの変換処理を組み込むloader

${APP_DIR}/package.json の更新

scriptsにビルドコマンドを追加する

{
  "scripts": {
    "watch": "webpack --watch",
    "build": "webpack --mode production"
  },
  "devDependencies": {
    # 略
  }
}

${APP_DIR}/tsconfig.json の作成

TypeScriptをどのように変換するかを設定する

{
  "compilerOptions": {
    "sourceMap": true,
    "target": "ES5", // TSはECMAScript 5に変換
    "module": "ES2015" // TSのモジュールはES Modulesとして出力
  }
}
  • CommonJSのモジュール:
    • require/module.exportsで外部JSファイルを扱う
    • Node.jsが採用している
    • tsconfig.jsonのデフォルト
  • ES Modules:
    • import/exportで外部JSファイルを扱う
    • ES2015が採用している
    • webpackのTree Shaking(未使用のimportを振り落とす)が効く

${APP_DIR}/webpack.config.js の作成

webpackのバンドル設定を行う

const path = require('path')

module.exports = {
  // production:  改行やインデントを削除してJSファイル出力
  // development: ソースマップ有効でJSファイル出力
  // ただし `webpack --mode production` したときは production が有効になる
  mode: 'development',

  // 入力元の指定
  entry: {
    index: path.join(__dirname, '/src/index.ts'),
  },

  // 出力先の指定
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].js',
  },

  module: {
    rules: [
      {
        test: /\.ts$/,            // 拡張子 .ts のファイルを
        use: 'ts-loader',         // ts-loaderでトランスパイルする
        exclude: /node_modules/,  // ただし外部ライブラリは除く
      },
    ],
  },

  // import './MyLib.ts' が 
  // import './MyLib' の書き方でOKになる
  resolve: {
    extensions: [
      '.ts', '.js',
    ],
  },
};

tsファイルを書いてみる

${APP_DIR}/src/index.ts

import { Animal } from './Animal';
let animal = new Animal("cat", "pipimi");
animal.say();

${APP_DIR}/src/Animal.ts

export class Animal {
  animaltype: string;
  name: string;

  constructor(animalType: string, name: string) {
    this.animaltype = animalType;
    this.name = name;
  }

  say() {
    console.log(`Hello. I am ${this.name} of ${this.animaltype}.`);
  }
}

ビルドする

npm run build

参考

TypeScript+webpack環境を作る - babel使う版

TypeScriptをはじめてみる - コピペコードで快適生活 の続き

必要なライブラリをインストール

npm install --save-dev webpack webpack-cli typescript babel-loader @babel/core @babel/preset-typescript
  • webpack:
    • モジュールバンドラー。
    • JavaScript等の複数のファイルを1つにまとめる。
    • loaderを組み込むことで、まとめる前に変換処理(ES2015→ES5など)を加えることができる。
  • webpack-cli:
    • webpackのcli
  • babel-loader:
    • webpackにbabelの変換処理を組み込むloader
    • babelは古いブラウザで動くJavaScriptに変換するツール群
  • @babel/core:
    • babelのコア機能
  • @babel/preset-typescript
    • TypeScript→JavaScriptの変換を行う
    • babelでの変換処理本体は、プラグインとして提供される
    • @babel/preset-env は最新のJavaScriptからの変換を行う

${APP_DIR}/package.json の更新

scriptsにビルドコマンドを追加する

{
  "scripts": {
    "watch": "webpack --watch",
    "build": "webpack --mode production"
  },
  "devDependencies": {
    "@babel/core": "^7.14.8",
    "@babel/preset-typescript": "^7.14.5",
    "babel-loader": "^8.2.2",
    "typescript": "^4.3.5",
    "webpack": "^5.45.1",
    "webpack-cli": "^4.7.2"
  }
}

${APP_DIR}/tsconfig.json の作成

TypeScriptをどのように変換するかを設定する

{
  "compilerOptions": {
    "sourceMap": true,
    "target": "ES5", // TSはECMAScript 5に変換
    "module": "ES2015" // TSのモジュールはES Modulesとして出力
  }
}
  • CommonJSのモジュール:
    • require/module.exportsで外部JSファイルを扱う
    • Node.jsが採用している
    • tsconfig.jsonのデフォルト
  • ES Modules:
    • import/exportで外部JSファイルを扱う
    • ES2015が採用している
    • webpackのTree Shaking(未使用のimportを振り落とす)が効く

${APP_DIR}/webpack.config.js の作成

webpackのバンドル設定を行う

const path = require('path')

module.exports = {
  // production:  改行やインデントを削除してJSファイル出力
  // development: ソースマップ有効でJSファイル出力
  // ただし `webpack --mode production` したときは production が有効になる
  mode: 'development',

  // 入力元の指定
  entry: {
    index: path.join(__dirname, '/src/index.ts'),
  },

  // 出力先の指定
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].js',
  },

  module: {
    rules: [
      {
        test: /\.ts$/,            // 拡張子 .ts のファイルを
        loader: 'babel-loader',   // babelでトランスパイルする
        options: {
          presets: ['@babel/preset-typescript']
        },
        exclude: /node_modules/,  // ただし外部ライブラリは除く
      },
    ],
  },

  // import './MyLib.ts' が 
  // import './MyLib' の書き方でOKになる
  resolve: {
    extensions: [
      '.ts', '.js',
    ],
  },
};

tsファイルを書いてみる

${APP_DIR}/src/index.ts

import { Animal } from './Animal';
let animal = new Animal("cat", "pipimi");
animal.say();

${APP_DIR}/src/Animal.ts

export class Animal {
  animaltype: string;
  name: string;

  constructor(animalType: string, name: string) {
    this.animaltype = animalType;
    this.name = name;
  }

  say() {
    console.log(`Hello. I am ${this.name} of ${this.animaltype}.`);
  }
}

ビルドする

npm run build

参考