手把手搭建乾坤微服务

·  阅读 1399
手把手搭建乾坤微服务

手把手教你搭建前端微服务【乾坤】

本文搭建一个基于 Vue3.0 的微服务框架,并以 Angular、Vue3.0、React 作为微服务,接入主应用。文章比较详细,步骤较多,走完,一个基础的微服务框架就真的搭建好了。我们先将学习最小化,亲力亲为搭建好之后,再去考虑怎么优化,有坑的话,记得留言哦~

1. 页面结构

下图是我们搭建的系统的页面结构,在主应用中,包含 header 和左侧的 menu 部分,container 部分用来放置我们的微服务,本文章会使用 Vue 3.0、Angular、React 三个前端流行框架作为示例。

page-layout.png

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-reactCreate React App.

npx create-react-app sub-react

4、搭建一个以Angular为框架的子应用sub-angularCreate 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="../assets/logo.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': '*',允许跨域请求文件;修改打包方式,从而让乾坤拿到导出的钩子函数,获取掌控权。
分类:
前端
标签:
分类:
开发工具
标签:
收藏成功!
已添加到「」, 点击更改