├── .github ├── FUNDING.yml └── workflows │ └── build_and_deploy.yml ├── .gitignore ├── .husky └── pre-commit ├── .prettierignore ├── LICENSE ├── README.md ├── babel.config.json ├── eslint.config.mjs ├── package.json ├── pnpm-lock.yaml ├── src ├── declarations.d.ts ├── index.ejs ├── microfrontend-layout.html └── vue-mf-root-config.ts ├── tsconfig.json └── webpack.config.js /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: ["joeldenning"] 4 | patreon: singlespa 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/workflows/build_and_deploy.yml: -------------------------------------------------------------------------------- 1 | name: Build, Test, Release 2 | 3 | on: 4 | push: 5 | branches: main 6 | pull_request: 7 | branches: "*" 8 | 9 | jobs: 10 | build_test: 11 | name: Build and Test 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout Repo 16 | uses: actions/checkout@v4 17 | - name: Install Pnpm 18 | uses: pnpm/action-setup@v4 19 | - name: Setup Node.js 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: 22 23 | cache: pnpm 24 | - name: Install dependencies 25 | run: pnpm install --frozen-lockfile 26 | 27 | - name: Test 28 | run: | 29 | pnpm run lint 30 | pnpm run check-format 31 | pnpm run test 32 | 33 | - name: Build 34 | run: pnpm run build 35 | 36 | - name: Store artifact 37 | uses: actions/upload-artifact@v4 38 | with: 39 | name: dist 40 | path: dist 41 | 42 | release: 43 | name: Release 44 | needs: build_test 45 | runs-on: ubuntu-latest 46 | if: ${{ github.ref == 'refs/heads/main' }} 47 | permissions: 48 | contents: "read" 49 | id-token: "write" 50 | 51 | steps: 52 | - name: Download build artifact 53 | uses: actions/download-artifact@v4 54 | with: 55 | name: dist 56 | 57 | - name: Authenticate with GCP 58 | uses: "google-github-actions/auth@v2" 59 | with: 60 | project_id: neural-passkey-248222 61 | workload_identity_provider: "projects/654158993889/locations/global/workloadIdentityPools/github/providers/my-repo" 62 | service_account: github-workload-identity-feder@neural-passkey-248222.iam.gserviceaccount.com 63 | 64 | - name: Upload Static Files to CDN 65 | uses: "google-github-actions/upload-cloud-storage@v2" 66 | with: 67 | path: . 68 | destination: vue.microfrontends.app/${{ github.event.repository.name }}/${{ github.run_id }} 69 | 70 | - name: Update Import Map 71 | uses: single-spa/action-deploy-to-import-map-deployer@v1 72 | with: 73 | host: ${{ secrets.DEPLOYER_HOST }} 74 | username: ${{ secrets.DEPLOYER_USERNAME }} 75 | password: ${{ secrets.DEPLOYER_PASSWORD }} 76 | environment-name: vue 77 | service-name: "@vue-mf/${{ github.event.repository.name }}" 78 | service-url: "https://vue.microfrontends.app/${{ github.event.repository.name }}/${{ github.run_id }}/vue-mf-${{ github.event.repository.name }}.js" 79 | service-integrity-file-path: vue-mf-${{ github.event.repository.name }}.js 80 | 81 | - name: Update Index.html 82 | uses: "google-github-actions/upload-cloud-storage@v2" 83 | with: 84 | path: ./index.html 85 | destination: vue.microfrontends.app/ 86 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | dist 63 | 64 | # Editor directories and files 65 | .idea 66 | .vscode 67 | *.suo 68 | *.ntvs* 69 | *.njsproj 70 | *.sln 71 | *.sw? 72 | .DS_Store 73 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | pnpm exec pretty-quick --staged && pnpm exec concurrently pnpm:test pnpm:lint 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | .prettierignore 3 | yarn.lock 4 | yarn-error.log 5 | package-lock.json 6 | LICENSE 7 | *.ejs 8 | dist 9 | coverage 10 | pnpm-lock.yaml -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 vue-microfrontends 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vue Microfrontends root-config 2 | 3 | [![CircleCI](https://circleci.com/gh/vue-microfrontends/root-config.svg?style=svg)](https://circleci.com/gh/vue-microfrontends/root-config) 4 | 5 | ## What is this? 6 | 7 | This is an example microfrontend repo demonstrating how to use [single-spa](https://single-spa.js.org). You can see the code running at https://vue.microfrontends.app. 8 | 9 | ## How does it work? 10 | 11 | [Full article](https://single-spa.js.org/docs/recommended-setup) 12 | 13 | This repository is a javascript project that creates a javascript bundle that is an in-browser javascript module (explanation on [youtube](https://www.youtube.com/watch?v=Jxqiu6pdMSU&list=PLLUD8RtHvsAOhtHnyGx57EYXoaNsxGrTU&index=2) / [bilibili](https://www.bilibili.com/video/av83498486/)). The currently deployed version of the in-browser module can be seen at https://vue.microfrontends.app/importmap.json. 14 | 15 | This project uses [Vue](https://vuejs.org) and was created with the [create-single-spa](https://single-spa.js.org/docs/create-single-spa) CLI. It uses webpack and babel. 16 | 17 | Whenever a pull request is merged to master, [CircleCI builds and deploys the project](https://circleci.com/gh/vue-microfrontends/root-config). The ["workflows" view](https://circleci.com/gh/vue-microfrontends/workflows) (pictured below) can be seen if you are logged into CircleCI. Deployments for this in-browser module are completely independent of deployments for any other module. 18 | 19 | ![image](https://user-images.githubusercontent.com/5524384/75210801-5ba02700-573f-11ea-8064-46af165cba0a.png) 20 | 21 | ## Local development 22 | 23 | [Full documentation](https://single-spa.js.org/docs/recommended-setup#local-development) 24 | 25 | Tutorial video: [youtube](https://www.youtube.com/watch?v=vjjcuIxqIzY&list=PLLUD8RtHvsAOhtHnyGx57EYXoaNsxGrTU&index=4) / [bilibili](https://www.bilibili.com/video/av83617789/) 26 | 27 | There are two ways to do local development. It is preferred to do one module at a time, whenever possible. 28 | 29 | ### One module at a time 30 | 31 | ```sh 32 | cd root-config 33 | yarn install 34 | yarn start --https 35 | ``` 36 | 37 | Go to https://localhost:9000/vue-mf-root-config.js and verify that you are able to load the file without any SSL problems. To solve SSL problems, see [these instructions](https://improveandrepeat.com/2016/09/allowing-self-signed-certificates-on-localhost-with-chrome-and-firefox/). 38 | 39 | Now, go to https://vue.microfrontends.app. In the browser console, run the following: 40 | 41 | ```js 42 | localStorage.setItem("devtools", true); 43 | ``` 44 | 45 | Refresh the page. Click on the tan / beige rectangle: 46 | 47 | ![image](https://user-images.githubusercontent.com/5524384/75211359-e46b9280-5740-11ea-80bb-974846df414b.png) 48 | 49 | Set an [import map override](https://github.com/joeldenning/import-map-overrides/) to `9000`. 50 | 51 | ![image](https://user-images.githubusercontent.com/5524384/75211553-7e333f80-5741-11ea-97d6-d3d86ffd1826.png) 52 | 53 | Refresh the page. Your local code for this module will now be running on https://vue.microfrontends.app. You may make changes locally and refresh the page to see them. 54 | 55 | ### All modules together 56 | 57 | Run the root-config project locally: 58 | 59 | ``` 60 | cd root-config 61 | yarn install 62 | yarn start 63 | ``` 64 | 65 | Now follow the steps above for "One module at a time" for each of the modules you wish to work on. 66 | 67 | ## Adapting for your organization 68 | 69 | Feel free to fork and modify any files you would like when doing a proof of concept for your organization. When it's time to actually create / adapt your organization's projects, consider using [create-single-spa](https://single-spa.js.org/docs/create-single-spa) instead of forking this repository. 70 | -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-typescript"], 3 | "plugins": [ 4 | [ 5 | "@babel/plugin-transform-runtime", 6 | { 7 | "useESModules": true, 8 | "regenerator": false 9 | } 10 | ] 11 | ], 12 | "env": { 13 | "test": { 14 | "presets": [ 15 | [ 16 | "@babel/preset-env", 17 | { 18 | "targets": "current node" 19 | } 20 | ] 21 | ] 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import babelParser from "@babel/eslint-parser"; 2 | import path from "node:path"; 3 | import { fileURLToPath } from "node:url"; 4 | import js from "@eslint/js"; 5 | import { FlatCompat } from "@eslint/eslintrc"; 6 | 7 | const __filename = fileURLToPath(import.meta.url); 8 | const __dirname = path.dirname(__filename); 9 | const compat = new FlatCompat({ 10 | baseDirectory: __dirname, 11 | recommendedConfig: js.configs.recommended, 12 | allConfig: js.configs.all, 13 | }); 14 | 15 | export default [ 16 | ...compat.extends("ts-important-stuff", "plugin:prettier/recommended"), 17 | { 18 | files: ["**/*.js", "**/*.ts"], 19 | languageOptions: { 20 | parser: babelParser, 21 | }, 22 | }, 23 | ]; 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vue-mf/root-config", 3 | "scripts": { 4 | "start": "webpack serve --port 9000 --env isLocal", 5 | "lint": "eslint src", 6 | "test": "cross-env BABEL_ENV=test jest --passWithNoTests", 7 | "format": "prettier --write .", 8 | "check-format": "prettier --check .", 9 | "prepare": "husky", 10 | "build": "concurrently pnpm:build:*", 11 | "build:webpack": "webpack --mode=production", 12 | "build:types": "tsc" 13 | }, 14 | "devDependencies": { 15 | "@babel/core": "^7.23.3", 16 | "@babel/eslint-parser": "^7.23.3", 17 | "@babel/plugin-transform-runtime": "^7.23.3", 18 | "@babel/preset-env": "^7.23.3", 19 | "@babel/preset-typescript": "^7.23.3", 20 | "@babel/runtime": "^7.23.3", 21 | "@eslint/eslintrc": "^3.2.0", 22 | "@eslint/js": "^9.19.0", 23 | "concurrently": "^9.1.2", 24 | "cross-env": "^7.0.3", 25 | "eslint": "^9.19.0", 26 | "eslint-config-prettier": "^10.0.1", 27 | "eslint-config-ts-important-stuff": "^1.1.0", 28 | "eslint-plugin-prettier": "^5.2.3", 29 | "html-webpack-plugin": "^5.3.2", 30 | "husky": "^9.1.7", 31 | "jest": "^29.7.0", 32 | "jest-cli": "^29.7.0", 33 | "prettier": "^3.4.2", 34 | "pretty-quick": "^4.0.0", 35 | "serve": "^14.2.4", 36 | "ts-config-single-spa": "^3.0.0", 37 | "typescript": "^5.7.3", 38 | "webpack": "^5.89.0", 39 | "webpack-cli": "^6.0.1", 40 | "webpack-config-single-spa-ts": "^6.0.1", 41 | "webpack-dev-server": "^5.2.0", 42 | "webpack-merge": "^6.0.1" 43 | }, 44 | "dependencies": { 45 | "@types/jest": "^29.5.14", 46 | "@types/systemjs": "^6.1.1", 47 | "@types/webpack-env": "^1.16.2", 48 | "single-spa": "^6.0.3", 49 | "single-spa-layout": "^3.0.0" 50 | }, 51 | "types": "dist/vue-mf-root-config.d.ts", 52 | "packageManager": "pnpm@10.2.0" 53 | } 54 | -------------------------------------------------------------------------------- /src/declarations.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.html" { 2 | const rawHtmlFile: string; 3 | export = rawHtmlFile; 4 | } 5 | 6 | declare module "*.bmp" { 7 | const src: string; 8 | export default src; 9 | } 10 | 11 | declare module "*.gif" { 12 | const src: string; 13 | export default src; 14 | } 15 | 16 | declare module "*.jpg" { 17 | const src: string; 18 | export default src; 19 | } 20 | 21 | declare module "*.jpeg" { 22 | const src: string; 23 | export default src; 24 | } 25 | 26 | declare module "*.png" { 27 | const src: string; 28 | export default src; 29 | } 30 | 31 | declare module "*.webp" { 32 | const src: string; 33 | export default src; 34 | } 35 | 36 | declare module "*.svg" { 37 | const src: string; 38 | export default src; 39 | } 40 | -------------------------------------------------------------------------------- /src/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vue Microfrontends 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | <% if (isLocal) { %> 18 | 25 | <% } %> 26 | 27 | 28 | 29 | 30 | 31 | 34 |
35 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/microfrontend-layout.html: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 | -------------------------------------------------------------------------------- /src/vue-mf-root-config.ts: -------------------------------------------------------------------------------- 1 | import { registerApplication, start } from "single-spa"; 2 | import { 3 | constructApplications, 4 | constructRoutes, 5 | constructLayoutEngine, 6 | } from "single-spa-layout"; 7 | import microfrontendLayout from "./microfrontend-layout.html"; 8 | 9 | const routes = constructRoutes(microfrontendLayout); 10 | const applications = constructApplications({ 11 | routes, 12 | loadApp({ name }) { 13 | return import(/* webpackIgnore: true */ name); 14 | }, 15 | }); 16 | const layoutEngine = constructLayoutEngine({ routes, applications }); 17 | 18 | applications.forEach(registerApplication); 19 | layoutEngine.activate(); 20 | start(); 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "ts-config-single-spa", 3 | "files": ["src/vue-mf-root-config.ts"], 4 | "compilerOptions": { 5 | "declarationDir": "dist" 6 | }, 7 | "include": ["src/**/*"], 8 | "exclude": ["src/**/*.test*"] 9 | } 10 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const { merge } = require("webpack-merge"); 2 | const singleSpaDefaults = require("webpack-config-single-spa-ts"); 3 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 4 | 5 | module.exports = (webpackConfigEnv, argv) => { 6 | const orgName = "vue-mf"; 7 | const defaultConfig = singleSpaDefaults({ 8 | orgName, 9 | projectName: "root-config", 10 | webpackConfigEnv, 11 | argv, 12 | disableHtmlGeneration: true, 13 | }); 14 | 15 | return merge(defaultConfig, { 16 | // modify the webpack config however you'd like to by adding to this object 17 | // devServer: { 18 | // hot: false, 19 | // }, 20 | plugins: [ 21 | new HtmlWebpackPlugin({ 22 | inject: false, 23 | template: "src/index.ejs", 24 | templateParameters: { 25 | isLocal: webpackConfigEnv && webpackConfigEnv.isLocal, 26 | orgName, 27 | }, 28 | }), 29 | ], 30 | }); 31 | }; 32 | --------------------------------------------------------------------------------