Bundle with Vite.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
Anders Kaseorg
2023-01-18 23:32:26 -05:00
committed by Anders Kaseorg
parent 2f4103248d
commit d42b752ac1
20 changed files with 1196 additions and 200 deletions

6
.gitignore vendored
View File

@@ -8,7 +8,8 @@
.transifexrc
# Compiled binary build directory
dist/
/dist/
/dist-electron/
#snap generated files
snap/parts
@@ -39,6 +40,3 @@ config.gypi
# tests/package.json
.python-version
# Ignore all the typescript compiled files
app/**/*.js

View File

@@ -1,3 +1,3 @@
/app/**/*.js
/dist
/dist-electron
/public/translations/*.json

15
app/common/paths.ts Normal file
View File

@@ -0,0 +1,15 @@
import path from "node:path";
import process from "node:process";
import url from "node:url";
export const bundlePath = __dirname;
export const publicPath = import.meta.env.DEV
? path.join(bundlePath, "../public")
: bundlePath;
export const bundleUrl = import.meta.env.DEV
? process.env.VITE_DEV_SERVER_URL
: url.pathToFileURL(__dirname).href + "/";
export const publicUrl = bundleUrl;

View File

@@ -3,9 +3,10 @@ import path from "node:path";
import i18n from "i18n";
import * as ConfigUtil from "./config-util.js";
import {publicPath} from "./paths.js";
i18n.configure({
directory: path.join(__dirname, "../../public/translations/"),
directory: path.join(publicPath, "translations/"),
updateFiles: false,
});

View File

@@ -7,6 +7,7 @@ import * as remoteMain from "@electron/remote/main";
import windowStateKeeper from "electron-window-state";
import * as ConfigUtil from "../common/config-util.js";
import {bundlePath, bundleUrl, publicPath} from "../common/paths.js";
import type {RendererMessage} from "../common/typed-ipc.js";
import type {MenuProps} from "../common/types.js";
@@ -35,13 +36,13 @@ let badgeCount: number;
let isQuitting = false;
// Load this url in main window
const mainUrl = "file://" + path.join(__dirname, "../renderer", "main.html");
// Load this file in main window
const mainUrl = new URL("app/renderer/main.html", bundleUrl).href;
const permissionCallbacks = new Map<number, (grant: boolean) => void>();
let nextPermissionCallbackId = 0;
const appIcon = path.join(__dirname, "../../public/resources", "Icon");
const appIcon = path.join(publicPath, "resources/Icon");
const iconPath = (): string =>
appIcon + (process.platform === "win32" ? ".ico" : ".png");
@@ -74,7 +75,7 @@ function createMainWindow(): BrowserWindow {
minWidth: 500,
minHeight: 400,
webPreferences: {
preload: require.resolve("../renderer/js/main"),
preload: path.join(bundlePath, "renderer.js"),
sandbox: false,
webviewTag: true,
},

View File

@@ -1,6 +1,6 @@
import {app} from "electron/main";
import * as Sentry from "@sentry/electron";
import * as Sentry from "@sentry/electron/main"; // eslint-disable-line n/file-extension-in-import
import {getConfigItem} from "../common/config-util.js";

26
app/renderer/about.html Normal file
View File

@@ -0,0 +1,26 @@
<!DOCTYPE html>
<meta charset="UTF-8" />
<link rel="stylesheet" href="css/about.css" />
<!-- Initially hidden to prevent FOUC -->
<div class="about" hidden>
<img class="logo" src="../resources/zulip.png" />
<p class="detail" id="version"></p>
<div class="maintenance-info">
<p class="detail maintainer">
Maintained by
<a href="https://zulip.com" target="_blank" rel="noopener noreferrer"
>Zulip</a
>
</p>
<p class="detail license">
Available under the
<a
href="https://github.com/zulip/zulip-desktop/blob/main/LICENSE"
target="_blank"
rel="noopener noreferrer"
>Apache 2.0 License</a
>
</p>
</div>
</div>

View File

@@ -1,6 +1,5 @@
import type {WebContents} from "electron/main";
import fs from "node:fs";
import path from "node:path";
import process from "node:process";
import * as remote from "@electron/remote";
@@ -11,6 +10,7 @@ import type {Html} from "../../../common/html.js";
import {html} from "../../../common/html.js";
import type {RendererMessage} from "../../../common/typed-ipc.js";
import type {TabRole} from "../../../common/types.js";
import preloadCss from "../../css/preload.css?raw"; // eslint-disable-line n/file-extension-in-import
import {ipcRenderer} from "../typed-ipc-renderer.js";
import * as SystemUtil from "../utils/system-util.js";
@@ -210,10 +210,7 @@ export default class WebView {
this.focus();
this.props.onTitleChange();
// Injecting preload css in webview to override some css rules
(async () =>
this.getWebContents().insertCSS(
fs.readFileSync(path.join(__dirname, "/../../css/preload.css"), "utf8"),
))();
(async () => this.getWebContents().insertCSS(preloadCss))();
// Get customCSS again from config util to avoid warning user again
const customCss = ConfigUtil.getConfigItem("customCSS", null);

View File

@@ -2,6 +2,7 @@ import {clipboard} from "electron/common";
import fs from "node:fs";
import path from "node:path";
import process from "node:process";
import url from "node:url";
import {Menu, app, dialog, session} from "@electron/remote";
import * as remote from "@electron/remote";
@@ -15,6 +16,7 @@ import * as EnterpriseUtil from "../../common/enterprise-util.js";
import * as LinkUtil from "../../common/link-util.js";
import Logger from "../../common/logger-util.js";
import * as Messages from "../../common/messages.js";
import {bundlePath, bundleUrl} from "../../common/paths.js";
import type {NavItem, ServerConf, TabData} from "../../common/types.js";
import FunctionalTab from "./components/functional-tab.js";
@@ -45,12 +47,13 @@ const logger = new Logger({
file: "errors.log",
});
const rendererDirectory = path.resolve(__dirname, "..");
type ServerOrFunctionalTab = ServerTab | FunctionalTab;
const rootWebContents = remote.getCurrentWebContents();
const dingSound = new Audio("../../public/resources/sounds/ding.ogg");
const dingSound = new Audio(
new URL("resources/sounds/ding.ogg", bundleUrl).href,
);
export class ServerManagerView {
$addServerButton: HTMLButtonElement;
@@ -401,7 +404,7 @@ export class ServerManagerView {
await this.openNetworkTroubleshooting(index);
},
onTitleChange: this.updateBadge.bind(this),
preload: "js/preload.js",
preload: url.pathToFileURL(path.join(bundlePath, "preload.js")).href,
}),
}),
);
@@ -628,7 +631,7 @@ export class ServerManagerView {
reconnectUtil.pollInternetAndReload();
await webview
.getWebContents()
.loadURL(`file://${rendererDirectory}/network.html`);
.loadURL(new URL("app/renderer/network.html", bundleUrl).href);
}
async activateLastTab(index: number): Promise<void> {

View File

@@ -1,45 +1,21 @@
import {app} from "@electron/remote";
import {html} from "../../../common/html.js";
import {bundleUrl} from "../../../common/paths.js";
export class AboutView {
static async create(): Promise<AboutView> {
return new AboutView();
return new AboutView(
await (await fetch(new URL("app/renderer/about.html", bundleUrl))).text(),
);
}
readonly $view: HTMLElement;
private constructor() {
private constructor(templateHtml: string) {
this.$view = document.createElement("div");
const $shadow = this.$view.attachShadow({mode: "open"});
$shadow.innerHTML = html`
<link rel="stylesheet" href="css/about.css" />
<!-- Initially hidden to prevent FOUC -->
<div class="about" hidden>
<img class="logo" src="../resources/zulip.png" />
<p class="detail" id="version">v${app.getVersion()}</p>
<div class="maintenance-info">
<p class="detail maintainer">
Maintained by
<a
href="https://zulip.com"
target="_blank"
rel="noopener noreferrer"
>Zulip</a
>
</p>
<p class="detail license">
Available under the
<a
href="https://github.com/zulip/zulip-desktop/blob/main/LICENSE"
target="_blank"
rel="noopener noreferrer"
>Apache 2.0 License</a
>
</p>
</div>
</div>
`.html;
$shadow.innerHTML = templateHtml;
$shadow.querySelector("#version")!.textContent = `v${app.getVersion()}`;
}
destroy() {

View File

@@ -1,7 +1,7 @@
import process from "node:process";
import type {DndSettings} from "../../../../common/dnd-util.js";
import {html} from "../../../../common/html.js";
import {bundleUrl} from "../../../../common/paths.js";
import type {NavItem} from "../../../../common/types.js";
import {ipcRenderer} from "../../typed-ipc-renderer.js";
@@ -14,7 +14,11 @@ import {initShortcutsSection} from "./shortcuts-section.js";
export class PreferenceView {
static async create(): Promise<PreferenceView> {
return new PreferenceView();
return new PreferenceView(
await (
await fetch(new URL("app/renderer/preference.html", bundleUrl))
).text(),
);
}
readonly $view: HTMLElement;
@@ -23,28 +27,10 @@ export class PreferenceView {
private readonly nav: Nav;
private navItem: NavItem = "General";
private constructor() {
private constructor(templateHtml: string) {
this.$view = document.createElement("div");
this.$shadow = this.$view.attachShadow({mode: "open"});
this.$shadow.innerHTML = html`
<link
rel="stylesheet"
href="${require.resolve("../../../css/fonts.css")}"
/>
<link
rel="stylesheet"
href="${require.resolve("../../../css/preference.css")}"
/>
<link
rel="stylesheet"
href="${require.resolve("@yaireo/tagify/dist/tagify.css")}"
/>
<!-- Initially hidden to prevent FOUC -->
<div id="content" hidden>
<div id="sidebar"></div>
<div id="settings-container"></div>
</div>
`.html;
this.$shadow.innerHTML = templateHtml;
const $sidebarContainer = this.$shadow.querySelector("#sidebar")!;
this.$settingsContainer = this.$shadow.querySelector(

View File

@@ -1,5 +1,8 @@
import {contextBridge, webFrame} from "electron/renderer";
import fs from "node:fs";
import path from "node:path";
import {bundlePath} from "../../common/paths.js";
import electron_bridge, {bridgeEvents} from "./electron-bridge.js";
import * as NetworkError from "./pages/network.js";
@@ -77,5 +80,5 @@ window.addEventListener("load", () => {
(async () =>
webFrame.executeJavaScript(
fs.readFileSync(require.resolve("./injected"), "utf8"),
fs.readFileSync(path.join(bundlePath, "injected.js"), "utf8"),
))();

View File

@@ -7,6 +7,7 @@ import process from "node:process";
import {BrowserWindow, Menu, Tray} from "@electron/remote";
import * as ConfigUtil from "../../common/config-util.js";
import {publicPath} from "../../common/paths.js";
import type {RendererMessage} from "../../common/typed-ipc.js";
import type {ServerManagerView} from "./main.js";
@@ -14,11 +15,7 @@ import {ipcRenderer} from "./typed-ipc-renderer.js";
let tray: ElectronTray | null = null;
const iconDir = "../../../public/resources/tray";
const traySuffix = "tray";
const appIcon = path.join(__dirname, iconDir, traySuffix);
const appIcon = path.join(publicPath, "resources/tray/tray");
const iconPath = (): string => {
if (process.platform === "linux") {

View File

@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width" />
<title>Zulip</title>
<link rel="stylesheet" href="css/fonts.css" />
<link rel="stylesheet" href="css/main.css" type="text/css" media="screen" />
<link rel="stylesheet" href="css/main.css" />
</head>
<body>

View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<meta charset="UTF-8" />
<link rel="stylesheet" href="css/fonts.css" />
<link rel="stylesheet" href="css/preference.css" />
<script type="module">
import "@yaireo/tagify/dist/tagify.css";
</script>
<!-- Initially hidden to prevent FOUC -->
<div id="content" hidden>
<div id="sidebar"></div>
<div id="settings-container"></div>
</div>

1046
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
"name": "zulip",
"productName": "Zulip",
"version": "5.9.4",
"main": "./app/main",
"main": "./dist-electron",
"description": "Zulip Desktop App",
"license": "Apache-2.0",
"copyright": "Kandra Labs, Inc.",
@@ -21,8 +21,7 @@
"node": ">=16.13.2"
},
"scripts": {
"start": "tsc && electron .",
"clean-ts-files": "git clean \"app/*.js\" -xf",
"start": "vite",
"watch-ts": "tsc -w",
"reinstall": "rimraf node_modules && npm install",
"postinstall": "electron-builder install-app-deps",
@@ -30,11 +29,11 @@
"lint-html": "htmlhint \"app/**/*.html\"",
"lint-js": "xo",
"prettier-non-js": "prettier --check --loglevel=warn . \"!**/*.{js,ts}\"",
"test": "tsc --noEmit && npm run lint-html && npm run lint-css && npm run lint-js && npm run prettier-non-js",
"test-e2e": "tsc && tape \"tests/**/*.js\"",
"pack": "tsc && electron-builder --dir",
"dist": "tsc && electron-builder",
"mas": "tsc && electron-builder --mac mas"
"test": "tsc && npm run lint-html && npm run lint-css && npm run lint-js && npm run prettier-non-js",
"test-e2e": "vite build && tape \"tests/**/*.js\"",
"pack": "vite build && electron-builder --dir",
"dist": "vite build && electron-builder",
"mas": "vite build && electron-builder --mac mas"
},
"pre-commit": [
"test"
@@ -47,8 +46,7 @@
"**/*.node"
],
"files": [
"app/**/*",
"public/**/*"
"dist-electron/**/*"
],
"copyright": "©2020 Kandra Labs, Inc.",
"mac": {
@@ -143,25 +141,13 @@
"InstantMessaging"
],
"dependencies": {
"@electron/remote": "^2.0.8",
"@sentry/electron": "^4.1.2",
"@yaireo/tagify": "^4.5.0",
"adm-zip": "^0.5.5",
"auto-launch": "^5.0.5",
"backoff": "^2.5.0",
"electron-log": "^4.3.5",
"electron-updater": "^5.0.1",
"electron-window-state": "^5.0.3",
"escape-goat": "^3.0.0",
"gatemaker": "^1.0.0",
"get-stream": "^6.0.1",
"i18n": "^0.15.1",
"iso-639-1": "^2.1.9",
"node-json-db": "^1.3.0",
"semver": "^7.3.5",
"zod": "^3.5.1"
"@electron/remote": "^2.0.8"
},
"optionalDependencies": {
"fs-xattr": "^0.3.1"
},
"devDependencies": {
"@sentry/electron": "^4.1.2",
"@types/adm-zip": "^0.5.0",
"@types/auto-launch": "^5.0.2",
"@types/backoff": "^2.5.2",
@@ -169,22 +155,39 @@
"@types/node": "^16.11.26",
"@types/requestidlecallback": "^0.3.4",
"@types/yaireo__tagify": "^4.3.2",
"@yaireo/tagify": "^4.5.0",
"adm-zip": "^0.5.5",
"auto-launch": "^5.0.5",
"backoff": "^2.5.0",
"dotenv": "^16.0.0",
"electron": "^22.0.0",
"electron-builder": "^23.0.3",
"electron-log": "^4.3.5",
"electron-notarize": "^1.0.0",
"electron-updater": "^5.0.1",
"electron-window-state": "^5.0.3",
"escape-goat": "^3.0.0",
"gatemaker": "^1.0.0",
"get-stream": "^6.0.1",
"htmlhint": "^1.1.2",
"i18n": "^0.15.1",
"iso-639-1": "^2.1.9",
"medium": "^1.2.0",
"node-json-db": "^1.3.0",
"playwright-core": "^1.30.0-alpha-jan-3-2023",
"pre-commit": "^1.2.2",
"prettier": "^2.3.2",
"rimraf": "^3.0.2",
"semver": "^7.3.5",
"stylelint": "^14.5.3",
"stylelint-config-prettier": "^9.0.3",
"stylelint-config-standard": "^29.0.0",
"tape": "^5.2.2",
"typescript": "^4.3.5",
"xo": "^0.53.1"
"vite": "^4.1.1",
"vite-plugin-electron": "^0.11.1",
"xo": "^0.53.1",
"zod": "^3.5.1"
},
"prettier": {
"bracketSpacing": false,
@@ -220,7 +223,8 @@
"from": "./app",
"except": [
"./common",
"./renderer"
"./renderer",
"./resources"
]
}
]

View File

@@ -1,5 +1,5 @@
{
"version": "5.9.3",
"productName": "ZulipTest",
"main": "../app/main/index.js"
"main": "../dist-electron"
}

View File

@@ -1,10 +1,13 @@
{
"compilerOptions": {
"target": "es2021",
"module": "commonjs",
"noEmit": true,
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"esModuleInterop": true,
"resolveJsonModule": true,
"strict": true,
"noImplicitOverride": true
"noImplicitOverride": true,
"types": ["vite/client"]
}
}

101
vite.config.ts Normal file
View File

@@ -0,0 +1,101 @@
import * as path from "node:path";
import {defineConfig} from "vite";
import electron from "vite-plugin-electron";
let resolveInjected: () => void;
let resolvePreload: () => void;
let resolveRenderer: () => void;
const whenInjected = new Promise<void>((resolve) => {
resolveInjected = resolve;
});
const whenPreload = new Promise<void>((resolve) => {
resolvePreload = resolve;
});
const whenRenderer = new Promise<void>((resolve) => {
resolveRenderer = resolve;
});
export default defineConfig({
plugins: [
electron([
{
entry: {
index: "app/main",
},
async onstart({startup}) {
await whenInjected;
await whenPreload;
await whenRenderer;
await startup();
},
vite: {
build: {
sourcemap: true,
rollupOptions: {
external: ["electron", /^electron\//, "fs-xattr"],
},
ssr: true,
},
},
},
{
entry: {
injected: "app/renderer/js/injected.ts",
},
onstart() {
resolveInjected();
},
vite: {
build: {
sourcemap: "inline",
},
},
},
{
entry: {
preload: "app/renderer/js/preload.ts",
},
onstart() {
resolvePreload();
},
vite: {
build: {
sourcemap: "inline",
rollupOptions: {
external: ["electron", /^electron\//],
},
},
},
},
{
entry: {
renderer: "app/renderer/js/main.ts",
},
onstart() {
resolveRenderer();
},
vite: {
build: {
sourcemap: true,
rollupOptions: {
external: ["electron", /^electron\//, "@yaireo/tagify"],
},
},
},
},
]),
],
build: {
outDir: "dist-electron",
sourcemap: true,
rollupOptions: {
input: {
renderer: path.join(__dirname, "app/renderer/main.html"),
network: path.join(__dirname, "app/renderer/network.html"),
about: path.join(__dirname, "app/renderer/about.html"),
preference: path.join(__dirname, "app/renderer/preference.html"),
},
},
},
});