├── .gitignore ├── LICENSE ├── README.md ├── bin.js ├── electron.js ├── index.html └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | sandbox.js 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019, Hyperdivision ApS 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nanotron 2 | 3 | Small opinionated dev program for developing Electron apps 4 | 5 | ``` 6 | npm install -g nanotron 7 | ``` 8 | 9 | ## Usage 10 | 11 | ``` 12 | # Browserifies ./index.js and wraps it in a Electron shell 13 | nanotron 14 | ``` 15 | 16 | * Uses the locally installed `electron`, otherwise the one bundled with this module. 17 | * If `electron.js` exists, this file will be required as part of the electron process 18 | * If `index.html` exists this file will be used as the html wrapper. 19 | 20 | ## License 21 | 22 | [ISC](LICENSE) 23 | -------------------------------------------------------------------------------- /bin.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | process.title = 'nanotron' 4 | 5 | const { spawn } = require('child_process') 6 | const browserify = require('browserify') 7 | const envify = require('envify/custom') 8 | const watchify = require('watchify') 9 | const minimist = require('minimist') 10 | const path = require('path') 11 | const os = require('os') 12 | const fs = require('fs') 13 | const pump = require('pump') 14 | const crypto = require('crypto') 15 | 16 | const argv = minimist(process.argv.slice(2), { 17 | alias: { 18 | e: 'exclude', 19 | h: 'help', 20 | w: 'warnings' 21 | } 22 | }) 23 | 24 | if (argv.help) { 25 | console.error('nanotron [options]\n -e, --exclude [module-name]') 26 | process.exit(0) 27 | } 28 | 29 | if (argv.warnings !== true) { 30 | process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true' 31 | } 32 | 33 | const electron = localRequire('electron') || require('electron') 34 | const file = resolveSync(argv._[0] || 'index.js') 35 | const fileDir = path.dirname(file) 36 | const id = crypto.createHash('sha256').update(file).digest('hex') 37 | 38 | if (!process.argv[2] && !fs.existsSync(file)) fs.writeFileSync(file, '') 39 | const fileBundle = path.join(os.tmpdir(), path.basename(file, '.js') + '.' + id + '.bundle.js') 40 | process.env.BUNDLE_PATH = fileBundle 41 | 42 | const opts = { 43 | node: true, 44 | fullPaths: true, 45 | basedir: fileDir, 46 | cache: {}, 47 | packageCache: {}, 48 | plugin: [watchify], 49 | debug: true 50 | } 51 | 52 | const b = browserify(file, opts) 53 | 54 | b.exclude('electron') 55 | for (const e of [].concat(argv.exclude || [])) b.exclude(e) 56 | 57 | b.transform(envify({ NODE_ENV: process.env.NODE_ENV })) 58 | 59 | b.on('error', (err) => { writeError(err) }) 60 | 61 | bundle(() => { 62 | const proc = spawn(electron, [path.join(__dirname, 'electron.js')], { stdio: 'inherit' }) 63 | proc.on('close', (code) => process.exit(code)) 64 | 65 | b.on('error', () => { 66 | proc.kill('SIGHUP') 67 | }) 68 | b.on('update', () => bundle(() => { 69 | proc.kill('SIGHUP') 70 | })) 71 | }) 72 | 73 | function bundle (cb) { 74 | const ws = fs.createWriteStream(fileBundle) 75 | ws.write('__dirname = ' + JSON.stringify(fileDir) + ';__filename =' + JSON.stringify(file) + ';') 76 | pump(b.bundle(), ws, function (err) { 77 | if (err) writeError(err) 78 | if (cb) cb() 79 | }) 80 | } 81 | 82 | function writeError (err) { 83 | const stack = err.stack.toString().replace(new RegExp(path.resolve(), 'g'), '.') 84 | console.error(stack) 85 | fs.writeFileSync(fileBundle, `document.body.innerHTML = \`
${stack}
\``) 86 | } 87 | 88 | function localRequire (name) { 89 | const localPaths = [path.join(process.cwd(), 'node_modules')] 90 | while (true) { 91 | const top = localPaths[localPaths.length - 1] 92 | const next = path.resolve(top, '../..', 'node_modules') 93 | if (next === top) break 94 | localPaths.push(next) 95 | } 96 | 97 | try { 98 | const p = require.resolve(name, { 99 | paths: localPaths 100 | }) 101 | 102 | return require(p) 103 | } catch (_) { 104 | return null 105 | } 106 | } 107 | 108 | function resolveSync (file) { 109 | file = path.resolve(file) 110 | if (!fs.existsSync(file)) return file 111 | if (fs.statSync(file).isDirectory() && fs.existsSync(path.join(file, 'index.js'))) return path.join(file, 'index.js') 112 | return file 113 | } 114 | -------------------------------------------------------------------------------- /electron.js: -------------------------------------------------------------------------------- 1 | const electron = require('electron') 2 | const fs = require('fs') 3 | const path = require('path') 4 | 5 | const { BrowserWindow, app } = electron 6 | let win 7 | 8 | if (fs.existsSync('package.json')) { 9 | try { 10 | const pkg = require(path.resolve('package.json')) 11 | app.setName(pkg.title || pkg.name || 'Nanotron') 12 | } catch (_) { 13 | app.setName('Nanotron') 14 | } 15 | } else { 16 | app.setName('Nanotron') 17 | } 18 | 19 | app.allowRendererProcessReuse = false 20 | app.on('ready', function () { 21 | win = new BrowserWindow({ 22 | webPreferences: { 23 | nodeIntegration: true, 24 | contextIsolation: false 25 | } 26 | }) 27 | win.loadURL('file://' + require.resolve('./index.html')) 28 | win.webContents.on('did-finish-load', () => win.webContents.openDevTools({ mode: 'detach' })) 29 | win.webContents.on('context-menu', onContextMenu) 30 | 31 | process.on('SIGHUP', () => { 32 | win.loadURL(win.webContents.getURL()) 33 | }) 34 | }) 35 | 36 | if (fs.existsSync('electron.js')) { 37 | require(path.resolve('electron.js')) 38 | } 39 | 40 | function onContextMenu (event, params) { 41 | const { editFlags } = params 42 | const hasText = params.selectionText.trim().length > 0 43 | const can = type => editFlags[`can${type}`] && hasText 44 | 45 | const menuTpl = [{ 46 | type: 'separator' 47 | }, { 48 | id: 'cut', 49 | label: 'Cut', 50 | // Needed because of macOS limitation: 51 | // https://github.com/electron/electron/issues/5860 52 | role: can('Cut') ? 'cut' : '', 53 | enabled: can('Cut'), 54 | visible: params.isEditable 55 | }, { 56 | id: 'copy', 57 | label: 'Copy', 58 | role: can('Copy') ? 'copy' : '', 59 | enabled: can('Copy'), 60 | visible: params.isEditable || hasText 61 | }, { 62 | id: 'paste', 63 | label: 'Paste', 64 | role: editFlags.canPaste ? 'paste' : '', 65 | enabled: editFlags.canPaste, 66 | visible: params.isEditable 67 | }, { 68 | type: 'separator' 69 | }, { 70 | id: 'inspect', 71 | label: 'Inspect Element', 72 | click () { 73 | win.inspectElement(params.x, params.y) 74 | 75 | if (win.webContents.isDevToolsOpened()) { 76 | win.webContents.devToolsWebContents.focus() 77 | } 78 | } 79 | }, { 80 | type: 'separator' 81 | }] 82 | 83 | const menu = electron.Menu.buildFromTemplate(menuTpl) 84 | menu.popup(win) 85 | } 86 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nanotron", 3 | "version": "5.1.0", 4 | "description": "Small opionated dev program for developing Electron apps using the nano* stack (the modules backing Choo.js)", 5 | "main": "bin.js", 6 | "bin": { 7 | "nanotron": "./bin.js" 8 | }, 9 | "dependencies": { 10 | "browserify": "^16.5.2", 11 | "electron": "^11.0.0", 12 | "envify": "^4.1.0", 13 | "minimist": "^1.2.5", 14 | "pump": "^3.0.0", 15 | "watchify": "^3.11.1" 16 | }, 17 | "devDependencies": {}, 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/hyperdivision/nanotron.git" 21 | }, 22 | "author": "Mathias Buus (@mafintosh)", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/hyperdivision/nanotron/issues" 26 | }, 27 | "homepage": "https://github.com/hyperdivision/nanotron" 28 | } 29 | --------------------------------------------------------------------------------