initial Bun prototype

This commit is contained in:
CanadaHonk 2023-01-01 13:24:47 +00:00
parent 7db2b28752
commit c66066af57
11 changed files with 202 additions and 64 deletions

View File

@ -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) [![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 ## Features
- **Uses normal system installed browsers** - allows user choice by **supporting most Chromium *and Firefox*** based browsers, no webviews - **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 - **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 - **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!) - **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 - **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
<!-- - **No forks needed** - Gluon doesn't need forks of Node or Chromium/etc to use them, it just uses normal versions --> <!-- - **No forks needed** - Gluon doesn't need forks of Bun or Chromium/etc to use them, it just uses normal versions -->
![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)
<br> <br>
@ -23,12 +23,12 @@ Gluon is currently **barely a month old**, so is still in an **early and experim
### Specific feature statuses ### Specific feature statuses
- Using Chromium based browsers: Stable - Using Chromium based browsers: Stable
- Using Firefox based browsers: Experimental - Using Firefox based browsers: Experimental
- Web-Node IPC: Stable - Web-Bun IPC: Stable
<br> <br>
## Ecosystem ## 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) - [Glugun](https://github.com/gluon-framework/glugun): builds Gluon apps into releasable builds with optional bundling (soon)
### Apps ### Apps
@ -39,20 +39,16 @@ Gluon is currently **barely a month old**, so is still in an **early and experim
<br> <br>
## Trying Gluon ## Trying Gluon
1. Clone [the Gluon examples repo](https://github.com/gluon-framework/examples) 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. Inside of `gluworld`, run `npm install` 2. `bun run gluworld/index.js`
3. Now do `node .` to run it!
<details> <details>
<summary>Shell example</summary> <summary>Shell example</summary>
```sh ```sh
$ git clone https://github.com/gluon-framework/examples.git $ git clone --branch bun https://github.com/gluon-framework/gluon.git
$ cd examples $ cd gluon
$ cd gluworld $ bun run gluworld/index.js
$ npm install
...
$ node .
``` ```
</details> </details>
@ -68,7 +64,7 @@ console.log(reply); // { give: 'back', different: 'stuff' }
``` ```
```js ```js
// In your Node backend // In your Bun backend
import * as Gluon from '@gluon-framework/gluon'; import * as Gluon from '@gluon-framework/gluon';
const Window = await Gluon.open(...); const Window = await Gluon.open(...);
@ -84,7 +80,7 @@ Window.ipc.on('my type', data => { // { more: 'data' }
| Part | Gluon | Electron | Tauri | Neutralinojs | | Part | Gluon | Electron | Tauri | Neutralinojs |
| ---- | ----- | -------- | ------------ | ----- | | ---- | ----- | -------- | ------------ | ----- |
| Frontend | System installed Chromium *or Firefox* | Self-contained Chromium | System installed webview | System installed webview | | 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 | | IPC | Window object | Preload | Window object | Window object |
| Status | Early in development | Production ready | Usable | Usable | | Status | Early in development | Production ready | Usable | Usable |
| Ecosystem | Integrated | Distributed | Integrated | Integrated | | 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] | | Build Size | <1MB[^system][^gluon][^1] | ~220MB | ~1.8MB[^system] | ~2.6MB[^system] |
| Memory Usage | ~80MB[^gluon] | ~100MB | ~90MB | ~90MB | | 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] | | 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.* *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. [^system]: Does not include system installed components.
[^gluon]: Using Chrome as system browser. Early/WIP data, may change in future. [^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). [^2]: Backend like non-Web (not Chromium/WebView2/etc).
[^3]: Includes Node.JS spinup time. [^3]: Includes Node.JS spinup time.
[^4]: Built for win32 zip (not Squirrel) as a fairer comparison. [^4]: Built for win32 zip (not Squirrel) as a fairer comparison.

136
gluworld/index.html Normal file
View File

@ -0,0 +1,136 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<link rel="icon" href="https://raw.githubusercontent.com/OpenAsar/gluon/main/assets/logo.png">
<title>Gluworld</title>
</head>
<body>
<h1 id="main">
Gluon <code id="gluon_version"></code> <br>
<span id="built_with">built with <code id="builder"></code> <br></span>
running on <code id="product"></code>
</h1>
<div id="versions">
<h2>
<span id="engine_name"></span> <br>
<code id="browser_version"></code> <br>
<p><span id="js_engine_name"></span> <code id="browser_v8"></code></p>
</h2>
<h2 id="build">
Build Size <br>
<code id="build_size"></code>
</h2>
<h2>
Bun <br>
<code id="bun_version"></code> <br>
<p>WebKit <code id="bun_webkit"></code></p>
</h2>
</div>
<style>
@import url('https://fonts.googleapis.com/css2?family=Fira+Code:wght@300;400;500;600;700&family=Inter:wght@300;400;500;600;700;800;900');
html, body {
background: #101418;
color: #fff;
font-family: Inter, sans-serif;
margin: 0;
width: 100%;
height: 100%;
}
body {
box-sizing: border-box;
padding: 5vw 5vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-around;
}
h1, h2, code {
text-align: center;
margin: 0;
}
h1 {
font-weight: 900;
font-size: 34px;
}
h1 > code, h2 > p > code {
margin-left: 8px;
}
h2 {
font-weight: 800;
font-size: 28px;
}
h2 > p, h2 > p > code {
font-weight: 600;
font-size: 18px;
color: #bbb;
}
code {
font-size: 100%;
font-family: Fira Code;
color: #ddd;
}
#versions {
display: flex;
justify-content: space-around;
align-items: center;
width: 100%;
}
#build, #built_with {
display: none;
}
</style>
<script> (async () => {
await new Promise(res => {
const check = () => {
if (!window.Gluon) return setTimeout(check, 100);
res();
};
check();
});
browser_version.textContent = Gluon.versions.browser;
bun_version.textContent = `${Gluon.versions.embedded.bun ? 'Embed' : 'System'} ${Gluon.versions.bun}`;
gluon_version.textContent = Gluon.versions.gluon;
builder.textContent = Gluon.versions.builder;
product.textContent = Gluon.versions.product;
bun_webkit.textContent = Gluon.versions.js.bun;
browser_v8.textContent = Gluon.versions.js.browser;
if (Gluon.versions.builder !== 'nothing') {
build.style.display = 'block';
built_with.style.display = 'block';
// main.innerHTML = main.innerHTML.replace(`built with <code id="builder">nothing</code> <br>`, '');
}
engine_name.textContent = Gluon.versions.browserType === 'firefox' ? 'Firefox' : 'Chromium';
js_engine_name.textContent = Gluon.versions.browserType === 'firefox' ? 'SpiderMonkey' : 'V8';
Gluon.ipc.on('build size', size => {
const kb = size / 1024;
const mb = kb / 1024;
build_size.textContent = mb > 0.1 ? `${mb.toFixed(2)}MB` : `${kb.toFixed(0)}KB`;
});
})();
</script>
</body>
</html>

26
gluworld/index.js Normal file
View File

@ -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);
})();

View File

@ -17,6 +17,5 @@
"homepage": "https://github.com/gluon-framework/gluon#readme", "homepage": "https://github.com/gluon-framework/gluon#readme",
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"ws": "^8.11.0"
} }
} }

View File

@ -14,5 +14,5 @@ export default async ({ browserName, browserPath, dataPath }, { url, windowSize
`--user-data-dir=${dataPath}`, `--user-data-dir=${dataPath}`,
windowSize ? `--window-size=${windowSize.join(',')}` : '', 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(' ') ...`--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 });
}; };

View File

@ -1,5 +1,5 @@
import { mkdir, writeFile } from 'fs/promises'; import { mkdir, writeFile } from 'node:fs/promises';
import { join } from 'path'; import { join } from 'node:path';
import StartBrowser from '../launcher/start.js'; import StartBrowser from '../launcher/start.js';

View File

@ -1,28 +1,26 @@
const rgb = (r, g, b, msg) => `\x1b[38;2;${r};${g};${b}m${msg}\x1b[0m`; 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); 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 { join, delimiter, sep } from 'node:path';
import { access, readdir } from 'fs/promises'; import { access, readdir } from 'node:fs/promises';
import { fileURLToPath } from 'url';
import Chromium from './browser/chromium.js'; import Chromium from './browser/chromium.js';
import Firefox from './browser/firefox.js'; import Firefox from './browser/firefox.js';
import IdleAPI from './lib/idle.js'; import IdleAPI from './lib/idle.js';
const __filename = fileURLToPath(import.meta.url); const __dirname = import.meta.dir;
const __dirname = dirname(__filename);
const browserPaths = ({ const browserPaths = ({
win32: process.platform === 'win32' && { windows: process.platform === 'win32' && {
chrome: join(process.env.PROGRAMFILES, 'Google', 'Chrome', 'Application', 'chrome.exe'), chrome: join(process.env['PROGRAMFILES'], 'Google', 'Chrome', 'Application', 'chrome.exe'),
chrome_canary: join(process.env.LOCALAPPDATA, 'Google', 'Chrome SxS', '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'), edge: join(process.env['PROGRAMFILES(x86)'], 'Microsoft', 'Edge', 'Application', 'msedge.exe'),
firefox: join(process.env.PROGRAMFILES, 'Mozilla Firefox', 'firefox.exe'), firefox: join(process.env['PROGRAMFILES'], 'Mozilla Firefox', 'firefox.exe'),
firefox_nightly: join(process.env.PROGRAMFILES, 'Firefox Nightly', '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 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 () => { const getBinariesInPath = async () => {
if (_binariesInPath) return _binariesInPath; if (_binariesInPath) return _binariesInPath;
return _binariesInPath = (await Promise.all(process.env.PATH return _binariesInPath = (await Promise.all(process.env['PATH']
.replaceAll('"', '') .replaceAll('"', '')
.split(delimiter) .split(delimiter)
.filter(Boolean) .filter(Boolean)

View File

@ -1,4 +1,4 @@
import { spawn } from 'child_process'; import { spawn } from 'node:child_process';
import ConnectCDP from '../lib/cdp.js'; import ConnectCDP from '../lib/cdp.js';
import InjectInto from './inject.js'; import InjectInto from './inject.js';
@ -16,9 +16,6 @@ export default async (browserPath, args, transport, extra) => {
stdio: ['ignore', 'pipe', 'pipe', 'pipe', 'pipe'] 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})`}...`); log(`connecting to CDP over ${transport === 'stdio' ? 'stdio pipe' : `websocket (${port})`}...`);
let CDP; let CDP;

View File

@ -1,6 +1,3 @@
import WebSocket from 'ws';
import { get } from 'http';
export default async ({ pipe: { pipeWrite, pipeRead } = {}, port }) => { export default async ({ pipe: { pipeWrite, pipeRead } = {}, port }) => {
let messageCallbacks = [], onReply = {}; let messageCallbacks = [], onReply = {};
const onMessage = msg => { const onMessage = msg => {
@ -62,17 +59,7 @@ export default async ({ pipe: { pipeWrite, pipeRead } = {}, port }) => {
attempt(); attempt();
}); });
const targets = await continualTrying(() => new Promise((resolve, reject) => get(`http://127.0.0.1:${port}/json/list`, res => { const targets = await continualTrying(async () => await (await fetch(`http://127.0.0.1:${port}/json/list`)).json());
let body = '';
res.on('data', chunk => body += chunk.toString());
res.on('end', () => {
try {
resolve(JSON.parse(body))
} catch {
reject();
}
});
}).on('error', reject)));
console.log(); console.log();
@ -81,11 +68,10 @@ export default async ({ pipe: { pipeWrite, pipeRead } = {}, port }) => {
log('got target', target); log('got target', target);
const ws = new WebSocket(target.webSocketDebuggerUrl); const ws = new WebSocket(target.webSocketDebuggerUrl);
await new Promise(resolve => ws.on('open', resolve)); await new Promise(resolve => ws.onopen = resolve);
ws.on('message', data => onMessage(data));
_send = data => ws.send(data); _send = data => ws.send(data);
ws.onmessage = ({ data }) => onMessage(data);
_close = () => ws.close(); _close = () => ws.close();
} else { } else {

View File

@ -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) => { 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 => { resolve(out.toString().split('\r\n').slice(2).map(x => {

View File

@ -6,18 +6,18 @@ window.Gluon = {
versions: { versions: {
gluon: '${process.versions.gluon}', gluon: '${process.versions.gluon}',
builder: '${'GLUGUN_VERSION' === 'G\LUGUN_VERSION' ? 'nothing' : 'Glugun GLUGUN_VERSION'}', builder: '${'GLUGUN_VERSION' === 'G\LUGUN_VERSION' ? 'nothing' : 'Glugun GLUGUN_VERSION'}',
node: '${process.versions.node}', bun: '${Bun.version}',
browser: '${browserInfo.product.split('/')[1]}', browser: '${browserInfo?.product.split('/')[1]}',
browserType: '${browserName.startsWith('Firefox') ? 'firefox' : 'chromium'}', browserType: '${browserName.startsWith('Firefox') ? 'firefox' : 'chromium'}',
product: '${browserName}', product: '${browserName}',
js: { js: {
node: '${process.versions.v8}', bun: '${process.versions.webkit.slice(0, 7)}',
browser: '${browserInfo.jsVersion}' browser: '${browserInfo?.jsVersion}'
}, },
embedded: { embedded: {
node: ${'EMBEDDED_NODE' === 'true' ? 'true' : 'false'}, bun: ${'EMBEDDED_BUN' === 'true' ? 'true' : 'false'},
browser: false browser: false
} }
}, },