11ty + ts + rollup
TL;DR
https://github.com/kobakazu0429/11ty-typescript-rollup
解説
11tyでtsを使うには各ファイルから型を捨てつつcjsにできれば良い
問題は近年ライブラリのesm only化が進んでいることで、node_modules側もトランスパイルしておく必要がある
FYI: Pure ESM package
単純にesm→cjsにするとentryファイルなどはimport→requireにいい感じにinteropされるが、その先は何もされていない
例
import { VFile } from "vfile"
const file = new VFile("test");
これをesbuildなどでビルドすると
build.js
const esbuild = require("esbuild");
async function build() {
const option = {
entryPoints: ["entry.ts"],
outdir: "output",
format: "cjs"
};
await esbuild.build(option);
}
build();
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __markAsModule = (target) => __defProp(target, "__esModule", { value: true });
var __reExport = (target, module2, desc) => {
if (module2 && typeof module2 === "object" || typeof module2 === "function") {
for (let key of __getOwnPropNames(module2))
if (!__hasOwnProp.call(target, key) && key !== "default")
__defProp(target, key, { get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable });
}
return target;
};
var __toModule = (module2) => {
return __reExport(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", module2 && module2.__esModule && "default" in module2 ? { get: () => module2.default, enumerable: true } : { value: module2, enumerable: true })), module2);
};
var import_vfile = __toModule(require("vfile"));
const file = new import_vfile.VFile("test");
となる
最後の2行がメインの部分
__toModule(require("vfile"))
に注目する
一見トランスパイルで読み込めそうだが、vfile自体がバンドルされていない(esmで配布)なので、次のようなエラーが出る
node:internal/modules/cjs/loader:1112
throw new ERR_REQUIRE_ESM(filename, parentPath, packageJsonPath);
^
Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: /Users/kazu/workspace/vfile/node_modules/vfile/index.js
require() of ES modules is not supported.
require() of /Users/kazu/workspace/vfile/node_modules/vfile/index.js from /Users/kazu/workspace/vfile/output/example.js 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 /Users/kazu/workspace/vfile/node_modules/vfile/package.json.
at new NodeError (node:internal/errors:370:5)
at Object.Module._extensions..js (node:internal/modules/cjs/loader:1112:13)
at Module.load (node:internal/modules/cjs/loader:975:32)
at Function.Module._load (node:internal/modules/cjs/loader:816:12)
at Module.require (node:internal/modules/cjs/loader:999:19)
at require (node:internal/modules/cjs/helpers:93:18)
at Object. (/Users/kazu/workspace/vfile/output/example.js:19:31)
at Module._compile (node:internal/modules/cjs/loader:1095:14)
at Object.Module._extensions..js (node:internal/modules/cjs/loader:1124:10)
at Module.load (node:internal/modules/cjs/loader:975:32) {
code: 'ERR_REQUIRE_ESM'
}
解決策として、vfileごとバンドルしてしまう手などが考えられる
解決手順
Googleで検索
調べているとUsing esbuild with 11tyという記事に出会った。
これは11tyのイベント(https://www.11ty.dev/docs/events/)をフックにesbuildでビルドするもの。
BEFOREBUILD
The beforeBuild event runs every time Eleventy starts building, so it will run before the start of each stand-alone build, as well as each time building starts as either part of --watch or --serve. To use it, attach the event handler to your Eleventy config:module.exports = function (eleventyConfig) { eleventyConfig.on('beforeBuild', () => { // Run me before the build starts }); };
beforeBuildがあるのでここでビルドしたら良いのではと考えた
その方向で実装してみると、記事データの取得APIがビルドの前に走ってしまい、こける問題が発生
これはhttps://github.com/11ty/eleventy/issues/1488と類似の問題だと思う
ただこのissueは非アクティブで、まだ自分の英語力に自信がないので
- docs/ソースコードを読んだら解説があるかもしれない
- そもそもバグじゃない
- 上手に自分の意見を伝えられそうにない
のような意識があり、見るだけで終わってしまった
理想はソースコードを読んだり、PRを投げることだが、そこに至れない
これは今後枷になりそうなので、どうにかしたいと思ってる...
11ty + ts を目指す他のプロジェクトを探す
https://github.com/jhukdev/11tyby などが見つかった
tsだけなら問題なく動くが、esm onlyなライブラリを試すとうまくいかない
今思うと少し手を加えたら動いたかも
諦めて頑張る
本題
サンプルリポジトリはtl;drでも書いたがここ
メインのrollup.config.jsを以下に示す
import path from "path";
import glob from "tiny-glob/sync";
import nodeResolve from "@rollup/plugin-node-resolve";
import typescript from "rollup-plugin-typescript2";
import commonjs from "@rollup/plugin-commonjs";
import json from "@rollup/plugin-json";
const tsFiles = [
...glob(
"./src/**/*.ts"
),
].map(p => path.resolve(path.join(__dirname, p)));
const entryFiles = tsFiles.map(v => {
const filename = path.basename(v, path.extname(v)) + ".js"
const outputPath = path.join(__dirname, "build", path.relative(path.join(__dirname, "src"), path.dirname(v)))
return { entry: v, outputPath, filename }
});
const config = /** @type import("rollup").NormalizedInputOptions */ ({
plugins: [
typescript(),
nodeResolve({
preferBuiltins: true,
}),
commonjs(),
json(),
]
});
export default entryFiles.map(f => ({
...config,
input: f.entry,
output: [{
format: "cjs",
file: path.join(f.outputPath, f.filename),
exports: "auto"
}]
}));
あまり大したことはしていなくてentryファイルをglobで列挙して、それをディレクトリ構造を維持しつつ、build/に出力するだけ
また各ファイルにライブラリをバンドルすることでesm onlyでも問題なし
ただ、node-canvasを使っておりこちらには.nodeというファイルが含まれていた(自分が見たのは初めて)
調べてみるとnative moduleというものでc++をnodeで実行できるようにしたものっぽい
rollup-plugin-nativesというpluginもあったが上手に動かなかったのでexternalにいれて、バンドルしないことにした(node-canvasがesm onlyではないため出来た)
const config = {
...
external: [
"canvas"
]
}
rollupでバンドルしたコードは全てサーバーサイドなどで動くのでminifyはしていない
また自分の環境だとファイル数が少し多いせいかそれなりにビルド時間がかかっている
- rollup-plugin-typescript2が型チェックしているからかも(あんまり調べてないが、型を落とすだけで型チェックは
tsc --noEmit
に任せる手がある) - こちらも調べていないが多分直列ビルドなので並列にビルドしたら早くなるかも
- 実は今回rollupを初めて使っていて、普段使っているwebpackではcacheが効いているから早い気がしているのでそこも調べたい
ここら辺をすれば多少はマシになりそう
あとは頻繁にビルド(ts→cjs)するものでもないので気にし過ぎなくてもいいかも
最後に
この記事が公開された段階ではまだblogに適用はしていないので、今度運用していく中で何かしら不具合に出会うかも
そしたら追記します
2021/08/06 追記
tl;dr
tsxを使えるようにした
リポジトリは同じ
PRはこれ #1
解説
@rollup/plugin-babel
とbabel-plugin-transform-jsx-to-htm
でtsxをhtmを使った形に変換してる
さらにpreact-render-to-string
を使って文字列化してるだけ
あとは型エラーが出るのでとりあえず @types/react を入れたけど、正解がわからないので誰か教えてください