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に登録する
- https://packagist.org/ でユーザ登録する。
- https://packagist.org/packages/submit から gitのURLを設定する。
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
要素名にはプレフィックスを付けることが推奨されている
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
にだけ適用する
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-server
→ webpack 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