-
Notifications
You must be signed in to change notification settings - Fork 58
Build Script
The build script is called jasyscript.py
and should be placed in the root folder of the project. This file is only needed in projects which define own tasks but is not required in library projects which you are including.
Whenever you call jasy
without arguments or with an explicit -h
/--help
you will be presented the help screen. The help screen will list you all possible options and available tasks with their description and arguments.
A trivial example of jasyscript.py
might look like this:
@task
def build():
"""This is the help text for the build task"""
# Resolving classes
classes = Resolver(session).addClassName("notebook.Application").getSortedClasses()
# Write compressed classes
OutputManager(session).storeCompressed(classes, "$prefix/simple.js")
This is plain and simple Python code to detect the dependencies of notebook.Application
and compress all relevant classes into build/simple.js
.
- The
$prefix
is a placeholder to inject the current prefix (typically the name of the task) into the filenames. - The dependency handling starts with the classes added via
addClassName
and processes classes based on unresolved names in each file until no more relevant files are required for inclusion. - Dependency tracking works by full references to other classes. It does not work when classes are accessed using
[]
, via strings e.g.my["ui"].Button
or via variables e.g.var myui = mylib.ui; var btn = myui.Button();
does not figure out that you usemylib.ui.Button
. - There is an alternative to
getSortedClasses()
calledgetIncludedClasses()
which works without sorting. That's somewhat faster and might be relevant in cases where you require a simple list, but the order is not important. - The return value of
getSortedClasses()
andgetIncludedClasses()
is a Pythonlist
with JasyClass
objects. These objects allow easy access to things like dependencies, meta data, compressed code, etc. - The compressor uses a global configuration defined via the
jsFormatting
andjsOptimization
objects. You can configure these objects anywhere in your code viaenable("feature")
anddisable("feature")
.
You are able to pass (string) arguments from the outside to any jasy task:
$ jasy build --formatting on
To make this possible you have to change your build task a little:
@task
def build(formatting="off"):
"""This is the help text for the build task"""
# Resolving classes
classes = Resolver(session).addClassName("notebook.Application").getSortedClasses()
# Enable formatting of code when user passes parameter
if formatting == "on":
outputManager = OutputManager(session, compressionLevel=0, formattingLevel=1)
else:
outputManager = OutputManager(session, compressionLevel=2, formattingLevel=0)
# Write compressed classes
outputManager.storeCompressed(classes, "$prefix/simple.js")
These parameters should define default values to make it possible to leave them out. Requiring these arguments is typically not a good idea.
When you run the jasy
command you are able to see that all classes are reprocessed for compression. The Jasy cache is modular and stores very specific entries e.g. the exact set of formatting, optmization, etc. of that exact class. So after calling jasy
you know have two compressed results of every class in the cache.
Typically it's not needed to clear the cache at all. Jasy is pretty good at invalidating the cache whenever changes occur. And Jasy automatically clears cache files whenever a version change of Jasy is detected. This means other than for cleaning up there is no real requirement to cleanup the caches at all.
@task
def clean():
"""Cleaning the cache files"""
session.clean()
Jasy can pass data about assets (images, styles, fonts, etc.) to your generated JavaScript files. This happens somewhat automatically based on the needs to the classes. A class which requires specific assets can do this via simple documentation comments:
/**
* #asset(notebook/css/*)
*/
notebook.Application = function() {
core.io.Asset.load(["notebook/css/main.css"], function() {
alert("CSS files was loaded and applied");
});
};
This so-called "asset" hint defines that all assets of the project "notebook" in the folder "css" should be added whenever this class is included. This happens via the already used method storeCompressed
(and also storeLoader
).
Including asset means to add information about the existence of these assets to the client side JavaScript. This is especially useful for preloading assets like images, knowing about image sizes before actually loading them, etc. Asset management also simplifies access to assets and unifies their usage inside the application code. You don't work with absolute or relative paths anymore, but just use assets by the project's name they belong to independently of their current location.
To make loading actually working in your project you have to attach profile(s) to your assets to let them know where to find them. This makes it possible to load assets from different locations, servers, CDNs etc. without changing a single line in your application code.
To use assets in our current project we add a call to assetManager.addBuildProfile()
to our jasyscript.py
:
assetManager = AssetManager(session)
assetManager.addBuildProfile()
outputManager = OutputManager(session, assetManager)
This adds data to your assets so that they will be loaded from a local folder (called asset
by default). This means even assets from other projects will be expected to be loaded from there aka all assets are merged into this single folder. As adding the data is not enough, you also need to copy these assets around. You don't have to do this manually though.
outputManager.deployAssets(["notebook.Application"])
As you can see deployAssets
needs the list of main application classes for deployment. This is how the build script can look like now:
@task
def build(formatting="off"):
"""This is the help text for the build task"""
# Use assets from local asset folder
assetManager = AssetManager(session)
assetManager.addBuildProfile()
# Enable formatting of code when user passes parameter
if formatting == "on":
outputManager = OutputManager(session, assetManager, compressionLevel=0, formattingLevel=1)
else:
outputManager = OutputManager(session, assetManager, compressionLevel=2, formattingLevel=0)
# Copy over all used assets to local asset folder
outputManager.deployAssets(["notebook.Application"])
# Resolving classes
classes = Resolver(session).addClassName("notebook.Application").getSortedClasses()
# Write compressed classes
outputManager.storeCompressed(classes, "$prefix/simple.js")
@task
def clean():
"""Cleaning the cache files"""
session.clean()
If you run that task you now should have the following structure in your project's build
folder:
- build
- script
- kernel.js
- asset
- notebook
- css
main.css
-
kernel.js
contains all classes required bynotebook.Application
- the
asset
folder contains all assets required to run that application class
This means you have an fully standalone, deploy-ready application inside the build
folder now. What might be missing is some kind of HTML
file you are using to open your application from inside the browser. Typically that file is placed in the root folder of your source
folder. You need to copy over that file to the build folder:
@task
def build(formatting="off"):
"""This is the help text for the build task"""
# Use assets from local asset folder
assetManager = AssetManager(session)
assetManager.addBuildProfile()
# Enable formatting of code when user passes parameter
if formatting == "on":
outputManager = OutputManager(session, assetManager, compressionLevel=0, formattingLevel=1)
else:
outputManager = OutputManager(session, assetManager, compressionLevel=2, formattingLevel=0)
# Copy over all used assets to local asset folder
outputManager.deployAssets(["notebook.Application"])
# Copy over files
fileManager = FileManager(session)
fileManager.updateFile("source/index.html", "$prefix/index.html")
# Resolving classes
classes = Resolver(session).addClassName("notebook.Application").getSortedClasses()
# Write compressed classes
outputManager.storeCompressed(classes, "$prefix/simple.js")
@task
def clean():
"""Cleaning the cache files"""
session.clean()
The modification copied over the file source/index.html
to build/index.html
. As already mentioned the destination folder is typically auto prepended based on the name of the current task e.g. build
.
One of the nice features of Jasy is integrated handling for fields and permutations. This means you can pass arbitrary information from the build script to the client and also build different output scripts or structures for different environments/devices/locales.
You can access fields by APIs of the Core Framework:
-
jasy.Env.getValue("fieldName")
=>var
-
jasy.Env.isSet("fieldName", expectedValue)
=>bool
-
jasy.Env.isSet("fieldName")
=>bool
-
jasy.Env.select("fieldName", {expectedValue1: result1, expectedValue2: result2})
=>var
The values will be automatically injected by Jasy like that:
if (jasy.Env.isSet("debug", true)) {
console.log("VAR: ", myvar);
}
If you set debug
to true
that's the result:
if (true) {
console.log("VAR: ", myvar);
}
But that's not all. The dead code optimizer removes all un-reachable code and optimizes all true
cases automatically as well. So the final result for the compressor looks like:
console.log("VAR: ", myvar);
Fields can only be set inside jasyscript.py
and not on the client side anymore. To configure a field you have two approaches:
session.setField("fieldname", fieldvalue)
session.permutateField("fieldname")
The first sets the value of the field to exactly the given value. The second one cycles through all possible values. Possible values could be defined by the jasyproject.json
which defines the field or can be defined in the jasyscript.py
:
session.permutateField("fieldname", valueList, detectionClass, defaultValue)
-
valueList
: A list of values to build e.g.["desktop", "phone", "tablet"]
-
detectionClass
: Class to detect the value of the given field on the client. -
defaultValue
: Value to use whendetectionClass
is not configured or fails with detection.