Contents
This is a starting point repository for Electron applications that integrate the following:
- TypeScript – A type-safety system on top of JavaScript
- Svelte – Web UI Framework
- Electron Context Isolation - The latest security practices incorporated into Electron.
Package | Version |
---|---|
Electron | 19.0.6 |
Svelte | 3.48.0 |
Check the new Electron release cadence for information on updating to new releases.
Many articles on the web purport to explain how to combine
Svelte, Electron and TypeScript. One usually ends up with a
hello world screen that can not be easily extended
and nor run well in Electron 12 (which
changed the nodeIntegration
default in favor of contextIsolation
).
If you do not intend to load remote content in your renderer processes, then this approach may be overkill for you. You can probably follow other "getting-started" tutorials that do a better job of configuring hot-replacment and just remember to add
nodeIntegration: true
contextIsolation: false
to your web preferences.
Use degit rather than
git clone
to skim the latest version of this repository
and get started with your own repository.
-
Copy code.
npx degit https://github.com/pglezen/electron-typescript-svelte-starter.git myapp
-
Change to
myapp
directory.cd myapp
-
Install dependencies.
npm install
-
Compile main process components.
npm run build:main
-
Compile main window compoments.
npm run build:ui
-
Run application.
npm run start
-
build
- Used for build-time artifacts like icons. -
deploy
– Used by electron-builder for distributing the final executable. Note that electron-builder usesdist
by default. This has to be changed in the build configuration. -
dist
- Main process files go here. Files for supporting render processes go in subdirectories. This is a transpiling target for TypeScript and Svelte. -
dist/mainWindow
– The renderer process of the main window. -
dist/logsWindow
- The renderer process of the logger window. -
src/sometypes.d.ts
– a type definition file for use by the project. -
src/main
– TypeScript source for the main processes. -
src/UI
– Svelte source code for the renderer process usinglang="ts"
to support TypeScript. For each subdirectoryxxxx
of this folder there should be adist/xxxxWindow
folder- a
buildWindow('xxxx')
entry in the exported array at the bottom ofrollup.config.js
.
I had trouble combining the TypeScript configuration between the Svelte files and the Electron files. So I created two:
tsconfig-main.json
– Electron main process.tsconfig-ui.json
– Electron renderer processes.
Each one is referenced from its build. The main build uses the
--project
option of the tsc
command to reference tsconfig-main.json
and compile its files (in src/main
) to the the dist
directory.
The Svelte components are processed through Rollup. In Rollup,
TypeScript processing is configured through the @rollup/plugin-typescript
entry in rollup.config.js
.
typescript({
tsconfig: 'tsconfig-ui.json',
sourceMap: !production,
inlineSources: !production,
}),
For renderers with contextIsolation = true
, neither the Node.js nor the
Electron components which leverage Node.js are available to the renderer
processes; not even ipcRenderer
, which is generally regarded as the bare
minimum. The "loophole" is a preload.js
script (configured in
webPreferences
) that determines exactly what is allowed by
- Importing/Creating it,
- Passing it to
contextBridge.exposeInMainWorld
.
The contextBridge will
ensure that these items (and only these items) are available to renderer processes
via the window
object. If you try to assign directly to the window
object
from within the preload.js
, it will be gone by the time the renderer loads.
There is a whole range of techniques for configuring the ContextBridge.
This StackOverflow anwser is
what schooled me. It specifies exactly what the renderer can do and nothing
more. It's air tight; but it requires one to individually catalog each message.
The approach I've taken is a bit more relaxed, using a generic send
and on
methods of an ipc
member, to be added to the window
global context of the
render.
The diagram above illustates how to interweave the context bridge definitions with the global namespace TypeScript declarations.
-
The
preload.js
script (shown at the top) is defined in the main process. It is passed to thewebPreferences
option for a newBrowserWindow
instance. -
The new renderer process has the
send
andon
functions available to it on thewindow
object as attributes of anipc
object (i.e.window.ipc.send
andwindow.ipc.on
). -
A TypeScript compiler will mark these functions as undefined. To inform the TypeScript compiler of their existence, a
declare global
statement is added to declare the existence of these functions on thewindow.ipc
scope. This makes it to all the Svelte code files that import the store.
The context bridge action is indicated with red arrow. The
affect of the TypeScript global declare
is indicated with
the orange arrows.
I'm just not good enough at this stuff, yet. For now, I recompile
the main process each time I change electron code. For the renderers,
I run npm run dev
that dynamically recompiles the renderer TypeScript.
But the renderer window still requires a manual refresh (ctrl/cmd-R).
I'm not sure if rollup-plugin-livereload
knows how to deal with an
Electron renderer.
Here are the steps for creating a new window.
-
Create a new subdirectory of
dist
namedxxxxWindow
wherexxxx
is some prefix that identifies your window. We'll be usingxxxx
as a prefix for several other artifacts. -
Copy the
index.html
from thedist/mainWindow
directory intodist/xxxxWindow
. Change the<title>
and script references as appropriate. Leave the references to./bundle.css
and./bundle.js
. -
Create a
xxxx
subdirectory ofsrc/UI
. Copysrc/UI/main/index.ts
into this new directory. This will be your window's entry point. -
Add your Svelte code to this directory. Reference your
*.svelte
component fromindex.ts
. -
(Optional) Add a custom
xxxxPreload.js
script tosrc/main
if you wish to customize what is exposed to this window. This might be desirable if this window loads external content or scripts. Otherwise using thepreload.js
used by the main window is fine. -
Edit
rollup.config.js
. Add a new call tobuildWindow
for your new window. For example:export default [ buildWindow('main', true), buildWindow('logs'), buildWindow('xxxx'), ]
Omitting the second parameter (default
false
) avoids running multiple instances of the test server. Only the main window should run the test server.
You won't see this window until you load it from your main process.
A logging component is included both as a useful utility and as a demonstration of creating a new window with its own preload script.
The console.log
output from a renderer process is available from
the BrowserWindow
debugger. The main process output is available
from the CLI when the application is run from the CLI. But this
is not the case when run as an application. This component creates
a window that application users can open and use to report back to
the application developer events about the main process.
The package.json
has a build
section for running
electron-builder. I didn't add it
to this project's dependency list because many people prefer to install
it globally. If you do not wish to use electon-builder, simply
disregard or remove the build
section from package.json
.
Note that this project's build configuration overrides the default
output directory to be deploy
instead of dist
, since dist
is
already being used for the transpilation target. The dist
directory
is the source for electron-builder.