NAME

Chandra::Assets - Asset bundling and resource loading for Chandra apps

SYNOPSIS

use Chandra::Assets;

my $assets = Chandra::Assets->new(
    root   => 'assets/',       # Base directory
    prefix => 'app',           # URL prefix: app://asset/path
);

# Register with app (sets up protocol handler)
$assets->mount($app);

# Now in HTML — use data-href / data-src to avoid console
# "unsupported URL" warnings from the native resource loader:
# <link rel="stylesheet" data-href="app://css/style.css">
# <script data-src="app://js/main.js"></script>
# <img data-src="app://images/logo.png">
#
# Plain href/src also work but the browser logs a harmless error
# before the JS interception replaces the element.

# Inline assets directly
my $css_tag = $assets->inline_css('css/style.css');
# Returns: <style>...contents...</style>

my $js_tag = $assets->inline_js('js/main.js');
# Returns: <script>...contents...</script>

my $img_tag = $assets->inline_image('images/logo.png');
# Returns: <img src="data:image/png;base64,...">

# Bundle multiple files
my $bundle = $assets->bundle(
    css => ['css/reset.css', 'css/style.css'],
    js  => ['js/utils.js', 'js/main.js'],
);
# $bundle->{css} => '<style>...combined...</style>'
# $bundle->{js}  => '<script>...combined...</script>'

# List available assets
my @files = $assets->list;
my @css   = $assets->list('*.css');

# Read asset content
my $content = $assets->read('css/style.css');

# Check existence
if ($assets->exists('images/logo.png')) { ... }

DESCRIPTION

Serve local CSS, JS, images, and fonts from a directory via custom protocol. Uses Chandra::Protocol internally to register the asset scheme. Path traversal attacks are blocked.

METHODS

new

my $assets = Chandra::Assets->new(
    root   => 'assets/',    # required
    prefix => 'app',        # default: 'asset'
    app    => $app,         # optional, can pass to mount() instead
);

root

Returns the asset root directory.

prefix

Returns the URL prefix (scheme name).

mount

$assets->mount($app);

Register the asset protocol with a Chandra app. After mounting, prefix://path URLs in the webview will serve files from the root directory. The injected JavaScript transparently intercepts <link>, <script>, <img>, and fetch() calls for the registered scheme.

Use data-href / data-src attributes instead of plain href / src to prevent the browser's native loader from logging a "Failed to load resource: unsupported URL" warning:

<link rel="stylesheet" data-href="app://css/style.css">
<script data-src="app://js/main.js"></script>
<img data-src="app://images/logo.png">

read

my $content = $assets->read('css/style.css');

Read an asset file's content. Croak on path traversal attempts.

exists

if ($assets->exists('images/logo.png')) { ... }

Check if an asset file exists.

list

my @all = $assets->list;
my @css = $assets->list('*.css');

List asset files, optionally filtered by a simple glob pattern.

mime_type

my $mime = $assets->mime_type('style.css');  # 'text/css'

Returns the MIME type for a given filename.

inline_css

my $tag = $assets->inline_css('css/style.css');
# <style>...contents...</style>

inline_js

my $tag = $assets->inline_js('js/main.js');
# <script>...contents...</script>

inline_image

my $tag = $assets->inline_image('images/logo.png');
# <img src="data:image/png;base64,...">

bundle

my $result = $assets->bundle(
    css => ['reset.css', 'style.css'],
    js  => ['utils.js', 'main.js'],
);
# $result->{css} => '<style>...combined...</style>'
# $result->{js}  => '<script>...combined...</script>'

SECURITY

Path traversal is prevented: .., absolute paths, backslashes, and null bytes are all rejected.

DEPENDENCIES

File::Raw, MIME::Base64 (core), Chandra::Protocol

AUTHOR

LNATION <email@lnation.org>

LICENSE

This is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

1 POD Error

The following errors were encountered while parsing the POD:

Around line 31:

Non-ASCII character seen before =encoding in '—'. Assuming UTF-8