Automatic local bundling of remote resources as relocatable Julia objects
A small Julia package to automate the process of bundling web resources from remote URLs (usually CDNs) into embedded relocatable content that can then be served from a single server rather than from multiple third-party sources.
using BundledWebResources
const RESOURCE = Resource(
"https://cdn.jsdelivr.net/npm/plotly.js@2.26.2/dist/plotly.min.js";
sha256 = "bf56aa89e1d4df155b43b9192f2fd85dfb0e6279e05c025e6090d8503d004608",
)
You must provide a SHA256 hash of the expected content to ensure resource integrity of the included files. Verify the validity of the hash before including it in any deployments.
If you want to be able to Revise
that value without running into redefinition errors
then use the @comptime
macro can help out with that.
using BundledWebResources
function resource()
return @comptime Resource(
"https://cdn.jsdelivr.net/npm/plotly.js@2.26.2/dist/plotly.min.js";
sha256 = "bf56aa89e1d4df155b43b9192f2fd85dfb0e6279e05c025e6090d8503d004608",
)
end
This will evaluate the Resource
at compile-time such that the content of the
remote resource will be embedded in system images to avoid returning deployed
code to download the resource on initial startup.
Local resources, such as artifacts generated by external tools such as JS or
CSS bundlers, can be made to participate in the same system but using the
LocalResource
type. In combination with RelocatableFolders
this allows for
easy deployment of built artifacts via system images.
using RelocatableFolders, BundledWebResources
const DIST_DIR = @path joinpath(@__DIR__, "dist")
function resource()
return @comptime LocalResource(DIST_DIR, "output.css")
end
Again using the @comptime
macro to allow for redefinition if required. Though
using a bare const
would usually be sufficient.
using RelocatableFolders, BundledWebResources
const DIST_DIR = @path joinpath(@__DIR__, "dist")
const CSS_OUTPUT = LocalResource(DIST_DIR, "output.css")
The LocalResource
type accepts a third optional argument that is a function
(directory, file) -> content
that can be used to generate the content of the
resource at request time (cached in production use). This can be used to
generate resources from other resources, such as TypeScript to JavaScript conversion
using the experimental bun_build
function.
function bundled_resource()
return @comptime LocalResource(
DATA_DIR,
"bundled.js",
BundledWebResources.bun_build("bundled.ts"),
)
end
Above bun_build
returns a closure that will convert bundled.ts
to bundled.js
using the bun build
command.
You can use this optional transformer argument to do any kind of custom
processing of resources you want, even generating completely synthetic resources
from String
content.
function fake_resource_content(_dir, _file)
return """
console.log("Hello, world!");
"""
end
function fake_resource()
return @comptime LocalResource(
DATA_DIR,
"fake.js",
fake_resource_content,
)
end
Note that these resource generating functions are Revise
-aware and will
rebuild the resource content when the String
content changes if you have
Revise
loaded and browser hot-reloads enabled using ReloadableMiddleware
.
The main use case of these resources is serving them to clients via an HTTP
server. A ResourceRouter(mod::Module)
function is exported for this use. You
can define a router for all resources defined in a Module
that can be used in
an HTTP.jl
server. When Revise
is loaded these resources will "live
updated", otherwise they'll remain as static content in production builds that
don't include Revise
.
module MyBundledResources
using BundledWebResources
# ...
end
using HTTP, BundledWebResources
resource_router = ResourceRouter(MyBundledResources)
HTTP.serve(endpoint_router |> resource_router, HTTP.Sockets.localhost, 8080)
This feature is subject to change.
Experimental support for bundling web resources is provided via the bun
command-line tool which is provided via the Julia artifacts system and does not
need to be installed manually. Note that bun
does not currently support
Windows. The BundledWebResources.bun
function will throw an error on that
platform currently.
A watch
function is provided that can register a callback function to be run
each time the bun build
rebuilds the bundled files. This can be used to
trigger browser reloads or other actions.
A selection of recipes for common use cases of this package and integrations with other complementary packages.
Rather than manually downloading and installing fonts to be served by an
application (instead of a CDN) you can use BundledWebResources
to bundle
fonts into your application and serve them from there.
Fontsource is a good source for retrieving fonts from for this purpose. Locate a font, or several, for example Inter. Navigate to the CDN tab and select the variants you want to include in your application.
Find the current version of the font (rather than using latest
, which will
change over time and void the sha256
hash). This can be found on the NPM
link on the font page. Also find the URL to use for the font, which will be
url()
in the required CSS content. Something like
https://cdn.jsdelivr.net/fontsource/fonts/inter:vf@latest/latin-wght-normal.woff2
.
Switch the latest
for the version number. E.g.
https://cdn.jsdelivr.net/fontsource/fonts/inter:vf@5.0.16/latin-wght-normal.woff2
so that the sha256
hash will be stable.
function inter_woff2()
return @comptime Resource(
"https://cdn.jsdelivr.net/fontsource/fonts/inter:vf@5.0.16/latin-wght-normal.woff2";
sha256 = "88df0b5a7bc397dbc13a26bb8b3742cc62cd1c9b0dded57da7832416d6f52f42",
)
end
Create a LocalResource
for the fontface CSS file. This will be used to load the
font from the bundled resource.
function fontfaces_css_content(_dir, _file)
return """
/* inter-latin-wght-normal */
@font-face {
font-family: 'Inter Variable';
font-style: normal;
font-display: swap;
font-weight: 100 900;
src: url($(pathof(inter_woff2()))) format('woff2-variations');
unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
"""
end
function fontfaces_css()
return @comptime LocalResource(
@__DIR__,
"fontfaces.css",
fontfaces_css_content,
)
end
And then reference the fontfaces.css
file in your HTML. Here using
HypertextTemplates.jl dynamic attribute syntax, .href=
, to interpolate the
path to the versioned resource.
<link rel="stylesheet" .href="$(pathof(fontfaces_css()))" />
Finally, set your font-family to the font name in your CSS.
body {
font-family: "Inter Variable", sans-serif;
}
If using Tailwind CSS, for example, you can set the fontFamily
property in
your tailwind.config.js
file.
module.exports = {
theme: {
fontFamily: {
sans: ["Inter Variable", "sans-serif"],
},
},
};