initial Bun prototype
This commit is contained in:
parent
7db2b28752
commit
c66066af57
38
README.md
38
README.md
@ -1,19 +1,19 @@
|
||||
# Gluon
|
||||
# Gluon (experimental Bun edition)
|
||||
[](https://choosealicense.com/licenses/mit/l) [](https://github.com/sponsors/CanadaHonk) [](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
|
||||
<!-- - **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 -->
|
||||
|
||||

|
||||

|
||||
|
||||
<br>
|
||||
|
||||
@ -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
|
||||
|
||||
<br>
|
||||
|
||||
## 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
|
||||
<br>
|
||||
|
||||
## 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`
|
||||
|
||||
<details>
|
||||
<summary>Shell example</summary>
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
</details>
|
||||
@ -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.
|
||||
|
136
gluworld/index.html
Normal file
136
gluworld/index.html
Normal 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
26
gluworld/index.js
Normal 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);
|
||||
})();
|
@ -17,6 +17,5 @@
|
||||
"homepage": "https://github.com/gluon-framework/gluon#readme",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"ws": "^8.11.0"
|
||||
}
|
||||
}
|
@ -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 });
|
||||
};
|
@ -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';
|
||||
|
||||
|
22
src/index.js
22
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)
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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 => {
|
||||
|
@ -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
|
||||
}
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user