html形式の自己展開書庫

厳密に言う自己展開圧縮書庫。htmlなのでbrowserで書庫の中身を取り出す事が可能です。**自己展開という事で外部js不要**。programを以下に示す。御覧の通りhtml+JavaScriptなのでbrowserで動作させます(**CompressionStream**を実装しているbrowser専用)。
```html:arc.htm
<input multiple type=file id=i><script>
//書庫のhtml部品群
const D0=`<meta charset=l1><noscript id=s>`,
D1='</noscript><script>new Response(new Blob([Uint8Array.from(s.innerText',
D2=',(c,i)=>(i=c.charCodeAt()%65533)>>8?129+" \x82\x83\x84\x85\x86\x87\x88\x89\x8A\x8B\x8C \x8E  \x91\x92\x93\x94\x95\x96\x97\x98\x99\x9A\x9B\x9C \x9E\x9F".indexOf(c):i)]).stream().pipeThrough(new DecompressionStream("deflate-raw"))).arrayBuffer().then((A,a=0)=>{for(A=new Uint8Array(A);A[a]+1;l.innerHTML+=`<li><a download="${f}"href=${URL.createObjectURL(new Blob([A.subarray(a,a+=d)]))}>${f}</a> ${d}`){var c=32,d=A[a++],f=d>>5;for(d&=31;f--;c*=256)d+=c*A[a++];for(f=a;A[a++];);f=new TextDecoder().decode(new Uint8Array(A.subarray(f,a-1)))}})<\/script><input type=button value=save onclick="for(let a of links)a.click()"><ol id=l>';

i.onchange=async function(e){
	var a=0,c,d,f=this.files,i,r,n=[],o,p;
	//file名かき集める
	for(e of f)n.push(r=new TextEncoder().encode(e.name)),a+=r.length+e.size+6;
	o=new Uint8Array(a);a=0;
	//file尺とfile中身かき集める
	for(e of f){
		e=new Uint8Array(await e.arrayBuffer(i=a));
		for(c=32,d=r=e.length;d-=o[a++]=d&c-1;c=256)d/=c,o[i]+=32;
		o.set(c=n.shift(),a,a-=~c.length);
		o.set(e,a,a+=r)
	}
	//それら全て圧縮
	o=new Uint8Array(await new Response(new Blob([o.subarray(0,a)]).stream().pipeThrough(new CompressionStream("deflate-raw"))).arrayBuffer());
	//文字頻度表作成
	f=new Uint32Array(256);
	for(e of o)f[e]++;r=D1;

	//escape文字の候補選出
	for(e=256;e;a>c&&e^13&&(a=c,d=e))c=f[--e];
	c=f[13];n=d>1?1:2;f=d^96?"`":'"';
	e=String.fromCharCode(d);

	//\n()*+?[\\ must be escaped in regexp
	p=~"()*+?[\\".indexOf(e)?"\\"+e:d^10?e:"\\n";
	if(c)r+=`.replace(/${p+p}|${p+String.fromCharCode(n)}/g,a=>a[0]!=a[1]?\`\\r\`:${f+(d^92?e:p)+f})`;
	r+=D2;
	f=new Uint8Array(a+c+o.length+D0.length+r.length);
	//meta + noscript
	a=0;for(e of D0)f[a++]=e.charCodeAt();
	//write archive data
	if(c)for(e of o)e==13?(f[a++]=d,e=n):e==d&&(f[a++]=d,e=d),f[a++]=e;
	else f.set(o,a,a+=o.length);
	//write decoder + html
	for(e of r)f[a++]=e.charCodeAt();
	l.innerText=a;l.href=URL.createObjectURL(new Blob([f]))
}
</script><a id=l download=a.htm>
```
**2024.11.22更新版**
書庫の`noscript`要素をhtmlの末端に追いやり、安全性を高め、`script`要素を排除して`body`要素の`onload`属性に展開処理を全て詰め込み、記述量を削減
```html:arc.htm
<input multiple type=file id=i><script>
//書庫のhtml部品群
const D1="<meta charset=l1><body onload='new Response(new Blob([Uint8Array.from(s.innerText",
D2=',(c,i)=>(i=c.charCodeAt()%65533)>>8?129+" \x82\x83\x84\x85\x86\x87\x88\x89\x8A\x8B\x8C \x8E  \x91\x92\x93\x94\x95\x96\x97\x98\x99\x9A\x9B\x9C \x9E\x9F".indexOf(c):i)]).stream().pipeThrough(new DecompressionStream("deflate-raw"))).arrayBuffer().then((A,a=0)=>{for(A=new Uint8Array(A);A[a]+1;l.innerHTML+=`<li><a download="${f}"href=${URL.createObjectURL(new Blob([A.subarray(a,a+=d)]))}>${f}</a> ${d}`){var c=32,d=A[a++],f=d>>5;for(d&=31;f--;c*=256)d+=c*A[a++];for(f=a;A[a++];);f=new TextDecoder().decode(new Uint8Array(A.subarray(f,a-1)))}})\'><button onclick="for(let a of links)a.click()">save</button><ol id=l></ol><noscript id=s>';

i.oninput=async function(e){
	var a=0,c,d,f=this.files,i,r,n=[],o,p;
	//file名かき集める
	for(e of f)n.push(r=new TextEncoder().encode(e.name)),a+=r.length+e.size+6;
	o=new Uint8Array(a);a=0;
	//file尺とfile中身かき集める
	for(e of f){
		e=new Uint8Array(await e.arrayBuffer(i=a));
		for(c=32,d=r=e.length;d-=o[a++]=d&c-1;c=256)d/=c,o[i]+=32;
		o.set(c=n.shift(),a,a-=~c.length);
		o.set(e,a,a+=r)
	}
	//それら全て圧縮
	o=new Uint8Array(await new Response(new Blob([o.subarray(0,a)]).stream().pipeThrough(new CompressionStream("deflate-raw"))).arrayBuffer());
	//文字頻度表作成
	f=new Uint32Array(256);
	for(e of o)f[e]++;r=D1;

	//escape文字の候補選出
	for(e=256;e;a>c&&e^39&&e^13&&(a=c,d=e))c=f[--e];
	c=f[13];n=d>1?1:2;f=d^96?"`":'"';
	e=String.fromCharCode(d);

	//\n()*+?[\\ must be escaped in regexp
	p=~"()*+?[\\".indexOf(e)?"\\"+e:d^10?e:"\\n";
	if(c)r+=`.replace(/${p+p}|${p+String.fromCharCode(n)}/g,a=>a[0]!=a[1]?\`\\r\`:${f+(d^92?e:p)+f})`;

	f=new Uint8Array(a+c+o.length+(r+=D2).length);a=0;
	//wirte decoder+html
	for(e of r)f[a++]=e.charCodeAt();
	//write archive data
	if(c)for(e of o)e==13?(f[a++]=d,e=n):e==d&&(f[a++]=d,e=d),f[a++]=e;
	else f.set(o,a,a+=o.length);
	l.innerText=a;l.href=URL.createObjectURL(new Blob([f]));this.value=""
}
</script><a id=l download=a.htm>
```
file選択buttonを押下して複数fileを選択、または複数fileをbuttonにDrag & Dropすると書庫を作り始めやがります。無事に書庫が完成したらdownload linkが生成されやがるので、どうしても書庫が欲しければclickでもして下さいな。
標準でa.htmという名前の書庫を落とせるはずなので、保存したらbrowserで開いてみて下さい。先程選び抜いたfileどもの一覧が表示されるはずです。file名の横の数字はfileの大きさを表しています。
saveを押下すると全file連続download可能。file名をclickすると個別downloadとなります。
## 実演
<p class="codepen" data-height="300" data-default-tab="result" data-slug-hash="eYodvLq" data-user="xezz" style="height:300px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;border:2px solid;margin:1em 0;padding:1em"> <span>See the Pen <a href="https://codepen.io/xezz/pen/eYodvLq">sfx archive creater</a> by xezz (<a href="https://codepen.io/xezz">@xezz</a>) on <a href="https://codepen.io">CodePen</a>.</span></p><script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

----
過去に投稿した [JavaScriptで独自の書庫を作る](https://qiita.com/mashuel/items/8f2138025a162c6af661) とかいう記事では**自己展開しない書庫**の作り方を網羅しています。本記事のprogramの参考になるので、解説が気になる人は読んでみて下さい。
それからbinary dataをhtmlに埋め込む方法は[この記事](https://qiita.com/mashuel/items/6e4ad7153dc7430be74a)で解説