execjs-fastnode 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +20 -8
- data/Gemfile +1 -2
- data/{LICENSE.txt → LICENSE} +0 -0
- data/README.md +16 -11
- data/lib/execjs/fastnode/external_piped_runtime.rb +72 -22
- data/lib/execjs/fastnode/node_piped_runner.js +70 -58
- data/lib/execjs/fastnode/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f5fe8728df68607be57c58f5207618c5e19d967f
|
4
|
+
data.tar.gz: 36f90d80531ea43e2a7c5ef9686d2459c081bf1b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f55e3aba398c6221eed47a33ffb15829808f974172e144ce82a6168bead521e5cae15e71f76b8313603cba894ff89c017ffdd7b4f01676b532a7848e0c993a36
|
7
|
+
data.tar.gz: a08c714d6f3c56a55aaac58657c8d01645c42f3271af1ee4d1973363a587de3a2811abe4218fca502b6a9377d9072c38ecca3693a0a9d379fd280dfb72d7acc3
|
data/.travis.yml
CHANGED
@@ -3,11 +3,23 @@ cache: bundler
|
|
3
3
|
sudo: false
|
4
4
|
before_script:
|
5
5
|
- node --version
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
-
|
11
|
-
-
|
12
|
-
-
|
13
|
-
-
|
6
|
+
env:
|
7
|
+
global:
|
8
|
+
EXECJS_RUNTIME: FastNode
|
9
|
+
matrix:
|
10
|
+
- NODE_VERSION=v0.10
|
11
|
+
- NODE_VERSION=v0.12
|
12
|
+
- NODE_VERSION=v4
|
13
|
+
- NODE_VERSION=v6
|
14
|
+
rvm:
|
15
|
+
- 2.0.0
|
16
|
+
- 2.3.1
|
17
|
+
- jruby-head
|
18
|
+
before_install:
|
19
|
+
- gem install bundler
|
20
|
+
- gem update bundler
|
21
|
+
- . $HOME/.nvm/nvm.sh
|
22
|
+
- nvm install $NODE_VERSION
|
23
|
+
os:
|
24
|
+
- osx
|
25
|
+
- linux
|
data/Gemfile
CHANGED
data/{LICENSE.txt → LICENSE}
RENAMED
File without changes
|
data/README.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# ExecJS FastNode
|
2
2
|
|
3
|
+
[![Build Status](https://travis-ci.org/jhawthorn/execjs-fastnode.svg?branch=master)](https://travis-ci.org/jhawthorn/execjs-fastnode)
|
4
|
+
|
3
5
|
## What's this?
|
4
6
|
|
5
7
|
An alternative implementation of ExecJS's Node.js integration. This aims to speed up sprockets compilation without needing to embed a javascript interpreter inside of ruby like [therubyracer](cowboyd/therubyracer).
|
@@ -11,10 +13,10 @@ Much.
|
|
11
13
|
```
|
12
14
|
$ rake bench
|
13
15
|
...
|
14
|
-
|
15
|
-
Node.js (V8) fast 0.000000 0.000000 0.000000
|
16
|
-
therubyracer (V8) 0.020000 0.000000 0.020000
|
17
|
-
Node.js (V8) 0.000000 0.010000 1.470000
|
16
|
+
user system total real
|
17
|
+
Node.js (V8) fast 0.000000 0.000000 0.000000 (0.069723)
|
18
|
+
therubyracer (V8) 0.020000 0.000000 0.020000 (0.018010)
|
19
|
+
Node.js (V8) 0.000000 0.010000 1.470000 (1.487579)
|
18
20
|
```
|
19
21
|
|
20
22
|
Okay, so it's not as fast as `therubyracer`, but it's 20x faster than the standard ExecJS Node.js implementation.
|
@@ -25,17 +27,17 @@ The benchmark measures the time to compile the javascript CoffeeScript compiler
|
|
25
27
|
|
26
28
|
The existing ExecJS runtime has to run a new Node.js process each time any JS is to be run. This means that if you are loading up the CoffeeScript compiler in order to compile some sprockets assets, it needs to reload the node executable and the entire coffeescript compiler for each file it is compiling.
|
27
29
|
|
28
|
-
This implementation avoids this by starting
|
30
|
+
This implementation avoids this by starting a persistently running Node.js process connected through a UNIX socket. Isolation between different ExecJS contexts is achieved through Node's [vm.Script](https://nodejs.org/api/vm.html).
|
29
31
|
|
30
|
-
|
|
32
|
+
| ExecJS FastNode | Standard ExecJS Node |
|
31
33
|
| --- | --- |
|
32
|
-
| ![](docs/
|
34
|
+
| ![](docs/example_new.png) | ![](docs/example_old.png) |
|
33
35
|
|
34
36
|
## Is this production ready?
|
35
37
|
|
36
38
|
Maybe? It needs more testing to be labeled as such. If you encounter any troubles please [file an issue](https://github.com/jhawthorn/execjs-fastnode/issues/new).
|
37
39
|
|
38
|
-
Currently minimal effort is made to handle catastrophic errors: Node.js crashing, running out of memory, being killed.
|
40
|
+
Currently minimal effort is made to handle catastrophic errors: Node.js crashing, running out of memory, being killed.
|
39
41
|
|
40
42
|
It's probably fine for development.
|
41
43
|
|
@@ -78,9 +80,14 @@ I haven't seen any benchmarks or bug reports that demonstrate this, so I conside
|
|
78
80
|
|
79
81
|
[mini_racer](https://github.com/discourse/mini_racer) is another option.
|
80
82
|
|
81
|
-
The ExecJS Node runtime has its benefits as well. It should
|
83
|
+
The ExecJS Node runtime has its benefits as well. It should work on jRuby and other non-MRI runtimes.
|
82
84
|
If this were merged upstream it would give developers fast javascript execution without needing an extra gem or configuration, just a working `node` somewhere in `$PATH`.
|
83
85
|
|
86
|
+
## Limitations
|
87
|
+
|
88
|
+
* Syntax errors on Node.js versions 0.10.x and 4.x can't report the exact line number of the error. Instead they will always display `(execjs):1`.
|
89
|
+
* This only runs a single node process, so javascript execution can only make use of a single processor. This isn't much of an issue because sprockets doesn't parallelize builds. This runtime is so much faster than the original that this would only be an issue with very specific use cases.
|
90
|
+
|
84
91
|
## Development
|
85
92
|
|
86
93
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
@@ -91,8 +98,6 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
91
98
|
|
92
99
|
Bug reports and pull requests are welcome on GitHub at https://github.com/jhawthorn/execjs-fastnode.
|
93
100
|
|
94
|
-
|
95
101
|
## License
|
96
102
|
|
97
103
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
98
|
-
|
@@ -1,28 +1,70 @@
|
|
1
|
+
require "net/http"
|
2
|
+
require "socket"
|
3
|
+
require "thread"
|
1
4
|
require "tmpdir"
|
2
5
|
require 'json'
|
3
|
-
require "open3"
|
4
|
-
require "thread"
|
5
6
|
require "execjs/runtime"
|
6
7
|
require "execjs/fastnode/command"
|
7
8
|
|
8
9
|
module ExecJS
|
9
10
|
module FastNode
|
10
11
|
class ExternalPipedRuntime < ExecJS::Runtime
|
11
|
-
class
|
12
|
-
|
12
|
+
class VMCommand
|
13
|
+
def initialize(socket_path, cmd, arguments)
|
14
|
+
@socket_path = socket_path.to_s
|
15
|
+
@cmd = cmd
|
16
|
+
@arguments = arguments
|
17
|
+
end
|
18
|
+
|
19
|
+
def execute
|
20
|
+
sock = Net::BufferedIO.new(socket)
|
21
|
+
|
22
|
+
request = Net::HTTP::Post.new("/")
|
23
|
+
request['Connection'] = 'close'
|
24
|
+
request['Content-Type'] = 'application/json'
|
25
|
+
request.body = contents
|
26
|
+
request.exec(sock, "1.1", "/")
|
27
|
+
|
28
|
+
begin
|
29
|
+
response = Net::HTTPResponse.read_new(sock)
|
30
|
+
end while response.kind_of?(Net::HTTPContinue)
|
31
|
+
|
32
|
+
response.reading_body(sock, request.response_body_permitted?) { }
|
33
|
+
sock.close
|
34
|
+
|
35
|
+
parse(response.body)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def socket
|
41
|
+
UNIXSocket.new(@socket_path)
|
42
|
+
end
|
43
|
+
|
44
|
+
def contents
|
45
|
+
::JSON.generate({cmd: @cmd, args: @arguments})
|
46
|
+
end
|
47
|
+
|
48
|
+
def parse(body)
|
49
|
+
::JSON.parse(body, create_additions: false)
|
50
|
+
end
|
51
|
+
end
|
13
52
|
|
53
|
+
class VM
|
14
54
|
def initialize(options)
|
15
55
|
@mutex = Mutex.new
|
16
|
-
@
|
56
|
+
@socket_path = nil
|
17
57
|
@options = options
|
18
58
|
end
|
19
59
|
|
20
60
|
def started?
|
21
|
-
!!@
|
61
|
+
!!@socket_path
|
22
62
|
end
|
23
63
|
|
24
|
-
def self.finalize(
|
25
|
-
proc {
|
64
|
+
def self.finalize(socket_path)
|
65
|
+
proc {
|
66
|
+
VMCommand.new(socket_path, "exit", [0]).execute
|
67
|
+
}
|
26
68
|
end
|
27
69
|
|
28
70
|
def exec(context, source)
|
@@ -34,9 +76,7 @@ module ExecJS
|
|
34
76
|
end
|
35
77
|
|
36
78
|
def start
|
37
|
-
return if started?
|
38
79
|
@mutex.synchronize do
|
39
|
-
# will double check started? with the lock held
|
40
80
|
start_without_synchronization
|
41
81
|
end
|
42
82
|
end
|
@@ -45,15 +85,27 @@ module ExecJS
|
|
45
85
|
|
46
86
|
def start_without_synchronization
|
47
87
|
return if started?
|
48
|
-
|
49
|
-
|
88
|
+
dir = Dir.mktmpdir("execjs-fastnode-")
|
89
|
+
@socket_path = File.join(dir, "socket")
|
90
|
+
@pid = Process.spawn({"PORT" => @socket_path.to_s}, @options[:binary], @options[:runner_path])
|
91
|
+
|
92
|
+
retries = 20
|
93
|
+
while !File.exists?(@socket_path)
|
94
|
+
sleep 0.05
|
95
|
+
retries -= 1
|
96
|
+
|
97
|
+
if retries == 0
|
98
|
+
raise "Unable to start nodejs process"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
ObjectSpace.define_finalizer(self, self.class.finalize(@socket_path))
|
50
103
|
end
|
51
104
|
|
52
105
|
def command(cmd, *arguments)
|
53
|
-
start
|
54
106
|
@mutex.synchronize do
|
55
|
-
|
56
|
-
|
107
|
+
start_without_synchronization
|
108
|
+
VMCommand.new(@socket_path, cmd, arguments).execute
|
57
109
|
end
|
58
110
|
end
|
59
111
|
end
|
@@ -126,18 +178,16 @@ module ExecJS
|
|
126
178
|
@deprecated = !!options[:deprecated]
|
127
179
|
@binary = nil
|
128
180
|
|
181
|
+
@vm = VM.new(
|
182
|
+
binary: binary,
|
183
|
+
runner_path: @runner_path
|
184
|
+
)
|
185
|
+
|
129
186
|
@popen_options = {}
|
130
187
|
@popen_options[:external_encoding] = @encoding if @encoding
|
131
188
|
@popen_options[:internal_encoding] = ::Encoding.default_internal || 'UTF-8'
|
132
189
|
end
|
133
190
|
|
134
|
-
def vm
|
135
|
-
@vm ||= VM.new(
|
136
|
-
binary: @binary,
|
137
|
-
runner_path: @runner_path
|
138
|
-
)
|
139
|
-
end
|
140
|
-
|
141
191
|
def available?
|
142
192
|
binary ? true : false
|
143
193
|
end
|
@@ -1,72 +1,84 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
var buf = ""
|
1
|
+
var stdin = process.stdin
|
2
|
+
var stdout = process.stdout
|
3
|
+
var buf = ""
|
5
4
|
|
6
|
-
|
5
|
+
stdin.setEncoding('utf8')
|
7
6
|
|
8
|
-
|
9
|
-
|
7
|
+
var vm = require('vm');
|
8
|
+
var contexts = {};
|
10
9
|
|
11
|
-
|
12
|
-
|
10
|
+
/*
|
11
|
+
* Versions of node before 0.12 (notably 0.10) didn't properly propagate
|
12
|
+
* syntax errors.
|
13
|
+
* This also regressed in the node 4.0 releases.
|
14
|
+
*
|
15
|
+
* To get around this, if it looks like we are missing the location of the
|
16
|
+
* error, we guess it is (execjs):1
|
17
|
+
*
|
18
|
+
* This is obviously not ideal, but only affects syntax errors, and only on
|
19
|
+
* these versions.
|
20
|
+
*/
|
21
|
+
function massageStackTrace(stack) {
|
22
|
+
if (stack && stack.indexOf("SyntaxError") == 0) {
|
23
|
+
return "(execjs):1\n" + stack;
|
24
|
+
} else {
|
25
|
+
return stack;
|
13
26
|
}
|
27
|
+
}
|
14
28
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
29
|
+
function getContext(uuid) {
|
30
|
+
return contexts[uuid] || (contexts[uuid] = vm.createContext())
|
31
|
+
}
|
32
|
+
|
33
|
+
var commands = {
|
34
|
+
deleteContext: function(uuid) {
|
35
|
+
delete contexts[uuid];
|
36
|
+
return 1;
|
37
|
+
},
|
38
|
+
exit: function(code) {
|
39
|
+
process.exit(code)
|
40
|
+
},
|
41
|
+
exec: function execJS(input) {
|
42
|
+
var context = getContext(input.context);
|
43
|
+
var source = input.source;
|
44
|
+
try {
|
45
|
+
var program = function(){
|
46
|
+
return vm.runInContext(source, context, "(execjs)");
|
47
|
+
}
|
48
|
+
result = program();
|
49
|
+
if (typeof result == 'undefined' && result !== null) {
|
50
|
+
return ['ok'];
|
51
|
+
} else {
|
52
|
+
try {
|
53
|
+
return ['ok', result];
|
54
|
+
} catch (err) {
|
55
|
+
return ['err', '' + err, err.stack];
|
39
56
|
}
|
40
|
-
} catch (err) {
|
41
|
-
return ['err', '' + err, err.stack];
|
42
57
|
}
|
58
|
+
} catch (err) {
|
59
|
+
return ['err', '' + err, massageStackTrace(err.stack)];
|
43
60
|
}
|
44
61
|
}
|
62
|
+
}
|
45
63
|
|
46
|
-
|
47
|
-
|
48
|
-
|
64
|
+
var http = require('http')
|
65
|
+
var server = http.createServer(function(req, res) {
|
66
|
+
var contents = '';
|
49
67
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
}
|
68
|
+
req.on('data', function (dataIn) {
|
69
|
+
contents += dataIn;
|
70
|
+
});
|
54
71
|
|
55
|
-
function
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
processLine(lines[i]);
|
60
|
-
}
|
61
|
-
buf = lines[lines.length - 1]
|
62
|
-
}
|
63
|
-
}
|
72
|
+
req.on('end', function () {
|
73
|
+
var input = JSON.parse(contents)
|
74
|
+
var result = commands[input.cmd].apply(null, input.args)
|
75
|
+
var outputJSON = JSON.stringify(result)
|
64
76
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
buf += chunk
|
69
|
-
processBuffer()
|
70
|
-
}
|
77
|
+
res.statusCode = 200;
|
78
|
+
res.setHeader('Content-Type', 'application/json');
|
79
|
+
res.end(outputJSON);
|
71
80
|
});
|
72
|
-
})
|
81
|
+
});
|
82
|
+
|
83
|
+
var port = process.env.PORT || 3001;
|
84
|
+
server.listen(port);
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: execjs-fastnode
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Hawthorn
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-10-
|
11
|
+
date: 2016-10-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: execjs
|
@@ -77,7 +77,7 @@ files:
|
|
77
77
|
- ".gitmodules"
|
78
78
|
- ".travis.yml"
|
79
79
|
- Gemfile
|
80
|
-
- LICENSE
|
80
|
+
- LICENSE
|
81
81
|
- README.md
|
82
82
|
- Rakefile
|
83
83
|
- bin/console
|