Hi! First of all, thanks again for creating this plugin! I've used it for 4 projects all with great success! 🎉
TL;DR: Tiny optimization suggestion to reduce the build output by ~ 1kb (≈ 23% in my example project). Feel free to close the issue if it's out of scope!
Background
When importing any kind of JS modules, vite build automatically adds a minimal import wrapper in the front of the build output. This code seems to handle importing "modules" by adding script tags.
Notice how it's bound to the '/assets/' directory as a root path for loading new modules. However, we don't load any modules from '/assets/' since everything is inlined into index.html when using vite-plugin-singlefile.
!(function (e = '.', t = '__import__') {
try {
self[t] = new Function('u', 'return import(u)')
} catch (r) {
const n = new URL(e, location),
o = (e) => {
URL.revokeObjectURL(e.src), e.remove()
}
;(self[t] = (e) =>
new Promise((r, a) => {
const i = new URL(e, n)
if (self[t].moduleMap[i]) return r(self[t].moduleMap[i])
const s = new Blob(
[
`import * as m from '${i}';`,
`${t}.moduleMap['${i}']=m;`,
],
{ type: 'text/javascript' }
),
c = Object.assign(document.createElement('script'), {
type: 'module',
src: URL.createObjectURL(s),
onerror() {
a(new Error(`Failed to import: ${e}`)), o(c)
},
onload() {
r(self[t].moduleMap[i]), o(c)
},
})
document.head.appendChild(c)
})),
(self[t].moduleMap = {})
}
})('/assets/')
Raw module loader snippet
!function(e=".",t="__import__"){try{self[t]=new Function("u","return import(u)")}catch(r){const n=new URL(e,location),o=e=>{URL.revokeObjectURL(e.src),e.remove()};self[t]=e=>new Promise(((r,a)=>{const i=new URL(e,n);if(self[t].moduleMap[i])return r(self[t].moduleMap[i]);const s=new Blob([`import * as m from '${i}';`,`${t}.moduleMap['${i}']=m;`],{type:"text/javascript"}),c=Object.assign(document.createElement("script"),{type:"module",src:URL.createObjectURL(s),onerror(){a(new Error(`Failed to import: ${e}`)),o(c)},onload(){r(self[t].moduleMap[i]),o(c)}});document.head.appendChild(c)})),self[t].moduleMap={}}}("/assets/");
The idea
Maybe we could remove the vite module loader?
I've tested it in one of my (tiny) projects and managed to reduce the JS output by 23% (~ 1kb).
Note: This project is currently in a major rewrite, so it's not the ideal reproduction environment. But at least the steps below explain the process:
Open /web/build/index.html and find the main script type="module" in the <head> section.
Remove the raw module loader snippet (available above) from the build JS output.
run npm run serve and see that the page still works on localhost:5000
JS output after building with vite-plugin-singlefile (3012 bytes)
var e,t;!function(e=".",t="__import__"){try{self[t]=new Function("u","return import(u)")}catch(r){const n=new URL(e,location),o=e=>{URL.revokeObjectURL(e.src),e.remove()};self[t]=e=>new Promise(((r,a)=>{const i=new URL(e,n);if(self[t].moduleMap[i])return r(self[t].moduleMap[i]);const s=new Blob([`import * as m from '${i}';`,`${t}.moduleMap['${i}']=m;`],{type:"text/javascript"}),c=Object.assign(document.createElement("script"),{type:"module",src:URL.createObjectURL(s),onerror(){a(new Error(`Failed to import: ${e}`)),o(c)},onload(){r(self[t].moduleMap[i]),o(c)}});document.head.appendChild(c)})),self[t].moduleMap={}}}("/assets/");var r={chars:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",bits:6},n=function(e,t){return function(e,t,r){if(void 0===r&&(r={}),!t.codes){t.codes={};for(var n=0;n=8&&(i-=8,a[c++]=255&s>>i)}if(i>=t.bits||255&s<<8-i)throw new SyntaxError("Unexpected end of data");return a}(e,r,t)};const o=document.querySelector.bind(document),[a,i,s,c,d,l]=["input","iframe","header","#msg","#locked","#unlocked"].map(o);""===window.pl&&(a.disabled=!0,g("No encrypted payload."));const u=n(window.pl),w=u.slice(0,32),m=u.slice(32,48),p=u.slice(48),f=(null==(e=window.crypto)?void 0:e.subtle)||(null==(t=window.crypto)?void 0:t.webkitSubtle);function h(e){e.classList.remove("hidden")}function v(e){e.classList.add("hidden")}function g(e){c.innerText=e,s.classList.toggle("text-red-600",!0),s.classList.toggle("text-green-600",!1),h(d),v(l)}window.crypto.subtle||(g("Please use a modern browser."),a.disabled=!0),o("form").addEventListener("submit",(async e=>{e.preventDefault();try{t="Decrypting...",c.innerText=t,s.classList.toggle("text-green-600",!1),s.classList.toggle("text-red-600",!1),h(d),v(l),await async function(e){return new Promise((t=>setTimeout(t,e)))}(60);const e=await async function({salt:e,iv:t,ciphertext:r},n){const o=new TextDecoder,a=new TextEncoder,i=await f.importKey("raw",a.encode(n),"PBKDF2",!1,["deriveKey"]),s=await f.deriveKey({name:"PBKDF2",salt:e,iterations:1e6,hash:"SHA-256"},i,{name:"AES-GCM",length:256},!1,["decrypt"]),c=new Uint8Array(await f.decrypt({name:"AES-GCM",iv:t},s,r));return o.decode(c)}({salt:w,iv:m,ciphertext:p},a.value);if(!e)throw"Malformed data";!function(e){c.innerText=e,s.classList.toggle("text-green-600",!0),s.classList.toggle("text-red-600",!1),h(l),v(d)}("Success!"),i.srcdoc=e.replace("",''),window.setTimeout((()=>{o("main").remove(),h(i),document.querySelectorAll("script").forEach((e=>e.remove()))}),1e3)}catch(r){g("Wrong password."),a.value=""}var t}));
JS output after removing module loader (2381 bytes - 23% less code)
var e,t,r={chars:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",bits:6},n=function(e,t){return function(e,t,r){if(void 0===r&&(r={}),!t.codes){t.codes={};for(var n=0;n=8&&(i-=8,a[c++]=255&s>>i)}if(i>=t.bits||255&s<<8-i)throw new SyntaxError("Unexpected end of data");return a}(e,r,t)};const o=document.querySelector.bind(document),[a,i,s,c,d,l]=["input","iframe","header","#msg","#locked","#unlocked"].map(o);""===window.pl&&(a.disabled=!0,g("No encrypted payload."));const u=n(window.pl),w=u.slice(0,32),m=u.slice(32,48),p=u.slice(48),f=(null==(e=window.crypto)?void 0:e.subtle)||(null==(t=window.crypto)?void 0:t.webkitSubtle);function h(e){e.classList.remove("hidden")}function v(e){e.classList.add("hidden")}function g(e){c.innerText=e,s.classList.toggle("text-red-600",!0),s.classList.toggle("text-green-600",!1),h(d),v(l)}window.crypto.subtle||(g("Please use a modern browser."),a.disabled=!0),o("form").addEventListener("submit",(async e=>{e.preventDefault();try{t="Decrypting...",c.innerText=t,s.classList.toggle("text-green-600",!1),s.classList.toggle("text-red-600",!1),h(d),v(l),await async function(e){return new Promise((t=>setTimeout(t,e)))}(60);const e=await async function({salt:e,iv:t,ciphertext:r},n){const o=new TextDecoder,a=new TextEncoder,i=await f.importKey("raw",a.encode(n),"PBKDF2",!1,["deriveKey"]),s=await f.deriveKey({name:"PBKDF2",salt:e,iterations:1e6,hash:"SHA-256"},i,{name:"AES-GCM",length:256},!1,["decrypt"]),c=new Uint8Array(await f.decrypt({name:"AES-GCM",iv:t},s,r));return o.decode(c)}({salt:w,iv:m,ciphertext:p},a.value);if(!e)throw"Malformed data";!function(e){c.innerText=e,s.classList.toggle("text-green-600",!0),s.classList.toggle("text-red-600",!1),h(l),v(d)}("Success!"),i.srcdoc=e.replace("",''),window.setTimeout((()=>{o("main").remove(),h(i),document.querySelectorAll("script").forEach((e=>e.remove()))}),1e3)}catch(r){g("Wrong password."),a.value=""}var t}));
Let me know if we need a better demo to reproduce and test this.
Why?
This would be a tiny optimization to save ~ 1kb. This is so small it's barely noticeable for a normal app with 50kb. But for a simple HTML file with a simple JS file of < 5 kb, this is a significant part of the build output.
Remove unused code so the entire build output is useful.
Hi! First of all, thanks again for creating this plugin! I've used it for 4 projects all with great success! 🎉
TL;DR: Tiny optimization suggestion to reduce the build output by
~ 1kb
(≈ 23% in my example project). Feel free to close the issue if it's out of scope!Background
When importing any kind of JS modules, vite build automatically adds a minimal import wrapper in the front of the build output. This code seems to handle importing "modules" by adding script tags.
Notice how it's bound to the
'/assets/'
directory as a root path for loading new modules. However, we don't load any modules from'/assets/'
since everything is inlined intoindex.html
when usingvite-plugin-singlefile
.Raw module loader snippet
!function(e=".",t="__import__"){try{self[t]=new Function("u","return import(u)")}catch(r){const n=new URL(e,location),o=e=>{URL.revokeObjectURL(e.src),e.remove()};self[t]=e=>new Promise(((r,a)=>{const i=new URL(e,n);if(self[t].moduleMap[i])return r(self[t].moduleMap[i]);const s=new Blob([`import * as m from '${i}';`,`${t}.moduleMap['${i}']=m;`],{type:"text/javascript"}),c=Object.assign(document.createElement("script"),{type:"module",src:URL.createObjectURL(s),onerror(){a(new Error(`Failed to import: ${e}`)),o(c)},onload(){r(self[t].moduleMap[i]),o(c)}});document.head.appendChild(c)})),self[t].moduleMap={}}}("/assets/");
The idea
Maybe we could remove the vite module loader?
I've tested it in one of my (tiny) projects and managed to reduce the JS output by 23% (~ 1kb).
Note: This project is currently in a major rewrite, so it's not the ideal reproduction environment. But at least the steps below explain the process:
npm run build
in the pagecrypt project/web/build/index.html
and find the main script type="module" in the<head>
section.npm run serve
and see that the page still works onlocalhost:5000
JS output after building with vite-plugin-singlefile (3012 bytes)
var e,t;!function(e=".",t="__import__"){try{self[t]=new Function("u","return import(u)")}catch(r){const n=new URL(e,location),o=e=>{URL.revokeObjectURL(e.src),e.remove()};self[t]=e=>new Promise(((r,a)=>{const i=new URL(e,n);if(self[t].moduleMap[i])return r(self[t].moduleMap[i]);const s=new Blob([`import * as m from '${i}';`,`${t}.moduleMap['${i}']=m;`],{type:"text/javascript"}),c=Object.assign(document.createElement("script"),{type:"module",src:URL.createObjectURL(s),onerror(){a(new Error(`Failed to import: ${e}`)),o(c)},onload(){r(self[t].moduleMap[i]),o(c)}});document.head.appendChild(c)})),self[t].moduleMap={}}}("/assets/");var r={chars:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",bits:6},n=function(e,t){return function(e,t,r){if(void 0===r&&(r={}),!t.codes){t.codes={};for(var n=0;n=8&&(i-=8,a[c++]=255&s>>i)}if(i>=t.bits||255&s<<8-i)throw new SyntaxError("Unexpected end of data");return a}(e,r,t)};const o=document.querySelector.bind(document),[a,i,s,c,d,l]=["input","iframe","header","#msg","#locked","#unlocked"].map(o);""===window.pl&&(a.disabled=!0,g("No encrypted payload."));const u=n(window.pl),w=u.slice(0,32),m=u.slice(32,48),p=u.slice(48),f=(null==(e=window.crypto)?void 0:e.subtle)||(null==(t=window.crypto)?void 0:t.webkitSubtle);function h(e){e.classList.remove("hidden")}function v(e){e.classList.add("hidden")}function g(e){c.innerText=e,s.classList.toggle("text-red-600",!0),s.classList.toggle("text-green-600",!1),h(d),v(l)}window.crypto.subtle||(g("Please use a modern browser."),a.disabled=!0),o("form").addEventListener("submit",(async e=>{e.preventDefault();try{t="Decrypting...",c.innerText=t,s.classList.toggle("text-green-600",!1),s.classList.toggle("text-red-600",!1),h(d),v(l),await async function(e){return new Promise((t=>setTimeout(t,e)))}(60);const e=await async function({salt:e,iv:t,ciphertext:r},n){const o=new TextDecoder,a=new TextEncoder,i=await f.importKey("raw",a.encode(n),"PBKDF2",!1,["deriveKey"]),s=await f.deriveKey({name:"PBKDF2",salt:e,iterations:1e6,hash:"SHA-256"},i,{name:"AES-GCM",length:256},!1,["decrypt"]),c=new Uint8Array(await f.decrypt({name:"AES-GCM",iv:t},s,r));return o.decode(c)}({salt:w,iv:m,ciphertext:p},a.value);if(!e)throw"Malformed data";!function(e){c.innerText=e,s.classList.toggle("text-green-600",!0),s.classList.toggle("text-red-600",!1),h(l),v(d)}("Success!"),i.srcdoc=e.replace("",' '),window.setTimeout((()=>{o("main").remove(),h(i),document.querySelectorAll("script").forEach((e=>e.remove()))}),1e3)}catch(r){g("Wrong password."),a.value=""}var t}));
JS output after removing module loader (2381 bytes - 23% less code)
var e,t,r={chars:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",bits:6},n=function(e,t){return function(e,t,r){if(void 0===r&&(r={}),!t.codes){t.codes={};for(var n=0;n=8&&(i-=8,a[c++]=255&s>>i)}if(i>=t.bits||255&s<<8-i)throw new SyntaxError("Unexpected end of data");return a}(e,r,t)};const o=document.querySelector.bind(document),[a,i,s,c,d,l]=["input","iframe","header","#msg","#locked","#unlocked"].map(o);""===window.pl&&(a.disabled=!0,g("No encrypted payload."));const u=n(window.pl),w=u.slice(0,32),m=u.slice(32,48),p=u.slice(48),f=(null==(e=window.crypto)?void 0:e.subtle)||(null==(t=window.crypto)?void 0:t.webkitSubtle);function h(e){e.classList.remove("hidden")}function v(e){e.classList.add("hidden")}function g(e){c.innerText=e,s.classList.toggle("text-red-600",!0),s.classList.toggle("text-green-600",!1),h(d),v(l)}window.crypto.subtle||(g("Please use a modern browser."),a.disabled=!0),o("form").addEventListener("submit",(async e=>{e.preventDefault();try{t="Decrypting...",c.innerText=t,s.classList.toggle("text-green-600",!1),s.classList.toggle("text-red-600",!1),h(d),v(l),await async function(e){return new Promise((t=>setTimeout(t,e)))}(60);const e=await async function({salt:e,iv:t,ciphertext:r},n){const o=new TextDecoder,a=new TextEncoder,i=await f.importKey("raw",a.encode(n),"PBKDF2",!1,["deriveKey"]),s=await f.deriveKey({name:"PBKDF2",salt:e,iterations:1e6,hash:"SHA-256"},i,{name:"AES-GCM",length:256},!1,["decrypt"]),c=new Uint8Array(await f.decrypt({name:"AES-GCM",iv:t},s,r));return o.decode(c)}({salt:w,iv:m,ciphertext:p},a.value);if(!e)throw"Malformed data";!function(e){c.innerText=e,s.classList.toggle("text-green-600",!0),s.classList.toggle("text-red-600",!1),h(l),v(d)}("Success!"),i.srcdoc=e.replace("",' '),window.setTimeout((()=>{o("main").remove(),h(i),document.querySelectorAll("script").forEach((e=>e.remove()))}),1e3)}catch(r){g("Wrong password."),a.value=""}var t}));
Let me know if we need a better demo to reproduce and test this.
Why?
~ 1kb
. This is so small it's barely noticeable for a normal app with50kb
. But for a simple HTML file with a simple JS file of < 5 kb, this is a significant part of the build output.