├── .npmrc ├── apps ├── dashboard │ ├── src │ │ ├── assets │ │ │ ├── .gitkeep │ │ │ └── logo.png │ │ ├── main.ts │ │ ├── favicon.ico │ │ ├── svelte-shims.d.ts │ │ ├── vue-shims.d.ts │ │ ├── index.html │ │ ├── bootstrap.tsx │ │ ├── app │ │ │ └── app.tsx │ │ └── styles.css │ ├── .babelrc │ ├── project.json │ ├── tsconfig.json │ ├── tsconfig.app.json │ └── webpack.config.js ├── budget │ ├── src │ │ ├── styles.css │ │ ├── main.ts │ │ ├── vue-shims.d.ts │ │ ├── bootstrap.ts │ │ └── app │ │ │ ├── App.spec.ts │ │ │ └── App.vue │ ├── public │ │ └── logo.png │ ├── project.json │ ├── tsconfig.app.json │ ├── index.html │ ├── tsconfig.json │ └── vite.config.ts ├── products │ ├── package.json │ ├── public │ │ ├── logo.png │ │ ├── global.css │ │ └── favicon.png │ ├── eslint.config.cjs │ ├── src │ │ ├── main.ts │ │ ├── svelte-shims.d.ts │ │ ├── bootstrap.ts │ │ └── App.svelte │ ├── svelte.config.cjs │ ├── tsconfig.json │ ├── index.html │ ├── tsconfig.spec.json │ ├── project.json │ ├── .eslintrc.js │ ├── tsconfig.app.json │ └── vite.config.ts └── cart │ ├── public │ ├── logo.png │ └── favicon.ico │ ├── src │ ├── main.ts │ ├── styles.css │ ├── app │ │ ├── app.config.ts │ │ └── app.component.ts │ ├── bootstrap.ts │ ├── index.html │ └── services │ │ └── redux.service.ts │ ├── tsconfig.editor.json │ ├── tsconfig.app.json │ ├── tsconfig.json │ ├── eslint.config.cjs │ ├── webpack.config.js │ └── project.json ├── .prettierrc ├── libs └── shared │ ├── src │ ├── index.ts │ ├── types │ │ └── index.ts │ ├── environments │ │ └── index.ts │ ├── store │ │ ├── index.ts │ │ ├── counterSlice.ts │ │ └── appSlice.ts │ └── styles │ │ └── index.css │ ├── README.md │ ├── tsconfig.lib.json │ ├── package.json │ ├── tsconfig.json │ └── project.json ├── .prettierignore ├── .vscode └── extensions.json ├── .editorconfig ├── .gitignore ├── tsconfig.base.json ├── eslint.config.cjs ├── package.json ├── nx.json └── README.md /.npmrc: -------------------------------------------------------------------------------- 1 | legacy-peer-deps=true -------------------------------------------------------------------------------- /apps/dashboard/src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/dashboard/src/main.ts: -------------------------------------------------------------------------------- 1 | import('./bootstrap') -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /apps/budget/src/styles.css: -------------------------------------------------------------------------------- 1 | @import url('../../../libs/shared/src/styles/index.css'); 2 | -------------------------------------------------------------------------------- /apps/products/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "products", 3 | "version": "0.0.1" 4 | } 5 | -------------------------------------------------------------------------------- /apps/cart/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IrfanAli17899/micro-frontends/HEAD/apps/cart/public/logo.png -------------------------------------------------------------------------------- /libs/shared/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './environments'; 2 | export * from './store'; 3 | export * from './types'; 4 | -------------------------------------------------------------------------------- /apps/budget/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IrfanAli17899/micro-frontends/HEAD/apps/budget/public/logo.png -------------------------------------------------------------------------------- /apps/cart/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IrfanAli17899/micro-frontends/HEAD/apps/cart/public/favicon.ico -------------------------------------------------------------------------------- /apps/products/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IrfanAli17899/micro-frontends/HEAD/apps/products/public/logo.png -------------------------------------------------------------------------------- /apps/cart/src/main.ts: -------------------------------------------------------------------------------- 1 | import('./bootstrap').then(({ bootstrap }) => { 2 | bootstrap(); 3 | }).catch(err => console.error(err)); -------------------------------------------------------------------------------- /apps/dashboard/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IrfanAli17899/micro-frontends/HEAD/apps/dashboard/src/favicon.ico -------------------------------------------------------------------------------- /libs/shared/src/types/index.ts: -------------------------------------------------------------------------------- 1 | export interface Product { 2 | id: string; 3 | name: string; 4 | price: number; 5 | } -------------------------------------------------------------------------------- /apps/dashboard/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IrfanAli17899/micro-frontends/HEAD/apps/dashboard/src/assets/logo.png -------------------------------------------------------------------------------- /apps/products/eslint.config.cjs: -------------------------------------------------------------------------------- 1 | const baseConfig = require('../../eslint.config.cjs'); 2 | 3 | module.exports = [...baseConfig]; 4 | -------------------------------------------------------------------------------- /apps/products/src/main.ts: -------------------------------------------------------------------------------- 1 | import('./bootstrap') 2 | .then(({ bootstrap }) => bootstrap(document.body)) 3 | .catch((err) => console.error(err)); -------------------------------------------------------------------------------- /apps/budget/src/main.ts: -------------------------------------------------------------------------------- 1 | import('./bootstrap').then(({ bootstrap }) => { 2 | bootstrap('#root'); 3 | }).catch((err) => console.error(err)); 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Add files here to ignore them from prettier formatting 2 | /dist 3 | /coverage 4 | /.nx/cache 5 | /.nx/workspace-data 6 | .angular 7 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "nrwl.angular-console", 4 | "esbenp.prettier-vscode", 5 | "dbaeumer.vscode-eslint" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /apps/cart/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | @import url('../../../libs/shared/src/styles/index.css'); 3 | -------------------------------------------------------------------------------- /libs/shared/README.md: -------------------------------------------------------------------------------- 1 | # shared 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Building 6 | 7 | Run `nx build shared` to build the library. 8 | -------------------------------------------------------------------------------- /apps/dashboard/src/svelte-shims.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.svelte' { 2 | import type { ComponentType } from 'svelte'; 3 | const component: ComponentType; 4 | export default component; 5 | } -------------------------------------------------------------------------------- /apps/products/src/svelte-shims.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.svelte' { 2 | import type { ComponentType } from 'svelte'; 3 | const component: ComponentType; 4 | export default component; 5 | } -------------------------------------------------------------------------------- /apps/budget/src/vue-shims.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import { defineComponent } from 'vue'; 3 | const component: ReturnType; 4 | export default component; 5 | } 6 | -------------------------------------------------------------------------------- /apps/dashboard/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@nx/react/babel", 5 | { 6 | "runtime": "automatic" 7 | } 8 | ] 9 | ], 10 | "plugins": [] 11 | } 12 | -------------------------------------------------------------------------------- /apps/dashboard/src/vue-shims.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import { defineComponent } from 'vue'; 3 | const component: ReturnType; 4 | export default component; 5 | } 6 | -------------------------------------------------------------------------------- /apps/cart/tsconfig.editor.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src/**/*.ts"], 4 | "compilerOptions": {}, 5 | "exclude": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts"] 6 | } 7 | -------------------------------------------------------------------------------- /libs/shared/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "declaration": true, 6 | "types": ["node"] 7 | }, 8 | "include": ["src/**/*.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /apps/budget/src/bootstrap.ts: -------------------------------------------------------------------------------- 1 | import './styles.css'; 2 | import { createApp } from 'vue'; 3 | import App from './app/App.vue'; 4 | 5 | export const bootstrap = (container:string) => { 6 | const app = createApp(App); 7 | app.mount(container); 8 | }; 9 | -------------------------------------------------------------------------------- /apps/cart/src/app/app.config.ts: -------------------------------------------------------------------------------- 1 | import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; 2 | 3 | export const appConfig: ApplicationConfig = { 4 | providers: [ 5 | provideZoneChangeDetection({ eventCoalescing: true }), 6 | ], 7 | }; 8 | -------------------------------------------------------------------------------- /libs/shared/src/environments/index.ts: -------------------------------------------------------------------------------- 1 | export const config = { 2 | production: false, 3 | cartUrl: 'http://localhost:4201', 4 | budgetUrl: 'http://localhost:4202', 5 | productsUrl: 'http://localhost:4203' 6 | }; 7 | 8 | export type Config = typeof config; 9 | -------------------------------------------------------------------------------- /apps/products/svelte.config.cjs: -------------------------------------------------------------------------------- 1 | const sveltePreprocess = require('svelte-preprocess'); 2 | 3 | module.exports = { 4 | // Consult https://github.com/sveltejs/svelte-preprocess 5 | // for more information about preprocessors 6 | preprocess: sveltePreprocess(), 7 | }; 8 | -------------------------------------------------------------------------------- /libs/shared/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@micro-frontend-tutorial/shared", 3 | "version": "0.0.1", 4 | "dependencies": { 5 | "tslib": "^2.3.0" 6 | }, 7 | "type": "commonjs", 8 | "main": "./src/index.js", 9 | "typings": "./src/index.d.ts", 10 | "private": true 11 | } 12 | -------------------------------------------------------------------------------- /apps/budget/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "budget", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "projectType": "application", 5 | "sourceRoot": "apps/budget/src", 6 | "// targets": "to see all targets run: nx products project budget --web", 7 | "targets": {} 8 | } 9 | -------------------------------------------------------------------------------- /apps/cart/src/bootstrap.ts: -------------------------------------------------------------------------------- 1 | import { bootstrapApplication } from '@angular/platform-browser'; 2 | import { appConfig } from './app/app.config'; 3 | import { AppComponent } from './app/app.component'; 4 | 5 | export const bootstrap = () => { 6 | return bootstrapApplication(AppComponent, appConfig) 7 | } 8 | -------------------------------------------------------------------------------- /apps/budget/src/app/App.spec.ts: -------------------------------------------------------------------------------- 1 | import { mount } from '@vue/test-utils'; 2 | import App from './App.vue'; 3 | 4 | describe('App', () => { 5 | it('renders properly', async () => { 6 | const wrapper = mount(App, {}); 7 | expect(wrapper.text()).toContain('Welcome budget 👋'); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /apps/products/src/bootstrap.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import App from './App.svelte'; 3 | 4 | export const bootstrap = (container: HTMLElement) => { 5 | const app = new App({ 6 | target: container, 7 | props: { 8 | name: 'products', 9 | }, 10 | }); 11 | 12 | return app; 13 | } 14 | 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /apps/cart/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "types": [] 6 | }, 7 | "files": ["src/main.ts"], 8 | "include": ["src/**/*.d.ts"], 9 | "exclude": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /apps/dashboard/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dashboard", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "apps/dashboard/src", 5 | "projectType": "application", 6 | "tags": [], 7 | "// targets": "to see all targets run: nx products project dashboard --web", 8 | "targets": {} 9 | } 10 | -------------------------------------------------------------------------------- /apps/dashboard/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Dashboard 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /apps/cart/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | cart 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /apps/products/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": false, 4 | "esModuleInterop": false, 5 | "allowSyntheticDefaultImports": true, 6 | "types": ["vite/client"] 7 | }, 8 | "files": [], 9 | "include": [], 10 | "references": [ 11 | { 12 | "path": "./tsconfig.app.json" 13 | } 14 | ], 15 | "extends": "../../tsconfig.base.json" 16 | } 17 | -------------------------------------------------------------------------------- /apps/budget/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "types": ["vite/client"] 6 | }, 7 | "exclude": [ 8 | "src/**/*.spec.ts", 9 | "src/**/*.test.ts", 10 | "src/**/*.spec.vue", 11 | "src/**/*.test.vue" 12 | ], 13 | "include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.vue"] 14 | } 15 | -------------------------------------------------------------------------------- /apps/budget/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | budget 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /apps/dashboard/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react-jsx", 4 | "allowJs": false, 5 | "esModuleInterop": false, 6 | "allowSyntheticDefaultImports": true, 7 | "strict": true 8 | }, 9 | "files": [], 10 | "include": [ 11 | "./svelte-shims.d.ts" 12 | ], 13 | "references": [ 14 | { 15 | "path": "./tsconfig.app.json" 16 | } 17 | ], 18 | "extends": "../../tsconfig.base.json" 19 | } 20 | -------------------------------------------------------------------------------- /apps/products/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Svelte + TS + Vite App 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /apps/products/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["node"] 7 | }, 8 | "include": [ 9 | "src/**/*.test.ts", 10 | "src/**/*.spec.ts", 11 | "src/**/*.test.tsx", 12 | "src/**/*.spec.tsx", 13 | "src/**/*.test.js", 14 | "src/**/*.spec.js", 15 | "src/**/*.test.jsx", 16 | "src/**/*.spec.jsx", 17 | "src/**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /apps/dashboard/src/bootstrap.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import { Provider } from 'react-redux'; 3 | import * as ReactDOM from 'react-dom/client'; 4 | 5 | import { store } from '@micro-frontend-tutorial/shared'; 6 | 7 | import App from './app/app'; 8 | 9 | const root = ReactDOM.createRoot( 10 | document.getElementById('root') as HTMLElement 11 | ); 12 | root.render( 13 | 14 | 15 | 16 | 17 | 18 | ); 19 | -------------------------------------------------------------------------------- /apps/budget/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "esModuleInterop": false, 5 | "allowSyntheticDefaultImports": true, 6 | "strict": true, 7 | "jsx": "preserve", 8 | "jsxImportSource": "vue", 9 | "moduleResolution": "node", 10 | "resolveJsonModule": true 11 | }, 12 | "files": [], 13 | "include": [], 14 | "references": [ 15 | { 16 | "path": "./tsconfig.app.json" 17 | } 18 | ], 19 | "extends": "../../tsconfig.base.json" 20 | } 21 | -------------------------------------------------------------------------------- /libs/shared/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "forceConsistentCasingInFileNames": true, 6 | "strict": true, 7 | "noImplicitOverride": true, 8 | "noImplicitReturns": true, 9 | "noFallthroughCasesInSwitch": true, 10 | "noPropertyAccessFromIndexSignature": true 11 | }, 12 | "files": [], 13 | "include": [], 14 | "references": [ 15 | { 16 | "path": "./tsconfig.lib.json" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /libs/shared/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shared", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "libs/shared/src", 5 | "projectType": "library", 6 | "targets": { 7 | "build": { 8 | "executor": "@nx/js:tsc", 9 | "outputs": ["{options.outputPath}"], 10 | "options": { 11 | "outputPath": "dist/libs/shared", 12 | "main": "libs/shared/src/index.ts", 13 | "tsConfig": "libs/shared/tsconfig.lib.json", 14 | "assets": ["libs/shared/*.md"] 15 | } 16 | } 17 | }, 18 | "tags": [] 19 | } 20 | -------------------------------------------------------------------------------- /apps/products/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "products", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "apps/products/src", 5 | "projectType": "application", 6 | "tags": [], 7 | "targets": { 8 | "lint": { 9 | "executor": "@nx/eslint:lint", 10 | "options": { 11 | "lintFilePatterns": ["apps/products/**/*.{ts,svelte,spec.ts}"] 12 | } 13 | }, 14 | "check": { 15 | "executor": "nx:run-commands", 16 | "options": { 17 | "command": "svelte-check", 18 | "cwd": "apps/products" 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /apps/dashboard/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "types": [ 6 | "node", 7 | "@nx/react/typings/cssmodule.d.ts", 8 | "@nx/react/typings/image.d.ts" 9 | ] 10 | }, 11 | "exclude": [ 12 | "src/**/*.spec.ts", 13 | "src/**/*.test.ts", 14 | "src/**/*.spec.tsx", 15 | "src/**/*.test.tsx", 16 | "src/**/*.spec.js", 17 | "src/**/*.test.js", 18 | "src/**/*.spec.jsx", 19 | "src/**/*.test.jsx" 20 | ], 21 | "include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"] 22 | } 23 | -------------------------------------------------------------------------------- /libs/shared/src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { configureStore } from '@reduxjs/toolkit' 2 | import counterReducer from './counterSlice'; 3 | import appReducer from './appSlice'; 4 | 5 | export const store = configureStore({ 6 | reducer: { 7 | counter: counterReducer, 8 | app: appReducer, 9 | }, 10 | }) 11 | 12 | // Infer the `RootState` and `AppDispatch` types from the store itself 13 | export type RootState = ReturnType 14 | // Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState} 15 | export type AppDispatch = typeof store.dispatch 16 | 17 | export * from './counterSlice'; 18 | export * from './appSlice'; -------------------------------------------------------------------------------- /libs/shared/src/store/counterSlice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit' 2 | 3 | export interface CounterState { 4 | value: number 5 | } 6 | 7 | const initialState: CounterState = { 8 | value: 0, 9 | } 10 | 11 | export const counterSlice = createSlice({ 12 | name: 'counter', 13 | initialState, 14 | reducers: { 15 | cart: (state) => { 16 | state.value += 1 17 | }, 18 | budget: (state) => { 19 | state.value -= 1 20 | } 21 | }, 22 | }) 23 | 24 | // Action creators are generated for each case reducer function 25 | export const { cart, budget } = counterSlice.actions 26 | 27 | export default counterSlice.reducer -------------------------------------------------------------------------------- /apps/products/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | plugins: ['svelte3', '@typescript-eslint'], 4 | extends: ['../../.eslintrc.json'], 5 | ignorePatterns: ['!**/*', 'vite.config.ts'], 6 | overrides: [ 7 | { 8 | files: ['*.ts', '*.js', '*.svelte'], 9 | rules: {}, 10 | }, 11 | { 12 | files: ['*.ts', '*.tsx'], 13 | rules: {}, 14 | }, 15 | { 16 | files: ['*.js', '*.jsx'], 17 | rules: {}, 18 | }, 19 | { 20 | files: ['*.svelte'], 21 | processor: 'svelte3/svelte3', 22 | }, 23 | ], 24 | settings: { 25 | 'svelte3/typescript': require('typescript'), 26 | }, 27 | }; 28 | -------------------------------------------------------------------------------- /apps/products/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "moduleResolution": "node", 5 | "target": "es2017", 6 | "verbatimModuleSyntax": true, 7 | "isolatedModules": true, 8 | "sourceMap": true, 9 | "types": ["svelte", "node", "vite/client"], 10 | "strict": false, 11 | "esModuleInterop": true, 12 | "skipLibCheck": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "checkJs": true, 15 | "allowJs": true 16 | }, 17 | "files": [], 18 | "include": [], 19 | "references": [ 20 | { 21 | "path": "./tsconfig.app.json" 22 | }, 23 | { 24 | "path": "./tsconfig.spec.json" 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /apps/cart/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "forceConsistentCasingInFileNames": true, 5 | "strict": true, 6 | "noImplicitOverride": true, 7 | "noPropertyAccessFromIndexSignature": true, 8 | "noImplicitReturns": true, 9 | "noFallthroughCasesInSwitch": true 10 | }, 11 | "files": [], 12 | "include": [], 13 | "references": [ 14 | { 15 | "path": "./tsconfig.editor.json" 16 | }, 17 | { 18 | "path": "./tsconfig.app.json" 19 | } 20 | ], 21 | "extends": "../../tsconfig.base.json", 22 | "angularCompilerOptions": { 23 | "enableI18nLegacyMessageIdFormat": false, 24 | "strictInjectionParameters": true, 25 | "strictInputAccessModifiers": true, 26 | "strictTemplates": true 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | dist 5 | tmp 6 | out-tsc 7 | 8 | # dependencies 9 | node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | yarn-error.log 34 | testem.log 35 | /typings 36 | 37 | # System Files 38 | .DS_Store 39 | Thumbs.db 40 | 41 | .nx/cache 42 | .nx/workspace-data 43 | 44 | .angular 45 | 46 | vite.config.*.timestamp* 47 | vitest.config.*.timestamp* -------------------------------------------------------------------------------- /apps/cart/eslint.config.cjs: -------------------------------------------------------------------------------- 1 | const nx = require('@nx/eslint-plugin'); 2 | const baseConfig = require('../../eslint.config.cjs'); 3 | 4 | module.exports = [ 5 | ...baseConfig, 6 | ...nx.configs['flat/angular'], 7 | ...nx.configs['flat/angular-template'], 8 | { 9 | files: ['**/*.ts'], 10 | rules: { 11 | '@angular-eslint/directive-selector': [ 12 | 'error', 13 | { 14 | type: 'attribute', 15 | prefix: 'app', 16 | style: 'camelCase', 17 | }, 18 | ], 19 | '@angular-eslint/component-selector': [ 20 | 'error', 21 | { 22 | type: 'element', 23 | prefix: 'app', 24 | style: 'kebab-case', 25 | }, 26 | ], 27 | }, 28 | }, 29 | { 30 | files: ['**/*.html'], 31 | // Override or add rules here 32 | rules: {}, 33 | }, 34 | ]; 35 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "sourceMap": true, 6 | "declaration": false, 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "importHelpers": true, 11 | "target": "es2015", 12 | "module": "esnext", 13 | "lib": ["es2020", "dom"], 14 | "skipLibCheck": true, 15 | "skipDefaultLibCheck": true, 16 | "baseUrl": ".", 17 | "paths": { 18 | "@micro-frontend-tutorial/shared/*": ["libs/shared/src/*"], 19 | "@micro-frontend-tutorial/shared": ["libs/shared/src/index.ts"], 20 | "budget/Module": ["apps/budget/src/bootstrap.ts"], 21 | "cart/Module": ["apps/cart/src/bootstrap.ts"], 22 | "products/Module": ["apps/products/src/bootstrap.ts"] 23 | } 24 | }, 25 | "exclude": ["node_modules", "tmp"] 26 | } 27 | -------------------------------------------------------------------------------- /eslint.config.cjs: -------------------------------------------------------------------------------- 1 | const nx = require('@nx/eslint-plugin'); 2 | 3 | module.exports = [ 4 | ...nx.configs['flat/base'], 5 | ...nx.configs['flat/typescript'], 6 | ...nx.configs['flat/javascript'], 7 | { 8 | ignores: ['**/dist'], 9 | }, 10 | { 11 | files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], 12 | rules: { 13 | '@nx/enforce-module-boundaries': [ 14 | 'false', 15 | { 16 | enforceBuildableLibDependency: true, 17 | allow: ['^.*/eslint(\\.base)?\\.config\\.[cm]?js$'], 18 | depConstraints: [ 19 | { 20 | sourceTag: '*', 21 | onlyDependOnLibsWithTags: ['*'], 22 | }, 23 | ], 24 | }, 25 | ], 26 | }, 27 | }, 28 | { 29 | files: [ 30 | '**/*.ts', 31 | '**/*.tsx', 32 | '**/*.js', 33 | '**/*.jsx', 34 | '**/*.cjs', 35 | '**/*.mjs', 36 | ], 37 | // Override or add rules here 38 | rules: {}, 39 | }, 40 | ]; 41 | -------------------------------------------------------------------------------- /libs/shared/src/store/appSlice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction } from '@reduxjs/toolkit' 2 | import { Product } from '../types'; 3 | 4 | export interface AppState { 5 | products: Product[]; 6 | selectedProducts: Record; 7 | } 8 | 9 | const initialState: AppState = { 10 | products: [ 11 | { id: '1', name: 'Product 1', price: 10 }, 12 | { id: '2', name: 'Product 2', price: 20 }, 13 | { id: '3', name: 'Product 3', price: 30 }, 14 | ], 15 | selectedProducts: {}, 16 | } 17 | 18 | export const appSlice = createSlice({ 19 | name: 'app', 20 | initialState, 21 | reducers: { 22 | addProduct: (state, action: PayloadAction) => { 23 | state.selectedProducts[action.payload.id] = true; 24 | }, 25 | removeProduct: (state, action: PayloadAction) => { 26 | delete state.selectedProducts[action.payload.id]; 27 | } 28 | }, 29 | }) 30 | 31 | // Action creators are generated for each case reducer function 32 | export const { addProduct, removeProduct } = appSlice.actions 33 | 34 | export default appSlice.reducer -------------------------------------------------------------------------------- /apps/cart/webpack.config.js: -------------------------------------------------------------------------------- 1 | // const {} = require('@nx/webpack'); 2 | const { ModuleFederationPlugin } = require('webpack').container; 3 | 4 | module.exports = (config, ctx) => { 5 | config.devServer = { 6 | ...(config.devServer ?? {}), 7 | port: 4201, 8 | }; 9 | 10 | config.experiments = { 11 | ...(config.experiments ?? {}), 12 | outputModule: true, 13 | }; 14 | 15 | config.output = { 16 | ...(config.output ?? {}), 17 | uniqueName: 'cart', 18 | publicPath: 'auto', 19 | scriptType: 'module', 20 | } 21 | 22 | config.optimization = { 23 | ...(config.optimization ?? {}), 24 | runtimeChunk: false, 25 | }; 26 | 27 | config.plugins = [ 28 | ...(config.plugins ?? []), 29 | new ModuleFederationPlugin({ 30 | name: 'cart', 31 | filename: 'remoteEntry.js', 32 | exposes: { 33 | './Module': './apps/cart/src/bootstrap.ts', 34 | }, 35 | library: { type: 'module' }, 36 | shared: { 37 | '@micro-frontend-tutorial/shared': { 38 | singleton: true, 39 | requiredVersion: false, 40 | import: 'libs/shared/src/index.ts', 41 | }, 42 | }, 43 | }), 44 | ]; 45 | return config; 46 | }; 47 | -------------------------------------------------------------------------------- /apps/products/public/global.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | position: relative; 4 | width: 100%; 5 | height: 100%; 6 | } 7 | 8 | body { 9 | color: #333; 10 | margin: 0; 11 | padding: 8px; 12 | box-sizing: border-box; 13 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 14 | Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif; 15 | } 16 | 17 | a { 18 | color: rgb(0, 100, 200); 19 | text-decoration: none; 20 | } 21 | 22 | a:hover { 23 | text-decoration: underline; 24 | } 25 | 26 | a:visited { 27 | color: rgb(0, 80, 160); 28 | } 29 | 30 | label { 31 | display: block; 32 | } 33 | 34 | input, 35 | button, 36 | select, 37 | textarea { 38 | font-family: inherit; 39 | font-size: inherit; 40 | -webkit-padding: 0.4em 0; 41 | padding: 0.4em; 42 | margin: 0 0 0.5em 0; 43 | box-sizing: border-box; 44 | border: 1px solid #ccc; 45 | border-radius: 2px; 46 | } 47 | 48 | input:disabled { 49 | color: #ccc; 50 | } 51 | 52 | button { 53 | color: #333; 54 | background-color: #f4f4f4; 55 | outline: none; 56 | } 57 | 58 | button:disabled { 59 | color: #999; 60 | } 61 | 62 | button:not(:disabled):active { 63 | background-color: #ddd; 64 | } 65 | 66 | button:focus { 67 | border-color: #666; 68 | } 69 | -------------------------------------------------------------------------------- /apps/cart/src/services/redux.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, OnDestroy } from '@angular/core'; 2 | import { RootState, store } from '@micro-frontend-tutorial/shared'; 3 | import { PayloadAction } from '@reduxjs/toolkit'; 4 | import { BehaviorSubject, Observable } from 'rxjs'; 5 | import { map, distinctUntilChanged } from 'rxjs/operators'; 6 | 7 | @Injectable({ 8 | providedIn: 'root', 9 | }) 10 | export class ReduxService implements OnDestroy { 11 | private stateSubject = new BehaviorSubject(store.getState()); 12 | private unsubscribe: () => void; 13 | 14 | readonly state$ = this.stateSubject.asObservable(); 15 | 16 | constructor() { 17 | this.unsubscribe = store.subscribe(() => { 18 | const newState = store.getState(); 19 | if (this.stateSubject.getValue() !== newState) { 20 | this.stateSubject.next(newState); 21 | } 22 | }); 23 | } 24 | 25 | select(selector: (state: RootState) => T): Observable { 26 | return this.state$.pipe( 27 | map(selector), 28 | distinctUntilChanged() 29 | ); 30 | } 31 | 32 | dispatch(action: PayloadAction) { 33 | store.dispatch(action); 34 | } 35 | 36 | ngOnDestroy(): void { 37 | this.unsubscribe(); 38 | this.stateSubject.complete(); 39 | } 40 | } -------------------------------------------------------------------------------- /apps/products/vite.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { defineConfig } from 'vite'; 3 | import { svelte } from '@sveltejs/vite-plugin-svelte'; 4 | import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; 5 | import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; 6 | import federation from '@originjs/vite-plugin-federation'; 7 | 8 | 9 | export default defineConfig({ 10 | root: __dirname, 11 | cacheDir: '../../node_modules/.vite/apps/products', 12 | server: { 13 | port: 4203, 14 | host: 'localhost', 15 | }, 16 | preview: { 17 | port: 4203, 18 | host: 'localhost', 19 | }, 20 | plugins: [svelte(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md']), 21 | federation({ 22 | name: 'products', 23 | filename: 'remoteEntry.js', 24 | exposes: { 25 | './Module': './src/bootstrap.ts', 26 | }, 27 | shared: { 28 | '@micro-frontend-tutorial/shared': { 29 | requiredVersion: false, 30 | packagePath: 'libs/shared/src/index.ts', 31 | }, 32 | }, 33 | }), 34 | ], 35 | // Uncomment this if you are using workers. 36 | // worker: { 37 | // plugins: [ nxViteTsPaths() ], 38 | // }, 39 | build: { 40 | outDir: '../../dist/apps/products', 41 | assetsDir: '', 42 | target: 'esnext', 43 | emptyOutDir: true, 44 | reportCompressedSize: true, 45 | commonjsOptions: { 46 | transformMixedEsModules: true, 47 | }, 48 | }, 49 | }); 50 | -------------------------------------------------------------------------------- /apps/budget/vite.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { defineConfig } from 'vite'; 3 | import vue from '@vitejs/plugin-vue'; 4 | import federation from '@originjs/vite-plugin-federation'; 5 | import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; 6 | import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; 7 | 8 | export default defineConfig({ 9 | root: __dirname, 10 | cacheDir: '../../node_modules/.vite/apps/budget', 11 | server: { 12 | port: 4202, 13 | host: 'localhost', 14 | }, 15 | preview: { 16 | port: 4202, 17 | host: 'localhost', 18 | }, 19 | plugins: [ 20 | vue(), 21 | nxViteTsPaths(), 22 | nxCopyAssetsPlugin(['*.md']), 23 | federation({ 24 | name: 'budget', 25 | filename: 'remoteEntry.js', 26 | exposes: { 27 | './Module': './src/bootstrap.ts', 28 | }, 29 | shared: { 30 | '@micro-frontend-tutorial/shared': { 31 | requiredVersion: false, 32 | packagePath: 'libs/shared/src/index.ts', 33 | }, 34 | }, 35 | }), 36 | ], 37 | // Uncomment this if you are using workers. 38 | // worker: { 39 | // plugins: [ nxViteTsPaths() ], 40 | // }, 41 | build: { 42 | outDir: '../../dist/apps/budget', 43 | assetsDir: '', 44 | target:'esnext', 45 | emptyOutDir: true, 46 | reportCompressedSize: true, 47 | commonjsOptions: { 48 | transformMixedEsModules: true, 49 | }, 50 | }, 51 | }); 52 | -------------------------------------------------------------------------------- /apps/dashboard/src/app/app.tsx: -------------------------------------------------------------------------------- 1 | import 'zone.js'; 2 | import { useEffect, useRef } from 'react'; 3 | import { useSelector } from 'react-redux'; 4 | import { RootState } from '@micro-frontend-tutorial/shared'; 5 | 6 | export function App() { 7 | const isMounted = useRef(false); 8 | const selectedProducts = useSelector>( 9 | (state) => state.app.selectedProducts 10 | ); 11 | const count = Object.keys(selectedProducts).length; 12 | 13 | useEffect(() => { 14 | if (!isMounted.current) { 15 | loadModules(); 16 | isMounted.current = true; 17 | } 18 | }, []); 19 | 20 | const loadModules = async () => { 21 | const { bootstrap: bootstrapCart } = await import('cart/Module'); 22 | const { bootstrap: bootstrapBudget } = await import('budget/Module'); 23 | const { bootstrap: bootstrapProducts } = await import('products/Module'); 24 | bootstrapCart(); // load angular cart app 25 | bootstrapBudget('#budget'); // load vue budget app 26 | bootstrapProducts(document.getElementById('products') as HTMLElement); // load svelte products app 27 | }; 28 | 29 | return ( 30 |
31 |
32 |
33 | Logo 34 |

Dashboard 👋 ({count})

35 |
36 |
37 |
38 |
39 |
40 | {/* @ts-ignore */} 41 | 42 |
43 |
44 |
45 |
46 | ); 47 | } 48 | 49 | export default App; 50 | -------------------------------------------------------------------------------- /apps/products/src/App.svelte: -------------------------------------------------------------------------------- 1 | 27 | 28 |
29 |
30 | 31 |

Products App 👋

32 |
33 |
34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | {#each $appStore.products as product} 44 | 45 | 52 | 53 | 54 | 55 | {/each} 56 | 57 |
NamePrice
46 | toggleSelection(product)} 50 | /> 51 | {product.name}${product.price}
58 |
59 |
60 | 61 | 66 | -------------------------------------------------------------------------------- /apps/dashboard/webpack.config.js: -------------------------------------------------------------------------------- 1 | const { NxAppWebpackPlugin } = require('@nx/webpack/app-plugin'); 2 | const { NxReactWebpackPlugin } = require('@nx/react/webpack-plugin'); 3 | const { ModuleFederationPlugin } = require('webpack').container; 4 | const { join } = require('path'); 5 | 6 | module.exports = ()=>{ 7 | return { 8 | output: { 9 | path: join(__dirname, '../../dist/apps/dashboard'), 10 | }, 11 | 12 | experiments: { 13 | outputModule: true, 14 | }, 15 | devServer: { 16 | port: 4200, 17 | historyApiFallback: { 18 | index: '/index.html', 19 | disableDotRule: true, 20 | htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'], 21 | }, 22 | }, 23 | plugins: [ 24 | new NxAppWebpackPlugin({ 25 | tsConfig: './tsconfig.app.json', 26 | compiler: 'babel', 27 | main: './src/main.ts', 28 | index: './src/index.html', 29 | baseHref: '/', 30 | assets: ['./src/favicon.ico', './src/assets'], 31 | styles: ['./src/styles.css'], 32 | outputHashing: process.env['NODE_ENV'] === 'production' ? 'all' : 'none', 33 | optimization: process.env['NODE_ENV'] === 'production', 34 | }), 35 | new NxReactWebpackPlugin({ 36 | // Uncomment this line if you don't want to use SVGR 37 | // See: https://react-svgr.com/ 38 | // svgr: false 39 | }), 40 | new ModuleFederationPlugin({ 41 | name: 'dashboard', 42 | filename: 'remoteEntry.js', 43 | remotes:{ 44 | cart: 'http://localhost:4201/remoteEntry.js', 45 | budget: 'http://localhost:4202/remoteEntry.js', 46 | products: 'http://localhost:4203/remoteEntry.js', 47 | }, 48 | remoteType:'module', 49 | shared: { 50 | '@micro-frontend-tutorial/shared': { 51 | singleton: true, 52 | requiredVersion: false, 53 | import: 'libs/shared/src/index.ts', 54 | }, 55 | }, 56 | }), 57 | ], 58 | } 59 | }; 60 | -------------------------------------------------------------------------------- /libs/shared/src/styles/index.css: -------------------------------------------------------------------------------- 1 | /* Shared Simple Card Styles */ 2 | .simple-card { 3 | font-family: 'Arial', sans-serif; 4 | background-color: #f4f7fa; 5 | padding: 20px; 6 | border-radius: 12px; 7 | box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); 8 | max-width: 800px; 9 | margin: 40px auto; 10 | } 11 | 12 | .simple-card .header { 13 | display: flex; 14 | align-items: center; 15 | /* justify-content: center; */ 16 | margin-bottom: 20px; 17 | gap: 1rem; 18 | } 19 | 20 | .simple-card .header h1 { 21 | font-size: 1.8rem; 22 | margin: 0px; 23 | color: #333; 24 | } 25 | 26 | .simple-card .header p { 27 | color: #666; 28 | margin: 0; 29 | } 30 | 31 | .simple-card .logo { 32 | width: 40px; 33 | height: 40px; 34 | } 35 | 36 | .simple-card .content { 37 | background: #fff; 38 | border: 1px solid #e0e0e0; 39 | border-radius: 8px; 40 | padding: 16px; 41 | } 42 | 43 | .simple-card .content h2 { 44 | margin-bottom: 16px; 45 | font-size: 1.5rem; 46 | color: #444; 47 | border-bottom: 2px solid #e0e0e0; 48 | padding-bottom: 8px; 49 | } 50 | 51 | .simple-card table { 52 | width: 100%; 53 | border-collapse: collapse; 54 | margin-bottom: 20px; 55 | } 56 | 57 | .simple-card table thead th { 58 | text-align: left; 59 | padding: 8px; 60 | background-color: #f0f0f0; 61 | border: 1px solid #ddd; 62 | font-weight: bold; 63 | } 64 | 65 | .simple-card table tbody td { 66 | padding: 8px; 67 | border: 1px solid #ddd; 68 | } 69 | 70 | .simple-card table tbody tr:hover { 71 | background-color: #f9f9f9; 72 | } 73 | 74 | .simple-card .btn { 75 | display: block; 76 | width: 100%; 77 | padding: 12px; 78 | font-size: 1rem; 79 | font-weight: bold; 80 | color: #fff; 81 | background-color: #007bff; 82 | border: none; 83 | border-radius: 6px; 84 | cursor: pointer; 85 | transition: background-color 0.3s ease; 86 | } 87 | 88 | .simple-card .btn:disabled { 89 | background-color: #ccc; 90 | cursor: not-allowed; 91 | } 92 | 93 | .simple-card .btn:not(:disabled):hover { 94 | background-color: #0056b3; 95 | } 96 | -------------------------------------------------------------------------------- /apps/dashboard/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | @import url('../../../libs/shared/src/styles/index.css'); 3 | /* You can add global styles to this file, and also import other style files */ 4 | html { 5 | -webkit-text-size-adjust: 100%; 6 | font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 7 | 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 8 | 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 9 | 'Noto Color Emoji'; 10 | line-height: 1.5; 11 | tab-size: 4; 12 | scroll-behavior: smooth; 13 | zoom: 0.9; 14 | } 15 | body { 16 | font-family: inherit; 17 | line-height: inherit; 18 | margin: 0; 19 | } 20 | h1, 21 | h2, 22 | p, 23 | pre { 24 | margin: 0; 25 | } 26 | *, 27 | ::before, 28 | ::after { 29 | box-sizing: border-box; 30 | border-width: 0; 31 | border-style: solid; 32 | border-color: currentColor; 33 | } 34 | h1, 35 | h2 { 36 | font-size: inherit; 37 | font-weight: inherit; 38 | } 39 | a { 40 | color: inherit; 41 | text-decoration: inherit; 42 | } 43 | .container { 44 | margin-left: auto; 45 | margin-right: auto; 46 | padding: 1rem; 47 | max-width: 90%; 48 | color: rgba(55, 65, 81, 1); 49 | width: 100%; 50 | } 51 | #welcome h1 { 52 | font-size: 3rem; 53 | font-weight: 500; 54 | letter-spacing: -0.025em; 55 | line-height: 1; 56 | } 57 | #welcome span { 58 | display: block; 59 | font-size: 1.875rem; 60 | font-weight: 300; 61 | line-height: 2.25rem; 62 | margin-bottom: 0.5rem; 63 | } 64 | 65 | .apps-container { 66 | padding-top: 20px; 67 | display: grid; 68 | grid-template-columns: 1fr 1fr; /* Two equal columns */ 69 | grid-template-rows: auto auto; /* Two rows */ 70 | gap: 16px; /* Optional: adds spacing between grid items */ 71 | } 72 | 73 | #budget { 74 | grid-column: span 2; 75 | .info-side { 76 | flex: 1 0 250px; 77 | } 78 | } 79 | 80 | .simple-card { 81 | max-width: 100% !important; 82 | margin: 0 !important; 83 | } 84 | .logo { 85 | width: 60px; 86 | height: 60px; 87 | } 88 | .header { 89 | display: flex; 90 | align-items: center; 91 | gap: 30px; 92 | } -------------------------------------------------------------------------------- /apps/cart/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cart", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "projectType": "application", 5 | "prefix": "app", 6 | "sourceRoot": "apps/cart/src", 7 | "tags": [], 8 | "targets": { 9 | "build": { 10 | "executor": "@nx/angular:webpack-browser", 11 | "outputs": ["{options.outputPath}"], 12 | "options": { 13 | "outputPath": "dist/apps/cart", 14 | "index": "apps/cart/src/index.html", 15 | "main": "apps/cart/src/main.ts", 16 | "polyfills": ["zone.js"], 17 | "tsConfig": "apps/cart/tsconfig.app.json", 18 | "assets": [ 19 | { 20 | "glob": "**/*", 21 | "input": "apps/cart/public" 22 | } 23 | ], 24 | "styles": ["apps/cart/src/styles.css", "@micro-frontend-tutorial/shared/styles/index.css"], 25 | "scripts": [], 26 | "customWebpackConfig": { 27 | "path": "apps/cart/webpack.config.js" 28 | } 29 | }, 30 | "configurations": { 31 | "production": { 32 | "budgets": [ 33 | { 34 | "type": "initial", 35 | "maximumWarning": "500kb", 36 | "maximumError": "1mb" 37 | }, 38 | { 39 | "type": "anyComponentStyle", 40 | "maximumWarning": "4kb", 41 | "maximumError": "8kb" 42 | } 43 | ], 44 | "outputHashing": "all" 45 | }, 46 | "development": { 47 | "buildOptimizer": false, 48 | "optimization": false, 49 | "vendorChunk": true, 50 | "extractLicenses": false, 51 | "sourceMap": true, 52 | "namedChunks": true 53 | } 54 | }, 55 | "defaultConfiguration": "production" 56 | }, 57 | "serve": { 58 | "executor": "@nx/angular:dev-server", 59 | "configurations": { 60 | "production": { 61 | "buildTarget": "cart:build:production" 62 | }, 63 | "development": { 64 | "buildTarget": "cart:build:development" 65 | } 66 | }, 67 | "defaultConfiguration": "development" 68 | }, 69 | "extract-i18n": { 70 | "executor": "@angular-devkit/build-angular:extract-i18n", 71 | "options": { 72 | "buildTarget": "cart:build" 73 | } 74 | }, 75 | "lint": { 76 | "executor": "@nx/eslint:lint" 77 | }, 78 | "serve-static": { 79 | "executor": "@nx/web:file-server", 80 | "options": { 81 | "buildTarget": "cart:build", 82 | "spa": true 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /apps/cart/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, NgZone, OnDestroy, OnInit } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { config, Product, removeProduct } from '@micro-frontend-tutorial/shared'; 4 | import { ReduxService } from '../services/redux.service'; 5 | import { Subject, takeUntil } from 'rxjs'; 6 | 7 | @Component({ 8 | standalone: true, 9 | imports: [CommonModule], 10 | selector: 'app-cart', 11 | template: ` 12 |
13 |
14 | 15 |

16 | Cart App 👋 17 |

18 |
19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 35 | 36 | 37 |
Product NamePrice
{{ product.name }}\${{ product.price }} 33 | 34 |
38 | 39 |

Your cart is currently empty. Add some products to see them here.

40 |
41 |
42 |
43 | `, 44 | styles: [ 45 | ` 46 | .select-column { 47 | width: 2rem; 48 | } 49 | .select-column button:hover { 50 | cursor: pointer; 51 | } 52 | `, 53 | ], 54 | }) 55 | export class AppComponent implements OnInit, OnDestroy { 56 | private destroy$ = new Subject(); 57 | logoUrl = `${config.cartUrl}/logo.png`; 58 | cartProducts: Product[] = []; 59 | 60 | constructor(private zone: NgZone, private reduxService: ReduxService) { 61 | this.cartProducts = []; 62 | } 63 | 64 | removeFromCart(product: Product): void { 65 | this.reduxService.dispatch(removeProduct(product)); 66 | } 67 | 68 | ngOnInit(): void { 69 | this.reduxService 70 | .select((state) => state.app) 71 | .pipe(takeUntil(this.destroy$)) 72 | .subscribe((appState) => { 73 | this.zone.run(() => { 74 | this.cartProducts = appState.products.filter( 75 | (product) => appState.selectedProducts[product.id] 76 | ); 77 | }); 78 | }); 79 | 80 | } 81 | 82 | ngOnDestroy(): void { 83 | this.destroy$.next(); 84 | this.destroy$.complete(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /apps/products/public/favicon.png: -------------------------------------------------------------------------------- 1 | �PNG 2 |  3 | IHDR���>a� pHYs  ��~� �IDATx��] �G�����M D&u&'��� �� � 7N8A�0'\�bbG0bߣg���c�ߪ���Z,����W��~���K�az�c��Ӎ�3L9f�r�M� ���ba)_���p3��<�y3�ة� �� �� �BX�ׂ�v[!��ڨ�SO���j�/�_s+�������{c* �R�H��/�No 4 | H���Z-Ra��R¢�9/� \��SC���r��*x$�z�q��L<��� ������1ƛ�_<�q���Jv�j^�����Y=�b"%@��WC�+x���mp��[�8Td�I���A��!@�\5��=�B���RJ���L��l���� ����v�0��Ly�v;v����ann�{����������ݻwݿ�\Vm+7o�Ȯv���߃�^XX{�� �w��3?~ o޼ �_��^��~�����˵Â9r$�ٳGt�akk+�x�BS2�c��u��x�v��'N�/|?@�gϞu���ۨKд�&��kQ�]]o��o߆hH�+1F��a�Ȯ�%��nǮW�K�ſw�^�hBMTG�;�v�7�H�c\�x�j�����;~��w�'�H�"��zd����N�*���,�O�������r�����]|K#�HH�a������ɮ]�r��O�<ٽj[���P�"��%�׮�p��p��]�=�c%�\�NJi)�ʛ->v��ѣG[����cǸ]�� 7k�g�͢y��%\;)}q�R� h\`��5�vR@e=~��sQ<���Y.>Dt�;n\@/?z�h�~F��<M\�``�k� 'sT����;9�q�Ńz8x� �;����s�&�z_-�g���n�����`�Ν濭V�֍<];���O���7$ �=�� �ۋE�w�:V�Qa�X� ���,D( =@�c�q��Ȼ_T� ��˯�������;�N�Kٷo_#oE�R�1�%�(;������g�\#BQ�b �k��" "�0D���g�~�$coT@GT��~�0]�����"hJ�U�C��uE�%� �X��� ���oh�ݺk�*�� �X��d�mTRY��?��C2�*�# �Xτr0�n���]�%�)I �� #� �$v� 5 | �%� �"+� ���!��B�D4XJ���qc�`��U V��EVQ���:-��9�h �4�����ڵ�����SQ����قb�^�Ɛ�~������#�� $��Y@BxΆ���$���Wj��@�C�5�6Ip����A�%�$N�#�D�SQ�泠6_�#����H`H�k�"ZYG��w� 6 | N�b������:���%��Q(���p�e�1�� ��Y�^�]P5���^li �� `�qR�޽�������ΈSp܇�`E.���X����&^�} $��/��Y*����j�X�)�=�'Q��Y�F�q�g>���H-� �eLcV��ry5�� ��V��Hk�CV�?*`$ !�@,0�:D�z6��ɹ���r pā ���JHТI��I�v��/>j@W�B���`9���j�� u^C h*GV^Z;��6 �!�L��"����j�w��|y���@4(h�H)u�š��kr����5z�Ƥ0�#c��#�U�U�*��z�Νm��K�<��<‐6ֹ� v�E��,&�3\�<U!K��ħ�� ���E��U>���ie�Z%fH�ª�sAr ���^,< R.%�`O�hM��@��5���km54���5�v�������[��2����Q�3���a�XM�э��P���z}q�dN�������}&I�A)XK��{�;y�7�kPs%.@�Sݾ��HD��Z09�t;Ў.��Y_ȭ���jt����~4� !����8��!T�/k ,f �J���0#@3X��5VX����#w�vX�*�C�� @r^ �PX�wm�Y�‘ue ��C�f��9s�??��!�����V ͡, 0H�R#��Q�,x��A� w�JޥH�̰�7t-,l,@ "x5�R���@�/UiN�~6E�,m�ch(�% �^��;�=n�1�4y�@�z^��(�j�n �ˆTК*�]�A@�Y��mS��x���,`?�<���մ�����u������CG��Pk:%[���)�Y�[i�"�O�n�N�q�}�����U�� �"�X��X�Qu��:�9�x;O-�cT���!%+%}IXT,0\�A���k쏡��oVq%� ��"��a�A�D���F��0f�+̇��(��/>D�o��{�.�BZ� 'M1VW�H���N]jBRJϹ��5�� 7 | �nt���4ev��tP/�3�� "��Œ��%��d�R�-����Ma\c��a]դj ��B��[M@����_ɺ���S���hv��M@#�Hw+�zq#�6T �� 8 | �%��u�����1F�� P'@�J� ���AЮށ���w8at3�z��M+X@E�ơd4��˗/=��k�&����־/�����vD 9 | O���Ny��V�V0#@�J�5�,�#P�k��Y �a��:׮ L%!�tU:@�BT��5� �W����e��޵k7�ɰ Z��5�+·��-,�*׮ � �� 6<��h�k�E@H)]̽n5J�ֺvMPt�q�q=��_+��j׮ �J�^d�`��Z�&���j@ȇN���^�a��P�P\ɗJf��k��6TK�~�:��<�t�!6s���/z/ZC�~d A3��,$�3��}\��3�}��̠��3L3B�o����*�IEND�B`� -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@micro-frontends-tutorial/source", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "dashboard": "nx run dashboard:serve" 7 | }, 8 | "private": true, 9 | "devDependencies": { 10 | "@angular-devkit/build-angular": "~19.0.0", 11 | "@angular-devkit/core": "~19.0.0", 12 | "@angular-devkit/schematics": "~19.0.0", 13 | "@angular/cli": "~19.0.0", 14 | "@angular/compiler-cli": "~19.0.0", 15 | "@angular/language-service": "~19.0.0", 16 | "@babel/core": "^7.14.5", 17 | "@babel/preset-react": "^7.14.5", 18 | "@eslint/js": "^9.8.0", 19 | "@nx/angular": "^20.3.0", 20 | "@nx/cypress": "20.4.0", 21 | "@nx/eslint": "20.3.0", 22 | "@nx/eslint-plugin": "20.3.0", 23 | "@nx/jest": "20.4.0", 24 | "@nx/js": "20.3.0", 25 | "@nx/react": "^20.3.0", 26 | "@nx/vite": "20.3.0", 27 | "@nx/vue": "^20.3.0", 28 | "@nx/web": "20.3.0", 29 | "@nx/webpack": "20.3.0", 30 | "@nx/workspace": "20.3.0", 31 | "@nxext/svelte": "^20.0.3", 32 | "@originjs/vite-plugin-federation": "^1.3.7", 33 | "@pmmmwh/react-refresh-webpack-plugin": "^0.5.7", 34 | "@schematics/angular": "~19.0.0", 35 | "@sveltejs/vite-plugin-svelte": "^1.0.1", 36 | "@svgr/webpack": "^8.0.1", 37 | "@swc-node/register": "~1.9.1", 38 | "@swc/core": "~1.5.7", 39 | "@swc/helpers": "~0.5.11", 40 | "@testing-library/svelte": "^3.2.2", 41 | "@tsconfig/svelte": "^4.0.1", 42 | "@types/node": "18.16.9", 43 | "@types/react": "18.3.1", 44 | "@types/react-dom": "18.3.0", 45 | "@typescript-eslint/utils": "^8.13.0", 46 | "@vitejs/plugin-vue": "^4.5.0", 47 | "@vitest/ui": "^1.3.1", 48 | "@vue/test-utils": "^2.4.1", 49 | "angular-eslint": "^19.0.2", 50 | "cypress": "^13.13.0", 51 | "eslint": "^9.8.0", 52 | "eslint-config-prettier": "^9.0.0", 53 | "eslint-plugin-svelte3": "^4.0.0", 54 | "jest": "^29.7.0", 55 | "jsdom": "~22.1.0", 56 | "nx": "20.3.0", 57 | "prettier": "^2.6.2", 58 | "react-refresh": "^0.10.0", 59 | "svelte": "^4.2.12", 60 | "svelte-check": "^2.10.2", 61 | "svelte-jester": "^2.3.2", 62 | "svelte-preprocess": "^5.1.3", 63 | "tslib": "^2.3.0", 64 | "typescript": "~5.6.2", 65 | "typescript-eslint": "^8.13.0", 66 | "vite": "^5.0.0", 67 | "vitest": "^1.3.1", 68 | "vue-tsc": "^2.0.0", 69 | "webpack-cli": "^5.1.4" 70 | }, 71 | "dependencies": { 72 | "@angular/animations": "~19.0.0", 73 | "@angular/common": "~19.0.0", 74 | "@angular/compiler": "~19.0.0", 75 | "@angular/core": "~19.0.0", 76 | "@angular/forms": "~19.0.0", 77 | "@angular/platform-browser": "~19.0.0", 78 | "@angular/platform-browser-dynamic": "~19.0.0", 79 | "@angular/router": "~19.0.0", 80 | "@reduxjs/toolkit": "^2.5.0", 81 | "chart.js": "^4.4.9", 82 | "react": "18.3.1", 83 | "react-dom": "18.3.1", 84 | "react-redux": "^9.2.0", 85 | "rxjs": "~7.8.0", 86 | "vue": "^3.3.4", 87 | "zone.js": "~0.15.0" 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/nx/schemas/nx-schema.json", 3 | "namedInputs": { 4 | "default": ["{projectRoot}/**/*", "sharedGlobals"], 5 | "production": [ 6 | "default", 7 | "!{projectRoot}/.eslintrc.json", 8 | "!{projectRoot}/eslint.config.cjs", 9 | "!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)", 10 | "!{projectRoot}/tsconfig.spec.json", 11 | "!{projectRoot}/src/test-setup.[jt]s", 12 | "!{projectRoot}/jest.config.[jt]s", 13 | "!{projectRoot}/test-setup.[jt]s", 14 | "!{projectRoot}/cypress/**/*", 15 | "!{projectRoot}/**/*.cy.[jt]s?(x)", 16 | "!{projectRoot}/cypress.config.[jt]s" 17 | ], 18 | "sharedGlobals": [] 19 | }, 20 | "plugins": [ 21 | { 22 | "plugin": "@nx/webpack/plugin", 23 | "options": { 24 | "buildTargetName": "build", 25 | "serveTargetName": "serve", 26 | "previewTargetName": "preview" 27 | } 28 | }, 29 | { 30 | "plugin": "@nx/vite/plugin", 31 | "options": { 32 | "buildTargetName": "build", 33 | "testTargetName": "test", 34 | "serveTargetName": "serve", 35 | "previewTargetName": "preview", 36 | "serveStaticTargetName": "serve-static", 37 | "typecheckTargetName": "typecheck" 38 | } 39 | }, 40 | "@nxext/svelte" 41 | ], 42 | "generators": { 43 | "@nx/react": { 44 | "application": { 45 | "babel": true, 46 | "style": "css", 47 | "linter": "none", 48 | "bundler": "webpack" 49 | }, 50 | "component": { 51 | "style": "css" 52 | }, 53 | "library": { 54 | "style": "css", 55 | "linter": "none" 56 | } 57 | }, 58 | "@nx/angular:application": { 59 | "e2eTestRunner": "none", 60 | "linter": "eslint", 61 | "style": "css", 62 | "unitTestRunner": "none" 63 | } 64 | }, 65 | "targetDefaults": { 66 | "@angular-devkit/build-angular:browser": { 67 | "cache": true, 68 | "dependsOn": ["^build"], 69 | "inputs": ["production", "^production"] 70 | }, 71 | "@nx/eslint:lint": { 72 | "cache": true, 73 | "inputs": [ 74 | "default", 75 | "{workspaceRoot}/.eslintrc.json", 76 | "{workspaceRoot}/.eslintignore", 77 | "{workspaceRoot}/eslint.config.cjs" 78 | ] 79 | }, 80 | "@nx/js:tsc": { 81 | "cache": true, 82 | "dependsOn": ["^build"], 83 | "inputs": ["production", "^production"] 84 | }, 85 | "@nx/jest:jest": { 86 | "cache": true, 87 | "inputs": ["default", "^production", "{workspaceRoot}/jest.preset.js"], 88 | "options": { 89 | "passWithNoTests": true 90 | }, 91 | "configurations": { 92 | "ci": { 93 | "ci": true, 94 | "codeCoverage": true 95 | } 96 | } 97 | }, 98 | "e2e": { 99 | "cache": true, 100 | "inputs": ["default", "^production"] 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MicroFrontendsTutorial 2 | 3 | 4 | 5 | ✨ Your new, shiny [Nx workspace](https://nx.dev) is ready ✨. 6 | 7 | [Learn more about this workspace setup and its capabilities](https://nx.dev/getting-started/intro#learn-nx?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) or run `npx nx graph` to visually explore what was created. Now, let's get you up to speed! 8 | 9 | ## Run tasks 10 | 11 | To run tasks with Nx use: 12 | 13 | ```sh 14 | npx nx 15 | ``` 16 | 17 | For example: 18 | 19 | ```sh 20 | npx nx build myproject 21 | ``` 22 | 23 | These targets are either [inferred automatically](https://nx.dev/concepts/inferred-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) or defined in the `project.json` or `package.json` files. 24 | 25 | [More about running tasks in the docs »](https://nx.dev/features/run-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) 26 | 27 | ## Add new projects 28 | 29 | While you could add new projects to your workspace manually, you might want to leverage [Nx plugins](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) and their [code generation](https://nx.dev/features/generate-code?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) feature. 30 | 31 | To install a new plugin you can use the `nx add` command. Here's an example of adding the React plugin: 32 | ```sh 33 | npx nx add @nx/react 34 | ``` 35 | 36 | Use the plugin's generator to create new projects. For example, to create a new React app or library: 37 | 38 | ```sh 39 | # Generate an app 40 | npx nx g @nx/react:app demo 41 | 42 | # Generate a library 43 | npx nx g @nx/react:lib some-lib 44 | ``` 45 | 46 | You can use `npx nx list` to get a list of installed plugins. Then, run `npx nx list ` to learn about more specific capabilities of a particular plugin. Alternatively, [install Nx Console](https://nx.dev/getting-started/editor-setup?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) to browse plugins and generators in your IDE. 47 | 48 | [Learn more about Nx plugins »](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) | [Browse the plugin registry »](https://nx.dev/plugin-registry?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) 49 | 50 | ## Set up CI! 51 | 52 | ### Step 1 53 | 54 | To connect to Nx Cloud, run the following command: 55 | 56 | ```sh 57 | npx nx connect 58 | ``` 59 | 60 | Connecting to Nx Cloud ensures a [fast and scalable CI](https://nx.dev/ci/intro/why-nx-cloud?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) pipeline. It includes features such as: 61 | 62 | - [Remote caching](https://nx.dev/ci/features/remote-cache?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) 63 | - [Task distribution across multiple machines](https://nx.dev/ci/features/distribute-task-execution?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) 64 | - [Automated e2e test splitting](https://nx.dev/ci/features/split-e2e-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) 65 | - [Task flakiness detection and rerunning](https://nx.dev/ci/features/flaky-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) 66 | 67 | ### Step 2 68 | 69 | Use the following command to configure a CI workflow for your workspace: 70 | 71 | ```sh 72 | npx nx g ci-workflow 73 | ``` 74 | 75 | [Learn more about Nx on CI](https://nx.dev/ci/intro/ci-with-nx#ready-get-started-with-your-provider?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) 76 | 77 | ## Install Nx Console 78 | 79 | Nx Console is an editor extension that enriches your developer experience. It lets you run tasks, generate code, and improves code autocompletion in your IDE. It is available for VSCode and IntelliJ. 80 | 81 | [Install Nx Console »](https://nx.dev/getting-started/editor-setup?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) 82 | 83 | ## Useful links 84 | 85 | Learn more: 86 | 87 | - [Learn more about this workspace setup](https://nx.dev/getting-started/intro#learn-nx?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) 88 | - [Learn about Nx on CI](https://nx.dev/ci/intro/ci-with-nx?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) 89 | - [Releasing Packages with Nx release](https://nx.dev/features/manage-releases?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) 90 | - [What are Nx plugins?](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) 91 | 92 | And join the Nx community: 93 | - [Discord](https://go.nx.dev/community) 94 | - [Follow us on X](https://twitter.com/nxdevtools) or [LinkedIn](https://www.linkedin.com/company/nrwl) 95 | - [Our Youtube channel](https://www.youtube.com/@nxdevtools) 96 | - [Our blog](https://nx.dev/blog?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) 97 | -------------------------------------------------------------------------------- /apps/budget/src/app/App.vue: -------------------------------------------------------------------------------- 1 | 62 | 63 | 288 | 289 | 396 | --------------------------------------------------------------------------------