• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
JavaScript Modules Done Right
 

JavaScript Modules Done Right

on

  • 6,448 views

JavaScript Modules Done Right

JavaScript Modules Done Right

Client & server-side code organization

Statistics

Views

Total Views
6,448
Views on SlideShare
6,443
Embed Views
5

Actions

Likes
11
Downloads
63
Comments
8

2 Embeds 5

https://twitter.com 3
http://a0.twimg.com 2

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel

18 of 8 previous next Post a comment

  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
  • @r4j4h I don't do much white-box testing, on my side it happens only if one of my modules is considered to be internal (filename prefixed with underscore) and I test it individually. I never try or provide means to access some module internals directly, however what's important my modules are rather small, I don't try to fit something complex into one module, it's then hard to maintain (and test).

    Honestly I don't believe in white-box testing approach (at least so far I didn't have a need for it). You should try to achieve 100% code coverage just by testing public API, if you can't it just means you have a dead code. Other benefit of that approach is that you don't have to rewrite tests when you refactor internals without changing API, it's a big win as instantly you know whether your refactor works as expected.
    Are you sure you want to
    Your message goes here
    Processing…
  • Great presentation!!

    However, I would like to ask, how do you handle testing? Do you have a way of accomplishing white box testing? It seems to me that most people seem to simply accept black box testing with Harmony-esque modules. And if so, how? Do you export everything from each module?

    I lately am tending towards the namespace approach for this reason, private state can be accomplished Google/Python style with underscores and provides 100% testability with ease. Besides this, there are no real downsides other than the forethought and consideration required for the namespacing (you can't pull your 'modules' into a local variable, although technically you could pull them in, link local var to their namespace, then delete their namespace I guess). But even modules experience this to a degree in their naming.

    Look forward to your feedback, testability is the biggest blocker for me to adopt these types of modules. :)
    Are you sure you want to
    Your message goes here
    Processing…
  • @Patrick

    I think you missed the point. What I've stated is that:
    1. AMD doesn't work in Node.js natively and it's not made with server-side in mind. It's clearly just client-side solution.
    2. I don't see any point in using Almond when we can write modules Node.js style.
    Are you sure you want to
    Your message goes here
    Processing…
  • RequireJS doesn't need a complie step, so it's useful in development and testing. Almond is a trivially small loader with only the functionality that's necessary in production with concatenated files.

    I don't know how well AMD works with Node, but it's a good choice when compatibility with Node isn't a priority.
    Are you sure you want to
    Your message goes here
    Processing…
  • @geddesign,

    The thing that I could point better is: 'We cannot use our modules on server-side (Node.js) without extra compilation step', as indeed instead of compilation you can use either bootstrap or other tools. So that's indeed inaccuracy, I'm sorry about that

    Still situation is clear:

    1. Plain AMD modules won't run in Node.js, so 'Node.js can't run them properly'
    2. but you can run AMD modules in Node.js with help of additional tools or wrappers

    Node.js and AMD modules are very different and incompatible, you can somehow made AMD module working in node, but you can't seamlessly mix both systems without any limitations it's just not possible.
    I think it's unfair to diminish those facts

    My other point was, that I don't see any advantage of AMD when using synchronously, for synchronous load CommonJS/Node.js modules are much more powerful and cleaner concept.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    JavaScript Modules Done Right JavaScript Modules Done Right Presentation Transcript

    • JavaScript ModulesDone rightClient & server-side code organization meetjs · November 2011 · Warsaw, Poland
    • Mariusz Nowakmariusz@medikoo.com @medikoo github.com/medikooJavaScript Programmer at Roche Ltd.
    • BrowserIn the beginning there was Browser.How we managed large code bases (?)
    • BrowserIn the beginning there was Browser.How we managed large code bases (?)• Files concatenation
    • BrowserWritten usually this style:MAIN.module = (function() { var privateVar = ..; var privateMethod = function (args) { // ... }; return { publicProperty: .., publicMethod: function () { // ... } };}());
    • BrowserIn the beginning there was Browser.How we managed large code bases (?)• Files concatenation
    • BrowserIn the beginning there was Browser.How we managed large code bases (?)• Files concatenation• Module pattern
    • ServerBeginning of 2009• Growing number of interpreters: Rhino, Spidermonkey, V8, JSCore• No cross-interpreter standards• There are programmers who want to write server-side JavaScript butwant to have their code runnable on any engine
    • ServerBeginning of 2009• Growing number of interpreters: Rhino, Spidermonkey, V8, JSCore• No cross-interpreter standards• There are programmers who want to write server-side JavaScript butwant to have their code runnable on any engine• There’s a need for standard library API that would allow JavaScriptecosystem grow (as it happened for Ruby, Python or Java)
    • ServerBeginning of 2009• Growing number of interpreters: Rhino, Spidermonkey, V8, JSCore• No cross-interpreter standards• There are programmers who want to write server-side JavaScript butwant to have their code runnable on any engine• There’s a need for standard library API that would allow JavaScriptecosystem grow (as it happened for Ruby, Python or Java)• ServerJS initiative is announced, renamed later into CommonJS-> http://www.blueskyonmars.com/2009/01/29/what-server-side-javascript-needs/
    • ServerCommonJS presents Modules specification ->http://www.commonjs.org/specs/modules/1.0/
    • ServerCommonJS presents Modules specification ->http://www.commonjs.org/specs/modules/1.0/add.jsexports.add = function() { var sum = 0, i = 0, args = arguments, l = args.length; while (i < l) sum += args[i++]; return sum;};increment.jsvar add = require(add).add;exports.increment = function(val) { return add(val, 1);};program.jsvar inc = require(increment).increment;var a = 1;inc(a); // 2
    • ServerIn about same time Ryan Dahl creates Node.js, whichimplements CommonJS Modulesadd.jsexports.add = function() { var sum = 0, i = 0, args = arguments, l = args.length; while (i < l) sum += args[i++]; return sum;};increment.jsvar add = require(add).add;exports.increment = function(val) { return add(val, 1);};program.jsvar inc = require(increment).increment;var a = 1;inc(a); // 2
    • ServerIn about same time Ryan Dahl creates Node.js, whichimplements CommonJS Modules... with some improvementsadd.jsmodule.exports = function() { var sum = 0, i = 0, args = arguments, l = args.length; while (i < l) sum += args[i++]; return sum;};increment.jsvar add = require(./add);module.exports = function(val) { return add(val, 1);};program.jsvar inc = require(./increment);var a = 1;inc(a); // 2
    • ServerHow module works: // increment.js var add = require(./add); module.exports = function (val) { return add(val, 1); };
    • ServerHow module works:var exports, module;function (exports, require, module) { // increment.js var add = require(./add); module.exports = function (val) { return add(val, 1); };}.call(exports = {}, exports, function (path) { // import external module}, module = { exports: exports });MODULE = module.exports;
    • ServerLet’s get back to module pattern.
    • ServerLet’s get back to module pattern.module.jsMAIN.module = (function() { var privateVar = ..; var privateMethod = function (args) { // ... }; return { publicProperty: .., publicMethod: function () { // ... } };}());
    • ServerModule pattern as CommonJS module:module.jsvar privateVar = ..;var privateMethod = function (args) { // ...};exports.publicProperty: ..,exports.publicMethod: function () { // ...};program.jsvar foobar = require(‘./module’);
    • ServerModule pattern as CommonJS module:module.jsvar privateVar = ..;var privateMethod = function (args) { // ...};exports.publicProperty: ..,exports.publicMethod: function () { // ...};program.jsvar foobar = require(‘./module’); ↑ we decide locally under which name we’ll access imported module
    • ServerCommonJS Modules is indeed great specification.What are the benefits ?
    • ServerCommonJS Modules is indeed great specification.What are the benefits ?First of all, cleanliness and encapsulation on highestavailable (for JavaScript) level
    • ServerCommonJS Modules is indeed great specification.What are the benefits ?First of all, cleanliness and encapsulation on highestavailable (for JavaScript) level• There’s no need to write obsolete function wrappers to work in privatescope
    • ServerCommonJS Modules is indeed great specification.What are the benefits ?First of all, cleanliness and encapsulation on highestavailable (for JavaScript) level• There’s no need to write obsolete function wrappers to work in privatescope• We don’t have to deal with long namespaces, each required module isassigned to local variable
    • ServerCommonJS Modules is indeed great specification.What are the benefits ?First of all, cleanliness and encapsulation on highestavailable (for JavaScript) level• There’s no need to write obsolete function wrappers to work in privatescope• We don’t have to deal with long namespaces, each required module isassigned to local variable• We can build large complex application without a need to touch globalnamespace
    • Server & BrowserHow to run it in the Browser ?
    • Server & BrowserHow to run it in the Browser ?When I started working with CommonJS and Node.js(beginning 2011) I thought there’s solid solution alreadyavailable
    • Server & BrowserHow to run it in the Browser ?When I started working with CommonJS and Node.js(beginning 2011) I thought there’s solid solution alreadyavailableI’ve found about 8-10 solutions that aimed at somethingsimilar, however none of them were really working right
    • Server & BrowserMain issue was that each of them, was trying to besomething more than Node.js modules parser, and “more”seemed to have more attention than dependency parser Iwas after.
    • Server & BrowserMain issue was that each of them, was trying to besomething more than Node.js modules parser, and “more”seemed to have more attention than dependency parser Iwas after.Parsers were incomplete and buggy
    • Server & BrowserAnyway, time goes by, there are few solutions thatdeserve focus
    • Server & BrowserAnyway, time goes by, there are few solutions thatdeserve focusBrowserify -> https://github.com/substack/node-browserify
    • Server & BrowserAnyway, time goes by, there are few solutions thatdeserve focusBrowserify -> https://github.com/substack/node-browserifyVery interesting tool, if we’re after reusing native Node.js modules onclient-side. Browserify struggles to make it possible, I guess it was mainintention behind the project.
    • Server & BrowserAnyway, time goes by, there are few solutions thatdeserve focusBrowserify -> https://github.com/substack/node-browserifyVery interesting tool, if we’re after reusing native Node.js modules onclient-side. Browserify struggles to make it possible, I guess it was mainintention behind the project.You need to add extra 320 lines of code for client-side to make it work,that’s not impressive, this code could be much smaller.
    • Server & BrowserAnyway, time goes by, there are few solutions thatdeserve focusBrowserify -> https://github.com/substack/node-browserifyVery interesting tool, if we’re after reusing native Node.js modules onclient-side. Browserify struggles to make it possible, I guess it was mainintention behind the project.You need to add extra 320 lines of code for client-side to make it work,that’s not impressive, this code could be much smaller.It doesn’t work well with some paths, modules from external packageswere not found when I played with that (not sure if it’s bug or limitation)
    • Server & BrowserAMD -> Asynchronous Module Definition
    • Server & BrowserAMD -> Asynchronous Module DefinitionIt gained some attention lately:http://addyosmani.com/writing-modular-js/http://blog.millermedeiros.com/2011/09/amd-is-better-for-the-web-than-commonjs-modules/http://unscriptable.com/index.php/2011/09/30/amd-versus-cjs-whats-the-best-format/
    • Server & BrowserAMD ?
    • Server & BrowserAMD ?It was made for browsers - Node.js won’t run AMD module properly
    • Server & BrowserAMD ?It was made for browsers - Node.js won’t run AMD module properly
    • Server & BrowserAMD ?It was made for browsers - Node.js won’t run AMD module properlyAMD has not much to do with CommonJS Modules, you need towrite modules differently
    • Server & BrowserAMD ?It was made for browsers - Node.js won’t run AMD module properlyAMD has not much to do with CommonJS Modules, you need towrite modules differently• All dependencies needs to be declared at begin of module
    • Server & BrowserAMD ?It was made for browsers - Node.js won’t run AMD module properlyAMD has not much to do with CommonJS Modules, you need towrite modules differently• All dependencies needs to be declared at begin of moduleIn CommonJS we can reach for external modules in any place at any time, and it’ssame for native modules that JavaScript will have in a future.
    • Server & BrowserAMD ?It was made for browsers - Node.js won’t run AMD module properlyAMD has not much to do with CommonJS Modules, you need towrite modules differently• All dependencies needs to be declared at begin of moduleIn CommonJS we can reach for external modules in any place at any time, and it’ssame for native modules that JavaScript will have in a future.• Again, we need to use function wrappers. Each module needs to be declared withfunction call
    • Server & BrowserWhy is that ?
    • Server & BrowserWhy is that ?AMD tries to be solution for two problems:• Code modularization and organization• Dynamic dependency handling, so we load only thatcode that is actually used
    • Server & BrowserWhy is that ?AMD tries to be solution for two problems:• Code modularization and organization• Dynamic dependency handling, so we load only thatcode that is actually usedSounds promising, but is it worth it ?
    • Server & BrowserHow AMD modules look like ?
    • Server & BrowserHow AMD modules look like ?add.jsdefine(function () { return function() { var sum = 0, i = 0, args = arguments, l = args.length; while (i < l) sum += args[i++]; return sum; };});increment.jsdefine([add], function (add) { return function(val) { return add(val, 1); };});program.jsdefine([increment], function (inc) { var a = 1; inc(a); // 2});
    • Server & BrowserMost popular implementation -> http://requirejs.org/requires us to load library made of 2000 lines of code.
    • Server & BrowserMost popular implementation -> http://requirejs.org/requires us to load library made of 2000 lines of code.... it’s needed just to run code of our application.
    • Server & BrowserMost popular implementation -> http://requirejs.org/requires us to load library made of 2000 lines of code.... it’s needed just to run code of our application.... and still we cannot use our modules on server-side(Node.js) without extra compilation step.
    • Server & BrowserOk, but what about dynamic dependency resolution, isn’t itmain purpose of AMD ?
    • Server & BrowserOk, but what about dynamic dependency resolution, isn’t itmain purpose of AMD ?In my opinion dynamic dependency resolution should beconsidered on application functionality level not onmodules level
    • Server & BrowserSummary:
    • Server & BrowserSummary:We miss light and clean CommonJS (precisely Node.js) Modulesimplementation, that would allow us to use them on client side
    • Server & BrowserSummary:We miss light and clean CommonJS (precisely Node.js) Modulesimplementation, that would allow us to use them on client sideImplementation that wouldn’t try to be solution for otherproblems in first place.
    • Server & BrowserSummary:We miss light and clean CommonJS (precisely Node.js) Modulesimplementation, that would allow us to use them on client sideImplementation that wouldn’t try to be solution for otherproblems in first place.Code for handling modules on client side should be minimal,unnoticeable in size when compared to application code
    • Server & BrowserFinally new project was born:
    • Server & BrowserFinally new project was born:modules-webmake ->https://github.com/medikoo/modules-webmakenpm install -g webmake
    • Modules Webmakemodules-webmake scans modules, builds dependency tree andproduces output that it executable in the browser
    • Modules Webmakemodules-webmake scans modules, builds dependency tree andproduces output that it executable in the browserCurrent implementation is very basic, we might say, we just started
    • Modules Webmakemodules-webmake scans modules, builds dependency tree andproduces output that it executable in the browserCurrent implementation is very basic, we might say, we just startedIt doesn’t limit us though from building complex applications madefrom hundreds of modules that originated from dozens of packages
    • Modules Webmakemodules-webmake scans modules, builds dependency tree andproduces output that it executable in the browserCurrent implementation is very basic, we might say, we just startedIt doesn’t limit us though from building complex applications madefrom hundreds of modules that originated from dozens of packagesWith modules-webmake you can build complex applications, rightnow
    • Modules WebmakeHow generated file looks like ?
    • Modules WebmakeHow generated file looks like ?(function (modules) { // 53 lines of import/export logic})({ "root": { "add.js": function (exports, module, require) { module.exports = function () { var sum = 0, i = 0, args = arguments, l = args.length; while (i < l) sum += args[i++]; return sum; }; }, "increment.js": function (exports, module, require) { var add = require(./add); module.exports = function (val) { return add(val, 1); }; }, "program.js": function (exports, module, require) { var inc = require(./increment); var a = 1; inc(a); // 2 } }})("root/program");
    • Modules WebmakeHow generated file looks like ?(function (modules) { // 53 lines of import/export logic})({ "root": { "add.js": function (exports, module, require) { module.exports = function () { var sum = 0, i = 0, args = arguments, l = args.length; while (i < l) sum += args[i++]; return sum; }; }, "increment.js": function (exports, module, require) { var add = require(./add); module.exports = function (val) { return add(val, 1); }; }, "program.js": function (exports, module, require) { var inc = require(./increment); var a = 1; inc(a); // 2 } }})("root/program"); <- Execution of module that initiates application
    • Modules Webmake• modules-webmake can be used both from shell and programmatically - we canbind it to http server in Node.js and generate files on demand
    • Modules Webmake• modules-webmake can be used both from shell and programmatically - we canbind it to http server in Node.js and generate files on demand• Dependencies are parsed statically, it introduces some limitations, for specificcases we may force inclusion of chosen modules with ‘include’ option.
    • Modules Webmake• modules-webmake can be used both from shell and programmatically - we canbind it to http server in Node.js and generate files on demand• Dependencies are parsed statically, it introduces some limitations, for specificcases we may force inclusion of chosen modules with ‘include’ option.• modules-webmake reads local dependencies and those from external packages,one limitation is that it doesn’t recognize two different versions of same package (itwill be addressed)
    • Modules Webmake• modules-webmake can be used both from shell and programmatically - we canbind it to http server in Node.js and generate files on demand• Dependencies are parsed statically, it introduces some limitations, for specificcases we may force inclusion of chosen modules with ‘include’ option.• modules-webmake reads local dependencies and those from external packages,one limitation is that it doesn’t recognize two different versions of same package (itwill be addressed)• Up to date documentation can be found at github project page:https://github.com/medikoo/modules-webmake
    • Modules WebmakeLast week I finished work on application that was built with help ofmodules-webmake.• It’s HTML5 application, targetted only for modern browsers (newest FF, Chrome andSafari (both OSX & iOS))• To support Offline mode we needed to put whole application logic to client side,including templates and simple database engine.• Node.js on server-side, takes care about clients synchronization and saving data tophysical database (mongodb)• Minimalistic client-server communication based on sockets (Socket.IO)
    • Modules WebmakeFinal JavaScript file for client consists of 273 modules from 19 packages.176 of mentioned modules works also on server side.It means that about 60% of client-side code is also running on server-side.Concatenated file is 11 thousand lines longand weights about 370kB before minification and zipping.It looks quite promising, if we take into account, that it consists of templates forwhole application and all other modules that application is made of, including simpledatabase engine.After application load, all client-server communication is minimal as all pages asgenerated by client.
    • Modules WebmakeUpcoming improvements:
    • Modules WebmakeUpcoming improvements:• Not only parser, but full modules engine. What for? It will allow to run and testmodules with it’s dependencies in differently setup environments, like pure V8 +Modules, or emulation of browser environment.
    • Modules WebmakeUpcoming improvements:• Not only parser, but full modules engine. What for? It will allow to run and testmodules with it’s dependencies in differently setup environments, like pure V8 +Modules, or emulation of browser environment.• Work in a background, it will allow much more optimized and faster file generation(watching file system for changes, immediate serving of generated files whenintegrated with node.js http server)
    • Modules WebmakeUpcoming improvements:• Not only parser, but full modules engine. What for? It will allow to run and testmodules with it’s dependencies in differently setup environments, like pure V8 +Modules, or emulation of browser environment.• Work in a background, it will allow much more optimized and faster file generation(watching file system for changes, immediate serving of generated files whenintegrated with node.js http server)• Optional minification, compilation
    • Modules WebmakeUpcoming improvements:• Not only parser, but full modules engine. What for? It will allow to run and testmodules with it’s dependencies in differently setup environments, like pure V8 +Modules, or emulation of browser environment.• Work in a background, it will allow much more optimized and faster file generation(watching file system for changes, immediate serving of generated files whenintegrated with node.js http server)• Optional minification, compilation• Discovering modules that were required but not used
    • Modules WebmakeUpcoming improvements:• Not only parser, but full modules engine. What for? It will allow to run and testmodules with it’s dependencies in differently setup environments, like pure V8 +Modules, or emulation of browser environment.• Work in a background, it will allow much more optimized and faster file generation(watching file system for changes, immediate serving of generated files whenintegrated with node.js http server)• Optional minification, compilation• Discovering modules that were required but not used• Splitting into few files (for faster client-side load)
    • Modules WebmakeUpcoming improvements:• Not only parser, but full modules engine. What for? It will allow to run and testmodules with it’s dependencies in differently setup environments, like pure V8 +Modules, or emulation of browser environment.• Work in a background, it will allow much more optimized and faster file generation(watching file system for changes, immediate serving of generated files whenintegrated with node.js http server)• Optional minification, compilation• Discovering modules that were required but not used• Splitting into few files (for faster client-side load)• Modules should not be duplicated in different files, introduction of intelligent morethan one file generation
    • Modules WebmakeUpcoming improvements:• Not only parser, but full modules engine. What for? It will allow to run and testmodules with it’s dependencies in differently setup environments, like pure V8 +Modules, or emulation of browser environment.• Work in a background, it will allow much more optimized and faster file generation(watching file system for changes, immediate serving of generated files whenintegrated with node.js http server)• Optional minification, compilation• Discovering modules that were required but not used• Splitting into few files (for faster client-side load)• Modules should not be duplicated in different files, introduction of intelligent morethan one file generation• Small code fixes for buggy engines e.g. quoting reserved keywords used asproperty names
    • Future: HarmonyWe will have modules natively in JavaScripthttp://wiki.ecmascript.org/doku.php?id=harmony:modules_examples
    • Future: HarmonyWe will have modules natively in JavaScripthttp://wiki.ecmascript.org/doku.php?id=harmony:modules_examplesBase concept of Harmony Modules is same as inCommonJS Modules.
    • Future: HarmonyWe will have modules natively in JavaScripthttp://wiki.ecmascript.org/doku.php?id=harmony:modules_examplesBase concept of Harmony Modules is same as inCommonJS Modules.Differences lies in dedicated syntax, and powerfuloptional features like dynamic loading, possibility to loadexternal modules via url etc.
    • Harmony Modules
    • Harmony ModulesWhat we currently write for Node.js:add.jsmodule.exports = function() { var sum = 0, i = 0, args = arguments, l = args.length; while (i < l) sum += args[i++]; return sum;};increment.jsvar add = require(./add);module.exports = function(val) { return add(val, 1);};program.jsvar inc = require(./increment);var a = 1;inc(a); // 2
    • Harmony ModulesWill be written that way with Harmony:add.jsexport function add () { var sum = 0, i = 0, args = arguments, l = args.length; while (i < l) sum += args[i++]; return sum;};increment.jsimport add from ./add;export function increment (val) { return add(val, 1);};program.jsimport { increment: inc } from ./increment;var a = 1;inc(a); // 2
    • Harmony ModulesModules Webmake for EcmaScript 3/5:(function (modules) { // 53 lines of import/export logic})({ "root": { "add.js": function (exports, module, require) { module.exports = function () { var sum = 0, i = 0, args = arguments, l = args.length; while (i < l) sum += args[i++]; return sum; }; }, "increment.js": function (exports, module, require) { var add = require(./add); module.exports = function (val) { return add(val, 1); }; }, "program.js": function (exports, module, require) { var inc = require(./increment); var a = 1; inc(a); // 2 } }})("root/program");
    • Harmony ModulesModules Webmake for Harmony: module add { export function add () { var sum = 0, i = 0, args = arguments, l = args.length; while (i < l) sum += args[i++]; return sum; }; }; module increment { import add from add; export function increment (val) { return add(val, 1); }; }; module program { import { increment: inc } from increment; var a = 1; inc(a); // 2 };import * from program;
    • Server & BrowserIf you use CommonJS/Node.js Modules today, it won’t beproblematic for you to switch to Harmony in few yearstime
    • Server & BrowserIf you use CommonJS/Node.js Modules today, it won’t beproblematic for you to switch to Harmony in few yearstimeToday:CommonJS/Node.js Modules & modules-webmake
    • Server & BrowserIf you use CommonJS/Node.js Modules today, it won’t beproblematic for you to switch to Harmony in few yearstimeToday:CommonJS/Node.js Modules & modules-webmakeTomorrow:Harmony Modules
    • Server & BrowserIf you use CommonJS/Node.js Modules today, it won’t beproblematic for you to switch to Harmony in few yearstimeToday:CommonJS/Node.js Modules & modules-webmakeTomorrow:Harmony Modules (& modules-webmake ?)
    • Questions ?
    • Thank You !Mariusz Nowakmariusz@medikoo.com @medikoo github.com/medikoo