0

I have created a simple Vue3 + Vite website to present something to another person. It works fine when previewing on my side (using npm run dev or npm run build + npm run preview and then opening the browser at localhost:5173). It also works when deployed to Github Pages.

What I would like to do is to simply be able to hand over the HTML, CSS and JS files of the website to the other person and for them to be able to simply open the index.html in the browser and view the website.

Currently, when trying that with index.html (from the generated dist directory) it does not work due to the:

Access to script at 'file:///assets/index-a911f458.js' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, isolated-app, brave, https, chrome-untrusted, data, chrome-extension, chrome.

error.

I want to avoid forcing the other person to install node.js as well as dockerizing the project etc.

gorczyca
  • 21
  • 4
  • 1
    You will need a webserver. Search the error message you will find similar questions. Like this: ["access to script from origin 'null' has been blocked by CORS policy" error while trying to launch an html page using an imported js function](https://stackoverflow.com/questions/59912641/access-to-script-from-origin-null-has-been-blocked-by-cors-policy-error-whil) – Peter Krebs Mar 16 '23 at 08:27
  • I hope there is a workaround. I understand that importing works only from a webserver but I would expect there to be ways of converting all the scripts into a single JS with no imports / modules file that can be included in the HTML and just work. – gorczyca Mar 16 '23 at 09:16
  • Well you could host the dist folder on some webserver and send the URL. If in the same network it's possible you can just replace localhost with your computer's IP and send that URL over. – Peter Krebs Mar 16 '23 at 09:21
  • Unfortunately I cannot do that. The data presented there might be sensitive hence I need a client-side working solution. I cannot expect him to host his own webserver either. – gorczyca Mar 17 '23 at 08:33
  • In that case you might need a different solution. file:// is your problem here I'm pretty sure. The link I posted suggested that older JS standard would work, so not using modules or at least transpiling for an older standard. – Peter Krebs Mar 17 '23 at 08:36
  • 1
    Thanks for your help, I'll look this issue up and update if I find some solution. My use case seems quite reasonable to me, but it looks like it might not be what Vue (and other front-end frameworks) developers envisioned them to be used for. – gorczyca Mar 17 '23 at 11:33
  • Yes choose your tools for the task. It also depends how you load the application. – Peter Krebs Mar 17 '23 at 12:23

2 Answers2

1

Ad-hoc solution: convert the website to an Electron.js app

This ad-hoc solution solves the issue: I can simply send a bundled application to a non-technical user and they can simply run it by double-clicking on the .exe, .app etc file. No node, docker etc. required.

Should an actual solution appear in this thread, I will accept it. But for now maybe this helps somebody facing a similar problem. Below I describe the steps.

What is Electron.js

Electron is a

framework is designed to create desktop applications using web technologies that are rendered using a version of the Chromium browser engine and a back end using the Node.js runtime environment. Wikipedia

So it essentially creates a new browser window within which the app is rendered.

Steps to reproduce

I mostly followed the video tutorial (credits to LearnVue @ YouTube), electron's quick-start guide and electron packager guide. I will write down the steps from the video and additionally how to bundle the app:

  1. (I am assuming here that the Vue+Vite app is working properly)

  2. Install electron npm module (npm install --save-dev electron)

  3. Our app should have this structure:

    <MY APP>
    ├── package.json // we already have it
    └── main.js      // missing
    └── preload.js   // missing
    └── index.html   // we already have it
    └── ...
    └── src/         //  our Vue sources 
    |   └── ...
    |   └── main.js
    |   └── ...
    └── ...
    

    The basic electron setup requires the four files: package.json, main.js, preload.js, index.html. (The src/ listed above is where our Vue app sources are.) At this point we have only the package.json and index.html file, so we are missing main.js and preload.js. (Note that we don't have the main.js, we have src/main.js, in non-root directory, which is a Vite file, not Electron).

  4. Create the main.js and preload.js files: simply fill them with the contents of the files from the quick-start guide. A single change to main.js is necessary: win.loadFile('index.html') to win.loadFile('dist/index.html')

    File contents:

    // main.js
    
    // Modules to control application life and create native browser window
    const { app, BrowserWindow } = require('electron')
    const path = require('path')
    
    const createWindow = () => {
    // Create the browser window.
    const mainWindow = new BrowserWindow({
        width: 800,
        height: 600,
        webPreferences: {
        preload: path.join(__dirname, 'preload.js')
        }
    })
    
    // and load the index.html of the app.
    mainWindow.loadFile('dist/index.html')
    
    // Open the DevTools.
    // mainWindow.webContents.openDevTools()
    }
    
    // This method will be called when Electron has finished
    // initialization and is ready to create browser windows.
    // Some APIs can only be used after this event occurs.
    app.whenReady().then(() => {
    createWindow()
    
    app.on('activate', () => {
        // On macOS it's common to re-create a window in the app when the
        // dock icon is clicked and there are no other windows open.
        if (BrowserWindow.getAllWindows().length === 0) createWindow()
    })
    })
    
    // Quit when all windows are closed, except on macOS. There, it's common
    // for applications and their menu bar to stay active until the user quits
    // explicitly with Cmd + Q.
    app.on('window-all-closed', () => {
    if (process.platform !== 'darwin') app.quit()
    })
    
    // In this file you can include the rest of your app's specific main process
    // code. You can also put them in separate files and require them here.
    
    // preload.js
    
    // All the Node.js APIs are available in the preload process.
    // It has the same sandbox as a Chrome extension.
    window.addEventListener('DOMContentLoaded', () => {
    const replaceText = (selector, text) => {
        const element = document.getElementById(selector)
        if (element) element.innerText = text
    }
    
    for (const dependency of ['chrome', 'node', 'electron']) {
        replaceText(`${dependency}-version`, process.versions[dependency])
    }
    })
    
  5. According to the video tutorial (I ended up doing otherwise, see below), now we should set the base path in vite.config.js: in order for our references to correctly point to the /dist directory, we have to set the base path for our entire project.

    Add these lines to vite.config.js:

    • const path = require('path')
    • base: path.resolve(__dirname, './dist') so that the file has this structure:
    ...
    
    const path = require('path')
    
    ...
    export default defineConfig({
        plugins: [vue()],
        ... 
        base: path.resolve(__dirname, './dist')
    })
    

    Note: in my case doing it resulted in fixing the absolute path to external sources yielding the entire solution not portable anymore. I simply changed the base path to: base: './' and it works. I think it may have something to do with the optional step 6.

  6. In the package.json: add the "main": "main.js" at the root level of the JSON (by default Electron will look for index.js file, hence we need to specify the new main file):

{
  "name": <NAME>
  "version": <VERSION>
  "main": "main.js",
  ...
}
  1. [Optional] In the package.json: inside the "scripts" section add "electron:start ." - this will give us a way to run the app by setting running npm run electron:start. If we only want to bundle it, I think this step can be ommited.

  2. Package the app into bundles (.app, .exe etc).: install Electron Packager (npm install --save-dev electron-packager) Add the following to the "scripts" section of package.json:

    "electron:build": "electron-packager . <PROJECT NAME> --platform=win32,darwin,linux --arch=x64 --icon=dist/favicon.ico --overwrite"
    

    Replace the with your project name. The platforms are win32 (Windows), darwin (macOS) and linux. You can simply not included those that are not needed. The remainig parameters are CPU architecture, icon path, and whether to overwrite already created bundles.

  3. Now simply run:

    • npm run build (to build your Vue+Vite app and create the dist directory)

    • npm run electron:build to create the bundles. Note that building a Windows bundle on a non-windows system requires Wine, so it may be easier to just build it from Windows machine if possible.

I tested it on Windows, Linux and macOS.

gorczyca
  • 21
  • 4
0

Compile all code into index.html

This avoids those CORS errors because there's nothing to import at run-time.

I had the same need as OP; a static index.html that can be shared and opened locally in browsers.

By compiling all code into index.html, we avoided the same CORS errors we'd otherwise have (unavoidable CORS errors when attempting to include JS modules). FYI regular non-module JS includes still work like normal, if we wanted to have an extra .js file.

We didn't have any media assets so we easily distributed this single index.html as-is, and people could open it locally, even offline (since we didn't connect to any API).

Main caveat: Your index.html will be large, e.g. 120kb. This is because everything required to run your app is jammed into it, including any framework (e.g. Vue). You definitely want to keep minification on.

For convenience, we did this using the Vite build plugin below. Please read about it as it has limitations.

https://github.com/richardtallent/vite-plugin-singlefile

vite.config.js

plugins: [
  viteSingleFile()
]
Kalnode
  • 9,386
  • 3
  • 34
  • 62