TypeScriptでESMで動くJavaScript製ライブラリ(remark-shortcodes)を使う
TypeScriptでESMで動くJavaScript製ライブラリ(remark-shortcodes)を使う
TypeScriptで、remarkと、そのプラグインremark-shortcodesを使おうと思ったところハマったのでメモ。
経緯は飛ばして、結論だけを見たいときには、まとめをご覧ください。
経緯
JavaScriptのライブラリを読み込みたいが型定義ファイルがないのでエラー
まずは、次のような最小限のコードで動くかどうか実験。
import { unified } from 'unified';
import parse from 'remark-parse';
import shortcodes from 'remark-shortcodes';
const markdown = `# title
Example paragraph
[[ testTag id="abc" ]]
`;
const test = () => {
const tree = unified().use(parse).use(shortcodes).parse(markdown);
console.log(tree);
};
test();
上記を入力すると、VS Codeで次のようなエラーが発生。
Could not find a declaration file for module 'remark-shortcodes'. 'workdir/node_modules/remark-shortcodes/index.js' implicitly has an 'any' type.
Try `npm i --save-dev @types/remark-shortcodes` if it exists or add a new declaration (.d.ts) file containing `declare module 'remark-shortcodes';`
「.d.ts」でimport文があるとグローバル宣言ができずエラー
remark-shortcodesの型定義ファイルがないので、作成する必要がありそう。
いろいろなやり方があるようですが、今回は、「index.ts」と同じ階層に「index.d.ts」を作成してみることに決定。
そこで、下記のような型定義ファイルを作成。
import { Plugin } from 'unified';
import { Root } from 'remark-parse/lib';
declare module 'remark-shortcodes' {
const shortcodes: Plugin<string[][] | void[], string, Root>;
export default shortcodes;
}
それでもエラーが消えず。
Could not find a declaration file for module 'remark-shortcodes'. 'workdir/node_modules/remark-shortcodes/index.js' implicitly has an 'any' type.
Try `npm i --save-dev @types/remark-shortcodes` if it exists or add a new declaration (.d.ts) file containing `declare module 'remark-shortcodes';`
たしか、importを書くと、単にdeclare module
では、globalな定義にならないのが原因と推測。
型定義ファイルをVS Codeでは認識したのにts-nodeで認識できずにエラー
そこで、型定義ファイルの書き方を少し修正して、importをdeclare module
の内側に移動。
declare module 'remark-shortcodes' {
const shortcodes: import('unified').Plugin<
string[][] | void[],
string,
import('remark-parse/lib').Root
>;
export default shortcodes;
}
これで、VS Codeではエラーが消えました。
ところが、ts-nodeを実行すると、下記のエラーが発生。
"scripts": {
"ts-node": "ts-node",
},
> npm run ts-node ./src/index.ts
src/index.ts:3:24 - error TS7016: Could not find a declaration file for module 'remark-shortcodes'. 'workdir/node_modules/remark-shortcodes/index.js' implicitly has an 'any' type.
Try `npm i --save-dev @types/remark-shortcodes` if it exists or add a new declaration (.d.ts) file containing `declare module 'remark-shortcodes';`
3 import shortcodes from 'remark-shortcodes';
~~~~~~~~~~~~~~~~~~~
ts-nodeで、型定義ファイルが読み込めていない様子。
ライブラリでES modulesを使用しているためエラー
いろいろ調べていくと、独立した型定義ファイルをts-nodeで読み込ませるには--files
オプションを付ければ良いらしい。
参考:https://github.com/TypeStrong/ts-node#files
そこで、これを設定して再実行。
"scripts": {
"ts-node": "ts-node --files",
},
> npm run ts-node ./src/index.ts
Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: workdir\node_modules\unified\index.js
require() of ES modules is not supported.
require() of workdir\node_modules\unified\index.js from workdir\lib\postmd_remark\index.ts is an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which defines all .js files in that package scope as ES modules.
Instead rename index.js to end in .cjs, change the requiring code to use import(), or remove "type": "module" from workdir\node_modules\unified\package.json.
たしかに、型定義エラーは消えましたが、今度はES modules is not supported
というエラーが発生。
手元の設定ではCommon JSを使う設定になっており、それが原因でエラーになっているっぽい。
ES Modules化したら型定義ファイルが読み込めないエラーが復活
先ほどのブログを参考に、設定を変更。
そして、ts-node
ではなく、node --loader ts-node/esm ./src/index.ts
で呼び出すと良いらしいということで、試してみる。
"scripts": {
"ts-node": "ts-node --files",
},
"type": "module", //追加
"compilerOptions": {
"module": "esnext", // ← 元々"module": "commonjs", だったのを修正
}
> node --loader ts-node/esm ./src/index.ts
src/index.ts:3:24 - error TS7016: Could not find a declaration file for module 'remark-shortcodes'. 'workdir/node_modules/remark-shortcodes/index.js' implicitly has an 'any' type.
Try `npm i --save-dev @types/remark-shortcodes` if it exists or add a new declaration (.d.ts) file containing `declare module 'remark-shortcodes';`
3 import shortcodes from 'remark-shortcodes';
~~~~~~~~~~~~~~~~~~~
また、最初の「型定義ファイルを読み込めない症状」が復活してしまいました・・・。
ts-nodeを起動して、ようやくエラーが消えた
型定義ファイルを読み込みつつ、ES Moduleとして実行することができないかts-nodeのドキュメントを探していたら、ts-node
コマンドの代わりにts-node-esm
コマンドを実行する方法を発見。
"scripts": {
"ts-node-esm": "ts-node-esm --files",
},
"type": "module", //追加
> npm run ts-node-esm ./src/index.ts
{
type: 'root',
children: [
{
type: 'heading',
depth: 1,
children: [Array],
position: [Object]
},
{ type: 'paragraph', children: [Array], position: [Object] },
{ type: 'paragraph', children: [Array], position: [Object] }
],
position: {
start: { line: 1, column: 1, offset: 0 },
end: { line: 5, column: 1, offset: 50 }
}
}
これで、ようやく動きました。
まとめ
- 型定義ファイルを読み込ませたい
テキトーな場所に「.d.ts」ファイルを作成し、ts-node起動時に--files
オプションを付ける
- TypeScriptのコードを、ES Modulesで起動したい
package.json、tsconfig.jsonをES Modulesが動くように設定し、ts-node-esm
コマンドを使う
最終的な設定
import { unified } from 'unified';
import parse from 'remark-parse';
import shortcodes from 'remark-shortcodes';
const markdown = `# title
Example paragraph
[[ testTag id="abc" ]]
`;
const test = () => {
const tree = unified().use(parse).use(shortcodes).parse(markdown);
console.log(tree);
};
test();
declare module 'remark-shortcodes' {
const shortcodes: import('unified').Plugin<
string[][] | void[],
string,
import('remark-parse/lib').Root
>;
export default shortcodes;
}
"scripts": {
"ts-node-esm": "ts-node-esm --files",
},
"type": "module",
"compilerOptions": {
"module": "esnext", // ← 元々"module": "commonjs", だったのを修正
}
あとは、下記のコマンドで、エラーなく実行できました。
npm run ts-node-esm ./src/index.ts