手把手教你建立前端微服务【天地】

本文建立一个依据 Vue3.0 的微服务结构,并以 Angular、Vue3.0、React 作为微服务,接入主使用。文章比较具体,步骤较多,走完,一个基础的微服务结构就真的建立好了。咱们先将学习最小化,亲力亲为建立好之后,再去考虑怎样优化,有坑的话,记得留言哦~

1. 页面结构

下图是咱们建立的体系的页面结构,在主使用中,包括 header 和左边的 menu 部分,container 部分用来放置咱们的微服务,本文章会运用 Vue 3.0、Angular、React 三个前端流行结构作为示例。

手把手搭建乾坤微服务

2. 目录结构

咱们的项目目录结构如下,main 用来寄存主体系,sub 寄存子体系。别的,package.json 现在先不论,后面会揭秘。

├── main
└── sub
    └── sub-react
    └── sub-vue
    └── sub-angular

3. 实操

3.1 创立一个 MSP 的项目

mkdir msp
cd msp
npm init msp -y

3.2 按照上面的目录,咱们开始建立

1、首先是建立一个以 Vue 3.0 为结构的主使用,vue-cli 传送门 主使用首要担任登录、注册、菜单的事务,其他的事务逻辑,咱就不要放在主使用了,主使用越简略越好。

vue create main

创立的时分咱们选用 Vue 3.0、Babel、Vuex、CSS Pre-processor(Less) 和 Linter/Formatter(Standard)。

主使用咱们不需求路由装备的,路由需求由咱们自己去掌控。

eslint 的装备咱们选择在放在独自的文件里。

为了减少咱们在 ESLint 上纠结,咱们先暂时装备 eslint 如下(main/eslintrc.js):

module.exports = {
  root: true,
  env: {
    node: true,
  },
  extends: ['plugin:vue/vue3-essential', 'eslint:recommended'],
  parserOptions: {
    parser: 'babel-eslint',
  },
  rules: {},
  globals: {
    __webpack_public_path__: true, // 全局变量注入,不然 linter 会报错
  },
};

接下来,咱们持续创立好三个子使用,然后咱们在一起更改主、子使用的装备。

2、建立一个以Vue 3.0为结构的子使用sub-vue,跟主使用一样的办法。

mkdir sub
cd sub
vue create sub-vue

3、建立一个以 React 为结构的子使用sub-react ,Create React App.

npx create-react-app sub-react

4、建立一个以Angular为结构的子使用sub-angular,Create Angular App

ng new sub-angular

一路选择 Yes,并选择 Scss 预编译

通过上面四步之后,实践目录结构如下:

├── package.json
├── main
│   ├── public
│   └── src
│       ├── assets
│       ├── components
│       ├── qiankun
│       └── store
└── sub
    ├── sub-angular
    │   └── src
    │       ├── app
    │       ├── assets
    │       └── environments
    ├── sub-react
    │   ├── public
    │   └── src
    └── sub-vue
        ├── public
        └── src
            ├── assets
            ├── components
            ├── routes
            ├── store
            └── views

3.3 改造主、子使用

3.3.1 改造子使用sub-vue

  1. 重命名src/routersrc/routes,这个文件夹改成直接导出一切的路由装备,而不直接导出路由实例,咱们把路由放到 main.js 里边装备
import Home from '../views/Home.vue';
const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home,
  },
  {
    path: '/about',
    name: 'About',
    component: () =>
      import(/* webpackChunkName: "about" */ '../views/About.vue'),
  },
];
export default routes;
  1. 批改src/main.js:包装出 render 函数,便利依据运转环境,来运转项目
import './public-path';
import { createApp } from 'vue';
import { createRouter, createWebHistory } from 'vue-router';
import App from './App.vue';
import routes from './routes';
import store from './store';
let router = null;
let instance = null;
// todo: 在天地调用 render 函数的时分,会附上一些信息,比方说容器的挂载节点等
function render(props = {}) {
  const { container } = props;
  router = createRouter({
    // 假如是运转在天地的环境下,一切的路由路径会在开头加上/sub-vue
    history: createWebHistory(window.__POWERED_BY_QIANKUN__ ? '/sub-vue' : '/'),
    routes,
  });
  instance = createApp(App);
  instance.use(router);
  instance.use(store);
  instance.mount(container ? container.querySelector('#app') : '#app');
}
// 假如不是天地环境,直接运转render,然后让子使用能够独立运转
if (!window.__POWERED_BY_QIANKUN__) {
  render();
}
  1. 批改src/main.js:增加三个生命周期函数(bootstrap、mount、unmount),这三个函数会被主使用调用,来完成子使用的发动、挂载、卸载
export async function bootstrap() {
  // 这个函数能够学学怎样用的,加个颜色
  console.log('%c ', 'color: green;', 'vue3.0 app bootstraped');
}
export async function mount(props) {
  render(props);
}
export async function unmount() {
  instance.unmount();
  instance._container.innerHTML = '';
  instance = null;
  router = null;
}
  1. src目录下增加文件public-path.js
if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

假如呈现 eslint 报错’webpack_public_path‘ is not defined ,那么批改一下 eslint 的装备文件,重启服务。

module.exports = {
  root: true,
  env: {
    node: true,
  },
  extends: ['plugin:vue/vue3-essential', 'eslint:recommended', '@vue/prettier'],
  parserOptions: {
    parser: 'babel-eslint',
  },
  rules: {
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
  },
  globals: {
    __webpack_public_path__: true,
  },
};
  1. 批改 webpack 装备

在 src 下新增vue.config.js,首要意图是更改项意图打包方式,为了让主使用能正确识别微使用暴露出来的一些信息(生命周期钩子等),记住咱们的子使用端口是 8080 哦!

const path = require('path');
const { name } = require('./package');
function resolve(dir) {
  return path.join(__dirname, dir);
}
module.exports = {
  outputDir: 'dist',
  assetsDir: 'static',
  filenameHashing: true,
  devServer: {
    hot: true,
    disableHostCheck: true,
    port: '8080',
    overlay: {
      warnings: false,
      errors: true,
    },
    clientLogLevel: 'warning',
    compress: true,
    headers: {
      // 一定要加的,因为天地会用http的请求去获取子使用的代码,那么必然会呈现跨域的问题。
      'Access-Control-Allow-Origin': '*',
    },
    historyApiFallback: true,
  },
  configureWebpack: {
    resolve: {
      alias: {
        '@': resolve('src'),
      },
    },
    output: {
      // 把子使用打包成 umd 库格式,然后主使用能够获取到子使用导出的生命周期钩子函数
      library: `${name}-[name]`,
      libraryTarget: 'umd',
      jsonpFunction: `webpackJsonp_${name}`,
    },
  },
};

榜首个子使用sub-vue装备完毕,跑一跑?假如没啥问题,子使用应该现已运转起来,为了美观一些,咱们更改一下/src/views/Home.vue的内容:

<template>
  <div class="home">
    <img alt="Vue logo" src="https://www.6hu.cc/wp-content/uploads/2023/05/1683672859-b675e7d8eeef563.png" />
    <HelloWorld msg="Welcome to Your Sub Vue App" />
  </div>
</template>
<script>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue';
export default {
  name: 'Home',
  components: {
    HelloWorld,
  },
};
</script>

3.3.2 注册子使用sub-vue

  1. 在主使用中增加天地

咱们只需求在主使用中引进天地,子使用是不依赖天地的,大大减少了代码侵入性。

cd main
yarn add qiankun #  perhaps  npm i qiankun -S
  1. 在主使用中注册子使用

创立一个目录qiankun,用来独自放置天地相关的文件,为了便利拓展,咱们把一切子使用放在一个数组中,这样假如有新注册的子使用,只需求往数组中增加装备即可。

mkdir src/qiankun
touch src/qiankun/index.js

index.js 内容如下:

import {
  registerMicroApps,
  runAfterFirstMounted,
  setDefaultMountApp,
  start,
} from 'qiankun';
/**
 * Step1 注册子使用
 */
const microApps = [
  {
    name: 'sub-vue',
    developer: 'vue3.x',
    entry: '//localhost:8080',
    activeRule: '/sub-vue',
  },
];
const apps = microApps.map((item) => {
  return {
    ...item,
    container: '#subapp-container', //  子使用挂载的 div
    props: {
      developer: item.developer,
      routerBase: item.activeRule,
    },
  };
});
// 子使用挂载的几个生命周期钩子
registerMicroApps(apps, {
  beforeLoad: (app) => {
    console.log('before load app.name====>>>>>', app.name);
  },
  beforeMount: [
    (app) => {
      console.log('[LifeCycle] before mount %c%s', 'color: green;', app.name);
    },
  ],
  afterMount: [
    (app) => {
      console.log('[LifeCycle] after mount %c%s', 'color: green;', app.name);
    },
  ],
  afterUnmount: [
    (app) => {
      console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name);
    },
  ],
});
/**
 * Step2  设置挂载的子使用
 */
setDefaultMountApp('/sub-vue');
/**
 * Step3  发动使用
 */
start();
runAfterFirstMounted(() => {
  console.log('[MainApp] first app mounted');
});
export default apps;
  1. 批改App.vue,导入咱们的微使用数组,绘制菜单
<template>
  <div class="layout-wrapper">
    <header class="layout-header">
      <div class="logo">MSP</div>
    </header>
    <main class="layout-main">
      <aside class="layout-aside">
        <ul>
          <li v-for="item in microApps" :key="item.name" @click="goto(item)">
            {{ item.name }}
          </li>
        </ul>
      </aside>
      <section class="layout-section" id="subapp-container"></section>
    </main>
  </div>
</template>
<script>
import microApps from './qiankun';
export default {
  name: 'App',
  data() {
    return {
      isLoading: true,
      microApps,
    };
  },
  methods: {
    goto(item) {
      history.pushState(null, item.activeRule, item.activeRule);
    },
  },
};
</script>
<style lang="less">
* {
  margin: 0;
  padding: 0;
}
html,
body,
.layout-wrapper {
  height: 100%;
  overflow: hidden;
}
.layout-wrapper {
  .layout-header {
    height: 50px;
    width: 100%;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    line-height: 50px;
    position: relative;
    .logo {
      float: left;
      margin: 0 50px;
    }
    .userinfo {
      position: absolute;
      right: 100px;
      top: 0;
    }
  }
  .layout-main {
    height: calc(100% - 50px);
    overflow: hidden;
    display: flex;
    justify-content: space-evenly;
    .layout-aside {
      width: 190px;
      ul {
        margin: 50px 0 0 20px;
        border-right: 2px solid #aaa;
        li {
          list-style: none;
          display: inline-block;
          padding: 0 20px;
          color: #aaa;
          margin: 20px 0;
          font-size: 18px;
          font-weight: 400;
          cursor: pointer;
          &.active {
            color: #42b983;
            text-decoration: underline;
          }
          &:hover {
            color: #444;
          }
        }
      }
    }
    .layout-section {
      width: 100%;
      height: 100%;
    }
  }
}
</style>
  1. 更改主使用运转端口

因为咱们现在一切的子使用都是在咱们自己的电脑是运转,所以,每个子使用的端口不能重复。榜首个子使用的端口是 8080,主使用咱们现在改成 8443。在主使用的根目录下创立vue.config.js

module.exports = {
  devServer: {
    port: '8443',
    clientLogLevel: 'warning',
    disableHostCheck: true,
    compress: true,
    historyApiFallback: true,
  },
};

到这儿,咱们现已装备好主使用 main, 并注册了子使用 sub-vue。跑起来,不出啥意外,你应该能够看到这个样子的页面。

3.3.3 改造子使用sub-angular

  1. 首先在src下增加public-path.js
if (window.__POWERED_BY_QIANKUN__) {
  // eslint-disable-next-line no-undef
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
  1. 设置history形式路由的 base,假如是运转在天地环境下,就以/sub-angular/开头,批改src/app/app-routing.module.ts文件
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { APP_BASE_HREF } from '@angular/common';
const routes: Routes = [];
@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
  providers: [
    {
      provide: APP_BASE_HREF,
      // @ts-ignore
      useValue: window.__POWERED_BY_QIANKUN__ ? '/sub-angular' : '/',
    },
  ],
})
export class AppRoutingModule {}
  1. 批改进口文件,包装出render函数,使其在天地环境下,由生命钩子履行render,在本地环境,直接render,批改 src/main.ts 文件
import './public-path';
import { enableProdMode, NgModuleRef } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
  enableProdMode();
}
let app: void | NgModuleRef<AppModule>;
async function render() {
  app = await platformBrowserDynamic()
    .bootstrapModule(AppModule)
    .catch((err) => console.error(err));
}
if (!(window as any).__POWERED_BY_QIANKUN__) {
  render();
}
export async function bootstrap(props: Object) {
  console.log(props);
}
export async function mount(props: Object) {
  render();
}
export async function unmount(props: Object) {
  console.log(props);
  // @ts-ignore
  app.destroy();
}
  1. 批改 webpack 打包装备
  • 先装置 @angular-builders/custom-webpack 插件
npm i @angular-builders/custom-webpack -D
  • 在根目录增加 custom-webpack.config.js ,内容为:
const appName = require('./package.json').name;
module.exports = {
  devServer: {
    headers: {
      'Access-Control-Allow-Origin': '*',
    },
  },
  output: {
    library: `${appName}-[name]`,
    libraryTarget: 'umd',
    chunkLoadingGlobal: `webpackJsonp_${appName}`,
  },
};
  • 批改 angular.json,将 [packageName] > architect > build > builder[packageName] > architect > serve > builder 的值改为咱们装置的插件,将咱们的打包装备文件加入到 [packageName] > architect > build > options
{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "sub-angular": {
      "projectType": "application",
      "schematics": {
        "@schematics/angular:component": {
          "style": "scss"
        },
        "@schematics/angular:application": {
          "strict": true
        }
      },
      "root": "",
      "sourceRoot": "src",
      "prefix": "app",
      "architect": {
        "build": {
          "builder": "@angular-builders/custom-webpack:browser",
          "options": {
            "customWebpackConfig": {
              "path": "./custom-webpack.config.js"
            },
            "outputPath": "dist/sub-angular",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "tsconfig.app.json",
            "inlineStyleLanguage": "scss",
            "assets": ["src/favicon.ico", "src/assets"],
            "styles": ["src/styles.scss"],
            "scripts": []
          },
          "configurations": {
            "production": {
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "500kb",
                  "maximumError": "1mb"
                },
                {
                  "type": "anyComponentStyle",
                  "maximumWarning": "2kb",
                  "maximumError": "4kb"
                }
              ],
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],
              "outputHashing": "all"
            },
            "development": {
              "buildOptimizer": false,
              "optimization": false,
              "vendorChunk": true,
              "extractLicenses": false,
              "sourceMap": true,
              "namedChunks": true
            }
          },
          "defaultConfiguration": "production"
        },
        "serve": {
          "builder": "@angular-builders/custom-webpack:dev-server",
          "configurations": {
            "production": {
              "browserTarget": "sub-angular:build:production"
            },
            "development": {
              "browserTarget": "sub-angular:build:development"
            }
          },
          "defaultConfiguration": "development"
        },
        "extract-i18n": {
          "builder": "@angular-devkit/build-angular:extract-i18n",
          "options": {
            "browserTarget": "sub-angular:build"
          }
        },
        "test": {
          "builder": "@angular-devkit/build-angular:karma",
          "options": {
            "main": "src/test.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "tsconfig.spec.json",
            "karmaConfig": "karma.conf.js",
            "inlineStyleLanguage": "scss",
            "assets": ["src/favicon.ico", "src/assets"],
            "styles": ["src/styles.scss"],
            "scripts": []
          }
        }
      }
    }
  },
  "defaultProject": "sub-angular"
}
  1. 处理 zone.js 的问题

在父使用引进 zone.js,需求在 import qiankun 之前引进。

将微使用的 src/polyfills.ts 里边的引进 zone.js 代码删掉。

-
import 'zone.js/dist/zone';

在微使用的 src/index.html 里边的 标签加上下面内容,微使用独立访问时运用。

<script src="https://unpkg.com/zone.js" ignore></script>
  1. 批改 ng build 打包报错问题,批改 tsconfig.json 文件,参考 issues/431
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
  "compileOnSave": false,
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "sourceMap": true,
    "declaration": false,
    "downlevelIteration": true,
    "experimentalDecorators": true,
    "moduleResolution": "node",
    "importHelpers": true,
    "target": "es5",
    "typeRoots": ["node_modules/@types"],
    "module": "es2020",
    "lib": ["es2018", "dom"]
  },
  "angularCompilerOptions": {
    "enableI18nLegacyMessageIdFormat": false,
    "strictInjectionParameters": true,
    "strictInputAccessModifiers": true,
    "strictTemplates": true
  }
}
  1. 为了防止主使用或其他微使用也运用 angular 时,<app-root></app-root> 会抵触的问题,主张给<app-root> 加上一个仅有的 id,比方说当时使用称号

批改src/index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>SubAngular</title>
    <base href="/" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="icon" type="image/x-icon" href="favicon.ico" />
    <script src="https://unpkg.com/zone.js" ignore></script>
  </head>
  <body>
    <app-root id="sub-angular"></app-root>
  </body>
</html>

批改`src/app/app.component.ts :

import { Component } from '@angular/core';
@Component({
  selector: '#sub-angular app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
})
export class AppComponent {
  title = 'sub-angular';
}
  1. 老规矩,改改页面款式,美观一点

批改 app.component.html:

<h1>This is an Angular APP!</h1>
<router-outlet></router-outlet>

到这儿咱们现已接入了一个 Angular 结构的前端微使用。

3.3.3 改造子使用sub-react

create react app 生成的 react 17 项目为例。

  1. 在 src 目录新增 public-path.js:
if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
  1. 进口文件 index.js 批改,为了防止根 id #root 与其他的 DOM 抵触,需求约束查找规模。
import './public-path';
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
function render(props) {
  const { container } = props;
  ReactDOM.render(
    <App />,
    container
      ? container.querySelector('#root')
      : document.querySelector('#root')
  );
}
if (!window.__POWERED_BY_QIANKUN__) {
  render({});
}
/**
 * bootstrap 只会在微使用初始化的时分调用一次,下次微使用从头进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
 * 通常咱们能够在这儿做一些全局变量的初始化,比方不会在 unmount 阶段被毁掉的使用等级的缓存等。
 */
export async function bootstrap() {
  console.log('[react16] react app bootstraped');
}
/**
 * 使用每次进入都会调用 mount 办法,通常咱们在这儿触发使用的烘托办法
 */
export async function mount(props) {
  console.log('[react16] props from main framework', props);
  render(props);
}
/**
 * 使用每次 切出/卸载 会调用的办法,通常在这儿咱们会卸载微使用的使用实例
 */
export async function unmount(props) {
  const { container } = props;
  ReactDOM.unmountComponentAtNode(
    container
      ? container.querySelector('#root')
      : document.querySelector('#root')
  );
}
/**
 * 可选生命周期钩子,仅运用 loadMicroApp 方式加载微使用时生效
 */
export async function update(props) {
  console.log('update props', props);
}
  1. 批改 webpack 装备

咱们运用react-app-rewired来批改react-scripts的 webpack 装备。

npm i react-app-rewired -D

在根目录下增加config-overrides.js:

const { name } = require('./package.json');
const ReactRefreshPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
const webpack = require('webpack');
module.exports = {
  webpack: function override(config, env) {
    config.output.library = `${name}-[name]`;
    config.output.libraryTarget = 'umd';
    config.output.jsonpFunction = `webpackJsonp_${name}`;
    // Remove 'react-refresh' from the loaders.
    for (const rule of config.module.rules) {
      if (!rule.oneOf) continue;
      for (const one of rule.oneOf) {
        if (
          one.loader &&
          one.loader.includes('babel-loader') &&
          one.options &&
          one.options.plugins
        ) {
          one.options.plugins = one.options.plugins.filter(
            (plugin) =>
              typeof plugin !== 'string' || !plugin.includes('react-refresh')
          );
        }
      }
    }
    config.plugins = config.plugins.filter(
      (plugin) =>
        !(plugin instanceof webpack.HotModuleReplacementPlugin) &&
        !(plugin instanceof ReactRefreshPlugin)
    );
    return config;
  },
  devServer: (configFunction) => {
    return function (proxy, allowedHost) {
      const config = configFunction(proxy, allowedHost);
      config.open = false;
      config.hot = false;
      config.headers = {
        'Access-Control-Allow-Origin': '*',
      };
      return config;
    };
  },
};

批改package.jsonscripts 部分:

"scripts": {
  "start": "react-app-rewired start",
  "build": "react-app-rewired build",
  "test": "react-app-rewired test",
  "eject": "react-app-rewired eject"
}
  1. 批改端口,增加文件 .env:
SKIP_PREFLIGHT_CHECK=true
BROWSER=none
PORT=3000
  1. 老规矩,改改咱们页面,让页面美观点

自己删掉哪些没用的 CSS 叭,回头再仔细加

批改 src/App.js:

import './App.css';
function App() {
  return <div className='App'>This is a React APP!</div>;
}
export default App;
  1. 最终一步了,子使用注册到主使用

批改 main/src/qiankun/index.js 的 变量 microApps

const microApps = [
  {
    name: 'sub-vue',
    developer: 'vue3.x',
    entry: '//localhost:8080',
    activeRule: '/sub-vue',
  },
  {
    name: 'sub-angular',
    developer: 'angular13',
    entry: '//localhost:4200',
    activeRule: '/sub-angular',
  },
  {
    name: 'sub-react',
    developer: 'react16',
    entry: '//localhost:3000',
    activeRule: '/sub-react',
  },
];

到这儿咱们现已把三个微使用接入主使用,接下来,咱们分别把四个使用运转起来。这个大家应该都会滴。

运转主使用:

cd main
npm run serve

运转子使用 sub-vue

cd sub/sub-vue
npm run serve

运转子使用 sub-react

cd sub/sub-react
npm run start

运转子使用 sub-angular

cd sub/sub-angular
npm run start

总算看到页面咯?

4. 本地开发装备优化

每次跑这么多命令,运转各个微使用,仍是蛮累的。下面咱们介绍一个利器npm-run-all,来批量运转咱们的微使用。

npm i npm-run-all -D

批改 package.json 下的 scripts:

  "scripts": {
    "start": "npm-run-all --parallel start:*",
    "start:sub-react": "cd sub/sub-react && npm run start",
    "start:sub-vue": "cd sub/sub-vue && npm run serve",
    "start:sub-angular": "cd sub/sub-angular && npm run start",
    "start:main": "cd main && npm run serve"
  },

履行 npm run start,引发一切 APP。到这儿,咱们的 MSP 就现已建立完成咯!

5. 小结

子使用改造:

  1. 增加 public-path.js,为什么需求加呢?2. 防止根 id 与其他的 DOM 抵触,需求约束查找规模。
  2. 完成四个钩子函数,并导出。
  3. 批改 webpack 装备,增加'Access-Control-Allow-Origin': '*',答应跨域请求文件;批改打包方式,然后让天地拿到导出的钩子函数,获取掌控权。