Template project with Vite, Typescript, Electron Forge, SvelteKit, Tailwind CSS
[!Note]
This template contains numerous comments with explanations and links throughout the source code.
git clone https://github.com/codec-xyz/vtest MyAwesomeApp
cd MyAwesomeApp
npm install
npm run start
[!Note]
Typescript will complain in the editor when you first clone the template. When you first run
npm run start
a.svelte-kit
folder will be generated and the errors and warnings should go away.
This template project consists of two parts...
The example code is the part that will be bundled with the electron application. It has 3 parts...
SvelteKit build outputs code to render your files on a server. These will be located in .svelte-kit/output/
and during npm run start
a dev server runs to serve files using this code. During npm run package
adaptor-static will then run this same code to make the browser html/css/js files and save them at .vite/renderer/main_window/
as specified in svelte.config.js
.
I recommend you do not use SvelteKit's prerendering for your Electron apps. SvelteKit prerendering will slightly speed up the first load (when you open a window). However when navigating, SvelteKit will load Javascript and render pages even if they have been prerendered. Unlike web use cases Electron apps are likely to see almost none of the prerender benefits. Not using the prerendering will slightly simplify development. If you do however want prerendering here is how to do it...
export const prerender = false;
export const ssr = false;
[!Note]
For adaptor-static keep the values the same, so either both true or both false.
prerender
- weather or not adapter-static will output an html file for this pagessr
- weather or no adapter-static will prerender the page aka: false = blank page (and browser js to render the page)Values of prerender = false
and ssr = false
means no html file is output for the this page. It will work as an SPA (Single Page Application) where this or any other page that are not present will use the fallback page 200.html
which is specified to adapter-static in svelte.config.js
.
Values of prerender = true
and ssr = true
will prerender the page at build time and output an html file. One that is not blank. Reactivity, event handlers, and all other svelte features will still work. However this is prerendered during build time meaning no Electron feature and some other features will not be present. For example, state cannot be dependent on preferred color theme or window size. Use this to detect browser vs prerender...
import { browser } from '$app/environment';
if(browser) { ... }
Make sure to put lang='ts'
in the Svelte files to use Typescript...
<script lang="ts">
// ...
</script>
The Vite SvelteKit dev server serves a fallback html file for all urls that do no point to a file. This is replicated in the built version of the app by registering an app://
schema and handling resolving urls manual. This uses Electrons protocol.handle
and protocol.registerSchemesAsPrivileged
. The window url is set to the dev server in dev mode or app://-/
when built. The Electron protocol.handle
simply takes a callback that is invoked to handle every request to the specified schema however it wants. The code is located in src-main/main.ts
. For more see Electron protocol api.
The build system looks like this...
forge.config.ts
- Electron forge./build-plugins/forge-plugin.vite.ts
- An Electron Forge plugin that builds the project during start
and package
using Vite. See Forge Plugin - Vite.vite.main.config.ts
./build-plugins/vite-plugin.nativeNodeFile.ts
- If your app needs native .node
files bundled. See Native node addons.vite.preload.config.ts
vite.renderer.config.ts
- Svelte/Svelte Kit is implemented as a Vite plugin which is passed heresvelte.config.js
postcss.config.js
- Tailwind CSS dependency
Located in ./build-plugins/forge-plugin.vite.ts
. Its config can be found in forge.config.ts
.
[!NOTE] This template does NOT use the official
@electron-forge/plugin-vite
for a few reasons...
- It hides away configuration
- Its functionality cannot be extended
- And I would say its better to own this part of the build process in case you need to change it
npm run start
Runs the app in dev mode with hot reloading.
build-plugins/forge-plugin.vite.ts
does the following...
vite.renderer.config.ts
which has a Svelte Kit pluginvite.main.config.ts
and vite.preload.config.ts
npm run package
Packages the "application into a platform-specific executable bundle"ref.
First Electron Forge copies the entire project to a temporary folder.
Then build-plugins/forge-plugin.vite.ts
does the following...
@electron/rebuild
- builds native node addons for Electron - Note: In this template there are no native node addons. See Native node addons..vite
folder and package.json
- Note: package.json
is needed for its main
and type
fields[!NOTE]
./build-plugins/forge-plugin.vite.ts
calls@electron/rebuild
itself to be able to run Vite and delete all uneccesay files after the rebuild. Electron Forge does NOT give plugins a hook to run after rebuild and before files are packaged in the output folder.
Native node addons allow native code to be imported directly into node. These need to be compiled against Electron to be used. The @electron/rebuild
that comes with Electron Forge does this. The included build-plugins/vite-plugin.nativeNodeFiles.ts
is a Vite plugin that will bundle the .node
files into the build.
Packages need custom handling to make this work and all work differently and do their own special things. Good luck trying to make them work. Likely you will need to modify ./build-plugins/forge-plugin.vite.ts
.
Run npm i -D node-gyp node-addon-api
.
binding.gyp
file in root...{
"targets": [{
"target_name": "native",
"sources": [ "src-native/index.cpp" ],
"include_dirs": ["<!@(node -p \"require('node-addon-api').include\")"],
"dependencies": ["<!(node -p \"require('node-addon-api').gyp\")"],
"cflags!": [ "-fno-exceptions" ],
"cflags_cc!": [ "-fno-exceptions" ],
"xcode_settings": {
"GCC_ENABLE_CPP_EXCEPTIONS": "YES",
"CLANG_CXX_LIBRARY": "libc++",
"MACOSX_DEPLOYMENT_TARGET": "10.7"
},
"msvs_settings": {
"VCCLCompilerTool": { "ExceptionHandling": 1 },
},
"conditions": [
["OS=='mac'", {
"defines": [ "MAC_OS" ],
"cflags+": ["-fvisibility=hidden"],
"xcode_settings": {
"GCC_SYMBOLS_PRIVATE_EXTERN": "YES", # -fvisibility=hidden
}
}],
["OS=='win'", {
"defines": [ "WINDOWS_OS" ]
}]
]
}]
}
src-native/index.cpp
file...#define NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED
#include <napi.h>
#include <iostream>
Napi::Value helloWorld(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
std::cout << "C++: Hello" << std::endl;
return Napi::String::New(env, "World");
}
Napi::Object init(Napi::Env env, Napi::Object exports) {
exports.Set(Napi::String::New(env, "helloWorld"), Napi::Function::New(env, helloWorld));
return exports;
}
NODE_API_MODULE(addon, init);
src-native/native.d.ts
file...declare const native: {
helloWorld: () => string,
};
export default native;
vite.main.config.ts
file...//...
import { nativeNodeFile } from './build-plugins/vite-plugin.nativeNodeFile';
export default defineConfig({
//...
plugins: [
nativeNodeFile([{
// Location of the type definition file. Whenever this is imported this plugin
// will replace the import with an import of the `.node` file.
import: './src-native/native',
// Source location of the built `.node` file.
built: './build/Release/native.node',
// Location to put the `.node` file relative to output bundle.
includePath: '../bin/native.node'
}]),
],
//...
});
src-main/main.ts
file add...import native from '../src-native/native';
console.log('Javascript:', native.helloWorld());
The native code will NOT be compiled after the first time so...
force: true,
in forge.config.ts
to make sure your code gets compiled every time you start
or package
."rebuild": "electron-rebuild -f -w native --disable-pre-gyp-copy"
command to your package.json
.native
is the name of the module given in binding.gyp
.The Electron Forge CLI commands in package.json
run the cli command script file with tsx to fix the Electron Forge CLI's typescript support for the config file. This is a known issue #3671. Note: You can specify any cli options the usually way.
Code/assets in this template come from...
And everything else done by me (codec) is marked with CC0 1.0