15
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

TypeScriptAdvent Calendar 2022

Day 7

ブックマークレットをTypeScriptで書く(rollup編)

Last updated at Posted at 2022-12-07

rollupを用いて、ブックマークレットをTypeScriptで書く環境を構築する手順を示します。

ブックマークレットをTypeScriptで書きたい

私は、日々のウェブブラウジングを快適にするためのブックマークレットを自作しています。
ブックマークレットの魅力は、なんといっても1行でさくっと書けるお手軽さにあります。
しかしながら、ある程度の規模を超えたブックマークレットを素のJavaScriptで、それも1行の中に書いていくのは、なかなかつらいものです。
そこで、ブックマークレットをTypeScriptで書きたいと思いました。

ブックマークレットをTypeScriptで書くには

ブックマークレットをTypeScriptで書くには、おおむね次のようなことをおこなう必要があります。

  • TypeScriptをJavaScriptにトランスパイル
  • ファイルが分かれている場合は1ファイルにバンドル
  • グローバル空間の汚染を避けるために、スクリプト全体をIIFEでラップ
  • 複数行に渡る場合は1行に圧縮
  • ファイルの先頭にjavascript:を付与

これには様々な手法がありますが、今回はモジュールバンドラーのrollupを採用しました。

特徴

この記事で紹介する手法には、次のような特徴があります。
これらの特徴によって、快適なDX(開発者体験)がもたらされることでしょう。

ファイル分割

適切にファイルを分割することで、大規模なブックマークレットでもメンテナビリティを高く保つことができます。
また、ブックマークレット間で共通する処理を切り出して共用することもできます。
たとえば、クリップボードにコピーする処理をよく使うのであればcopy.tsのようなファイルに関数を定義しておき、それを各所でインポートするとよいでしょう。

Jestによるテスト実行

テストを書くことで、ブックマークレットの品質を担保することができます。
もちろん、テストコードの中でDOMを扱うこともできます。

ウォッチモード

開発時に使用するウォッチモードではソースコードを保存する度にビルドが走り、ブックマークレットが更新されます。

ディレクトリ構成

この記事で紹介する手法では、次のようなディレクトリ構成になります。

.
├── .eslintrc.js
├── .gitignore
├── dist
│   └── foo.min.js
├── jest.config.ts
├── package-lock.json
├── package.json
├── rollup.config.ts
├── src
│   └── foo
│       └── main.ts
└── tsconfig.json
  • .eslintrc.js:ESLintの設定ファイル
  • .gitignore:Gitの除外設定ファイル
  • dist:ブックマークレットが出力されるディレクトリ
    • dist/**/*.min.js:ブックマークレット
  • jest.config.ts:Jestの設定ファイル
  • package-lock.json:npmのパッケージ情報のロックファイル
  • package.json:npmのパッケージ情報ファイル
  • rollup.config.ts:rollupの設定ファイル
  • src:ブックマークレットのソースコードを配置するディレクトリ
    • src/**/main.ts:ブックマークレットの本体にあたるエントリーファイル
  • tsconfig.json:TypeScriptの設定ファイル

このようにファイルパスのルールを定めることで、新たにブックマークレットを追加する場合はsrcディレクトリ以下に任意のディレクトリを切り、最低限main.tsの1ファイルさえつくればいい状態になります。

使用環境

記事執筆時点で使用している環境について、記載しておきます。

開発環境

ブックマークレットの開発には、次のような環境を使っています。

  • OS:macOS Monterey 12.6
  • JSランタイム:Node.js v16.11.1
  • パッケージマネージャー:npm v8.0.0

実行するコマンドやインストールするパッケージは、使用するNode.jsのバージョンやパッケージマネージャーに応じてよしなに読み替えてください。

実行環境

ブックマークレットの実行には、次のような環境を使っています。

  • OS:macOS Monterey 12.6
  • ブラウザ:Google Chrome v108.0.5359.94

環境構築

ブックマークレットを開発するための環境を構築します。

パッケージをインストール

開発に必要なパッケージをひととおりインストールします。

npm i -D \
  @munierujp/eslint-config-typescript \
  @rollup/plugin-terser \
  @rollup/plugin-typescript \
  @tsconfig/node16 \
  @types/glob \
  @types/node@^16.11 \
  eslint \
  eslint-plugin-jest \
  glob \
  jest \
  jest-environment-jsdom \
  rimraf \
  rollup \
  rollup-plugin-bookmarklet \
  ts-jest \
  typescript

ただし、使用するパッケージやバージョンは状況に応じて適切なものを選んでください。

  • @tsconfig/node16:使用するNode.jsのバージョンに対応したパッケージを使うこと1
  • @types/glob:使用するglobのバージョンに対応したバージョンを使うこと2
  • @types/node:使用するNode.jsのバージョンに対応したバージョンを使うこと2

package.jsonscriptsを定義

package.jsonに、次のようなscriptsを定義します。

package.json
{
  "scripts": {
    "prebuild": "npm run clean",
    "build": "rollup --config rollup.config.ts --configPlugin typescript",
    "clean": "rimraf dist",
    "predev": "npm run clean",
    "dev": "rollup --config rollup.config.ts --configPlugin typescript --watch",
    "lint": "eslint '**/*.{js,ts}'",
    "test": "jest --passWithNoTests"
  }
}

それぞれの役割は以下の通りです。

  • build:ソースコードをビルドしてブックマークレットを生成
  • clean:ビルド結果を削除
  • dev:開発用のウォッチモードを起動
  • lint:ESLintによる静的解析を実行
  • test:Jestによるテストを実行

tsconfig.jsonを作成

TypeScriptの設定ファイルとして、tsconfig.jsonをつくります。

tsconfig.json
{
  "extends": "@tsconfig/node16/tsconfig.json",
  "compilerOptions": {
    "lib": [
      // see https://www.npmjs.com/package/@tsconfig/node16
      "es2021",
      "dom"
    ]
  }
}

@tsconfig/node16/tsconfig.jsonを継承していますが、DOMを扱うためcompilerOptions.libdomを含めたものを再定義しています。

.eslintrc.jsを作成

ESLintの設定ファイルとして、.eslintrc.jsをつくります。

.eslintrc.js
module.exports = {
  ignorePatterns: [
    'dist/**/*.js'
  ],
  extends: [
    '@munierujp/eslint-config-typescript',
    'plugin:jest/recommended'
  ],
  parserOptions: {
    project: './tsconfig.json'
  }
}

ignorePatternsdist/**/*.jsを指定することで、生成されたブックマークレットには適用されないようにしています。

jest.config.tsを作成

Jestの設定ファイルとして、jest.config.tsをつくります。

jest.config.ts
import type { Config } from 'jest'

const config: Config = {
  preset: 'ts-jest',
  testEnvironment: 'jsdom',
  collectCoverage: true,
  collectCoverageFrom: [
    'src/**/*.ts'
  ]
}

export default config

TypeScriptを扱うためpresetにはts-jestを、DOMを扱うためtestEnvironmentにはjsdomをそれぞれ指定しています。

rollup.config.tsを作成

rollupの設定ファイルとして、rollup.config.tsをつくります。

rollup.config.ts
import terser from '@rollup/plugin-terser'
import typescript from '@rollup/plugin-typescript'
import glob from 'glob'
import type { RollupOptions } from 'rollup'
import bookmarklet from 'rollup-plugin-bookmarklet'

const entryPaths = glob.sync('src/**/main.ts')
const configs: RollupOptions[] = entryPaths.map(entryPath => ({
  input: entryPath,
  output: {
    file: entryPath.replace(/^src\//, 'dist/').replace(/\/(.+)\/main\.ts$/, '/$1.min.js'),
    format: 'iife'
  },
  plugins: [
    typescript(),
    terser(),
    bookmarklet()
  ]
}))

export default configs

ブックマークレットのサンプル

ブックマークレットを開発するための環境が整ったので、いくつかの例を示します。

Hello World

はじめにもっともシンプルな例として、Hello Worldというアラートを表示するブックマークレットをつくります。
まずはsrcディレクトリ以下にhelloディレクトリを切り、その中にエントリーファイルとしてmain.tsをつくります。

src/hello/main.ts
alert('Hello World')

npm run buildまたはnpm run devでソースコードをビルドすると、distディレクトリにブックマークレットが生成されます。

dist/hello.min.js
javascript:!function(){"use strict";alert("Hello World")}();

タイトルをコピー

次にもう少し複雑な例として、ページのタイトルをコピーするブックマークレットをつくります。
まずはsrcディレクトリ以下にcopy-titleディレクトリを切り、その中にエントリーファイルとしてmain.tsをつくります。

src/copy-title/main.ts
import { copy } from './copy'

copy(document.title).catch(error => {
  throw error
})

また、他にも必要なファイルをひととおりつくります。

src/copy-title/copy.ts
export const copy = async (text: string): Promise<void> => {
  await navigator.clipboard.writeText(text)
}

npm run buildまたはnpm run devでソースコードをビルドすると、distディレクトリにブックマークレットが生成されます。

dist/copy-title.min.js
javascript:!function(){"use strict";(async t=>{await navigator.clipboard.writeText(t)})(document.title).catch((t=>{throw t}))}();

サンプルリポジトリ

この記事で紹介している手法は、こちらのリポジトリで実際に採用しているものです。

よければ参考にしてみてください。

次回予告

次回は、ユーザースクリプトをTypeScriptで書く方法について紹介します。

  1. tsconfig.jsonを書くときはTSConfig Basesを使うと便利 - Qiita

  2. TypeScriptの型定義パッケージ(@types/~)はどのバージョンをインストールすればいいのか - Qiita 2

15
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?