Crystal language bindings for zserge's Webview which is an excellent cross-platform single-header webview library for C/C++ using Gtk, Cocoa, or MSHTML/Edge, depending on the host OS.
Webview relies on default rendering engine of host Operating System, thus binaries generated with this Shard will be much more leaner as compared to Electron which bundles Chromium with each distribution.
This shard supports two-way bindings between Crystal and JavaScript. You can invoke JS code via Webview::Webview#eval and calling Crystal code from JS is done via WebView::Webview#bind (refer to Examples 3 & 4 for samples on how to invoke Crystal functions from JS).
Webview-supported platforms and the engines you can expect to render your application content are as follows:
| Operating System | Browser Engine Used |
|---|---|
| macOS | Cocoa, WebKit |
| Linux | GTK 3, WebKitGTK |
| Windows | Windows API, WebView2 |
If you're planning on targeting Linux or BSD you must ensure that WebKit2GTK is already installed and available for discovery via the pkg-config command.
Debian-based systems:
apt install libgtk-3-dev libwebkit2gtk-4.1-devapt install libgtk-3-0 libwebkit2gtk-4.1-0BSD-based systems:
pkg install webkit2-gtk3wxallowed option (see mount(8)) to your fstab to bypass W^X memory protection for your executable. Please see if it works without disabling this security feature first.Microsoft Windows:
git clone https://github.com/webview/webview to get WebView sourceswebview\script\build.bat to compile them (it will download required nuget package)webview\dll\x64\webview.lib to <your crystal installation>\libwebview\dll\x64\webview.dll to directory with your programAdd the dependency to your shard.yml:
dependencies:
webview:
github: naqvis/webview
Run shards install
Use bind_typed for compile-time type safety and automatic JSON conversion:
wv.bind_typed("add", Int32, Int32) do |a, b|
a + b # Clean, automatic conversion!
end
Instead of manual JSON handling:
wv.bind("add", Webview::JSProc.new { |args|
a = args[0].as_i.to_i32 # Manual conversion
b = args[1].as_i.to_i32
JSON::Any.new(a + b) # Manual wrapping
})
Automatic cleanup with with_window:
Webview.with_window(800, 600, Webview::SizeHints::NONE, "My App") do |wv|
wv.html = "<h1>Hello</h1>"
wv.run
end # Automatically destroyed
React to page events:
wv.on_load = -> { puts "Page loaded!" }
wv.on_navigate = ->(url : String) { puts "Navigating to #{url}" }
Non-blocking JavaScript evaluation:
wv.eval_async("console.log('Hello')") do
puts "JavaScript executed"
end
# Or with channels
channel = wv.eval_with_channel("someCode()")
channel.receive # Wait for completion
Manage multiple windows easily:
Webview::WindowManager.with_manager do |manager|
window1 = manager.create_window(800, 600, Webview::SizeHints::NONE, "Window 1")
window2 = manager.create_window(800, 600, Webview::SizeHints::NONE, "Window 2")
# All windows automatically cleaned up
end
Errors now include context:
# Errors show what operation failed
wv.navigate("invalid://url") # Error: "navigating to invalid://url"
Access platform-specific handles:
window_handle = wv.window
ui_widget = wv.native_handle(Webview::NativeHandleKind::UI_WIDGET)
See the examples/ directory for complete working examples.
require "webview"
wv = Webview.window(640, 480, Webview::SizeHints::NONE, "Hello WebView", "http://crystal-lang.org")
wv.run
wv.destroy
require "webview"
html = <<-HTML
<!DOCTYPE html><html lang="en-US">
<head>
<title>Hello,World!</title>
</head>
<body>
<div class="container">
<header>
<!-- Logo -->
<h1>City Gallery</h1>
</header>
<nav>
<ul>
<li><a href="/London">London</a></li>
<li><a href="/Paris">Paris</a></li>
<li><a href="/Tokyo">Tokyo</a></li>
</ul>
</nav>
<article>
<h1>London</h1>
<img src="pic_mountain.jpg" alt="Mountain View" style="width:304px;height:228px;">
<p>London is the capital city of England. It is the most populous city in the United Kingdom, with a metropolitan area of over 13 million inhabitants.</p>
<p>Standing on the River Thames, London has been a major settlement for two millennia, its history going back to its founding by the Romans, who named it Londinium.</p>
</article>
<footer>Copyright © W3Schools.com</footer>
</div>
</body>
</html>
HTML
wv = Webview.window(640, 480, Webview::SizeHints::NONE, "Hello WebView")
wv.html = html
wv.run
wv.destroy
require "webview"
html = <<-HTML
<!doctype html>
<html>
<body>hello</body>
<script>
window.onload = function() {
document.body.innerText = "Javascript calling Crystal code";
noop().then(function(res) {
console.log('noop res', res);
add(1, 2).then(function(res) {
console.log('add res', res);
});
});
};
</script>
</html>
HTML
wv = Webview.window(640, 480, Webview::SizeHints::NONE, "Hello WebView", true)
wv.html = html
wv.bind("noop", Webview::JSProc.new { |a|
pp "Noop called with arguments: #{a}"
JSON::Any.new("noop")
})
wv.bind("add", Webview::JSProc.new { |a|
pp "add called with arguments: #{a}"
ret = 0_i64
a.each do |v|
ret += v.as_i64
end
JSON::Any.new(ret)
})
wv.run
wv.destroy
require "webview"
html = <<-HTML
<!DOCTYPE html><html lang="en-US">
<head>
<title>Hello,World!</title>
</head>
<body>
<button onClick="add(document.body.children.length)">Add</button>
</body>
</html>
HTML
inject = <<-JS
elem = document.createElement('div');
elem.innerHTML = "hello webview %s";
document.body.appendChild(elem);
JS
wv = Webview.window(640, 480, Webview::SizeHints::NONE, "Hello WebView", true)
wv.html = html
wv.bind("add", Webview::JSProc.new { |n|
wv.eval(sprintf(inject, n))
JSON::Any.new(nil)
})
wv.run
wv.destroy
Thread.new do
get "/" do
"hello from kemal"
end
Kemal.run
end
wv = Webview.window(640, 480, Webview::SizeHints::NONE, "WebView with local webapp!", "http://localhost:3000")
wv.run
wv.destroy
require "webview"
html = <<-HTML
<!DOCTYPE html>
<html>
<body>
<button onclick="testAdd()">Test Add</button>
<div id="result"></div>
<script>
async function testAdd() {
const result = await add(5, 3);
document.getElementById('result').textContent = 'Result: ' + result;
}
</script>
</body>
</html>
HTML
Webview.with_window(640, 480, Webview::SizeHints::NONE, "Type-Safe Demo") do |wv|
wv.html = html
# Type-safe binding - automatic conversion!
wv.bind_typed("add", Int32, Int32) do |a, b|
a + b
end
wv.run
end # Automatically destroyed
Distribution of your app is outside the scope of this library but we can give some pointers for you to explore.
On macOS you would typically create a bundle for your app with an icon and proper metadata.
A minimalistic bundle typically has the following directory structure:
example.app bundle
└── Contents
├── Info.plist information property list
├── MacOS
| └── example executable
└── Resources
└── example.icns icon
Read more about the structure of bundles at the Apple Developer site.
Tip: The
png2icnstool can create icns files from PNG files. See theicnsutilspackage for Debian-based systems.
You would typically create a resource script file (*.rc) with information about the app as well as an icon. Since you should have MinGW-w64 readily available then you can compile the file using windres and link it into your program. If you instead use Visual C++ then look into the Windows Resource Compiler.
The directory structure could look like this:
my-project/
├── icons/
| ├── application.ico
| └── window.ico
├── basic.cc
└── resources.rc
resources.rc:
100 ICON "icons\\application.ico"
32512 ICON "icons\\window.ico"
Note: The ID of the icon resource to be used for the window must be
32512(IDI_APPLICATION).
Since a browser engine is not a full web browser it may not support every feature you may expect from a browser. If you find that a feature does not work as expected then please consult with the browser engine's documentation and open an issue on webview library if you think that the library should support it.
For example, the webview library does not attempt to support user interaction features like alert(), confirm() and prompt() and other non-essential features like console.log().
git checkout -b my-new-feature)git commit -am 'Add some feature')git push origin my-new-feature)C++
82.9%
Crystal
16.7%
Crystal language bindings for zserge's Webview which is an excellent cross-platform single-header webview library for C/C++ using Gtk, Cocoa, or MSHTML/Edge, depending on the host OS.
Webview relies on default rendering engine of host Operating System, thus binaries generated with this Shard will be much more leaner as compared to Electron which bundles Chromium with each distribution.
This shard supports two-way bindings between Crystal and JavaScript. You can invoke JS code via Webview::Webview#eval and calling Crystal code from JS is done via WebView::Webview#bind (refer to Examples 3 & 4 for samples on how to invoke Crystal functions from JS).
Webview-supported platforms and the engines you can expect to render your application content are as follows:
| Operating System | Browser Engine Used |
|---|---|
| macOS | Cocoa, WebKit |
| Linux | GTK 3, WebKitGTK |
| Windows | Windows API, WebView2 |
If you're planning on targeting Linux or BSD you must ensure that WebKit2GTK is already installed and available for discovery via the pkg-config command.
Debian-based systems:
apt install libgtk-3-dev libwebkit2gtk-4.1-devapt install libgtk-3-0 libwebkit2gtk-4.1-0BSD-based systems:
pkg install webkit2-gtk3wxallowed option (see mount(8)) to your fstab to bypass W^X memory protection for your executable. Please see if it works without disabling this security feature first.Microsoft Windows:
git clone https://github.com/webview/webview to get WebView sourceswebview\script\build.bat to compile them (it will download required nuget package)webview\dll\x64\webview.lib to <your crystal installation>\libwebview\dll\x64\webview.dll to directory with your programAdd the dependency to your shard.yml:
dependencies:
webview:
github: naqvis/webview
Run shards install
Use bind_typed for compile-time type safety and automatic JSON conversion:
wv.bind_typed("add", Int32, Int32) do |a, b|
a + b # Clean, automatic conversion!
end
Instead of manual JSON handling:
wv.bind("add", Webview::JSProc.new { |args|
a = args[0].as_i.to_i32 # Manual conversion
b = args[1].as_i.to_i32
JSON::Any.new(a + b) # Manual wrapping
})
Automatic cleanup with with_window:
Webview.with_window(800, 600, Webview::SizeHints::NONE, "My App") do |wv|
wv.html = "<h1>Hello</h1>"
wv.run
end # Automatically destroyed
React to page events:
wv.on_load = -> { puts "Page loaded!" }
wv.on_navigate = ->(url : String) { puts "Navigating to #{url}" }
Non-blocking JavaScript evaluation:
wv.eval_async("console.log('Hello')") do
puts "JavaScript executed"
end
# Or with channels
channel = wv.eval_with_channel("someCode()")
channel.receive # Wait for completion
Manage multiple windows easily:
Webview::WindowManager.with_manager do |manager|
window1 = manager.create_window(800, 600, Webview::SizeHints::NONE, "Window 1")
window2 = manager.create_window(800, 600, Webview::SizeHints::NONE, "Window 2")
# All windows automatically cleaned up
end
Errors now include context:
# Errors show what operation failed
wv.navigate("invalid://url") # Error: "navigating to invalid://url"
Access platform-specific handles:
window_handle = wv.window
ui_widget = wv.native_handle(Webview::NativeHandleKind::UI_WIDGET)
See the examples/ directory for complete working examples.
require "webview"
wv = Webview.window(640, 480, Webview::SizeHints::NONE, "Hello WebView", "http://crystal-lang.org")
wv.run
wv.destroy
require "webview"
html = <<-HTML
<!DOCTYPE html><html lang="en-US">
<head>
<title>Hello,World!</title>
</head>
<body>
<div class="container">
<header>
<!-- Logo -->
<h1>City Gallery</h1>
</header>
<nav>
<ul>
<li><a href="/London">London</a></li>
<li><a href="/Paris">Paris</a></li>
<li><a href="/Tokyo">Tokyo</a></li>
</ul>
</nav>
<article>
<h1>London</h1>
<img src="pic_mountain.jpg" alt="Mountain View" style="width:304px;height:228px;">
<p>London is the capital city of England. It is the most populous city in the United Kingdom, with a metropolitan area of over 13 million inhabitants.</p>
<p>Standing on the River Thames, London has been a major settlement for two millennia, its history going back to its founding by the Romans, who named it Londinium.</p>
</article>
<footer>Copyright © W3Schools.com</footer>
</div>
</body>
</html>
HTML
wv = Webview.window(640, 480, Webview::SizeHints::NONE, "Hello WebView")
wv.html = html
wv.run
wv.destroy
require "webview"
html = <<-HTML
<!doctype html>
<html>
<body>hello</body>
<script>
window.onload = function() {
document.body.innerText = "Javascript calling Crystal code";
noop().then(function(res) {
console.log('noop res', res);
add(1, 2).then(function(res) {
console.log('add res', res);
});
});
};
</script>
</html>
HTML
wv = Webview.window(640, 480, Webview::SizeHints::NONE, "Hello WebView", true)
wv.html = html
wv.bind("noop", Webview::JSProc.new { |a|
pp "Noop called with arguments: #{a}"
JSON::Any.new("noop")
})
wv.bind("add", Webview::JSProc.new { |a|
pp "add called with arguments: #{a}"
ret = 0_i64
a.each do |v|
ret += v.as_i64
end
JSON::Any.new(ret)
})
wv.run
wv.destroy
require "webview"
html = <<-HTML
<!DOCTYPE html><html lang="en-US">
<head>
<title>Hello,World!</title>
</head>
<body>
<button onClick="add(document.body.children.length)">Add</button>
</body>
</html>
HTML
inject = <<-JS
elem = document.createElement('div');
elem.innerHTML = "hello webview %s";
document.body.appendChild(elem);
JS
wv = Webview.window(640, 480, Webview::SizeHints::NONE, "Hello WebView", true)
wv.html = html
wv.bind("add", Webview::JSProc.new { |n|
wv.eval(sprintf(inject, n))
JSON::Any.new(nil)
})
wv.run
wv.destroy
Thread.new do
get "/" do
"hello from kemal"
end
Kemal.run
end
wv = Webview.window(640, 480, Webview::SizeHints::NONE, "WebView with local webapp!", "http://localhost:3000")
wv.run
wv.destroy
require "webview"
html = <<-HTML
<!DOCTYPE html>
<html>
<body>
<button onclick="testAdd()">Test Add</button>
<div id="result"></div>
<script>
async function testAdd() {
const result = await add(5, 3);
document.getElementById('result').textContent = 'Result: ' + result;
}
</script>
</body>
</html>
HTML
Webview.with_window(640, 480, Webview::SizeHints::NONE, "Type-Safe Demo") do |wv|
wv.html = html
# Type-safe binding - automatic conversion!
wv.bind_typed("add", Int32, Int32) do |a, b|
a + b
end
wv.run
end # Automatically destroyed
Distribution of your app is outside the scope of this library but we can give some pointers for you to explore.
On macOS you would typically create a bundle for your app with an icon and proper metadata.
A minimalistic bundle typically has the following directory structure:
example.app bundle
└── Contents
├── Info.plist information property list
├── MacOS
| └── example executable
└── Resources
└── example.icns icon
Read more about the structure of bundles at the Apple Developer site.
Tip: The
png2icnstool can create icns files from PNG files. See theicnsutilspackage for Debian-based systems.
You would typically create a resource script file (*.rc) with information about the app as well as an icon. Since you should have MinGW-w64 readily available then you can compile the file using windres and link it into your program. If you instead use Visual C++ then look into the Windows Resource Compiler.
The directory structure could look like this:
my-project/
├── icons/
| ├── application.ico
| └── window.ico
├── basic.cc
└── resources.rc
resources.rc:
100 ICON "icons\\application.ico"
32512 ICON "icons\\window.ico"
Note: The ID of the icon resource to be used for the window must be
32512(IDI_APPLICATION).
Since a browser engine is not a full web browser it may not support every feature you may expect from a browser. If you find that a feature does not work as expected then please consult with the browser engine's documentation and open an issue on webview library if you think that the library should support it.
For example, the webview library does not attempt to support user interaction features like alert(), confirm() and prompt() and other non-essential features like console.log().
git checkout -b my-new-feature)git commit -am 'Add some feature')git push origin my-new-feature)C++
82.9%
Crystal
16.7%