background

Project A integrates projects of multiple lines of business, but uses A Git repository and A release pipeline together, resulting in A chaotic code organization. A new business line needs to be introduced this time. If it is directly developed in project A, the original project will be more complicated and chaotic. Therefore, we plan to release this project separately and embed it in project A in the way of micro front-end.

plan

Micro front-end (qiankun)

  1. Stack independent

2. Independent development and deployment of the microapplication warehouse are independent, and the front and back ends can be independently developed. After deployment, the main framework automatically completes synchronous update. Independent run time States are isolated between each microapplication and run time states are not shared

architecture

steps

  1. Transform the existing A project into the main application

    1. Install qiankun

    2. Register microapplications in the main application

      Once the url of the browser changed after the microapplication information was registered, the matching logic of qiankun would be automatically triggered. All microapplications matched by activeRule rules would be inserted into the specified Container and the lifecycle hooks exposed by the microapplication would be called in turn.

      const ENV = getENV();
      const genActiveRule = hash= > location= > location.hash.startsWith(hash);
      let microApp = {
          name: "microApp".entry: {
              localhost: "//localhost:8080".test: "//test.com".online: "//online.com"
          }[ENV],
          container: "#microApp".activeRule: genActiveRule("#/microApp")};Copy the code
    3. The microapplication needs to use the menu and toolbar of the main application, so the microapplication is loaded in the entry.vue file

      // entry.vue
      import {start} from 'qiankun';
      mounted() {
          // Start the microapplication
          if (!window.qiankunStarted) {
              window.qiankunStarted = true; start(); }}Copy the code
    4. The primary application must reserve the route entry for the sub-application to load. For details, see FaQs

      / * * *@description Subapplication route */
      {
          path: '/microApp/*'.name: 'microApp'.component: resolve= > require(['.. /views/entry.vue'], resolve),
      },
      Copy the code
    5. 404 page configuration

      Instead of writing wildcard *, you can register the 404 page as a normal routing page, such as /404, and check the main application’s routing hook function to redirect to the 404 page if it is neither the main application route nor the micro application.

      const childrenPath = ['microApp'];
      router.beforeEach((to, from, next) = > {
        if(to.name) { // If there is a name attribute, it indicates the route of the primary application
          next()
        }
        if(childrenPath.some(item= > to.path.includes(item))){
          next()
        }
        next({ name: '404'})})Copy the code
    6. Due to a naming conflict, all window.Vue in the main application was changed to window.vue2

  2. Transform new projects into microapplications

    1. Three hooks are exposed for the main application to load

      // The parent application loads the child application, which must expose three interfaces: bootstrap, mount, and unmount
      export async function bootstrap(props) {};export async function mount(props) {
          render(props)
      }
      
      export async function unmount(props) {
          instance.$destroy();
      }
      Copy the code
    2. Ensure that subapplications can run independently and as microapplications

      let instance = null
      function render(props) {
          instance = new Vue({
              router,
              store,
              render: (h) = > h(App),
          }).$mount("#app"); // This is mounting to its OWN HTML and the base is going to take this mounted HTML and insert it into it
      }
      
      if (window.__POWERED_BY_QIANKUN__) { // Dynamically add publicPath
          __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
      }
      if (!window.__POWERED_BY_QIANKUN__) { // Run independently by default
          render();
      }
      Copy the code
    3. Allows cross-domain and UMD packaging

      // vue.config.js
      module.exports = {
          devServer: {
              port: '8080'.headers: {
                  'Access-Control-Allow-Origin': The '*'}},configureWebpack: {
              output: {
                  library: 'microApp'.libraryTarget: 'umd'}}};Copy the code
  3. Routing patterns

    1. Keep the hash mode of the main application unchanged. (When the main application is a Vue project and its child application is react, the main application uses hash mode, which causes the child application cannot be loaded. Therefore, the main application is changed to history mode later.)
    2. Microapplications also use hash mode, but path is prefixed with “/microApp”
  4. The difficulties in

    1. Deployment scheme:

      1. The master and child apps are deployed in different Dockers

        # dockerfille
        FROM registry.cn-beijing.aliyuncs.com/xxxxx/tengine22.1.:v03.
        
        ADD ./dist /usr/share/nginx/html/dist
        
        WORKDIR /usr/share/nginx/html
        
        COPY default.conf /etc/nginx/conf.d/default.conf
        Copy the code
        # dockerfile.dapper
        FROM registry.cn-beijing.aliyuncs.com/xxxx/node:14.15-buster-slim
          
        USER root
          
        ENV DAPPER_SOURCE /code
          
        ENV DAPPER_RUN_ARGS "-v /tmp/mvnrepo:/root/.m2/repository"
          
        ENV DAPPER_ENV APP_ENV APP_GROUP APP_NAME APP_VERSION
        ENV HOMEThe ${DAPPER_SOURCE}
        WORKDIRThe ${DAPPER_SOURCE}
          
        ENTRYPOINT ["bash"."./scripts/entry"]
        CMD ["ci"]
        Copy the code
      2. Subapplications apply for domain names

    2. The child applications stand alone

      Business code, interface, build, deploy

    3. Microapplication font file access 404

      1. The reason is that qiankun changed the outer chain style to an inline style, but the loading path of the font file and background image is relative. Once a CSS file is packaged, you can’t change the path of font files and background images by dynamically modifying publicPath.

      2. The solution

        const os = require('os')
        
        let publicPath = process.env.BASE_URL
        let ip = ' '
        let port = '8080'
        if (process.env.npm_lifecycle_event === 'serve') {
            const network =
                os.networkInterfaces()['vEthernet (Default Switch)'] ||
                os.networkInterfaces().en0
            ip = network[1].address
            publicPath = `http://${ip}:${port}/ `
        }
        module.exports = {
            lintOnSave: process.env.NODE_ENV === 'development',
            publicPath,
            chainWebpack: (config) = > {
                config.resolve.symlinks(true)
                const fontRule = config.module.rule('fonts')
                fontRule.uses.clear()
                fontRule
                    .use('file-loader')
                    .loader('file-loader')
                    .options({
                        name: 'fonts/[name].[hash:8].[ext]',
                        publicPath,
                    })
                    .end()
                const imgRule = config.module.rule('images')
                imgRule.uses.clear()
                imgRule
                    .use('file-loader')
                    .loader('file-loader')
                    .options({
                        name: 'img/[name].[hash:8].[ext]',
                        publicPath,
                    })
                    .end()
            },
        }
        
        Copy the code
    4. All static resources (HTML, CSS, JS, img) need cORS set

      server {
          listen       80; Server_name _; # listening address, domain name, the location / {root/usr/share/nginx/HTML/dist /; try_files $uri $uri/ /index.html; add_headerCache-Control "no-cache, no-store"; # do not use cache add_headerAccess-Control-Allow-Origin *;
              add_header Access-Control-Allow-Methods 'GET.POST.OPTIONS';
              add_header Access-Control-Allow-Headers 'DNT.X-Mx-ReqToken.Keep-Alive.User-Agent.X-Requested-With.If-Modified-Since.Cache-Control.Content-Type.Authorization';
      
              if ($request_method = 'OPTIONS') {
                  return 204; } } location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ { root /usr/share/nginx/html/dist/; # add_headerCache-Control 'max-age=1296000';
              add_header Access-Control-Allow-Origin *;
          }
      
          location /api {
              
          }
      }
      Copy the code

todos

The main app has a buggy page number permission control, which only hides the menu and can be bypassed if the page path is known

– Solution 1: The beforeRouterEnter hook determines whether the user has access to the page, which partially solves this problem

– Solution 2: Dynamically generate the router based on the permission information returned in the background. The code is complex, but the effect is better than Solution 1