Switch to using electron-vite instead of WebPack (#45)

This sets us back up to use Vite via the electron-vite package. This
will let us continue to build our testing suite on Vitest and take
advantage of Vite features like Hot Module Reloading, etc.

---------

Co-authored-by: sawka <mike.sawka@gmail.com>
This commit is contained in:
Evan Simkowitz 2024-06-13 16:49:25 -07:00 committed by GitHub
parent 0f992c535d
commit b2b1f9b9df
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 240 additions and 2884 deletions

View File

@ -4,25 +4,25 @@ Prereqs:
You'll need to install "task" (which we're using as a build/run system):
```
```sh
brew install go-task
```
On first checkout:
```
```sh
yarn
go mod tidy
```
To run the app, you'll first need to run the webpack watcher:
Then, run the following command to start the app using the Vite dev server (this will enable Hot Module Reloading):
```
task webpack
```sh
task electron:dev
```
Then, in a separate terminal, this command will run the electron app:
To run the app without the dev server, run the following instead:
```
task electron
```sh
task electron:start
```

View File

@ -1,431 +0,0 @@
version: "3"
vars:
APP_NAME: "NextWave"
BIN_DIR: "bin"
VITE_PORT: "{{.WAILS_VITE_PORT | default 9245}}"
tasks:
## -------------------------- Build -------------------------- ##
build:
summary: Builds the application
cmds:
# Build for current OS
- task: build:{{OS}}
# Uncomment to build for specific OSes
# - task: build:linux
# - task: build:windows
# - task: build:darwin
## ------> Windows <-------
build:windows:
summary: Builds the application for Windows
deps:
- task: go:mod:tidy
- task: build:frontend
- task: generate:icons
- task: generate:syso
vars:
ARCH: "{{.ARCH}}"
cmds:
- go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}}.exe
vars:
BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -ldflags="-w -s -H windowsgui"{{else}}-gcflags=all="-l"{{end}}'
env:
GOOS: windows
CGO_ENABLED: 0
GOARCH: "{{.ARCH | default ARCH}}"
PRODUCTION: '{{.PRODUCTION | default "false"}}'
build:windows:prod:arm64:
summary: Creates a production build of the application
cmds:
- task: build:windows
vars:
ARCH: arm64
PRODUCTION: "true"
build:windows:prod:amd64:
summary: Creates a production build of the application
cmds:
- task: build:windows
vars:
ARCH: amd64
PRODUCTION: "true"
build:windows:debug:arm64:
summary: Creates a debug build of the application
cmds:
- task: build:windows
vars:
ARCH: arm64
build:windows:debug:amd64:
summary: Creates a debug build of the application
cmds:
- task: build:windows
vars:
ARCH: amd64
## ------> Darwin <-------
build:darwin:
summary: Creates a production build of the application
deps:
- task: go:mod:tidy
- task: build:frontend
- task: generate:icons
cmds:
- go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}}
vars:
BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -ldflags="-w -s"{{else}}-gcflags=all="-l"{{end}}'
env:
GOOS: darwin
CGO_ENABLED: 1
GOARCH: "{{.ARCH | default ARCH}}"
CGO_CFLAGS: "-mmacosx-version-min=10.15"
CGO_LDFLAGS: "-mmacosx-version-min=10.15"
MACOSX_DEPLOYMENT_TARGET: "10.15"
PRODUCTION: '{{.PRODUCTION | default "false"}}'
build:darwin:prod:arm64:
summary: Creates a production build of the application
cmds:
- task: build:darwin
vars:
ARCH: arm64
PRODUCTION: "true"
build:darwin:prod:amd64:
summary: Creates a production build of the application
cmds:
- task: build:darwin
vars:
ARCH: amd64
PRODUCTION: "true"
build:darwin:debug:arm64:
summary: Creates a debug build of the application
cmds:
- task: build:darwin
vars:
ARCH: arm64
build:darwin:debug:amd64:
summary: Creates a debug build of the application
cmds:
- task: build:darwin
vars:
ARCH: amd64
## ------> Linux <-------
build:linux:
summary: Builds the application for Linux
deps:
- task: go:mod:tidy
- task: build:frontend
- task: generate:icons
vars:
ARCH: "{{.ARCH}}"
cmds:
- go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/w2
vars:
BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -ldflags="-w -s"{{else}}-gcflags=all="-l"{{end}}'
env:
GOOS: linux
CGO_ENABLED: 1
GOARCH: "{{.ARCH | default ARCH}}"
PRODUCTION: '{{.PRODUCTION | default "false"}}'
build:linux:prod:arm64:
summary: Creates a production build of the application
cmds:
- task: build:linux
vars:
ARCH: arm64
PRODUCTION: "true"
build:linux:prod:amd64:
summary: Creates a production build of the application
cmds:
- task: build:linux
vars:
ARCH: amd64
PRODUCTION: "true"
build:linux:debug:arm64:
summary: Creates a debug build of the application
cmds:
- task: build:linux
vars:
ARCH: arm64
build:linux:debug:amd64:
summary: Creates a debug build of the application
cmds:
- task: build:linux
vars:
ARCH: amd64
## -------------------------- Package -------------------------- ##
package:
summary: Packages a production build of the application into a bundle
cmds:
# Package for current OS
- task: package:{{OS}}
# Package for specific os/arch
# - task: package:darwin:arm64
# - task: package:darwin:amd64
# - task: package:windows:arm64
# - task: package:windows:amd64
## ------> Windows <------
package:windows:
summary: Packages a production build of the application into a `.exe` bundle
cmds:
- task: create:nsis:installer
vars:
ARCH: "{{.ARCH}}"
vars:
ARCH: "{{.ARCH | default ARCH}}"
package:windows:arm64:
summary: Packages a production build of the application into a `.exe` bundle
cmds:
- task: package:windows
vars:
ARCH: arm64
package:windows:amd64:
summary: Packages a production build of the application into a `.exe` bundle
cmds:
- task: package:windows
vars:
ARCH: amd64
generate:syso:
summary: Generates Windows `.syso` file
dir: build
cmds:
- wails3 generate syso -arch {{.ARCH}} -icon icon.ico -manifest wails.exe.manifest -info info.json -out ../wails.syso
vars:
ARCH: "{{.ARCH | default ARCH}}"
create:nsis:installer:
summary: Creates an NSIS installer
label: "NSIS Installer ({{.ARCH}})"
dir: build/nsis
sources:
- "{{.ROOT_DIR}}\\bin\\{{.APP_NAME}}.exe"
generates:
- "{{.ROOT_DIR}}\\bin\\{{.APP_NAME}}-{{.ARCH}}-installer.exe"
deps:
- task: build:windows
vars:
PRODUCTION: "true"
ARCH: "{{.ARCH}}"
cmds:
- makensis -DARG_WAILS_'{{.ARG_FLAG}}'_BINARY="{{.ROOT_DIR}}\{{.BIN_DIR}}\{{.APP_NAME}}.exe" project.nsi
vars:
ARCH: "{{.ARCH | default ARCH}}"
ARG_FLAG: '{{if eq .ARCH "amd64"}}AMD64{{else}}ARM64{{end}}'
## ------> Darwin <------
package:darwin:
summary: Packages a production build of the application into a `.app` bundle
platforms: [darwin]
deps:
- task: build:darwin
vars:
PRODUCTION: "true"
cmds:
- task: create:app:bundle
package:darwin:arm64:
summary: Packages a production build of the application into a `.app` bundle
platforms: [darwin/arm64]
deps:
- task: package:darwin
vars:
ARCH: arm64
package:darwin:amd64:
summary: Packages a production build of the application into a `.app` bundle
platforms: [darwin/amd64]
deps:
- task: package:darwin
vars:
ARCH: amd64
create:app:bundle:
summary: Creates an `.app` bundle
cmds:
- mkdir -p {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/{MacOS,Resources}
- cp build/icons.icns {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/Resources
- cp {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/MacOS
- cp build/Info.plist {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents
## ------> Linux <------
package:linux:
summary: Packages a production build of the application for Linux
platforms: [linux]
deps:
- task: build:linux
vars:
PRODUCTION: "true"
cmds:
- task: create:appimage
create:appimage:
summary: Creates an AppImage
dir: build/appimage
platforms: [linux]
deps:
- task: build:linux
vars:
PRODUCTION: "true"
- task: generate:linux:dotdesktop
cmds:
# Copy binary + icon to appimage dir
- cp {{.APP_BINARY}} {{.APP_NAME}}
- cp ../appicon.png appicon.png
# Generate AppImage
- wails3 generate appimage -binary {{.APP_NAME}} -icon {{.ICON}} -desktopfile {{.DESKTOP_FILE}} -outputdir {{.OUTPUT_DIR}} -builddir {{.ROOT_DIR}}/build/appimage
vars:
APP_NAME: "{{.APP_NAME}}"
APP_BINARY: "../../bin/{{.APP_NAME}}"
ICON: "../appicon.png"
DESKTOP_FILE: "{{.APP_NAME}}.desktop"
OUTPUT_DIR: "../../bin"
generate:linux:dotdesktop:
summary: Generates a `.desktop` file
dir: build
sources:
- "appicon.png"
generates:
- "{{.ROOT_DIR}}/build/appimage/{{.APP_NAME}}.desktop"
cmds:
- mkdir -p {{.ROOT_DIR}}/build/appimage
# Run `wails3 generate .desktop -help` for all the options
- wails3 generate .desktop -name "{{.APP_NAME}}" -exec "{{.EXEC}}" -icon "{{.ICON}}" -outputfile {{.ROOT_DIR}}/build/appimage/{{.APP_NAME}}.desktop -categories "{{.CATEGORIES}}"
# -comment "A comment"
# -terminal "true"
# -version "1.0"
# -genericname "Generic Name"
# -keywords "keyword1;keyword2;"
# -startupnotify "true"
# -mimetype "application/x-extension1;application/x-extension2;"
vars:
APP_NAME: "{{.APP_NAME}}"
EXEC: "{{.APP_NAME}}"
ICON: "appicon"
CATEGORIES: "Development;"
OUTPUTFILE: "{{.ROOT_DIR}}/build/appimage/{{.APP_NAME}}.desktop"
## -------------------------- Misc -------------------------- ##
generate:icons:
summary: Generates Windows `.ico` and Mac `.icns` files from an image
dir: build
sources:
- "appicon.png"
generates:
- "icons.icns"
- "icons.ico"
method: timestamp
cmds:
# Generates both .ico and .icns files
# commented out for now
# - wails3 generate icons -input appicon.png
install:frontend:deps:
summary: Install frontend dependencies
sources:
- package.json
- yarn.lock
generates:
- node_modules/*
preconditions:
- sh: yarn --version
msg: "Looks like yarn isn't installed."
cmds:
- yarn
build:frontend:
summary: Build the frontend project
sources:
- "**/*"
generates:
- dist/*
deps:
- install:frontend:deps
- generate:bindings
cmds:
- yarn build
generate:bindings:
summary: Generates bindings for the frontend
sources:
- "**/*.go"
generates:
- "frontend/bindings/**/*"
cmds:
- wails3 generate bindings -silent -ts
# - wails3 generate bindings -silent
go:mod:tidy:
summary: Runs `go mod tidy`
internal: true
generates:
- go.sum
sources:
- go.mod
cmds:
- go mod tidy
# ----------------------- dev ----------------------- #
run:
summary: Runs the application
cmds:
- task: run:{{OS}}
run:windows:
cmds:
- '{{.BIN_DIR}}\\{{.APP_NAME}}.exe'
run:linux:
cmds:
- "{{.BIN_DIR}}/{{.APP_NAME}}"
run:darwin:
cmds:
- "{{.BIN_DIR}}/{{.APP_NAME}}"
dev:frontend:
summary: Runs the frontend in development mode
deps:
- task: install:frontend:deps
cmds:
- yarn dev --port {{.VITE_PORT}} --strictPort
dev:
summary: Runs the application in development mode
cmds:
- wails3 dev -config ./build/devmode.config.yaml -port {{.VITE_PORT}}
dev:reload:
summary: Reloads the application
cmds:
- task: run

View File

@ -1,3 +1,6 @@
# Copyright 2024, Command Line Inc.
# SPDX-License-Identifier: Apache-2.0
version: "3"
vars:
@ -5,7 +8,6 @@ vars:
BIN_DIR: "bin"
VERSION: "0.1.0"
tasks:
generate:
cmds:
@ -15,24 +17,26 @@ tasks:
- "pkg/service/**/*.go"
- "pkg/wstore/*.go"
webpack:
electron:dev:
cmds:
- yarn run webpack --watch --env dev
- WAVETERM_DEV=1 yarn dev
deps:
- build:server
electron:
electron:start:
cmds:
- WAVETERM_DEV=1 yarn run electron dist-dev/emain.js
- WAVETERM_DEV=1 yarn start
deps:
- build:server
build:server:
cmds:
- go build -o bin/wavesrv cmd/server/main-server.go
- go build -o dist/bin/wavesrv cmd/server/main-server.go
sources:
- "cmd/server/*.go"
- "pkg/**/*.go"
generates:
- bin/wavesrv
- dist/bin/wavesrv
deps:
- go:mod:tidy
@ -45,5 +49,3 @@ tasks:
- go.mod
cmds:
- go mod tidy

59
electron.vite.config.ts Normal file
View File

@ -0,0 +1,59 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import react from "@vitejs/plugin-react";
import { defineConfig } from "electron-vite";
import { resolve } from "path";
import { viteStaticCopy } from "vite-plugin-static-copy";
import tsconfigPaths from "vite-tsconfig-paths";
export default defineConfig({
main: {
root: ".",
build: {
rollupOptions: {
input: {
index: resolve(__dirname, "emain/emain.ts"),
},
},
outDir: "dist/main",
},
plugins: [tsconfigPaths()],
},
preload: {
root: ".",
build: {
sourcemap: true,
rollupOptions: {
input: {
index: resolve(__dirname, "emain/preload.ts"),
},
output: {
format: "cjs",
},
},
outDir: "dist/preload",
},
plugins: [tsconfigPaths()],
},
renderer: {
root: ".",
build: {
target: "es6",
sourcemap: true,
outDir: "dist/frontend",
rollupOptions: {
input: {
index: resolve(__dirname, "index.html"),
},
},
},
plugins: [
react({}),
tsconfigPaths(),
viteStaticCopy({
targets: [{ src: "node_modules/monaco-editor/min/vs/*", dest: "monaco" }],
}),
],
},
});

View File

@ -9,7 +9,8 @@ import { debounce } from "throttle-debounce";
import * as services from "../frontend/app/store/services";
const electronApp = electron.app;
const isDev = true;
const isDev = process.env.WAVETERM_DEV;
const isDevServer = !electronApp.isPackaged && process.env.ELECTRON_RENDERER_URL;
const WaveAppPathVarName = "WAVETERM_APP_PATH";
const WaveDevVarName = "WAVETERM_DEV";
@ -17,7 +18,6 @@ const WaveSrvReadySignalPidVarName = "WAVETERM_READY_SIGNAL_PID";
const AuthKeyFile = "waveterm.authkey";
const DevServerEndpoint = "http://127.0.0.1:8190";
const ProdServerEndpoint = "http://127.0.0.1:1719";
const DistDir = "dist-dev";
let waveSrvReadyResolve = (value: boolean) => {};
let waveSrvReady: Promise<boolean> = new Promise((resolve, _) => {
@ -88,7 +88,7 @@ function runWaveSrv(): Promise<boolean> {
electronApp.quit();
});
proc.on("spawn", (e) => {
console.log("spawnned wavesrv");
console.log("spawned wavesrv");
waveSrvProc = proc;
pResolve(true);
});
@ -170,7 +170,7 @@ function createWindow(client: Client, waveWindow: WaveWindow): Electron.BrowserW
? path.join(getElectronAppBasePath(), "public/logos/wave-logo-dark.png")
: undefined,
webPreferences: {
preload: path.join(getElectronAppBasePath(), DistDir, "preload.js"),
preload: path.join(getElectronAppBasePath(), "preload", "index.cjs"),
},
show: false,
autoHideMenuBar: true,
@ -184,7 +184,14 @@ function createWindow(client: Client, waveWindow: WaveWindow): Electron.BrowserW
usp.set("clientid", client.oid);
usp.set("windowid", waveWindow.oid);
const indexHtml = "index.html";
win.loadFile(path.join(getElectronAppBasePath(), "public", indexHtml), { search: usp.toString() });
if (isDevServer) {
console.log("running as dev server");
win.loadURL(`${process.env.ELECTRON_RENDERER_URL}/index.html?${usp.toString()}`);
} else {
console.log("running as file");
win.loadFile(path.join(getElectronAppBasePath(), "frontend", indexHtml), { search: usp.toString() });
}
win.webContents.on("will-navigate", shNavHandler);
win.webContents.on("will-frame-navigate", shFrameNavHandler);
win.on(
@ -209,7 +216,16 @@ function createWindow(client: Client, waveWindow: WaveWindow): Electron.BrowserW
return win;
}
electron.ipcMain.on("isDev", () => {
return isDev;
});
electron.ipcMain.on("isDevServer", () => {
return isDevServer;
});
process.on("SIGUSR1", function () {
``;
waveSrvReadyResolve(true);
});

View File

@ -1,6 +0,0 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
let { contextBridge, ipcRenderer } = require("electron");
contextBridge.exposeInMainWorld("api", {});

9
emain/preload.ts Normal file
View File

@ -0,0 +1,9 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
let { contextBridge, ipcRenderer } = require("electron");
contextBridge.exposeInMainWorld("api", {
isDev: () => ipcRenderer.sendSync("isDev"),
isDevServer: () => ipcRenderer.sendSync("isDevServer"),
});

View File

@ -4,13 +4,13 @@ import eslint from "@eslint/js";
import eslintConfigPrettier from "eslint-config-prettier";
import tseslint from "typescript-eslint";
const baseConfig = tseslint.config(eslint.configs.recommended, ...tseslint.configs.recommended, eslintConfigPrettier);
const baseConfig = tseslint.config(eslint.configs.recommended, ...tseslint.configs.recommended);
const customConfig = {
...baseConfig,
overrides: [
{
files: ["emain/emain.ts", "vite.config.ts", "electron.vite.config.ts"],
files: ["emain/emain.ts", "electron.vite.config.ts"],
env: {
node: true,
},
@ -18,4 +18,4 @@ const customConfig = {
],
};
export default customConfig;
export default [customConfig, eslintConfigPrettier];

View File

@ -1,7 +1,7 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import { CodeEditView } from "@/app/view/codeedit";
import { CodeEdit } from "@/app/view/codeedit";
import { PlotView } from "@/app/view/plotview";
import { PreviewView } from "@/app/view/preview";
import { TerminalView } from "@/app/view/term";
@ -49,7 +49,7 @@ const Block = ({ blockId, onClose }: BlockProps) => {
} else if (blockData.view === "plot") {
blockElem = <PlotView />;
} else if (blockData.view === "codeedit") {
blockElem = <CodeEditView text={null} />;
blockElem = <CodeEdit text={null} />;
}
return (
<div className="block" ref={blockRef}>

View File

@ -15,6 +15,7 @@
line-height: 1.5;
white-space: nowrap;
user-select: none;
-webkit-user-select: none;
color: var(--main-text-color);
background: var(--accent-color);

View File

@ -182,9 +182,14 @@ waveobjUpdateSubject.subscribe((msg: WSEventType) => {
WOS.updateWaveObject(update);
});
function getApi(): ElectronApi {
return (window as any).api;
}
export {
WOS,
atoms,
getApi,
getBackendHostPort,
getEventORefSubject,
getEventSubject,

View File

@ -15,7 +15,7 @@ declare var monaco: Monaco;
let monacoLoadedAtom = jotai.atom(false);
function loadMonaco() {
loader.config({ paths: { vs: "./dist-dev/monaco" } });
loader.config({ paths: { vs: "monaco" } });
loader
.init()
.then(() => {
@ -93,35 +93,22 @@ export function CodeEdit({ readonly = false, text, language, filename }: CodeEdi
const editorOpts = defaultEditorOptions();
editorOpts.readOnly = readonly;
return (
<div className="codeedit" ref={divRef}>
{divDims != null && monacoLoaded ? (
<Editor
theme={theme}
height={divDims.height}
value={text}
onMount={handleEditorMount}
options={editorOpts}
onChange={handleEditorChange}
path={filename}
language={language}
/>
) : null}
</div>
);
}
interface CodeEditViewProps {
readonly?: boolean;
text: string;
language?: string;
filename?: string;
}
export function CodeEditView({ readonly, text, language, filename }: CodeEditViewProps) {
return (
<div className="view-codeedit">
<CodeEdit readonly={readonly} text={text} language={language} filename={filename} />
<div className="codeedit" ref={divRef}>
{divDims != null && monacoLoaded ? (
<Editor
theme={theme}
height={divDims.height}
value={text}
onMount={handleEditorMount}
options={editorOpts}
onChange={handleEditorChange}
path={filename}
language={language}
/>
) : null}
</div>
</div>
);
}

View File

@ -9,7 +9,7 @@ import * as util from "@/util/util";
import clsx from "clsx";
import * as jotai from "jotai";
import { CenteredDiv } from "../element/quickelems";
import { CodeEditView } from "./codeedit";
import { CodeEdit } from "./codeedit";
import { DirectoryPreview } from "./directorypreview";
import "./view.less";
@ -185,7 +185,7 @@ function PreviewView({ blockId }: { blockId: string }) {
(mimeType.startsWith("application/") &&
(mimeType.includes("json") || mimeType.includes("yaml") || mimeType.includes("toml")))
) {
specializedView = specializedView = <CodeEditView readonly={true} text={fileContent} filename={fileName} />;
specializedView = specializedView = <CodeEdit readonly={true} text={fileContent} filename={fileName} />;
} else if (mimeType === "directory") {
specializedView = <DirectoryPreview contentAtom={fileContentAtom} fileNameAtom={fileNameAtom} />;
} else {

View File

@ -10,9 +10,9 @@ import { Terminal } from "@xterm/xterm";
import clsx from "clsx";
import * as React from "react";
import "public/xterm.css";
import { debounce } from "throttle-debounce";
import "./view.less";
import "/public/xterm.css";
function getThemeFromCSSVars(el: Element): ITheme {
const theme: ITheme = {};

View File

@ -5,6 +5,11 @@ declare global {
type TabLayoutData = {
blockId: string;
};
type ElectronApi = {
isDev: () => boolean;
isDevServer: () => boolean;
};
}
export {};

View File

@ -17,15 +17,15 @@ function loadJetBrainsMonoFont() {
return;
}
isJetBrainsMonoLoaded = true;
const jbmFontNormal = new FontFace("JetBrains Mono", "url('public/fonts/jetbrains-mono-v13-latin-regular.woff2')", {
const jbmFontNormal = new FontFace("JetBrains Mono", "url('fonts/jetbrains-mono-v13-latin-regular.woff2')", {
style: "normal",
weight: "400",
});
const jbmFont200 = new FontFace("JetBrains Mono", "url('public/fonts/jetbrains-mono-v13-latin-200.woff2')", {
const jbmFont200 = new FontFace("JetBrains Mono", "url('fonts/jetbrains-mono-v13-latin-200.woff2')", {
style: "normal",
weight: "200",
});
const jbmFont700 = new FontFace("JetBrains Mono", "url('public/fonts/jetbrains-mono-v13-latin-700.woff2')", {
const jbmFont700 = new FontFace("JetBrains Mono", "url('fonts/jetbrains-mono-v13-latin-700.woff2')", {
style: "normal",
weight: "700",
});
@ -42,11 +42,11 @@ function loadLatoFont() {
return;
}
isLatoFontLoaded = true;
const latoFont = new FontFace("Lato", "url('public/fonts/lato-regular.woff')", {
const latoFont = new FontFace("Lato", "url('fonts/lato-regular.woff')", {
style: "normal",
weight: "400",
});
const latoFontBold = new FontFace("Lato", "url('public/fonts/lato-bold.woff')", {
const latoFontBold = new FontFace("Lato", "url('fonts/lato-bold.woff')", {
style: "normal",
weight: "700",
});
@ -61,11 +61,11 @@ function loadFiraCodeFont() {
return;
}
isFiraCodeLoaded = true;
const firaCodeRegular = new FontFace("Fira Code", "url('public/fonts/firacode-regular.woff2')", {
const firaCodeRegular = new FontFace("Fira Code", "url('fonts/firacode-regular.woff2')", {
style: "normal",
weight: "400",
});
const firaCodeBold = new FontFace("Fira Code", "url('public/fonts/firacode-bold.woff2')", {
const firaCodeBold = new FontFace("Fira Code", "url('fonts/firacode-bold.woff2')", {
style: "normal",
weight: "700",
});
@ -80,19 +80,19 @@ function loadHackFont() {
return;
}
isHackFontLoaded = true;
const hackRegular = new FontFace("Hack", "url('public/fonts/hack-regular.woff2')", {
const hackRegular = new FontFace("Hack", "url('fonts/hack-regular.woff2')", {
style: "normal",
weight: "400",
});
const hackBold = new FontFace("Hack", "url('public/fonts/hack-bold.woff2')", {
const hackBold = new FontFace("Hack", "url('fonts/hack-bold.woff2')", {
style: "normal",
weight: "700",
});
const hackItalic = new FontFace("Hack", "url('public/fonts/hack-italic.woff2')", {
const hackItalic = new FontFace("Hack", "url('fonts/hack-italic.woff2')", {
style: "italic",
weight: "400",
});
const hackBoldItalic = new FontFace("Hack", "url('public/fonts/hack-bolditalic.woff2')", {
const hackBoldItalic = new FontFace("Hack", "url('fonts/hack-bolditalic.woff2')", {
style: "italic",
weight: "700",
});
@ -111,7 +111,7 @@ function loadBaseFonts() {
return;
}
isBaseFontsLoaded = true;
const mmFont = new FontFace("Martian Mono", "url('public/fonts/MartianMono-VariableFont_wdth,wght.ttf')", {
const mmFont = new FontFace("Martian Mono", "url('fonts/MartianMono-VariableFont_wdth,wght.ttf')", {
style: "normal",
weight: "normal",
});

2
go.mod
View File

@ -8,6 +8,7 @@ require (
github.com/creack/pty v1.1.18
github.com/golang-migrate/migrate/v4 v4.17.1
github.com/google/uuid v1.4.0
github.com/gorilla/handlers v1.5.2
github.com/gorilla/mux v1.8.0
github.com/gorilla/websocket v1.5.0
github.com/jmoiron/sqlx v1.4.0
@ -19,6 +20,7 @@ require (
)
require (
github.com/felixge/httpsnoop v1.0.3 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/stretchr/testify v1.8.4 // indirect

4
go.sum
View File

@ -5,12 +5,16 @@ github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/golang-migrate/migrate/v4 v4.17.1 h1:4zQ6iqL6t6AiItphxJctQb3cFqWiSpMnX7wLTPnnYO4=
github.com/golang-migrate/migrate/v4 v4.17.1/go.mod h1:m8hinFyWBn0SA4QKHuKh175Pm9wjmxj3S2Mia7dbXzM=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=

17
index.html Normal file
View File

@ -0,0 +1,17 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>The Next Wave</title>
<link rel="stylesheet" href="/fontawesome/css/fontawesome.min.css" />
<link rel="stylesheet" href="/fontawesome/css/brands.min.css" />
<link rel="stylesheet" href="/fontawesome/css/solid.min.css" />
<link rel="stylesheet" href="/fontawesome/css/sharp-solid.min.css" />
<link rel="stylesheet" href="/fontawesome/css/sharp-regular.min.css" />
<script type="module" src="/frontend/wave.ts"></script>
</head>
<body>
<div id="main"></div>
</body>
</html>

View File

@ -2,29 +2,19 @@
"name": "thenextwave",
"private": true,
"version": "0.0.0",
"main": "dist/emain.js",
"browser": "dist/wave.js",
"main": "./dist/main/index.js",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build --minify false --mode development",
"build:prod": "vite build --mode production",
"preview": "vite preview",
"dev": "electron-vite dev",
"start": "electron-vite preview",
"build:dev": "electron-vite build --mode development",
"build:prod": "electron-vite build --mode production",
"storybook": "storybook dev -p 6006 --no-open",
"build-storybook": "storybook build",
"coverage": "vitest run --coverage",
"test": "vitest"
},
"devDependencies": {
"@babel/core": "^7.24.7",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-proposal-decorators": "^7.24.7",
"@babel/plugin-proposal-private-methods": "^7.18.6",
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@babel/plugin-transform-react-jsx": "^7.24.7",
"@babel/plugin-transform-runtime": "^7.24.7",
"@babel/preset-env": "^7.24.7",
"@babel/preset-react": "^7.24.7",
"@babel/preset-typescript": "^7.24.7",
"@chromatic-com/storybook": "^1.5.0",
"@eslint/js": "^9.2.0",
"@storybook/addon-essentials": "^8.1.4",
@ -35,41 +25,28 @@
"@storybook/react": "^8.1.4",
"@storybook/react-vite": "^8.1.4",
"@storybook/test": "^8.1.4",
"@types/babel__core": "^7",
"@types/babel__plugin-transform-runtime": "^7",
"@types/babel__preset-env": "^7",
"@types/node": "^20.12.12",
"@types/react": "^18.3.2",
"@types/throttle-debounce": "^5",
"@types/uuid": "^9.0.8",
"@vitejs/plugin-react": "^4.3.0",
"@vitest/coverage-istanbul": "^1.6.0",
"babel-loader": "^9.1.3",
"babel-plugin-jsx-control-statements": "^4.1.2",
"copy-webpack-plugin": "^12.0.2",
"css-loader": "^7.1.2",
"electron-vite": "^2.2.0",
"eslint": "^9.2.0",
"eslint-config-prettier": "^9.1.0",
"less": "^4.2.0",
"less-loader": "^12.2.0",
"mini-css-extract-plugin": "^2.9.0",
"prettier": "^3.2.5",
"prettier-plugin-jsdoc": "^1.3.0",
"prettier-plugin-organize-imports": "^3.2.4",
"storybook": "^8.1.4",
"style-loader": "^4.0.0",
"ts-node": "^10.9.2",
"tsconfig-paths-webpack-plugin": "^4.1.0",
"tslib": "^2.6.2",
"typescript": "^5.4.5",
"typescript-eslint": "^7.8.0",
"vite": "^5.0.0",
"vite-plugin-static-copy": "^1.0.5",
"vite-tsconfig-paths": "^4.3.2",
"vitest": "^1.6.0",
"webpack": "^5.91.0",
"webpack-cli": "^5.1.4"
"vitest": "^1.6.0"
},
"dependencies": {
"@monaco-editor/loader": "^1.4.0",

View File

@ -15,6 +15,7 @@ import (
"time"
"github.com/google/uuid"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
"github.com/wavetermdev/thenextwave/pkg/filestore"
"github.com/wavetermdev/thenextwave/pkg/service"
@ -199,15 +200,18 @@ func RunWebServer() {
gr.HandleFunc("/wave/file", WebFnWrap(WebFnOpts{AllowCaching: false}, handleWaveFile))
gr.HandleFunc("/wave/service", WebFnWrap(WebFnOpts{JsonErrors: true}, handleService))
serverAddr := MainServerAddr
var allowedOrigins handlers.CORSOption
if wavebase.IsDevMode() {
log.Println("isDevMode")
serverAddr = MainServerDevAddr
allowedOrigins = handlers.AllowedOrigins([]string{"*"})
}
server := &http.Server{
Addr: serverAddr,
ReadTimeout: HttpReadTimeout,
WriteTimeout: HttpWriteTimeout,
MaxHeaderBytes: HttpMaxHeaderBytes,
Handler: http.TimeoutHandler(gr, HttpTimeoutDuration, "Timeout"),
Handler: handlers.CORS(allowedOrigins)(http.TimeoutHandler(gr, HttpTimeoutDuration, "Timeout")),
}
log.Printf("Running main server on %s\n", serverAddr)
err := server.ListenAndServe()

View File

@ -1,19 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<base href="../" />
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>NextWave</title>
<link rel="stylesheet" href="public/fontawesome/css/fontawesome.min.css" />
<link rel="stylesheet" href="public/fontawesome/css/brands.min.css" />
<link rel="stylesheet" href="public/fontawesome/css/solid.min.css" />
<link rel="stylesheet" href="public/fontawesome/css/sharp-solid.min.css" />
<link rel="stylesheet" href="public/fontawesome/css/sharp-regular.min.css" />
<script type="module" src="./dist-dev/wave.js"></script>
<link rel="stylesheet" href="./dist-dev/wave.css" />
</head>
<body>
<div id="main"></div>
</body>
</html>

View File

@ -1,24 +0,0 @@
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";
import { viteStaticCopy } from "vite-plugin-static-copy";
import tsconfigPaths from "vite-tsconfig-paths";
export default defineConfig({
plugins: [
react({}),
tsconfigPaths(),
viteStaticCopy({
targets: [{ src: "node_modules/monaco-editor/min/vs/*", dest: "monaco" }],
}),
],
publicDir: "public",
build: {
target: "es6",
sourcemap: true,
rollupOptions: {
input: {
app: "public/index.html",
},
},
},
});

View File

@ -1,8 +1,8 @@
import { defineConfig, mergeConfig } from "vitest/config";
import viteConfig from "./vite.config";
import { UserConfig, defineConfig, mergeConfig } from "vitest/config";
import electronViteConfig from "./electron.vite.config";
export default mergeConfig(
viteConfig,
electronViteConfig.renderer as UserConfig,
defineConfig({
test: {
reporters: ["verbose", "junit"],

View File

@ -1,30 +0,0 @@
const { webDev, webProd } = require("./webpack/webpack.web.js");
const { electronDev, electronProd } = require("./webpack/webpack.electron.js");
module.exports = (env) => {
if (env.prod) {
console.log("using PROD (web+electron) webpack environment");
return [webProd, electronProd];
}
if (env["prod:web"]) {
console.log("using PROD (web) webpack environment");
return webProd;
}
if (env["prod:electron"]) {
console.log("using PROD (electron) webpack environment");
return electronProd;
}
if (env.dev) {
console.log("using DEV (web+electron) webpack environment");
return [webDev, electronDev];
}
if (env["dev:web"]) {
console.log("using DEV (web) webpack environment");
return webDev;
}
if (env["dev:electron"]) {
console.log("using DEV (electron) webpack environment");
return electronDev;
}
console.log("must specify a webpack environment using --env [dev|prod]");
};

View File

@ -1,114 +0,0 @@
const webpack = require("webpack");
const webpackMerge = require("webpack-merge");
const path = require("path");
const moment = require("dayjs");
const VERSION = require("../version.js");
const CopyPlugin = require("copy-webpack-plugin");
function makeBuildStr() {
let buildStr = moment().format("YYYYMMDD-HHmmss");
// console.log("waveterm:electron " + VERSION + " build " + buildStr);
return buildStr;
}
const BUILD = makeBuildStr();
var electronCommon = {
entry: {
emain: ["./emain/emain.ts"],
},
target: "electron-main",
externals: {
fs: "require('fs')",
"fs-ext": "require('fs-ext')",
},
devtool: "source-map",
module: {
rules: [
{
test: /\.tsx?$/,
// exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: [
[
"@babel/preset-env",
{
targets:
"defaults and not ie > 0 and not op_mini all and not op_mob > 0 and not kaios > 0 and not and_qq > 0 and not and_uc > 0 and not baidu > 0",
},
],
"@babel/preset-react",
"@babel/preset-typescript",
],
plugins: [
["@babel/transform-runtime", { regenerator: true }],
"@babel/plugin-transform-react-jsx",
["@babel/plugin-proposal-decorators", { legacy: true }],
["@babel/plugin-proposal-class-properties", { loose: true }],
["@babel/plugin-proposal-private-methods", { loose: true }],
["@babel/plugin-proposal-private-property-in-object", { loose: true }],
"babel-plugin-jsx-control-statements",
],
},
},
},
],
},
resolve: {
extensions: [".ts", ".tsx", ".js"],
alias: {
"@/app": path.resolve(__dirname, "../src/app/"),
"@/util": path.resolve(__dirname, "../src/util/"),
"@/models": path.resolve(__dirname, "../src/models/"),
"@/common": path.resolve(__dirname, "../src/app/common/"),
"@/elements": path.resolve(__dirname, "../src/app/common/elements/"),
"@/modals": path.resolve(__dirname, "../src/app/common/modals/"),
"@/assets": path.resolve(__dirname, "../src/app/assets/"),
"@/plugins": path.resolve(__dirname, "../src/plugins/"),
"@/autocomplete": path.resolve(__dirname, "../src/autocomplete/"),
},
},
};
var electronDev = webpackMerge.merge(electronCommon, {
mode: "development",
output: {
path: path.resolve(__dirname, "../dist-dev"),
filename: "[name].js",
},
plugins: [
new CopyPlugin({
patterns: [{ from: "emain/preload.js", to: "preload.js" }],
}),
new webpack.DefinePlugin({
__WAVETERM_DEV__: "true",
__WAVETERM_VERSION__: JSON.stringify(VERSION),
__WAVETERM_BUILD__: JSON.stringify("devbuild"),
}),
],
});
var electronProd = webpackMerge.merge(electronCommon, {
mode: "production",
output: {
path: path.resolve(__dirname, "../dist"),
filename: "[name].js",
},
plugins: [
new CopyPlugin({
patterns: [{ from: "src/electron/preload.js", to: "preload.js" }],
}),
new webpack.DefinePlugin({
__WAVETERM_DEV__: "false",
__WAVETERM_VERSION__: JSON.stringify(VERSION),
__WAVETERM_BUILD__: JSON.stringify(BUILD),
}),
],
optimization: {
minimize: true,
},
});
module.exports = { electronDev: electronDev, electronProd: electronProd };

View File

@ -1,154 +0,0 @@
const webpack = require("webpack");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CopyPlugin = require("copy-webpack-plugin");
const webpackMerge = require("webpack-merge");
const path = require("path");
const moment = require("dayjs");
const VERSION = require("../version.js");
const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
function makeBuildStr() {
let buildStr = moment().format("YYYYMMDD-HHmmss");
// console.log("waveterm:web " + VERSION + " build " + buildStr);
return buildStr;
}
const BUILD = makeBuildStr();
let BundleAnalyzerPlugin = null;
if (process.env.WEBPACK_ANALYZE) {
BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
}
var webCommon = {
entry: {
wave: ["./frontend/wave.ts"],
},
module: {
rules: [
{
test: /\.tsx?$/,
// exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: [
[
"@babel/preset-env",
{
targets:
"defaults and not ie > 0 and not op_mini all and not op_mob > 0 and not kaios > 0 and not and_qq > 0 and not and_uc > 0 and not baidu > 0",
},
],
"@babel/preset-react",
"@babel/preset-typescript",
],
plugins: [
["@babel/transform-runtime", { regenerator: true }],
"@babel/plugin-transform-react-jsx",
["@babel/plugin-proposal-decorators", { legacy: true }],
["@babel/plugin-proposal-class-properties", { loose: true }],
["@babel/plugin-proposal-private-methods", { loose: true }],
["@babel/plugin-proposal-private-property-in-object", { loose: true }],
"babel-plugin-jsx-control-statements",
],
},
},
},
{
test: /\.css$/,
use: ["style-loader", "css-loader"],
},
{
test: /\.less$/,
use: [{ loader: MiniCssExtractPlugin.loader }, "css-loader", "less-loader"],
},
{
test: /\.svg$/,
use: [{ loader: "@svgr/webpack", options: { icon: true, svgo: false } }, "file-loader"],
},
{
test: /\.md$/,
type: "asset/source",
},
{
test: /\.(png|jpe?g|gif)$/i,
type: "asset/resource",
},
],
},
resolve: {
extensions: [".ts", ".tsx", ".js", ".mjs", ".cjs", ".wasm", ".json", ".less", ".css"],
plugins: [
new TsconfigPathsPlugin({
configFile: path.resolve(__dirname, "../tsconfig.json"),
}),
],
},
};
var webDev = webpackMerge.merge(webCommon, {
mode: "development",
output: {
path: path.resolve(__dirname, "../dist-dev"),
filename: "[name].js",
},
devtool: "source-map",
devServer: {
static: {
directory: path.join(__dirname, "../public"),
},
port: 9000,
headers: {
"Cache-Control": "no-store",
},
},
plugins: [
new MiniCssExtractPlugin({ filename: "[name].css", ignoreOrder: true }),
new CopyPlugin({
patterns: [
{
from: "min/vs",
to: "monaco",
context: "node_modules/monaco-editor/",
},
],
}),
new webpack.ProvidePlugin({
React: 'react'
}),
new webpack.DefinePlugin({
__WAVETERM_DEV__: "true",
__WAVETERM_VERSION__: JSON.stringify(VERSION),
__WAVETERM_BUILD__: JSON.stringify("devbuild"),
}),
],
watchOptions: {
aggregateTimeout: 200,
},
});
var webProd = webpackMerge.merge(webCommon, {
mode: "production",
output: {
path: path.resolve(__dirname, "../dist"),
filename: "[name].js",
},
devtool: "source-map",
plugins: [
new MiniCssExtractPlugin({ filename: "[name].css", ignoreOrder: true }),
new webpack.DefinePlugin({
__WAVETERM_DEV__: "false",
__WAVETERM_VERSION__: JSON.stringify(VERSION),
__WAVETERM_BUILD__: JSON.stringify(BUILD),
}),
],
optimization: {
minimize: true,
},
});
if (BundleAnalyzerPlugin != null) {
webProd.plugins.push(new BundleAnalyzerPlugin());
}
module.exports = { webDev: webDev, webProd: webProd };

2050
yarn.lock

File diff suppressed because it is too large Load Diff