-
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 0.12, Amber has monolithic nature - it had to be in the driver seat and it was hard to write code in separate modules and libraries located in different stores - you had to have all the Amber code in one place, similarly to images of classical Smalltalks.
The main 'why' for changing the loader (which began on 0.11 already by adapting the custom loader to multiple locations) was to allow to load, and possible save, Amber packages from different locations. This would allow creating Amber libraries of its own.
It creates a summary AMD configuration for your projects, where all the path mappings, shim dependencies and other pieces of configuration are collected for all parts of your project; and saves them in a config.js
file, which more or less consist only of one big require.config
call with all that configuration 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.
On the plus side, since produced config.js
contains all the needed mappings at one place, you can use it as mainConfigFile
parameter in 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 so it is actual, so you can just 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 Amber library is highly likely amber config
compliant and have *.amd.json
manifest files. amber config
uses them to build config.js
. In case of adding JS library which does not have manifest files in it, you will need to add them (externally, not writing them in the directory of the library) yourself, which is often 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").
Having namespace is consequence of:
- Amber packages being AMD modules now, and of
- 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 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. Amber does not have image; and web app are often composed from different pieces from different 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 you 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 config needed are placed, in JSON notation. Amber itself has one, Helios has one, your new app create by amber init
for 0.13 version of Amber has it.
amber config
, shortly speaking, just takes those local.amd.json
files, takes all the mappings and other config infos, and merges them in one big require.config
call written into config.js
. 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
.
Absolutely not. As already said in previous answer, you can put the library whenever you want inside you app directory tree. The key 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 depndencies for you automatically.
Look at bower.io for more. In essence, it is the repository of web packages. Anyone can register a package by pointing a place where the versions of the package can be found. Each package also list all its dependencies along with their version constraints - so whenever you install bower package, all its dependencies are looked for and installed as well in correct versions. By default, bower
puts all installed packages into directory 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 appropriate file.
Until the time where all JS libraries will include their local.amd.json
, you should provide manifest file for them. Not by putting local.amd.json
into their directory - this would be destroyed by and 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 see for yourself that Amber and Helios use external JS libraries themselves, and 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 on local.amd.json
itself is easier way, but makes thing more entangled. For example, if you remove the library, you should find it 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 more namespace in your app, yes. You can of course have more namespaces in your app. Each can be mapped to different directory, but they can also be mapped into same one and you use the different namespace just for separation of concerns (it later allows you to painlessly move files with same namespace to different dir or even to different library).