-
Notifications
You must be signed in to change notification settings - Fork 123
Amber Config
To easily assemble AMD mapping for a loader, such as RequireJS.
To use proven, tested loader / modularization technology which would allow:
- Amber to use Amber packages from multiple sources and to some extent even develop them in parallel (save to multiple locations).
- Amber to cease being in the driver seat and be embeddable to different settings.
- Amber to use external non-Amber libraries from multiple sources.
Before introducing RequireJS in version 0.12 the Amber code base w-s monolithic. Amber needed to be in the driver seat and it was hard to write code in separate modules and use libraries located in different stores. You had to have all the Amber code in one place, similarly to image files of classical Smalltalks.
The main reason for changing the loader was to allow to load, and possible save, Amber packages from different locations. The is to have Amber create its own libraries. This change process began started in version 0.11 by adapting the custom loader to multiple locations.
The amber config
command creates a summary AMD configuration for your projects. This configuration contains
- all the path mappings,
- shim dependencies and
- other pieces of configuration are collected for all parts of your project;
The configuration is saved in a config.js
file. This is more or less just one big require.config
call with all that configuration data passed in.
As a result, if you build an Amber app, you only need to load config.js
to have all namespaces of your app, libraries and Amber itself mapped, shim dependencies set, etc. The only thing you need to do after loading config.js
is to have RequireJS (or other AMD loader) loaded and issue a require
call to load packages of your app.
In addition since produced config.js
contains all the needed mappings in one place, you can use it as mainConfigFile
parameter for the r.js
optimizer, which takes all .js files, .css files and possible also other parts of your app and minimizes and bundles them to one single .js file for deployment.
Whenever you add, update or remove dependencies in your application (including Amber), or you change some piece of local configuration (move your files around), you basically do not need to do much more than just issuing amber config
from CLI. It recreates your configuration. You just have to reload your page.
This scenario can really work: you find some Amber library you want to use. It is published on bower. You do bower install library-i-needed --save
, then you run amber config
. All namespaces that are present in the library as well as all other symbolic names it uses for its dependencies are immediately present in your config.js. You may just start to use the library, by adding it to your require
call in index.html
to load it and you can start writing code that uses it.
This is because an Amber library is highly likely amber config
compliant and have a *.amd.json
manifest file. amber config
uses these files to build config.js
. If you add a JS library which does not have a manifest file, you will need to add it. This needs to be done externally, you should not a it to the directory of the library. This is often a one-time task and does not take more than a few minutes (unless you get stuck on petty details like "what name should I use for this library").
To have namespaces is consequence of:
- Amber packages are AMD module
- the way how path mapping works in AMD.
AMD is about defining modules in form of define ([module_name], array_of_dependency_names, factory_fn);
. That is, a file with AMD module calls define
to register a module, passing list of names for all dependencies, and a function which will instantiate the module once it's needed. No code of the module is actually run until the module is require
d, either directly, or indirectly as a dependency of some require
d module. In this way, modules can be loaded asynchronously, and AMD loader can create the modules from their factory methods in dependency order (require
call is itself also asynchronous: require(array_of_required_module_names, callback_fn)
).
In Smalltalk, packages reference parts of each other a lot. Subclasses are often created from classes of different packages. Extension methods are provided for classes of different packages. As such, a tree of loading dependencies is present in any Amber app.
Since an AMD module must be able to list its dependencies, statically, in a define
call, each package must have its canonical name for AMD. This is the first of the reasons listed above. So far, just using the name of the package itself, maybe with some hard-coded prefix, would be enough. But there's more.
If you build your application from more package groups (Amber core, libraries, your app code itself), you have source code (and its compiled counterpart) from different sources. You do the same thing in Pharo, but there Monticallo takes care of loading them for you and they are then all mixed in to the image file. Amber does not have an image. Web applications are most often composed of pieces coming from different URL locations.
In AMD, your module have symbolic names like jquery
, amber/devel
or helios/Helios-Core
. These symbolic name looks like unix paths - they indeed work as paths, and /
is the path separator. You can config your AMD loader with path mappings - you can map any path prefix into url path. So you may instruct AMD to look for jquery
in bower_components/jquery.min
(.js
should be omitted). If jquery
itself is required, it is assumed to be a file, and bower_components/jquery.min.js
is loaded. But you may also instruct the loader to look for helios
in bower_components/helios
. So if helios/Helios-Core
is required, loader looks for it in bower_components/helios/Helios-Core.js
. In other words, you can map any portion of the path - if used by itself, it is used as a file name (with .js
added automatically), if used as path prefix, it is used accordingly.
This is the second reason listed above. To allow for easy modularization of Amber libraries, each of it can reside in a place of its own - you can map its path prefix to local path or even to URL, and it will work.
For this to work, each Amber library (a set of packages developed and deployed as a group and thus residing in the same location) must have its definitive path prefix that can be mapped. The prefix must be definitive because of the first reason.
The namespace is this prefix.
Each library should have a manifest file called local.amd.json
in its root directory, where all the path mappings and other pieces of configuration are placed in JSON notation. Amber itself has one, Helios has one, your new app created by amber init
(Amber version 0.13) has it.
The function of amber config
The amber config
command takes local.amd.json
files, takes all the mappings and other config infos, and merges them into one big require.config
call written into the config.js
file. All path mappings in local.amd.json
are interpreted relatively to the directory of the library itself, so no matter where you actually put the library in your app directory layout - if it is there, it's local.amd.json
is found, and incorporated into config.js
, with paths properly resolved.
Most often, the library's local.amd.json
is as simple as
{
"paths": {
"library-namespace": "src"
}
}
(btw, this is what default local.amd.json
that amber init
creates for you by default looks like)
That is, it map library-namespace
to local src
directory; this is picked by amber config
so library-namespace
is mapped to path/to/library/src
in config.js
.
No. As mentioned above you can put the library wherever you want inside you application directory tree. The key requirement is that amber config
can find its local.amd.json
. If you want to use any other layout (and ways to get your library code), you can, though bower
is very convenient and solves dependencies for you automatically.
Look at bower.io for more. In essence, it is the repository of client side web packages. Anyone can register a package by pointing to the place where the versions of the package can be found. Each package also lists all its dependencies along with their version constraints - so whenever you install a bower package, all its dependencies are searched for and installed as well in the correct versions. By default, bower
puts all installed packages into a directory called bower_components
.
Amber itself and Helios IDE are both bower packages. Any new application / library you create using amber init
has its bower manifest created already, so you can register it as a bower package as well.
Well, I have a dream: jQuery will include local.amd.json
in its distribution which will map 'jquery' to the appropriate file.
Until that time where all JS libraries will include their local.amd.json
, you should provide manifest files for them. Not by putting local.amd.json
into their directory - this would be destroyed by any update or similar operation. On the contrary, you put dirname.amd.json
out of the library directory - ideally besides your own local.amd.json
. If the library is installed in some/path/dirname
, then dirname.amd.json
will be used just as if it was local.amd.json
inside that directory. This way, you defined .amd.json
externally for any directory holding the external JS library, where you can put the path mappings and often also shim the dependencies (see RequireJS docs for this or other advanced pieces of AMD configuration).
You can check out how Amber and Helios use external JS libraries. They also define their external .amd.json
manifests. So should you if you use such library in your app.
You can, but this introduces unnecessary coupling. In essence, this is just smaller instance of the same problem amber config
tries to solve in big picture - assembling configuration from its local pieces. Putting info on JS library dependency into local.amd.json
itself is an easier way, but makes thing more entangled in the long run. For example, if you remove the library, you should find its setting all over local.amd.json
- in paths
, in shim
, etc. With libdir.amd.json
, you just remove the file and run amber config
.
Until you create one or more namespaces in your app, yes. You can of course have more namespaces in your app. Each can be mapped to a different directory, but they can also be mapped to the same one and you use the different namespace just for separation of concerns (it later allows you to painlessly move files with the same namespace to a different directory or even to different library).