Skip to content

Instantly share code, notes, and snippets.

@quantrung9
Forked from fwalzel/README.md
Created January 28, 2025 18:38
Show Gist options
  • Save quantrung9/4fdfd2ff872a1040a4faa59eccba87dc to your computer and use it in GitHub Desktop.
Save quantrung9/4fdfd2ff872a1040a4faa59eccba87dc to your computer and use it in GitHub Desktop.
Running a Vue.js App as a Stand-alone Executable (Mac / Windows)

Running a Vue.js App as a Stand-alone Executable (Mac / Windows)

This gist describes how to bundle a built Vue.js project – the dist folder – together with a local web server in a stand-alone executable (no Node.js installation needed on the client).

Problem

When we want to present a completed Vue project to a third party (a client, a collaborator, etc.), we cannot just send the dist folder and explain how to open the index.html file. This won’t work because Vue presupposes a web server to run (at least as long as we don't manipulate the publicPath configuration). Setting up a whole web hosting environment, organizing a domain name, and securing it with a server lock, among other things, can be a bit much. On the other hand, explaining someone with no technical background how to install Node.js and run a local web server just to see our project is equally inconvenient. This can be a problem.

Solution

We create an executable package that contains a web server and our project. A simple double click on the executable will start the web server, open Google Chrome, and run our project.

This solution is not restricted to Vue projects alone. It can be adapted to all sorts of builds that require a webserver in order to run.

Process

1. Install npm modules http-server and pkg in the root of your Vue project

npm i http-server pkg

2. Create a file pkg.js, also in the root of your project directory

Copy the content of pkg.js as shown here to the file and save it (or clone this gist).

3. Modify your package.json

Add a key bin to your package.json with our file name as value.

"bin" : "pkg.js",

This tells pkg the entry point of the executable. Also, the package.json should contain:

"pkg": {
    "scripts": [
        "pkg.js",
        "package.json"
    ],
    "assets": "dist/**/*",
    "targets": [
        "node18-macos-x64",
        "node18-win-x64"
    ],
    "outputPath": "executable"
}
  • "scripts": the scripts you want to see included in the executable
  • "assets": your build of the Vue.js app, including all subfolders and resources; most likely, this is the folder ./dist
  • "targets": Node.js version, OS, and processors you want to build the pkg for
  • "outputPath": The folder your executables will be built in

4. (Optional) Add a custom .htaccess to your dist folder

If your project contains routes, you might want to be able to address these routes directly by entering the route in the URL address field of the browser, like: http://localhost:8080/my-route/. In an unmodified state of the .htaccess file, Vue projects can only be navigated programmatically from the entry point, but routes will not be resolved from the browser’s address bar.

To fix this, you can create a custom .htaccess folder and place it in the dist folder on the level of your index.html:

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteBase /
  RewriteRule ^index\.html$ - [L]
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteRule . /index.html [L]
</IfModule>

5. Test this setup

To see if your pkg.js does, what it should, simply run

node pkg.js

This should start http-server, open Google Chrome and run your app.

6. Build the executable(s)

If the test is fine, do:

pkg .

Your executable files (Mac/Win) will be build to the folder ./executable according to the specs given in the package.json. (This is what the dot '.' argument after the pkg command means: use the configutation as defined in the package.json).

Done! Happy Coding.

# Place this as an .htaccess file in your dist folder alongside with the index.html
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
</IfModule>
{
"name": "my-vue-project-with-executable",
"version": "1.0.0",
"bin" : "pkg.js",
"pkg": {
"scripts": [
"pkg.js",
"package.json"
],
"assets": "dist/**/*",
"targets": [
"node18-macos-x64",
"node18-win-x64"
],
"outputPath": "executable"
},
"dependencies": {
"vue": "^3.3.4",
"http-server": "^14.1.1",
"pkg": "^5.8.1"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.3.3",
"@vitejs/plugin-vue": "^4.3.4",
"@vue/eslint-config-prettier": "^8.0.0",
"eslint": "^8.49.0",
"eslint-plugin-vue": "^9.17.0",
"prettier": "^3.0.3",
"vite": "^4.4.9"
}
}
const execSync = require('node:child_process').execSync;
const path = require('node:path');
const http = require('http-server');
const packageJson = require('./package.json');
// Inform that App is running
console.log(`Running App ${packageJson.name}. Version: ${packageJson.version}`);
// Specify the options for the server
const options = {
root: path.join(__dirname, 'dist'), // our project directory here is ./dist, change at will
port: 8080, // Port number, change at will
};
// Create the server
const server = http.createServer(options);
// Define the shell command to open Google Chrome programatically, depening on the OS we are on
const shellCommand = process.platform === "win32"
? `start chrome http://localhost:${options.port}` // on Windows
: `open -a "Google Chrome" http://localhost:${options.port}`; // on Mac
// Start the Server and open Chrome
server.listen(options.port, () => {
execSync(shellCommand);
console.log(`Server is running on http://localhost:${options.port}`);
console.log(`Do not close this terminal window as long as you run the app.`);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment