generated at
UserScriptをbundleするDeno script
2024-01-09 12:11:41 deno bundleの代替コードを推奨

主にScrapboxのUserScriptを一つのJSファイルにまとめるDeno script
一般的なbundleにも使える
一部のESModuleをまとめないで import のままにする事もできる
既に自分のprojectにあるコードをimport先にすることができる

用途
たくさんのESModuleに分かれた他者のUserScriptを自分のprojectに移植するときに使う
すべてのファイルを手作業でコピペするのは地獄なので、一つにまとめたほうがいい
2021-06-08 22:21:21 script読み込みの高速化以外にメリットなくね?takker
確かに自分のprojectにコピペすれば、そのscriptに誰かが悪意のあるコードを入れられなくなるが、すでに悪意のあるコードが混入している場合には無力
1ファイルずつ、自分で変なコードが混じっていないか確認する必要がある

既知の問題
dynamic importに相対パスを使っているとうまくコードが動かなくなる
workerのソースも同様
scrapboxのUserScriptの場合、 ../ /api/code から始まるパスなら大丈夫
./ で同じページ内のファイルを指定していたときに事故る
tree shakingがうまく働かない?
deno bundleだと↓はうまくコードを省いてくれるが、本ページのscriptだと余計なものまで含まれてしまう
script.js
export {lightFormat, sub, getUnixTime} from 'https://deno.land/x/date_fns@v2.15.0/index.js';
importmap.json
{ "imports": { "../date-fns.min.js/script.js": "https://deno.land/x/date_fns@v2.15.0/index.js" } }
いやちゃんとtree shakingされた
気のせいだった?
reloadしなかったせい?

使い方
以下を実行する
sh
deno run -A --unstable https://scrapbox.io/api/code/takker/UserScriptをbundleするDeno_script/build.ts filename.js --bundle --minify --charset=utf8 --outfile=filename.min.js --external=xxx --external=yyy --importMap=map.json
コマンドライン引数はesbuildのものを流用できる
filename.js
bundleしたいscriptのentry point
localのファイルでもインターネット上のファイルでも可
--outfile
bundle後のJSファイルのファイル名
指定しないとコードが標準出力に出力される
パイプを使って | pbcopy | xsel をつなげれば、そのままクリップボードにコピーできる
--external
bundleに含めたくないESModuleのパス名
JSファイルに書き込まれているパス名をそのまま書く
相対パスで指定されていたら相対パスで、絶対パスで指定されていたら絶対パスで書く
挙動はただの文字列判定
import {...} from 'xxx' xxx --external に指定した文字列と一致した場合、そのmoduleをbundleから除外する
--importMap
import mapを指定できる
localファイルでもインターネット上のファイルでも可
あとはesbuildの引数と同じ
source mapを生成したいときは --sourcemap をつける
JSファイルに埋め込むときは --sourcemap=inline にする
source mapを生成しておくと、元の変数名を参照できるのでデバッグが楽になる

関連

実装したいこと
denomanderで親切なヘルプを作りたい
esbuildのオプション引数を全部羅列するのが面倒か

2022-02-01
13:52:06 コード雑だな……
直したいtakker
2021-06-05
12:45:46
build errorが出てもcacheを消す
refactor: 型をつける
12:03:40
APIとCLIを分離した
esbuildのversionを固定した
2021-05-28
22:36:22 esbuildのcache directoryを消すようにした
2021-05-26
18:17:27 --import-map --importMap にした
18:08:33 --external が一つのときにバグっていたのを直した

code
build.ts
import { run } from './script.ts'; import { parse } from 'https://deno.land/std@0.87.0/flags/mod.ts'; const {_: [entryFilePath], importMap, external, ...rest} = parse(Deno.args); if (typeof entryFilePath === 'number') throw Error('entryFilePath must be string'); let imports: {[key: string]: string} = {}; if (importMap) { if (/^https?:\/\//.test(importMap)) { const res = await fetch(importMap); imports = await res.json(); } else { imports = JSON.parse(await Deno.readTextFile(importMap)); } } await run(entryFilePath, imports, {external, ...rest});
script.ts
import { exists } from "https://deno.land/std@0.97.0/fs/mod.ts"; import { build, stop } from 'https://deno.land/x/esbuild@v0.12.6/mod.js'; import type { BuildOptions, BuildResult } from 'https://deno.land/x/esbuild@v0.12.6/mod.js'; import { cache } from 'https://raw.githubusercontent.com/takker99/esbuild-plugin-cache/master/deno/mod.ts'; // 特定のpropertyを削るやつ type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>; export async function run(filename: string, imports: {[key: string]: string}, {external, ...rest }: Omit<BuildOptions, 'entryPoints' | 'platform' | 'plugins'>) { let useTempFile = false; let result: BuildResult | undefined = undefined; try { if (/^https?:\/\//.test(filename)) { const tempname = `index-${Math.random()}.ts`; await Deno.writeTextFile(tempname, `import '${filename}';export * from '${filename}';`); filename = tempname; useTempFile = true; } const options: BuildOptions = { entryPoints: [filename], platform: 'neutral', plugins: [cache({directory: './cache', importmap: {imports}})], external: Array.isArray(external) ? external : (external ? [external] : []), ...rest }; result = await build(options); stop(); } catch(e) { throw e; }finally { //後始末 if (useTempFile) await Deno.remove(filename); if (await exists('./cache')) await Deno.remove('./cache', { recursive: true }); } return result; }

#2024-01-09 12:12:07
#2022-02-01 13:58:24
#2021-08-25 14:14:00
#2021-07-06 06:07:05
#2021-06-26 23:49:37
#2021-06-08 22:23:02
#2021-06-05 13:15:59
#2021-05-28 22:02:52
#2021-05-26 16:00:52
#2021-05-25 06:11:06