View Source Scenic.Assets.Static (Scenic v0.11.2)
Manages static assets, which are resources such as fonts or images (jpg or png) that ship with your application and do not change over time.
These assets live as seperate files and are hashed so that they are rejected if they change in any way after you compile your application. They are cacheable by the relay server if you remote your Scenic UI.
In previous versions of Scenic, static assets were rather complicated to set up and maintain. Starting with v0.11, Scenic has an assets build pipeline that manages the static assets library for you.
required-configuration
Required Configuration
Setting up the static asset pipeline requites several inputs that need to be maintained.
- Assets Directory: Typically
/assets
in your main app source directory. This is the folder that holds your raw asset files. - Assets Module: A module in your application that builds and holds the asset library.
- Assets Config: Configuration scripts in your application that indicates where the assets directory is and your assets module.
Assets Directory
The assets directory typically is typically called /assets
and lives at the root of
your application source directory. This can be changed in the config options.
Example:
my_app_src
assets
fonts
roboto.ttf
custom_font.ttf
images
parrot.jpg
my_logo.png
config
lib
etc...
Once the rest of the configuration is complete, adding a new font is as simple as dropping the *.ttf file into the /assets/fonts directory and compiling your assets module. Similar is true for images.
Assets Library
When your application is running, there needs to be a module that contains the built asset library referring to your static assets. This library holds things like the hash of the contents of each asset, and it's parsed metadata.
You must create this module and compile it with your application. The following example
is what this module should look like. Replace MyApplication
and :my_application
with
the actual name of your application.
defmodule MyApplication.Assets do
use Scenic.Assets.Static,
otp_app: :my_application,
sources: [
"assets",
{:scenic, "deps/scenic/assets"}
],
alias: [
parrot: "images/parrot.jpg"
]
end
Notice that there are several configuration sections in your assets module. Sources is the list of folders to look in to find assets. For example, if you take a dependency on a package that contains assets, you will need to add it's assets folder here. If can omit the sources section if you only use a single assets folder and scenic's default fonts. In other words, the sources configuration shown above is also the default.
The :alias
list creates shortcuts that refer to the files in the assets library. This is useful
if you think an asset id may change during development but want a constant way to refer to it
in your code. In the above example, the atom :parrot is mapped to the file images/parrot.jpb
and are interchangeable with each other in a graph.
In this example, the two rect fills are identical as the :parrot
alias was created
in the configuration script.
Graph.build()
|> rect({100, 50}, fill: {:image, "images/parrot.jpg"})
|> rect({100, 50}, fill: {:image, :parrot})
The fonts fonts/roboto.ttf
and fonts/roboto_mono.ttf
are considered the default
fonts for Scenic and are automatically aliased to :roboto
and :roboto_mono
.
It is expected that you will include those two fonts in your /assets/fonts
directory.
IMPORTANT NOTE: When you add a new asset to the assets directory, you may need to force this module to recompile for them to be usable. Adding or removing a return at the end should do the trick. In the future, there will be a file system watcher (much like Phoenix has) that will do this automatically. Until then, it is pretty easy to do manually.
Assets Configuration
The final piece is some configuration that connects scenic and your assets module
togther. Put this in your application's config.exs
file.
config :scenic, :assets,
module: MyApplication.Assets
troubleshooting
Troubleshooting
If you have added an asset to your assets directory and you think it should be in your library, but it isn't, or you can't compile a scene because the asset can't be found, then start troubleshooting with the following steps.
- Force your assets module to rebuild. Touch it in some way such as adding or removing a carriage return at the end, then compile again.
- Check that you are using the correct id for the asset in your graph.
- If you are using an alias, check it's spelling and its assignment in the config script.
- Confirm that the asset itself has valid contents, whether it is a font (.ttf) or an image (.jpg or .jpeg or .png)
That usually does it.
under-the-covers
Under the Covers
When your assets module is compiled, several steps are executed by Scenic.Assets.Static
- The files in your assets directory are parsed for validity and metadata. Valid files move on to the next step
- The valid assets files are hashed to create a cryptographic signature that is used later when the files are loaded to confirm that they are unchanged.
- The asset files are copied into the
/priv/static
directory, which is where they are actually loaded from at run time. The name of the file in this directory is aBase.url_encode64/2
version of the hash of the file's contents. - A map is created, which is the actual asset library used at runtime. This map has the original file name as keys and holds the hashes, and parsed metadata as the contents. This map is stored as a literal object in your assets module and is the reason it needs to be compiled when you add a new asset.
If you are curious and want to see the library yourself, you can query the
MyApplication.Assets.library/0
function, which is added at compile time. Alternately,
the function Scenic.Assets.Static.library/0
should return the same library.
future-work
Future Work
There are two pieces of work to the static assets pipeline that are planned for the future.
First is a file system watcher that automatically flags your assets module to be recompiled when the contents of the assets directory changes. This would work in a similar way to the file system watcher used by Phoenix.
The second, larger, piece of work is to include optional transform scripts/code when your assets module is compiled. This would let you do things like putting a very high resolution image in the sources folder and down-scaling at compile time as appropriate for the target device you are compiling for. In the meantime, just put in the assets you want to use directly.
Link to this section Summary
Functions
Return the compiled asset library.
Load the binary contents of an asset given it's id or hash.
Fetch the metadata for an asset by id.
Return the configured asset library module.
Transform an asset id into the file hash.
Link to this section Types
Link to this section Functions
Return the compiled asset library.
@spec load(id :: any()) :: {:ok, data :: binary()} | {:error, :not_found} | {:error, :hash_failed} | {:error, File.posix()}
Load the binary contents of an asset given it's id or hash.
Return is in the form of {:ok, bin}
If the asset is not in the library, {:error, :not_found}
is returned.
The contents of the file will be hashed and compared against the hash found in
the library. If this test fails, {:error, :hash_failed}
is returned.
If the output file cannot be read, it returns a posix error.
@spec load(library :: t(), id :: any()) :: {:ok, data :: binary()} | {:error, :not_found} | {:error, :hash_failed} | {:error, File.posix()}
Fetch the metadata for an asset by id.
Return is in the form of {:ok, metadata}
If the hash is not in the library, :error
is returned.
Example:
{:ok, meta} = Scenic.Assets.Static.meta( :parrot )
Return the configured asset library module.
Transform an asset id into the file hash.
If you pass in a valid hash, it is returned unchanged
Example:
alias Scenic.Assets.Static
library = Static.library()
{:ok, "VvWQFjblIwTGsvGx866t8MIG2czWyIc8by6Xc88AOns"} = Static.hash( library, :parrot )
{:ok, "VvWQFjblIwTGsvGx866t8MIG2czWyIc8by6Xc88AOns"} = Static.hash( library, "images/parrot.png" )
{:ok, "VvWQFjblIwTGsvGx866t8MIG2czWyIc8by6Xc88AOns"} = Static.hash( library, "VvWQFjblIwTGsvGx866t8MIG2czWyIc8by6Xc88AOns" )