An overview of the

Recently, I tried to get a general overview of the basic uses of vue2/vue3/ React/Angular2, and wanted to integrate things made with these frameworks, so I chose quankun, a popular micro front-end tool for the last two years, to integrate the project. The build instructions may be a little simple, but you can read them with links to other articles and project code that I’ve posted.

Set up the order

Because it is a micro front-end practice project integrating multiple frameworks, there will be a lot of directory structure involved, so it is necessary to list the order in advance and explain the construction process step by step to avoid too chaotic description.

  1. In this paper, vue2 + typescript + VUE-CLI4 + Qiankun + SCSS is used to build the base application. Then each micro-application introduced in the base will be displayed as a business module in the menu.
  2. Build article module based on VUe3, project collocation scheme is vue3 + Vite2 + typescript + SCSS.
  3. React17 + WebPack5 + redux + typescript + hooks react17 + WebPack5 + redux + typescript + hooks
  4. Build a Sprite module based on Angular2. The project works with Angular10.2.0 + single-spa-Angular.
  5. Build a server-side program based on Nest. js for data management. The project collocation method is Nest. js + typescript + Mongoose + mongoDB.

Build base application

As a front-end project, need to have a main application as a base, used to connect the different application projects, these applications can be a big modules of the overall project, also can is showing a certain part of the content of a page, just similar to use iframe, with their own project needs, optional introduction to display the content. A little more complicated than iframe, of course, but more powerful than iframe and better suited to single-page projects.

My base application uses vue2 + typescript + VUE-CLI4 + Qiankun + SCSS, so install VUE-CLI4 and run the following command on the terminal:

npm install -g @vue/cli
Copy the code

This will install the latest VUE-CLI4 into the global library.

Then create the project on the terminal using vue-CLI command

vue create project-main
Copy the code

When you execute the create command, you will enter the interface to select the create configuration. There are many articles on the Internet about what each step of the configuration means, SO I will not repeat them. I will just post my reference article:

Vue-cli4 + TS Building a New project (Foundation)

After creating the project, execute the following command on the terminal:

NPM Install Qiankun -- Save or YARN Add QiankunCopy the code

Then follow the steps in the linked article below to create the appropriate microapplication file to complete an initial base application structure:

Best Practices in Microfront-end based on Qiankun (Swastika) – from 0 to 1

Build vuE3 application

My Vue3 app uses vue3 + typescript + Vite2 + SCSS.

Run the following command on the terminal to start the Vite2 project generation process:

npm init vite
Copy the code

Enter the project name:

Select the framework to use:

Choose whether to write in JS or TS:

Press Enter to generate a vue3 + typescript + vite2 project

There is no state machine in the directory, no routing, and no style preprocessor, so you also need to execute at the terminal:

NPM install vue-router@next vuex@next --save or yarn add vue-router@next vuex@nextCopy the code
NPM install node-sass sass sass-loader --save-dev or yarn add node-sass sass sass-loader --devCopy the code

This gives you the state machine, routing, and style preprocessing, plus the corresponding processing files.

As a micro-application or sub-application, bootstrap/mount/unmount life cycle functions need to be exported because the building tool is Vite2. In order to better support the introduction of Qiankun, I used a third-party dependency package to be executed at the terminal:

yarn add vite-plugin-qiankun path --dev
Copy the code

For details about how to use this dependency package, see github.com/tengmaoqing…

In vite.config.ts, add the following code:

import { resolve } from 'path'
import qiankun from 'vite-plugin-qiankun'

export default defineConfig({
  plugins[...//Vue3MicroApp is the name in SRC /micro/apps.ts of the corresponding base application
    qiankun('Vue3MicroApp', {useDevMode: true})]})Copy the code

Add the following code to SRC /main.ts:

import { createApp } from 'vue';
import { createRouter, createWebHistory } from 'vue-router';
import { renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper';
import routes from "@/router/routes";
import { store, key } from "@/store";
import App from '@/App.vue';

let instance: any = null;
let router: any = null;

/** * Render functions * two cases: run in the main application lifecycle hook/run when the micro-application starts alone */
 function render(props: any) {
  // Create VueRouter in render to ensure that the location event listener is removed when the microapplication is uninstalled to prevent event contamination
  const routerBase = qiankunWindow.__POWERED_BY_QIANKUN__ ? "/micro/vue3" : "/";
  router = createRouter({
    history: createWebHistory(routerBase),
    routes: routes
  });

  // Container DOM or ID
  const containerEle = props.container ? props.container.querySelector('#app') : '#app';
  // Mount the application
  instance = createApp(App);
  instance.use(router).use(store, key).mount(containerEle);
}

renderWithQiankun({
  /** * The application calls the mount method every time it enters, and usually we trigger the application's render method here */
  mount(props) {
    console.log("Vue3MicroApp mount", props);
    render(props);
  },
  /** * Bootstrap will only be called once during the micro-application initialization. The next time the micro-application re-enters, the mount hook will be called directly. Bootstrap will not be triggered again. * This is usually where we can initialize global variables, such as application-level caches that will not be destroyed during the unmount phase. * /
  bootstrap() {
    console.log("Vue3MicroApp bootstraped");
  },
  /** * The method that is called each time the application is cut/unloaded, usually here we unload the application instance of the microapplication */
  unmount(props: any) {
    console.log("Vue3MicroApp unmount");
    // Unmount the application instance
    instance.unmount();
    instance = null;
    router = null; }});// Mount the application directly when running independently
if(! qiankunWindow.__POWERED_BY_QIANKUN__) { render({}); }Copy the code

This completes the docking base application.

Note that when I instantiate vue in my code, I write:

instance = createApp(App);
instance.use(router).use(store, key).mount(containerEle);
Copy the code

Instead of writing:

instance = createApp(App).use(router).use(store, key).mount(containerEle);
Copy the code

The first method returns a VUE instance object, while the second method returns a Proxy object. Therefore, the second method cannot find the unmount method and cannot execute the instance unmount.

Setting up the React application

Package list:

"dependencies": {
    "@babel/runtime-corejs3": "^ 7.13.10"."axios": "^ 0.21.1"."lodash": "^ 4.17.21"."react": "^ 17.0.2"."react-dom": "^ 17.0.2"."react-redux": "^ 7.2.5." "."react-router-dom": "^ 5.3.0." "."redux": "^ 4.4.1"."redux-saga": "^ 1.1.3." "
  },
  "devDependencies": {
    "@babel/core": "^ 7.13.13"."@babel/plugin-transform-runtime": "^ 7.13.10"."@babel/preset-env": "^ 7.13.12"."@babel/preset-react": "^ 7.13.13"."@babel/preset-typescript": "^ 7.13.0"."@commitlint/cli": "^ 12.0.1"."@commitlint/config-conventional": "^ 12.0.1"."@types/lodash": "^ 4.14.176"."@types/react": "^ 17.0.3"."@types/react-dom": "^ 17.0.3"."@types/react-router-dom": "^ 5.3.1"."@types/webpack-env": "^ 1.16.0"."@typescript-eslint/eslint-plugin": "^ 4.19.0"."@typescript-eslint/parser": "^ 4.19.0"."autoprefixer": "^ 10.2.5"."babel-loader": "^ 8.2.2"."chalk": "^ 4.1.0." "."clean-webpack-plugin": "^ 3.0.0"."conventional-changelog-cli": "^ 2.1.1"."copy-webpack-plugin": "^ 8.1.0"."cross-env": "^ 7.0.3." "."css-loader": "^ 5.2.0." "."css-minimizer-webpack-plugin": "^ 1.3.0"."detect-port-alt": "^ 1.1.6." "."error-overlay-webpack-plugin": "^ 0.4.2"."eslint": "^ 7.22.0"."eslint-config-airbnb": "^ 18.2.1"."eslint-config-prettier": "^ 8.1.0"."eslint-import-resolver-typescript": "^ 2.4.0." "."eslint-plugin-import": "^ 2.22.1"."eslint-plugin-jsx-a11y": "^ 6.4.1." "."eslint-plugin-prettier": "^ 3.3.1"."eslint-plugin-promise": "^ 4.3.1." "."eslint-plugin-react": "^ 7.23.1"."eslint-plugin-react-hooks": "^ 4.2.0"."eslint-plugin-unicorn": "^ 29.0.0"."fork-ts-checker-webpack-plugin": "^ 6.2.0"."html-webpack-plugin": "^ 5.3.1"."husky": "^ 4.3.8"."ip": "^ 1.1.5." "."is-root": "^ 2.1.0." "."lint-staged": "^ 10.5.4"."mini-css-extract-plugin": "^ 1.4.0." "."node-sass": "^ 5.0.0"."postcss": "^ 8.2.8"."postcss-flexbugs-fixes": "^ 5.0.2"."postcss-loader": "^ 5.2.0." "."postcss-preset-env": "^ 6.7.0"."prettier": "^ 2.2.1." "."sass-loader": "^ 11.0.1"."style-loader": "^ 2.0.0." "."stylelint": "^ 13.12.0"."stylelint-config-prettier": "^ 8.0.2." "."stylelint-config-rational-order": "^ 0.1.2." "."stylelint-config-standard": "^ 21.0.0"."stylelint-declaration-block-no-ignored-properties": "^ 2.3.0." "."stylelint-order": "^ 4.1.0." "."stylelint-scss": "^ 3.19.0"."terser-webpack-plugin": "^ 5.1.1." "."typescript": "^ holdings"."webpack": "^ 5.58.1"."webpack-bundle-analyzer": "^ 4.4.0"."webpack-cli": "^ 4.9.0"."webpack-dev-server": "^ 4.3.1." "."webpack-merge": "^ 5.7.3." "."webpackbar": "^ 5.0.0-3"
  }
Copy the code

After installing the dependency packages according to the directory above, add the following code into the portal file app. TSX to complete the connection of qiankun:

function renderApp(props: any) {
  const { container } = props;
  const appEle = container? container.querySelector('#root') : document.getElementById('root');
  ReactDOM.render(
    <Provider store={store}>
      <BasicRoute />
    </Provider>
    , appEle
  );
}

if((window as any).__POWERED_BY_QIANKUN__) {
  // Dynamically set webpack publicPath to prevent resource loading errors
  // eslint-disable-next-line no-undef
  __webpack_public_path__ = (window as any).__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

if(! (window as any).__POWERED_BY_QIANKUN__) {
  renderApp({});
}

/** * Bootstrap will only be called once during the micro-application initialization. The next time the micro-application re-enters, the mount hook will be called directly. Bootstrap will not be triggered again. * This is usually where we can initialize global variables, such as application-level caches that will not be destroyed during the unmount phase. * /
export async function bootstrap() {
  console.log("ReactMicroApp bootstraped");
}

/** * The application calls the mount method every time it enters, and usually we trigger the application's render method here */
export async function mount(props: any) {
  console.log("ReactMicroApp mount");
  renderApp(props);
}

/** * The method that is called each time the application is cut/unloaded, usually here we unload the application instance of the microapplication */
export async function unmount(props: any) {
  console.log("ReactMicroApp unmount");
  const { container } = props;
  const appEle = container? container.querySelector('#root') : document.getElementById('root');
  ReactDOM.unmountComponentAtNode(appEle);
}
Copy the code

I wrote the webpack configuration for this project following the link below:

Still wondering why the project scaffolding code was written that way? Here is a complete guide to configuring the Webpack5 + React + typescript environment

However, some of the configuration items in this link are somewhat different from the webpack5 configuration, so it is necessary to note the differences:

Scripts/config/webpack. Dev. Js, the output and devServer configuration items will be subject to the following code, the link above the configuration in the article does not agree with webpack5 official documentation usage

output: {
  filename: 'js/[name].js'.path: paths.appBuild,
  library: {
    name: `${paths.microAppName}`.type: 'umd',},chunkLoadingGlobal: `webpackJsonp_${paths.microAppName}`.globalObject: 'window'
},
devServer: {
  compress: true.client: {
    logging: 'info'.overlay: true.progress: true,},open: true.hot: false.proxy: {
    ...require(paths.appProxySetup),
  },
  headers: {
    'Access-Control-Allow-Origin': The '*',},historyApiFallback: true.liveReload: false,},Copy the code

Scripts/config/webpack. Prod. Js, the output configuration items will be subject to the following code:

output: {
  filename: 'js/[name].[contenthash:8].js'.path: paths.appBuild,
  assetModuleFilename: 'images/[name].[contenthash:8].[ext]'.library: {
    name: `${paths.microAppName}-[name]`.type: 'umd'
  },
  chunkLoadingGlobal: `webpackJsonp_${paths.microAppName}`.globalObject: 'window'
},
Copy the code

At this point, the React application’s microapplication coordination and WebPack configuration are all written.

Building an Angular App

Install Angular scaffolding by executing the following command on the terminal:

npm install -g @angular/cli
Copy the code

Once the scaffolding is installed, create an Angular project by executing ng new Angular-demo. Here I post the package list directly:

  "dependencies": {
    "@angular/animations": "~ 10.2.0"."@angular/common": "~ 10.2.0"."@angular/compiler": "~ 10.2.0"."@angular/core": "~ 10.2.0"."@angular/forms": "~ 10.2.0"."@angular/platform-browser": "~ 10.2.0"."@angular/platform-browser-dynamic": "~ 10.2.0"."@angular/router": "~ 10.2.0"."@fortawesome/angular-fontawesome": "^ 0.7.0"."@fortawesome/fontawesome-svg-core": "^ 1.2.32"."@fortawesome/free-brands-svg-icons": "^ 5.15.1"."@fortawesome/free-regular-svg-icons": "^ 5.15.1"."@fortawesome/free-solid-svg-icons": "^ 5.15.1"."angular2-websocket": "^ 0.9.8"."axios": "^ 0.21.0"."jsencrypt": 1 "" ^ 3.0.0 - rc.."rxjs": "~ 6.6.0"."single-spa": "> = 4.0.0"."single-spa-angular": "4.9.2"."tslib": "^ 2.0.0." "."zone.js": "~ 0.10.2." "
  },
  "devDependencies": {
    "@angular-builders/custom-webpack": "10.1.0 - beta. 0"."@angular-devkit/build-angular": "~ 0.1002.0"."@angular/cli": "~ 10.2.0"."@angular/compiler-cli": "~ 10.2.0"."@types/node": "^ 12.11.1." "."@types/jasmine": "~ 3.5.0." "."@types/jasminewd2": "~ 2.0.3"."codelyzer": "^ 6.0.0"."jasmine-core": "~ 3.6.0"."jasmine-spec-reporter": "~ 5.0.0"."karma": "~ 5.0.0"."karma-chrome-launcher": "~ 3.1.0"."karma-coverage-istanbul-reporter": "~ 3.0.2." "."karma-jasmine": "~ 4.0.0"."karma-jasmine-html-reporter": "^ 1.5.0." "."protractor": "~ 7.0.0." "."ts-node": "~ 8.3.0"."tslint": "~ 6.1.0"."typescript": "~ 4.0.2." "
  }
Copy the code

The angular project’s build configuration file is angular.json. The current version is Angular12 and I’m using Angular10, so my Angular. json configuration may be slightly different from the current version of Angular.

See the official angular documentation at angular.cn/docs

My main link for Angular micro-app access is the following:

Best Practices in Microfront-end based on Qiankun (Swastika) – from 0 to 1

Using single-spa-Angular to complete the Link to Angular, my Angular application will display the following error when accessing from the base:

Navigation triggered outside Angular zone, did you forget to call ‘ngZone.run()’?

If you know how to solve the problem, please let me know in the comments section, thanks ~~

Server program construction

Install nest. Js scaffolding by executing the following instructions:

npm i -g @nestjs/cli
Copy the code

Then execute the following commands to create the Nest project:

nest new project-serve
Copy the code

After creating the project, I completely refer to the following links to build nest:

How to build a blog with Nest.js, MongoDB, and vue.js

Follow the description of the article in the link to build a nest. Js + Mongoose server application, very simple.

Basic use of framework

Here is a brief overview of the vuE3 and Act17 frameworks.

The use of vue3

Currently vue3 can be written in the following three ways:

  • One is written in the same way as VUe2, which I suspect was written to facilitate the project migration of VUe2.
export default {
  name: 'TestOne'.props: {
    msg: {
      type: String.default: ' '}},data() {
    return {
      testList: [{name: 'List one'},
        {name: 'List two'},
        {name: 'List three'}}},],computed: {},created(){},mounted(){},mothods: {}};Copy the code
  • One way to write it is that the JS code is all written in setup, and then the data and logic are handled through vue3’s various hooks.
import { 
  defineComponent,
  onBeforeMount,
  onMounted,
  reactive,
  toRefs
} from 'vue';

type PageState = {
  list: any
}

export default defineComponent({
  name: 'TestTwo'.setup() {
    let state = reactive<PageState>({
      list: []});const getList = () = > {
      state.list = [
        {name: 'List one'}, 
        {name: 'List two'}, 
        {name: 'List three'},]; }; onBeforeMount(() = > {
      getList();
    });

    onMounted(() = >{});return {
      ...toRefs(state)
    }
  }
});
Copy the code
  • One is to place the attribute setup directly on the script tag, indicating that all the code that setup runs is executed internally.
<script lang="ts" setup>
import { defineProps, computed } from 'vue';
import { useStore } from 'vuex';
import { key } from '@src/store';

type Props = {
  msg: string
}
defineProps<Props>();
const store = useStore(key);
const count = computed(() = > {
  return store.state.count;
});
const inCrement = () = > {
  store.commit('increment');
};
</script>
Copy the code

Which one is better depends on the individual project.

Here are two articles from other diggers that are pretty clear on the use of VUE3:

Vue 3 gets you up to speed in 30 minutes

Vue3 Script setup syntax

React + hooks

Function components can use the same lifecycle as class components, which is much better for tree shaking, and looks much simpler than before. I suspect react was a big inspiration for vue3.

The basic code is as follows:

import { useState, useEffect } from 'react';
import { useDispatch } from "react-redux";
import { useHistory } from 'react-router-dom';
import * as planApi from '@src/api/plan';

interface PlanItem {
  readonly _id: string;
  readonly title: string;
  readonly time: number;
  readonly desc: string;
}

const PlanList: React.FC<{}> = () = > {
  const [list, setList] = useState([]);
  const dispatch = useDispatch();
  const routerHistory = useHistory();

  / * * useEffect ACTS as * componentDidMount/componentDidUpdate/componentWillUnmount three * / life cycle combination
  useEffect(() = >{(async() = > {const planList: any = awaitplanApi.getPlanList({}); setList(planList) })(); } []);// Jump to the details page
  const goDetail = (id: string) = > {
    routerHistory.push({
      pathname: '/planDetail'.state: {
        id: id
      }
    });
  }

  let getList = (list: Array<PlanItem>) = > {
    return list.map(item= > {
      return (
        <li className="item" onClick={()= > goDetail(item._id)}>
          <div className="p1">
            <div className="item-title">{item.title}</div>
            <div className="item-time">{item.time}</div>
          </div>
          <div className="p2">{item.desc}</div>
        </li>)})}return (
    <div className="planList">
      <ul>
        {getList(list)}
      </ul>
    </div>)}Copy the code

As you can see from the code above, the hooks use a variety of wrapped hook functions to accomplish life cycles, data management, page jumps, etc. Note that hooks are named from components that start with use. When called from components, they must be placed at the top of the function, not in judgment or nested functions. Otherwise, hooks cannot be detected.

Suspense component for lazy loading has also been added to make lazy loading components more elegant.

I mainly refer to the following articles written by Digger, which I think are pretty good:

React + TypeScript practices

2021-typescript + React Best practices

Relearn Redux and React-redux in TypeScript using Hooks

Stop asking React Hooks if they can replace Redux

Use React Hooks instead of Redux for state management and asynchronous requests, based on Typescript

At the end

Write this article in addition to exchange and share, but also to make a record for themselves, in case of some things forget, can also turn over to have a look, sorry for the bad writing!