diff --git a/README.md b/README.md index d60f953..287c2aa 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,17 @@ -# Gluon +# Gluon (experimental Deno 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 Deno, 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 Deno 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 Deno - **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 Deno backend - **Fast build times** - Gluon has build times under 1 second on most machines for small projects - **Actively developed** and **listening to feedback** - already on 5th major revision in ~1 week of development, 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) @@ -23,12 +23,12 @@ Gluon is currently **barely 1 week old**, so is still in an **early and experime ### Specific feature statuses - Using Chromium based browsers: Stable - Using Firefox based browsers: Experimental -- Web-Node IPC: Stable +- Web-Deno IPC: Stable
## Ecosystem -- [Gluon](https://github.com/gluon-framework/gluon): the Gluon framework (NodeJS) +- [Gluon](https://github.com/gluon-framework/gluon): the Gluon framework (Deno) - [Glugun](https://github.com/gluon-framework/glugun): builds Gluon apps into releasable builds with optional bundling (soon) ### Apps @@ -39,27 +39,23 @@ Gluon is currently **barely 1 week old**, so is still in an **early and experime
## 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 `deno` branch of this repo](https://github.com/gluon-framework/gluon/tree/deno) (`git clone --branch deno https://github.com/gluon-framework/gluon.git`) +2. `deno run -A 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 deno https://github.com/gluon-framework/gluon.git +$ cd gluon +$ deno run -A gluworld/index.js ```

-## IPC API + ## Comparisons ### Internals | 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 *or bundled* Deno | 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 | ~13MB[^gluon] (Deno) | ~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 Deno is expected as a system installed component, it is "just" bundled and minified Deno 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..bf10204 --- /dev/null +++ b/gluworld/index.html @@ -0,0 +1,136 @@ + + + + + + Gluworld + + +

+ Gluon
+ built with
+ running on +

+ +
+

+
+
+

+

+ +

+ Build Size
+ +

+ +

+ Deno
+
+

V8

+

+
+ + + + + + \ No newline at end of file diff --git a/gluworld/index.js b/gluworld/index.js new file mode 100644 index 0000000..9ee8652 --- /dev/null +++ b/gluworld/index.js @@ -0,0 +1,27 @@ +import * as Gluon from '../src/index.js'; + +import { fileURLToPath, pathToFileURL } from 'https://deno.land/std@0.170.0/node/url.ts'; +import { join, dirname } from 'https://deno.land/std@0.170.0/node/path.ts'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +(async () => { + if (Deno.args.length > 0) { // use argv as browsers to use + for (const forceBrowser of Deno.args) { + 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/gluworld/package.json b/gluworld/package.json new file mode 100644 index 0000000..3cdb8e5 --- /dev/null +++ b/gluworld/package.json @@ -0,0 +1 @@ +{"type":"module","dependencies":{"@gluon-framework/gluon":"^0.7.0"}} \ No newline at end of file diff --git a/src/browser/chromium.js b/src/browser/chromium.js index ca51816..bd6c184 100644 --- a/src/browser/chromium.js +++ b/src/browser/chromium.js @@ -10,9 +10,8 @@ const presets = { // Presets from OpenAsar export default async ({ browserName, browserPath, dataPath }, { url, windowSize }) => { return await StartBrowser(browserPath, [ `--app=${url}`, - `--remote-debugging-pipe`, `--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..c244eda 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 'https://deno.land/std@0.170.0/node/fs/promises.ts'; +import { join } from 'https://deno.land/std@0.170.0/node/path.ts'; import StartBrowser from '../launcher/start.js'; diff --git a/src/index.js b/src/index.js index b0ab3dc..e39e546 100644 --- a/src/index.js +++ b/src/index.js @@ -1,11 +1,15 @@ 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); +window.log = (...args) => console.log(`[${rgb(88, 101, 242, 'Gluon')}]`, ...args); -process.versions.gluon = '0.8.0-dev'; +Deno.version = { // have to do this because... Deno + ...Deno.version, + gluon: '0.8.0-deno-dev' +}; -import { join, dirname, delimiter, sep } from 'path'; -import { access, readdir } from 'fs/promises'; -import { fileURLToPath } from 'url'; + +import { join, dirname, delimiter, sep } from 'https://deno.land/std@0.170.0/node/path.ts'; +import { access, readdir } from 'https://deno.land/std@0.170.0/node/fs/promises.ts'; +import { fileURLToPath } from 'https://deno.land/std@0.170.0/node/url.ts'; import Chromium from './browser/chromium.js'; import Firefox from './browser/firefox.js'; @@ -16,13 +20,13 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); 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'), - edge: join(process.env['PROGRAMFILES(x86)'], 'Microsoft', 'Edge', 'Application', 'msedge.exe'), + windows: Deno.build.os === 'windows' && { + chrome: join(Deno.env.get('PROGRAMFILES'), 'Google', 'Chrome', 'Application', 'chrome.exe'), + chrome_canary: join(Deno.env.get('LOCALAPPDATA'), 'Google', 'Chrome SxS', 'Application', 'chrome.exe'), + edge: join(Deno.env.get('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(Deno.env.get('PROGRAMFILES'), 'Mozilla Firefox', 'firefox.exe'), + firefox_nightly: join(Deno.env.get('PROGRAMFILES'), 'Firefox Nightly', 'firefox.exe'), }, linux: { // these should be in path so just use the name of the binary @@ -31,13 +35,13 @@ const browserPaths = ({ chromium: [ 'chromium', 'chromium-browser' ], firefox: 'firefox', } -})[process.platform]; +})[Deno.build.os]; 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(Deno.env.get('PATH') .replaceAll('"', '') .split(delimiter) .filter(Boolean) @@ -65,7 +69,7 @@ const findBrowserPath = async (forceBrowser) => { if (forceBrowser) return [ await getBrowserPath(forceBrowser), forceBrowser ]; for (const x in browserPaths) { - if (process.argv.includes('--' + x) || process.argv.includes('--' + x.split('_')[0])) return [ await getBrowserPath(x), x ]; + if (Deno.args.includes('--' + x) || Deno.args.includes('--' + x.split('_')[0])) return [ await getBrowserPath(x), x ]; } for (const x in browserPaths) { diff --git a/src/launcher/start.js b/src/launcher/start.js index 2143561..0d3e99a 100644 --- a/src/launcher/start.js +++ b/src/launcher/start.js @@ -1,4 +1,4 @@ -import { spawn } from 'child_process'; +import { spawn } from 'https://deno.land/std@0.170.0/node/child_process.ts'; 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; @@ -28,6 +25,7 @@ export default async (browserPath, args, transport, extra) => { break; case 'stdio': + console.log('pain', proc.stdio); const { 3: pipeWrite, 4: pipeRead } = proc.stdio; CDP = await ConnectCDP({ pipe: { pipeWrite, pipeRead } }); break; diff --git a/src/lib/cdp.js b/src/lib/cdp.js index 0f9ace8..b3a0576 100644 --- a/src/lib/cdp.js +++ b/src/lib/cdp.js @@ -1,5 +1,4 @@ -import WebSocket from 'ws'; -import { get } from 'http'; +import { get } from 'https://deno.land/std@0.170.0/node/http.ts'; export default async ({ pipe: { pipeWrite, pipeRead } = {}, port }) => { let messageCallbacks = [], onReply = {}; @@ -46,7 +45,8 @@ export default async ({ pipe: { pipeWrite, pipeRead } = {}, port }) => { const continualTrying = func => new Promise(resolve => { const attempt = async () => { try { - process.stdout.write('.'); + console.log('a'); + // process.stdout.write('.'); resolve(await func()); } catch (e) { // fail, wait 100ms and try again await new Promise(res => setTimeout(res, 200)); @@ -75,12 +75,13 @@ export default async ({ pipe: { pipeWrite, pipeRead } = {}, port }) => { log('got target', target); + // await new Promise(res => setTimeout(res, 2000)); + const ws = new WebSocket(target.webSocketDebuggerUrl); - await new Promise(resolve => ws.on('open', resolve)); + await new Promise(resolve => ws.onopen = resolve); _send = data => ws.send(data); - - ws.on('message', data => onMessage(data)); + ws.onmessage = ({ data }) => onMessage(data); } else { let pending = ''; pipeRead.on('data', buf => { diff --git a/src/lib/idle.js b/src/lib/idle.js index b89dfff..90d286e 100644 --- a/src/lib/idle.js +++ b/src/lib/idle.js @@ -1,6 +1,6 @@ -import { exec } from 'child_process'; +import { exec } from 'https://deno.land/std@0.170.0/node/child_process.ts'; -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 => Deno.build.os !== 'windows' ? 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 => { const parsed = x.trim().split(',').slice(1).reverse(); @@ -11,7 +11,7 @@ const getProcesses = async containing => process.platform !== 'win32' ? Promise. }).filter(x => x[1] && x[1].includes(containing))); })); -const killProcesses = async pids => process.platform !== 'win32' ? Promise.resolve('') : new Promise(resolve => exec(`taskkill /F ${pids.map(x => `/PID ${x}`).join(' ')}`, (e, out) => resolve(out))); +const killProcesses = async pids => Deno.build.os !== 'windows' ? Promise.resolve('') : new Promise(resolve => exec(`taskkill /F ${pids.map(x => `/PID ${x}`).join(' ')}`, (e, out) => resolve(out))); export default async (CDP, { browserType, dataPath }) => { if (browserType !== 'chromium') { // current implementation is for chromium-based only @@ -57,7 +57,7 @@ export default async (CDP, { browserType, dataPath }) => { const { windowId } = await CDP.send('Browser.getWindowForTarget'); - let autoEnabled = process.argv.includes('--force-auto-idle'), autoOptions = { + let autoEnabled = Deno.args.includes('--force-auto-idle'), autoOptions = { timeMinimizedToHibernate: 5 }; diff --git a/src/lib/ipc.js b/src/lib/ipc.js index 94ee857..6139111 100644 --- a/src/lib/ipc.js +++ b/src/lib/ipc.js @@ -4,20 +4,20 @@ if (window.Gluon) return; let onIPCReply = {}, ipcListeners = {}; window.Gluon = { versions: { - gluon: '${process.versions.gluon}', + gluon: '${Deno.version.gluon}', builder: '${'GLUGUN_VERSION' === 'G\LUGUN_VERSION' ? 'nothing' : 'Glugun GLUGUN_VERSION'}', - node: '${process.versions.node}', - browser: '${browserInfo.product.split('/')[1]}', + deno: '${Deno.version.deno}', + browser: '${browserInfo?.product.split('/')[1]}', browserType: '${browserName.startsWith('Firefox') ? 'firefox' : 'chromium'}', product: '${browserName}', js: { - node: '${process.versions.v8}', - browser: '${browserInfo.jsVersion}' + deno: '${Deno.version.v8}', + browser: '${browserInfo?.jsVersion}' }, embedded: { - node: ${'EMBEDDED_NODE' === 'true' ? 'true' : 'false'}, + deno: ${'EMBEDDED_DENO' === 'true' ? 'true' : 'false'}, browser: false } },