Browse Source

bam!

pull/3/head
mattdesl 6 years ago
commit
8789bc9a5d
  1. 5
      .gitignore
  2. 10
      .npmignore
  3. 21
      LICENSE.md
  4. 52
      README.md
  5. 14
      bin/index.js
  6. 17
      example/build.js
  7. 2
      example/client.js
  8. 43
      lib/file-watch.js
  9. 9
      lib/index.html
  10. 61
      lib/preload.js
  11. 62
      lib/require-hook.js
  12. 42
      package.json
  13. 131
      server.js

5
.gitignore

@ -0,0 +1,5 @@
bower_components
node_modules
*.log
.DS_Store
bundle.js

10
.npmignore

@ -0,0 +1,10 @@
bower_components
node_modules
*.log
.DS_Store
bundle.js
test
test.js
demo/
.npmignore
LICENSE.md

21
LICENSE.md

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Jam3
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
OR OTHER DEALINGS IN THE SOFTWARE.

52
README.md

@ -0,0 +1,52 @@
# devtool
[![experimental](http://badges.github.io/stability-badges/dist/experimental.svg)](http://github.com/badges/stability-badges)
### WORK IN PROGRESS
Documentation is currently being written for this module. Check back soon!
---
Runs Node.js source code through Chromium DevTools (using Electron).
This allows you to profile, debug and develop typical Node.js programs with some of the features of Chrome DevTools.
## Example
For example, we can use this to profile and debug [browserify](https://github.com/substack/node-browserify), a node program that would not typically work within the browser's DevTools.
```js
var browserify = require('browserify');
// Start DevTools profiling...
console.profile('build');
browserify('client.js').bundle(function (err, src) {
if (err) throw err;
// Finish DevTools profiling...
console.profileEnd('build');
});
```
Here are some screenshots after the profile has run, and also during debugging of a hot code path.
## Features
This builds on Electron, providing some additional features:
- Improved error handling (more detailed syntax errors in console)
- Improved source map support for required files
- Makes various Node features behave as expected, like `require.main` and `process.argv`
- Console redirection back to terminal (optional)
- File watching for development and quit-on-error flags for unit testing (e.g. continuous integration)
- Exit error codes
## Usage
[![NPM](https://nodei.co/npm/devtool.png)](https://www.npmjs.com/package/devtool)
## License
MIT, see [LICENSE.md](http://github.com/Jam3/devtool/blob/master/LICENSE.md) for details.

14
bin/index.js

@ -0,0 +1,14 @@
#!/usr/bin/env node
const spawn = require('child_process').spawn;
const electron = require('electron-prebuilt');
const path = require('path');
const serverPath = path.join(__dirname, '../server.js');
var args = [ serverPath ].concat([].concat(process.argv).splice(2));
var proc = spawn(electron, args);
proc.stdout.pipe(process.stdout);
proc.stderr.pipe(process.stderr);
proc.on('close', function (code) {
process.exit(code);
});

17
example/build.js

@ -0,0 +1,17 @@
// An example profiling browserify source
var browserify = require('browserify');
var path = require('path');
function run () {
console.profile('build');
browserify(path.resolve(__dirname, 'client.js'))
.bundle(function (err, src) {
if (err) throw err;
console.profileEnd('build');
console.log('bundle size in bytes:', src.length);
});
}
if (require.main === module) {
run();
}

2
example/client.js

@ -0,0 +1,2 @@
var url = require('url');
console.log(url.parse(window.location.href));

43
lib/file-watch.js

@ -0,0 +1,43 @@
// a thin wrapper around chokidar file watching HTML / CSS
var chokidar = require('chokidar');
var assign = require('object-assign');
var EventEmitter = require('events').EventEmitter;
var ignores = [
'node_modules/**', 'bower_components/**',
'.git', '.hg', '.svn', '.DS_Store',
'*.swp', 'thumbs.db', 'desktop.ini'
];
module.exports = function fileWatch (glob, opt) {
opt = assign({
ignored: ignores,
ignoreInitial: true
}, opt);
if (opt.poll) {
opt.usePolling = true;
}
var emitter = new EventEmitter();
var closed = false;
var ready = false;
var watcher = chokidar.watch(glob, opt);
watcher.on('change', function (file) {
emitter.emit('change', file);
});
// chokidar@1.0.0-r6 only allows close after ready event
watcher.once('ready', function () {
ready = true;
if (closed) watcher.close();
});
emitter.close = function () {
if (closed) return;
if (ready) watcher.close();
closed = true;
};
return emitter;
};

9
lib/index.html

@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>devtool</title>
</head>
<body>
</body>
</html>

61
lib/preload.js

@ -0,0 +1,61 @@
(function () {
var electron = require('electron');
var path = require('path');
var serialize = require('serializerr');
var remote = electron.remote;
var requireHook = require('./require-hook');
var ipc = electron.ipcRenderer;
var _process = remote.process;
var cwd = _process.cwd();
// setup renderer process to look a bit more like node
process.chdir(cwd);
process.argv = _process.argv;
process.exit = _process.exit.bind(_process);
// if we should pipe DevTools console back to terminal
if (remote.getGlobal('__electronConsoleHook')) {
require('console-redirect/process');
}
// in DevTools console (i.e. REPL), these will be
// undefined to mimic Node REPL
delete global.__dirname;
delete global.__filename;
// When there is an uncaught exception in the entry
// script, we may want to quit the devtool (i.e. for CI)
// or just print an error in DevTools console (i.e. for dev)
var shouldQuit = remote.getGlobal('__shouldElectronQuitOnError');
if (shouldQuit) {
window.onerror = function (a, b, c, d, err) {
fatalError(err);
return true;
};
}
// get an absolute path to our entry point
var entry = remote.getGlobal('__electronEntryFile');
entry = path.isAbsolute(entry) ? entry : path.resolve(cwd, entry);
// hook into the internal require for a few features:
// - better error reporting on syntax errors and missing modules
// - require.main acts like node.js CLI
// - add source maps so the files show up in DevTools Sources
requireHook({
entry: entry,
basedir: cwd
}, function (err) {
if (err && shouldQuit) {
fatalError(err);
}
});
// boot up entry application
require(entry);
function fatalError (err) {
ipc.send('error', JSON.stringify(serialize(err)));
}
})();

62
lib/require-hook.js

@ -0,0 +1,62 @@
var path = require('path');
var noop = function () {};
module.exports = function requireHook (opts, cb) {
if (typeof opts === 'function') {
cb = opts;
opts = {};
}
opts = opts || {};
cb = cb || noop;
var remote = require('electron').remote;
var Module = require('module');
var syntaxError = require('syntax-error');
var fs = remote.require('fs');
var stripBOM = require('strip-bom');
var combineSourceMap = require('combine-source-map');
var entry = opts.entry;
// var basedir = opts.basedir || remote.process.cwd();
var hasSetMain = false;
var currentWrapFile = null;
require.extensions['.js'] = function devtoolCompileModule (module, file) {
// set the main module so that Node.js scripts run correctly
if (!hasSetMain && entry && file === entry) {
hasSetMain = true;
process.mainModule = module;
}
var code = fs.readFileSync(file, 'utf8');
try {
currentWrapFile = file;
module._compile(stripBOM(code), file);
cb(null);
} catch (err) {
// improve Electron's error handling (i.e. SyntaxError)
var realErr = syntaxError(code, file) || err;
var msg = 'Error compiling module: ' + file + '\n' + (realErr.annotated || realErr.message);
console.error(msg);
cb(new Error(msg));
}
};
// Include source maps for required modules
var wrap = Module.wrap;
Module.wrap = function (script) {
var wrapScript = wrap.call(wrap, script);
if (!currentWrapFile) return wrapScript;
var baseFileDir = path.dirname(entry);
// TODO: Use path.dirname(entry) or opts.basedir ?
var sourceFile = path.relative(baseFileDir, currentWrapFile)
.replace(/\\/g, '/');
var sourceMap = combineSourceMap.create().addFile(
{ sourceFile: sourceFile, source: script },
{ line: 0 });
return [
combineSourceMap.removeComments(wrapScript),
sourceMap.comment()
].join('\n');
};
};

42
package.json

@ -0,0 +1,42 @@
{
"name": "devtool",
"version": "1.0.0",
"description": "",
"main": "index.js",
"license": "MIT",
"author": {
"name": "Matt DesLauriers",
"email": "dave.des@gmail.com",
"url": "https://github.com/mattdesl"
},
"dependencies": {
"chokidar": "^1.4.2",
"combine-source-map": "^0.7.1",
"console-redirect": "^1.0.0",
"electron-prebuilt": "^0.35.6",
"events": "^1.1.0",
"minimist": "^1.2.0",
"object-assign": "^4.0.1",
"serializerr": "^1.0.2",
"strip-bom": "^2.0.0",
"syntax-error": "^1.1.4"
},
"devDependencies": {
"browserify": "^13.0.0"
},
"scripts": {
"test": "electron server.js"
},
"keywords": [],
"repository": {
"type": "git",
"url": "git://github.com/Jam3/devtool.git"
},
"homepage": "https://github.com/Jam3/devtool",
"bugs": {
"url": "https://github.com/Jam3/devtool/issues"
},
"bin": {
"devtool": "./bin/index.js"
}
}

131
server.js

@ -0,0 +1,131 @@
var path = require('path');
var fs = require('fs');
var createWatch = require('./lib/file-watch');
var electron = require('electron');
var app = electron.app;
var BrowserWindow = electron.BrowserWindow;
var ipc = electron.ipcMain;
var argv = require('minimist')(process.argv.slice(2), {
boolean: [ 'console', 'quit', 'poll', 'show' ],
string: [ 'index' ],
alias: {
watch: 'w',
quit: 'q',
console: 'c',
index: 'i',
poll: 'p',
show: 's'
}
});
app.commandLine.appendSwitch('disable-http-cache');
app.commandLine.appendSwitch('v', 0);
app.commandLine.appendSwitch('vmodule', 'console=0');
global.__shouldElectronQuitOnError = true; // true until app starts
global.__electronEntryFile = argv._[0];
global.__electronConsoleHook = argv.console;
if (!global.__electronEntryFile) {
console.error('No entry file specified! Usage:\n devtool index.js');
process.exit(1);
}
var exitWithCode1 = false;
process.on('uncaughtException', function (err) {
console.error(err);
if (global.__shouldElectronQuitOnError) {
exitWithCode1 = true;
app.quit();
}
});
var cwd = process.cwd();
var htmlFile = path.resolve(__dirname, 'lib', 'index.html');
if (argv.index) {
htmlFile = path.isAbsolute(argv.index) ? argv.index : path.resolve(cwd, argv.index);
}
var htmlData = fs.readFileSync(htmlFile);
var watcher = null;
var mainWindow = null;
app.on('window-all-closed', function () {
app.quit();
});
// Quit the server with the correct exit code
app.on('quit', function () {
if (watcher) watcher.close();
if (exitWithCode1) process.exit(1);
});
app.on('ready', function () {
fs.stat(global.__electronEntryFile, function (err, stat) {
if (err) return fatal(err);
if (!stat.isFile()) return fatal('Given entry is not a file! Usage:\n devtool index.js');
});
var mainIndexURL = 'file://' + __dirname + '/index.html';
electron.protocol.interceptBufferProtocol('file', function (request, callback) {
if (request.url === mainIndexURL) {
callback({
data: htmlData,
mimeType: 'text/html'
});
} else {
callback(request);
}
}, function (err) {
if (err) fatal(err);
});
mainWindow = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, 'lib', 'preload.js'),
nodeIntegration: true
},
show: argv.show
});
if (argv.watch) {
var globs = [].concat(argv.watch).filter(function (f) {
return typeof f === 'string';
});
if (globs.length === 0) globs = [ '**/*.{js,json}' ];
watcher = createWatch(globs, argv);
watcher.on('change', function (file) {
mainWindow.reload();
});
}
ipc.on('error', function (event, errObj) {
var err = JSON.parse(errObj);
bail(err.stack);
});
var webContents = mainWindow.webContents;
webContents.once('did-finish-load', function () {
global.__shouldElectronQuitOnError = argv.quit;
mainWindow.openDevTools();
});
mainWindow.loadURL(mainIndexURL);
mainWindow.on('closed', function () {
mainWindow = null;
});
function bail (err) {
console.error(err.stack ? err.stack : err);
if (global.__shouldElectronQuitOnError) {
exitWithCode1 = true;
if (mainWindow) mainWindow.close();
}
}
function fatal (err) {
global.__shouldElectronQuitOnError = true;
bail(err);
}
});
Loading…
Cancel
Save