281 Angular 14 Library, Lazy loading, Internationalization - chempkovsky/CS82ANGULAR GitHub Wiki

Steps required to accomplish the task

Create Angular Repository

  • run windows terminal, cd the drive and folder you like
    • our choice was E:\
  • run the command
ng new test-lib --no-create-application
cd test-lib

Create subprojects

  • we continue with the current folder
    • in our case it is e:\test-lib
  • run the following commands
ng generate library lazy-lib
ng generate application lazy-shell

Generate routing module and component for lib

  • we continue with the current folder
    • in our case it is e:\test-lib
  • run the following commands
ng generate module lazymodule --routing --project lazy-lib
ng generate component lazymodule/lazycomp  --project lazy-lib

Modify generated routing module

  • modify projects/lazy-lib/src/lib/lazymodule/lazymodule-routing.module.ts file as follows
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { LazycompComponent } from './lazycomp/lazycomp.component';

const routes: Routes = [
  {
    path: '',
    component: LazycompComponent,
    pathMatch: 'full'
  },
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class LazymoduleRoutingModule { }

Add component to the app

  • we continue with the current folder
    • in our case it is e:\test-lib
  • run the following command
ng generate component shellcomp  --project lazy-shell

Modify AppComponent html

Modify projects/lazy-shell/src/app/app.component.html file as follows

<p>Shell AppComponent works!</p>

<a routerLink="/">Go to ShellComponent</a><br>

<a routerLink="/lazycomponent">Go to  Lazy comp</a>

<router-outlet></router-outlet>

Modify public api file

Modify projects/lazy-lib/src/public-api.ts file as follows

/*
 * Public API Surface of lazy-lib
 */

// export * from './lib/lazy-lib.service';
// export * from './lib/lazy-lib.component';
// export * from './lib/lazy-lib.module';
export * from './lib/lazymodule/lazymodule.module';

Build the Lib

  • we continue with the current folder
    • in our case it is e:\test-lib
  • run the following command
ng build --project lazy-lib

Modify routing module of the app

Modify projects/lazy-shell/src/app/app-routing.module.ts file as follows

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ShellcompComponent } from './shellcomp/shellcomp.component';

const routes: Routes = [
 { 
    path: '', 
    component: ShellcompComponent, 
    pathMatch: 'full'
 },
 {
  path: 'lazycomponent',
  loadChildren: () => import('lazy-lib').then(m=>m.LazymoduleModule)
 }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Test app

  • we continue with the current folder
    • in our case it is e:\test-lib
  • run the following command
ng serve -o --project lazy-shell
  • make sure the navigation is working properly !!!

Add Internationalization

  • we continue with the current folder
    • in our case it is e:\test-lib
  • run the following command
ng add @angular/localize
  • Here is a responce
E:\test-lib>ng add @angular/localize
i Using package manager: npm
√ Found compatible package version: @angular/[email protected].
√ Package information loaded.

The package @angular/[email protected] will be installed and executed.
Would you like to proceed? Yes
√ Packages successfully installed.
Option "project" is required.

Modify LazycompComponent ts

Modify projects\lazy-lib\src\lib\lazymodule\lazycomp\lazycomp.component.ts file as follows

import { Component, OnInit } from '@angular/core';
@Component({
  selector: 'lib-lazycomp',
  templateUrl: './lazycomp.component.html',
  styleUrls: ['./lazycomp.component.css']
})
export class LazycompComponent implements OnInit {
  additionaltitle: string='';
  frases: {[key:string]: string}  = {
    'additionaltitle': $localize`:Here is a title to translate for LazycompComponent@@LazycompComponent.title-to-translate-for-LazycompComponent:Here is a title to translate for LazycompComponent`,
  };
  constructor() { }
  ngOnInit(): void {
    this.additionaltitle = this.frases['additionaltitle'];
  }
}

Modify ShellcompComponent ts

Modify projects\lazy-shell\src\app\shellcomp\shellcomp.component.ts file as follows

import { Component, OnInit } from '@angular/core';
@Component({
  selector: 'app-shellcomp',
  templateUrl: './shellcomp.component.html',
  styleUrls: ['./shellcomp.component.css']
})
export class ShellcompComponent implements OnInit {
  additionaltitle: string='';
  frases: {[key:string]: string}  = {
    'additionaltitle': $localize`:Here is a title to translate for ShellcompComponent@@ShellcompComponent.title-to-translate-for-ShellcompComponent:Here is a title to translate for ShellcompComponent`,
  };
  constructor() { }
  ngOnInit(): void {
    this.additionaltitle = this.frases['additionaltitle'];
  }
}

Modify LazycompComponent html

Modify projects\lazy-lib\src\lib\lazymodule\lazycomp\lazycomp.component.html file as follows

<p>lazycomp works!</p>
<p>{{ additionaltitle }}</p>

Modify ShellcompComponent html

Modify projects\lazy-shell\src\app\shellcomp\shellcomp.component.html file as follows

<p>shellcomp works!</p>
<p>{{ additionaltitle }}</p>

Try to build lib

  • we continue with the current folder
    • in our case it is e:\test-lib
  • run the following command
ng build --project lazy-lib
  • Here is a response
PS E:\test-lib> ng build --project lazy-lib
Building Angular Package

------------------------------------------------------------------------------
Building entry point 'lazy-lib'
------------------------------------------------------------------------------
✖ Compiling with Angular sources in Ivy partial compilation mode.
projects/lazy-lib/src/lib/lazymodule/lazycomp/lazycomp.component.ts:10:24 - error TS2304: Cannot find name '$localize'.

10     'additionaltitle': $localize`:Here is a title to translate for LazycompComponent@@LazycompComponent.title-to-translate-for-LazycompComponent:Here is a title to translate for LazycompComponent`,
                          ~~~~~~~~~

Modify public api file and build

Modify projects/lazy-lib/src/public-api.ts file as follows

/*
 * Public API Surface of lazy-lib
 */

//export * from './lib/lazy-lib.service';
//export * from './lib/lazy-lib.component';
//export * from './lib/lazy-lib.module';
import '@angular/localize/init';
export * from './lib/lazymodule/lazymodule.module';
  • we continue with the current folder
    • in our case it is e:\test-lib
  • run the following command
ng build --project lazy-lib
  • Here is a response
PS E:\test-lib> ng build --project lazy-lib
Building Angular Package

------------------------------------------------------------------------------
Building entry point 'lazy-lib'
------------------------------------------------------------------------------
✔ Compiling with Angular sources in Ivy partial compilation mode.
✔ Generating FESM2020
✔ Generating FESM2015
✔ Copying assets
✔ Writing package manifest
✔ Built lazy-lib

------------------------------------------------------------------------------
Built Angular Package
 - from: E:\test-lib\projects\lazy-lib
 - to:   E:\test-lib\dist\lazy-lib
------------------------------------------------------------------------------

Build at: 2022-10-13T12:48:15.754Z - Time: 7490ms

Try to serve app

  • we continue with the current folder
    • in our case it is e:\test-lib
  • run the following command
ng serve -o --project lazy-shell

The error will be shown in the console

main.ts:11 ERROR Error: Uncaught (in promise): Error: It looks like your application or one of its dependencies is using i18n.
Angular 9 introduced a global `$localize()` function that needs to be loaded.
Please run `ng add @angular/localize` from the Angular CLI.
(For non-CLI projects, add `import '@angular/localize/init';` to your `polyfills.ts` file.
For server-side rendering applications add the import to your `main.server.ts` file.)
Error: It looks like your application or one of its dependencies is using i18n.
Angular 9 introduced a global `$localize()` function that needs to be loaded.
Please run `ng add @angular/localize` from the Angular CLI.
(For non-CLI projects, add `import '@angular/localize/init';` to your `polyfills.ts` file.
For server-side rendering applications add the import to your `main.server.ts` file.)
    at _global.$localize (core.mjs:29729:15)
    at new ShellcompComponent (shellcomp.component.ts:11:33)
    at NodeInjectorFactory.ShellcompComponent_Factory [as factory] (shellcomp.component.ts:16:4)
...

Modify polyfills file and serve app

Modify projects\lazy-shell\src\polyfills.ts file as follows

import 'zone.js';  // Included with Angular CLI.
import '@angular/localize/init';
  • we continue with the current folder
    • in our case it is e:\test-lib
  • run the following command
ng serve -o --project lazy-shell
  • There is no error!!!

Modify Angular json

Modify angular.json file as follows

  • under projects:lazy-shell add
			"i18n": {
				"sourceLocale": {
					"code": "en",
					"baseHref": ""
				},				
				"locales": {
				  "ru": {
					"translation": "projects/lazy-shell/src/locale/messages.ru.xlf",
					"baseHref": ""
				  }
				}
			},
  • under projects:lazy-shell:architect:build:options add
"localize": true,
"aot": true,
  • under projects:lazy-shell:architect:build:configurations:development add
"localize": false,
  • under projects:lazy-shell:architect:build:configurations add
            "ru": {
              "localize": ["ru"]
            },
            "en": {
              "localize": ["en"]
            }            
  • under projects:lazy-shell:architect:serve:configurations add
            "ru": {
              "browserTarget": "lazy-shell:build:development,ru"
            },
            "en": {
              "browserTarget": "lazy-shell:build:development,en"
            }            

extract i18n

  • we continue with the current folder
    • in our case it is e:\test-lib
  • run the following command
ng extract-i18n --project lazy-shell --output-path projects/lazy-shell/src/locale
  • Here is projects\lazy-shell\src\locale\messages.xlf file
<?xml version="1.0" encoding="UTF-8" ?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
  <file source-language="en" datatype="plaintext" original="ng2.template">
    <body>
      <trans-unit id="LazycompComponent.title-to-translate-for-LazycompComponent" datatype="html">
        <source>Here is a title to translate for LazycompComponent</source>
        <context-group purpose="location">
          <context context-type="sourcefile">projects/lazy-lib/src/lib/lazymodule/lazycomp/lazycomp.component.ts</context>
          <context context-type="linenumber">10,17</context>
        </context-group>
        <note priority="1" from="description">Here is a title to translate for LazycompComponent</note>
      </trans-unit>
      <trans-unit id="ShellcompComponent.title-to-translate-for-ShellcompComponent" datatype="html">
        <source>Here is a title to translate for ShellcompComponent</source>
        <context-group purpose="location">
          <context context-type="sourcefile">projects/lazy-shell/src/app/shellcomp/shellcomp.component.ts</context>
          <context context-type="linenumber">11</context>
        </context-group>
        <note priority="1" from="description">Here is a title to translate for ShellcompComponent</note>
      </trans-unit>
    </body>
  </file>
</xliff>
  • For understanding: the following fragment is from the library !!!!
<trans-unit id="LazycompComponent.title-to-translate-for-LazycompComponent" datatype="html">
...
</trans-unit>

translate i18n

  • we continue with the current folder
    • in our case it is e:\test-lib
  • run the following commands
npm install -g xlf-auto-translate
xlf-auto-translate -i projects/lazy-shell/src/locale/messages.xlf -o projects/lazy-shell/src/locale/messages.ru.xlf -f en -t ru

Test app with i18n

  • we continue with the current folder
    • in our case it is e:\test-lib
  • run the following commands
ng serve -o --project lazy-shell --configuration=ru
  • Everything is fine

Conclusion

  • we do not need to make separate i18n-translation of the library
  • The notation of Lazy Loading is as follows:
 {
  path: 'lazycomponent',
  loadChildren: () => import('lazy-lib').then(m=>m.LazymoduleModule)
 }
  • The last requires a proper definition of public-api.ts for each library of the app.
    • In case of the custom code generators it requires the parsing of public-api.ts-file. :(

How to update webpack config js

Reading

We start with a file

  • The typical file will be as follows
const { shareAll, withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack');
module.exports = withModuleFederationPlugin({
    name: 'test_module_federation',
    exposes: {
      './Themodule11': './projects/mfe1/src/app/components/md11/md11.module',
      './Themodule12': './projects/mfe1/src/app/components/md12/lazymodule.module1',
    },
    shared: {
      ...shareAll({ singleton: true, strictVersion: true, requiredVersion: 'auto' }),
    },
}); 
  • we need to update the body of exposes-property

Create new folder

  • lets create test- folder on the disk e:
e:
mkdir test
cd test

Install babel

  • we continue with the current folder
    • in our case it is e:\test
  • run the following commands
npm install --save-dev @babel/core

Create the file for testing

  • we continue with the current folder
    • in our case it is e:\test
  • create E:\test\updater.js-file with the following content
const code = `
const { shareAll, withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack');
module.exports = withModuleFederationPlugin({
    name: 'test_module_federation',
    exposes: {
      './Themodule11': './projects/mfe1/src/app/components/md11/md11.module',
      './Themodule12': './projects/mfe1/src/app/components/md12/lazymodule.module1',
    },
    shared: {
      ...shareAll({ singleton: true, strictVersion: true, requiredVersion: 'auto' }),
    },
}); 
`;


var fullPath = process.argv[2];
var lastName = '';
let indexOfLastDelimeter = fullPath.lastIndexOf('/');
if(indexOfLastDelimeter > -1) {
  lastName = fullPath.substring(indexOfLastDelimeter+1);
} else {
  lastName = fullPath;
}

const parserInst = require('@babel/parser');
const babelInst = require("@babel/core");

const ast = parserInst.parse(code, {
  sourceType: "module",
  plugins: ["flow"]
});

let isNotUpdated =  true;
let isExposesPresent = false;
const StringLiteralVisitor = {StringLiteral(path) {
    if(path.node.value.endsWith('/'+ lastName) || (path.node.value === lastName)) {
      path.node.value = fullPath;
      isNotUpdated = false;
    }
}};
const ObjectPropertyVisitor = {ObjectProperty(path) {
    if(path.node.value.value.endsWith('/'+ lastName) || (path.node.value.value === lastName)) {
      path.node.value.value = fullPath;
      isNotUpdated = false;
    }
}};
const IdentifierVisitor = { Identifier(path) {
    if(path.node.name === "exposes") {
      path.parentPath.traverse(ObjectPropertyVisitor);
      isExposesPresent = true;
    }
}};

babelInst.traverse(ast, IdentifierVisitor);
  
if(isNotUpdated) {
  if(isExposesPresent) {
    babelInst.traverse(ast, {
      Identifier: function(path) {
          if(path.node.name === "exposes") {
            path.parentPath.node.value.properties.push(babelInst.types.objectProperty(babelInst.types.stringLiteral(fullPath),babelInst.types.stringLiteral(fullPath)));
          }
        },
      });
  } else {
    babelInst.traverse(ast, {
      CallExpression: function(path) {
        if(path.node.callee.type === 'Identifier') {
          if (path.node.callee.name === 'withModuleFederationPlugin') {
            if (path.node.arguments.count < 1) {
                path.node.arguments.push(
                  babelInst.types.objectExpression([
                    babelInst.types.objectProperty(
                      // key
                      babelInst.types.identifier("exposes"),
                      // value
                      babelInst.types.objectExpression([babelInst.types.objectProperty(babelInst.types.stringLiteral(fullPath),babelInst.types.stringLiteral(fullPath))])
                    )
                  ])
                );
              } else {
                path.node.arguments = [
                  babelInst.types.objectExpression([
                    babelInst.types.objectProperty(
                      // key
                      babelInst.types.identifier("exposes"),
                      // value
                      babelInst.types.objectExpression([
                        babelInst.types.objectProperty(babelInst.types.stringLiteral(fullPath),babelInst.types.stringLiteral(fullPath))
                      ])
                    )
                  ])
                ];
              }
          }
        }
      }
    });
  }
}
  
const result = babelInst.transformFromAst(ast);
console.log(result.code);

Run the first test

  • the first test modifies existing line
  • we continue with the current folder
    • in our case it is e:\test
  • run the following commands
node updater.js "./projects/folder_for_test/lazymodule.module"
  • Here is a response
PS E:\test> node updater.js "./projects/folder_for_test/lazymodule.module"
const {
  shareAll,
  withModuleFederationPlugin
} = require('@angular-architects/module-federation/webpack');
module.exports = withModuleFederationPlugin({
  name: 'test_module_federation',
  exposes: {
    './Themodule11': './projects/mfe1/src/app/components/md11/md11.module',
    './Themodule12': "./projects/folder_for_test/lazymodule.module"
  },
  shared: {
    ...shareAll({
      singleton: true,
      strictVersion: true,
      requiredVersion: 'auto'
    })
  }
});
PS E:\test> 

Run the second test

  • the second test adds new line
  • we continue with the current folder
    • in our case it is e:\test
  • run the following command
node updater.js "./projects/folder_for_test/lazymodule.module000"

Run the third test

  • the third test adds new line into empty exposes
    • modify the code as follows
const code = `
const { shareAll, withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack');
module.exports = withModuleFederationPlugin({
    name: 'test_module_federation',
    exposes: {},
    shared: {
      ...shareAll({ singleton: true, strictVersion: true, requiredVersion: 'auto' }),
    },
}); 
`;
  • we continue with the current folder
    • in our case it is e:\test
  • run the following command
node updater.js "./projects/folder_for_test/lazymodule.module000"

Run the fourth test

  • the fourth test adds exposes-def
    • modify the code as follows
const code = `
const { shareAll, withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack');
module.exports = withModuleFederationPlugin({
    name: 'test_module_federation',
    shared: {
      ...shareAll({ singleton: true, strictVersion: true, requiredVersion: 'auto' }),
    },
}); 
`;
  • we continue with the current folder
    • in our case it is e:\test
  • run the following command
node updater.js "./projects/folder_for_test/lazymodule.module000"

Run the fifth test

  • the fifths test modifies {}-empty object
    • modify the code as follows
const code = `
const { shareAll, withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack');
module.exports = withModuleFederationPlugin({}); 
`;
  • run the following command
node updater.js "./projects/folder_for_test/lazymodule.module000"

Run the sixth test

  • the sixth test modifies method call without params
    • modify the code as follows
const code = `
const { shareAll, withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack');
module.exports = withModuleFederationPlugin(); 
`;
  • run the following command
node updater.js "./projects/folder_for_test/lazymodule.module000"

Run updater js anywhere

  • copy updater.js into new empty folder
  • we continue with the current folder
    • in our case it is e:\test
  • run the following commands
mkdir e:\test1
copy updater.js e:\test1\updater.js
cd e:\test1
node updater.js "./projects/folder_for_test/lazymodule.module000"
  • here is a response
e:\test1>node updater.js "./projects/folder_for_test/lazymodule.module000"
node:internal/modules/cjs/loader:959
  throw err;
  ^

Error: Cannot find module '@babel/parser'
Require stack:
- e:\test1\updater.js
←[90m    at Function.Module._resolveFilename (node:internal/modules/cjs/loader:956:15)←[39m
←[90m    at Function.Module._load (node:internal/modules/cjs/loader:804:27)←[39m
←[90m    at Module.require (node:internal/modules/cjs/loader:1028:19)←[39m
←[90m    at require (node:internal/modules/cjs/helpers:102:18)←[39m
    at Object.<anonymous> ←[90m(e:\test1\←[39mupdater.js:21:20←[90m)←[39m
←[90m    at Module._compile (node:internal/modules/cjs/loader:1126:14)←[39m
←[90m    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1180:10)←[39m
←[90m    at Module.load (node:internal/modules/cjs/loader:1004:32)←[39m
←[90m    at Function.Module._load (node:internal/modules/cjs/loader:839:12)←[39m
←[90m    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)←[39m {
  code: ←[32m'MODULE_NOT_FOUND'←[39m,
  requireStack: [ ←[32m'e:\\test1\\updater.js'←[39m ]
}
  • Modify two lines of the e:\test1\updater.js as follows
const parserInst = require('e:/test/node_modules/@babel/parser');
const babelInst = require(`e:/test/node_modules/@babel/core`);
  • run the command
node updater.js "./projects/folder_for_test/lazymodule.module000"
  • There is no error!!!

How to update public api ts

We start with a typical file

  • The typical 'public-api.ts'-file will be as follows
import '@angular/localize/init'
export * from './lib/lazymodule/lazymodule.module' 

File for testing

const code = `
import '@angular/localize/init'
export * from './lib/lazymodule/lazymodule.module' 
`;


var fullPath = process.argv[2];
var lastName = '';
let indexOfLastDelimeter = fullPath.lastIndexOf('/');
if(indexOfLastDelimeter > -1) {
  lastName = fullPath.substring(indexOfLastDelimeter+1);
} else {
  lastName = fullPath;
}

const parserInst = require('@babel/parser')
const babelInst = require("@babel/core");

const ast = parserInst.parse(code, {
  sourceType: "module",
  plugins: ["flow"]
});


let isNotUpdated =  true;
let isExposesPresent = false;

const StringLiteralVisitor = {
  StringLiteral(path) {
    if(path.node.value.endsWith('/'+ lastName) || (path.node.value === lastName)) {
      path.node.value = fullPath;
      isNotUpdated = false;
    }
  }
};
const ExportVisitor = {
  ExportAllDeclaration(path) {
    path.traverse(StringLiteralVisitor);
  },
  ExportNamedDeclaration(path) {
    path.traverse(StringLiteralVisitor);
  },
  ExportDefaultDeclaration(path) {
    path.traverse(StringLiteralVisitor);
  },
};
babelInst.traverse(ast, ExportVisitor);
const ProgramVisitor = {
  Program(path) {
    if(path.node.body) {
      path.node.body.push(babelInst.types.exportAllDeclaration(babelInst.types.stringLiteral(fullPath)));
    } else {
      path.node.body = [babelInst.types.exportAllDeclaration(babelInst.types.stringLiteral(fullPath))];
    }
  }
}
if(isNotUpdated) {
  babelInst.traverse(ast, ProgramVisitor);
}
  
const result = babelInst.transformFromAst(ast);
console.log(result.code);

Three tests

  • the file above passes three tests

    • modify existing line
    • add new line
    • add new line to empty file
  • here are two of the tests

PS E:\test3> node updater02.js "./projects/folder_for_test/lazymodule.module"
import '@angular/localize/init';
export * from "./projects/folder_for_test/lazymodule.module";
PS E:\test3> node updater02.js "./projects/folder_for_test/lazymodule.module1"
import '@angular/localize/init';
export * from './lib/lazymodule/lazymodule.module';
export * from "./projects/folder_for_test/lazymodule.module1";
PS E:\test3> 
  • the last test for const code as follows:
const code = ``;
  • here is a result
PS E:\test3> node updater02.js "./projects/folder_for_test/lazymodule.module1"
export * from "./projects/folder_for_test/lazymodule.module1";
PS E:\test3> 
⚠️ **GitHub.com Fallback** ⚠️