├── .babelrc ├── .github ├── FUNDING.yml └── workflows │ └── build_and_deploy.yml ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── .prettierignore ├── LICENSE ├── README.md ├── eslint.config.mjs ├── jest.config.json ├── package.json ├── pnpm-lock.yaml ├── src ├── component-library │ └── page-header.vue ├── global.css └── vue-mf-styleguide.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"], 3 | "plugins": [] 4 | } 5 | -------------------------------------------------------------------------------- /.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 | 31 | - name: Build 32 | run: pnpm run build 33 | 34 | - name: Store artifact 35 | uses: actions/upload-artifact@v4 36 | with: 37 | name: dist 38 | path: dist 39 | 40 | release: 41 | name: Release 42 | needs: build_test 43 | runs-on: ubuntu-latest 44 | if: ${{ github.ref == 'refs/heads/main' }} 45 | permissions: 46 | contents: "read" 47 | id-token: "write" 48 | 49 | steps: 50 | - name: Download build artifact 51 | uses: actions/download-artifact@v4 52 | with: 53 | name: dist 54 | 55 | - name: Authenticate with GCP 56 | uses: "google-github-actions/auth@v2" 57 | with: 58 | project_id: neural-passkey-248222 59 | workload_identity_provider: "projects/654158993889/locations/global/workloadIdentityPools/github/providers/my-repo" 60 | service_account: github-workload-identity-feder@neural-passkey-248222.iam.gserviceaccount.com 61 | 62 | - name: Upload Static Files to CDN 63 | uses: "google-github-actions/upload-cloud-storage@v2" 64 | with: 65 | path: . 66 | destination: vue.microfrontends.app/${{ github.event.repository.name }}/${{ github.run_id }}/ 67 | 68 | - name: Update Import Map 69 | uses: single-spa/action-deploy-to-import-map-deployer@v1 70 | with: 71 | host: ${{ secrets.DEPLOYER_HOST }} 72 | username: ${{ secrets.DEPLOYER_USERNAME }} 73 | password: ${{ secrets.DEPLOYER_PASSWORD }} 74 | environment-name: vue 75 | service-name: "@vue-mf/${{ github.event.repository.name }}" 76 | service-url: "https://vue.microfrontends.app/${{ github.event.repository.name }}/${{ github.run_id }}/vue-mf-${{ github.event.repository.name }}.js" 77 | service-integrity-file-path: vue-mf-${{ github.event.repository.name }}.js 78 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | pnpm exec pretty-quick --staged && pnpm exec concurrently pnpm:test pnpm:lint 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | .prettierignore 3 | pnpm-lock.yaml 4 | package-lock.json -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 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 | # styleguide 2 | 3 | [![CircleCI](https://circleci.com/gh/vue-microfrontends/styleguide.svg?style=svg)](https://circleci.com/gh/vue-microfrontends/styleguide) 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/styleguide). 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 styleguide 33 | pnpm install 34 | pnpm start --https --port 9001 35 | ``` 36 | 37 | Note that you may use whichever port you would like to. Go to https://localhost:9001/vue-mf-styleguide.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 `9001`. 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 | pnpm install 62 | pnpm 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 | -------------------------------------------------------------------------------- /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("important-stuff", "plugin:prettier/recommended"), 17 | { 18 | languageOptions: { 19 | parser: babelParser, 20 | }, 21 | }, 22 | ]; 23 | -------------------------------------------------------------------------------- /jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "transform": { 3 | "^.+\\.tsx?$": "babel-jest" 4 | }, 5 | "moduleNameMapper": { 6 | "\\.(css)$": "identity-obj-proxy" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "start": "webpack-dev-server", 4 | "build": "webpack --mode=production", 5 | "analyze": "webpack --mode=production --env.analyze=true", 6 | "lint": "eslint src", 7 | "format": "prettier --write .", 8 | "check-format": "prettier --check .", 9 | "test": "jest --passWithNoTests", 10 | "watch-tests": "jest --watch", 11 | "coverage": "jest --coverage", 12 | "prepare": "husky install" 13 | }, 14 | "devDependencies": { 15 | "@babel/core": "^7.15.0", 16 | "@babel/eslint-parser": "^7.15.0", 17 | "@babel/preset-env": "^7.15.0", 18 | "@eslint/eslintrc": "^3.2.0", 19 | "@eslint/js": "^9.19.0", 20 | "@types/jest": "^29.5.14", 21 | "babel-jest": "^29.7.0", 22 | "babel-loader": "^9.2.1", 23 | "concurrently": "^9.1.2", 24 | "css-loader": "^7.1.2", 25 | "eslint": "^9.19.0", 26 | "eslint-config-important-stuff": "^1.1.0", 27 | "eslint-config-prettier": "^10.0.1", 28 | "eslint-plugin-prettier": "^5.2.3", 29 | "husky": "^9.1.7", 30 | "identity-obj-proxy": "^3.0.0", 31 | "jest": "^29.7.0", 32 | "jest-cli": "^29.7.0", 33 | "prettier": "^3.4.2", 34 | "pretty-quick": "^4.0.0", 35 | "style-loader": "^4.0.0", 36 | "systemjs-webpack-interop": "^2.3.7", 37 | "vue-loader": "^17.4.2", 38 | "vue-template-compiler": "^2.6.14", 39 | "webpack": "^5.51.1", 40 | "webpack-cli": "^6.0.1", 41 | "webpack-config-single-spa": "^7.0.1", 42 | "webpack-dev-server": "^5.2.0", 43 | "webpack-merge": "^6.0.1" 44 | }, 45 | "dependencies": { 46 | "vue": "^3.5.13" 47 | }, 48 | "packageManager": "pnpm@10.2.0" 49 | } 50 | -------------------------------------------------------------------------------- /src/component-library/page-header.vue: -------------------------------------------------------------------------------- 1 | 6 | 11 | 26 | -------------------------------------------------------------------------------- /src/global.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css?family=Montserrat&display=swap"); 2 | 3 | :root { 4 | /* 1rem === 10px, but remain responsive to user font size settings */ 5 | font-size: 62.5%; 6 | 7 | --navbar-width: 23.6rem; 8 | } 9 | 10 | body { 11 | font-family: "Montserrat", sans-serif; 12 | /* default font size is 16px */ 13 | font-size: 1.6rem; 14 | background-color: #efefef; 15 | margin: 0; 16 | } 17 | -------------------------------------------------------------------------------- /src/vue-mf-styleguide.js: -------------------------------------------------------------------------------- 1 | import "./global.css"; 2 | 3 | // You can export Vue components from this file and import them into your microfrontends 4 | export { default as PageHeader } from "./component-library/page-header.vue"; 5 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const { merge } = require("webpack-merge"); 2 | const singleSpaDefaults = require("webpack-config-single-spa"); 3 | const { VueLoaderPlugin } = require("vue-loader"); 4 | 5 | module.exports = (webpackConfigEnv) => { 6 | const defaultConfig = singleSpaDefaults({ 7 | orgName: "vue-mf", 8 | projectName: "styleguide", 9 | webpackConfigEnv, 10 | outputSystemJS: false, 11 | }); 12 | 13 | const config = merge(defaultConfig, { 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.vue$/, 18 | use: ["vue-loader"], 19 | }, 20 | ], 21 | }, 22 | externals: ["vue", "vue-router", /^@vue-mf\/.+/], 23 | plugins: [new VueLoaderPlugin()], 24 | }); 25 | 26 | return config; 27 | }; 28 | --------------------------------------------------------------------------------