Integrating with JavaScript
This guide explains how to integrate JavaScript into your Utopia application.
Using Import Maps
Import maps provide a modern way to manage JavaScript module dependencies. Utopia includes built-in support for import maps through the class Utopia::ImportMap class.
Installing JavaScript Libraries
Declare browser libraries as production dependencies in package.json. The web-packages.packages section selects the files that should be served and assigns their browser import names:
{
"private": true,
"dependencies": {
"@socketry/syntax": "^0.6.1"
},
"web-packages": {
"packages": {
"@socketry/syntax": {
"include": [
"Syntax.js"
],
"imports": {
"@socketry/syntax": "Syntax.js"
}
}
}
}
}
Install the dependencies using the configured package manager, then generate the browser-facing package projection:
$ bundle exec bake web:packages:install
$ bundle exec bake web:packages:update
This installs dependencies into node_modules/ and copies only the selected browser files into public/_components/. Treat both directories as generated projections rather than authored source.
Use an immutable installation and verify the checked-in projection in CI:
$ bundle exec bake web:packages:install frozen=true
$ bundle exec bake web:packages:check
Creating the Import Map
Create a global import map in lib/my_website/import_map.rb:
require "utopia/import_map"
module MyWebsite
IMPORT_MAP = Utopia::ImportMap.load_manifest("public/_components")
end
This loads the generated package mappings directly, so the browser import map remains synchronized with package.json.
Then load this in lib/my_website.rb:
require_relative "my_website/import_map"
Adding to Your Pages
Add it to your page template (pages/_page.xnode), using relative_to to adjust paths for the current page:
<html>
<head>
#{MyWebsite::IMPORT_MAP.relative_to(request.path + "/")}
</head>
<body>
<!-- Your content -->
</body>
</html>
Using the Library
Once the import map is set up, you can import and use the library in your scripts:
<script type="module">
// <![CDATA[
import Syntax from '@socketry/syntax';
await Syntax.highlight();
// ]]>
</script>
Inspect the generated import map with bundle exec bake web:packages:import_map:show when debugging package resolution.
See the Web Packages documentation for workspace packages, package selection, and alternative package managers.
Advanced Import Map Features
Using CDN URLs
Import maps support direct CDN imports without downloading files:
IMPORT_MAP = Utopia::ImportMap.build do |map|
map.import("react", "https://esm.sh/react@18")
map.import("vue", "https://cdn.jsdelivr.net/npm/vue@3/dist/vue.esm-browser.js")
end
Nested Base URLs
You can organize imports from different sources using nested with(base:) blocks:
IMPORT_MAP = Utopia::ImportMap.build do |map|
# Local components
map.with(base: "/_components/") do |local|
local.import "app", "./app.js"
end
# CDN imports
map.with(base: "https://cdn.jsdelivr.net/npm/") do |cdn|
cdn.import "lit", "lit@2.7.5/index.js"
cdn.import "lit/decorators.js", "lit@2.7.5/decorators.js"
end
end
Subresource Integrity
Add integrity hashes for enhanced security:
IMPORT_MAP = Utopia::ImportMap.build do |map|
map.import("react", "https://esm.sh/react@18", integrity: "sha384-...")
end
Scoped Imports
Use scopes to resolve imports differently based on the referrer URL (the page or module location where the import is being made):
IMPORT_MAP = Utopia::ImportMap.build do |map|
map.import("utils", "/utils.js")
# When importing from any page under /admin/, use a different utils module
map.scope("/admin/", {"utils" => "/admin/utils.js"})
end
When you're on a page at /admin/dashboard and you import "utils", it will resolve to /admin/utils.js. On other pages, it resolves to /utils.js.
Traditional JavaScript
You can also use JavaScript by embedding it directly into your HTML, or by creating a JavaScript source file and referencing that.
Embedding Code
When embedding JavaScript directly in XRB templates, wrap the code in CDATA comments to prevent XRB's parser from interpreting special characters like <, >, and &:
<html>
<body>
<script type="text/javascript">
// <![CDATA[
console.log("Hello World")
// ]]>
</script>
</body>
</html>
External Script
In script.js:
console.log("Hello World")
In your HTML view:
<html>
<body>
<script type="text/javascript" src="script.js"></script>
</body>
</html>