ES6 modules migration - camptocamp/ngeo GitHub Wiki

ES6 modules migration

Preparation

Modular Angular architecture

Previous architecture / shortcomings

  1. a unique "one-ring to rule them all" ngeo Angular module (+ one for gmf)
  2. src/ngeo.js / contribs/gmf/src/gmf.js is a holdall (namespace, root Angular module, ...)
  3. each Angular piece of code does goog.require('ngeo') and registers itself into the "one-ring" module
  4. a ton of "ignore unused require" annotations

New modular architecture

  1. the ngeo namespace stays in src/ngeo.js (with content like ngeo.baseTemplateUrl = 'ngeo';)
  2. the ngeo one-ring Angular module moves to src/module.js and is renamed "ngeo.module" (this is the module currently required from all files and where we currently register angular components, directives, services, values). This is only transitory, it will be removed at the end of the migration.
  3. the dist entrypoint is created in src/index.js and is named "ngeo.distmodule" it requires all top level Angular modules and extras and adds them to its dependencies. It is used to generate dist/ngeo.js containing the entire library
  4. each Angular file is wrapped in a dedicated Angular module which is stored in exports for goog.module or the symbol for goog.provide example for a component: goog.module('ngeo.mycomponent') ... exports = angular.module('ngeo.mycomponent', dependencies).component('ngeo.mycomponent', descr) or goog.provide('ngeo.mycomponent') ... ngeo.mycomponent = angular.module('ngeo.mycomponent', dependencies).component('ngeo.mycomponent', descr) if the component requires some other service / partial, it should require them and add their Angular modules as dependencies (use [] if no dependency). It is the same for external Angular module dependencies: ng-touch, .... the created angular module is added as dependency to the "one-ring" module (with ngeo.module.requires.push(theModule.name))
  5. Angular files are grouped in Angular "top level" modules Example: the search top level module goog.requires the searchdirective (module) and adds it as dependency How to split the library in consistent toplevel modules is to be discussed with someone like Stéphane and Alexandre
  6. partials, services, ... are moved to the directory of their top level Angular module, there are no more "partials" or "services" directories. General purpose services can be added to the "src/utils" top level module.
  7. Very specific Angular files are not added to the top level modules; instead they are added to "extra Angular modules" as dependencies. Extra Angular modules are created next to its toplevel module: example: search/extramodule.js where all these extra Angular files are added as dependencies.

Notes

Applications are expected to require toplevel Angular modules. When they require more they will also depend on individual Angular files. During migration, the modules of Angular files are added as dependencies both to:

  • the "one-ring" ngeo Angular module (legacy);
  • its top level or extra module. At the end of the migration we will remove the "one-ring" module (and the associated goog.require and module.dependencies.push code)

How we split / build the code with webpack will be specified later (single build with chunks for gmf apps, examples?, dist...)

The transform to ES6 modules is automatic and has 2 steps:

  • transform goog.provide to goog.module
  • transform goog.module to ES6 modules. As a consequence, it is not a necessity to transform manually goog.provide to goog.modules

Practical indications

Dealing with services

Service filem must export their constructor instead of the Angular module of the service. This is necessary for GCC.

As a consequence their Angular module should be stored in a "module" property of the constructor. Ex:

goog.module('ngeo.MyService'); goog.require('ngeo.module'); exports = class { constructor() { ... } };

const name = 'ngeoMyService'; exports.module = angular.module(name, []).service(name, exports); ngeo.module.requires.push(exports.module.name);

Finding a directory for a service

Some services are:

  • specific to a task: move to the Angular top level module directory which makes sense.
  • a core feature of the library: move to their own Angular top level module directory (ex: permalink -> src/permalink/Permalink.js).
  • utilities: move to the src/utils directory (ex: ngeo.utils.File.js)

Naming conventions

The permalink service: src/permalink/Permalink.js. The search top level module: src/search/module.js. The search component: src/search/component.js.

What must be done to move a component - step by step:

  • Create you directory in the src/<your_module> (contribs/gmf/src/<your_module>)
  • Move your component and eventually the related service ans partial inside this folder
  • Rename your component as "component.js", the partial as "component.html", the service as ".js"
  • In each file:
    • add a goog.provide('gmf.<module>.<filename>');
    • Remove the old goog.provide
    • Adapt the name of your object (for: controller, value, factory, service...) or create one (for components)
    • Create a module based on the name of what you provide (gmf|ngeo.<module>.<object.module|component>) = angular.module('<a_logical_name>', [<here, add dependencies of your component like that: ngeo|gmf..module.name>]` Note that the name should be identical as before if possible. Otherwise all call to this element in AngularJS must be adapted.
    • Declare your sevice|comntroller|...|component: ngeo|gmf.<module>.<object.module|component>.<sevice|controller|...|component>('<a_logical_name>', ngeo|gmf.<module>.<object|component>);
    • Push the dependency in the gmf module like that: gmf|ngeo.module.requires.push(ngeo|gmf.<module>.<object.module|component>.name);
  • Create a src/<module>/module.js file that will define the dependencies of your new module.
  • Create a src/<module>/.eslintrc.yaml file that contains: extends: [../.eslintrc-googshift.yaml] (to have a nice linter for your module)

Then in the related examples, in the .js :

  • Remove the old goog.require and add the new one to your component: goog.require('gmf|ngeo.<module>.module');
  • Add your your module as dependencies in the app|gmfapp: angular.module('(gmf)app', [..., gmf|ngeo.<module>.module.name,...]

For the applications :

  • In the right src/controllers/abstract.js, remove the old goog.require (nothing to add)
  • In the contribs/gmf/apps/appmodule.js, add your require and your new dependency (with ngeo|gmf.<module>.module.name)

Readers comments

  • I'm not sure about the service for the point 6 "partials, services, ... are moved to the directory of their top level Angular module, there are no more "partials" or "services" directories". A service is made to be shared between at least two "things". So, I don't think that make sens to not have a "service" directory. We have services that are only for one component, like the FulltextSearch service. But that's a good example of a bad usage of service. The code can be in the controller directly, the service is useless. [Benjamin]

[Guillaume] {

  • the code should be organized in modules/submodules => all what is related to the module should be moved next to it, including partials and services;
  • I think it is fine to have a service for only one component (it is actually a best practice in Angular2+); controllers should ideally be minimalistic, but it is unrelated to the ES6 migration; }
⚠️ **GitHub.com Fallback** ⚠️