[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**Desktop (please complete the following information):**\n - OS: [e.g. iOS]\n - Browser [e.g. chrome, safari]\n - Version [e.g. 22]\n\n**Smartphone (please complete the following information):**\n - Device: [e.g. iPhone6]\n - OS: [e.g. iOS8.1]\n - Browser [e.g. stock browser, safari]\n - Version [e.g. 22]\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules/\ndist/\n.idea/\n"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"trailingComma\": \"es5\",\n  \"tabWidth\": 4,\n  \"semi\": true,\n  \"singleQuote\": true,\n  \"jsxSingleQuote\": true,\n  \"bracketSpacing\": true,\n  \"jsxBracketSameLine\": true,\n  \"printWidth\": 120\n}\n"
  },
  {
    "path": "LICENSE.md",
    "content": "MIT License\n\nCopyright (c) 2018 Öner Zafer\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "README.md",
    "content": "# *microfe*\n*(microfe - short for micro frontends)*\n\nA naive infrastructure/meta-framework implementation for micro frontends. This project intends to provide the necessary tooling to achieve independent apps loaded separately and run on different parts on a single web page in complete isolation.\nFor detailed information on the topic can be found [micro-frontends.org](https://micro-frontends.org/)\n## Motivation\nWhen developing microservices there are lots of tools and libraries to help developers to focus the effort on the things needs to be done instead of fighting against a monolithic monster. For now, \"micro frontends\" idea is still premature and it needs time to grow something easy to use. My intention is to contribute to this discussion and also provide necessary tooling and a sample architecture for developers who would like to give it a try. Providing an easy to use infrastructure for individuals and companies can be considered as an ultimate goal.\n## Who will/may/can use *microfe*?\nIdeally *microfe* is not suitable for small teams and for them trying to use it would not be necessary. For this kind of teams refactoring their monolithic fe apps would be more productive instead of using *microfe* to divide a relatively big app into smaller pieces and trying to maintain each piece. If the project contains at least two independent teams which are responsible for the same monolithic app then *microfe* can be beneficial. Because *microfe* gives the opportunity of working on independent tech stack by each team. It can provide isolation and managed communication channels between micro-apps.\n## On Micro Frontends\nWhile companies growing they usually move from one team to two or more and they start to divide the code base and on the backend side microservice architecture has lots of benefits to scale the company up. On the frontend side, the code base becomes a growing monolith even if it is written in a modular fashion. So scaling a front end team is not so easy and problems start to appear. Lack of communication between teams, conflicting merges, hard to change tech stacks, hard to update dependencies and the list goes on.\n\nSimilar to microservices, the micro frontends provides the opportunity to isolate code bases and make the teams free to use any code standards and tech stack and focus on relatively small parts of the application.\n## Goals\n* Isolated and Independent apps\n* A way to have a unified UI\n* Inter-app communication (i.e. authentication)\n* Easy to maintain apps\n* Not to break already available build environments for major frameworks (React, Angular, Vue)\n* Freedom of tech stack choice\n\n## Requirements\nTo run the microfe locally you need to clone and run [micro-fe-registry](https://github.com/onerzafer/micro-fe-registry). The documentation for micro-fe-registry can be found under its own repository.\n\n## Usage\nCurrently, there is no npm package provided and the usage is not recommended at this phase. Yet if you are willing to experiment by yourself clone both repositories. For micro-fe-registry part follow the instructions on its own repository. Then execute following commands\n```bash\nnpm install\nnpm start\n```\nThis command will open your browser on http://localhost:8080 and you will be able to see the page is running.\n\nIf you see just blank page be sure your micro-fe-registry installation is up and running. And if it is running already please check if the requested micro-apps are available on the registry folder with requested names. If you have still problems of running please open an issue I will be happy to help you.\n\n## Top level architecture\nThe microfe library basically has 4 different main parts *AppsManager, Loader, Router* and *Store*. It also provides some helper functions and classes: *Bootstrapper, Microfe decorator* and  *provider*.\nThese all parts of the microfe library can function with a specific micro-apps wich implements microfe interface.\n\n### Definition of a microfe app\nA microfe app should implement the following interface\n```TypeScript\ninterface MicroApp {\n    name: string;\n    deps?: string[];\n    initialize: (args: {\n        AppsManager: AppsManager;\n        Config: ConfigInterface;\n        [key: string]: any;\n    }) => any | void;\n}\n```\n**initialize** function may return the instance of the app\n\n### Bootstrapper\nThe responsibility of Bootstrapper is initializing the AppsManager and all other micro-apps provided inside the library. So it can be considered as the entry point of the microfe library. The signature for bootstrapper can be described like this:\n```TypeScript\nconst bootstrap = (\n    routes: Route[],\n    config: ConfigInterface\n) => (...microApps: MicroApp[]) => void\n```\nTo boostrap the microfe meta-framework the following example can be used as a refrence:\n```TypeScript\nimport { Microfe, Bootstrap, Route, ConfigInterface } from './lib';\n\n@Microfe({\n    deps: ['LayoutApp'],\n})\nclass Main {\n    constructor() {\n        console.log('Initialised');\n    }\n}\n\nconst Routes: Route[] = [\n    { path: '/', redirectTo: '/angular' },\n    { path: '/angular', microApp: 'demoAngular', tagName: 'demo-angular' },\n    { path: '/react', microApp: 'reactDemo', tagName: 'react-demo' },\n    { path: '/static', microApp: 'staticApp', tagName: 'static-app' },\n    { path: '*', microApp: 'NotFoundApp', tagName: 'not-found-app' },\n];\n\nconst Config: ConfigInterface = {\n    registryApi: 'http://localhost:3000/registry',\n    registryPublic: 'http://localhost:3000',\n};\n\nBootstrap(Routes, Config)(Main);\n```\n\n### AppsManager\nThe main functionality of AppsManager is creating the dependency tree and when all of the dependencies of a micro-app are ready, it instantiates the micro-app by providing the dependencies instances. The public API for AppsManager can be summarized as follows:\n```TypeScript\ninterface AppsManager {\n    register: (app: MicroApp) => void;\n    subscribe: (fn: (notFoundApps: MicroApp[]) => void) => {\n        unsubscribe: () => void\n    }\n}\n```\nAppsManager passes the config and itself as default dependency to all of the instances of provided micro-apps. Which means all have the access to AppsManager and its public API. Alternatively, AppsManager can be accessed from window global as AppsManager.\n\n*AppsManager is the only part which does not implement the MicroApp interface. The rest of the library actually is a collection of micro-apps.*\n### Loader\nWhen registered by Bootstrapper the Loader requires Config and waits until it is provided. With the config Loader receives the micro-fe-registry public URLs. After getting the Config AppsManagers instantiates the Loader. On constructer Loader subscribes to AppsManagers and start the not found micro-apps. When a new not found micro-app available the Loader parse the micro-app URL by combining the name of the micro-app and public URL of micro-fe-registry injects it to the dom as a remote script. Naturally, the browser loads the micro-app from given URL. The loader can be a dependency and it has only one public function.\n```TypeScript\nconst Loader {\n    fetchMicroApp: (name: string) => void\n}\n```\n### Router\nUnlike the common routers, the microfe router has limited functionality. It is capable of solving the first part of the declared URLs. This implementation assumes the rest of the URL will be resolved by the responsible micro-app. If the Router can resolve the URL from the browser location it triggers the Loader.fetchMicroApp function with the name of resolved micro-app. So it has two dependencies routes and Loader.\n#### Route\nThe routes object is an array of the Route objects which has the following interface:\n```TypeScript\ninterface Route {\n    path: string;\n    tagName?: string;\n    redirectTo?: string;\n    microApp?: string;\n}\n```\n#### micro-router the Router outlet\nWhen Router instantiates it register a web component called micro-router. This is the expected place for all other micro-apps loads on route hit. The usage is pretty simple and available for all micro-apps living on the client at the moment.\n```html\n<micro-router></micro-router>\n```\nCurrently, it has no targetting of sub-routes. Which means all of the micro-router tags will display the same target micro-app. So current recommendations are using only one micro-router the page. In the future, some sub-routes can be targetted to some named micro-router tags.\n#### micro-link\nRouter also provides a simple navigation element with no design. All micro-apps will be able to access it any time since it is provided as a web component like micro-router. micro-link has one attribute which is href and if the given path is the current route, it assigns itself automatically active class. So no need to observe history and match correct path to put active class to the links.\n```html\n<micro-link href=\"/some/cool/page\">\n    Some Cool Page\n</micro-link>\n```\nWhen we navigate to /some/cool/page this micro-link above will be marked as active. \n### Store\nWith the assumption of only big teams and big code bases will need the microfe and nearly all of the already managing app state, the microfe library provides a global shared inter-app state. this state can be used as a shared event bus or shared global state. By nature, this store is reactive and powered by RxJS. Yet it still has the similar functionalities of Redux library.\n```TypeScript\ninterface Action {\n    type: string;\n    [key: string]: any;\n}\n\ninterface State {\n    [key: string]: any;\n}\n\ninterface Reducer {\n    (action: Action, state: State) => State) => void;\n}\n\ninterface ReducerTreePiece {\n    [key: string]: Reducer | ReducerTreePiece\n}\n\ninterface MicroAppStore {\n    addReducer: (reducerTreePiece: ReducerTreePiece) => void;\n    dispatch: (action: Action) => void;\n    select: (selector: string) => Observable<State>;\n}\n```\nThe main issue with the MicroAppStore is the reducers may arrive on different times. The select function is pretty useful on this case. Because if the selected reducer is not available it sibly emits undefined and when the reducer arrives it emmits to all subscribers the related state.\n```TypeScript\nconst Todos$ = MicroAppsStore.select('todos');\nTodos$.subscribe(todos => console.log(todos)); // immediatelly logs undefined\nconst todosReducer = (state = [], action: Action) => {\n    return state;\n}\nMicroAppsStore.addReducer({todos: todoRecucer});\n// At this point Todos$.subscribe will receive [] as todos and will log []\n```\n### Microfe decorator\nThis decorator can be used as an helper for casting any class to a micro-app\n```TypeScript\n@Microfe({\n    deps: ['LayoutApp']\n})\nexport class Main {\n    private layoutApp;\n    constructor({LayoutApp}) {\n        this.layoutApp = LayoutApp;\n        this.render();\n    }\n\n    private render() {\n        this.layoutApp.someLayoutAppFunction();\n    }\n}\n```\nThe code block above will be equavalent to following code:\n```javascript\n{\n    name: 'Main',\n    deps: ['LayoutApp']\n    initialize: function({LayoutApp}) {\n        LayoutApp.someLayoutAppFunction();\n    }\n}\n```\n### Provider\nThe provider is a helper function to provide objects as micro-app. So any static data can be provided to other micro-apps with Provide function:\n```TypeScript\nconst languageEn = {hello: 'Hello'};\nconst languageEnProvider = provide(languageEn);\n```\nThen languageEnProvider can be passed down to all micro-apps which has the dependency as follows:\n```TypeScript\nBootstrap(Routes, Config)(Main, LanguageEnProvider);\n```\n# License\n[MIT](https://choosealicense.com/licenses/mit/)"
  },
  {
    "path": "index.html",
    "content": "<html>\n<head>\n    <title>Test App</title>\n    <style>\n        body, html {\n            margin: 0;\n            padding: 0;\n        }\n    </style>\n</head>\n<body>\n    <layout-app></layout-app>\n    <script src=\"/main.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "karma.config.js",
    "content": "const webpackConfig = require('./webpack.config.js');\nwebpackConfig.mode = 'production';\n\nmodule.exports = function(config) {\n  config.set({\n    singleRun: true,\n    \n    browsers: [\n      'PhantomJS'\n    ],\n\n    frameworks: [\n      'jasmine'\n    ],\n\n    files: [\n      'spec.bundle.js'\n    ],\n\n    preprocessors: {\n      'spec.bundle.js': ['webpack']\n    },\n\n    webpack: webpackConfig,\n\n    webpackMiddleware: {\n      stats: 'errors-only'\n    },\n\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-phantomjs-launcher'),\n      require('karma-webpack')\n    ]\n  });\n};"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"micro-fe\",\n  \"version\": \"0.0.1\",\n  \"description\": \"A naive micro frontend solution\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"start\": \"webpack-dev-server  --progress --open --history-api-fallback\",\n    \"build\": \"webpack --config webpack.production.config.js --mode production\",\n    \"test\": \"karma start karma.config.js\"\n  },\n  \"author\": \"Öner Zafer\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/jasmine\": \"2.8.7\",\n    \"@types/node\": \"7.0.0\",\n    \"jasmine-core\": \"3.1.0\",\n    \"karma\": \"2.0.4\",\n    \"karma-jasmine\": \"1.1.2\",\n    \"karma-phantomjs-launcher\": \"1.0.4\",\n    \"karma-webpack\": \"3.0.0\",\n    \"ts-loader\": \"^4.1.0\",\n    \"typescript\": \"^2.6.2\",\n    \"tslint\": \"5.10.0\",\n    \"tslint-loader\": \"3.6.0\",\n    \"webpack\": \"^4.28.4\",\n    \"webpack-cli\": \"^3.2.1\",\n    \"webpack-dev-server\": \"^3.1.14\",\n    \"fork-ts-checker-webpack-plugin\": \"^0.5.2\"\n  },\n  \"dependencies\": {\n    \"rxjs\": \"^6.3.3\"\n  }\n}\n"
  },
  {
    "path": "spec.bundle.js",
    "content": "var testsContext = require.context(\".\", true, /.spec.ts/);\ntestsContext.keys().forEach(testsContext);"
  },
  {
    "path": "src/lib/AppsManager/AppsManager.ts",
    "content": "import { STATUS } from './status.enum';\nimport { MicroAppProvider } from '../Interfaces/AppsManager.interface';\nimport { AnyObj, BoolObj, MicroAppDef, MicroAppsGraph } from '../Interfaces/AppsManager.internal.interface';\n\nexport class AppsManager {\n    private microAppsGraph: MicroAppsGraph = {};\n    private instanceCache: AnyObj = {};\n    private subscriptions: Array<(appList: MicroAppDef[]) => void> = [];\n\n    constructor() {\n        window['AppsManager'] = this;\n    }\n\n    register(microApp: MicroAppProvider | any) {\n        const microAppDef = AppsManager.generateMicroAppDef(microApp);\n        const tempGraph = { ...this.microAppsGraph, [microAppDef.name]: microAppDef };\n        if (this.isDefinedBefore(microAppDef.name)) {\n            console.error(`[Conflict error]: \"${microAppDef.name}\" is defined before.`);\n            return;\n        }\n        if (this.isCyclic(microAppDef.name, undefined, undefined, tempGraph)) {\n            const deps = microApp.deps.join(', ');\n            console.error(`[Dependency error]: \"${microApp.name}\" has cyclic dependency. Check \"${deps}\"`);\n            return;\n        }\n        this.addMicroAppToGraph(microAppDef);\n        microAppDef.deps.forEach(microAppName => {\n            const dep = this.microAppsGraph[microAppName];\n            if (!dep) {\n                this.addMicroAppToGraph(AppsManager.generatePlaceholderMicroAppDef(microAppName));\n            }\n        });\n        this.updateMicroAppStatuses();\n        this.runReadyMicroApps();\n        this.dispatch();\n    }\n\n    isDefinedBefore(microAppName: string): boolean {\n        return this.microAppsGraph[microAppName] && this.microAppsGraph[microAppName].status !== STATUS.NOTFOUND;\n    }\n\n    subscribe(fn: (appList: MicroAppDef[]) => void) {\n        this.subscriptions.push(fn);\n    }\n\n    dispatch() {\n        const appList = Object.keys(this.microAppsGraph).map(microAppName => this.microAppsGraph[microAppName]);\n        const notFoundList = appList.filter(microApp => microApp.status === STATUS.NOTFOUND);\n        this.subscriptions.forEach(fn => {\n            fn.call(null, notFoundList);\n        });\n    }\n\n    private checkDepsRunning(microApp: MicroAppDef): boolean {\n        return (\n            !microApp.deps.length ||\n            (microApp.deps.length &&\n                microApp.deps.filter(microAppName => {\n                    return this.microAppsGraph[microAppName].status === STATUS.RUNNING;\n                }).length === microApp.deps.length)\n        );\n    }\n\n    private isCyclic(vertex: string, visited: BoolObj = {}, recStack: BoolObj = {}, list: MicroAppsGraph): boolean {\n        if (!visited[vertex]) {\n            visited[vertex] = true;\n            recStack[vertex] = true;\n            const neighbours = (list[vertex] && list[vertex].deps) || [];\n            for (let i = 0; i < neighbours.length; i++) {\n                const current = neighbours[i];\n                if (!visited[current] && this.isCyclic(current, visited, recStack, list)) {\n                    return true;\n                } else if (recStack[current]) {\n                    return true;\n                }\n            }\n        }\n        recStack[vertex] = false;\n        return false;\n    }\n\n    private addMicroAppToGraph(microApp: MicroAppDef) {\n        if (!this.microAppsGraph[microApp.name]) {\n            this.microAppsGraph = { ...this.microAppsGraph, [microApp.name]: microApp };\n        } else {\n            this.microAppsGraph = {\n                ...this.microAppsGraph,\n                [microApp.name]: {\n                    ...this.microAppsGraph[microApp.name],\n                    ...microApp,\n                },\n            };\n        }\n    }\n\n    private runReadyMicroApps() {\n        let hasSomethingRun = false;\n        Object.keys(this.microAppsGraph)\n            .filter(\n                microAppName =>\n                    this.microAppsGraph[microAppName].status === STATUS.READY && this.microAppsGraph[microAppName].app\n            )\n            .forEach(microAppName => {\n                const deps = this.provideDepsInstances(microAppName, this.microAppsGraph[microAppName].deps);\n                this.instanceCache[microAppName] = this.microAppsGraph[microAppName].app.initialize(deps);\n                this.microAppsGraph[microAppName].status = STATUS.RUNNING;\n                hasSomethingRun = true;\n            });\n        if (hasSomethingRun) {\n            this.updateMicroAppStatuses();\n        }\n    }\n\n    private updateMicroAppStatuses() {\n        let hasUpdated = false;\n        Object.keys(this.microAppsGraph)\n            .filter(microAppName => this.microAppsGraph[microAppName].status === STATUS.WAITING)\n            .forEach(microAppName => {\n                if (this.checkDepsRunning(this.microAppsGraph[microAppName])) {\n                    this.microAppsGraph[microAppName].status = STATUS.READY;\n                    hasUpdated = true;\n                }\n            });\n        if (hasUpdated) {\n            this.runReadyMicroApps();\n        }\n    }\n\n    private provideDepsInstances(microAppName: string, deps: string[]): { [key: string]: any } {\n        return deps.reduce(\n            (cumulative, current) => {\n                return {\n                    ...cumulative,\n                    [current]: this.instanceCache[current],\n                };\n            },\n            { AppsManager: this, PATH: this.generateAppScopedPath(microAppName) }\n        );\n    }\n\n    private generateAppScopedPath(microAppName: string): string {\n        return this.instanceCache.Config && this.instanceCache.Config.registryPublic\n            ? `${this.instanceCache.Config.registryPublic}/${microAppName}`\n            : '';\n    }\n\n    static generateMicroAppDef(microApp: MicroAppProvider): MicroAppDef {\n        return {\n            name: microApp.name,\n            status: microApp.deps ? STATUS.WAITING : STATUS.READY,\n            deps: microApp.deps ? [...microApp.deps] : [],\n            app: microApp,\n        };\n    }\n\n    static generatePlaceholderMicroAppDef(name: string): MicroAppDef {\n        return {\n            name,\n            status: STATUS.NOTFOUND,\n            deps: [],\n            app: undefined,\n        };\n    }\n}\n"
  },
  {
    "path": "src/lib/AppsManager/status.enum.ts",
    "content": "export enum STATUS {\n    NOTFOUND = 0,\n    WAITING = 1,\n    READY = 2,\n    RUNNING = 3,\n}\n"
  },
  {
    "path": "src/lib/AppsManager/tag.enum.ts",
    "content": "export enum TAG {\n    script = 'script',\n    style = 'style',\n}\n\nexport enum TAG_TYPE {\n    script = 'text/javascript',\n    style = 'text/css',\n}\n"
  },
  {
    "path": "src/lib/Bootstrapper/bootstrapper.ts",
    "content": "import { AppsManager } from '../AppsManager/AppsManager';\nimport { Route } from '..';\nimport { Loader } from '../Loader/Loader';\nimport { MicroAppStore } from '../Store/MicroAppStore';\nimport { RouterOutlet } from '../Router/RouterOutlet';\nimport { MicroAppRouter } from '../Router/Router';\nimport { Provide } from '../Provider/Provider';\nimport { MicroLink } from '../Router/Link';\n\nexport const Bootstrap = (\n    routes?: Route[],\n    config?: { registryApi: string; registryPublic: string; [key: string]: any }\n) => (...entryApps: any[]) => {\n    if (!entryApps.length) {\n        throw new Error('At least one entry app should be provided');\n    }\n\n    const manager = new AppsManager();\n    manager.register(Provide('Routes', routes || []));\n    manager.register(Provide('Config', config));\n    manager.register(MicroAppStore);\n    manager.register(Loader);\n    manager.register(MicroAppRouter);\n    manager.register(RouterOutlet);\n    manager.register(MicroLink);\n    entryApps.forEach(entryApp => {\n        manager.register(entryApp);\n    });\n};\n"
  },
  {
    "path": "src/lib/Decorators/Microfe.decorator.ts",
    "content": "import { MicroAppMeta } from '..';\n\nexport const Microfe = function(meta?: MicroAppMeta ) {\n    return function(WrappedClass) {\n        WrappedClass.deps = meta && meta.deps;\n        WrappedClass.initialize = (args) => new WrappedClass(args);\n    };\n};\n"
  },
  {
    "path": "src/lib/Interfaces/AppsManager.interface.ts",
    "content": "export interface MicroAppMeta {\n    deps?: string[];\n}\n\nexport interface MicroAppProvider extends MicroAppMeta {\n    name: string;\n    initialize: (deps: {[key: string]: any}) => void;\n}\n"
  },
  {
    "path": "src/lib/Interfaces/AppsManager.internal.interface.ts",
    "content": "import { STATUS } from '../AppsManager/status.enum';\nimport { MicroAppProvider } from './AppsManager.interface';\n\nexport interface MicroAppDef {\n    name: string; // must be unique\n    status: STATUS;\n    deps: string[];\n    app: MicroAppProvider;\n}\n\nexport interface MicroAppsGraph {\n    [key: string]: MicroAppDef;\n}\n\nexport interface BoolObj {\n    [key: string]: boolean;\n}\n\nexport interface AnyObj {\n    [key: string]: any;\n}\n"
  },
  {
    "path": "src/lib/Interfaces/Config.interface.ts",
    "content": "export interface ConfigInterface {\n    registryApi: string;\n    registryPublic: string;\n    [key: string]: any;\n}\n"
  },
  {
    "path": "src/lib/Interfaces/Router.interface.ts",
    "content": "export interface Route {\n    path: string;\n    tagName?: string;\n    redirectTo?: string;\n    microApp?: string;\n}\n\nexport interface ResolvedRoute {\n    path: string;\n    resolvedPath: string;\n    route: Route;\n    query?: {[key: string]: string | number | boolean};\n    hash?: string;\n}\n"
  },
  {
    "path": "src/lib/Loader/Loader.ts",
    "content": "import { AppsManager } from '../AppsManager/AppsManager';\nimport { TAG, TAG_TYPE } from '../AppsManager/tag.enum';\nimport { Microfe } from '../Decorators/Microfe.decorator';\nimport { MicroAppDef } from '../Interfaces/AppsManager.internal.interface';\nimport { ConfigInterface } from '../Interfaces/Config.interface';\n\n@Microfe({\n    deps: ['Config']\n})\nexport class Loader {\n    private loadingList: string[] = [];\n    private readonly apiUrl: string = '';\n    private appsManager: AppsManager;\n    constructor({AppsManager, Config}: {AppsManager: AppsManager, Config: ConfigInterface}) {\n        this.apiUrl = (Config && Config.registryApi) || '';\n        this.appsManager = AppsManager;\n        this.appsManager.subscribe(this.onNotFoundApp.bind(this));\n    }\n\n    fetchMicroApp(microAppName: string) {\n        if (!this.appsManager.isDefinedBefore(microAppName)) {\n            const id = `${microAppName}_js_${new Date().getTime()}`;\n            Loader.injectJsToHead(id, `${this.apiUrl}/${microAppName}.js`);\n        }\n    }\n\n    private onNotFoundApp(appList: MicroAppDef[]) {\n        appList.forEach(({ name }) => {\n            if (this.loadingList.indexOf(name) === -1) {\n                this.loadingList.push(name);\n                this.fetchMicroApp(name);\n            }\n        });\n    }\n\n    private static injectJsToHead(id: string, appUrl: string) {\n        const script = document.createElement(TAG.script) as HTMLScriptElement;\n        script.id = id;\n        script.type = TAG_TYPE.script;\n        script.src = appUrl;\n        script.setAttribute('async', '');\n        const firstScriptTag = document.getElementsByTagName('script')[0];\n        firstScriptTag.parentNode.insertBefore(script, firstScriptTag);\n    }\n}\n"
  },
  {
    "path": "src/lib/Provider/Provider.ts",
    "content": "export const Provide = (name: string, provideable: any) => ({\n    name,\n    initialize: () => provideable,\n});"
  },
  {
    "path": "src/lib/Router/Link.ts",
    "content": "import { MicroAppRouter } from './Router';\nimport { Microfe } from '../Decorators/Microfe.decorator';\n\n@Microfe({\n    deps: ['MicroAppRouter'],\n})\nexport class MicroLink {\n    constructor({ MicroAppRouter }: { MicroAppRouter: MicroAppRouter }) {\n        class MicroLinkElement extends HTMLElement {\n            private styleText = `\n                :host {\n                    display: inline-block;\n                    cursor: pointer;\n                }\n            `;\n            constructor() {\n                super();\n                this.attachShadow({ mode: 'open' });\n                MicroAppRouter.onChange(this.handleRouteChange);\n                this.render();\n            }\n\n            get href() {\n                return this.getAttribute('href');\n            }\n\n            set href(newValue) {\n                this.setAttribute('href', newValue);\n            }\n\n            onclick = () => {\n                MicroAppRouter.navigate(this.href);\n            };\n\n            handleRouteChange = () => {\n                MicroAppRouter.isActive(this.href) ? this.classList.add('active') : this.classList.remove('active');\n            };\n\n            private render = () => {\n                const styleElm = document.createElement('style');\n                styleElm.innerHTML = this.styleText;\n                this.shadowRoot.appendChild(styleElm);\n                this.shadowRoot.appendChild(document.createElement('slot'));\n            };\n        }\n        customElements.define('micro-link', MicroLinkElement);\n    }\n}\n"
  },
  {
    "path": "src/lib/Router/Router.ts",
    "content": "import { ResolvedRoute, Route } from '../Interfaces/Router.interface';\nimport { Microfe } from '../Decorators/Microfe.decorator';\n\n@Microfe({\n    deps: ['Routes'],\n})\nexport class MicroAppRouter {\n    private oldRoute: ResolvedRoute;\n    private onChangeHandlers: Array<(oldPath: string, newPath: string) => void> = [];\n    private routes: Array<Route>;\n\n    constructor({ Routes }: { Routes: Array<Route> }) {\n        this.routes = Routes;\n        window.onpopstate = () => {\n            this.navigate(window.location.pathname);\n        };\n\n        this.navigate(window.location.pathname, true);\n    }\n\n    navigate(path: string, isSilent: boolean = false) {\n        const resolvedRoute = this.resolve(MicroAppRouter.cleanPath(path));\n        if (resolvedRoute) {\n            if (!this.oldRoute || this.oldRoute.path !== resolvedRoute.path) {\n                if (!isSilent) {\n                    window.history.pushState(undefined, undefined, resolvedRoute.path);\n                }\n                this.changed(resolvedRoute);\n            }\n        } else {\n            window.location.href = MicroAppRouter.cleanPath(path);\n        }\n    }\n\n    onChange(fn: (oldPath: string, newPath: string, resolvedRoute?: ResolvedRoute) => void) {\n        this.onChangeHandlers.push(fn);\n        if (this.oldRoute) {\n            fn.apply(null, [undefined, this.oldRoute.path, this.oldRoute]);\n        }\n    }\n\n    isActive(pathToCheck: string): boolean {\n        return this.oldRoute && MicroAppRouter.isHit(this.oldRoute.route, MicroAppRouter.cleanPath(pathToCheck));\n    }\n\n    private changed(resolvedRoute: ResolvedRoute) {\n        const oldPath = this.oldRoute && this.oldRoute.path;\n        this.oldRoute = {\n            ...resolvedRoute,\n        };\n        this.onChangeHandlers.forEach(fn => {\n            fn.apply(null, [oldPath, resolvedRoute.path, resolvedRoute]);\n        });\n    }\n\n    private resolve(path: string): ResolvedRoute {\n        const foundRoute = this.routes.find(route => MicroAppRouter.isHit(route, path));\n        if (foundRoute && !foundRoute.redirectTo) {\n            return {\n                path: path,\n                resolvedPath: foundRoute.path,\n                route: {\n                    ...foundRoute,\n                },\n            };\n        } else if (foundRoute && foundRoute.redirectTo) {\n            return this.resolve(foundRoute.redirectTo);\n        } else {\n            return undefined;\n        }\n    }\n\n    private static isHit(route: Route, path: string): boolean {\n        return route.path === '/' ? route.path === path : MicroAppRouter.pathToRegexp(route.path).test(path);\n    }\n\n    private static pathToRegexp(path: string): RegExp {\n        return new RegExp(`^${path.replace(/\\\\\\//g, '/').replace('*', '.*?')}`);\n    }\n\n    private static cleanPath(path: string): string {\n        return path && path.replace(new RegExp(window.location.origin), '');\n    }\n}\n"
  },
  {
    "path": "src/lib/Router/RouterOutlet.ts",
    "content": "import { Loader } from '../Loader/Loader';\nimport { MicroAppRouter } from './Router';\nimport { ResolvedRoute } from '../Interfaces/Router.interface';\nimport { Microfe } from '../Decorators/Microfe.decorator';\n\n@Microfe({\n    deps: ['Loader', 'MicroAppRouter'],\n})\nexport class RouterOutlet {\n    constructor({ Loader, MicroAppRouter }: { Loader: Loader; MicroAppRouter: MicroAppRouter }) {\n        class RouterOutletElement extends HTMLElement {\n            shadow = this.attachShadow({ mode: 'open' });\n            constructor() {\n                super();\n                MicroAppRouter.onChange((oldPath, newPath, resolvedRoute) =>\n                    this.handlePath(oldPath, newPath, resolvedRoute)\n                );\n            }\n\n            private handlePath(oldPath: string, newPath: string, resolvedRoute: ResolvedRoute) {\n                if (resolvedRoute) {\n                    Loader.fetchMicroApp(resolvedRoute.route.microApp);\n                    const appTag = document.createElement(resolvedRoute.route.tagName);\n                    while (this.shadow.firstChild) {\n                        this.shadow.removeChild(this.shadow.firstChild);\n                    }\n                    this.shadow.appendChild(appTag);\n                }\n            }\n        }\n        customElements.define('micro-router', RouterOutletElement);\n    }\n}\n"
  },
  {
    "path": "src/lib/Store/MicroAppStore.ts",
    "content": "import { BehaviorSubject } from 'rxjs';\nimport { pluck, scan } from 'rxjs/operators';\nimport { Microfe } from '../Decorators/Microfe.decorator';\n\n@Microfe()\nexport class MicroAppStore  {\n    private subject = new BehaviorSubject({});\n    private source = this.subject.pipe(scan((state, action) => this.applyReducers(state, action), this.subject.value));\n    private reducers = {};\n\n    constructor() {\n        this.dispatch({type: '[@@MicroAppStore]: Init'});\n    }\n\n    private applyReducers(state, action) {\n        return Object.keys(this.reducers).reduce((cum, curr) => {\n            return {\n                ...cum,\n                [curr]: this.reducers[curr](state[curr], action) || undefined,\n            };\n        }, {});\n    }\n\n    public addReducer(reducerTreePiece) {\n        // TODO: validate the reducer schema\n        this.reducers = {\n            ...this.reducers,\n            ...reducerTreePiece,\n        };\n    }\n\n    public dispatch(action: { type: string; payload?: any }) {\n        this.subject.next(action);\n    }\n\n    public select(...selector) {\n        return this.source.pipe(pluck(...selector));\n    }\n}\n"
  },
  {
    "path": "src/lib/index.ts",
    "content": "// PUBLIC INTERFACES\nexport * from './Interfaces/AppsManager.interface';\nexport * from './Interfaces/Router.interface';\nexport * from './Interfaces/Config.interface';\n\n// PUBLIC API\nexport * from './Bootstrapper/bootstrapper';\nexport * from './Decorators/Microfe.decorator';\nexport * from './Provider/Provider';\n"
  },
  {
    "path": "src/main.ts",
    "content": "import { Microfe, Bootstrap, Route, ConfigInterface } from './lib';\n\n@Microfe({\n    deps: ['LayoutApp'],\n})\nclass Main {\n    constructor() {\n        console.log('Initialised');\n    }\n}\n\nconst Routes: Route[] = [\n    { path: '/', redirectTo: '/angular' },\n    { path: '/angular', microApp: 'demoAngular', tagName: 'demo-angular' },\n    { path: '/react', microApp: 'reactDemo', tagName: 'react-demo' },\n    { path: '/static', microApp: 'staticApp', tagName: 'static-app' },\n    { path: '*', microApp: 'NotFoundApp', tagName: 'not-found-app' },\n];\n\nconst Config: ConfigInterface = {\n    registryApi: 'http://localhost:3000/registry',\n    registryPublic: 'http://localhost:3000',\n};\n\nBootstrap(Routes, Config)(Main);\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"sourceMap\": true,\n        \"typeRoots\": [\"node_modules/@types\"],\n        \"lib\": [\"es2017\", \"dom\"],\n        \"target\": \"es6\",\n        \"moduleResolution\": \"node\",\n        \"experimentalDecorators\": true\n    }\n}\n"
  },
  {
    "path": "tslint.json",
    "content": "{\n    \"jsRules\": {\n        \"class-name\": true,\n        \"comment-format\": [\n            true,\n            \"check-space\"\n        ],\n        \"indent\": [\n            true,\n            \"spaces\"\n        ],\n        \"no-duplicate-variable\": true,\n        \"no-eval\": true,\n        \"no-trailing-whitespace\": true,\n        \"no-unsafe-finally\": true,\n        \"one-line\": [\n            true,\n            \"check-open-brace\",\n            \"check-whitespace\"\n        ],\n        \"quotemark\": [\n            true,\n            \"single\"\n        ],\n        \"semicolon\": [\n            true,\n            \"always\"\n        ],\n        \"triple-equals\": [\n            true,\n            \"allow-null-check\"\n        ],\n        \"variable-name\": [\n            true,\n            \"ban-keywords\"\n        ],\n        \"whitespace\": [\n            true,\n            \"check-branch\",\n            \"check-decl\",\n            \"check-operator\",\n            \"check-separator\",\n            \"check-type\"\n        ]\n    },\n    \"rules\": {\n        \"class-name\": true,\n        \"comment-format\": [\n            true,\n            \"check-space\"\n        ],\n        \"indent\": [\n            true,\n            \"spaces\"\n        ],\n        \"no-eval\": true,\n        \"no-internal-module\": true,\n        \"no-trailing-whitespace\": true,\n        \"no-unsafe-finally\": true,\n        \"no-var-keyword\": false,\n        \"one-line\": [\n            true,\n            \"check-open-brace\",\n            \"check-whitespace\"\n        ],\n        \"quotemark\": [\n            true,\n            \"single\"\n        ],\n        \"semicolon\": [\n            true,\n            \"always\"\n        ],\n        \"triple-equals\": [\n            true,\n            \"allow-null-check\"\n        ],\n        \"typedef-whitespace\": [\n            true,\n            {\n                \"call-signature\": \"nospace\",\n                \"index-signature\": \"nospace\",\n                \"parameter\": \"nospace\",\n                \"property-declaration\": \"nospace\",\n                \"variable-declaration\": \"nospace\"\n            }\n        ],\n        \"variable-name\": [\n            true,\n            \"ban-keywords\"\n        ],\n        \"whitespace\": [\n            true,\n            \"check-branch\",\n            \"check-decl\",\n            \"check-operator\",\n            \"check-separator\",\n            \"check-type\"\n        ]\n    }\n}"
  },
  {
    "path": "webpack.config.js",
    "content": "'use strict';\nconst rxPaths = require('rxjs/_esm5/path-mapping');\nconst ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');\n\nmodule.exports = {\n    devtool: 'inline-source-map',\n    entry: './src/main.ts',\n    output: {\n        pathinfo: false,\n    },\n    mode: 'development',\n    optimization: {\n        removeAvailableModules: false,\n        removeEmptyChunks: false,\n        splitChunks: false,\n    },\n    module: {\n        rules: [\n            {\n                test: /\\.tsx?$/,\n                use: [\n                    {\n                        loader: 'ts-loader',\n                        options: {\n                            transpileOnly: true,\n                            experimentalWatchApi: true,\n                        },\n                    },\n                ],\n            },\n        ],\n    },\n    resolve: {\n        extensions: ['.ts', '.tsx', '.js'],\n        modules: ['./node_modules'],\n        alias: rxPaths(),\n    },\n    plugins: [\n        new ForkTsCheckerWebpackPlugin({\n            tslintAutoFix: true,\n            formatter: 'codeframe',\n        }),\n    ],\n};\n"
  },
  {
    "path": "webpack.production.config.js",
    "content": "'use strict';\nconst rxPaths = require('rxjs/_esm5/path-mapping');\nconst ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');\n\nmodule.exports = {\n    devtool: 'inline-source-map',\n    entry: './src/main.ts',\n    output: {\n        pathinfo: false,\n    },\n    mode: 'production',\n    module: {\n        rules: [\n            {\n                test: /\\.tsx?$/,\n                use: [\n                    {\n                        loader: 'ts-loader',\n                        options: {\n                            transpileOnly: false,\n                        },\n                    },\n                ],\n            },\n        ],\n    },\n    resolve: {\n        extensions: ['.ts', '.tsx', '.js'],\n        modules: ['./node_modules'],\n        alias: rxPaths(),\n    },\n    plugins: [\n        new ForkTsCheckerWebpackPlugin({\n            tslintAutoFix: true,\n            formatter: 'codeframe',\n        }),\n    ],\n};\n"
  }
]