概要

Web ツール類を TypeScript で開発するための方法を記す.

今回は,こんな感じで,1つのサイト内に複数の Web ツールが入っているような形を目指す.

/ (root)
├ index.html
├ favicon.ico
├ tool1/
│ ├ index.html
│ └ dist.js
├ tool2/
│ ├ index.html
│ └ dist.js
├ tool3/
│ ├ index.html
│ └ dist.js
︙

環境の準備

まずは以下の形を目指す.

hoge/
├ node_modules/
├ types/
├ dist/
├ src/
│ ├ tool1/
│ │ ├ main.ts
│ │ ├ rollup.config.js
│ │ ├ fuga.ts
│ │ ︙
│ ├ tool2/
│ │ ├ main.ts
│ │ ├ rollup.config.js
│ │ ├ piyo.ts
│ │ ︙
│ ︙
├ static/
│ ├ favicon.ico
│ ├ index.html
│ ├ tool1/
│ │ ├ index.html
│ │ ︙
│ ├ tool2/
│ │ ├ index.html
│ │ ︙
│ ︙
├ package.json
├ tsconfig.json
└ eslintrc.json

何はともあれ init.

$ npm init
package name: (hoge) 
version: (1.0.0) 
description: 説明文を入力する
entry point: (index.js) 
test command: jest
git repository: 
keywords: 
author: xxx
license: (ISC) MIT

$ npm install --save-dev typescript sass rollup prettier rollup-plugin-html rollup-plugin-scss@3 rollup-plugin-typescript ts-jest jest eslint-plugin-prettier eslint-config-prettier eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin @types/jest cpx http-server

package.json を次のようにする.具体的には,

  • entry point ("main": "index.js",) を消す
  • typeRoots を追加する
  • scripts 内に bundle などを追加する
    • Linux のみサポートなら下記で OK.
    • Windows 上で開発したいなら,"lint": "tsc --noEmit && eslint \"src/**/*.ts\"", とかにする
      • ; で区切ったままだと Unknown compiler option '--noEmit;'. Did you mean 'noEmit'? と言われる
      • eslint で探すファイルのパスをシングルクォートで囲んだままだと,No files matching the pattern を言われる
  • repository, bugs, homepage に git の情報を追加する(あれば)
  • jest の設定を追加する
    • package.json 以外の場所で設定したい人はそれでも OK
{
  "name": "hoge",
  "version": "1.0.0",
  "description": "説明文を入力する",
  "typeRoots": [
    "types",
    "node_modules/@types"
  ],
  "scripts": {
    "copy": "cpx \"static/**/*.{html,ico}\" dist",
    "prettier": "prettier --write $PWD/static/'**/*.{js,jsx,ts,tsx,vue,css,html}'",
    "lint": "tsc --noEmit; eslint --ignore-path .gitignore \"**/*.{ts,tsx}\"",
    "lint:fix": "tsc --noEmit; eslint --ignore-path .gitignore \"**/*.{ts,tsx}\" --fix",
    "bundle": "rollup -c $npm_config_fn",
    "build": "npm run lint:fix && npm run prettier && npm run copy && rollup -c $npm_config_fn",
    "serve": "http-server -o /dist/",
    "test": "jest"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/xxx/hoge.git"
  },
  "author": "iilj",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/xxx/hoge/issues"
  },
  "homepage": "https://github.com/xxx/hoge#readme",
  "devDependencies": {
    "@types/jest": "^27.0.2",
    "@typescript-eslint/eslint-plugin": "^5.1.0",
    "@typescript-eslint/parser": "^5.1.0",
    "cpx": "^1.5.0",
    "eslint": "^8.0.1",
    "eslint-config-prettier": "^8.3.0",
    "eslint-plugin-prettier": "^4.0.0",
    "http-server": "^14.0.0",
    "jest": "^27.3.1",
    "prettier": "^2.4.1",
    "rollup": "^2.58.0",
    "rollup-plugin-html": "^0.2.1",
    "rollup-plugin-scss": "^3.0.0",
    "rollup-plugin-typescript": "^1.0.1",
    "sass": "^1.43.2",
    "ts-jest": "^27.0.7",
    "typescript": "^4.4.4"
  },
  "jest": {
    "moduleFileExtensions": [
      "ts",
      "js"
    ],
    "transform": {
      "^.+\\.ts$": "ts-jest"
    },
    "globals": {
      "ts-jest": {
        "tsconfig": "tsconfig.json"
      }
    },
    "testMatch": [
      "<rootDir>/src/**/*.test.ts"
    ]
  }
}

rollup.config.js を作成する.ここでは,src/main.ts をエントリポイントにしている.なお,以下の点に留意すること.

  • @match の記述は適宜変更すること.
  • @exclude, @require, @resource などは適宜追加すること.
  • @grant の記述は適宜変更すること.
import typescript from "rollup-plugin-typescript";
import html from "rollup-plugin-html";
import scss from 'rollup-plugin-scss';

export default [
    {
        input: "src/hoge/main.ts",
        output: {
            file: "dist/hoge/dist.js"
        },
        plugins: [
            html({
                include: "**/*.html"
            }),
            scss({
                output: false
            }),
            typescript()
        ]
    }
];

tsconfig.json を作成する.es2017 では新しすぎる場合は,es6 あたりにしておく.strict は様子を見ながら,大丈夫そうなら true にする.

{
    "compilerOptions": {
        "module": "commonjs",
        "target": "es2017",
        "typeRoots": [
            "./types",
            "./node_modules/@types"
        ],
        "strict": true
    },
    "exclude": [
        "node_modules"
    ]
}

.eslintrc.json を作成する.なお eslint の “indent” は prettier のインデントとバッティングするので,指定しないで prettier に任せる.

{
    "extends": [
        "eslint:recommended",
        "plugin:@typescript-eslint/recommended",
        "plugin:@typescript-eslint/recommended-requiring-type-checking",
        "plugin:prettier/recommended"
    ],
    "plugins": [
        "@typescript-eslint"
    ],
    "env": {
        "browser": true,
        "es2017": true
    },
    "parser": "@typescript-eslint/parser",
    "parserOptions": {
        "sourceType": "module",
        "project": "./tsconfig.json",
        "ecmaVersion": 2017
    },
    "rules": {
        // "indent": [
        //     "error",
        //     4
        // ],
        "@typescript-eslint/no-use-before-define": [
            "error",
            {
                "functions": false,
                "classes": false
            }
        ],
        "prettier/prettier": [
            "error",
            {
                "printWidth": 120,
                "tabWidth": 4,
                "singleQuote": true
            }
        ]
    }
}

開発

rollup.config.js に記載した .ts ファイル(今回は src/main.ts) をエントリポイントとしてコードを記述していく.

main.ts の中身は,たとえば次のような感じで OK.

import { Hoge } from './hogehoge';

(() => {
    // 何かする
    Hoge.doSomething();
    console.log('Hello World!');
})();

適宜,types ディレクトリに *.d.ts を追加していく.

CSS, HTML の import

たとえば,rollup-plugin-scss 経由で scss の中身を string として import したいとき,types/scss.d.ts を作成し,次のように記述する.

declare module "*.scss" {
    const content: string;
    export default content;
}

import する側は,通常通り次のような書き方で OK. これで,rollup されるときには,scss の記法から css の記法に変換されたものが変数 css の中身に入るので,

import css from './hoge.scss';

export const hoge = (): void => {
    GM_addStyle(css);
};

細かい設定を変えたいときは,rollup.config.js の中身を弄る.

HTML の中身を string として読みたいときも,大まかな手順は同じで,types/html.d.ts など適当な名前で定義ファイルを作成すればよい.

Linter

$ npm rum lint:fix

バンドル

以下を実行すると,dist/dist.js に内容が吐かれる.

$ npm run bundle

ブラウザに読ませる

開発時は,次のようなものを用意して,通常時に使うほうを無効化すると便利.

// ==UserScript==
// @name        hoge-dev
// @namespace   iilj
// @version     1.0.0
// @description 開発用のスクリプトです。ローカルからビルド済みスクリプトをロードします。
// @author      iilj
// @license     MIT
// @require     file://[リポジトリの親ディレクトリへのパス]/hgoe/dist/bundle.js
// @supportURL  https://github.com/xxx/hoge/issues
// @run-at      document-end
// @match       https://example.com/*
// ==/UserScript==

ただし,拡張機能に対してローカルファイルへのアクセスを許可する必要がある.ブラウザの拡張機能一覧画面から,拡張機能ごとに設定を行える.

なお,上記スクリプトは,rollup.config.js に記載したのと同じように,@exclude, @require, @resource, @grant などを設定する必要がある.

また,ファイルの場所は file:///home/hoge/... のように,通常はルートから記述する.

以上