The introduction

Hello, this is Anyin.

Recently, a small project (small program ordering system) was reconfigured as a microservice + microapplication model, with Vue3 + TS + ElementPlus + Qiankun as the front-end technology stack. Here I document the process of building basic microapplications.

Notice in advance: this small program ordering system code after the plan open source, and the reconstruction of the project is also planned open source, please look forward to.

Related address of the project after reconstruction:

  • Back-end: Anyin Cloud
  • Front base: Anyin Cloud Parent
  • Front-end microapplication: Anyin Cloud Base

Ok, next, let’s see how to build our microapplication project based on Vue3+TS+ElementPlus+Qiankun.

Create the main application (dock)

In addition, why not use Vite to build here? The reason is that there are some problems with the application of Vite combined with Qiankun construction (pit mining), so we gave up here.

Initialize the project

First, we create a parent project using vue-CLI:

# vue create anyin-cloud-parent
Copy the code

Next, manually select the component we want to add:

We select the following components to build the application, use the space bar for multiple selections, and press Enter:

Select the vue3.x version

The related code style and routing mode are used by default, CSS compilation we use less:

For the coding specification we use ESLint + Airbnb Config:

Finally, the full options are as follows:

When vue-CLI helped us create the project, we entered the project to check the project directory, as follows:

Install Qiankun

First of all, the main application of Qiankun required installation dependency, while the micro application did not need installation dependency and could modify the configuration. Here we first install the dependencies in the main application

# npm i qiankun -S
Copy the code

Next, the relevant microapplication configuration is carried out. We added a new app.ts to record the entry of all microapplications:

// src/modules/apps.ts
const apps: any[] = [{name: 'anyin-center-base'.entry: '//localhost:4001'.container: '#micro-app'.activeRule: '/base',},];export default apps;
Copy the code

Then, register the microapplication and export the start method

// src/modules/index.ts
import {
  registerMicroApps,
  addGlobalUncaughtErrorHandler,
  start,
} from 'qiankun';

// Micro application information
import apps from "@/modules/app";

/** * Register microapplication * first parameter - registration information for microapplication * second parameter - global lifecycle hook */
registerMicroApps(apps, {
  // Qiankun Lifecycle Hook - micro application before loading
  beforeLoad: (app: any) = > {
    // Load the progress bar before loading the microapplication
    console.log('before load', app.name);
    return Promise.resolve();
  },
  // Qiankun Lifecycle Hook - micro application after mount
  afterMount: (app: any) = > {
    // The progress bar is loaded before the microapplication is loaded
    console.log('after mount', app.name);
    return Promise.resolve(); }});/** * Adds a global uncaught exception handler */
addGlobalUncaughtErrorHandler((event: Event | string) = > {
  console.error(event);
});

// Export the startup function of Qiankun
export default start;
Copy the code

Once the start method is exported, it needs to be executed on the entry

// src/main.ts
import { createApp } from "vue";
import App from "./App.vue";
import router from './router';
import store from './store';
import start from './modules';


const app = createApp(App)
start();

app.use(store).use(router).mount('#app');
Copy the code

Install ElementPlus

For the entire interface, we want the overall layout to look like this:

So, after installing ElementPlus, we need to do this layout.

First, install ElementPlus

# npm i element-plus -S
Copy the code

Next, we introduce the ElementPlus component in main.ts as follows:

// src/main.ts
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
// Introduce ElementPlus components and styles
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import start from './modules';

const app = createApp(App)

start();

app.use(store)
  .use(router)
  / / use ElementPlus
  .use(ElementPlus)
  .mount("#app");
Copy the code

Then create a Layout component layout.vue as follows:

<! -- src/views/layout/Layout.vue -->
<template>
  <el-container>
    <el-header>
      <el-row>
        <el-col :span="3">
          <h2>Anyin Cloud</h2>
        </el-col>
        <el-col :span="21">
          <el-menu :router="true" mode="horizontal" @select="handleSelect" :default-active="activeIndex">
            <el-menu-item :key="item.path" v-for="item in system" :index="item.path">
              {{ item.name }}
            </el-menu-item>
          </el-menu>
        </el-col>
      </el-row>
    </el-header>
    <el-container>
      <el-main class="main-container">
        <! -- Application mounted node -->
        <div id="micro-app"></div>
        <router-view />
      </el-main>
    </el-container>
  </el-container>
</template>
<script lang="ts" setup>
// Each application system
const system = [
  { name: "Home page".path: "/" },
  { name: "System Management".path: "/base" },
  { name: "User Management".path: "/customer" },
  { name: "Ordering Management".path: "/meal"},]const activeIndex = "";
const handleSelect = (key: string, keyPath: string[]) = > {
  console.log(key, keyPath);
};
</script>
<style lang="less">
.main-container {
  padding: 0 ! important;
}
</style>
Copy the code

Finally, register the component in app.vue

<template>
  <Layout/>
</template>
<script lang="ts" setup>
import Layout from "@/views/layout/Layout.vue";
</script>
<style lang="less">
body {
  margin: 0;
  padding: 0;
}
</style>
Copy the code

When running, it looks like this:

Creating a microapplication

The initialization of the microapplication is the same as the main application, which is not detailed here.

Microapplications don’t need to rely on Qiankun. Here we do some configuration.

configurationpublic-path.js

Add a public-path.js file to the SRC directory with the following contents:

// src/public-path.js
if (window.__POWERED_BY_QIANKUN__) {
    // eslint-disable-next-line no-undef
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
Copy the code

Import this file in main.ts

// src/main.ts
import "./public-path";
Copy the code

Configuring Routing Information

Add a new route configuration file, here we create the corresponding route information, and compatible with independent operation, the content is as follows:

// src/router/index.ts
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
import Home from ".. /views/Home.vue";
import Route from "@/views/route/Route.vue";
import RouteInfo from "@/views/route/RouteInfo.vue";
import Dict from "@/views/dict/Dict.vue";
import Config from "@/views/config/Config.vue";

const routes: Array<RouteRecordRaw> = [
  {
    path: "/".name: "Home".component: Home,
  },
  {
    path: "/route".name: "Route".component: Route,
  },
  {
    path: "/route/info/:id".name: "RouteInfo".component: RouteInfo,
  },
  {
    path: "/dict".name: "Dict".component: Dict,
  },
  {
    path: "/config".name: "Config".component: Config,
  },
];

const router = createRouter({
  // Use history mode and can run independently
  // When running microapplications, use '/base' for baseUrl, because the activeRule we configured in the main application is' /base '
  history: createWebHistory(window.__POWERED_BY_QIANKUN__ ? "/base/" : "/"),
  routes,
});

export default router;
Copy the code

Next, modify the main.ts code for instantiation as follows:

// src/main.ts
// Define a Vue instance
let instance: Vue.App<Element>;
// This interface needs to be defined, otherwise '/ SRC /router/index.ts' cannot use' window.__powered_by_QIANkun__ '
declare global {
  interfaceWindow { __POWERED_BY_QIANKUN__? :string; }}interface IRenderProps {
  container: Element | string;
}

// Render method
function render(props: IRenderProps) {
  const { container } = props;
  instance = createApp(App);
  instance
    .use(store)
    .use(router)
    .use(ElementPlus)
    .mount(
      container instanceof Element
        ? (container.querySelector("#app") as Element)
        : (container as string)); }// Independent runtime
if (!window.__POWERED_BY_QIANKUN__) {
  render({ container: "#app" });
}
Copy the code

The main thing here is to define a render method and then expose the Vue instance, since hooks will be used later in the microapplication lifecycle.

Exposed lifecycle hooks for microapplications

Microapplications also need to expose lifecycle hook methods so that the main application can recognize them. Add the following method to main.ts:

// src/main.ts
// Expose the main application lifecycle hook
export async function bootstrap() {
  console.log("subapp bootstraped");
}

export async function mount(props: any) {
  console.log("mount subapp");
  render(props);
}

export async function unmount() {
  console.log("unmount college app");
  instance.unmount();
}
Copy the code

Modifying the packaging Configuration

There is no vue.config.js file to create a project using vue. Here we manually create a project and configure the relevant details, the code is as follows:

const path = require('path'); const { name } = require('./package'); const port = 4001; // dev port function resolve(dir) { return path.join(__dirname, dir); } module.exports = {outputDir: 'dist', assetsDir: 'static', filenameHashing: true, devServer: {host: '0.0.0.0', hot: true, disableHostCheck: true, port, overlay: { warnings: false, errors: true, }, headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': '*', 'Access-Control-Allow-Credentials': 'true', 'access-control-allow-methods ': '*',' cache-control ': 'no-cache'},}, // configureWebpack: Library: '${name}-[name]', libraryTarget: {alias: {'@': resolve(' SRC '),},}, output: { 'umd', jsonpFunction: `webpackJsonp_${name}`, } }, };Copy the code
  • So here we importpackage.jsonthenameField, because this needs to be configured with the main applicationapp.tsOf the filenameFields are consistent
  • headersAdd cross-domain configuration as the master application is passedfetchMethod to obtain microapplication resources, and microapplication is launched on a different port, so need to add cross-domain configuration
  • outputOnly after the packaging format of the microapplication is configured, the master application can correctly identify some configurations of the microapplication

Write the layout

Remember our picture below?

I think the Header should belong to the Main app, while the Aside and Main below are both micro-apps. Aside blocks are generally used to display menus. Personally, I think micro-apps should maintain their own menus instead of being maintained by the Main app.

So, in microapplications, we also need to deal with the menu layout on the left.

We added a layout. vue Layout file

// src/views/layout/Layout.vue
<template>
  <el-container>
    <el-aside width="180px">
      <el-menu
        :router="true"
        @select="handleSelect"
        :default-active="activeIndex"
      >
        <el-menu-item :key="item.path" v-for="item in menus" :index="item.path">
          {{ item.name }}
        </el-menu-item>
      </el-menu>
    </el-aside>
    <el-main>
      <router-view />
    </el-main>
  </el-container>
</template>
<script lang="ts" setup>
// The menu here should be retrieved from the back end
const menus = [
  { name: "Route Configuration".path: "/route" },
  { name: "Dictionary configuration".path: "/dict" },
  { name: "Parameter Configuration".path: "/config"},];const activeIndex = "";
const handleSelect = (key: string, keyPath: string[]) = > {
  console.log(key, keyPath);
};
</script>
Copy the code

The results of

So far, we have processed a basic structure of Vue3+TS+ElementPlus+Qiankun to construct a microapplication project.

Home page

Micro application

The last

Ok, we have built the basic framework of the micro-application project based on Vue3+TS+ElementPlus+Qiankun, and then it is time to fill in some tools and pages.

Source code address:

  • Main application: Anyin Cloud Parent
  • Microapplication: Anyin Cloud Base