Electron is a popular framework for developing desktop applications using web technologies like HTML, CSS, and JavaScript. There are many applications (including both open source and closed source) built on top of Electron and some of them can be found at the following websites: - Electron Apps - Awesome Electron
After being listed on Wanted Ports for some time, Electron landed on the FreeBSD’s Ports tree in May 2019 and two major versions (4 and 6) are currently supported. Two Electron-based applications, both of which happen to be code editors, Visual Studio Code and Atom, have been added to the Ports since then.
If you look into those ports, you will find a lengthy Makefile and a lot of patch files. So, you may think porting an Electron-based application is a laborious work. However, it is not the case usually. Those two editors are outliers and porting an Electron-based application should be easier than you think.
With some helper makefiles (still work-in-progress), you can make a port by writing less than a hundred lines of Makefile and some companion files.
In this post, I will describe how you can make use of those .mk
files for porting an Electron-based application. I pick a simple (but still useful) application, which is YouTube Music Desktop, for illustration purposes.
Prerequisites
Before starting porting, we need to do some preparatory work. First, we need information about the application such as: - The version of Electron the application depends, - The version of Node needed for building the application, and - The Node package manager needed for building the application.
A README document and/or a file called package.json
in the application’s source archive usually provide such information. Let’s download the archive and look into it.
fetch https://github.com/ytmdesktop/ytmdesktop/archive/v1.8.2.tar.gz
tar -xzf v1.8.2.tar.gz
less ytmdesktop-1.8.2/package.json
- Electron version — Electron is usually specified as a development dependency of an application project. Look into
devDependencies
section ofpackage.json
and we find the line"electron": "^7.1.11",
. The version of Electron needed is7
. - Node version — We don’t find specific descriptions about Node in the archive. So, we assume any supported Node versions can be used. In our case, let’s use version
12
. (NOTE: This is just because I like to use the latest LTS (Long Term Support) version and any other supported versions should work.) - Package manager — An application project usually uses a file for locking down all the versions of its dependencies (node modules) for project reproducibility. The file
package-lock.json
is for the package manager “NPM” andyarn.lock
for “Yarn”. In our example, the archive has both files, which probably means either package manager can be used. We choose NPM as the package manager (at my discretion :-).
In summary, we will use: - Electron: 7 - Node: 12 - Package manager: NPM
One more step of preparation is necessary. As mentioned above, the port will use features provided by the work-in-progress .mk
files. So, let’s obtain those files and copy them into the ports directory.
git clone https://github.com/tagattie/FreeBSD-Electron.git
cp FreeBSD-Electron/Mk/Uses/*.mk ${PORTSDIR}/Mk/Uses
Now we’re all set to start making a port.
Porting
NOTE: The complete port is available at my forked Ports repository. If you quickly want to see what it looks, please head over there.
Initialize the port
First, initialize the port, for example, by executing the following commands. (NOTE: ports-mgmt/porttools
is used here.)
cd ${PORTSDIR}
port create multimedia/ytmdesktop
Put package.json and lock file
Copy the files package.json
and package-lock.json
in the application’s source archive into the port’s files/packagejsons
directory.
cd multimedia/ytmdesktop
mkdir -p files/packagejsons
cp /path/to/archive/ytmdesktop-1.8.2/package*.json files/packagejsons
Why do we need this?
The most troublesome part of porting an Electron-based application I guess is preparation of distribution files. An official port is required to be able to be built with Poudriere, which means all distribution files (including all node modules the application depends on) must be downloaded in advance and verified their integrity with the checksum file (distinfo
).
A project using NPM specifies all module dependencies in package.json
and package-lock.json
. The helper .mk
files make use of those files to “pre-fetch” all node modules needed by the application and tar them up into a single archive file subject to checksum verification.
So, you don’t have to manually handle distribution files for the node modules. The Ports infrastructure takes care of naming, fetching and making checksum of those node modules (when you execute make makesum
).
Write Makefile
Now, let’s prepare the Makefile. I will describe Electron-related parts only. For the complete Makefile, please check my forked repo.
There are three important variables we need to specify, which are USES
, USE_NODE
, and USE_ELECTRON
. First, let’s look at USES
and USE_NODE
. By defining those two variables, the specified versions of Electron and Node, and the specified packager manager are automatically added to necessary dependencies.
USES= electron:7 node:12,build
USE_NODE= npm
The next variable USE_ELECTRON
is about the heart of the features provided. So, I will explain these features in detail.
USE_ELECTRON= prefetch extract prebuild build:builder
PREFETCH_TIMESTAMP= 1582793516
prefetch
— When specified, if a distribution file does not exist in${DISTDIR}
, the fetch phase downloads all node modules the application depends on using pre-storedpackage.json
andpackage-lock.json
. Downloaded node modules are archived into an automatically-named single tar file as one ofDISTFILES
.PREFETCH_TIMESTAMP
— Ifprefetch
feature is used, this variable must be defined. The variable is a timestamp given to every directory, file or link in the tar archive, which is necessary for reproducibility of the archive. You can use the commanddate '+%s'
to obtain a value for this.
extract
— When specified, the extract phase installs the pre-fetched node modules into the port’s working source directory.prebuild
— When specified, the build phase rebuilds native node modules against the specified version of Node so that Node can execute the native modules for building the application. In addition, the feature enables rebuilding native node modules against the specified version of Electron before application packaging.
Roughly speaking, with those three features, the port build process executes operations equivalent to npm install
divided into three phases.
The last feature is about application packaging which generates a distributable form of the application. According to the website, there are the following popular tools: - electron-forge - electron-builder - electron-packager
The feature currently supports electron-builder and electron-packager (build:builder
and build:packager
respectively).
A packaging tool is usually specified as a development dependency. Look into devDependencies
of package.json
and we find the line "electron-builder": "^21.2.0",
. So, the application depends on electron-builder.
We are almost finished, but one last thing remains. Preparing the install phase is the most tedious work. You will have to write the installation target from scratch. Required processes are:
- create a wrapper script for the application,
- create a .desktop
entry for the application,
- generate icons for the application (unless there are in the source archive already), and
- write do-install
target in the Makefile.
A template of the wrapper script will look something like this:
#! /bin/sh
export NODE_ENV=production
export ELECTRON_IS_DEV=0
electron%%ELECTRON_VER_MAJOR%% %%DATADIR%%/resources/app.asar $@
Write do-install
target to manually install those created wrapper script, .desktop
file, and icons. Don’t forget to copy the application resources directory generated by the packaging tool to ${DATADIR}
.
do-install:
# installation of wrapper script, .desktop entry and icons
# to appropriate locations
(snip)
# install application data directory to ${DATADIR}
${MKDIR} ${STAGEDIR}${DATADIR}
cd ${WRKSRC}/dist/linux-unpacked && \
${COPYTREE_SHARE} resources ${STAGEDIR}${DATADIR}
Building
Finally, we are ready to build the port.
make makesum # to generate distinfo
make build
We still need pkg-descr
and pkg-plist
files to make a package of the application. I would like to leave them for you since needed work is not Electron-specific.