diff --git a/README.md b/README.md index 2d6ab86..6b93836 100644 --- a/README.md +++ b/README.md @@ -35,12 +35,12 @@ This `micro-frontends-frame` app has 2 types of configs: } } ``` - + ii. Location of the AWS S3 files: - Configure micro app names and relative URL to be used when deployed to production environment in file at location : `https://tc-platform-prod.s3.amazonaws.com/micro-frontends/micro-frontends-config-production.json` - Configure micro app names and relative URL to be used when deployed to development environment in file at location : `https://tc-platform-dev.s3.amazonaws.com/micro-frontends/micro-frontends-config-development.json` - Configure micro app names and relative URL to be used when deployed to local environment in file at location : `./micro-frontends-frame/config/micro-frontends-config-local.json` - + 2. Route mapping handled by the frame, containing `route path` and `micro app name` for each micro app. The configuration files are available on TC AWS S3 and have public access. @@ -54,7 +54,7 @@ This `micro-frontends-frame` app has 2 types of configs: - Configure route path and micro app name to be used when deployed to production environment in file at location : `https://tc-platform-prod.s3.amazonaws.com/micro-frontends/micro-frontends-routes-production.txt` - Configure route path and micro app name to be used when deployed to development environment in file at location : `https://tc-platform-dev.s3.amazonaws.com/micro-frontends/micro-frontends-routes-development.txt` - Configure route path and micro app name to be used when deployed to development environment in file at location : `./micro-frontends-frame/config/micro-frontends-routes-local.txt` - + ⚠️ **NOTE** : When a configuration files is updated on TC AWS S3, make sure to give public access to the file. ## NPM Commands @@ -133,11 +133,29 @@ Make sure you have [Heroky CLI](https://devcenter.heroku.com/articles/heroku-cli - `git push heroku master` - push changes to Heroku and trigger deploying - ⚠️ **NOTE** : Authorization would not work because only predefined list of domain allowed by `accounts-app`. +## Segment Analytics + +Because analytics can be normally initialized once per website, we are initializing Segment Analytics inside the Frame app instead of each child app separately. See [Segment Analytics Quick Guide](https://segment.com/docs/connections/sources/catalog/libraries/website/javascript/quickstart/). + +- Analytics requires `SEGMENT_ANALYTICS_KEY` to work. +- It should be set as environment variable `SEGMENT_ANALYTICS_KEY` during running `npm run build` (for production build) or `npm run local-client` (for local development). +- If `SEGMENT_ANALYTICS_KEY` environment variable is not set during the build process, the Segment Analytics would not be initialized. +- Analytics would be exposed to the `window` object as `window.analytics`. See [Segment Analytics API Guide](https://segment.com/docs/connections/sources/catalog/libraries/website/javascript/). +- Note, that because we can build the Frame app without analytics initialized, all the child apps which use analytics should always check if it's initialized before usage, for example like this: + ```js + if (window.analytics && typeof window.analytics.page === "function") { + window.analytics.page(); + } + ``` +- Each child app should take care about calling `window.analytics.page()` each time the page is changed. We can pass [additional arguments](https://segment.com/docs/connections/sources/catalog/libraries/website/javascript/#page) to this method if needed. +- We can enable debug mode by calling `analytics.debug();` inside browser console, see [debug documentation](https://segment.com/docs/connections/sources/catalog/libraries/website/javascript/#debug). +- TODO: we might consider implementing one global logic for calling `window.analytics.page()` inside Frame of Navbar app so child apps wouldn't worry about this. Though we have to make sure that it works smoothly. In particular we have to make sure that if child app updates page `` then `window.analytics.page()` is called after that and logs to the analytic correct page title. Also, child apps might want to provide additional arguments when calling `window.analytics.page()`. So we might come to this improvement in some time, after we try the current approach. + ## Add/Remove child app For adding a child app to the root app make the following steps: -1. Add child app path to importmap. File underpath +1. Add child app path to importmap. File underpath - `https://tc-platform-prod.s3.amazonaws.com/micro-frontends/micro-frontends-config-production.json` for production deployment - `https://tc-platform-dev.s3.amazonaws.com/micro-frontends/micro-frontends-config-development.json` for development deployment - `./micro-frontends-frame/config/micro-frontends-config-local.json` for local deployment @@ -154,7 +172,7 @@ For adding a child app to the root app make the following steps: "@topcoder/micro-frontends-angular-app": "//localhost:4200/topcoder-micro-frontends-angular-app.js" ``` -2. Add a route which should show the app. File underpath +2. Add a route which should show the app. File underpath - `https://tc-platform-prod.s3.amazonaws.com/micro-frontends/micro-frontends-routes-production.txt` for production deployment - `https://tc-platform-dev.s3.amazonaws.com/micro-frontends/micro-frontends-routes-development.txt` for development deployment - `./micro-frontends-frame/config/micro-frontends-routes-local.txt` for local deployment diff --git a/config/micro-frontends-config-local.json b/config/micro-frontends-config-local.json index d1f169e..0137f5d 100644 --- a/config/micro-frontends-config-local.json +++ b/config/micro-frontends-config-local.json @@ -1,12 +1,12 @@ { "imports": { - "@topcoder/micro-frontends-navbar-app": "http://local.topcoder-dev.com:3001/navbar/topcoder-micro-frontends-navbar-app.js", - "@topcoder/micro-frontends-react-app": "http://local.topcoder-dev.com:8500/react/topcoder-micro-frontends-react-app.js", - "@topcoder/micro-frontends-angular-app": "http://local.topcoder-dev.com:4200/angular/main.js", - "@topcoder/micro-frontends-teams": "http://local.topcoder-dev.com:8501/taas-app/topcoder-micro-frontends-teams.js", - "@topcoder/micro-frontends-task-marketplace-app": "http://local.topcoder-dev.com:8502/tasks/topcoder-micro-frontends-task-marketplace-app.js", - "@topcoder/micro-frontends-earn-app": "http://local.topcoder-dev.com:8008/earn/topcoder-micro-frontends-earn-app.js", - "@topcoder/micro-frontends-submission-review-app": "http://local.topcoder-dev.com:8503/submissionreview/topcoder-micro-frontends-submission-review-app.js", - "@topcoder/micro-frontends-model-app": "http://local.topcoder-dev.com:8504/model/topcoder-micro-frontends-model-app.js" + "@topcoder/micro-frontends-navbar-app": "http://localhost:3001/navbar/topcoder-micro-frontends-navbar-app.js", + "@topcoder/micro-frontends-react-app": "http://localhost:8500/react/topcoder-micro-frontends-react-app.js", + "@topcoder/micro-frontends-angular-app": "http://localhost:4200/angular/main.js", + "@topcoder/micro-frontends-teams": "http://localhost:8501/taas-app/topcoder-micro-frontends-teams.js", + "@topcoder/micro-frontends-task-marketplace-app": "http://localhost:8502/tasks/topcoder-micro-frontends-task-marketplace-app.js", + "@topcoder/micro-frontends-earn-app": "http://localhost:8008/earn/topcoder-micro-frontends-earn-app.js", + "@topcoder/micro-frontends-submission-review-app": "http://localhost:8503/submissionreview/topcoder-micro-frontends-submission-review-app.js", + "@topcoder/micro-frontends-model-app": "http://localhost:8504/model-app/topcoder-micro-frontends-model-app.js" } } \ No newline at end of file diff --git a/src/index.ejs b/src/index.ejs index f766fae..14a3bf7 100644 --- a/src/index.ejs +++ b/src/index.ejs @@ -20,7 +20,7 @@ Learn more about CSP policies at https://content-security-policy.com/#directive --> <!-- <meta http-equiv="Content-Security-Policy" content="default-src 'self' https: localhost:*; script-src 'unsafe-inline' https: localhost:*; connect-src https: localhost:* ws://localhost:*; style-src 'unsafe-inline' https:; object-src 'none';"> --> - <meta http-equiv="Content-Security-Policy" content="default-src * data: blob: 'unsafe-inline' 'unsafe-eval'; script-src * 'unsafe-inline' 'unsafe-eval'; connect-src * 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src *; style-src * data: blob: 'unsafe-inline'; font-src * data: blob: 'unsafe-inline';"> + <meta http-equiv="Content-Security-Policy" content="default-src * data: blob: 'unsafe-inline' 'unsafe-eval'; script-src * 'unsafe-inline' 'unsafe-eval'; connect-src * 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src *; style-src * data: blob: 'unsafe-inline'; font-src * data: blob: 'unsafe-inline'; worker-src 'self' blob:"> <meta name="importmap-type" content="systemjs-importmap" /> <!-- If you wish to turn off import-map-overrides for specific environments (prod), uncomment the line below --> <!-- More info at https://github.com/joeldenning/import-map-overrides/blob/master/docs/configuration.md#domain-list --> @@ -52,7 +52,7 @@ If you need to support Angular applications, uncomment the script tag below to ensure only one instance of ZoneJS is loaded Learn more about why at https://single-spa.js.org/docs/ecosystem-angular/#zonejs --> - + <script src="https://cdn.jsdelivr.net/npm/zone.js@0.10.3/dist/zone.min.js"></script> <% if (htmlWebpackPlugin.options.templateParameters.isLocal) { %> @@ -66,7 +66,7 @@ <script src="https://cdn.jsdelivr.net/npm/systemjs@6.4.0/dist/extras/amd.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/systemjs@6.4.0/dist/extras/named-exports.min.js"></script> <% } %> - + <template id="single-spa-layout"> <single-spa-router> <nav> @@ -75,6 +75,17 @@ <div id="single-spa-main"></div> </single-spa-router> </template> + + <% if (!!htmlWebpackPlugin.options.templateParameters.SEGMENT_ANALYTICS_KEY) { %> + <!-- Segment Analytics Code --> + <script type="text/javascript"> + !function(){var analytics=window.analytics=window.analytics||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("Segment snippet included twice.");else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","reset","group","track","ready","alias","debug","page","once","off","on","addSourceMiddleware","addIntegrationMiddleware","setAnonymousId","addDestinationMiddleware"];analytics.factory=function(t){return function(){var e=Array.prototype.slice.call(arguments);e.unshift(t);analytics.push(e);return analytics}};for(var t=0;t<analytics.methods.length;t++){var e=analytics.methods[t];analytics[e]=analytics.factory(e)}analytics.load=function(t,e){var n=document.createElement("script");n.type="text/javascript";n.async=!0;n.src="https://cdn.segment.com/analytics.js/v1/"+t+"/analytics.min.js";var a=document.getElementsByTagName("script")[0];a.parentNode.insertBefore(n,a);analytics._loadOptions=e};analytics.SNIPPET_VERSION="4.1.0"; + analytics.load("<%= htmlWebpackPlugin.options.templateParameters.SEGMENT_ANALYTICS_KEY %>"); + // analytics.page(); - don't call the page, each app should call it when it loads a page by itself + }}(); + </script> + <!-- // end: Segment Analytics Code --> + <% } %> </head> <body> <script> diff --git a/webpack.config.js b/webpack.config.js index 51242a0..f1adac8 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,3 +1,4 @@ +/* global process */ const webpackMerge = require("webpack-merge"); const singleSpaDefaults = require("webpack-config-single-spa"); const HtmlWebpackPlugin = require("html-webpack-plugin"); @@ -32,6 +33,7 @@ module.exports = (webpackConfigEnv) => { template: "src/index.ejs", templateParameters: { env: webpackConfigEnv.config, + SEGMENT_ANALYTICS_KEY: process.env.SEGMENT_ANALYTICS_KEY, orgName, }, }),