项目作者: jualoppaz

项目描述 :
Angular v8 application with two example pages for be included in a single-spa application as registered app.
高级语言: TypeScript
项目地址: git://github.com/jualoppaz/single-spa-angular-app.git
创建时间: 2020-01-25T11:31:06Z
项目社区:https://github.com/jualoppaz/single-spa-angular-app

开源协议:MIT License

下载





npm version

single-spa-angular-app

This is an Angular v8 application example used as NPM package in single-spa-login-example-with-npm-packages in order to register an application. See the installation instructions there.

✍🏻 Motivation

This is an Angular v8 application which contains two routed pages for embbed the app inside a root single-spa application.

How it works ❓

There are several files for the right working of this application and they are:

  • src/app/app.module.ts
  • src/app/app-routing.module.ts
  • src/main.single-spa.ts
  • angular.json
  • extra-webpack.config.ts
  • package.json

This file has no custom config. But we must set desired config here if needed.

src/app/app.module.ts

  1. import { AppComponent } from './app.component';
  2. import {APP_BASE_HREF} from '@angular/common';
  3. import { ListComponent } from './list/list.component';
  4. BrowserModule,
  5. AppRoutingModule,
  6. ],
  7. providers: [
  8. {provide: APP_BASE_HREF, useValue: '/'}
  9. ],
  10. bootstrap: [AppComponent]
  11. })
  12. export class AppModule { }

As this application will be mounted when browser url starts with /angular, we need to provide the APP_BASE_HREF property with /angular value. However, as is documented here, this config causes strange behaviours in angular router when navigating between registered apps.

A simple way of avoid this is set / as value of APP_BASE_HREF property and repeat angular prefix in all routes as you can see in app-routing.module.ts file.

src/app/app-routing.module.ts

  1. import { NgModule } from '@angular/core';
  2. import { Routes, RouterModule } from '@angular/router';
  3. import {ListComponent} from './list/list.component';
  4. import {DetailComponent} from './detail/detail.component';
  5. import {EmptyRouteComponent} from './empty-route/empty-route.component';
  6. const routes: Routes = [
  7. { path: 'angular', component: ListComponent },
  8. { path: 'angular/detail', component: DetailComponent },
  9. { path: '**', component: EmptyRouteComponent }
  10. ];
  11. @NgModule({
  12. imports: [RouterModule.forRoot(routes)],
  13. exports: [RouterModule]
  14. })
  15. export class AppRoutingModule { }

As it is explained in src/app/app.module.ts section we need to add angular prefix in every routes.

src/main.single-spa.ts

  1. import { enableProdMode, NgZone } from '@angular/core';
  2. import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
  3. import { Router } from '@angular/router';
  4. import { AppModule } from './app/app.module';
  5. import { environment } from './environments/environment';
  6. import singleSpaAngular from 'single-spa-angular';
  7. import { singleSpaPropsSubject } from './single-spa/single-spa-props';
  8. if (environment.production) {
  9. enableProdMode();
  10. }
  11. const lifecycles = singleSpaAngular({
  12. bootstrapFunction: singleSpaProps => {
  13. singleSpaPropsSubject.next(singleSpaProps);
  14. return platformBrowserDynamic().bootstrapModule(AppModule);
  15. },
  16. template: '<app-root ></app-root>',
  17. Router,
  18. NgZone,
  19. domElementGetter: () => document.getElementById('angular-app')
  20. });
  21. export const bootstrap = lifecycles.bootstrap;
  22. export const mount = lifecycles.mount;
  23. export const unmount = lifecycles.unmount;

The lifecycles object contains all single-spa-angular methods for the single-spa lifecycle of this app. All used config is default one but the custom config of the domElementGetter option. It’s assumed that an element with angular-app id is defined in the index.html where this application will be mounted.

angular.json

  1. {
  2. "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  3. "version": 1,
  4. "newProjectRoot": "projects",
  5. "projects": {
  6. "single-spa-angular-app": {
  7. "projectType": "application",
  8. "schematics": {
  9. "@schematics/angular:component": {
  10. "style": "scss"
  11. }
  12. },
  13. "root": "",
  14. "sourceRoot": "src",
  15. "prefix": "app",
  16. "architect": {
  17. "build": {
  18. "builder": "@angular-builders/custom-webpack:browser",
  19. "options": {
  20. "outputPath": "dist",
  21. "index": "src/index.html",
  22. "main": "src/main.single-spa.ts",
  23. "polyfills": "src/polyfills.ts",
  24. "tsConfig": "tsconfig.app.json",
  25. "aot": false,
  26. "assets": [
  27. "src/favicon.ico",
  28. "src/assets"
  29. ],
  30. "styles": [
  31. "src/styles.scss"
  32. ],
  33. "scripts": [],
  34. "customWebpackConfig": {
  35. "path": "./extra-webpack.config.js"
  36. }
  37. },
  38. "configurations": {
  39. "production": {
  40. "fileReplacements": [
  41. {
  42. "replace": "src/environments/environment.ts",
  43. "with": "src/environments/environment.prod.ts"
  44. }
  45. ],
  46. "optimization": true,
  47. "outputHashing": "none",
  48. "sourceMap": false,
  49. "extractCss": true,
  50. "namedChunks": false,
  51. "aot": true,
  52. "extractLicenses": true,
  53. "vendorChunk": false,
  54. "buildOptimizer": true,
  55. "budgets": [
  56. {
  57. "type": "initial",
  58. "maximumWarning": "2mb",
  59. "maximumError": "5mb"
  60. },
  61. {
  62. "type": "anyComponentStyle",
  63. "maximumWarning": "6kb",
  64. "maximumError": "10kb"
  65. }
  66. ]
  67. }
  68. }
  69. },
  70. "serve": {
  71. "builder": "@angular-builders/custom-webpack:dev-server",
  72. "options": {
  73. "browserTarget": "single-spa-angular-app:build"
  74. },
  75. "configurations": {
  76. "production": {
  77. "browserTarget": "single-spa-angular-app:build:production"
  78. }
  79. }
  80. },
  81. "extract-i18n": {
  82. "builder": "@angular-devkit/build-angular:extract-i18n",
  83. "options": {
  84. "browserTarget": "single-spa-angular-app:build"
  85. }
  86. },
  87. "test": {
  88. "builder": "@angular-devkit/build-angular:karma",
  89. "options": {
  90. "main": "src/test.ts",
  91. "polyfills": "src/polyfills.ts",
  92. "tsConfig": "tsconfig.spec.json",
  93. "karmaConfig": "karma.conf.js",
  94. "assets": [
  95. "src/favicon.ico",
  96. "src/assets"
  97. ],
  98. "styles": [
  99. "src/styles.scss"
  100. ],
  101. "scripts": []
  102. }
  103. },
  104. "lint": {
  105. "builder": "@angular-devkit/build-angular:tslint",
  106. "options": {
  107. "tsConfig": [
  108. "tsconfig.app.json",
  109. "tsconfig.spec.json",
  110. "e2e/tsconfig.json"
  111. ],
  112. "exclude": [
  113. "**/node_modules/**"
  114. ]
  115. }
  116. },
  117. "e2e": {
  118. "builder": "@angular-devkit/build-angular:protractor",
  119. "options": {
  120. "protractorConfig": "e2e/protractor.conf.js",
  121. "devServerTarget": "single-spa-angular-app:serve"
  122. },
  123. "configurations": {
  124. "production": {
  125. "devServerTarget": "single-spa-angular-app:serve:production"
  126. }
  127. }
  128. }
  129. }
  130. }
  131. },
  132. "defaultProject": "single-spa-angular-app"
  133. }

The essential config is in @angular-builders/custom-webpack:browser builder. Be care of this config is autogenerated when install single-spa-angular.

extra-webpack.config.js

  1. const singleSpaAngularWebpack = require('single-spa-angular/lib/webpack').default
  2. module.exports = (angularWebpackConfig, options) => {
  3. const singleSpaWebpackConfig = singleSpaAngularWebpack(angularWebpackConfig, options)
  4. // Feel free to modify this webpack config however you'd like to
  5. return singleSpaWebpackConfig
  6. }

package.json

  1. {
  2. "name": "single-spa-angular-app",
  3. "version": "0.1.5",
  4. "description": "Angular v8 application with two example pages for be included in a single-spa application as registered app.",
  5. "main": "dist/main-es2015.js",
  6. "scripts": {
  7. "ng": "ng",
  8. "test": "ng test",
  9. "lint": "ng lint",
  10. "build:single-spa": "ng build --prod"
  11. },
  12. "dependencies": {
  13. "@angular-builders/custom-webpack": "8.4.1",
  14. "@angular/common": "8.2.14",
  15. "@angular/compiler": "8.2.14",
  16. "@angular/core": "8.2.14",
  17. "@angular/forms": "8.2.14",
  18. "@angular/platform-browser": "8.2.14",
  19. "@angular/platform-browser-dynamic": "8.2.14",
  20. "@angular/router": "8.2.14",
  21. "rxjs": "6.4.0",
  22. "single-spa-angular": "3.1.0",
  23. "tslib": "1.10.0",
  24. "zone.js": "0.9.1"
  25. },
  26. "devDependencies": {
  27. "@angular-devkit/build-angular": "0.803.23",
  28. "@angular-devkit/build-ng-packagr": "0.803.23",
  29. "@angular/cli": "8.3.23",
  30. "@angular/compiler-cli": "8.2.14",
  31. "@angular/language-service": "8.2.14",
  32. "@types/node": "8.9.5",
  33. "@types/jasmine": "3.3.16",
  34. "@types/jasminewd2": "2.0.8",
  35. "codelyzer": "5.2.1",
  36. "jasmine-core": "3.4.0",
  37. "jasmine-spec-reporter": "4.2.1",
  38. "karma": "4.1.0",
  39. "karma-chrome-launcher": "2.2.0",
  40. "karma-coverage-istanbul-reporter": "2.0.6",
  41. "karma-jasmine": "2.0.1",
  42. "karma-jasmine-html-reporter": "1.5.1",
  43. "ng-packagr": "5.7.1",
  44. "protractor": "5.4.2",
  45. "ts-node": "7.0.1",
  46. "tsickle": "0.37.1",
  47. "tslint": "5.15.0",
  48. "typescript": "3.5.3"
  49. }
  50. }

There are several scripts in this project:

  • ng: for use global ng cli
  • test: use for run unit tests
  • lint: for run eslint in all project
  • build:single-spa: for compile the application and build it as a libray in umd format