MAM is a modular system that uses a new way of working with code and organizing it. Agnostic means that the module is not locked into any concrete language. A module is a directory that contains files in different languages.
MAM is designed to automate the process of working with modules as much as possible and to remove the routine. Agreements are used instead of configurations. The application code is separated from the development environment code. Only used modules will be added to the bundle. A module is created in one action. The versioning system allows you to maintain only one version of a module - the latest.
Installation and setup VSCode
Update NodeJS to LTS
Clone the repository
git clone https://github.com/hyoo-ru/mam.git ./mam && cd mam
Separate the application/library code from the dev environment
No need to clone the repository for each project. This is done once. MAM separates the developer environment from the application code into a separate repository. In a single environment, you can work on multiple projects in a uniform way. You can update the developer environment centrally for all projects.
Where is the MAM code located?
This repository depends on the NPM package mam. The source of this package is the $mol_build module, located here. All of the MAM logic is implemented in this module. It was one of the first to be created and has long needed refactoring. A prototype of a new version of the builder has been created, but there are no resources to finalize it. Maybe someone would like to help?
How to create a module?
Think of a name and create a folder with this name.
Create the implementation files for this module.
Conventionally, the modules can be divided into three types:
Namespace.
Module.
Submodule.
Namespace
This is the root folder for your modules. You can use the my namespace for your projects and experiments. $mol is used mol. If you will be using MAM in your company, you will have a namespace with the company name. That is, you just create a folder and give it the name you want.
The complete path will be as follows: mam/mol, mam/my.
There is a limitation, at this level you cannot create a file index.html. You need to create a module inside the namespace, and put it there.
Module
Just a folder with the source code
Submodule
You can create modules to an unlimited depth. In practice, there are rarely more than 3-4 levels, if you have more than that, you may have a wrong way of decomposing your code into modules or a very large project.
Creating your first module
Clone the MAM repository, install the NPM dependencies and the plugins for VSCode - instructions above. We will now create a module to be developed during this manual.
Go to mam directory
cd mam
Create namespace
mkdir my && cd my
Create module directory
mkdir counter && cd counter
I usually give my for namespace for my small projects. You can name it whatever you like. In the next step, in the counter module, we will create the files that implement it. Now see what files can make up the module.
What languages/formats can the module consist of?
index.html - Entry point for the web bundle. It specifies the root component for web bundle.
*.web.ts, *.web.jam.js - Web-specific code. Goes into the web bundle.
*.node.ts, *.node.jam.js - Node-specific code, gets into the node bandle.
*.ts, *.jam.js - Common code for both platforms, gets into the web-bundle and into the node bundle.
*.view.tree - Language for declarative description of view-components.
*.view.ts - View-component behavior.
*.locale=*.json - Localized texts.
package.json - Used to publish a module to the NPM.
readme.md - Module documentation, copied to the bundle
How do I name the module files?
Usually the file names, repeat the module names, for example: counter.view.tree, counter.view.ts, counter.view.css - for files in module my/counter. But the builder will scan all the files in the module's directory and find the necessary entities, even if the files have different names. The main thing is to name the entities correctly in the code.
Creating Counter
We will create a Counter app and use it to learn how to use MAM. In the next chapters we will upgrade it.
Create a file mam/my/counter.index.html with the following content:
Based on the components, let's create an application class
class Counter extends View {
// Synchronization with local storage, all application tabs will be synchronized.
storage<Value>( key: string, next?: Value ) {
if ( next === undefined ) return JSON.parse( localStorage.getItem( key ) ?? 'null' )
if ( next === null ) localStorage.removeItem( key )
else localStorage.setItem( key, JSON.stringify( next ) )
return next
}
// This is where we will store the counter value. This method has two-way binding with localStorage
// use `this.count()` for get value
// use `this.count(10)` for set value
// This is called a channel and will be discussed in the chapter on reactivity.
count( next?: number ) {
return this.storage( 'count' , next ) ?? 0
}
// Helper for converting to string and number
count_str( next?: string ) {
return String( this.count(next ? Number(next) : undefined) )
}
// Increment action
inc() {
this.count( this.count() + 1 )
}
// Decrement action
dec() {
this.count( this.count() - 1 )
}
// Create increment button, bind with increment action and cache
_Inc = null as unknown as View
Inc() {
if (this._Inc) return this._Inc
const obj = new Button
obj.title = ()=> '+'
obj.click = ()=> this.inc()
return this._Inc = obj
}
_Dec = null as unknown as View
Dec() {
if (this._Dec) return this._Dec
const obj = new Button
obj.title = ()=> '-'
obj.click = ()=> this.dec()
return this._Dec = obj
}
// Count input binded to count store
_Count = null as unknown as View
Count() {
if (this._Count) return this._Count
const obj = new Input
obj.value = (next?: string)=> this.count_str( next )
return this._Count = obj
}
// children
sub() {
return [
this.Dec(),
this.Count(),
this.Inc(),
]
}
static mount() {
const node = document.querySelector( '#root' )
const obj = new Counter()
node?.replaceWith( obj.dom_tree() )
// Reactivity will be in the next chapter, for now we will use the hack.
setInterval( ()=> obj.dom_tree() , 100 )
}
}
// Don't forget to call the `mount`
Counter.mount()
How to build a module manually?
Just run the command npm start path/to/module. It is possible to run a dev server, described here. When you run the build manually, the builder collects all the files, when the dev server is running, only the few necessary files.
Where do I look for the bundle?
Build artifacts are created in the - folder, which is located in the folder of the module to be built. When creating a repository in .gitignore it is only necessary to add a single line -*.
You can also see three more folders -css, -view.tree, -node - these are intermediate results, they are also created for all modules as needed, which depends on the module being built. In the git settings, you have to ignore everything whose name starts with a minus sign.
What files does the bundle consist of?
You will see the following files:
- web.js - javascript bundle for the browser, css compiles into js bundle
- web.js.map - source maps for js bundle
- web.esm.js - as an esm module
- web.esm.js.map - source maps for esm
- web.test.js - bundle with tests
- web.test.js.map - source maps for test
- web.view.tree - the declarations of all view.tree components included in the bundle
- web.locale=*.json - bundles with localized texts, each language has its own bundle
- web.deps.json - information about the graph of module dependencies
- web.audit.js - information about static typing errors
- web.d.ts - a bundle with types of everything in the js bundle
- node.js - all of the above, but for nodejs
- node.js.map
- node.esm.js
- node.esm.js.map
- node.test.js
- node.test.js.map
- node.view.tree
- node.locale=*.json
- node.deps.json
- node.audit.js
- node.d.ts
- index.html - entry point for launching the module in the browser
- test.html - entry point for running tests in the browser
- README.md - module documentation
- package.json - allows you to publish the assembled module in the NPM
Build Counter application
In MAM you can build any module without prior preparation. Let's build our application.
cd mam
npm start my/counter
After launching, the builder will return an error ReferenceError: document is not defined for the line const node = document.querySelector('#root'). Note the file name node.test.js.
The builder creates all types of bundles at once. The node.test.js bundle runs automatically after the build. The bundle contains all of our code, tests of our code and tests of all the modules our code depends on. It will start even if there are no tests yet at all.
In the application code, there is a run of the `Counter.mount' method. It runs under NodeJS, so it throws an error. Now we will install a hack in this place and fix it later.
Add a line to the beginning of the Counter.mount method.
This additionally loads the file client.js, which establishes a connection to the server via web sockets and reloads the page at the command of the dev server.
And js code that adds the download of the two files via the script tag. The file web.test.js contains code for tests that will run in the browser after each file change.The file web.audit.js contains one line console.info("Audit passed"), in case Typescript finds errors in our code, instead of the line Audit passed the text of errors will be displayed in the browser console.
The readme.md file contains the content of the root file mam/readme.md. If the module does not contain its own readme.md file, it is searched in the parent module, and so on to the root.
In the web.js file you will find our code.
A complete list of files and a short description is given above.
How to run the dev server?
You can run the dev server, and it will automatically rebuild the project when files change, as well as reload the tab in the browser.
cd mam
npm start
A link http://127.0.0.1:9080 will appear in the terminal. Open it, you will see the contents of the mam directory in the file manager. Open the developer tools and on the network tab disable caching. In the file manager, open the project module. At the moment, only modules containing the index.html file are supported.
By opening the folder with the index.html file, the url will contain http://127.0.0.1:9081/my/counter/-/test.html. The path to the module /my/counter, the folder where the build artifacts are put /-/ and the bundle type test.html.
When the browser loads the html file, it will load the js file referenced in the script tag: <script src="web.js"></script>. The browser will make a request at the following url http://127.0.0.1:9081/my/counter/-/web.js.
The dev server analyzes the path to the module and the bundle type, build only this bundle type and returns it in the response. If you start the build manually, the builder will build all types of bundles at once.
Right now the dev server only supports frontend projects. If you create a backend project, you can start the dev server and manually send requests for it to build the required type of bundle - node.js or node.test.js.
Next, use the dev server in practice.
How to import/export modules?
Just write the name of the entity in the code and nothing more. But the name of the entity must be special. It starts with $ and contains the path to the module.
$mol_data_string - means that the file is in mam/mol/data/string. The name of the entity contains the path to the module in the file system.
Such names are called `Fully qualified names - FQN'. You should use FQN names instead of imports and exports.
// mam/my/csv/cvs.ts
function $my_csv_decode( text = 'a;b;c\n1;2;3' ) {
return $mol_csv_parse( text )
}
function $my_csv_encode( list = [['a','b','c'], [1,2,3]] ) {
return list.map(
line => line.map( cell => `"${cell.replace( /"/g, '""' )}"` ).join(';')
).join('\n')
}
Note that we do not have to put decode and encode into separate submodules - mam/my/csv/decode and mam/my/csv/encode. You can declare them in mam/my/csv, as long as the prefix is the same. For example, the name $my_json cannot be used in mam/my/csv, but you can use $my_csv_to_json because the prefix matches. The MAM builder will find the necessary declarations in the code itself.
$using_fully_qualified_names
Initially we just put all the entities in one file, so now let's put them in order and put them in their places.
Only used modules will be included in the bundle. The builder creates a list of dependencies of the module to be built, so recursively for each dependency. The final bundle contains only the modules you actually use. All files and all entities in module files are included in the bundle. Submodules, if not used, are not included.
Let's create a module to work with localStorage. Create a file mam/my/counter/storage/storage.ts with the following contents:
class $my_counter_storage {
static value<Value>( key: string, next?: Value ) {
if ( next === undefined ) return JSON.parse( localStorage.getItem( key ) ?? 'null' )
if ( next === null ) localStorage.removeItem( key )
else localStorage.setItem( key, JSON.stringify( next ) )
return next
}
}
At this point it does not need to be used in $my_counter.
And in the file mam/my/counter/button/button.ts let's add one more class for the minor button. We'll use it later, but for now we'll just add a class. The contents of the file will be as follows:
Build the $my_counter module manually, If you haven't already started the dev server.
npm start my/counter
Check the web.js' bundle. The$my_counter_button_minoris included in the bundle, because this class is declared in the same file as$my_counter_buttonwhich is used in the project.To prevent$my_counter_button_minorfrom being included in the bundle, you need to create a directoryminor` in the module folder, and put the source code of the class there.
Make sure that the class $my_counter_storage are not added to the web.js bundle.
Now let's add the use of $my_counter_storage to the code.
// mam/my/counter/counter.ts
class $my_counter extends $my_counter_view {
// delete
// - storage<Value>( key: string, next?: Value ) {
// - if ( next === undefined ) return JSON.parse( localStorage.getItem( key ) ?? 'null' )
// -
// - if ( next === null ) localStorage.removeItem( key )
// - else localStorage.setItem( key, JSON.stringify( next ) )
// -
// - return next
// - }
count( next?: number ) {
// - return this.storage( 'count' , next ) ?? 0
return $my_counter_storage.value( 'count' , next ) ?? 0 // +
}
After building you will find $my_counter_storage in the bundle.
Cross-language dependencies
All module files are included in the bundle. The modular system is separate from languages, dependencies can be cross-language. The modular system is separate from languages, dependencies can be cross-language.
For example, we have a module with styles for tables. We want them to have one color in a light theme, and another in a dark theme:
The code has been changed a little bit. setInterval is needed to see the changes without reloading the page. And the attribute will change every thirty seconds.
You will now see on the page that the background color changes every thirty seconds. The module $my_theme changes the attribute for the element html - document.documentElement every thirty seconds and the appropriate style applies.
The builder found the module name $my_theme in the counter.css file and added the module $my_theme to the bundle. Find its code in web.js.
After adding the css file, several new modules from the $mol namespace were added to the bundle. Styles are now added to the js bundle using the function $mol_style_attach, find it in web.js. New modules in the bundle are its dependencies.
How do I change the location of a module?
During refactoring, you may need to move the module to another location. Since the names are location-dependent, they need to be changed as well. In all module files FQN-names are written the same way, except *.css - here there is no $ sign in the beginning. You need to run the find and replace command. Example: find my_module_name and replace to my_module_new_name after moved module.
Let's move common modules like $my_counter_view, $my_counter_button from the Counter application to the $my_counter module. And let's leave only the application code in the application.
Create a directory for the $my_lom module
cd my
mkdir lom
Move folder $my_counter_view to $my_lom_view and use find and replace for string "my_counter_view" to "my_lob_view" for mam directory.
Repeat the previous step for $my_counter_button, $my_counter_input, $my_counter_storage, $my_theme.
FQN names everywhere are written in the same style, which allows in one operation of search to find all places where a module is used, in one operation of replacement to change the names when moving a module.
Now all of the library code is in the $my_lom module, and the application code is in the $my_counter module. Make sure that after refactoring the application works!
Monorepo and polyrepo
MAM supports the use of both monorepo and polyrepo at the same time. You can create as many modules as you want in one repository. Or you can tell MAM where to look for the repository, and then when you build the module, it will clone the repositories you want. MAM automatically clones missing repositories when the builder starts.
In order to the builder to clone repository, it needs to know where to clone it from. You need to create a repository for the namespace and create there a file *.meta.tree - example
pack apps git \https://github.com/hyoo-ru/apps.hyoo.ru.git - pack is an instruction indicating that it is a remote repository. The apps is the name of the module. git - version control system (only git is supported now). After \ a link to the repository.
For example, if after installing MAM, you run yarn start hyoo/draw. The builder will look in the root .meta.tree and find the line pack hyoo git \https://github.com/hyoo-ru/mam_hyoo.git there without finding the hyoo folder. It will clone the repository and if it doesn't find the draw module, it will look in the /hyoo/hyoo.meta.tree and find a link to draw there.
For VSCode to work correctly with multiple repositories, create a file mam/.gitmodules with similar content (specify paths for your repositories):
Tip: VSCode does not always immediately show the repository added to .gitmodules on the Source Control tab, to fix this try restart VSCode. Also try changing the order of the modules in .gitmodules.
Let's now put the $my_lom and $my_counter modules into separate git repositories.
Create the mam_my repository on github(or another system).
Create a git repository in the $my module and link with github repository
cd mam/my
echo "# mam_my - namespace for MAM-based projects" >> readme.md
git init
git add readme.md
git commit -m "Init"
git branch -M main
git remote add origin https://github.com/**YOUR_NAME**/mam_my.git
git push -u origin main
Do steps 1 and 2 for the modules $my_lom and $my_counter.
Now delete the module directories $my_lom and $my_counter. And run the builder npm start my/counter, the builder will download the necessary repositories and build the project. You will see a similar log in the terminal:
come
time \2022-03-25T19:42:27.723Z
place \$mol_exec
dir \my/lom
message \Run
command \git init
come
time \2022-03-25T19:42:27.738Z
place \$mol_exec
dir \my/lom
message \Run
command \git remote show https://github.com/**YOUR_NAME**/my_lom.git
come
time \2022-03-25T19:42:28.475Z
place \$mol_exec
dir \my/lom
message \Run
command \git remote add --track main origin https://github.com/**YOUR_NAME**/my_lom.git
come
time \2022-03-25T19:42:28.488Z
place \$mol_exec
dir \my/lom
message \Run
command \git pull
What versioning model does MAM use?
It is called verless or versionless. It works on the open-close principle.
The module always has one version - the latest.
The module can be expanded with new functionality without breaking the API, but only by adding.
The new version of the module, with changes in the API, should be called by a different or similar name($mol_atom -> $mol_atom2 -> $mol_wire_fiber).
Implementation of the old version of the module is replaced by an adapter to the new version.
While the module is under development and its api is unstable, the unstable tag is added to the readme.md
What it gives:
Several versions of the same module can coexist side by side. You can gradually migrate to a new one.
If you use two versions of the same module, the size of the bundle will only increase by the size of the adapter.
The module maintainer and module users focus on one version of the module instead of spreading out their focus over multiple versions.
It is necessary to write tests.
There is currently no means to auto-update modules when multiple repositories are in use. Therefore, it is now necessary to manually perform a `git pull'.
In case the update breaks something, the standard tricks work: fixing the revision, etc.
Splitting code by platform
Two platforms are supported now, the browser and nodejs. The code in the *.web.ts files will only be included in the web bundle. The code in the *.node.ts files will be included only in the node-bandle. Code without platform tag *.ts, will be included in both bundles.
In the previous practice we added a hack to make the project run on NodeJS: if (typeof document === undefined) return. Let's fix that.
First, let's update the $my_lom_view module by adding two static properties to the class.
We moved mount to its place and root is needed to know which component is root. Remove the mount method and its call from $my_counter and add a value setting for $my_lom_view.root there.
This code will only be added to the web.js bundle. The $my_lom_view.mount method call is asynchronous, because in the bundle $my_lom_view is above $my_counter and setting the value for $my_lom_view.root is done after running this code. Make sure this code is in the web.js' bundle and not in thenode.js' bundle.
How to use NPM packages in the node?
On the backend, there is no need to add NPM packages to the bundle. To use NPM packages, there is a special $node module. You simply write $node['is-odd'] or $node.ramda in your code. The MAM builder will automatically install the NPM packages used and add them to the package.json file in the bundle.
Let's add some isomorphism to Counter and refactor the DOM code as well. Create a directory for the module $my_lom_dom_render and in it render.ts file with following content:
// mam/my/lom/dom/render/render.ts
function $my_lom_dom_render(
node : Element | DocumentFragment ,
children: NodeList | Array< Node | string | null >
) {
const node_set = new Set< Node | string | null >( children )
let next = node.firstChild
for (const view of children) {
if (view === null) continue
if (view instanceof Node) {
while(true) {
if (!next) {
node.append(view)
break
}
if (next === view) {
next = next.nextSibling
break;
} else {
if (node_set.has(next)) {
next.before(view)
break;
} else {
const nn = next.nextSibling
next.remove()
next = nn
}
}
}
} else {
if( next && next.nodeName === '#text' ) {
const str = String( view )
if( next.nodeValue !== str ) next.nodeValue = str
next = next.nextSibling
} else {
const text = document.createTextNode( String( view ) )
node.insertBefore( text, next )
}
}
}
while( next ) {
const curr = next
next = curr.nextSibling
curr.remove()
}
}
Remove the $my_lom_view.render method. In the $my_lom_view.dom_tree method, use the $my_lom_dom_render function instead.
In the code for web, this variable will be assigned a window object. Create a file context.node.ts:
// mam/my/lom/context/context.node.ts
$my_lom_dom_context = new $node.jsdom.JSDOM( '' , { url : 'https://localhost/' } ).window as any
any is necessary because jsdom does not implement the full browser api, and its type does not converge to typeof globalThis
In the code for node, this variable is assigned to the instance of the class JSDOM so that under node our application can run. This can be used in tests.
Now wherever the document object is used, you must use $my_lom_dom_context.document.
Run the $my_counter module build - npm start my/counter. If the mam/node_modules folder does not already have jsdom in it, you will see in the terminal the installation log of the npm package jsdom. The builder will also install typescript types, if such a package exists.
> start
> node ./mol/build/-/node.js "my/counter"
come
time \2022-03-25T19:21:46.435Z
place \$mol_exec
dir \
message \Run
command \npm install jsdom
come
time \2022-03-25T19:21:50.155Z
place \$mol_exec
dir \
message \Run
command \npm install @types/jsdom
The mam/my/counter/-/package.json file contains dependencies:
{
"jsdom": "*",
"colorette": "*"
}
The module $node depends on another module which uses the NPM-package colorlette, so this package is here.
The * is used because MAM verless' extends the NPM as well. As a last resort, if the NPM package breaks after an upgrade, you can fix the version inpackage.json`.
The file mam/my/counter/-node/deps.d.ts contains a general list of used modules - NPM and modules with which NodeJS is delivered. It is used for typescript typing.
Create a file module_name.meta.tree, add the line deploy \path/to/file/image.png there. The file will be created in the bundle folder under the same path, i.e. my/module/-/path/to/file/image.png. Example.
Let's add a favicon to the Counter app. Create a directory for the $my_counter_logo module and save the icon there.
In the $my_counter module, create a file counter.meta.tree with this content:
deploy \/my/counter/logo/logo.svg
Build the application module - npm start my/counter. After completion, the file with the icon mam/my/counter/-/my/counter/logo/logo.svg appeared.
Add the line <link href="my/counter/logo/logo.svg" rel="icon"> in the mam/my/counter/index.html file, inside the head tag. After rebuilding, the icon will be displayed.
How to use NPM packages in the frontend?
For the frontend, you need to add the NPM package directly to the bundle.
Install the necessary NPM package and types for it. Create a module for it, in it import this package via require - $lib_react = require('react') as typeof import('react').
Example.
There is a special repository for bindings to NPM packages. It will be better for everyone if you create a pull-request to it at once.
At the moment, the MAM builder itself handles NPM packages and includes their bundle, that can be a problem sometimes. In the future this will be handled by a builder created for NPM packages, such as webpack, etc.
Now you have to install NPM packages manually and there is no auto-update for them.
Now we will not use the Counter application, but add ReactJS to the $lib module and write a demo application. You also add another NPM package to $lib and open a pull-request yourself, as a practice.
First you need to clone the $lib repository, just run its npm start lib build. The link to it is already in mam/.meta.tree, so the builder knows where to find it.
Create a directory and ts file for the module $lib_react.
The namespace are parts of the inversion control system, which we will talk about later.
We simply import react and its types. Put the original object in the $lib_react_all variable. In $lib_react we put the function to create the element to use that name in tsx:
/** @jsx $lib_react */
Part of the entities from React, we pull out into separate variables for faster use.
Now create a submodule for react-dom - $lib_react_dom. It's the same here:
Let's try running a demo application. Start dev-server and open lib/react/demo, don't forget to disable caching. We will see an error:
// Uncaught ReferenceError: process is not defined
// at Object.<anonymous> (react.development.js:12:1)
// at -:2:1
if (process.env.NODE_ENV !== "production") {/*...*/}
The builder does not know how to cut such parts of the code. You need to create a stub to do this. Create the module $lib_react_env.
After sending a commit with this file to the github repository, the deplayer will start immediately. The github action source code is here.
Now let's go back to our Counter app and set it up with autodeploy on github. First, create a personal access token here. Create a secret in the my_counter repository, name it GH_PAT and put the previously created personal access token into it.
Create deploy.yml with following content. Remember to replace YOUR_NAME with your value.
In the meta parameter, a reference to the my namespace repository must be specified.So the script can get the my. meta.tree file and find the $my_lom module repository.
After pushing, the build will begin and once it's finished, you can open the application. You will find the address of the page on the github pages at the link https://github.com/**YOUR_NAME**/my_counter/settings/pages.
How do I include an independent module in the bundle?
The builder automatically adds to the bundle only modules that depend on the module being built. Sometimes you need to add modules on which your code does not directly depend. For example, when you create an application with a catalog of components.
Let's build the $my_lom module and see what goes into the web.js bundle.
npm start my/lom
Open the file mam/my/lom/-/web.js and you will see that there are no modules $my_lom_view, $my_lom_storage and others. But there is something there. Compare the contents of the mam/mam.ts and mam/mam.jam.js files with the contents of the web.js bundle.
Create the file mam/my/lom/lom.ts with the contents from the listing below and run the build again.
console.log('Hello web.js!')
Check web.js again and you will find the code you just added there.
There are a few simple rules:
The module is included in the bundle if there is a link to it. A reference to a module implies the use of one of the FQN names.
The whole module is included in the bundle. All of its files and all of the code in its files will be included in the bundle.
Modules with no dependencies are not included in the bundle.
Modules which are mentioned in the build command will be included in the bundle. For example npm start my/lom/storage - module code $my will be included in the bundle, module code $my_lom will be included in the bundle, module code $my_lom_storage will be included in the bundle. By code we mean its implementation files, the directories of its submodules do not belong to them. And if it has no implementation files, there is nothing to include in the bundle.
Sometimes you need to include a module in the bundle, even if it is not referenced in the module to be built.
Delete the file mam/my/lom/lom.ts. Create a directory for the module $my_lom_lib and a file inside it lib.meta.tree.
include \/my/lom/button
include \/my/lom/dom
include \/my/lom/input
include \/my/lom/lib
include \/my/lom/storage
include \/my/lom/theme
include \/my/lom/view
Build the $my_lom_lib module, run the npm start my/lom/lib command and check the mam/my/lom/lib/-/web.js file. All included modules are added to the bundle.
How to configure module auto-publish in npm?
Let's now publish the $my_lom_lib module on NPM. First, create an account or log in.
npm adduser
# or
npm login
Build the npm start my/lom/lib module, and check the name field in the mam/my/lom/lib/-/package.json file. Everyone who does this manual will have the same package name. Let's add pacakge.json with a different name.
Send the changes to the remote repository, wait for the build, and find the new package on NPM.
Cyclical dependencies
The builder estimates the hardness of the dependencies. The higher the hardness, the earlier the file will be included in the bundle. The hardness is now estimated by the number of indents in the line in which the dependency is located.
Now we will create some js outside of MAM and reproduce the cyclic dependency.
Create somewhere a directory cyclic and in it a file all.mjs with this content:
// cyclic/all.mjs
export class Foo {
get bar() {
return new Bar();
}
}
export class Bar extends Foo {}
console.log(new Foo().bar);
Run this code node app.mjs. There is a cyclic dependency here, but the code works correctly. Now split this code into three files.
// cyclic/foo.mjs
import { Bar } from './bar.mjs';
export class Foo {
get bar() {
return new Bar();
}
}
// cyclic/bar.mjs
import { Foo } from './foo.mjs';
export class Bar extends Foo {}
// cyclic/app.mjs
import { Foo } from './foo.mjs';
console.log(new Foo().bar);
To fix this, we need to add an import for bar.mjs before foo.mjs.
Build the $my_app module and look in the js bundle.
"use strict";
class $my_foo {
get bar() {
return new $my_bar();
}
}
//my/foo/foo.ts
;
"use strict";
class $my_bar extends $my_foo {
}
//my/bar/bar.ts
;
"use strict";
console.log(new $my_foo().bar);
//my/app/app.ts
The builder sticks the files together in the right order, as if we were writing code in the same file. The dependency of $my_bar on $my_foo is stricter than $my_foo on $my_bar, which means that these modules should be included in the bundle in this order: $my_foo, $my_bar, $my_app.
Using CamelCase
FQN-names, you can also write in camelCase style $myModuleHere. This is not canonical, but if you can't retrain to snake_case, this will work for you.
Using JavaScript
You can use native JavaScript for your code. In that case you must use the *.jam.js' extension. In the future, the need to writejam`(JAM - javasacript agnostic module) will go away.
Практика - взять и написать пример из МАМ Сборка фронтенда без боли.
TODO
Рассказать про прием как в mol/view/view/view.ts и в $mol_app_calc интересная штука
добавить автопулл гит репозиториев
добавить автоапдейт NPM-зависимостей
добавить раздел про то как повысить эффективность с помощью mam
добавить высокоуровневое описание принципов и идей
MAM - Mam owns language-Agnostic Modules
What is MAM?
MAM is a modular system that uses a new way of working with code and organizing it. Agnostic means that the module is not locked into any concrete language. A module is a directory that contains files in different languages.
MAM is designed to automate the process of working with modules as much as possible and to remove the routine. Agreements are used instead of configurations. The application code is separated from the development environment code. Only used modules will be added to the bundle. A module is created in one action. The versioning system allows you to maintain only one version of a module - the latest.
Installation and setup VSCode
Update NodeJS to LTS
Clone the repository
Install NPM dependencies
Install VSCode plugins
You can also use Gitpod (install plugins)
Separate the application/library code from the dev environment
No need to clone the repository for each project. This is done once. MAM separates the developer environment from the application code into a separate repository. In a single environment, you can work on multiple projects in a uniform way. You can update the developer environment centrally for all projects.
Where is the MAM code located?
This repository depends on the NPM package mam. The source of this package is the $mol_build module, located here. All of the MAM logic is implemented in this module. It was one of the first to be created and has long needed refactoring. A prototype of a new version of the builder has been created, but there are no resources to finalize it. Maybe someone would like to help?
How to create a module?
Conventionally, the modules can be divided into three types:
Namespace
This is the root folder for your modules. You can use the
my
namespace for your projects and experiments. $mol is usedmol
. If you will be using MAM in your company, you will have a namespace with the company name. That is, you just create a folder and give it the name you want.The complete path will be as follows:
mam/mol
,mam/my
.There is a limitation, at this level you cannot create a file
index.html
. You need to create a module inside the namespace, and put it there.Module
Just a folder with the source code
Submodule
You can create modules to an unlimited depth. In practice, there are rarely more than 3-4 levels, if you have more than that, you may have a wrong way of decomposing your code into modules or a very large project.
Creating your first module
Clone the MAM repository, install the NPM dependencies and the plugins for VSCode - instructions above. We will now create a module to be developed during this manual.
Go to mam directory
Create namespace
Create module directory
I usually give
my
for namespace for my small projects. You can name it whatever you like. In the next step, in thecounter
module, we will create the files that implement it. Now see what files can make up the module.What languages/formats can the module consist of?
index.html
- Entry point for the web bundle. It specifies the root component for web bundle.*.web.ts
,*.web.jam.js
- Web-specific code. Goes into the web bundle.*.node.ts
,*.node.jam.js
- Node-specific code, gets into the node bandle.*.ts
,*.jam.js
- Common code for both platforms, gets into the web-bundle and into the node bundle.*.test.ts
,*.test.jam.js
,*.web.test.ts
,*.web.test.jam.js
,*.node.test.ts
,*.node.test.jam.js
- Tests.*.view.css
,*.css
- CSS.*.view.css.ts
,*.css.ts
- CSS in TS.*.view.tree
- Language for declarative description of view-components.*.view.ts
- View-component behavior.*.locale=*.json
- Localized texts.package.json
- Used to publish a module to the NPM.readme.md
- Module documentation, copied to the bundleHow do I name the module files?
Usually the file names, repeat the module names, for example:
counter.view.tree
,counter.view.ts
,counter.view.css
- for files in modulemy/counter
. But the builder will scan all the files in the module's directory and find the necessary entities, even if the files have different names. The main thing is to name the entities correctly in the code.Creating Counter
We will create a Counter app and use it to learn how to use MAM. In the next chapters we will upgrade it.
Create a file
mam/my/counter.index.html
with the following content:Create a file
mam/my/counter/counter.ts
and add there all the code below:The View class is a wrapper for the DOM node and provides an interface to make working with DOM nodes easier.
Now let's create several components based on the View class
Based on the components, let's create an application class
How to build a module manually?
Just run the command
npm start path/to/module
. It is possible to run a dev server, described here. When you run the build manually, the builder collects all the files, when the dev server is running, only the few necessary files.Where do I look for the bundle?
Build artifacts are created in the
-
folder, which is located in the folder of the module to be built. When creating a repository in.gitignore
it is only necessary to add a single line-*
.You can also see three more folders
-css
,-view.tree
,-node
- these are intermediate results, they are also created for all modules as needed, which depends on the module being built. In the git settings, you have to ignore everything whose name starts with a minus sign.What files does the bundle consist of?
You will see the following files:
Build Counter application
In MAM you can build any module without prior preparation. Let's build our application.
After launching, the builder will return an error
ReferenceError: document is not defined
for the lineconst node = document.querySelector('#root')
. Note the file namenode.test.js
.The builder creates all types of bundles at once. The
node.test.js
bundle runs automatically after the build. The bundle contains all of our code, tests of our code and tests of all the modules our code depends on. It will start even if there are no tests yet at all.In the application code, there is a run of the `Counter.mount' method. It runs under NodeJS, so it throws an error. Now we will install a hack in this place and fix it later.
Add a line to the beginning of the
Counter.mount
method.Run the build again. When finished, look in the
mam/my/counter/-/
directory.You will find
index.html
copied without changes. Filetest.html
with additional code:This additionally loads the file
client.js
, which establishes a connection to the server via web sockets and reloads the page at the command of the dev server.And js code that adds the download of the two files via the script tag. The file
web.test.js
contains code for tests that will run in the browser after each file change.The fileweb.audit.js
contains one lineconsole.info("Audit passed")
, in case Typescript finds errors in our code, instead of the lineAudit passed
the text of errors will be displayed in the browser console.The
readme.md
file contains the content of the root filemam/readme.md
. If the module does not contain its ownreadme.md
file, it is searched in the parent module, and so on to the root.In the
web.js
file you will find our code.A complete list of files and a short description is given above.
How to run the dev server?
You can run the dev server, and it will automatically rebuild the project when files change, as well as reload the tab in the browser.
A link
http://127.0.0.1:9080
will appear in the terminal. Open it, you will see the contents of themam
directory in the file manager. Open the developer tools and on the network tab disable caching. In the file manager, open the project module. At the moment, only modules containing theindex.html
file are supported.By opening the folder with the
index.html
file, the url will containhttp://127.0.0.1:9081/my/counter/-/test.html
. The path to the module/my/counter
, the folder where the build artifacts are put/-/
and the bundle typetest.html
.When the browser loads the
html
file, it will load thejs
file referenced in thescript
tag:<script src="web.js"></script>
. The browser will make a request at the following urlhttp://127.0.0.1:9081/my/counter/-/web.js
.The dev server analyzes the path to the module and the bundle type, build only this bundle type and returns it in the response. If you start the build manually, the builder will build all types of bundles at once.
Right now the dev server only supports frontend projects. If you create a backend project, you can start the dev server and manually send requests for it to build the required type of bundle -
node.js
ornode.test.js
.Next, use the dev server in practice.
How to import/export modules?
Just write the name of the entity in the code and nothing more. But the name of the entity must be special. It starts with
$
and contains the path to the module.$mol_data_string
- means that the file is inmam/mol/data/string
. The name of the entity contains the path to the module in the file system.Such names are called `Fully qualified names - FQN'. You should use FQN names instead of imports and exports.
Note that we do not have to put
decode
andencode
into separate submodules -mam/my/csv/decode
andmam/my/csv/encode
. You can declare them inmam/my/csv
, as long as the prefix is the same. For example, the name$my_json
cannot be used inmam/my/csv
, but you can use$my_csv_to_json
because the prefix matches. The MAM builder will find the necessary declarations in the code itself.$using_fully_qualified_names
Initially we just put all the entities in one file, so now let's put them in order and put them in their places.
The new module structure will look like this:
Create
mam/my/counter/view/view.ts
and move the contents of theView
class into it. And replace the class name with$my_counter_view
.Do the same with the
Button
class:And with the
Input
class:In the file
mam/my/counter/counter.ts
we have only the classCounter
. Now change its name to FQN, and the references to the other classes in it.Just like in the previous practice section, in the
web.js
file you will find our code.What if a module is used many times and its name is too long
Just create an alias for it.
Creating aliases:
Which modules will be included in the bundle?
Only used modules will be included in the bundle. The builder creates a list of dependencies of the module to be built, so recursively for each dependency. The final bundle contains only the modules you actually use. All files and all entities in module files are included in the bundle. Submodules, if not used, are not included.
Let's create a module to work with localStorage. Create a file
mam/my/counter/storage/storage.ts
with the following contents:At this point it does not need to be used in
$my_counter
.And in the file
mam/my/counter/button/button.ts
let's add one more class for the minor button. We'll use it later, but for now we'll just add a class. The contents of the file will be as follows:Build the
$my_counter
module manually, If you haven't already started the dev server.Check the
web.js' bundle. The
$my_counter_button_minoris included in the bundle, because this class is declared in the same file as
$my_counter_buttonwhich is used in the project.To prevent
$my_counter_button_minorfrom being included in the bundle, you need to create a directory
minor` in the module folder, and put the source code of the class there.Make sure that the class
$my_counter_storage
are not added to theweb.js
bundle.Now let's add the use of
$my_counter_storage
to the code.After building you will find
$my_counter_storage
in the bundle.Cross-language dependencies
All module files are included in the bundle. The modular system is separate from languages, dependencies can be cross-language. The modular system is separate from languages, dependencies can be cross-language.
For example, we have a module with styles for tables. We want them to have one color in a light theme, and another in a dark theme:
/my/table/table.css
Note that in the code above
my_theme
isFQN
, incss
you cannot use the$
sign, so an exception applies here and it is not used/my/theme/theme.js
The $my_table depends on $my_theme. $my_theme will include in the bundle, and will change color depending on the time of day.
Let's add the example above to our application. Create a module
$my_theme
with the filetheme.ts
and add the following code to it:The code has been changed a little bit.
setInterval
is needed to see the changes without reloading the page. And the attribute will change every thirty seconds.Create a
css
file for the$my_counter
module.You will now see on the page that the background color changes every thirty seconds. The module
$my_theme
changes the attribute for the elementhtml
-document.documentElement
every thirty seconds and the appropriate style applies.The builder found the module name
$my_theme
in thecounter.css
file and added the module$my_theme
to the bundle. Find its code inweb.js
.After adding the css file, several new modules from the
$mol
namespace were added to the bundle. Styles are now added to the js bundle using the function$mol_style_attach
, find it inweb.js
. New modules in the bundle are its dependencies.How do I change the location of a module?
During refactoring, you may need to move the module to another location. Since the names are location-dependent, they need to be changed as well. In all module files FQN-names are written the same way, except
*.css
- here there is no$
sign in the beginning. You need to run thefind and replace
command. Example: findmy_module_name
and replace tomy_module_new_name
after moved module.Let's move common modules like
$my_counter_view
,$my_counter_button
from the Counter application to the$my_counter
module. And let's leave only the application code in the application.Create a directory for the
$my_lom
moduleMove folder $my_counter_view to $my_lom_view and use find and replace for string "my_counter_view" to "my_lob_view" for mam directory.
Repeat the previous step for $my_counter_button, $my_counter_input, $my_counter_storage, $my_theme.
FQN names everywhere are written in the same style, which allows in one operation of search to find all places where a module is used, in one operation of replacement to change the names when moving a module.
Now all of the library code is in the $my_lom module, and the application code is in the $my_counter module. Make sure that after refactoring the application works!
Monorepo and polyrepo
MAM supports the use of both monorepo and polyrepo at the same time. You can create as many modules as you want in one repository. Or you can tell MAM where to look for the repository, and then when you build the module, it will clone the repositories you want. MAM automatically clones missing repositories when the builder starts.
In order to the builder to clone repository, it needs to know where to clone it from. You need to create a repository for the namespace and create there a file
*.meta.tree
- examplepack apps git \https://github.com/hyoo-ru/apps.hyoo.ru.git
-pack
is an instruction indicating that it is a remote repository. Theapps
is the name of the module.git
- version control system (only git is supported now). After\
a link to the repository.For example, if after installing MAM, you run
yarn start hyoo/draw
. The builder will look in the root.meta.tree
and find the linepack hyoo git \https://github.com/hyoo-ru/mam_hyoo.git
there without finding thehyoo
folder. It will clone the repository and if it doesn't find thedraw
module, it will look in the/hyoo/hyoo.meta.tree
and find a link todraw
there.For VSCode to work correctly with multiple repositories, create a file
mam/.gitmodules
with similar content (specify paths for your repositories):Tip: VSCode does not always immediately show the repository added to
.gitmodules
on theSource Control
tab, to fix this try restart VSCode. Also try changing the order of the modules in.gitmodules
.Let's now put the $my_lom and $my_counter modules into separate git repositories.
mam_my
repository on github(or another system).$my_lom
and$my_counter
.cd mam/my/counter echo "# mam_counter" >> readme.md echo "-*" >> .gitignore git init git add --all git commit -m "Init" git branch -M main git remote add origin https://github.com/**YOUR_NAME**/my_counter.git git push -u origin main
my.meta.tree
Now delete the module directories
$my_lom
and$my_counter
. And run the buildernpm start my/counter
, the builder will download the necessary repositories and build the project. You will see a similar log in the terminal:What versioning model does MAM use?
It is called verless or versionless. It works on the open-close principle.
$mol_atom
->$mol_atom2
->$mol_wire_fiber
).unstable
tag is added to the readme.mdWhat it gives:
There is currently no means to auto-update modules when multiple repositories are in use. Therefore, it is now necessary to manually perform a `git pull'.
In case the update breaks something, the standard tricks work: fixing the revision, etc.
Splitting code by platform
Two platforms are supported now, the browser and nodejs. The code in the
*.web.ts
files will only be included in the web bundle. The code in the*.node.ts
files will be included only in the node-bandle. Code without platform tag*.ts
, will be included in both bundles.In the previous practice we added a hack to make the project run on NodeJS:
if (typeof document === undefined) return
. Let's fix that.First, let's update the
$my_lom_view
module by adding two static properties to the class.We moved
mount
to its place androot
is needed to know which component is root. Remove themount
method and its call from$my_counter
and add a value setting for$my_lom_view.root
there.Create a file
view.web.ts
in the module$mol_lom_view
with this content:This code will only be added to the
web.js
bundle. The$my_lom_view.mount
method call is asynchronous, because in the bundle$my_lom_view
is above$my_counter
and setting the value for$my_lom_view.root
is done after running this code. Make sure this code is in theweb.js' bundle and not in the
node.js' bundle.How to use NPM packages in the node?
On the backend, there is no need to add NPM packages to the bundle. To use NPM packages, there is a special
$node
module. You simply write$node['is-odd']
or$node.ramda
in your code. The MAM builder will automatically install the NPM packages used and add them to thepackage.json
file in the bundle.Let's add some isomorphism to Counter and refactor the DOM code as well. Create a directory for the module
$my_lom_dom_render
and in itrender.ts
file with following content:Remove the
$my_lom_view.render
method. In the$my_lom_view.dom_tree
method, use the$my_lom_dom_render
function instead.The
$my_lom_view
class is now a decent size, and$my_lom_dom_render
can be reused.Create a directory for the
$my_lom_dom_context
module and acontext.ts
file with this content:In the general code for
web
andnode
we simply declare the variable and its type. Create a filecontext.web.ts
:In the code for
web
, this variable will be assigned awindow
object. Create a filecontext.node.ts
:any
is necessary becausejsdom
does not implement the full browser api, and its type does not converge totypeof globalThis
In the code for
node
, this variable is assigned to the instance of the classJSDOM
so that undernode
our application can run. This can be used in tests.Now wherever the
document
object is used, you must use$my_lom_dom_context.document
.Run the
$my_counter
module build -npm start my/counter
. If themam/node_modules
folder does not already havejsdom
in it, you will see in the terminal the installation log of the npm packagejsdom
. The builder will also installtypescript
types, if such a package exists.The
mam/my/counter/-/package.json
file contains dependencies:The module
$node
depends on another module which uses the NPM-packagecolorlette
, so this package is here.The
*
is used because MAMverless' extends the NPM as well. As a last resort, if the NPM package breaks after an upgrade, you can fix the version in
package.json`.The file
mam/my/counter/-node/deps.d.ts
contains a general list of used modules - NPM and modules with which NodeJS is delivered. It is used fortypescript
typing.How to add any files to the bundle?
Create a file
module_name.meta.tree
, add the linedeploy \path/to/file/image.png
there. The file will be created in the bundle folder under the same path, i.e.my/module/-/path/to/file/image.png
. Example.Let's add a favicon to the Counter app. Create a directory for the
$my_counter_logo
module and save the icon there.In the
$my_counter
module, create a filecounter.meta.tree
with this content:Build the application module -
npm start my/counter
. After completion, the file with the iconmam/my/counter/-/my/counter/logo/logo.svg
appeared.Add the line
<link href="my/counter/logo/logo.svg" rel="icon">
in themam/my/counter/index.html
file, inside thehead
tag. After rebuilding, the icon will be displayed.How to use NPM packages in the frontend?
For the frontend, you need to add the NPM package directly to the bundle.
Install the necessary NPM package and types for it. Create a module for it, in it import this package via
require
-$lib_react = require('react') as typeof import('react')
. Example.There is a special repository for bindings to NPM packages. It will be better for everyone if you create a pull-request to it at once.
At the moment, the MAM builder itself handles NPM packages and includes their bundle, that can be a problem sometimes. In the future this will be handled by a builder created for NPM packages, such as webpack, etc.
Now you have to install NPM packages manually and there is no auto-update for them.
Now we will not use the Counter application, but add
ReactJS
to the $lib module and write a demo application. You also add another NPM package to $lib and open a pull-request yourself, as a practice.First you need to clone the
$lib
repository, just run itsnpm start lib
build. The link to it is already inmam/.meta.tree
, so the builder knows where to find it.Create a directory and
ts
file for the module$lib_react
.The
namespace
are parts of the inversion control system, which we will talk about later.We simply import
react
and its types. Put the original object in the$lib_react_all
variable. In$lib_react
we put the function to create the element to use that name intsx
:Part of the entities from React, we pull out into separate variables for faster use.
Now create a submodule for
react-dom
-$lib_react_dom
. It's the same here:Now create a small demo application -
$lib_react_demo
.Let's try running a demo application. Start dev-server and open
lib/react/demo
, don't forget to disable caching. We will see an error:The builder does not know how to cut such parts of the code. You need to create a stub to do this. Create the module
$lib_react_env
.We need this code to be in the bundle before the react and to be run before the react. Let's place a link to this function before react imports it.
Check the code order in
web.js
. There will be a function$lib_react_env
and run it. Then the react code and after it the code from that file.Refresh the page, the demo app now works. The adapter to the react still needs modifications, but a start has been made.
How to set up a Deploy to github pages?
You need to create a file
.github/workflows/deploy.yml
in the module to be deployed. Below is a sample config.After sending a commit with this file to the github repository, the deplayer will start immediately. The github action source code is here.
Now let's go back to our Counter app and set it up with autodeploy on github. First, create a personal access token here. Create a secret in the
my_counter
repository, name itGH_PAT
and put the previously created personal access token into it.Create
deploy.yml
with following content. Remember to replace YOUR_NAME with your value.In the
meta
parameter, a reference to themy
namespace repository must be specified.So the script can get themy. meta.tree
file and find the$my_lom
module repository.After pushing, the build will begin and once it's finished, you can open the application. You will find the address of the page on the github pages at the link
https://github.com/**YOUR_NAME**/my_counter/settings/pages
.How do I include an independent module in the bundle?
The builder automatically adds to the bundle only modules that depend on the module being built. Sometimes you need to add modules on which your code does not directly depend. For example, when you create an application with a catalog of components.
Let's build the
$my_lom
module and see what goes into theweb.js
bundle.Open the file
mam/my/lom/-/web.js
and you will see that there are no modules$my_lom_view
,$my_lom_storage
and others. But there is something there. Compare the contents of themam/mam.ts
andmam/mam.jam.js
files with the contents of theweb.js
bundle.Create the file
mam/my/lom/lom.ts
with the contents from the listing below and run the build again.Check
web.js
again and you will find the code you just added there.There are a few simple rules:
npm start my/lom/storage
- module code$my
will be included in the bundle, module code$my_lom
will be included in the bundle, module code$my_lom_storage
will be included in the bundle. By code we mean its implementation files, the directories of its submodules do not belong to them. And if it has no implementation files, there is nothing to include in the bundle.Sometimes you need to include a module in the bundle, even if it is not referenced in the module to be built.
Delete the file
mam/my/lom/lom.ts
. Create a directory for the module$my_lom_lib
and a file inside itlib.meta.tree
.Build the
$my_lom_lib
module, run thenpm start my/lom/lib
command and check themam/my/lom/lib/-/web.js
file. All included modules are added to the bundle.How to configure module auto-publish in npm?
Let's now publish the
$my_lom_lib
module on NPM. First, create an account or log in.Build the
npm start my/lom/lib
module, and check thename
field in themam/my/lom/lib/-/package.json
file. Everyone who does this manual will have the same package name. Let's addpacakge.json
with a different name.The fields from the manually added
package.json
will be merged with the fields of the generatedpackage.json
Build the module again and check the
package.json
file in the bundle - the name has changed. Now check that this name is free in the NPM.If it's busy, change it to your own and go on.
Now we need a personal access token for NPM.
Create a secret in the
my_lom
repository namedNPM_AUTH_TOKEN
.Create a file
mam/my/lom/.github/workflows/my_lom_lib.yml
with this content:Send the changes to the remote repository, wait for the build, and find the new package on NPM.
Cyclical dependencies
The builder estimates the hardness of the dependencies. The higher the hardness, the earlier the file will be included in the bundle. The hardness is now estimated by the number of indents in the line in which the dependency is located.
Now we will create some
js
outside of MAM and reproduce the cyclic dependency.Create somewhere a directory
cyclic
and in it a fileall.mjs
with this content:Run this code
node app.mjs
. There is a cyclic dependency here, but the code works correctly. Now split this code into three files.To fix this, we need to add an import for
bar.mjs
beforefoo.mjs
.Now let's move this code to MAM. Create three modules
$my_foo
,$my_bar
,$my_app
.Build the
$my_app
module and look in thejs
bundle.The builder sticks the files together in the right order, as if we were writing code in the same file. The dependency of
$my_bar
on$my_foo
is stricter than$my_foo
on$my_bar
, which means that these modules should be included in the bundle in this order:$my_foo
,$my_bar
,$my_app
.Using CamelCase
FQN-names, you can also write in camelCase style
$myModuleHere
. This is not canonical, but if you can't retrain to snake_case, this will work for you.Using JavaScript
You can use native JavaScript for your code. In that case you must use the
*.jam.js' extension. In the future, the need to write
jam`(JAM - javasacript agnostic module) will go away.Практика - взять и написать пример из МАМ Сборка фронтенда без боли.
TODO
Рассказать про прием как в mol/view/view/view.ts и в $mol_app_calc интересная штука
добавить автопулл гит репозиториев
добавить автоапдейт NPM-зависимостей
добавить раздел про то как повысить эффективность с помощью mam
добавить высокоуровневое описание принципов и идей