GulpとTypeScriptでブックマークレット開発環境を作ってみた
- 開発
- ブックマークレット
- Jest
- Gulp
- TypeScript
Bookmarkletってご存じですか? 最近ではあまり聞かないですが、ブラウザのブックマークに登録しておくと選択したらちょっと便利な機能があるやつです。例えば簡単に文字列を整形してコピーしたり、凄く単機能な拡張と思えば近いでしょうか。
作ろうと思った背景
前にも書いたように最近ちょこちょことブログをVuePressに移行しててよしぼちぼち完成だ! と思った矢先にアルファ版だったVeuPressが正式版になって同時にブログ関係のプラグインもアップデートされました。そしたら今まで進めてた移行用のものが使えなくなってしまった。
あとは、よくある他サイトのリンクをブログカードみたいなリンク表示するやつをVueのコンポーネントで実装しようと思ったらあまり上手くいかず。あとは静的に表現したかったのもあって、それならブックマークレットでOGPとかからデータ抜いてしまえ、という苦肉の策が背景です。
アプローチ
ブックマークレットの正体はJavaScriptの即時関数(無名関数とも言う)。なので、ペロっと書いてしまってちゃんと動けばそれでいいんですが、せっかくなので「ぼくのかんがえたさいきょうのブックマークレット開発環境」を作ってみることにしてみました。
それを目指すべきてんこもりの恩恵としては
- Linterが効く(ESlint)
- Formatterが効く(Prettier)
- 型(とその補完)が効く(TypeScript)
- Minify(Uglify)が効く
- TDDできる
これらをまとめてビルドするのはNPM Scriptで十分かと思ったんですが、せっかくなのでGulpでやることにしました。
つまり、GulpでESLintを通したTypeSciprtをコンパイル、Uglifyしてソースとは別のディレクトリに吐かせればいい。というわけですね。
Gulpとは
Gulp。ちょっと前に大流行したタスクランナー的なやつです(そのころはWebpackとかありませんでしたね)。v4が出る出るといってモタモタしてたらWebpackの登場で下火になった印象です。なので調べるとv3の情報がけっこうでます。
雑に言うとgulpfile
というタスクを書いたファイルに使うプラグインと実行するタスクを書いて、コマンドから実行するやつ、という感じです。gulpfile
はわりと読みやすいのが人気が出た理由でしょうか。
セットアップ
まず今回の仕様に合わせてセットアップしていきます。ここではNode.jsとyarnが入っている前提です。
$ yarn init
して適当に対話的に設定をします。
gulpに依存するものを入れていきます。
$ yarn add -D gulp gulp-eslint gulp-replace gulp-typescript gulp-uglify
ただこれだけでは全然足りないのでどんどん入れます。
次はTypeScriptまわり
$ yarn add -D typescript typescript-require ts-node
typescript-require
を入れるとGulpfileをTSで書くことが可能になります。
型定義ファイルも入れます。
$ yarn add -D @types/gulp @types/gulp-replace @types/gulp-uglify @types/jquery @types/node
jQueryは迷ったんですが、ブックマークレットでjQueryを使う方法もあるので入れておきます。
続いてESlint(+Prettier)まわり。今回はESlintの中でPrettierをかけます(最近それが良いのか分離したほうが良いのか悩んでますが、今回はこれで
$ yarn add -D eslint eslint-config-prettier eslint-plugin-prettier @typescript-eslint/eslint-plugin @typescript-eslint/parser
多分最小構成だとこれなんですが、standardとかに寄せたいのでさらに
$ yarn add -D eslint-config-standard eslint-plugin-import eslint-plugin-node eslint-plugin-promise eslint-plugin-standard
これでひとまず全部ですかね。ちなみにJestでテストを書くことも考えてたんですが、今回は割愛します。
(余裕があれば後日やる)
ディレクトリ構成はこんな感じです
├── dist
│ └── sample.js
├── src
│ └── sample.ts
├── gulpfile.ts
├── package.json
├── tsconfig.json
└── yarn.lock
/src
ディレクトリで開発し、ビルドした成果物は/dist
に入るようにします。
ESlint設定
とりあえず一例ですが、ESlintの設定です。さきほど入れたものからわかるように、ESlint経由でTypeScriptも対応させ、Prettierも中で動かします。eslintrc.js
module.exports = {
env: {
browser: true,
node: true,
es6: true
},
globals: {
jQuery: true
},
parser: '@typescript-eslint/parser',
parserOptions: {
sourceType: 'module',
},
plugins: [
'@typescript-eslint',
'prettier',
],
extends: [
'standard',
'prettier',
'prettier/@typescript-eslint',
],
rules: {
// サンプルです
'prettier/prettier': ['error', {
semi: false,
}],
'no-eval': 'error',
'@typescript-eslint/no-use-before-define': ['error', { functions: false, classes: true, variables: true }]
},
}
rules
のところはサンプルですが、それ以外の設定はおおむねこんな感じでしょうか。
tsconfig
TypeScript用の設定としてtsconfig.json
を用意する必要があります。通常は
$ tsc --init
を実行することで作られます。今回はこんな感じにしました。
tsconfig.json
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"removeComments": true,
"strict": true,
"noImplicitAny": false,
"alwaysStrict": false,
"baseUrl": "./src/",
"paths": {
"#/*": [
"*"
]
},
"esModuleInterop": true
}
}
gulpfile
Gulpはgulpfile.js
でタスクを定義するんですが、typescript-require
を入れたのでgulpfile.ts
にTypeScriptで書いていきます。
gulpfile.ts
import gulp from 'gulp'
import eslint from 'gulp-eslint'
import ts from 'gulp-typescript'
import uglify from 'gulp-uglify'
import replace from 'gulp-replace'
const tsProject = ts.createProject(`tsconfig.json`)
const srcDir = `src`
const destDir = `dist`
export default () => {
return gulp
.src(`${srcDir}/*.ts`)
.pipe(eslint({ useEslintrc: true }))
.pipe(eslint.format())
.pipe(eslint.failAfterError())
.pipe(tsProject())
.pipe(
uglify({
mangle: true,
compress: true,
})
)
.pipe(replace(/^(.*)$/, `javascript:$1`))
.pipe(gulp.dest(destDir))
}
と、こんな感じにしてみました。tsで書いてますが、tsの要素ないですね。..
ちなみにGulpですが、v3まではgulp.task
としてタスクを設定していましたが、v4からはexport
のJSそのままの書き方ができます。
残念ながらこのへんの情報が少ないです。現在のv4でもgulp.task
でも書けるのでどちらでもいいのですが、どっちが良いのかイマイチわかりません。でもせっかくなので新しい書きかたでやりたいですね。
サンプルで実行テスト
こんな感じで開発環境ができたのでちゃんとできるかやってみましょう。/src
の中にsample.ts
を作って書いてみます。
sample.ts
;(() => {
const title = document.title
prompt(`title is`, title)
})()
期待している動作は、ブックマークレットを実行したときに開いているページのタイトルを取得して、プロンプトに表示する、という動作です。
プロンプトに表示しておくのはコピー&ペーストしやすいようにですね。
ちなみに最初の行の;(() => {
ってなんだ? と思うかもしれませんが、アロー関数で即時関数を作っています。
そしたらコマンドで
$ gulp
を叩きます。デフォルトタスクが走りますのでgulpfile
に書いたとおり
1. リント + フォーマット
2. TypeScriptをJSに変換
3. JSを圧縮
4. 最初にjavascript:
という文字列を追加(ブックマークレットとして動作させるため)。
5. /dist
ディレクトリに出力
が順番に実行されます。
/dist
を見るとsample.js
が出力されていて中身は
sample.js
javascript:!function(){var t=document.title;prompt("title is",t)}();
となっているでしょう。
これをブラウザのブックマークとして登録します。
適当なブックマークを作って、URLにsample.js
をコピー&ペーストするだけです。
そしたら適当なページを開いて実行してみます。プロンプトが表示されてページのタイトルが表示されたら成功です。
感想
実は変換まわりの設定が上手くいかずけっこうてこずりました。あとは今やTypeScriptで即時関数を作ることなんてなかったのでよくわからず苦戦しました。でもなんとかうまくいって良かったです。
本当のことを言えばテストを導入してTDDで作れるようにも考えたんですが、どのような風にテスト環境を作ったらいいのかに悩んだのでいったんテストは忘れます。おそらくJest+Puppetterが良さそうかな、と見当をつけています。
(追記: 書きました)。
ブックマークレットをテスト駆動開発する - Trial and Spiral
ブックマークレット自体はそんな複雑なことを書くことなんてほとんどないので、この環境はオーバーキル気味ですが、いったん環境を作ってしまえば使えるし、今さらES6以前の書き方なんて考えたくもないので結果的には良かったんじゃないかな、と思っています。
というかすでに必要なブックマークレットができてしまったので無用の長物になってしまっているのは内緒です。
余談ですが、ここまでやってあらためてググってみたらほとんど同じことを考えていた人がすでに居らっしゃったようです。
TypeScriptを使ってgulpでブックマークレット開発(BoW) - Qiita