diff --git a/README.md b/README.md index c2f0e7e..a8f2653 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,19 @@ -# Gluon +# Gluon (experimental Bun edition) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://choosealicense.com/licenses/mit/l) [![GitHub Sponsors](https://img.shields.io/github/sponsors/CanadaHonk?label=Sponsors&logo=github)](https://github.com/sponsors/CanadaHonk) [![Discord](https://img.shields.io/discord/1051940602704564244.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/RFtUCA8fST) -**Gluon is a framework for creating "desktop apps" from websites**, using **system installed browsers** *(not webviews)* and NodeJS, differing a lot from other existing active projects - opening up innovation and allowing some major advantages. Instead of other similar frameworks bundling a browser like Chromium or using webviews (like Edge Webview2 on Windows), **Gluon just uses system installed browsers** like Chrome, Edge, Firefox, etc. Gluon supports Chromium ***and Firefox*** based browsers as the frontend, while Gluon's backend uses NodeJS to be versatile and easy to develop (also allowing easy learning from other popular frameworks like Electron by using the same-ish stack). +**Gluon is a framework for creating "desktop apps" from websites**, using **system installed browsers** *(not webviews)* and Bun, differing a lot from other existing active projects - opening up innovation and allowing some major advantages. Instead of other similar frameworks bundling a browser like Chromium or using webviews (like Edge Webview2 on Windows), **Gluon just uses system installed browsers** like Chrome, Edge, Firefox, etc. Gluon supports Chromium ***and Firefox*** based browsers as the frontend, while Gluon's backend uses Bun to be versatile and easy to develop (also allowing easy learning from other popular frameworks like Electron by using the same-ish stack). ## Features - **Uses normal system installed browsers** - allows user choice by **supporting most Chromium *and Firefox*** based browsers, no webviews -- **Tiny bundle sizes** - <1MB using system Node, <10MB when bundling it +- **Tiny bundle sizes** - <1MB using system Bun - **Chromium *and Firefox* supported as browser engine**, unlike any other active framework -- **Minimal and easy to use** - Gluon has a simple yet powerful API to make apps with a Node backend +- **Minimal and easy to use** - Gluon has a simple yet powerful API to make apps with a Bun backend - **Fast build times** - Gluon has build times under 1 second on most machines for small projects - **Actively developed** and **listening to feedback** - new updates are coming around weekly, quickly adding unplanned requested features if liked by the community (like Firefox support!) - **Lower memory usage** - compared to most other frameworks Gluon should have a slightly lower average memory usage by using browser flags to squeeze out more performance - + -![Gluworld Screenshot showing Chrome Canary and Firefox Nightly being used at once.](https://user-images.githubusercontent.com/19228318/207103757-fd5c9428-a927-4986-8c6c-02d9961ad422.png) +![Gluworld Screenshot showing Chromium using Bun.](https://user-images.githubusercontent.com/19228318/210020320-ff62e67f-0eb5-4e9e-989b-3ba4edc0fe35.png)
@@ -23,12 +23,12 @@ Gluon is currently **barely a month old**, so is still in an **early and experim ### Specific feature statuses - Using Chromium based browsers: Stable - Using Firefox based browsers: Experimental -- Web-Node IPC: Stable +- Web-Bun IPC: Stable
## Ecosystem -- [Gluon](https://github.com/gluon-framework/gluon): the Gluon framework (NodeJS) +- [Gluon](https://github.com/gluon-framework/gluon): the Gluon framework - [Glugun](https://github.com/gluon-framework/glugun): builds Gluon apps into releasable builds with optional bundling (soon) ### Apps @@ -39,20 +39,16 @@ Gluon is currently **barely a month old**, so is still in an **early and experim
## Trying Gluon -1. Clone [the Gluon examples repo](https://github.com/gluon-framework/examples) -2. Inside of `gluworld`, run `npm install` -3. Now do `node .` to run it! +1. Clone [the `bun` branch of this repo](https://github.com/gluon-framework/gluon/tree/bun) (`git clone --branch bun https://github.com/gluon-framework/gluon.git`) +2. `bun run gluworld/index.js`
Shell example ```sh -$ git clone https://github.com/gluon-framework/examples.git -$ cd examples -$ cd gluworld -$ npm install -... -$ node . +$ git clone --branch bun https://github.com/gluon-framework/gluon.git +$ cd gluon +$ bun run gluworld/index.js ```
@@ -68,7 +64,7 @@ console.log(reply); // { give: 'back', different: 'stuff' } ``` ```js -// In your Node backend +// In your Bun backend import * as Gluon from '@gluon-framework/gluon'; const Window = await Gluon.open(...); @@ -84,7 +80,7 @@ Window.ipc.on('my type', data => { // { more: 'data' } | Part | Gluon | Electron | Tauri | Neutralinojs | | ---- | ----- | -------- | ------------ | ----- | | Frontend | System installed Chromium *or Firefox* | Self-contained Chromium | System installed webview | System installed webview | -| Backend | System installed *or bundled* Node.JS | Self-contained Node.JS | Native (Rust) | Native (Any) | +| Backend | System installed Bun | Self-contained Node.JS | Native (Rust) | Native (Any) | | IPC | Window object | Preload | Window object | Window object | | Status | Early in development | Production ready | Usable | Usable | | Ecosystem | Integrated | Distributed | Integrated | Integrated | @@ -97,7 +93,7 @@ Basic (plain HTML) Hello World demo, measured on up to date Windows 10, on my ma | ---- | ----- | -------- | ------------ | ----- | | Build Size | <1MB[^system][^gluon][^1] | ~220MB | ~1.8MB[^system] | ~2.6MB[^system] | | Memory Usage | ~80MB[^gluon] | ~100MB | ~90MB | ~90MB | -| Backend[^2] Memory Usage | ~13MB[^gluon] (Node) | ~22MB (Node) | ~3MB (Native) | ~3MB (Native) | +| Backend[^2] Memory Usage | Not measured[^gluon] (Bun) | ~22MB (Node) | ~3MB (Native) | ~3MB (Native) | | Build Time | ~0.7s[^3] | ~20s[^4] | ~120s[^5] | ~2s[^3][^6] | *Extra info: All HTML/CSS/JS is unminified (including Gluon). Built in release configuration. All binaries were left as compiled with common size optimizations enabled for that language, no stripping/packing done.* @@ -105,7 +101,7 @@ Basic (plain HTML) Hello World demo, measured on up to date Windows 10, on my ma [^system]: Does not include system installed components. [^gluon]: Using Chrome as system browser. Early/WIP data, may change in future. -[^1]: *How is Gluon so small?* Since NodeJS is expected as a system installed component, it is "just" bundled and minified Node code. +[^1]: *How is Gluon so small?* Since Bun is expected as a system installed component, it is "just" bundled and minified Bun code. [^2]: Backend like non-Web (not Chromium/WebView2/etc). [^3]: Includes Node.JS spinup time. [^4]: Built for win32 zip (not Squirrel) as a fairer comparison. diff --git a/gluworld/index.html b/gluworld/index.html new file mode 100644 index 0000000..b35a215 --- /dev/null +++ b/gluworld/index.html @@ -0,0 +1,136 @@ + + + + + + Gluworld + + +

+ Gluon
+ built with
+ running on +

+ +
+

+
+
+

+

+ +

+ Build Size
+ +

+ +

+ Bun
+
+

WebKit

+

+
+ + + + + + \ No newline at end of file diff --git a/gluworld/index.js b/gluworld/index.js new file mode 100644 index 0000000..21fa701 --- /dev/null +++ b/gluworld/index.js @@ -0,0 +1,26 @@ +import * as Gluon from '../src/index.js'; + +import { pathToFileURL } from 'node:url'; +import { join } from 'node:path'; + +const __dirname = import.meta.dir; + +(async () => { + if (process.argv.length > 2) { // use argv as browsers to use + for (const forceBrowser of process.argv.slice(2)) { + await Gluon.open(pathToFileURL(join(__dirname, 'index.html')).href, { + windowSize: [ 800, 500 ], + forceBrowser + }); + } + + return; + } + + const Browser = await Gluon.open(pathToFileURL(join(__dirname, 'index.html')).href, { + windowSize: [ 800, 500 ] + }); + + // const buildSize = await dirSize(__dirname); + // Chromium.ipc.send('build size', buildSize); +})(); \ No newline at end of file diff --git a/package.json b/package.json index 0757091..d005e8f 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,5 @@ "homepage": "https://github.com/gluon-framework/gluon#readme", "type": "module", "dependencies": { - "ws": "^8.11.0" } } \ No newline at end of file diff --git a/src/browser/chromium.js b/src/browser/chromium.js index ca51816..4bcfc4f 100644 --- a/src/browser/chromium.js +++ b/src/browser/chromium.js @@ -14,5 +14,5 @@ export default async ({ browserName, browserPath, dataPath }, { url, windowSize `--user-data-dir=${dataPath}`, windowSize ? `--window-size=${windowSize.join(',')}` : '', ...`--new-window --disable-extensions --disable-default-apps --disable-breakpad --disable-crashpad --disable-background-networking --disable-domain-reliability --disable-component-update --disable-sync --disable-features=AutofillServerCommunication ${presets.perf}`.split(' ') - ], 'stdio', { browserName }); + ], 'websocket', { browserName }); }; \ No newline at end of file diff --git a/src/browser/firefox.js b/src/browser/firefox.js index a361930..0443d39 100644 --- a/src/browser/firefox.js +++ b/src/browser/firefox.js @@ -1,5 +1,5 @@ -import { mkdir, writeFile } from 'fs/promises'; -import { join } from 'path'; +import { mkdir, writeFile } from 'node:fs/promises'; +import { join } from 'node:path'; import StartBrowser from '../launcher/start.js'; diff --git a/src/index.js b/src/index.js index 8e34b4a..cd6d152 100644 --- a/src/index.js +++ b/src/index.js @@ -1,28 +1,26 @@ const rgb = (r, g, b, msg) => `\x1b[38;2;${r};${g};${b}m${msg}\x1b[0m`; global.log = (...args) => console.log(`[${rgb(88, 101, 242, 'Gluon')}]`, ...args); -process.versions.gluon = '0.8.0'; +process.versions.gluon = '0.8.0-bun-dev'; -import { join, dirname, delimiter, sep } from 'path'; -import { access, readdir } from 'fs/promises'; -import { fileURLToPath } from 'url'; +import { join, delimiter, sep } from 'node:path'; +import { access, readdir } from 'node:fs/promises'; import Chromium from './browser/chromium.js'; import Firefox from './browser/firefox.js'; import IdleAPI from './lib/idle.js'; -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); +const __dirname = import.meta.dir; const browserPaths = ({ - win32: process.platform === 'win32' && { - chrome: join(process.env.PROGRAMFILES, 'Google', 'Chrome', 'Application', 'chrome.exe'), - chrome_canary: join(process.env.LOCALAPPDATA, 'Google', 'Chrome SxS', 'Application', 'chrome.exe'), + windows: process.platform === 'win32' && { + chrome: join(process.env['PROGRAMFILES'], 'Google', 'Chrome', 'Application', 'chrome.exe'), + chrome_canary: join(process.env['LOCALAPPDATA'], 'Google', 'Chrome SxS', 'Application', 'chrome.exe'), edge: join(process.env['PROGRAMFILES(x86)'], 'Microsoft', 'Edge', 'Application', 'msedge.exe'), - firefox: join(process.env.PROGRAMFILES, 'Mozilla Firefox', 'firefox.exe'), - firefox_nightly: join(process.env.PROGRAMFILES, 'Firefox Nightly', 'firefox.exe'), + firefox: join(process.env['PROGRAMFILES'], 'Mozilla Firefox', 'firefox.exe'), + firefox_nightly: join(process.env['PROGRAMFILES'], 'Firefox Nightly', 'firefox.exe'), }, linux: { // these should be in path so just use the name of the binary @@ -37,7 +35,7 @@ let _binariesInPath; // cache as to avoid excessive reads const getBinariesInPath = async () => { if (_binariesInPath) return _binariesInPath; - return _binariesInPath = (await Promise.all(process.env.PATH + return _binariesInPath = (await Promise.all(process.env['PATH'] .replaceAll('"', '') .split(delimiter) .filter(Boolean) diff --git a/src/launcher/start.js b/src/launcher/start.js index f130630..51f4151 100644 --- a/src/launcher/start.js +++ b/src/launcher/start.js @@ -1,4 +1,4 @@ -import { spawn } from 'child_process'; +import { spawn } from 'node:child_process'; import ConnectCDP from '../lib/cdp.js'; import InjectInto from './inject.js'; @@ -16,9 +16,6 @@ export default async (browserPath, args, transport, extra) => { stdio: ['ignore', 'pipe', 'pipe', 'pipe', 'pipe'] }); - proc.stdout.pipe(proc.stdout); - proc.stderr.pipe(proc.stderr); - log(`connecting to CDP over ${transport === 'stdio' ? 'stdio pipe' : `websocket (${port})`}...`); let CDP; diff --git a/src/lib/cdp.js b/src/lib/cdp.js index a4975fc..ce7d6da 100644 --- a/src/lib/cdp.js +++ b/src/lib/cdp.js @@ -1,6 +1,3 @@ -import WebSocket from 'ws'; -import { get } from 'http'; - export default async ({ pipe: { pipeWrite, pipeRead } = {}, port }) => { let messageCallbacks = [], onReply = {}; const onMessage = msg => { @@ -62,17 +59,7 @@ export default async ({ pipe: { pipeWrite, pipeRead } = {}, port }) => { attempt(); }); - const targets = await continualTrying(() => new Promise((resolve, reject) => get(`http://127.0.0.1:${port}/json/list`, res => { - let body = ''; - res.on('data', chunk => body += chunk.toString()); - res.on('end', () => { - try { - resolve(JSON.parse(body)) - } catch { - reject(); - } - }); - }).on('error', reject))); + const targets = await continualTrying(async () => await (await fetch(`http://127.0.0.1:${port}/json/list`)).json()); console.log(); @@ -81,11 +68,10 @@ export default async ({ pipe: { pipeWrite, pipeRead } = {}, port }) => { log('got target', target); const ws = new WebSocket(target.webSocketDebuggerUrl); - await new Promise(resolve => ws.on('open', resolve)); - - ws.on('message', data => onMessage(data)); + await new Promise(resolve => ws.onopen = resolve); _send = data => ws.send(data); + ws.onmessage = ({ data }) => onMessage(data); _close = () => ws.close(); } else { diff --git a/src/lib/idle.js b/src/lib/idle.js index b89dfff..db05c0f 100644 --- a/src/lib/idle.js +++ b/src/lib/idle.js @@ -1,4 +1,4 @@ -import { exec } from 'child_process'; +import { exec } from 'node:child_process'; const getProcesses = async containing => process.platform !== 'win32' ? Promise.resolve([]) : new Promise(resolve => exec(`wmic process get Commandline,ProcessID /format:csv`, (e, out) => { resolve(out.toString().split('\r\n').slice(2).map(x => { diff --git a/src/lib/ipc.js b/src/lib/ipc.js index 94ee857..a8c3a23 100644 --- a/src/lib/ipc.js +++ b/src/lib/ipc.js @@ -6,18 +6,18 @@ window.Gluon = { versions: { gluon: '${process.versions.gluon}', builder: '${'GLUGUN_VERSION' === 'G\LUGUN_VERSION' ? 'nothing' : 'Glugun GLUGUN_VERSION'}', - node: '${process.versions.node}', - browser: '${browserInfo.product.split('/')[1]}', + bun: '${Bun.version}', + browser: '${browserInfo?.product.split('/')[1]}', browserType: '${browserName.startsWith('Firefox') ? 'firefox' : 'chromium'}', product: '${browserName}', js: { - node: '${process.versions.v8}', - browser: '${browserInfo.jsVersion}' + bun: '${process.versions.webkit.slice(0, 7)}', + browser: '${browserInfo?.jsVersion}' }, embedded: { - node: ${'EMBEDDED_NODE' === 'true' ? 'true' : 'false'}, + bun: ${'EMBEDDED_BUN' === 'true' ? 'true' : 'false'}, browser: false } },