BLOG ブログ
フットサルのワールドカップが終わってしまって、喪失感のあるkitoです。
(リカルジーニョが代表引退にして初優勝&MVP。。。なんてできたストーリーなんだ🙌)
更に夏の終わりも感じてヨルシカの「ただ君に晴れ」をヘビロテして過ごしている今日このごろ🥺
「これから先の人生、躓くことなんて当たり前だ。それでも、ただ君に、晴れぬ空などないことを」
ヨルシカ – ただ君に晴れ
感傷に浸りすぎて、ダークサイドに堕ちないよう前向きに生きます。
今回のブログは、
textlintを利用して文章チェックを行うChrome拡張機能を作成
したので、紹介いたします。
文章のチェック負荷を下げたいよー
開発チームのサポート対応では、誰が対応したとしても、
文章の品質を保った回答とするために、他者による文章チェックを行ってます。
また、この開発ブログ等、各種の文章も他者チェックすることがあります。
ただ、このチェック、、、
確認する人に依存して、指摘内容や気付くポイントが変わってしまう状況になってました。
なんとかしたいよなぁと🥺🥺🥺
人じゃなくても良いことは、機械に任せる
そうです。そこで、 textlint!
開発で勃発するソースコード関連の宗教戦争の解決として
機械的な自動フォーマットを用いるのと同様に、文章も機械的にチェックさせてしまおうとなりました。
どうせなら、全社に展開!textlintを簡単に使いたいよー
文章を書くのは、開発チームだけではないので、全社展開も視野に入れました。
ならば、textlint作者様にて提供されている textlint editor で勝てる!と思ったのですが、
導入つまずかないかなぁと。。。
ということで、導入・操作が簡単なChrome拡張機能でも作ってみようと思い立ち、
作ったのがコレ。
(Chromeウェブストアから追加するのみ。文章を入力したら、チェックされて結果表示されるだけ)
ここからは、作った過程の一部を紹介します✨
Chromeの拡張機能を作ってくよー
開発には、svelte-tailwind-extension-boilerplateを使いました。ありがたや…🙏🙏🙏
まずは、TypeScript化して、
node scripts/setupTypeScript.js
ESLintとPrettierを追加。
npm add -D prettier prettier-plugin-svelte
npm add -D eslint eslint-plugin-svelte3
npm add -D @typescript-eslint/parser @typescript-eslint/eslint-plugin
※以下のサイトを参考にESLintとPrettierの設定ファイルとscriptsを追加。ありがたや…🙏🙏🙏
つづけて、Chrome拡張機能のmanifest v3に対応するべく、
rollup-plugin-chrome-extensionを最新にしておきます。
- "rollup-plugin-chrome-extension": "^3.5.2",
+ "rollup-plugin-chrome-extension": "^3.6.2",
あわせてChrome拡張機能のmanifestを v3の形式 に変更します。
※v3の形式にしておくと、Chrome Web Store の審査が早く終わります。
コンポーネントを作ってくよー
特段難しいことは、しておらず。。。
素直に入力用と結果表示のコンポーネントとテストコードを作りました。
- 入力用のテキストエリア
(onKeyupでイベントをdispatch) - 結果件数表示
- 表示件数
- 結果コピーボタン
- lint結果表示部
- lintのタイトル
- 対象部の内容
- 対象の文章
必要となる情報を変数に切り出し、$
をつけてリアクティブな処理にしてあります。
lintの結果に変化があれば、自動的に内容が切り替わります。便利~😇
ブラウザー用にtextlintをweb worker化するよー
ブラウザーでtextlintを利用するために、 @textlint/compiler でweb workerにコンパイルします。
以下のサイトを参考に、いくつかのルールを追加してコンパイルしました。ありがたや…🙏🙏🙏
package.json
"scripts": {
"compile-textlint-worker": "textlint-script-compiler --output-dir ../../src/third-party --metadataName 'textlint-worker' --metadataNamespace 'http://localhost:3000/' --metadataHomepage 'http://localhost:3000/'"
},
"dependencies": {
"@textlint/script-compiler": "^0.12.1",
"textlint-rule-preset-ja-spacing": "^2.2.0",
"textlint-rule-preset-ja-technical-writing": "^7.0.0",
"textlint-rule-prh": "^5.3.0"
}
ハマリポイント
textlint-rule-prhで利用する辞書ファイル(yml)内で、別の辞書をimportして読み込んでいると、
web worker使用時にfs関連のエラーとなったので、個別で読み込みしました。
なお、辞書ファイルは、icsmedia社のルールとWEB+DB_PRESSのルールを利用してます。ありがたや…🙏🙏🙏
.textlintrc
"prh": {
"rulePaths": [
"./prh_rules/icsmedia/prh_idiom.yml",
"./prh_rules/icsmedia/prh_open_close.yml",
"./prh_rules/icsmedia/prh_redundancy.yml",
"./prh_rules/icsmedia/prh_duplicate.yml",
"./prh_rules/icsmedia/prh_cho_on.yml",
"./prh_rules/icsmedia/prh_corporation.yml",
"./prh_rules/icsmedia/prh_web_technology.yml",
"./prh_rules/WEB+DB_PRESS.yml"
]
},
Popupを実装するよー
処理の流れとしては、次の通り単純です。
- 文章入力のEventが発生する
- textlintを実行する
- 結果をコンポーネントに渡す
処理のポイントだけ、以下に抜粋します。
Popup.svelte – script部
(svelte、はじめて書いてます。不安しかない🥺🥺🥺)
let targetText: string;
let lines: string[] = [];
function lintText(event: CustomEvent<string>) {
targetText = String(event.detail.text);
lines = targetText === `` ? [] : targetText.split('\n');
}
const textlintWorker = new Worker('../third-party/textlint-worker.js');
let lintStatus: string;
let lintResults: lintMessages;
let errorMessage: string;
textlintWorker.onmessage = (event: MessageEvent) => {
lintStatus = event.data.command as string;
switch (lintStatus) {
case 'init':
break;
case 'lint:result':
lintResults = event.data.result as lintMessages;
break;
default:
lintStatus = '';
break;
}
};
textlintWorker.onerror = (event: ErrorEvent) => {
lintStatus = 'error';
errorMessage = event.message;
console.error(event);
};
$: {
if (lines.length === 0) {
lintStatus = '';
}
if (lines.length > 0) {
textlintWorker.postMessage({
command: 'lint',
text: targetText,
ext: '.md',
});
}
}
Popup.svelte – 表示部
<!-- textlintする文章 -->
<TextArea on:changedText={lintText} />
{#if lintStatus === 'init' && lines.length > 0}
<p>...校正中</p>
{:else if lintStatus === 'lint:result'}
<!-- 校正結果概要 -->
<TextLintedSummary messages={lintResults.messages} {lines} />
{#if lintResults.messages.length > 0}
<!-- 校正結果 -->
<TextLintedListItem messages={lintResults.messages} {lines} />
{/if}
{:else if lintStatus === 'error'}
<p style="color: red">{errorMessage}</p>
{/if}
Chrome ウェブストア に公開するよー
最後にbuildして、Chrome ウェブストアに公開し、社内だけの限定公開としました。
(手続きがなかなか大変。。。笑)
textlintの結果を取捨選択できる文章力が必要であるため、「意識して」読み・書きして文章力を強化するとともに、
実際に使って、効果を検証できればと思っています。
人じゃなくても良いことは、ドンドン機械に任せて効率あげていきたいですね。
それにしても、、、
日本語ってムズカシイ🤷♂️
それでは、また。