Skip to content

Commit

Permalink
✨ support custom fetch (#108)
Browse files Browse the repository at this point in the history
* ✨ support custom fetch
  • Loading branch information
howel52 authored and kuitos committed Oct 21, 2019
1 parent dab1b08 commit 76b2b21
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 82 deletions.
118 changes: 58 additions & 60 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
# qiankun(乾坤)

[![npm version](https://img.shields.io/npm/v/qiankun.svg?style=flat-square)](https://www.npmjs.com/package/qiankun)
[![coverage](https://img.shields.io/codecov/c/github/umijs/qiankun.svg?style=flat-square)](https://codecov.io/gh/umijs/qiankun)
[![npm downloads](https://img.shields.io/npm/dt/qiankun.svg?style=flat-square)](https://www.npmjs.com/package/qiankun)
[![Build Status](https://img.shields.io/travis/umijs/qiankun.svg?style=flat-square)](https://travis-ci.org/umijs/qiankun)
[![npm version](https://img.shields.io/npm/v/qiankun.svg?style=flat-square)](https://www.npmjs.com/package/qiankun) [![coverage](https://img.shields.io/codecov/c/github/umijs/qiankun.svg?style=flat-square)](https://codecov.io/gh/umijs/qiankun) [![npm downloads](https://img.shields.io/npm/dt/qiankun.svg?style=flat-square)](https://www.npmjs.com/package/qiankun) [![Build Status](https://img.shields.io/travis/umijs/qiankun.svg?style=flat-square)](https://travis-ci.org/umijs/qiankun)

> In Chinese traditional culture `qian` means heaven and `kun` stands for earth, so `qiankun` is the universe.
Expand All @@ -13,13 +10,13 @@ An implementation of [Micro Frontends](https://micro-frontends.org/), based on [

As we know what micro-frontends aims for:

> Techniques, strategies and recipes for building a **modern web app** with **multiple teams** using **different JavaScript frameworks**. [Micro Frontends](https://micro-frontends.org/)
> Techniques, strategies and recipes for building a **modern web app** with **multiple teams** using **different JavaScript frameworks**. [Micro Frontends](https://micro-frontends.org/)
An independent development experience is very important for a large system, especially with an enterprise application. But if you've tried to implement a micro-frontends architecture in such a system, you'll usually hurt your brain with such problems:

* How to compose your independent sub apps into your main system?
* How to guarantee your sub apps to be isolated by each other?
* and so on...
- How to compose your independent sub apps into your main system?
- How to guarantee your sub apps to be isolated by each other?
- and so on...

We built an library to help you solve these glitch problems automatically without any mental burden of yours, then named it `qiankun`.

Expand All @@ -40,28 +37,27 @@ import { registerMicroApps, start } from 'qiankun';

function render({ appContent, loading }) {
const container = document.getElementById('container');
ReactDOM.render(<Framework loading={loading} content={appContent}/>, container);
ReactDOM.render(<Framework loading={loading} content={appContent} />, container);
}

function genActiveRule(routerPrefix) {
return (location) => location.pathname.startsWith(routerPrefix);
return location => location.pathname.startsWith(routerPrefix);
}

registerMicroApps(
[
{
name: 'react app', // app name registered
entry: '//localhost:7100',
render,
activeRule: genActiveRule('/react') },
{
name: 'vue app',
entry: { scripts: [ '//localhost:7100/main.js' ] },
render,
activeRule: genActiveRule('/vue')
},
],
);
registerMicroApps([
{
name: 'react app', // app name registered
entry: '//localhost:7100',
render,
activeRule: genActiveRule('/react'),
},
{
name: 'vue app',
entry: { scripts: ['//localhost:7100/main.js'] },
render,
activeRule: genActiveRule('/vue'),
},
]);

start();
```
Expand All @@ -75,13 +71,14 @@ export async function bootstrap() {

export async function mount(props) {
console.log(props);
ReactDOM.render(<App/>, document.getElementById('react15Root'));
ReactDOM.render(<App />, document.getElementById('react15Root'));
}

export async function unmount() {
ReactDOM.unmountComponentAtNode(document.getElementById('react15Root'));
}
```

For more lifecycle information, see [single-spa lifecycles](https://single-spa.js.org/docs/building-applications.html#registered-application-lifecycle)

### 3. Config your sub app bundler
Expand All @@ -90,23 +87,23 @@ While you wanna build a sub app to integrate to qiankun, pls make sure your bund

#### webpack:

```js
output: {
library: packageName,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${packageName}`,
}
```
```js
output: {
library: packageName,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${packageName}`,
}
```

see https://webpack.js.org/configuration/output/#outputlibrary

#### parcel:

```shell
parcel serve entry.js --global myvariable
```
```shell
parcel serve entry.js --global myvariable
```

see https://en.parceljs.org/cli.html#expose-modules-as-umd
see https://en.parceljs.org/cli.html#expose-modules-as-umd

## 💿 Examples

Expand All @@ -120,30 +117,28 @@ Visit `http://localhost:7099`

![](./examples/example.gif)



## :sparkles: Features

* Based on [single-spa](https://github.com/CanopyTax/single-spa)
* HTML Entry
* Config Entry
* **Isolated styles**
* **JS Sandbox**
* Assets Prefetch
* [@umijs/plugin-qiankun](https://github.com/umijs/umi-plugin-qiankun) integration
- Based on [single-spa](https://github.com/CanopyTax/single-spa)
- HTML Entry
- Config Entry
- **Isolated styles**
- **JS Sandbox**
- Assets Prefetch
- [@umijs/plugin-qiankun](https://github.com/umijs/umi-plugin-qiankun) integration

## 📖 API

### registerMicroApps

```typescript
type RegistrableApp = {
// name to identify your app
// name to identify your app
name: string;
// where your sub app served from, supported html entry and config entry
entry: string | { scripts?: string[]; styles?: string[]; html?: string };
// render function called around sub app lifecycle
render: (props?: { appContent: string, loading: boolean }) => any;
render: (props?: { appContent: string; loading: boolean }) => any;
// when sub app active
activeRule: (location: Location) => boolean;
// props pass through to sub app
Expand All @@ -152,52 +147,55 @@ type RegistrableApp = {

type Lifecycle<T extends object> = (app: RegistrableApp<T>) => Promise<any>;
type LifeCycles<T extends object> = {
beforeLoad?: Lifecycle<T> | Array<Lifecycle<T>>;
beforeMount?: Lifecycle<T> | Array<Lifecycle<T>>;
afterMount?: Lifecycle<T> | Array<Lifecycle<T>>;
beforeUnmount?: Lifecycle<T> | Array<Lifecycle<T>>;
afterUnmount?: Lifecycle<T> | Array<Lifecycle<T>>;
beforeLoad?: Lifecycle<T> | Array<Lifecycle<T>>;
beforeMount?: Lifecycle<T> | Array<Lifecycle<T>>;
afterMount?: Lifecycle<T> | Array<Lifecycle<T>>;
beforeUnmount?: Lifecycle<T> | Array<Lifecycle<T>>;
afterUnmount?: Lifecycle<T> | Array<Lifecycle<T>>;
};

function registerMicroApps<T extends object = {}>(apps: Array<RegistrableApp<T>>, lifeCycles?: LifeCycles<T>): void;
function registerMicroApps<T extends object = {}>(
apps: Array<RegistrableApp<T>>,
lifeCycles?: LifeCycles<T>,
opts?: RegisterMicroAppsOpts,
): void;
```

### start

```typescript
function start({ prefetch: boolean, jsSandbox: boolean, singular: boolean }): void;
function start({ prefetch: boolean, jsSandbox: boolean, singular: boolean, fetch?: typeof fetch }): void;
```

### setDefaultMountApp

Set which sub app shoule be active by default after master loaded.

```typescript
function setDefaultMountApp(defaultAppLink: string): void
function setDefaultMountApp(defaultAppLink: string): void;
```

### runAfterFirstMounted

```typescript
function runAfterFirstMounted(effect: () => void): void
function runAfterFirstMounted(effect: () => void): void;
```

## 🎯 Roadmap

- [ ] Parcel apps integration (multiple sub apps displayed at the same time, but only one uses router at most)
- [ ] Communication development kits between master and sub apps
- [ ] Communication development kits between master and sub apps
- [ ] Custom side effects hijacker
- [ ] Nested Microfrontends

## ❓ FAQ

https://github.com/umijs/qiankun/wiki/FAQ


## 👬 Community

https://github.com/umijs/umi#community

## 🎁 Acknowledgements

* [single-spa](https://github.com/CanopyTax/single-spa) What an awesome meta-framework for micro-fronteds!
- [single-spa](https://github.com/CanopyTax/single-spa) What an awesome meta-framework for micro-fronteds!
39 changes: 27 additions & 12 deletions examples/main/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import React from 'react';
import ReactDOM from 'react-dom';
import fetch from 'isomorphic-fetch';
// import Vue from 'vue';
import { registerMicroApps, runAfterFirstMounted, setDefaultMountApp, start } from '../../dist/index.esm';
import Framework from './Framework';
Expand All @@ -13,7 +14,6 @@ import Framework from './Framework';
// let app = null;

function render({ appContent, loading }) {

/*
examples for vue
*/
Expand Down Expand Up @@ -41,7 +41,7 @@ function render({ appContent, loading }) {
// }

const container = document.getElementById('container');
ReactDOM.render(<Framework loading={loading} content={appContent}/>, container);
ReactDOM.render(<Framework loading={loading} content={appContent} />, container);
}

function genActiveRule(routerPrefix) {
Expand All @@ -50,26 +50,41 @@ function genActiveRule(routerPrefix) {

render({ loading: true });

// support custom fetch see: https://github.com/kuitos/import-html-entry/blob/91d542e936a74408c6c8cd1c9eebc5a9f83a8dc0/src/index.js#L163
const request = url =>
fetch(url, {
referrerPolicy: 'origin-when-cross-origin',
});

registerMicroApps(
[
{ name: 'react app', entry: '//localhost:7100', render, activeRule: genActiveRule('/react') },
{ name: 'react15 app', entry: '//localhost:7102', render, activeRule: genActiveRule('/15react15') },
{ name: 'vue app', entry: '//localhost:7101', render, activeRule: genActiveRule('/vue') },
],
{
beforeLoad: [app => {
console.log('before load', app);
}],
beforeMount: [app => {
console.log('before mount', app);
}],
afterUnmount: [app => {
console.log('after unload', app);
}],
beforeLoad: [
app => {
console.log('before load', app);
},
],
beforeMount: [
app => {
console.log('before mount', app);
},
],
afterUnmount: [
app => {
console.log('after unload', app);
},
],
},
{
fetch: request,
},
);

setDefaultMountApp('/react');
runAfterFirstMounted(() => console.info('first app mounted'));

start();
start({ prefetch: true, fetch: request });
1 change: 1 addition & 0 deletions examples/main/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"vue-template-compiler": "^2.6.10"
},
"dependencies": {
"isomorphic-fetch": "^2.2.1",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"vue": "^2.6.10",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"homepage": "https://github.com/kuitos/qiankun#readme",
"dependencies": {
"@babel/runtime": "^7.5.5",
"import-html-entry": "^1.0.0",
"import-html-entry": "^1.2.6",
"lodash": "^4.17.11",
"single-spa": "^4.3.4"
},
Expand Down
14 changes: 10 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import { importEntry } from 'import-html-entry';
import { isFunction } from 'lodash';
import { registerApplication, start as startSpa } from 'single-spa';
import { RegistrableApp, StartOpts } from './interfaces';
import { RegistrableApp, StartOpts, Fetch } from './interfaces';
import { prefetchAfterFirstMounted } from './prefetch';
import { genSandbox } from './sandbox';

Expand All @@ -26,6 +26,10 @@ export type LifeCycles<T extends object> = {
afterUnmount?: Lifecycle<T> | Array<Lifecycle<T>>; // function after app unmount
};

type RegisterMicroAppsOpts = {
fetch?: Fetch;
};

let microApps: RegistrableApp[] = [];

function toArray<T>(array: T | T[]): T[] {
Expand Down Expand Up @@ -72,8 +76,10 @@ let useJsSandbox = false;
export function registerMicroApps<T extends object = {}>(
apps: Array<RegistrableApp<T>>,
lifeCycles: LifeCycles<T> = {},
opts?: RegisterMicroAppsOpts,
) {
const { beforeUnmount = [], afterUnmount = [], afterMount = [], beforeMount = [], beforeLoad = [] } = lifeCycles;
const { fetch } = opts || {};
microApps = [...microApps, ...apps];

let prevAppUnmountedDeferred: Deferred<void>;
Expand All @@ -86,7 +92,7 @@ export function registerMicroApps<T extends object = {}>(

async ({ name: appName }) => {
// 获取入口 html 模板及脚本加载器
const { template: appContent, execScripts } = await importEntry(entry);
const { template: appContent, execScripts } = await importEntry(entry, { fetch });
// as single-spa load and bootstrap new app parallel with other apps unmounting
// (see https://github.com/CanopyTax/single-spa/blob/master/src/navigation/reroute.js#L74)
// we need wait to load the app until all apps are finishing unmount in singular mode
Expand Down Expand Up @@ -167,10 +173,10 @@ export function start(opts: StartOpts = {}) {
// eslint-disable-next-line no-underscore-dangle
window.__POWERED_BY_QIANKUN__ = true;

const { prefetch = true, jsSandbox = true, singular = true } = opts;
const { prefetch = true, jsSandbox = true, singular = true, fetch } = opts;

if (prefetch) {
prefetchAfterFirstMounted(microApps);
prefetchAfterFirstMounted(microApps, fetch);
}

if (jsSandbox) {
Expand Down
2 changes: 2 additions & 0 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ export type StartOpts = {
prefetch?: boolean;
jsSandbox?: boolean;
singular?: boolean | ((app: RegistrableApp<any>) => Promise<boolean>);
fetch?: Fetch;
};

export type Rebuilder = () => void;
export type Freer = () => Rebuilder;
export type Fetch = typeof fetch;
Loading

0 comments on commit 76b2b21

Please sign in to comment.