richardtallent / vite-plugin-singlefile Goto Github PK
View Code? Open in Web Editor NEWVite plugin for inlining JavaScript and CSS resources
License: MIT License
Vite plugin for inlining JavaScript and CSS resources
License: MIT License
The following regex matches both cases - minified and not - however I have only tested it for a couple of individual use cases
/(\s*<script type="module" crossorigin>\s*)\(function[\s\S]*?\}\)\(\);/
The removed segment is \(\)\{
after function
- when not minified, the polyfill is a named function
I have the issue that the plugin fails when I try to bundle multiple files. The error it throws is:
Invalid value for option "output.inlineDynamicImports" - multiple inputs are not supported when "output.inlineDynamicImports" is true.
This is my vite.config.js
file:
import { defineConfig } from 'vite';
import { viteSingleFile } from 'vite-plugin-singlefile';
import { resolve } from 'path';
export default defineConfig({
plugins: [viteSingleFile()],
build: {
emptyOutDir: false,
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html'),
other: resolve(__dirname, 'other.html'),
},
output: {
inlineDynamicImports: false,
},
},
},
});
It only works if I either disable the plugin or only keep one input. The output.inlineDynamicImports
setting doesn't do the trick, I still get the same error.
Thanks for your help.
replace() is replace first target.
vite-plugin-singlefile/src/index.ts
Lines 23 to 29 in 5516a45
It seems to me that replaceAll() should be used.
vite-plugin-singlefile/src/index.ts
Line 26 in 5516a45
โ
const newCode = scriptCode.replaceAll(preloadMarker, "void 0")
The plugin includes a comment above every inlined .js file. While this may be useful for debugging, it should be possible to disable this behaviour with an option like addDebugComments: false
. (Maybe it should be disabled by default?)
Receiving error on build:
error during build:
Error: Invalid value for option "output.manualChunks" - this option is not supported for "output.inlineDynamicImports".
Vite config:
export default defineConfig({
plugins: [react(), viteSingleFile()],
});
Vite version: 2.8.0
vite-single-file version: 0.9.0
Are there any plans to support images via inlining the base64 directly in the src
attribute?
I have a use case where I'd like to emit a single file at build time (with inlined assets/js/etc), but also produce my regular build as if vite-plugin-singlefile were not enabled. Any ideas how I might do this?
failed to load config from www/vite.config.js error during build: Error [ERR_REQUIRE_ESM]: require() of ES Module www/node_modules/vite-plugin-singlefile/node_modules/chalk/source/index.js from www/node_modules/vite-plugin-singlefile/dist/index.js not supported. Instead change the require of www/node_modules/vite-plugin-singlefile/node_modules/chalk/source/index.js in www/node_modules/vite-plugin-singlefile/dist/index.js to a dynamic import() which is available in all CommonJS modules. at Object.<anonymous> (www/node_modules/vite-plugin-singlefile/dist/index.js:7:33) at async Promise.all (index 0) at async loadConfigFromFile (www/node_modules/vite/dist/node/chunks/dep-9c153816.js:71335:31) at async resolveConfig (www/node_modules/vite/dist/node/chunks/dep-9c153816.js:70873:28) at async doBuild (www/node_modules/vite/dist/node/chunks/dep-9c153816.js:38971:20) at async build (www/node_modules/vite/dist/node/chunks/dep-9c153816.js:38959:16) at async CAC.<anonymous> (www/node_modules/vite/dist/node/cli.js:738:9)
Hi there.
I'm using vite-imagetools to add other image formats on a page, importing the images in the JS.
When building, my html ends up with several commented lines like these:
<!-- ASSET NOT INLINED: assets/image.9e86b0ad.avif -->
<!-- ASSET NOT INLINED: assets/image.8317db15.webp -->
Is there a way to avoid this output?
cheers!
localStorage is available (Tested on firefox, chrome and edge)
the file:// protocol is a secure context (https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts)
(and FileSystem API would need a secure context ๐)
Hello.
Thanks for this plugin.
I was wondering if it is possible to use it to only inline certain chunks. For example those smaller than 5kb to get a faster app startup.
again, thanks and regards
using viteSingleFile()
makes sourcemaps useless.
export default defineConfig({
root: "./src",
plugins: [react(), viteSingleFile()],
build: {
target: "esnext",
sourcemap: true, // sourcemap: "inline",
assetsInlineLimit: 100000000,
chunkSizeWarningLimit: 100000000,
cssCodeSplit: false,
outDir: "../dist",
},
});
Setting a breakpoint is not possible anymore, as the sourcemaps point to the wrong position.
This is the case for
sourcemap: "inline"
and sourcemap: true
.
When removing viteSingleFile
the breakpoints work as expected.
This is a problem when trying to debug a figma plugin. It is only allowed to have one html file
When using Node 14 I get this error:
[vite:singlefile] scriptCode.replaceAll is not a function
error during build:
TypeError: scriptCode.replaceAll is not a function
at replaceScript (file:///Users/username/Projects/project/node_modules/vite-plugin-singlefile/dist/esm/index.js:7:32)
at Object.generateBundle (file:///Users/username/Projects/project/node_modules/vite-plugin-singlefile/dist/esm/index.js:36:44)
at file:///Users/username/Projects/project/node_modules/rollup/dist/es/shared/rollup.js:22710:40
at processTicksAndRejections (internal/process/task_queues.js:95:5)
Doing some googling it turns out replaceAll()
wasn't introduced until Node 15. I upgraded to 16 and it works now.
It's a pretty easy fix since 16 is the latest LTS, but I just wanted to report it in case anyone else is searching for the issue like I was.
import { defineConfig } from 'vite';
import { resolve, dirname } from 'path';
import { svelte } from '@sveltejs/vite-plugin-svelte';
import { viteSingleFile } from 'vite-plugin-singlefile';
import { minifyHtml } from 'vite-plugin-html';
import { fileURLToPath } from 'url';
const ___filename = fileURLToPath(import.meta.url);
const ___dirname = dirname(___filename);
// https://vitejs.dev/config/
export default defineConfig({
plugins: [svelte(), viteSingleFile(), minifyHtml()],
build: {
target: 'es2015',
assetsInlineLimit: 100000000,
chunkSizeWarningLimit: 100000000,
cssCodeSplit: false,
brotliSize: false,
rollupOptions: {
inlineDynamicImports: true,
input: {
pure: resolve(___dirname, 'pure/index.html'),
simple: resolve(___dirname, 'simple/index.html'),
},
output: {
manualChunks: () => 'everything.js',
},
},
},
});
It compse all the js files
dist/pure/index.html 4.08 KiB
dist/simple/index.html 4.08 KiB
dist/assets/pure.d4df3b1d.js 3.68 KiB
dist/assets/style.4513d045.css 0.13 KiB
Hello,
Thank you for sharing this great plugin. I've just tried it but seem to run in to an issue. When running npm run build
I get the following error:
> [email protected] build
> vuedx-typecheck . && vite build
Running for /{path}/test-project
vite.config.ts:4:33 - error 7016: Could not find a declaration file for module 'vite-plugin-singlefile'. '/{path}/test-project/node_modules/vite-plugin-singlefile/dist/index.js' implicitly has an 'any' type.
Try `npm i --save-dev @types/vite-plugin-singlefile` if it exists or add a new declaration (.d.ts) file containing `declare module 'vite-plugin-singlefile';`
1 | import { defineConfig } from "vite"
2 | import vue from "@vitejs/plugin-vue"
3 | import { viteSingleFile } from "vite-plugin-singlefile"
| ~~~~~~~~~~~~~~~~~~~~~~~~
4 |
5 | export default defineConfig({
Found 1 error.npm ERR! code 2
npm ERR! path /{path}/test-project
npm ERR! command failed
npm ERR! command sh -c vuedx-typecheck . && vite build
npm ERR! A complete log of this run can be found in:
npm ERR! /Users/test/.npm/_logs/2021-01-20T16_25_51_953Z-debug.log
If I run npm i --save-dev @types/vite-plugin-singlefil
I get the following error:
npm ERR! code E404
npm ERR! 404 Not Found - GET https://registry.npmjs.org/@types%2fvite-plugin-singlefile - Not found
npm ERR! 404
npm ERR! 404 '@types/vite-plugin-singlefile@*' is not in the npm registry.
npm ERR! 404 You should bug the author to publish it (or use the name yourself!)
npm ERR! 404
npm ERR! 404 Note that you can also install from a
npm ERR! 404 tarball, folder, http url, or git url.
npm ERR! A complete log of this run can be found in:
npm ERR! /Users/test/.npm/_logs/2021-01-20T16_33_45_355Z-debug.log
Thanks for your help on this. I'm very new to this set up myself so not sure how to resolve.
It would be nice to also show gzip for the resulting index.html
file.
For this to work first vitejs/vite#12485 needs to get merged.
Than we also need to allow reportCompressedSize
to be true which is disabled at the moment.
I'll provide a PR for this!
Getting the following error on install:
โฏ npm i vite-plugin-singlefile -D
npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR!
npm ERR! While resolving: [email protected]
npm ERR! Found: [email protected]
npm ERR! node_modules/vite
npm ERR! dev vite@"^3.0.0" from the root project
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer vite@"^2.9.13" from [email protected]
npm ERR! node_modules/vite-plugin-singlefile
npm ERR! dev vite-plugin-singlefile@"*" from the root project
Can be installed with --force, and seems to work fine, but would be nice if the peer dependencies were updated so this error doesn't occur.
It will be handy to specify what exactly we should inline. Sometimes it's handy to inline just css and leave javascript to be loaded.
In my use case I build browser extension and it would be great to save a tiny loading time by not doing extra request to load stylesheets. In other hand those extensions prohibit inline scripts and I'd want to keep lazy loading too.
Even though for my use case I can find solutions, I believe it's logical for this plugin to configure what exactly we want to inline.
Title is self explanatory. any plans to implement something like this?
The problem is when you have some unembedding content in public folder. Even if you have unembedding content you have to embed js to neutralize CORS. So js in fact is moving to parent folder (from assets) and all links of imported from public pictures became broken.
This situation is typical for using relative paths: base: ''
To fix this problem we should move js to the root folder using rollupOptions:
{
base: '',
plugins: [
vue(),
viteSingleFile({
useRecommendedBuildConfig: false,
inlinePattern: [
'**/*.js'
]
}),
],
build: {
rollupOptions: {
output: {
chunkFileNames: '[name]-[hash].js',
entryFileNames: '[name]-[hash].js',
},
},
}
}
We should change this rollup options automatically if we have jsAssets or just describe it in help.
This is a great idea and exactly what I am looking for. Thank you for your effort. But is this plugin still working?
I have issues to get working with my Svelte project: https://github.com/exislow/freeDSP-Aurora-Svelte-Frontend/tree/singlefile
Every time I try to build my project, this happens:
$ npm run build
> [email protected] build
> vite build
9:29:01 PM [vite-plugin-svelte] you are building for production but compilerOptions.dev is true, forcing it to false
vite v4.4.11 building SSR bundle for production...
transforming (1) node_modules/@sveltejs/kit/src/runtime/server/index.js9:29:01 PM [vite-plugin-svelte]
[...]
โ 97 modules transformed.
Invalid value for option "output.inlineDynamicImports" - multiple inputs are not supported when "output.inlineDynamicImports" is true.
โ built in 1.37s
error during build:
RollupError: Invalid value for option "output.inlineDynamicImports" - multiple inputs are not supported when "output.inlineDynamicImports" is true.
[...]
I have even followed this tutorial: https://sean.eulenberg.de/posts/single-file-applications-with-svelte/
Do you have any idea whats wrong?
In newer versions of Vite, a vendor
file is generated and imported from the main script file regardless of other options. This seems to be non-trivial to unwire, since the imports from that vendor file are aliased when imported.
I don't know if this is something that is under Vite's control or Rollup's, but somehow, we need a way to disable all forms of code-splitting so we have a single JavaScript output.
Hi, I'm using your plugin . It's awesome! But when I used i18n in my project, it got wrong. It can't run normal and console with TypeError. I don't know how to fix this problem. Can you help me?
stylesheet replace fails because href and not src is used for stylesheet tag. and there is no end tag for me. see #4
I have a use case where I'm trying to compile a single file where a window level function can update data in a rendered Vue app. While I am able to get this working using the defineExposed() function in Vue3/Composition API locally via npm run dev
, after I compile with npm run build
the same working function now returns an error:
Uncaught TypeError: Cannot read properties of null (reading 'exposed')
at logTest (index.html:41:23)
at HTMLButtonElement.onclick (index.html:56:49)
It appears that the defineExposed() function is not compatible with the rollup, but I'm not super familiar with vue or this plugin so I'm having trouble trying to troubleshoot.
Basic test code is as follows:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Test App</title>
<script>
function logTest(newData) {
// eventually newData will be a passed JSON object
var item = {
"name": "TEST NEW ROW 1",
"regions": []
}
var item2 = {
"name": "TEST NEW ROW 2",
"regions": []
}
var update = {
"labelRows": [item, item2]
};
app._instance.exposed.logTest(update);
}
</script>
</head>
<body class="m-0 p-0">
<div id="app" class="w-full"></div>
<script type="module" src="/src/main.ts"></script>
<button onclick="logTest('This is a test')">TEST UPDATE</button>
</body>
</html>
import './assets/main.css'
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
window.app = app;
app.mount('#app')
<script setup lang="ts">
import { ref } from 'vue'
import data from './assets/defaultData.json'
const tableData = ref(data)
function logTest (newData) {
console.log("Test Reactivity Function");
alert(newData);
tableData.value = newData;
}
defineExpose({
logTest
})
</script>
<template>
<div class="px-4">
<table class="w-full">
<tr v-for="row in tableData.rows">
loop code here
</tr>
</table>
</div>
<h1>TEST VALUE: {{ tableData }}</h1>
</template>
Edit I just realized that this plugin automatically makes the base64 images. I feel silly. and pleasantly surprised
I am getting this with the below config file
import { defineConfig } from "vite"
import vue from "@vitejs/plugin-vue"
import { viteSingleFile } from "vite-plugin-singlefile"
export default defineConfig({
plugins: [vue(), viteSingleFile()],
})
Seems like the new inlinePattern config does only work if the file still ends with .js or .css but what about .mjs (or simmilar minified files)? I think it would be usefull to have an option to configure for example a regex pattern, or to give a list with file extionsion which are allowed to be inlined.
I'm working on a vite app that I am deploying to a subfolder, like http://example.com/my-app/
So I had set the base
option in vite to "/my-app/".
Later I realized a single file bundle made sense, so I tried using this plugin. It failed silently by not inlining any CSS/JS but still deleting my CSS/JS files.
Deleting the base
option (which doesn't really do anything when bundling to a single file, I think) fixes this problem. But I figure you should at least show an error message in the case where you notice a CSS/JS file, try to inline it in index.html, but actually the regex doesn't match anything so no replacement happens. Or, make the regex a little more broad so it supports arbitrary values in base
.
Hello,
Following the instructions from the readme lead me to this error
error during build:
Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: `PATH\node_modules\vite-plugin-singlefile\node_modules\chalk\source\index.js
require() of ES modules is not supported.
require() of PATH\node_modules\vite-plugin-singlefile\node_modules\chalk\source\index.js from PATH\node_modules\vite-plugin-singlefile\dist\index.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 PATH\node_modules\vite-plugin-singlefile\node_modules\chalk\source\index.js to end in .cjs, change the requiring code to use import(), or remove "type": "module" from PATH\node_modules\vite-plugin-singlefile\node_modules\chalk\package.json.
In my project I currently use both Electron and Vite for different parts of my software, but they share a common package.json
, and are ultimately built into a single executable. I tried using this plugin for the Vite side and got the ERR_REQUIRE_ESM
error from #23. So, following the comments from that issue, I added "type": "module"
to my package.json
. Now I get this error from my electron build:
Error [ERR_REQUIRE_ESM]: require() of ES Module C:\Users\...\project\dist\backend\main.js from C:\Users\...\project\node_modules\electron\dist\resources\default_app.asar\main.js not supported.
C:\Users\...\project\dist\backend\main.js is treated as an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which declares all .js files in that package scope as ES modules.
Instead rename C:\Users\...\project\dist\backend\main.js to end in .cjs, change the requiring code to use dynamic import() which is available in all CommonJS modules, or change "type": "module" to "type": "commonjs" in C:\Users\...\project\package.json to treat all .js files as CommonJS (using .mjs for all ES modules instead).
at Function.c._load (node:electron/js2c/asar_bundle:5:13343)
at loadApplicationPackage (C:\Users\...\project\node_modules\electron\dist\resources\default_app.asar\main.js:110:16)
at Object.<anonymous> (C:\Users\...\project\node_modules\electron\dist\resources\default_app.asar\main.js:222:9)
at Function.c._load (node:electron/js2c/asar_bundle:5:13343)
at Object.<anonymous> (node:electron/js2c/browser_init:185:3104)
at Object../lib/browser/init.ts (node:electron/js2c/browser_init:185:3308)
at __webpack_require__ (node:electron/js2c/browser_init:1:128)
at node:electron/js2c/browser_init:1:1200
at node:electron/js2c/browser_init:1:1267
at Function.c._load (node:electron/js2c/asar_bundle:5:13343)
Renaming that output file to .cjs
worked, but it just leads to a bunch of other issues, which I'm currently trying to resolve. Is there a way to use this plugin without setting "type": "module"
in package.json
?
Here is my steps :
import vue from "@vitejs/plugin-vue";
import ssr from "vite-plugin-ssr/plugin";
import { UserConfig } from "vite";
import { viteSingleFile } from "vite-plugin-singlefile";
const config: UserConfig = {
plugins: [vue(), ssr(), viteSingleFile()],
};
export default config;
{
"scripts": {
"dev": "npm run server",
"prod": "npm run build && npm run server:prod",
"build": "vite build",
"server": "ts-node ./server",
"server:prod": "cross-env NODE_ENV=production ts-node ./server"
},
"dependencies": {
"@types/compression": "^1.7.2",
"@types/express": "^4.17.13",
"@types/node": "^17.0.31",
"@vitejs/plugin-vue": "^3.0.0",
"@vue/compiler-sfc": "^3.2.37",
"@vue/server-renderer": "^3.2.37",
"compression": "^1.7.4",
"cross-env": "^7.0.3",
"express": "^4.18.1",
"sirv": "^2.0.2",
"ts-node": "^10.7.0",
"typescript": "^4.6.4",
"vite": "^2.9.14",
"vite-plugin-ssr": "^0.4.9",
"vite-plugin-singlefile": "^0.10.0",
"vue": "^3.2.37"
}
}
npm run prod
and got the error like thisInvalid value for option "output.inlineDynamicImports" - multiple inputs are not supported when "output.inlineDynamicImports" is true.
error during build:
Error: Invalid value for option "output.inlineDynamicImports" - multiple inputs are not supported when "output.inlineDynamicImports" is true.
at error (file:///Users/jonescai/git/github/server/node_modules/rollup/dist/es/shared/rollup.js:1858:30)
at getInlineDynamicImports (file:///Users/jonescai/git/github/server/node_modules/rollup/dist/es/shared/rollup.js:23442:16)
at normalizeOutputOptions (file:///Users/jonescai/git/github/server/node_modules/rollup/dist/es/shared/rollup.js:23344:34)
at getOutputOptions (file:///Users/jonescai/git/github/server/node_modules/rollup/dist/es/shared/rollup.js:23735:12)
at getOutputOptionsAndPluginDriver (file:///Users/jonescai/git/github/server/node_modules/rollup/dist/es/shared/rollup.js:23730:12)
at handleGenerateWrite (file:///Users/jonescai/git/github/server/node_modules/rollup/dist/es/shared/rollup.js:23705:74)
at Object.write (file:///Users/jonescai/git/github/server/node_modules/rollup/dist/es/shared/rollup.js:23673:20)
at generate (file:///Users/jonescai/git/github/server/node_modules/vite/dist/node/chunks/dep-561c5231.js:43424:64)
at doBuild (file:///Users/jonescai/git/github/server/node_modules/vite/dist/node/chunks/dep-561c5231.js:43437:26)
at async build (file:///Users/jonescai/git/github/server/node_modules/vite/dist/node/chunks/dep-561c5231.js:43261:16)
Hi.
So I came across this issue because my particular vite configuration outputs the script tags as:
<script async type="module" crossorigin src="/assets/whatever.cffa7373.js"></script>
This is not covered by the regular expression at L33
vite-plugin-singlefile/src/index.ts
Line 33 in 3d15a59
This Regex looks for a <script>
tag that starts with type="module" and then contains a src
attribute with the filename.
AFAICT the only thing the script should be looking for is a <script>
tag that contains the src
attribute with the filename we want to embed. so that Regex should be maybe something like:
<script[^>]*?src="[\./]*${o.fileName}"[^>]*?></script>
This is how I sorted my use case which may be different than the generic use case you're trying to solve, so this solution may not please everybody, although I would think that's generic enough though.
Vite requires, base to be '' for using relative assets url.
this plugin fails to inline assets.
If base is not not set '', then vue router uses url with '/' as base, which fails for navigation
Testing this with a React application.
There's the following line in the built HTML which breaks the page:
...else{switch(o=n.nodeType===9?n:n.ownerDocument,e===ls&&(e=mc(i)),e===ls?i==="script"?(e=o.createElement("div"),e.innerHTML="<script></script>",e=e.removeChild(e.firstChild))...
The part with e.innerHTML="<script></script>"
breaks the page and the rest of the javascript is rendered as text on the page.
I've checked the application code and I don't do this anywhere.
Only code that is similar is this little snippet:
return new Promise((resolve) => {
const scriptEl = document.createElement('script');
scriptEl.src = url;
scriptEl.onload = () => resolve();
document.body.appendChild(scriptEl);
});
Is this some optimization vite does, that it changes createElement
calls into innerHTML?
I investigated into this bug a bit and it turns out some of the assets are undefined in the inner loop when going through the CSS and Javascript. I have multiple entrypoints specified inside build.rollupOptions.input
and I disabled inlineDynamicImports
. I am also using the @vite/plugin-legacy
because the environment I am targeting doesn't support imports or exports. It turns out that all the entrypoints point to the same JS file that is generated by the legacy plugin, and in the code you run the delete
on JS assets that are already iterated over. I can create a patch for this that runs delete in the outside loop.
I have tested this plugin with vite default example. The assets are built in a dir in the dist folder. I also use
{ useRecommendedBuildConfig: true } but nothing change.
What am I missing? How can I get a single html file in the dist folder after build?
Thanks so much for making this, just trying it out for the first time, excited about using it for my projects!
I noticed right away that the plugin seems to remove my original script tag from the bottom of the body element and add a new script tag to the bottom of the head element. I was expecting my script tag to stay put, since the location of the script tag can affect the script's behavior, for example the state of the DOM when the top level script code runs. If I remember correctly, inlined JS code inside the body element is supposed to go inside an HTML comment nested immediately inside the script tag, to prevent it from being rendered as part of the document under some circumstances. And in fact I think that works even if the inlined script is in the head.
Haven't looked into any of this too deeply yet. Curious to hear your thoughts.
Hello,
I tried using your package with my vite project but I get the following error in the console:
ReferenceError: $computed is not defined
Here is my vite.config.js
:
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { viteSingleFile } from 'vite-plugin-singlefile'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
viteSingleFile()
]
})
Doing that and opening the page result in that error. What can I do?
Dear maintainer,
first of all, thanks for your great plugin! I would like to export vue apps, containing svg graphics. Unfortunately, these files don't get inlined and I receive messages during build like:
rendering chunks (1)...WARNING: asset not inlined: assets/games.e8418186.svg
Other graphic formats, like png are handled correctly.
In order to reproduce this error, I created a minimum demo project https://codeberg.org/MintApps/test
I would really appreciate any tips/help,
Thomas
Hey! Is it possible to pack everything to a single .js file, instead of html file? So that it can be included by many websites as a widget.
My html script tag looks like this:
<script type="module" crossorigin src="/assets/index.21d23eb8.js"></script>
and will not be replaced. I have a fix that uses a regex so the arguments are not relevant
My config:
import { defineConfig } from "vite";
import preact from "@preact/preset-vite";
import { viteSingleFile } from "vite-plugin-singlefile"
import { createHtmlPlugin } from "vite-plugin-html";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [preact(), createHtmlPlugin(), viteSingleFile({ removeViteModuleLoader: true })],
base: "/shri2023-performance/",
});
My css:
.event__icon_temp {
background-image: url(assets/icon_temperature.svg);
}
I got link like this:
background-image:url(/assets/icon_temperature-b86a9b9d.svg)
Expected result:
background-image:url(/shri2023-performance/assets/icon_temperature-b86a9b9d.svg)
I have a pretty niche use case where I want all code in a single file but it also needs to support browser version outside of the defaults supported by vite. The intended solution was to use a vite config similar to this
import react from '@vitejs/plugin-react'
import legacy from '@vitejs/plugin-legacy'
import { viteSingleFile } from 'vite-plugin-singlefile'
export default {
plugins: [
react(),
legacy({
targets: ['Chrome >= 33'],
renderModernChunks: false,
}),
viteSingleFile()
]
}
However, this fails to the legacy code getting loaded into the root index.html via System.import, the resulting index.html looks like this:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Test</title>
</head>
<body>
<div id="root"></div>
<script crossorigin id="vite-legacy-polyfill">[CORRECTLY LOADED JAVASCRIPT IS INSERTED HERE]</script>
<script crossorigin id="vite-legacy-entry" data-src="/assets/index-legacy.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
</body>
</html>
As can be seen, the index.html is still referencing an external file in the script tag with "vite-legacy-entry" the plugin has also deleted the file that it is being referenced as expected.
The desired behaviour is that both the vite legacy plugin and vite single file plugin can be used together
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!
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/')
!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/");
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 on localhost:5000
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}));
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.
~ 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.I'm currently at VueConf in Fort Lauderdale. If you want to say hi, please find me! I'm the almost-bald guy with a short beard in the diamond-patterned white shirt.
Hey! I needed your plugin together with UnoCSS to generate one index.html where everything is inlined.
Because UnoCSS replaces the placeholders in the generateBundle
hook it didn't generate the CSS correctly. The CSS from UnoCSS wasn't replaced and the placeholders where still in the code.
So I tried to fix this and tested a bit with the hooks and got a working plugin which 1. generates the bundle correctly and 2. deletes the entries of the bundle
so they don't get generated into the assets
dir.
The code is probably not very perfect and could be adjusted. I copied your Regex from your plugin.
With this code it should be possible to generate multiple .html
files with inlined assets. Not sure if this works when you use transformIndexHtml
? It's also my first plugin haha.
Keep in mind it doesn't include the extra functions like the custom build config yet. :)
{
name: 'singlefile',
apply: 'build',
enforce: 'post',
generateBundle: (_, bundle) => {
const htmlFiles = Object.keys(bundle).filter((i) => i.endsWith('.html'))
const cssAssets = Object.keys(bundle).filter((i) => i.endsWith('.css'))
const jsAssets = Object.keys(bundle).filter((i) => i.endsWith('.js'))
for (const name of htmlFiles) {
const htmlChunk = bundle[name]
let replacedHtml = htmlChunk.source
for (const jsName of jsAssets) {
const jsChunk = bundle[jsName]
const reScript = new RegExp(
`<script type="module"[^>]*?src="[\./]*${jsChunk.fileName}"[^>]*?></script>`
)
const code = `<script type="module">\n//${jsChunk.fileName}\n${jsChunk.code}\n</script>`
replacedHtml = replacedHtml.replace(reScript, code)
delete bundle[jsName]
}
for (const cssName of cssAssets) {
const cssChunk = bundle[cssName]
const reCSS = new RegExp(
`<link rel="stylesheet"[^>]*?href="[\./]*${cssChunk.fileName}"[^>]*?>`
)
const code = `<style type="text/css">\n${cssChunk.source}\n</style>`
replacedHtml = replacedHtml.replace(reCSS, code)
delete bundle[cssName]
}
htmlChunk.source = replacedHtml
}
}
}
Let me know what you think! :)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.