⏪ revert UIEvent patch (#885)
This commit is contained in:
commit
a889c992b9
16
.editorconfig
Normal file
16
.editorconfig
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
1
.eslintignore
Normal file
1
.eslintignore
Normal file
|
|
@ -0,0 +1 @@
|
|||
/examples
|
||||
17
.eslintrc.js
Executable file
17
.eslintrc.js
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
module.exports = {
|
||||
extends: [require.resolve('@umijs/fabric/dist/eslint')],
|
||||
rules: {
|
||||
'@typescript-eslint/prefer-interface': 0,
|
||||
'@typescript-eslint/no-explicit-any': 0,
|
||||
'@typescript-eslint/array-type': ['error', { default: 'array-simple' }],
|
||||
'no-return-assign': 0,
|
||||
semi: ['error', 'always'],
|
||||
'no-confusing-arrow': 0,
|
||||
'no-console': 0,
|
||||
'max-len': ['error', { code: 120, ignoreComments: true, ignoreStrings: true }],
|
||||
// see https://github.com/prettier/prettier/issues/3847
|
||||
'space-before-function-paren': ['error', { anonymous: 'never', named: 'never', asyncArrow: 'always' }],
|
||||
'no-underscore-dangle': 0,
|
||||
'no-plusplus': 0,
|
||||
},
|
||||
};
|
||||
16
.fatherrc.js
Normal file
16
.fatherrc.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
export default {
|
||||
target: 'browser',
|
||||
esm: 'babel',
|
||||
cjs: 'babel',
|
||||
runtimeHelpers: true,
|
||||
extraBabelPlugins: [
|
||||
[
|
||||
'babel-plugin-import',
|
||||
{
|
||||
libraryName: 'lodash',
|
||||
libraryDirectory: '',
|
||||
camel2DashComponentName: false,
|
||||
},
|
||||
],
|
||||
],
|
||||
};
|
||||
29
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
29
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
name: 'Bug report'
|
||||
about: 'Report a bug to help us improve'
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
## What happens?
|
||||
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
## Mini Showcase Repository(REQUIRED)
|
||||
|
||||
> Provide a mini GitHub repository which can reproduce the issue.
|
||||
|
||||
<!-- https://github.com/YOUR_REPOSITORY_URL -->
|
||||
|
||||
## How To Reproduce
|
||||
|
||||
**Steps to reproduce the behavior:** 1. 2.
|
||||
|
||||
**Expected behavior** 1. 2.
|
||||
|
||||
## Context
|
||||
|
||||
- **qiankun Version**:
|
||||
- **Platform Version**:
|
||||
- **Browser Version**:
|
||||
33
.github/ISSUE_TEMPLATE/bug_report_cn.md
vendored
Normal file
33
.github/ISSUE_TEMPLATE/bug_report_cn.md
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
name: '缺陷问题反馈'
|
||||
about: '反馈问题以帮助我们改进'
|
||||
title: '[Bug]请遵循下文模板提交问题,否则您的问题会被关闭'
|
||||
labels: ''
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
# 提问之前强烈建立您能先阅读一下[《提问的智慧》](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/master/README-zh_CN.md)
|
||||
|
||||
<!--
|
||||
感谢您向我们反馈问题,为了高效的解决问题,我们期望你能提供以下信息:
|
||||
-->
|
||||
|
||||
## What happens?
|
||||
|
||||
<!-- 清晰的描述下遇到的问题。-->
|
||||
|
||||
## 最小可复现仓库
|
||||
|
||||
为节约大家的时间,无复现步骤的 ISSUE 会被关闭,提供之后再 REOPEN
|
||||
<!-- https://github.com/YOUR_REPOSITORY_URL -->
|
||||
|
||||
## 复现步骤,错误日志以及相关配置
|
||||
|
||||
<!-- 请提供复现步骤,错误日志以及相关配置 -->
|
||||
<!-- 可以尝试不要锁版本,重新安装依赖试试先 -->
|
||||
|
||||
## 相关环境信息
|
||||
|
||||
- **qiankun 版本**
|
||||
- **浏览器版本**:
|
||||
- **操作系统**:
|
||||
19
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
19
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
---
|
||||
name: 'Feature request'
|
||||
about: 'Suggest an idea for this project'
|
||||
title: '[Feature Request] say something'
|
||||
labels: ''
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
## Background
|
||||
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
## Proposal
|
||||
|
||||
Describe the solution you'd like, better to provide some pseudo code.
|
||||
|
||||
## Additional context
|
||||
|
||||
Add any other context or screenshots about the feature request here.
|
||||
20
.github/ISSUE_TEMPLATE/rfc_cn.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/rfc_cn.md
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
name: 'RFC Proposals'
|
||||
about: 'Provide a solution for this project'
|
||||
title: '[RFC] say something'
|
||||
labels: 'type: proposals'
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
## 背景
|
||||
|
||||
> 描述你希望解决的问题的现状,附上相关的 issue 地址
|
||||
|
||||
## 思路
|
||||
|
||||
> 描述大概的解决思路,可以包含 API 设计和伪代码等
|
||||
|
||||
## 跟进
|
||||
|
||||
- [ ] some task
|
||||
- [ ] PR URL
|
||||
25
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
25
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<!--
|
||||
Thank you for your pull request. Please review below requirements.
|
||||
Bug fixes and new features should include tests.
|
||||
Contributors guide: https://github.com/umijs/qiankun/blob/master/CONTRIBUTING.md
|
||||
|
||||
感谢您贡献代码。请确认下列 checklist 的完成情况。
|
||||
Bug 修复和新功能必须包含测试。
|
||||
Contributors guide: https://github.com/umijs/qiankun/blob/master/CONTRIBUTING.md
|
||||
-->
|
||||
|
||||
##### Checklist
|
||||
|
||||
<!-- Remove items that do not apply. For completed items, change [ ] to [x]. -->
|
||||
|
||||
- [ ] `npm test` passes
|
||||
- [ ] tests are included
|
||||
- [ ] documentation is changed or added
|
||||
- [ ] commit message follows commit guidelines
|
||||
|
||||
##### Description of change
|
||||
|
||||
<!-- Provide a description of the change below this comment. -->
|
||||
|
||||
- any feature?
|
||||
- close https://github.com/umijs/qiankun/ISSUE_URL
|
||||
26
.github/workflows/now.yml
vendored
Normal file
26
.github/workflows/now.yml
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
name: qiankun - deploy
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: build
|
||||
env:
|
||||
NOW_DEPLOY: true
|
||||
run: |
|
||||
yarn install
|
||||
yarn docs:build
|
||||
- uses: amondnet/vercel-action@v19
|
||||
id: vercel-deployment-production
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
vercel-token: ${{ secrets.ZEIT_TOKEN }}
|
||||
vercel-org-id: ${{ secrets.NOW_ORG_ID }}
|
||||
vercel-project-id: ${{ secrets.NOW_PROJECT_ID }}
|
||||
working-directory: dist
|
||||
vercel-args: '--prod'
|
||||
scope: umijs
|
||||
51
.github/workflows/release-notify.yml
vendored
Normal file
51
.github/workflows/release-notify.yml
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
name: Release notify
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Send DingGroup1 Notify
|
||||
uses: zcong1993/actions-ding@master
|
||||
with:
|
||||
dingToken: ${{ secrets.DING_GROUP_1_TOKEN }}
|
||||
secret: ${{ secrets.DING_GROUP_1_SIGN }}
|
||||
body: |
|
||||
{
|
||||
"msgtype": "markdown",
|
||||
"markdown": {
|
||||
"title": "qiankun ${{github.event.release.tag_name}} 发布公告",
|
||||
"text": "# qiankun [${{github.event.release.tag_name}}](${{github.event.release.html_url}}) 发布公告\n${{github.event.release.body}}",
|
||||
}
|
||||
}
|
||||
|
||||
- name: Send DingGroup2 Notify
|
||||
uses: zcong1993/actions-ding@master
|
||||
with:
|
||||
dingToken: ${{ secrets.DING_GROUP_2_TOKEN }}
|
||||
secret: ${{ secrets.DING_GROUP_2_SIGN }}
|
||||
body: |
|
||||
{
|
||||
"msgtype": "markdown",
|
||||
"markdown": {
|
||||
"title": "qiankun ${{github.event.release.tag_name}} 发布公告",
|
||||
"text": "# qiankun [${{github.event.release.tag_name}}](${{github.event.release.html_url}}) 发布公告\n${{github.event.release.body}}",
|
||||
}
|
||||
}
|
||||
|
||||
- name: Send DingGroupInc Notify
|
||||
uses: zcong1993/actions-ding@master
|
||||
with:
|
||||
dingToken: ${{ secrets.DING_GROUP_INC_TOKEN }}
|
||||
secret: ${{ secrets.DING_GROUP_INC_SIGN }}
|
||||
body: |
|
||||
{
|
||||
"msgtype": "markdown",
|
||||
"markdown": {
|
||||
"title": "qiankun ${{github.event.release.tag_name}} 发布公告",
|
||||
"text": "# qiankun [${{github.event.release.tag_name}}](${{github.event.release.html_url}}) 发布公告\n${{github.event.release.body}}",
|
||||
}
|
||||
}
|
||||
21
.gitignore
vendored
Executable file
21
.gitignore
vendored
Executable file
|
|
@ -0,0 +1,21 @@
|
|||
pids
|
||||
logs
|
||||
node_modules
|
||||
npm-debug.log
|
||||
coverage/
|
||||
run
|
||||
dist
|
||||
.DS_Store
|
||||
.nyc_output
|
||||
config.local.js
|
||||
.umi
|
||||
.umi-production
|
||||
.idea/
|
||||
.cache
|
||||
yarn.lock
|
||||
es
|
||||
lib
|
||||
package-lock.json
|
||||
.eslintcache
|
||||
.history
|
||||
.now
|
||||
6
.prettierignore
Normal file
6
.prettierignore
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
/test/fixtures
|
||||
**/*.gif
|
||||
/dist
|
||||
/docs
|
||||
/es
|
||||
/lib
|
||||
6
.prettierrc.js
Normal file
6
.prettierrc.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
const fabric = require('@umijs/fabric');
|
||||
|
||||
module.exports = {
|
||||
...fabric.prettier,
|
||||
printWidth: 120,
|
||||
};
|
||||
41
.travis.yml
Normal file
41
.travis.yml
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- lts/*
|
||||
install:
|
||||
- yarn install
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
script:
|
||||
- yarn ci
|
||||
- yarn docs:build
|
||||
|
||||
#after_success:
|
||||
# - npm run codecov
|
||||
|
||||
before_deploy: |
|
||||
function npm_dist_tag() {
|
||||
if [[ "$TRAVIS_TAG" = *"-"* ]]; then
|
||||
echo "next"
|
||||
else
|
||||
echo "latest"
|
||||
fi
|
||||
}
|
||||
|
||||
deploy:
|
||||
- edge: true
|
||||
provider: npm
|
||||
email: kuitos.lau@gmail.com
|
||||
api_key: $NPM_AUTH_TOKEN
|
||||
skip_cleanup: true
|
||||
on:
|
||||
tags: true
|
||||
tag: $(npm_dist_tag)
|
||||
|
||||
- provider: pages
|
||||
skip_cleanup: true
|
||||
local_dir: dist
|
||||
github_token: $GITHUB_TOKEN
|
||||
keep_history: true
|
||||
on:
|
||||
branch: master
|
||||
44
.umirc.ts
Normal file
44
.umirc.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import { defineConfig } from 'dumi';
|
||||
|
||||
export default defineConfig({
|
||||
mode: 'site',
|
||||
hash: true,
|
||||
ssr: {},
|
||||
publicPath: process.env.NOW_DEPLOY ? '/' : '/qiankun/',
|
||||
base: process.env.NOW_DEPLOY ? '/' : '/qiankun',
|
||||
resolve: {
|
||||
includes: ['docs'],
|
||||
previewLangs: [],
|
||||
},
|
||||
locales: [
|
||||
['en', 'English'],
|
||||
['zh', '中文'],
|
||||
],
|
||||
navs: {
|
||||
en: [
|
||||
null,
|
||||
{ title: 'Changelog', path: 'https://github.com/umijs/qiankun/releases' },
|
||||
{ title: '1.x', path: 'https://v1.qiankun.umijs.org/' },
|
||||
{ title: 'GitHub', path: 'https://github.com/umijs/qiankun' },
|
||||
],
|
||||
zh: [
|
||||
null,
|
||||
{ title: '发布日志', path: 'https://github.com/umijs/qiankun/releases' },
|
||||
{ title: '1.x', path: 'https://v1.qiankun.umijs.org/zh/' },
|
||||
{ title: 'GitHub', path: 'https://github.com/umijs/qiankun' },
|
||||
],
|
||||
},
|
||||
metas: [
|
||||
{
|
||||
name: 'keywords',
|
||||
content:
|
||||
'microfrontend, micro frontend, micro frontends, micro-frontend, micro-frontends, microservice, javascript',
|
||||
},
|
||||
],
|
||||
analytics: {
|
||||
ga: 'UA-157295698-1',
|
||||
baidu: '0f738d9b0ac90574c09183ea85bcfa2e',
|
||||
},
|
||||
exportStatic: {},
|
||||
logo: false,
|
||||
});
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2019 Kuitos
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
86
README.md
Normal file
86
README.md
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
# qiankun(乾坤)
|
||||
|
||||
[](https://www.npmjs.com/package/qiankun) [](https://codecov.io/gh/umijs/qiankun) [](https://www.npmjs.com/package/qiankun) [](https://travis-ci.org/umijs/qiankun)
|
||||
|
||||
> In Chinese traditional culture `qian` means heaven and `kun` stands for earth, so `qiankun` is the universe.
|
||||
|
||||
An implementation of [Micro Frontends](https://micro-frontends.org/), based on [single-spa](https://github.com/CanopyTax/single-spa), but made it production-ready.
|
||||
|
||||
## 🤔 Motivation
|
||||
|
||||
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/)
|
||||
|
||||
Modularity is very important for large application. By breaking down a large system into individual sub-applications, we can achieve good divide-and-conquer between products and when necessary combination, especially for enterprise applications that usually involve multi-team collaboration. But if you're trying to implement such a micro frontends architecture system by yourself, you're likely to run into some tricky problems:
|
||||
|
||||
- In what form do sub applications publish static resources?
|
||||
- How does the main application integrate individual sub-applications?
|
||||
- How do you ensure that sub-applications are independent of each other (development independent, deployment independent) and runtime isolated?
|
||||
- Performance issues? What about public dependencies?
|
||||
- And so on...
|
||||
|
||||
After solving these common problems of micro frontends, we extracted the kernel of our solution after a lot of internal online application testing and polishing, and then named it `qiankun`.
|
||||
|
||||
**Probably the most complete micro-frontends solution you ever met🧐.**
|
||||
|
||||
## 📦 Installation
|
||||
|
||||
```shell
|
||||
$ yarn add qiankun # or npm i qiankun -S
|
||||
```
|
||||
|
||||
## 📖 Documentation
|
||||
|
||||
https://qiankun.umijs.org/
|
||||
|
||||
## 💿 Getting started
|
||||
|
||||
This repo contains an `examples` folder with a sample Shell app and multiple mounted Micro FE apps. To run this app, first clone `qiankun`:
|
||||
|
||||
```shell
|
||||
$ git clone https://github.com/umijs/qiankun.git
|
||||
$ cd qiankun
|
||||
```
|
||||
|
||||
Now run the yarn scripts to install and run the examples project:
|
||||
|
||||
```shell
|
||||
$ yarn install
|
||||
$ yarn examples:install
|
||||
$ yarn examples:start
|
||||
```
|
||||
|
||||
Visit `http://localhost:7099`.
|
||||
|
||||

|
||||
|
||||
## :sparkles: Features
|
||||
|
||||
- 📦 **Based On [single-spa](https://github.com/CanopyTax/single-spa)**
|
||||
- 📱 **Technology Agnostic**
|
||||
- 💪 **HTML Entry Access Mode**
|
||||
- 🛡 **Style Isolation**
|
||||
- 🧳 **JS Sandbox**
|
||||
- ⚡ **Prefetch Assets**
|
||||
- 🔌 **[Umi Plugin](https://github.com/umijs/plugins/tree/master/packages/plugin-qiankun) Integration**
|
||||
|
||||
## 🎯 Roadmap
|
||||
|
||||
- [x] Parcel apps integration (multiple sub apps displayed at the same time, but only one uses router at most)
|
||||
- [x] Communication development kits between master and sub apps
|
||||
- [ ] Custom side effects hijacker
|
||||
- [ ] Nested Microfrontends
|
||||
|
||||
## ❓ FAQ
|
||||
|
||||
https://qiankun.umijs.org/faq/
|
||||
|
||||
## 👬 Community
|
||||
|
||||
https://qiankun.umijs.org/#community
|
||||
|
||||
## 🎁 Acknowledgements
|
||||
|
||||
- [single-spa](https://github.com/CanopyTax/single-spa) What an awesome meta-framework for micro-frontends!
|
||||
- [import-html-entry](https://github.com/kuitos/import-html-entry/) An assets loader which supports html entry.
|
||||
47
docs/README.md
Normal file
47
docs/README.md
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
title: qiankun
|
||||
hero:
|
||||
title: qiankun
|
||||
desc: Probably the most complete micro-frontends solution you ever met🧐
|
||||
actions:
|
||||
- text: Get Started →
|
||||
link: /guide
|
||||
features:
|
||||
- title: Simple
|
||||
desc: Works with any javascript framework. Build your micro-frontend system just like using with iframe, but not iframe actually.
|
||||
- title: Complete
|
||||
desc: Includes almost all the basic capabilities required to build a micro-frontend system, such as style isolation, js sandbox, preloading, and so on.
|
||||
- title: Production-Ready
|
||||
desc: Had been extensively tested and polished by a large number of online applications both inside and outside of Ant Financial, the robustness is trustworthy.
|
||||
footer: MIT Licensed | Copyright © 2019-present<br />Powered by [dumi](https://d.umijs.org)
|
||||
---
|
||||
|
||||
## 📦 Installation
|
||||
|
||||
```shell
|
||||
$ yarn add qiankun # or npm i qiankun -S
|
||||
```
|
||||
|
||||
## 🔨 Getting Started
|
||||
|
||||
```tsx
|
||||
import { loadMicroApp } from 'qiankun';
|
||||
|
||||
// load micro app
|
||||
loadMicroApp({
|
||||
name: 'reactApp',
|
||||
entry: '//localhost:7100',
|
||||
container: '#container',
|
||||
props: {
|
||||
slogan: 'Hello Qiankun'
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
See details:[Getting Started](/guide/getting-started)
|
||||
|
||||
## Community
|
||||
|
||||
| Github Issue | 钉钉群 |
|
||||
| --- | --- |
|
||||
| [umijs/qiankun/issues](https://github.com/umijs/qiankun/issues) | <img src="https://gw.alipayobjects.com/mdn/rms_655822/afts/img/A*AdpES5z40LcAAAAAAAAAAABkARQnAQ" width="150" /> |
|
||||
47
docs/README.zh.md
Normal file
47
docs/README.zh.md
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
title: qiankun
|
||||
hero:
|
||||
title: qiankun
|
||||
desc: 可能是你见过最完善的微前端解决方案🧐
|
||||
actions:
|
||||
- text: 快速开始 →
|
||||
link: /zh/guide
|
||||
features:
|
||||
- title: 简单
|
||||
desc: 任意 js 框架均可使用。微应用接入像使用接入一个 iframe 系统一样简单,但实际不是 iframe。
|
||||
- title: 完备
|
||||
desc: 几乎包含所有构建微前端系统时所需要的基本能力,如 样式隔离、js 沙箱、预加载等。
|
||||
- title: 生产可用
|
||||
desc: 已在蚂蚁内外经受过足够大量的线上系统的考验及打磨,健壮性值得信赖。
|
||||
footer: MIT Licensed | Copyright © 2019-present<br />Powered by [dumi](https://d.umijs.org)
|
||||
---
|
||||
|
||||
## 📦 安装
|
||||
|
||||
```shell
|
||||
$ yarn add qiankun # or npm i qiankun -S
|
||||
```
|
||||
|
||||
## 🔨 使用
|
||||
|
||||
```tsx
|
||||
import { loadMicroApp } from 'qiankun';
|
||||
|
||||
// 加载微应用
|
||||
loadMicroApp({
|
||||
name: 'reactApp',
|
||||
entry: '//localhost:7100',
|
||||
container: '#container',
|
||||
props: {
|
||||
slogan: 'Hello Qiankun'
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
参考:[快速上手](/zh/guide/getting-started)。
|
||||
|
||||
## 社区
|
||||
|
||||
| Github Issue | 钉钉群 |
|
||||
| --- | --- |
|
||||
| [umijs/qiankun/issues](https://github.com/umijs/qiankun/issues) | <img src="https://gw.alipayobjects.com/mdn/rms_655822/afts/img/A*AdpES5z40LcAAAAAAAAAAABkARQnAQ" width="150" /> |
|
||||
434
docs/api/README.md
Normal file
434
docs/api/README.md
Normal file
|
|
@ -0,0 +1,434 @@
|
|||
---
|
||||
nav:
|
||||
title: API
|
||||
toc: menu
|
||||
---
|
||||
|
||||
# API
|
||||
|
||||
## Route based configuration
|
||||
|
||||
Suitable for route-based scenarios.
|
||||
|
||||
By linking the micro-application to some url rules, the function of automatically loading the corresponding micro-application when the browser url changes.
|
||||
|
||||
### `registerMicroApps(apps, lifeCycles?)`
|
||||
|
||||
- Parameters
|
||||
|
||||
- apps - `Array<RegistrableApp>` - required, registration information for the child application
|
||||
- lifeCycles - `LifeCycles` - optional, global sub app lifecycle hooks
|
||||
|
||||
- Type
|
||||
|
||||
- `RegistrableApp`
|
||||
|
||||
- name - `string` - required, the name of the child application and must be unique between the child applications.
|
||||
|
||||
- entry - `string | { scripts?: string[]; styles?: string[]; html?: string }` - required, The entry url of the child application.
|
||||
|
||||
- container - `string | HTMLElement` - required,A selector or Element instance of the container node of a micro application. Such as `container: '#root'` or `container: document.querySelector('#root')`.
|
||||
|
||||
- activeRule - - `string | (location: Location) => boolean | Array<string | (location: Location) => boolean> ` - required,activation rules for micro-apps.
|
||||
|
||||
* Support direct configuration of string or string array, such as `activeRule: '/app1'` or `activeRule: ['/app1', '/app2']`, when configured as a string, it will directly follow the path part in the url Do a prefix match. A successful match indicates that the current application will be activated.
|
||||
* Support to configure an active function or a group of active functions. The function will pass in the current location as a parameter. When the function returns true, it indicates that the current micro application will be activated. Such as `location => location.pathname.startsWith ('/app1')`.
|
||||
|
||||
Example rules:
|
||||
|
||||
`'/app1'`
|
||||
|
||||
* ✅ https://app.com/app1
|
||||
|
||||
* ✅ https://app.com/app1/anything/everything
|
||||
|
||||
* 🚫 https://app.com/app2
|
||||
|
||||
`'/users/:userId/profile'`
|
||||
|
||||
* ✅ https://app.com/users/123/profile
|
||||
* ✅ https://app.com/users/123/profile/sub-profile/
|
||||
* 🚫 https://app.com/users//profile/sub-profile/
|
||||
* 🚫 https://app.com/users/profile/sub-profile/
|
||||
|
||||
`'/pathname/#/hash'`
|
||||
|
||||
* ✅ https://app.com/pathname/#/hash
|
||||
* ✅ https://app.com/pathname/#/hash/route/nested
|
||||
* 🚫 https://app.com/pathname#/hash/route/nested
|
||||
* 🚫 https://app.com/pathname#/another-hash
|
||||
|
||||
`['/pathname/#/hash', '/app1']`
|
||||
|
||||
* ✅ https://app.com/pathname/#/hash/route/nested
|
||||
* ✅ https://app.com/app1/anything/everything
|
||||
* 🚫 https://app.com/pathname/app1
|
||||
* 🚫 https://app.com/app2
|
||||
|
||||
This function is called when the browser url changes, and `activeRule` returns `true` to indicate that the subapplication needs to be activated.
|
||||
|
||||
- loader - `(loading: boolean) => void` - optional, function will be invoked while the loading state changed.
|
||||
|
||||
- props - `object` - optional, data that the primary application needs to pass to the child application.
|
||||
|
||||
- `LifeCycles`
|
||||
|
||||
```ts
|
||||
type Lifecycle = (app: RegistrableApp) => Promise<any>;
|
||||
```
|
||||
|
||||
- beforeLoad - `Lifecycle | Array<Lifecycle>` - optional
|
||||
- beforeMount - `Lifecycle | Array<Lifecycle>` - optional
|
||||
- afterMount - `Lifecycle | Array<Lifecycle>` - optional
|
||||
- beforeUnmount - `Lifecycle | Array<Lifecycle>` - optional
|
||||
- afterUnmount - `Lifecycle | Array<Lifecycle>` - optional
|
||||
|
||||
- Usage
|
||||
|
||||
Configuration information for registered subapplications in the main application.
|
||||
|
||||
- Sample
|
||||
|
||||
```tsx
|
||||
import { registerMicroApps } from 'qiankun';
|
||||
|
||||
registerMicroApps(
|
||||
[
|
||||
{
|
||||
name: 'app1',
|
||||
entry: '//localhost:8080',
|
||||
container: '#container',
|
||||
activeRule: '/react',
|
||||
props: {
|
||||
name: 'kuitos',
|
||||
}
|
||||
}
|
||||
],
|
||||
{
|
||||
beforeLoad: app => console.log('before load', app.name),
|
||||
beforeMount: [
|
||||
app => console.log('before mount', app.name),
|
||||
],
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
### `start(opts?)`
|
||||
|
||||
- Parameters
|
||||
|
||||
- opts - `Options` optional
|
||||
|
||||
- Type
|
||||
|
||||
- `Options`
|
||||
|
||||
- prefetch - `boolean | 'all' | string[] | (( apps: RegistrableApp[] ) => { criticalAppNames: string[]; minorAppsName: string[] })` - optional, whether to enable prefetch, default is `true`.
|
||||
|
||||
A configuration of `true` starts prefetching static resources for other subapplications after the first subapplication mount completes.
|
||||
|
||||
If configured as `'all'`, the main application `start` will begin to preload all subapplication static resources.
|
||||
|
||||
If configured as `string[]`, starts prefetching static resources for subapplications after the first subapplication mount completes which be declared in this list.
|
||||
|
||||
If configured as `function`, the timing of all subapplication static resources will be controlled by yourself.
|
||||
|
||||
- sandbox - `boolean` | `{ strictStyleIsolation?: boolean, experimentalStyleIsolation?: boolean }` - optional, whether to open the js sandbox, default is `true`.
|
||||
|
||||
When configured as `{strictStyleIsolation: true}`, qiankun will convert the container dom of each application to a [shadow dom](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM), to ensure that the style of the application will not leak to the global.
|
||||
|
||||
And qiankun offered an experimental way to support css isolation, when experimentalStyleIsolation is set to true, qiankun will limit their scope of influence by add selector constraint, thereforce styles of sub-app will like following case:
|
||||
|
||||
```javascript
|
||||
// if app name is react16
|
||||
.app-main {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
div[data-qiankun-react16] .app-main {
|
||||
font-size: 14px;
|
||||
}
|
||||
```
|
||||
|
||||
notice:
|
||||
@keyframes, @font-face, @import, @page are not supported (i.e. will not be rewritten)
|
||||
P.S: In current stage, we're not support the case: Inserting external styles by `<link>` yet, we're consider add this part in the future.
|
||||
|
||||
- singular - `boolean | ((app: RegistrableApp<any>) => Promise<boolean>);` - Optional, whether it is a singleton scenario, singleton means just rendered one micro app at one time. default is `true`.
|
||||
|
||||
- fetch - `Function` - optional
|
||||
|
||||
- getPublicPath - `(url: string) => string` - optional
|
||||
|
||||
- getTemplate - `(tpl: string) => string` - optional
|
||||
|
||||
- excludeAssetFilter - `(assetUrl: string) => boolean` - optional,some special dynamic loaded micro app resources should not be handled by qiankun hijacking
|
||||
|
||||
- Usage
|
||||
|
||||
Start qiankun.
|
||||
|
||||
- Sample
|
||||
|
||||
```ts
|
||||
import { start } from 'qiankun';
|
||||
|
||||
start();
|
||||
```
|
||||
|
||||
### `setDefaultMountApp(appLink)`
|
||||
|
||||
- Parameters
|
||||
|
||||
- appLink - `string` - required
|
||||
|
||||
- Usage
|
||||
|
||||
Sets the child application that enters by default after the main application starts.
|
||||
|
||||
- Sample
|
||||
|
||||
```ts
|
||||
import { setDefaultMountApp } from 'qiankun';
|
||||
|
||||
setDefaultMountApp('/homeApp');
|
||||
```
|
||||
|
||||
### `runAfterFirstMounted(effect)`
|
||||
|
||||
- Parameters
|
||||
|
||||
- effect - `() => void` - required
|
||||
|
||||
- Usage
|
||||
|
||||
Methods that need to be called after the first subapplication mount, such as turning on some monitoring or buried scripts.
|
||||
|
||||
- Sample
|
||||
|
||||
```ts
|
||||
import { runAfterFirstMounted } from 'qiankun';
|
||||
|
||||
runAfterFirstMounted(() => startMonitor());
|
||||
```
|
||||
|
||||
## Manually load micro applications
|
||||
|
||||
It is suitable for scenarios where a micro application needs to be manually loaded / unloaded.
|
||||
|
||||
<Alert type="info">
|
||||
Usually in this scenario, the micro application is a business component that can run independently without routing.
|
||||
Micro applications should not be split too fine, it is recommended to split according to the business domain. Functional units with close business associations should be made into one micro-application, and conversely, those with less close association can be considered to be split into multiple micro-applications.
|
||||
A criterion for judging whether the business is closely related: <strong>Look at whether this micro application has frequent communication needs with other micro applications</strong>. If it is possible to show that these two micro-applications are serving the same business scenario, it may be more appropriate to merge them into one micro-application.
|
||||
</Alert>
|
||||
|
||||
### `loadMicroApp(app, configuration?)`
|
||||
|
||||
* Parameters
|
||||
* app - `LoadableApp` - Required, basic information of micro application
|
||||
* name - `string` - Required, the name of the micro application must be unique among the micro applications.
|
||||
* entry - `string | { scripts?: string[]; styles?: string[]; html?: string }` - Required, the entry address of the micro application.
|
||||
* container - `string | HTMLElement` - Required, selector or Element instance of the container node of the micro application. Such as `container: '#root'` or `container: document.querySelector('#root')`.
|
||||
* props - `object` - Optional, the data that needs to be passed to the micro-application during initialization.
|
||||
|
||||
* configuration - `Configuration` - Optional, configuration information of the micro application
|
||||
|
||||
* sandbox - `boolean` | `{ strictStyleIsolation?: boolean }` - Optional, whether to enable the sandbox, the default is `true`.
|
||||
|
||||
When configured as `{strictStyleIsolation: true}`, it means that strict style isolation mode is enabled. In this mode, qiankun will wrap a [shadow dom](https://developer.mozilla.org/zh-CN/docs/Web/Web_Components/Using_shadow_DOM) node for each micro-application container, so as to ensure that the style of the micro application will not affect the whole world.
|
||||
|
||||
* singular - `boolean | ((app: RegistrableApp<any>) => Promise<boolean>);` - Optional, whether it is a singleton scenario, singleton means just rendered one micro app at one time. Default is `false`.
|
||||
|
||||
* fetch - `Function` - Optional, custom fetch method.
|
||||
|
||||
* getPublicPath - `(url: string) => string` - Optional
|
||||
|
||||
* getTemplate - `(tpl: string) => string` - Optional
|
||||
|
||||
* excludeAssetFilter - `(assetUrl: string) => boolean` - optional,some special dynamic loaded micro app resources should not be handled by qiankun hijacking
|
||||
|
||||
* 返回值 - `MicroApp` - Micro application examples
|
||||
* mount(): Promise<null>;
|
||||
* unmount(): Promise<null>;
|
||||
* update(customProps: object): Promise<any>;
|
||||
* getStatus():
|
||||
| "NOT_LOADED"
|
||||
| "LOADING_SOURCE_CODE"
|
||||
| "NOT_BOOTSTRAPPED"
|
||||
| "BOOTSTRAPPING"
|
||||
| "NOT_MOUNTED"
|
||||
| "MOUNTING"
|
||||
| "MOUNTED"
|
||||
| "UPDATING"
|
||||
| "UNMOUNTING"
|
||||
| "UNLOADING"
|
||||
| "SKIP_BECAUSE_BROKEN"
|
||||
| "LOAD_ERROR";
|
||||
* loadPromise: Promise<null>;
|
||||
* bootstrapPromise: Promise<null>;
|
||||
* mountPromise: Promise<null>;
|
||||
* unmountPromise: Promise<null>;
|
||||
|
||||
* Usage
|
||||
|
||||
Load a micro application manually.
|
||||
|
||||
If you need to support the main application to manually update the micro application, you need to export an update hook for the micro application entry:
|
||||
|
||||
```ts
|
||||
export async function mount(props) {
|
||||
renderApp(props);
|
||||
}
|
||||
|
||||
// Added update hook to allow the main application to manually update the micro application
|
||||
export async function update(props) {
|
||||
renderPatch(props);
|
||||
}
|
||||
```
|
||||
|
||||
* Sample
|
||||
|
||||
```jsx
|
||||
import { loadMicroApp } from 'qiankun';
|
||||
import React from 'react';
|
||||
|
||||
class App extends React.Component {
|
||||
|
||||
containerRef = React.createRef();
|
||||
microApp = null;
|
||||
|
||||
componentDidMount() {
|
||||
this.microApp = loadMicroApp(
|
||||
{ name: 'app1', entry: '//localhost:1234', container: this.containerRef.current, props: { name: 'qiankun' } },
|
||||
);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.microApp.unmount();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.microApp.update({ name: 'kuitos' });
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div ref={this.containerRef}></div>;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `prefetchApps(apps, importEntryOpts?)`
|
||||
|
||||
- Parameters
|
||||
- apps - `AppMetadata[]` - Required - list of preloaded apps
|
||||
- importEntryOpts - Optional - Load configuration
|
||||
|
||||
- Type
|
||||
- `AppMetadata`
|
||||
- name - `string` - Required - Application name
|
||||
- entry - `string | { scripts?: string[]; styles?: string[]; html?: string }` - Required,The entry address of the microapp
|
||||
|
||||
- Usage
|
||||
|
||||
Manually preload the specified micro application static resources. Only needed to manually load micro-application scenarios, you can directly configure the `prefetch` attribute based on the route automatic activation scenario.
|
||||
|
||||
- Sample
|
||||
|
||||
```ts
|
||||
import { prefetchApps } from 'qiankun';
|
||||
|
||||
prefetchApps([ { name: 'app1', entry: '//locahost:7001' }, { name: 'app2', entry: '//locahost:7002' } ])
|
||||
```
|
||||
|
||||
## [addErrorHandler/removeErrorHandler](https://single-spa.js.org/docs/api#adderrorhandler)
|
||||
|
||||
## `addGlobalUncaughtErrorHandler(handler)`
|
||||
|
||||
- Parameters
|
||||
|
||||
- handler - `(...args: any[]) => void` - Required
|
||||
|
||||
- Usage
|
||||
|
||||
Add the global uncaught error hander.
|
||||
|
||||
- Sample
|
||||
|
||||
```ts
|
||||
import { addGlobalUncaughtErrorHandler } from 'qiankun';
|
||||
|
||||
addGlobalUncaughtErrorHandler(event => console.log(event));
|
||||
```
|
||||
|
||||
## `removeGlobalUncaughtErrorHandler(handler)`
|
||||
|
||||
- Parameters
|
||||
|
||||
- handler - `(...args: any[]) => void` - Required
|
||||
|
||||
- Usage
|
||||
|
||||
Remove the global uncaught error hander.
|
||||
|
||||
- Sample
|
||||
|
||||
```ts
|
||||
import { removeGlobalUncaughtErrorHandler } from 'qiankun';
|
||||
|
||||
removeGlobalUncaughtErrorHandler(handler);
|
||||
```
|
||||
|
||||
## `initGlobalState(state)`
|
||||
|
||||
- Parameters
|
||||
|
||||
- state - `Record<string, any>` - Required
|
||||
|
||||
- Usage
|
||||
|
||||
init global state, and return actions for communication. It is recommended to use in master, and slave get actions through props。
|
||||
|
||||
- Return
|
||||
|
||||
- MicroAppStateActions
|
||||
|
||||
- onGlobalStateChange: `(callback: OnGlobalStateChangeCallback, fireImmediately?: boolean) => void` - Listen the global status in the current application: when state changes will trigger callback; fireImmediately = true, will trigger callback immediately when use this method.
|
||||
|
||||
- setGlobalState: `(state: Record<string, any>) => boolean` - Set global state by first layer props, it can just modify first layer props what has defined.
|
||||
|
||||
- offGlobalStateChange: `() => boolean` - Remove Listener in this app, will default trigger when app unmount.
|
||||
|
||||
- Sample
|
||||
|
||||
Master:
|
||||
```ts
|
||||
import { initGlobalState, MicroAppStateActions } from 'qiankun';
|
||||
|
||||
// Initialize state
|
||||
const actions: MicroAppStateActions = initGlobalState(state);
|
||||
|
||||
actions.onGlobalStateChange((state, prev) => {
|
||||
// state: new state; prev old state
|
||||
console.log(state, prev);
|
||||
});
|
||||
actions.setGlobalState(state);
|
||||
actions.offGlobalStateChange();
|
||||
```
|
||||
|
||||
Slave:
|
||||
```ts
|
||||
// get actions from mount
|
||||
export function mount(props) {
|
||||
|
||||
props.onGlobalStateChange((state, prev) => {
|
||||
// state: new state; prev old state
|
||||
console.log(state, prev);
|
||||
});
|
||||
props.setGlobalState(state);
|
||||
|
||||
// It will trigger when slave umount, not necessary to use in non special cases.
|
||||
props.offGlobalStateChange();
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
452
docs/api/README.zh.md
Normal file
452
docs/api/README.zh.md
Normal file
|
|
@ -0,0 +1,452 @@
|
|||
---
|
||||
nav:
|
||||
title: API
|
||||
toc: menu
|
||||
---
|
||||
|
||||
# API 说明
|
||||
|
||||
## 基于路由配置
|
||||
|
||||
适用于 route-based 场景。
|
||||
|
||||
通过将微应用关联到一些 url 规则的方式,实现当浏览器 url 发生变化时,自动加载相应的微应用的功能。
|
||||
|
||||
### registerMicroApps(apps, lifeCycles?)
|
||||
|
||||
- 参数
|
||||
|
||||
- apps - `Array<RegistrableApp>` - 必选,微应用的一些注册信息
|
||||
- lifeCycles - `LifeCycles` - 可选,全局的微应用生命周期钩子
|
||||
|
||||
- 类型
|
||||
|
||||
- `RegistrableApp`
|
||||
|
||||
- name - `string` - 必选,微应用的名称,微应用之间必须确保唯一。
|
||||
|
||||
- entry - `string | { scripts?: string[]; styles?: string[]; html?: string }` - 必选,微应用的 entry 地址。
|
||||
|
||||
- container - `string | HTMLElement` - 必选,微应用的容器节点的选择器或者 Element 实例。如`container: '#root'` 或 `container: document.querySelector('#root')`。
|
||||
|
||||
- activeRule - `string | (location: Location) => boolean | Array<string | (location: Location) => boolean> ` - 必选,微应用的激活规则。
|
||||
|
||||
* 支持直接配置字符串或字符串数组,如 `activeRule: '/app1'` 或 `activeRule: ['/app1', '/app2']`,当配置为字符串时会直接跟 url 中的路径部分做前缀匹配,匹配成功表明当前应用会被激活。
|
||||
* 支持配置一个 active function 函数或一组 active function。函数会传入当前 location 作为参数,函数返回 true 时表明当前微应用会被激活。如 `location => location.pathname.startsWith('/app1')`。
|
||||
|
||||
规则示例:
|
||||
|
||||
`'/app1'`
|
||||
|
||||
* ✅ https://app.com/app1
|
||||
|
||||
* ✅ https://app.com/app1/anything/everything
|
||||
|
||||
* 🚫 https://app.com/app2
|
||||
|
||||
`'/users/:userId/profile'`
|
||||
|
||||
* ✅ https://app.com/users/123/profile
|
||||
* ✅ https://app.com/users/123/profile/sub-profile/
|
||||
* 🚫 https://app.com/users//profile/sub-profile/
|
||||
* 🚫 https://app.com/users/profile/sub-profile/
|
||||
|
||||
`'/pathname/#/hash'`
|
||||
|
||||
* ✅ https://app.com/pathname/#/hash
|
||||
* ✅ https://app.com/pathname/#/hash/route/nested
|
||||
* 🚫 https://app.com/pathname#/hash/route/nested
|
||||
* 🚫 https://app.com/pathname#/another-hash
|
||||
|
||||
`['/pathname/#/hash', '/app1']`
|
||||
|
||||
* ✅ https://app.com/pathname/#/hash/route/nested
|
||||
* ✅ https://app.com/app1/anything/everything
|
||||
* 🚫 https://app.com/pathname/app1
|
||||
* 🚫 https://app.com/app2
|
||||
|
||||
浏览器 url 发生变化会调用 activeRule 里的规则,`activeRule` 任意一个返回 `true` 时表明该微应用需要被激活。
|
||||
|
||||
- loader - `(loading: boolean) => void` - 可选,loading 状态发生变化时会调用的方法。
|
||||
|
||||
- props - `object` - 可选,主应用需要传递给微应用的数据。
|
||||
|
||||
- `LifeCycles`
|
||||
|
||||
```ts
|
||||
type Lifecycle = (app: RegistrableApp) => Promise<any>;
|
||||
```
|
||||
|
||||
- beforeLoad - `Lifecycle | Array<Lifecycle>` - 可选
|
||||
- beforeMount - `Lifecycle | Array<Lifecycle>` - 可选
|
||||
- afterMount - `Lifecycle | Array<Lifecycle>` - 可选
|
||||
- beforeUnmount - `Lifecycle | Array<Lifecycle>` - 可选
|
||||
- afterUnmount - `Lifecycle | Array<Lifecycle>` - 可选
|
||||
|
||||
- 用法
|
||||
|
||||
注册微应用的基础配置信息。当浏览器 url 发生变化时,会自动检查每一个微应用注册的 `activeRule` 规则,符合规则的应用将会被自动激活。
|
||||
|
||||
- 示例
|
||||
|
||||
```tsx
|
||||
import { registerMicroApps } from 'qiankun';
|
||||
|
||||
registerMicroApps(
|
||||
[
|
||||
{
|
||||
name: 'app1',
|
||||
entry: '//localhost:8080',
|
||||
container: '#container',
|
||||
activeRule: '/react',
|
||||
props: {
|
||||
name: 'kuitos',
|
||||
}
|
||||
}
|
||||
],
|
||||
{
|
||||
beforeLoad: app => console.log('before load', app.name),
|
||||
beforeMount: [
|
||||
app => console.log('before mount', app.name),
|
||||
],
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
### `start(opts?)`
|
||||
|
||||
- 参数
|
||||
|
||||
- opts - `Options` 可选
|
||||
|
||||
- 类型
|
||||
|
||||
- `Options`
|
||||
|
||||
- prefetch - `boolean | 'all' | string[] | (( apps: RegistrableApp[] ) => { criticalAppNames: string[]; minorAppsName: string[] })` - 可选,是否开启预加载,默认为 `true`。
|
||||
|
||||
配置为 `true` 则会在第一个微应用 mount 完成后开始预加载其他微应用的静态资源
|
||||
|
||||
配置为 `'all'` 则主应用 `start` 后即开始预加载所有微应用静态资源
|
||||
|
||||
配置为 `string[]` 则会在第一个微应用 mounted 后开始加载数组内的微应用资源
|
||||
|
||||
配置为 `function` 则可完全自定义应用的资源加载时机 (首屏应用及次屏应用)
|
||||
|
||||
- sandbox - `boolean` | `{ strictStyleIsolation?: boolean, experimentalStyleIsolation?: boolean }` - 可选,是否开启沙箱,默认为 `true`。
|
||||
|
||||
当配置为 `{ strictStyleIsolation: true }` 表示开启严格的样式隔离模式。这种模式下 qiankun 会为每个微应用的容器包裹上一个 [shadow dom](https://developer.mozilla.org/zh-CN/docs/Web/Web_Components/Using_shadow_DOM) 节点,从而确保微应用的样式不会对全局造成影响。
|
||||
|
||||
而除此以外,qiankun 还提供了一种实验性的方式来支持样式隔离,当 `experimentalStyleIsolation` 被设置为 true 时,qiankun 将会通过动态改写一个特殊的选择器约束来限制 css 的生效范围,应用的样式会按照如下模式改写:
|
||||
|
||||
```javascript
|
||||
// 假设应用名是 react16
|
||||
.app-main {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
div[data-qiankun-react16] .app-main {
|
||||
font-size: 14px;
|
||||
}
|
||||
```
|
||||
|
||||
注意:
|
||||
@keyframes, @font-face, @import, @page 将不被支持 (i.e. 不会被改写)
|
||||
P.S: 在目前的阶段,该功能还不支持动态的、使用 `<link />`标签来插入外联的样式,但考虑在未来支持这部分场景。
|
||||
|
||||
- singular - `boolean | ((app: RegistrableApp<any>) => Promise<boolean>);` - 可选,是否为单实例场景,单实例指的是同一时间只会渲染一个微应用。默认为 `true`。
|
||||
|
||||
- fetch - `Function` - 可选,自定义的 fetch 方法。
|
||||
|
||||
- getPublicPath - `(url: string) => string` - 可选
|
||||
|
||||
- getTemplate - `(tpl: string) => string` - 可选
|
||||
|
||||
- excludeAssetFilter - `(assetUrl: string) => boolean` - 可选,指定部分特殊的动态加载的微应用资源(css/js) 不被qiankun 劫持处理
|
||||
|
||||
- 用法
|
||||
|
||||
启动 qiankun。
|
||||
|
||||
- 示例
|
||||
|
||||
```ts
|
||||
import { start } from 'qiankun';
|
||||
|
||||
start();
|
||||
```
|
||||
|
||||
### setDefaultMountApp(appLink)
|
||||
|
||||
- 参数
|
||||
|
||||
- appLink - `string` - 必选
|
||||
|
||||
- 用法
|
||||
|
||||
设置主应用启动后默认进入的微应用。
|
||||
|
||||
- 示例
|
||||
|
||||
```ts
|
||||
import { setDefaultMountApp } from 'qiankun';
|
||||
|
||||
setDefaultMountApp('/homeApp');
|
||||
```
|
||||
|
||||
### `runAfterFirstMounted(effect)`
|
||||
|
||||
- 参数
|
||||
|
||||
- effect - `() => void` - 必选
|
||||
|
||||
- 用法
|
||||
|
||||
第一个微应用 mount 后需要调用的方法,比如开启一些监控或者埋点脚本。
|
||||
|
||||
- 示例
|
||||
|
||||
```ts
|
||||
import { runAfterFirstMounted } from 'qiankun';
|
||||
|
||||
runAfterFirstMounted(() => startMonitor());
|
||||
```
|
||||
|
||||
## 手动加载微应用
|
||||
|
||||
适用于需要手动 加载/卸载 一个微应用的场景。
|
||||
|
||||
<Alert type="info">
|
||||
通常这种场景下微应用是一个不带路由的可独立运行的业务组件。
|
||||
微应用不宜拆分过细,建议按照业务域来做拆分。业务关联紧密的功能单元应该做成一个微应用,反之关联不紧密的可以考虑拆分成多个微应用。
|
||||
一个判断业务关联是否紧密的标准:<strong>看这个微应用与其他微应用是否有频繁的通信需求</strong>。如果有可能说明这两个微应用本身就是服务于同一个业务场景,合并成一个微应用可能会更合适。
|
||||
</Alert>
|
||||
|
||||
### `loadMicroApp(app, configuration?)`
|
||||
|
||||
* 参数
|
||||
* app - `LoadableApp` - 必选,微应用的基础信息
|
||||
* name - `string` - 必选,微应用的名称,微应用之间必须确保唯一。
|
||||
* entry - `string | { scripts?: string[]; styles?: string[]; html?: string }` - 必选,微应用的 entry 地址。
|
||||
* container - `string | HTMLElement` - 必选,微应用的容器节点的选择器或者 Element 实例。如`container: '#root'` 或 `container: document.querySelector('#root')`。
|
||||
* props - `object` - 可选,初始化时需要传递给微应用的数据。
|
||||
|
||||
* configuration - `Configuration` - 可选,微应用的配置信息
|
||||
|
||||
* sandbox - `boolean` | `{ strictStyleIsolation?: boolean, experimentalStyleIsolation?: boolean }` - 可选,是否开启沙箱,默认为 `true`。
|
||||
|
||||
默认情况下沙箱可以确保单实例场景子应用之间的样式隔离,但是无法确保主应用跟子应用、或者多实例场景的子应用样式隔离。当配置为 `{ strictStyleIsolation: true }` 时表示开启严格的样式隔离模式。这种模式下 qiankun 会为每个微应用的容器包裹上一个 [shadow dom](https://developer.mozilla.org/zh-CN/docs/Web/Web_Components/Using_shadow_DOM) 节点,从而确保微应用的样式不会对全局造成影响。
|
||||
|
||||
<Alert>
|
||||
基于 ShadowDOM 的严格样式隔离并不是一个可以无脑使用的方案,大部分情况下都需要接入应用做一些适配后才能正常在 ShadowDOM 中运行起来(比如 react 场景下需要解决这些 <a target="_blank" href="https://github.com/facebook/react/issues/10422">问题</a>,使用者需要清楚开启了 <code>strictStyleIsolation</code> 意味着什么。后续 qiankun 会提供更多官方实践文档帮助用户能快速的将应用改造成可以运行在 ShadowDOM 环境的微应用。
|
||||
</Alert>
|
||||
|
||||
除此以外,qiankun 还提供了一个实验性的样式隔离特性,当 experimentalStyleIsolation 被设置为 true 时,qiankun 会改写子应用所添加的样式为所有样式规则增加一个特殊的选择器规则来限定其影响范围,因此改写后的代码会表达类似为如下结构:
|
||||
|
||||
```
|
||||
// 假设加载的应用名为 react16
|
||||
.app-main {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
div[data-qiankun-react16] .app-main {
|
||||
font-size: 14px;
|
||||
}
|
||||
```
|
||||
|
||||
注意事项:
|
||||
目前 @keyframes, @font-face, @import, @page 等规则不会支持 (i.e. 不会被改写)
|
||||
|
||||
在目前阶段, 我们还不支持以动态的外联形式 (`<link />`) 形式加入的样式,但我们考虑将来支持这一部分。
|
||||
|
||||
* singular - `boolean | ((app: RegistrableApp<any>) => Promise<boolean>);` - 可选,是否为单实例场景,单实例指的是同一时间只会渲染一个微应用。默认为 `false`。
|
||||
|
||||
* fetch - `Function` - 可选,自定义的 fetch 方法。
|
||||
|
||||
* getPublicPath - `(url: string) => string` - 可选
|
||||
|
||||
* getTemplate - `(tpl: string) => string` - 可选
|
||||
|
||||
* excludeAssetFilter - `(assetUrl: string) => boolean` - 可选,指定部分特殊的动态加载的微应用资源(css/js) 不被qiankun 劫持处理
|
||||
|
||||
* 返回值 - `MicroApp` - 微应用实例
|
||||
* mount(): Promise<null>;
|
||||
* unmount(): Promise<null>;
|
||||
* update(customProps: object): Promise<any>;
|
||||
* getStatus():
|
||||
| "NOT_LOADED"
|
||||
| "LOADING_SOURCE_CODE"
|
||||
| "NOT_BOOTSTRAPPED"
|
||||
| "BOOTSTRAPPING"
|
||||
| "NOT_MOUNTED"
|
||||
| "MOUNTING"
|
||||
| "MOUNTED"
|
||||
| "UPDATING"
|
||||
| "UNMOUNTING"
|
||||
| "UNLOADING"
|
||||
| "SKIP_BECAUSE_BROKEN"
|
||||
| "LOAD_ERROR";
|
||||
* loadPromise: Promise<null>;
|
||||
* bootstrapPromise: Promise<null>;
|
||||
* mountPromise: Promise<null>;
|
||||
* unmountPromise: Promise<null>;
|
||||
|
||||
* 用法
|
||||
|
||||
手动加载一个微应用。
|
||||
|
||||
如果需要能支持主应用手动 update 微应用,需要微应用 entry 再多导出一个 update 钩子:
|
||||
|
||||
```ts
|
||||
export async function mount(props) {
|
||||
renderApp(props);
|
||||
}
|
||||
|
||||
// 增加 update 钩子以便主应用手动更新微应用
|
||||
export async function update(props) {
|
||||
renderPatch(props);
|
||||
}
|
||||
```
|
||||
|
||||
* 示例
|
||||
|
||||
```jsx
|
||||
import { loadMicroApp } from 'qiankun';
|
||||
import React from 'react';
|
||||
|
||||
class App extends React.Component {
|
||||
|
||||
containerRef = React.createRef();
|
||||
microApp = null;
|
||||
|
||||
componentDidMount() {
|
||||
this.microApp = loadMicroApp(
|
||||
{ name: 'app1', entry: '//localhost:1234', container: this.containerRef.current, props: { name: 'qiankun' } },
|
||||
);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.microApp.unmount();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.microApp.update({ name: 'kuitos' });
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div ref={this.containerRef}></div>;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `prefetchApps(apps, importEntryOpts?)`
|
||||
|
||||
- 参数
|
||||
- apps - `AppMetadata[]` - 必选 - 预加载的应用列表
|
||||
- importEntryOpts - 可选 - 加载配置
|
||||
|
||||
- 类型
|
||||
- `AppMetadata`
|
||||
- name - `string` - 必选 - 应用名
|
||||
- entry - `string | { scripts?: string[]; styles?: string[]; html?: string }` - 必选,微应用的 entry 地址
|
||||
|
||||
- 用法
|
||||
|
||||
手动预加载指定的微应用静态资源。仅手动加载微应用场景需要,基于路由自动激活场景直接配置 `prefetch` 属性即可。
|
||||
|
||||
- 示例
|
||||
|
||||
```ts
|
||||
import { prefetchApps } from 'qiankun';
|
||||
|
||||
prefetchApps([ { name: 'app1', entry: '//locahost:7001' }, { name: 'app2', entry: '//locahost:7002' } ])
|
||||
```
|
||||
|
||||
## [addErrorHandler/removeErrorHandler](https://single-spa.js.org/docs/api#adderrorhandler)
|
||||
|
||||
## `addGlobalUncaughtErrorHandler(handler)`
|
||||
|
||||
- 参数
|
||||
|
||||
- handler - `(...args: any[]) => void` - 必选
|
||||
|
||||
- 用法
|
||||
|
||||
添加全局的未捕获异常处理器。
|
||||
|
||||
- 示例
|
||||
|
||||
```ts
|
||||
import { addGlobalUncaughtErrorHandler } from 'qiankun';
|
||||
|
||||
addGlobalUncaughtErrorHandler(event => console.log(event));
|
||||
```
|
||||
|
||||
## `removeGlobalUncaughtErrorHandler(handler)`
|
||||
|
||||
- 参数
|
||||
|
||||
- handler - `(...args: any[]) => void` - 必选
|
||||
|
||||
- 用法
|
||||
|
||||
移除全局的未捕获异常处理器。
|
||||
|
||||
- 示例
|
||||
|
||||
```ts
|
||||
import { removeGlobalUncaughtErrorHandler } from 'qiankun';
|
||||
|
||||
removeGlobalUncaughtErrorHandler(handler);
|
||||
```
|
||||
|
||||
## `initGlobalState(state)`
|
||||
|
||||
- 参数
|
||||
|
||||
- state - `Record<string, any>` - 必选
|
||||
|
||||
- 用法
|
||||
|
||||
定义全局状态,并返回通信方法,建议在主应用使用,微应用通过 props 获取通信方法。
|
||||
|
||||
- 返回
|
||||
|
||||
- MicroAppStateActions
|
||||
|
||||
- onGlobalStateChange: `(callback: OnGlobalStateChangeCallback, fireImmediately?: boolean) => void`, 在当前应用监听全局状态,有变更触发 callback,fireImmediately = true 立即触发 callback
|
||||
|
||||
- setGlobalState: `(state: Record<string, any>) => boolean`, 按一级属性设置全局状态,微应用中只能修改已存在的一级属性
|
||||
|
||||
- offGlobalStateChange: `() => boolean`,移除当前应用的状态监听,微应用 umount 时会默认调用
|
||||
|
||||
- 示例
|
||||
|
||||
主应用:
|
||||
```ts
|
||||
import { initGlobalState, MicroAppStateActions } from 'qiankun';
|
||||
|
||||
// 初始化 state
|
||||
const actions: MicroAppStateActions = initGlobalState(state);
|
||||
|
||||
actions.onGlobalStateChange((state, prev) => {
|
||||
// state: 变更后的状态; prev 变更前的状态
|
||||
console.log(state, prev);
|
||||
});
|
||||
actions.setGlobalState(state);
|
||||
actions.offGlobalStateChange();
|
||||
```
|
||||
|
||||
微应用:
|
||||
```ts
|
||||
// 从生命周期 mount 中获取通信方法,使用方式和 master 一致
|
||||
export function mount(props) {
|
||||
|
||||
props.onGlobalStateChange((state, prev) => {
|
||||
// state: 变更后的状态; prev 变更前的状态
|
||||
console.log(state, prev);
|
||||
});
|
||||
|
||||
props.setGlobalState(state);
|
||||
}
|
||||
```
|
||||
267
docs/faq/README.md
Normal file
267
docs/faq/README.md
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
---
|
||||
nav:
|
||||
title: FAQ
|
||||
toc: menu
|
||||
---
|
||||
|
||||
# FAQ
|
||||
|
||||
## `Application died in status LOADING_SOURCE_CODE: You need to export the functional lifecycles in xxx entry`
|
||||
|
||||
This error thrown as qiankun could not find the exported lifecycle method from your entry js.
|
||||
|
||||
To solve the exception, try the following steps:
|
||||
|
||||
1. check you have exported the specified lifecycles, see the [doc](/guide/getting-started#1-exports-lifecycles-from-sub-app-entry)
|
||||
|
||||
2. check you have set the specified configuration with your bundler, see the [doc](/guide/getting-started#2-config-sub-app-bundler)
|
||||
|
||||
3. check your `package.json` name field is unique between sub apps.
|
||||
|
||||
4. Check if the entry js in the sub-app's entry HTML is the last script to load. If not, move the order to make it be the last, or manually mark the entry js as `entry` in the HTML, such as:
|
||||
```html {2}
|
||||
<script src="/antd.js"></script>
|
||||
<script src="/appEntry.js" entry></script>
|
||||
<script src="https://www.google.com/analytics.js"></script>
|
||||
```
|
||||
|
||||
If it still not works after the steps above, this is usually due to browser compatibility issues. Try to **set the webpack `output.library` of the broken sub app the same with your main app registration for your app**, such as:
|
||||
|
||||
Such as here is the main configuration:
|
||||
|
||||
```ts {4}
|
||||
// main app
|
||||
registerMicroApps([
|
||||
{
|
||||
name: 'brokenSubApp',
|
||||
entry: '//localhost:7100',
|
||||
container: '#yourContainer',
|
||||
activeRule: '/react',
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
||||
Set the `output.library` the same with main app registration:
|
||||
|
||||
```js {4}
|
||||
module.exports = {
|
||||
output: {
|
||||
// Keep the same with the registration in main app
|
||||
library: 'brokenSubApp',
|
||||
libraryTarget: 'umd',
|
||||
jsonpFunction: `webpackJsonp_${packageName}`,
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Vue Router Error - `Uncaught TypeError: Cannot redefine property: $router`
|
||||
|
||||
If you pass `{ sandbox: true }` to `start()` function, `qiankun` will use `Proxy` to isolate global `window` object for sub applications. When you access `window.Vue` in sub application's code,it will check whether the `Vue` property in the proxyed `window` object. If the property does not exist, it will look it up in the global `window` object and return it.
|
||||
|
||||
There are three lines code in the `vue-router` as followed, and it will access `window.Vue` once the `vue-router` module is loaded. And the `window.Vue` in following code is your master application's `Vue`.
|
||||
|
||||
```javascript
|
||||
if (inBrowser && window.Vue) {
|
||||
window.Vue.use(VueRouter)
|
||||
}
|
||||
```
|
||||
|
||||
To solve the error, choose one of the options listed below:
|
||||
|
||||
1. Use bundler to pack `Vue` library, instead of CDN or external module
|
||||
2. Rename `Vue` to other name in master application, eg: `window.Vue2 = window.Vue; window.Vue = undefined`
|
||||
|
||||
## Tips on Using Vue Router
|
||||
|
||||
The qiankun main app activates the corresponding micro app according to the `activeRule` configuration.
|
||||
|
||||
### a. The main app is using Vue Router's hash mode
|
||||
|
||||
When the main app is in hash mode, generally, micro app is also in hash mode. In this case, the base hash path of the main app is assigned to the corresponding micro app (e.g. `#base`). At this time, if the micro app needs to make a secondary path jump in hash mode (such as `#/base1/child1`) when there is a base path, you just need to add a prefix for each route yourself.
|
||||
The base parameter in VueRouter's hash mode [does not support adding a hash path base](https://github.com/vuejs/vue-router/blob/dev/src/index.js#L55-L69).
|
||||
|
||||
### b. The main app is using Vue Router's history mode
|
||||
|
||||
When the main app is in history mode and the micro app is also in history mode, it works perfectly. And if the micro app needs to add a base path, just [set the base property](https://router.vuejs.org/api/#base) of the sub item.
|
||||
|
||||
When the main app is in history mode and the micro app is in hash mode, it works perfectly.
|
||||
|
||||
## Why dynamic imported assets missing?
|
||||
|
||||
Two way to solve that:
|
||||
|
||||
### 1. With webpack live public path config
|
||||
|
||||
qiankun will inject a live public path variable before your sub app bootstrap, what you need is to add this code at the top of your sub app entry js:
|
||||
|
||||
```js
|
||||
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
|
||||
```
|
||||
|
||||
For more details, check the [webpack doc](https://webpack.js.org/guides/public-path/#on-the-fly).
|
||||
|
||||
<Alert type="info">
|
||||
Runtime publicPath addresses the problem of incorrect scripts, styles, images, and other addresses for dynamically loaded in sub application.
|
||||
</Alert>
|
||||
|
||||
### 2. With webpack static public path config
|
||||
|
||||
You need to set your publicPath configuration to an absolute url, and in development with webpack it might be:
|
||||
|
||||
```js
|
||||
{
|
||||
output: {
|
||||
publicPath: `//localhost:${port}`;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Must a sub app asset support cors?
|
||||
|
||||
Yes it is.
|
||||
|
||||
Since qiankun get assets which imported by sub app via fetch, these static resources must be required to support [cors](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS).
|
||||
|
||||
See [Enable Nginx Cors](https://enable-cors.org/server_nginx.html).
|
||||
|
||||
## How to guarantee the main app stylesheet isolated with sub apps?
|
||||
|
||||
Qiankun will isolate stylesheet between your sub apps automatically, you can manually ensure isolation between master and child applications. Such as add a prefix to all classes in the master application, and if you are using [ant-design](https://ant.design), you can follow [this doc](https://ant.design/docs/react/customize-theme) to make it works.
|
||||
|
||||
## How to make sub app to run independently?
|
||||
|
||||
Use the builtin global variable to identify the environment which provided by qiankun master:
|
||||
|
||||
```js
|
||||
if (!window.__POWERED_BY_QIANKUN__) {
|
||||
render();
|
||||
}
|
||||
|
||||
export const mount = async () => render();
|
||||
```
|
||||
|
||||
## Could I active two sub apps at the same time?
|
||||
|
||||
When the subapp should be active depends on your `activeRule` config, like the example below, we set `activeRule` logic the same between `reactApp` and `react15App`:
|
||||
|
||||
```js {2,3,7}
|
||||
registerMicroApps([
|
||||
// define the activeRule by your self
|
||||
{ name: 'reactApp', entry: '//localhost:7100', container, activeRule: () => window.isReactApp },
|
||||
{ name: 'react15App', entry: '//localhost:7102', container, activeRule: () => window.isReactApp },
|
||||
{ name: 'vue app', entry: '//localhost:7101', container, activeRule: () => window.isVueApp },
|
||||
]);
|
||||
|
||||
start({ singular: false });
|
||||
```
|
||||
|
||||
After setting `singular: false` in `start` method, `reactApp` and `react15App` should be active at the same time once `isReactApp` method returns `true`.
|
||||
|
||||
<Alert>
|
||||
Notice that no more than one application that relies on router can be displayed on the page at the same time, as the browser has only one url location, if there is more than one routing apps, it will definitely result in one of them to be 404 found.
|
||||
</Alert>
|
||||
|
||||
## How to extract the common library?
|
||||
|
||||
> Don’t share a runtime, even if all teams use the same framework. - [Micro Frontends](https://micro-frontends.org/)
|
||||
|
||||
Although sharing dependencies isn't a good idea, but if you really need it, you can external the common dependencies from sub apps and then import them in master app.
|
||||
|
||||
In the future qiankun will provide a smarter way to make it automatically.
|
||||
|
||||
## Does qiankun compatible with ie?
|
||||
|
||||
Yes.
|
||||
|
||||
However, the IE environment (browsers that do not support Proxy) can only use the single-instance pattern, where the `singular` configuration will be set `true` automatically by qiankun if IE detected.
|
||||
|
||||
You can find the singular usage [here](/api#startopts).
|
||||
|
||||
### How to polyfill IE?
|
||||
|
||||
If you want qiankun (or its dependent libraries, or your own application) to work properly in IE, you need to introduce the following polyfills at the portal **at least**:
|
||||
|
||||
<Alert type="info">
|
||||
What's <a href="https://developer.mozilla.org/en-US/docs/Glossary/Polyfill" target="_blank">polyfill</a>
|
||||
</Alert>
|
||||
|
||||
```javascript
|
||||
import 'whatwg-fetch';
|
||||
import 'custom-event-polyfill';
|
||||
import 'core-js/stable/promise';
|
||||
import 'core-js/stable/symbol';
|
||||
import 'core-js/stable/string/starts-with';
|
||||
import 'core-js/web/url';
|
||||
```
|
||||
|
||||
**We recommend that you use @babel/preset-env plugin directly to polyfill IE automatically, all the instructions for @babel/preset-env you can found in [babel official document](https://babeljs.io/docs/en/babel-preset-env).**
|
||||
|
||||
## Error `Here is no "fetch" on the window env, you need to polyfill it`
|
||||
|
||||
Qiankun use `window.fetch` to get resources of the micro applications, but [some browsers does not support it](https://caniuse.com/#search=fetch), you should get the [polyfill](https://github.com/github/fetch) in the entry.
|
||||
|
||||
## Does qiankun support the subApp without bundler?
|
||||
|
||||
> Yes
|
||||
|
||||
The only change is that we need to declare a script tag, to export the `lifecycles`
|
||||
|
||||
example:
|
||||
|
||||
1. declare entry script
|
||||
|
||||
```diff
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Purehtml Example</title>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
Purehtml Example
|
||||
</div>
|
||||
</body>
|
||||
|
||||
+ <script src="//yourhost/entry.js" entry></script>
|
||||
</html>
|
||||
```
|
||||
|
||||
2. export lifecycles in the entry
|
||||
|
||||
```javascript
|
||||
const render = ($) => {
|
||||
$('#purehtml-container').html("Hello, render with jQuery");
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
(global => {
|
||||
global['purehtml'] = {
|
||||
bootstrap: () => {
|
||||
console.log('purehtml bootstrap');
|
||||
return Promise.resolve();
|
||||
},
|
||||
mount: () => {
|
||||
console.log('purehtml mount');
|
||||
return render($)
|
||||
},
|
||||
unmount: () => {
|
||||
console.log('purehtml unmount');
|
||||
return Promise.resolve();
|
||||
},
|
||||
};
|
||||
})(window);
|
||||
```
|
||||
|
||||
refer to the [purehtml examples](https://github.com/umijs/qiankun/tree/master/examples/purehtml)
|
||||
|
||||
At the same time, [the subApp must support the CORS](#must-a-sub-app-asset-support-cors)
|
||||
|
||||
## How to handle subapplication JSONP cross-domain errors?
|
||||
|
||||
qiankun will convert the dynamic script loading of the subapplication (such as JSONP) into a fetch request, so the corresponding back-end service needs to support cross-domain, otherwise it will cause an error.
|
||||
|
||||
In singular mode, you can use the `excludeAssetFilter` parameter to release this part of the resource request, but note that the resources released by this option will escape the sandbox, and the resulting side effects need to be handled by you.
|
||||
|
||||
If you use JSONP in not-singular mode, simply using `excludeAssetFilter` does not achieve good results, because each application is isolated by the sandbox; you can provide a unified JSONP tool in the main application, and the subapplication just calls the tool.
|
||||
337
docs/faq/README.zh.md
Normal file
337
docs/faq/README.zh.md
Normal file
|
|
@ -0,0 +1,337 @@
|
|||
---
|
||||
nav:
|
||||
title: 常见问题
|
||||
toc: menu
|
||||
---
|
||||
|
||||
# 常见问题
|
||||
|
||||
## `Application died in status LOADING_SOURCE_CODE: You need to export the functional lifecycles in xxx entry`
|
||||
|
||||
qiankun 抛出这个错误是因为无法从微应用的 entry js 中识别出其导出的生命周期钩子。
|
||||
|
||||
可以通过以下几个步骤解决这个问题:
|
||||
|
||||
1. 检查微应用是否已经导出相应的生命周期钩子,参考[文档](/zh/guide/getting-started#1-导出相应的生命周期钩子)。
|
||||
|
||||
2. 检查微应用的 webpack 是否增加了指定的配置,参考[文档](/zh/guide/getting-started#2-配置微应用的打包工具)。
|
||||
|
||||
3. 检查微应用的 `package.json` 中的 `name` 字段是否是微应用中唯一的。
|
||||
|
||||
4. 检查微应用的 entry html 中入口的 js 是不是最后一个加载的脚本。如果不是,需要移动顺序将其变成最后一个加载的 js,或者在 html 中将入口 js 手动标记为 `entry`,如:
|
||||
|
||||
```html {2}
|
||||
<script src="/antd.js"></script>
|
||||
<script src="/appEntry.js" entry></script>
|
||||
<script src="https://www.google.com/analytics.js"></script>
|
||||
```
|
||||
|
||||
如果在上述步骤完成后仍有问题,通常说明是浏览器兼容性问题导致的。可以尝试 **将有问题的微应用的 webpack `output.library` 配置成跟主应用中注册的 `name` 字段一致**,如:
|
||||
|
||||
假如主应用配置是这样的:
|
||||
|
||||
```ts {4}
|
||||
// 主应用
|
||||
registerMicroApps([
|
||||
{
|
||||
name: 'brokenSubApp',
|
||||
entry: '//localhost:7100',
|
||||
container: '#yourContainer',
|
||||
activeRule: '/react',
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
||||
将微应用的 `output.library` 改为跟主应用中注册的一致:
|
||||
|
||||
```js {4}
|
||||
module.exports = {
|
||||
output: {
|
||||
// 这里改成跟主应用中注册的一致
|
||||
library: 'brokenSubApp',
|
||||
libraryTarget: 'umd',
|
||||
jsonpFunction: `webpackJsonp_${packageName}`,
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Vue Router 报错 `Uncaught TypeError: Cannot redefine property: $router`
|
||||
|
||||
qiankun 中的代码使用 Proxy 去代理父页面的 window,来实现的沙箱,在微应用中访问 `window.Vue` 时,会先在自己的 window 里查找有没有 `Vue` 属性,如果没有就去父应用里查找。
|
||||
|
||||
在 VueRouter 的代码里有这样三行代码,会在模块加载的时候就访问 `window.Vue` 这个变量,子项目中报这个错,一般是由于父应用中的 Vue 挂载到了父应用的 `window` 对象上了。
|
||||
|
||||
```javascript
|
||||
if (inBrowser && window.Vue) {
|
||||
window.Vue.use(VueRouter)
|
||||
}
|
||||
```
|
||||
|
||||
可以从以下方式中选择一种来解决问题:
|
||||
|
||||
1. 在主应用中不使用 CDN 等 external 的方式来加载 `Vue` 框架,使用前端打包软件来加载模块
|
||||
2. 在主应用中,将 `window.Vue` 变量改个名称,例如 `window.Vue2 = window.Vue; window.Vue = undefined`
|
||||
|
||||
## Vue 框架下使用 Vue Router 的注意点
|
||||
|
||||
qiankun 主应用根据 `activeRule` 配置激活对应微应用。
|
||||
|
||||
### a. 主应用是 hash 模式
|
||||
|
||||
当主应用是 hash 模式时,一般微应用也是 hash 模式。主应用的一级 hash 路径会分配给对应的微应用(比如 `#/base1` ),此时微应用如果需要在 base 路径的基础上进行 hash 模式下的二级路径跳转(比如 `#/base1/child1` ),这个场景在当前 VueRouter 的实现方式下需要自己手动实现,给所有路由都添加一个前缀即可。VueRouter 的 hash 模式下的 base 参数[不支持添加 hash 路径 base](https://github.com/vuejs/vue-router/blob/dev/src/index.js#L55-L69)。
|
||||
|
||||
### b. 主应用是 history 模式
|
||||
|
||||
当主应用是 history 模式且微应用也是 history 模式时,表现完美。如果微应用需要添加 base 路径,设置子项目的 [base](https://router.vuejs.org/zh/api/#base) 属性即可。
|
||||
|
||||
当主应用是 history 模式,微应用是 hash 模式,表现完美。
|
||||
|
||||
## 为什么微应用加载的资源会 404?
|
||||
|
||||
原因是 webpack 加载资源时未使用正确的 `publicPath`。
|
||||
|
||||
可以通过以下两个方式解决这个问题:
|
||||
|
||||
### a. 使用 webpack 运行时 publicPath 配置
|
||||
|
||||
qiankun 将会在微应用 bootstrap 之前注入一个运行时的 publicPath 变量,你需要做的是在微应用的 entry js 的顶部添加如下代码:
|
||||
|
||||
```js
|
||||
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
|
||||
```
|
||||
|
||||
关于运行时 publicPath 的技术细节,可以参考 [webpack 文档](https://webpack.js.org/guides/public-path/#on-the-fly)。
|
||||
|
||||
<Alert type="info">
|
||||
runtime publicPath 主要解决的是微应用动态载入的 脚本、样式、图片 等地址不正确的问题。
|
||||
</Alert>
|
||||
|
||||
### b. 使用 webpack 静态 publicPath 配置
|
||||
|
||||
你需要将你的 webpack `publicPath` 配置设置成一个绝对地址的 url,比如在开发环境可能是:
|
||||
|
||||
```js
|
||||
{
|
||||
output: {
|
||||
publicPath: `//localhost:${port}`,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 微应用静态资源一定要支持跨域吗?
|
||||
|
||||
是的。
|
||||
|
||||
由于 qiankun 是通过 fetch 去获取微应用的引入的静态资源的,所以必须要求这些静态资源支持[跨域](https://developer.mozilla.org/zh/docs/Web/HTTP/Access_control_CORS)。
|
||||
|
||||
如果是自己的脚本,可以通过开发服务端跨域来支持。如果是三方脚本且无法为其添加跨域头,可以将脚本拖到本地,由自己的服务器 serve 来支持跨域。
|
||||
|
||||
参考:[Nginx 跨域配置](https://segmentfault.com/a/1190000012550346)
|
||||
|
||||
## 如何解决由于运营商动态插入的脚本加载异常导致微应用加载失败的问题
|
||||
|
||||
运营商插入的脚本通常会用 async 标记从而避免 block 微应用的加载,这种通常没问题,如:
|
||||
|
||||
```html
|
||||
<script async src="//www.rogue.com/rogue.js"></script>
|
||||
```
|
||||
|
||||
但如果有些插入的脚本不是被标记成 async 的,这类脚本一旦运行失败,将会导致整个应用被 block 且后续的脚本也不再执行。我们可以通过以下几个方式来解决这个问题:
|
||||
|
||||
### 使用自定义的 getTemplate 方法
|
||||
|
||||
通过自己实现的 getTemplate 方法过滤微应用 HTML 模板中的异常脚本
|
||||
|
||||
```js
|
||||
import { start } from 'qiankun';
|
||||
|
||||
start({
|
||||
getTemplate(tpl) {
|
||||
return tpl.replace('<script src="/to-be-replaced.js"><script>', '');
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 使用自定义的 fetch 方法
|
||||
|
||||
通过自己实现的 fetch 方法拦截有问题的脚本
|
||||
|
||||
```js
|
||||
import { start } from 'qiankun';
|
||||
|
||||
start({
|
||||
fetch(url, ...args) {
|
||||
if (url === 'http://to-be-replaced.js') {
|
||||
return {
|
||||
async text() { return '' }
|
||||
};
|
||||
}
|
||||
|
||||
return window.fetch(url, ...args);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 将微应用的 HTML 的 response content-type 改为 text/plain(终极方案)
|
||||
|
||||
原理是运营商只能识别 response content-type 为 text/html 的请求并插入脚本,text/plain 类型的响应则不会被劫持。
|
||||
|
||||
修改微应用 HTML 的 content-type 方法可以自行 google,也有一个更简单高效的方案:
|
||||
|
||||
1. 微应用发布时从 index.html 复制出一个 index.txt 文件出来
|
||||
|
||||
2. 将主应用中的 entry 改为 txt 地址,如:
|
||||
|
||||
```diff
|
||||
registerMicroApps(
|
||||
[
|
||||
- { name: 'app1', entry: '//localhost:8080/index.html', container, activeRule },
|
||||
+ { name: 'app1', entry: '//localhost:8080/index.txt', container, activeRule },
|
||||
],
|
||||
);
|
||||
```
|
||||
|
||||
## 如何确保主应用跟微应用之间的样式隔离
|
||||
|
||||
qiankun 将会自动隔离微应用之间的样式(开启沙箱的情况下),你可以通过手动的方式确保主应用与微应用之间的样式隔离。比如给主应用的所有样式添加一个前缀,或者假如你使用了 [ant-design](https://ant.design) 这样的组件库,你可以通过[这篇文档](https://ant.design/docs/react/customize-theme)中的配置方式给主应用样式自动添加指定的前缀。
|
||||
|
||||
## 如何独立运行微应用?
|
||||
|
||||
有些时候我们希望直接启动微应用从而更方便的开发调试,你可以使用这个全局变量来区分当前是否运行在 qiankun 的主应用的上下文中:
|
||||
|
||||
```ts
|
||||
if (!window.__POWERED_BY_QIANKUN__) {
|
||||
render();
|
||||
}
|
||||
|
||||
export const mount = async () => render();
|
||||
```
|
||||
|
||||
## 如何同时激活两个微应用?
|
||||
|
||||
微应用何时被激活完全取决于你的 `activeRule` 配置,比如下面的例子里,我们将 `reactApp` 和 `react15App` 的 `activeRule` 逻辑设置成一致的:
|
||||
|
||||
```js {2,3,7}
|
||||
registerMicroApps([
|
||||
{ name: 'reactApp', entry: '//localhost:7100', container, activeRule: () => isReactApp() },
|
||||
{ name: 'react15App', entry: '//localhost:7102', container, activeRule: () => isReactApp() },
|
||||
{ name: 'vueApp', entry: '//localhost:7101', container, activeRule: () => isVueApp() },
|
||||
]);
|
||||
|
||||
start({ singular: false });
|
||||
```
|
||||
|
||||
当在 `start` 方法中配置好 `singular: false` 后,只要 `isReactApp()` 返回 `true` 时,`reactApp` 和 `react15App` 将会同时被 mount。
|
||||
|
||||
<Alert>
|
||||
页面上不能同时显示多个依赖于路由的微应用,因为浏览器只有一个 url,如果有多个依赖路由的微应用同时被激活,那么必定会导致其中一个 404。
|
||||
</Alert>
|
||||
|
||||
## 如何提取出公共的依赖库?
|
||||
|
||||
> 不要共享运行时,即便所有的团队都是用同一个框架。- [微前端](https://micro-frontends.org/)
|
||||
|
||||
虽然共享依赖并不建议,但如果你真的有这个需求,你可以在微应用中将公共依赖配置成 `external`,然后在主应用中导入这些公共依赖。
|
||||
|
||||
qiankun 2.0 版本将提供一种更智能的方式使其自动化。
|
||||
|
||||
## qiankun 能兼容 ie 吗?
|
||||
|
||||
> 兼容.
|
||||
|
||||
但是 IE 环境下(不支持 Proxy 的浏览器)只能使用单实例模式,qiankun 会自动将 `singular` 配置为 `true`。
|
||||
|
||||
你可以在[这里](/zh/api#startopts)找到 singular 相关说明。
|
||||
|
||||
### 如何给 ie 打补丁?
|
||||
|
||||
如果希望 qiankun (或其依赖库、或者您的应用本身)在 IE 下正常运行,你**至少**需要在应用入口引入以下这些 polyfills:
|
||||
|
||||
<Alert type="info">
|
||||
什么是 <a href="https://developer.mozilla.org/zh-CN/docs/Glossary/Polyfill" target="_blank">polyfill</a>
|
||||
</Alert>
|
||||
|
||||
```javascript
|
||||
import 'whatwg-fetch';
|
||||
import 'custom-event-polyfill';
|
||||
import 'core-js/stable/promise';
|
||||
import 'core-js/stable/symbol';
|
||||
import 'core-js/stable/string/starts-with';
|
||||
import 'core-js/web/url';
|
||||
```
|
||||
|
||||
**通常我们建议您直接使用 @babel/preset-env 插件完成自动引入 IE 需要的 polyfill 的能力,所有的操作文档您都可以在 [babel 官方文档](https://babeljs.io/docs/en/babel-preset-env) 找到。**
|
||||
|
||||
<Alert type="info">
|
||||
您也可以查看<a href="https://www.yuque.com/kuitos/gky7yw/qskte2" target="_blank">这篇文章</a>来获取更多 IE 兼容相关的知识。
|
||||
</Alert>
|
||||
|
||||
## 报错 `Here is no "fetch" on the window env, you need to polyfill it`
|
||||
|
||||
qiankun 依赖的 import-html-entry 通过 `window.fetch` 来获取微应用的资源,部分[不支持 fetch 的浏览器](https://caniuse.com/#search=fetch)需要在入口处打上相应的 [polyfill](https://github.com/github/fetch)
|
||||
|
||||
## 非 webpack 构建的微应用支持接入 qiankun 么?
|
||||
|
||||
> 支持
|
||||
|
||||
需要额外声明一个 `script`,用于 `export` 相对应的 `lifecycles`
|
||||
|
||||
例如:
|
||||
|
||||
1. 声明 entry 入口
|
||||
|
||||
```diff
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Purehtml Example</title>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
Purehtml Example
|
||||
</div>
|
||||
</body>
|
||||
|
||||
+ <script src="//yourhost/entry.js" entry></script>
|
||||
</html>
|
||||
```
|
||||
|
||||
2. 在 entry js 里声明 lifecycles
|
||||
|
||||
```javascript
|
||||
const render = ($) => {
|
||||
$('#purehtml-container').html("Hello, render with jQuery");
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
(global => {
|
||||
global['purehtml'] = {
|
||||
bootstrap: () => {
|
||||
console.log('purehtml bootstrap');
|
||||
return Promise.resolve();
|
||||
},
|
||||
mount: () => {
|
||||
console.log('purehtml mount');
|
||||
return render($);
|
||||
},
|
||||
unmount: () => {
|
||||
console.log('purehtml unmount');
|
||||
return Promise.resolve();
|
||||
},
|
||||
};
|
||||
})(window);
|
||||
```
|
||||
|
||||
你也可以直接参照 examples 中 purehtml 部分的[代码](https://github.com/umijs/qiankun/tree/master/examples/purehtml)
|
||||
|
||||
同时,你也需要开启相关资源的 CORS,具体请参照[此处](#微应用静态资源一定要支持跨域吗?)
|
||||
|
||||
## 子应用 JSONP 跨域错误怎么处理?
|
||||
|
||||
qiankun 会将子应用的动态 script 加载(例如 JSONP)转化为 fetch 请求,因此需要相应的后端服务支持跨域,否则会导致错误。
|
||||
|
||||
在单实例模式下,你可以使用 `excludeAssetFilter` 参数来放行这部分资源请求,但是注意,被该选项放行的资源会逃逸出沙箱,由此带来的副作用需要你自行处理。
|
||||
|
||||
若在多实例模式下使用 JSONP,单纯使用 `excludeAssetFilter` 并不能取得好的效果,因为各应用被沙箱所隔离;你可以在主应用提供统一的 JSONP 工具,子应用调用主应用提供的该工具来曲线救国。
|
||||
70
docs/guide/README.md
Normal file
70
docs/guide/README.md
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
---
|
||||
nav:
|
||||
order: 0
|
||||
toc: menu
|
||||
---
|
||||
|
||||
# Introduction
|
||||
|
||||
Qiankun is an implementation of [Micro Frontends](https://micro-frontends.org/), which based on [single-spa](https://github.com/CanopyTax/single-spa). It aims to make it easier and painless to build a production-ready microfront-end architecture system.
|
||||
|
||||
Qiankun hatched from [Ant Financial](https://en.wikipedia.org/wiki/Ant_Financial)’s unified front-end platform for cloud products based on micro-frontends architecture. After full testing and polishing of a number of online applications, we extracted its micro-frontends kernel and open sourced it. We hope to help the systems who has the same requirement more convenient to build its own micro-frontends application in the community. At the same time, with the help of community, qiankun will be polished and improved.
|
||||
|
||||
At present qiankun has served more than 200 online applications inside Ant, and it is definitely trustworthy in terms of ease of use and completeness.
|
||||
|
||||
## What Are Micro FrontEnds
|
||||
|
||||
> Techniques, strategies and recipes for building a **modern web app** with **multiple teams** that can **ship features independently**. -- [Micro Frontends](https://micro-frontends.org/)
|
||||
|
||||
Micro Frontends architecture has the following core values:
|
||||
|
||||
- Technology Agnostic
|
||||
|
||||
The main framework does not restrict access to the technology stack of the application, and the sub-applications have full autonomy.
|
||||
|
||||
- Independent Development and Deployment
|
||||
|
||||
The sub application repo is independent, and the frontend and backend can be independently developed. After deployment, the main framework can be updated automatically.
|
||||
|
||||
- Incremental Upgrade
|
||||
|
||||
In the face of various complex scenarios, it is often difficult for us to upgrade or refactor the entire technology stack of an existing system. Micro frontends is a very good method and strategy for implementing progressive refactoring.
|
||||
|
||||
- Isolated Runtime
|
||||
|
||||
State is isolated between each subapplication and no shared runtime state.
|
||||
|
||||
The micro-frontends architecture is designed to solve the application of a single application in a relatively long time span. As a result of the increase in the number of people and teams involved, it has evolved from a common application to a [Frontend Monolith](https://www.youtube.com/watch?v=pU1gXA0rfwc) then becomes unmaintainable. Such a problem is especially common in enterprise web applications.
|
||||
|
||||
For more related introductions about micro frontends, I recommend that you check out these articles:
|
||||
|
||||
- [Micro Frontends](https://micro-frontends.org/)
|
||||
- [Micro Frontends from martinfowler.com](https://martinfowler.com/articles/micro-frontends.html)
|
||||
|
||||
## Core Design Philosophy Of qiankun
|
||||
|
||||
- 🥄 Simple
|
||||
|
||||
Since the main application sub-applications can be independent of the technology stack, qiankun is just a jQuery-like library for users. You need to call several qiankun APIs to complete the micro frontends transformation of your application. At the same time, due to the design of qiankun's HTML entry and sandbox, accessing sub-applications is as simple as using an iframe.
|
||||
|
||||
- 🍡 Decoupling/Technology Agnostic
|
||||
|
||||
As the core goal of the micro frontends is to disassemble the monolithic application into a number of loosely coupled micro applications that can be autonomous, all the designs of qiankun are follow this principle, such as HTML Entry, sandbox, and communicating mechanism between applications. Only in this way can we ensure that sub-applications truly have the ability to develop and run independently.
|
||||
|
||||
## How Does Qiankun Works
|
||||
|
||||
TODO
|
||||
|
||||
## Why Not Iframe
|
||||
|
||||
Check this article [Why Not Iframe](https://www.yuque.com/kuitos/gky7yw/gesexv)
|
||||
|
||||
## Features
|
||||
|
||||
- 📦 **Based On [single-spa](https://github.com/CanopyTax/single-spa)** , provide a more out-of-box APIs.
|
||||
- 📱 **Technology Agnostic**,any javascript framework can use/integrate, whether React/Vue/Angular/JQuery or the others.
|
||||
- 💪 **HTML Entry access mode**, allows you to access the son as simple application like use the iframe.
|
||||
- 🛡 **Style Isolation**, make sure styles don't interfere with each other.
|
||||
- 🧳 **JS Sandbox**, ensure that global variables/events do not conflict between sub-applications.
|
||||
- ⚡ **Prefetch Assets**, prefetch unopened sub-application assets during the browser idle time to speed up the sub-application opening speed.
|
||||
- 🔌 **Umi Plugin**, [@umijs/plugin-qiankun](https://github.com/umijs/plugins/tree/master/packages/plugin-qiankun) is provided for umi applications to switch to a micro frontends architecture system with one line code.
|
||||
72
docs/guide/README.zh.md
Normal file
72
docs/guide/README.zh.md
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
---
|
||||
nav:
|
||||
title: 指南
|
||||
order: 0
|
||||
toc: menu
|
||||
---
|
||||
|
||||
# 介绍
|
||||
|
||||
qiankun 是一个基于 [single-spa](https://github.com/CanopyTax/single-spa) 的[微前端](https://micro-frontends.org/)实现库,旨在帮助大家能更简单、无痛的构建一个生产可用微前端架构系统。
|
||||
|
||||
qiankun 孵化自蚂蚁金融科技基于微前端架构的云产品统一接入平台,在经过一批线上应用的充分检验及打磨后,我们将其微前端内核抽取出来并开源,希望能同时帮助社区有类似需求的系统更方便的构建自己的微前端系统,同时也希望通过社区的帮助将 qiankun 打磨的更加成熟完善。
|
||||
|
||||
目前 qiankun 已在蚂蚁内部服务了超过 200+ 线上应用,在易用性及完备性上,绝对是值得信赖的。
|
||||
|
||||
## 什么是微前端
|
||||
|
||||
> Techniques, strategies and recipes for building a **modern web app** with **multiple teams** that can **ship features independently**. -- [Micro Frontends](https://micro-frontends.org/)
|
||||
>
|
||||
> 微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。
|
||||
|
||||
微前端架构具备以下几个核心价值:
|
||||
|
||||
- 技术栈无关
|
||||
主框架不限制接入应用的技术栈,微应用具备完全自主权
|
||||
|
||||
- 独立开发、独立部署
|
||||
微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新
|
||||
|
||||
- 增量升级
|
||||
|
||||
在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略
|
||||
|
||||
- 独立运行时
|
||||
每个微应用之间状态隔离,运行时状态不共享
|
||||
|
||||
微前端架构旨在解决单体应用在一个相对长的时间跨度下,由于参与的人员、团队的增多、变迁,从一个普通应用演变成一个巨石应用([Frontend Monolith](https://www.youtube.com/watch?v=pU1gXA0rfwc))后,随之而来的应用不可维护的问题。这类问题在企业级 Web 应用中尤其常见。
|
||||
|
||||
更多关于微前端的相关介绍,推荐大家可以去看这几篇文章:
|
||||
|
||||
- [Micro Frontends](https://micro-frontends.org/)
|
||||
- [Micro Frontends from martinfowler.com](https://martinfowler.com/articles/micro-frontends.html)
|
||||
- [可能是你见过最完善的微前端解决方案](https://zhuanlan.zhihu.com/p/78362028)
|
||||
- [微前端的核心价值](https://zhuanlan.zhihu.com/p/95085796)
|
||||
|
||||
## qiankun 的核心设计理念
|
||||
|
||||
- 🥄 简单
|
||||
|
||||
由于主应用微应用都能做到技术栈无关,qiankun 对于用户而言只是一个类似 jQuery 的库,你需要调用几个 qiankun 的 API 即可完成应用的微前端改造。同时由于 qiankun 的 HTML entry 及沙箱的设计,使得微应用的接入像使用 iframe 一样简单。
|
||||
|
||||
- 🍡 解耦/技术栈无关
|
||||
|
||||
微前端的核心目标是将巨石应用拆解成若干可以自治的松耦合微应用,而 qiankun 的诸多设计均是秉持这一原则,如 HTML entry、沙箱、应用间通信等。这样才能确保微应用真正具备 独立开发、独立运行 的能力。
|
||||
|
||||
## 它是如何工作的
|
||||
|
||||
TODO
|
||||
|
||||
## 为什么不是 iframe
|
||||
|
||||
看这里 [Why Not Iframe](https://www.yuque.com/kuitos/gky7yw/gesexv)
|
||||
|
||||
## 特性
|
||||
|
||||
- 📦 **基于 [single-spa](https://github.com/CanopyTax/single-spa)** 封装,提供了更加开箱即用的 API。
|
||||
- 📱 **技术栈无关**,任意技术栈的应用均可 使用/接入,不论是 React/Vue/Angular/JQuery 还是其他等框架。
|
||||
- 💪 **HTML Entry 接入方式**,让你接入微应用像使用 iframe 一样简单。
|
||||
- 🛡 **样式隔离**,确保微应用之间样式互相不干扰。
|
||||
- 🧳 **JS 沙箱**,确保微应用之间 全局变量/事件 不冲突。
|
||||
- ⚡️ **资源预加载**,在浏览器空闲时间预加载未打开的微应用资源,加速微应用打开速度。
|
||||
- 🔌 **umi 插件**,提供了 [@umijs/plugin-qiankun](https://github.com/umijs/plugins/tree/master/packages/plugin-qiankun) 供 umi 应用一键切换成微前端架构系统。
|
||||
96
docs/guide/getting-started.md
Normal file
96
docs/guide/getting-started.md
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
---
|
||||
toc: menu
|
||||
---
|
||||
|
||||
# Getting Started
|
||||
|
||||
## Master Application
|
||||
### 1. Installation
|
||||
|
||||
```bash
|
||||
$ yarn add qiankun # or npm i qiankun -S
|
||||
```
|
||||
|
||||
### 2. Register Sub Apps In Master Application
|
||||
|
||||
```ts
|
||||
import { registerMicroApps, start } from 'qiankun';
|
||||
|
||||
registerMicroApps([
|
||||
{
|
||||
name: 'react app', // app name registered
|
||||
entry: '//localhost:7100',
|
||||
container: '#yourContainer',
|
||||
activeRule: '/yourActiveRule',
|
||||
},
|
||||
{
|
||||
name: 'vue app',
|
||||
entry: { scripts: ['//localhost:7100/main.js'] },
|
||||
container: '#yourContainer2',
|
||||
activeRule: '/yourActiveRule2',
|
||||
},
|
||||
]);
|
||||
|
||||
start();
|
||||
```
|
||||
|
||||
After the sub-application information is registered, the matching logic of the qiankun will be automatically triggered once the browser url changes, and all the render methods corresponding to the subapplications whose activeRule methods returns `true` will be called, at the same time the subapplications' exposed lifecycle hooks will be called in turn.
|
||||
|
||||
## Sub Application
|
||||
Sub applications do not need to install any additional dependencies to integrate to qiankun master application.
|
||||
### 1. Exports Lifecycles From Sub App Entry
|
||||
|
||||
The child application needs to export `bootstrap`,`mount`, `unmount` three lifecycle hooks in its own entry js (usually the entry js of webpack you configure) for the main application to call at the appropriate time.
|
||||
|
||||
```ts
|
||||
/**
|
||||
* The bootstrap will only be called once when the child application is initialized.
|
||||
* The next time the child application re-enters, the mount hook will be called directly, and bootstrap will not be triggered repeatedly.
|
||||
* Usually we can do some initialization of global variables here,
|
||||
* such as application-level caches that will not be destroyed during the unmount phase.
|
||||
*/
|
||||
export async function bootstrap() {
|
||||
console.log('react app bootstraped');
|
||||
}
|
||||
|
||||
/**
|
||||
* The mount method is called every time the application enters,
|
||||
* usually we trigger the application's rendering method here.
|
||||
*/
|
||||
export async function mount(props) {
|
||||
console.log(props);
|
||||
ReactDOM.render(<App />, document.getElementById('react15Root'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Methods that are called each time the application is switched/unloaded,
|
||||
* usually in this case we uninstall the application instance of the subapplication.
|
||||
*/
|
||||
export async function unmount() {
|
||||
ReactDOM.unmountComponentAtNode(document.getElementById('react15Root'));
|
||||
}
|
||||
```
|
||||
|
||||
As qiankun based on single-spa, you can find more documentation about the sub-application lifecycle [here](https://single-spa.js.org/docs/building-applications.html#registered-application-lifecycle).
|
||||
|
||||
Refer to [example without bundler](/faq#does-qiankun-support-the-subapp-without-bundler)
|
||||
|
||||
### 2. Config Sub App Bundler
|
||||
|
||||
In addition to exposing the corresponding life-cycle hooks in the code, in order for the main application to correctly identify some of the information exposed by the sub-application, the sub-application bundler needs to add the following configuration:
|
||||
|
||||
#### webpack:
|
||||
|
||||
```js
|
||||
const packageName = require('./package.json').name;
|
||||
|
||||
module.exports = {
|
||||
output: {
|
||||
library: `${packageName}-[name]`,
|
||||
libraryTarget: 'umd',
|
||||
jsonpFunction: `webpackJsonp_${packageName}`,
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
You can check the configuration description from [webpack doc](https://webpack.js.org/configuration/output/#outputlibrary)。
|
||||
115
docs/guide/getting-started.zh.md
Normal file
115
docs/guide/getting-started.zh.md
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
---
|
||||
toc: menu
|
||||
---
|
||||
|
||||
# 快速上手
|
||||
|
||||
## 主应用
|
||||
### 1. 安装 qiankun
|
||||
|
||||
```bash
|
||||
$ yarn add qiankun # 或者 npm i qiankun -S
|
||||
```
|
||||
|
||||
### 2. 在主应用中注册微应用
|
||||
|
||||
```ts
|
||||
import { registerMicroApps, start } from 'qiankun';
|
||||
|
||||
registerMicroApps([
|
||||
{
|
||||
name: 'react app', // app name registered
|
||||
entry: '//localhost:7100',
|
||||
container: '#yourContainer',
|
||||
activeRule: '/yourActiveRule',
|
||||
},
|
||||
{
|
||||
name: 'vue app',
|
||||
entry: { scripts: ['//localhost:7100/main.js'] },
|
||||
container: '#yourContainer2',
|
||||
activeRule: '/yourActiveRule2',
|
||||
},
|
||||
]);
|
||||
|
||||
start();
|
||||
```
|
||||
|
||||
当微应用信息注册完之后,一旦浏览器的 url 发生变化,便会自动触发 qiankun 的匹配逻辑,所有 activeRule 规则匹配上的微应用就会被插入到指定的 container 中,同时依次调用微应用暴露出的生命周期钩子。
|
||||
|
||||
如果微应用不是直接跟路由关联的时候,你也可以选择手动加载微应用的方式:
|
||||
|
||||
```ts
|
||||
import { loadMicroApp } from 'qiankun';
|
||||
|
||||
loadMicroApp(
|
||||
{
|
||||
name: 'app',
|
||||
entry: '//localhost:7100',
|
||||
container: '#yourContainer',
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
## 微应用
|
||||
微应用不需要额外安装任何其他依赖即可接入 qiankun 主应用。
|
||||
|
||||
### 1. 导出相应的生命周期钩子
|
||||
|
||||
微应用需要在自己的入口 js (通常就是你配置的 webpack 的 entry js) 导出 `bootstrap`、`mount`、`unmount` 三个生命周期钩子,以供主应用在适当的时机调用。
|
||||
|
||||
```ts
|
||||
/**
|
||||
* bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
|
||||
* 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
|
||||
*/
|
||||
export async function bootstrap() {
|
||||
console.log('react app bootstraped');
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
|
||||
*/
|
||||
export async function mount(props) {
|
||||
console.log(props);
|
||||
ReactDOM.render(<App />, document.getElementById('react15Root'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
|
||||
*/
|
||||
export async function unmount() {
|
||||
ReactDOM.unmountComponentAtNode(document.getElementById('react15Root'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效
|
||||
*/
|
||||
export async function update(props) {
|
||||
console.log('update props', props);
|
||||
}
|
||||
```
|
||||
|
||||
qiankun 基于 single-spa,所以你可以在[这里](https://single-spa.js.org/docs/building-applications.html#registered-application-lifecycle)找到更多关于微应用生命周期相关的文档说明。
|
||||
|
||||
|
||||
无 webpack 等构建工具的应用接入方式请见[这里](/zh/faq#非-webpack-构建的微应用支持接入-qiankun-么?)
|
||||
|
||||
### 2. 配置微应用的打包工具
|
||||
|
||||
除了代码中暴露出相应的生命周期钩子之外,为了让主应用能正确识别微应用暴露出来的一些信息,微应用的打包工具需要增加如下配置:
|
||||
|
||||
#### webpack:
|
||||
|
||||
```js
|
||||
const packageName = require('./package.json').name;
|
||||
|
||||
module.exports = {
|
||||
output: {
|
||||
library: `${packageName}-[name]`,
|
||||
libraryTarget: 'umd',
|
||||
jsonpFunction: `webpackJsonp_${packageName}`,
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
相关配置介绍可以查看 [webpack 相关文档](https://webpack.js.org/configuration/output/#outputlibrary)。
|
||||
13
examples/angular9/.editorconfig
Normal file
13
examples/angular9/.editorconfig
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# Editor configuration, see https://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
max_line_length = off
|
||||
trim_trailing_whitespace = false
|
||||
46
examples/angular9/.gitignore
vendored
Normal file
46
examples/angular9/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# compiled output
|
||||
/dist
|
||||
/tmp
|
||||
/out-tsc
|
||||
# Only exists if Bazel was run
|
||||
/bazel-out
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# profiling files
|
||||
chrome-profiler-events*.json
|
||||
speed-measure-plugin*.json
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# IDE - VSCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
.history/*
|
||||
|
||||
# misc
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
/coverage
|
||||
/libpeerconnection.log
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
testem.log
|
||||
/typings
|
||||
|
||||
# System Files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
27
examples/angular9/README.md
Normal file
27
examples/angular9/README.md
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# Angular9
|
||||
|
||||
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 9.0.3.
|
||||
|
||||
## Development server
|
||||
|
||||
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
|
||||
|
||||
## Code scaffolding
|
||||
|
||||
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
|
||||
|
||||
## Build
|
||||
|
||||
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
||||
|
||||
## Running end-to-end tests
|
||||
|
||||
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
|
||||
|
||||
## Further help
|
||||
|
||||
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
|
||||
115
examples/angular9/angular.json
Normal file
115
examples/angular9/angular.json
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"cli": {
|
||||
"packageManager": "yarn"
|
||||
},
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"angular9": {
|
||||
"projectType": "application",
|
||||
"schematics": {},
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"prefix": "angular9",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-builders/custom-webpack:browser",
|
||||
"options": {
|
||||
"outputPath": "dist/angular9",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.qiankun.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"aot": true,
|
||||
"assets": ["src/favicon.ico", "src/assets"],
|
||||
"styles": ["src/styles.css"],
|
||||
"scripts": [],
|
||||
"customWebpackConfig": {
|
||||
"path": "./extra-webpack.config.js"
|
||||
}
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"extractCss": true,
|
||||
"namedChunks": false,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
"buildOptimizer": true,
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "2mb",
|
||||
"maximumError": "5mb"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "6kb",
|
||||
"maximumError": "10kb"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-builders/custom-webpack:dev-server",
|
||||
"options": {
|
||||
"browserTarget": "angular9:build"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "angular9:build:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "angular9: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",
|
||||
"assets": ["src/favicon.ico", "src/assets"],
|
||||
"styles": ["src/styles.css"],
|
||||
"scripts": []
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
"tsConfig": ["tsconfig.app.json", "tsconfig.spec.json", "e2e/tsconfig.json"],
|
||||
"exclude": ["**/node_modules/**"]
|
||||
}
|
||||
},
|
||||
"e2e": {
|
||||
"builder": "@angular-devkit/build-angular:protractor",
|
||||
"options": {
|
||||
"protractorConfig": "e2e/protractor.conf.js",
|
||||
"devServerTarget": "angular9:serve"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"devServerTarget": "angular9:serve:production"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultProject": "angular9"
|
||||
}
|
||||
12
examples/angular9/browserslist
Normal file
12
examples/angular9/browserslist
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
|
||||
# For additional information regarding the format and rule options, please see:
|
||||
# https://github.com/browserslist/browserslist#queries
|
||||
|
||||
# You can see what browsers were selected by your queries by running:
|
||||
# npx browserslist
|
||||
|
||||
> 0.5%
|
||||
last 2 versions
|
||||
Firefox ESR
|
||||
not dead
|
||||
not IE 9-11 # For IE 9-11 support, remove 'not'.
|
||||
30
examples/angular9/e2e/protractor.conf.js
Normal file
30
examples/angular9/e2e/protractor.conf.js
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
// @ts-check
|
||||
// Protractor configuration file, see link for more information
|
||||
// https://github.com/angular/protractor/blob/master/lib/config.ts
|
||||
|
||||
const { SpecReporter } = require('jasmine-spec-reporter');
|
||||
|
||||
/**
|
||||
* @type { import("protractor").Config }
|
||||
*/
|
||||
exports.config = {
|
||||
allScriptsTimeout: 11000,
|
||||
specs: ['./src/**/*.e2e-spec.ts'],
|
||||
capabilities: {
|
||||
browserName: 'chrome',
|
||||
},
|
||||
directConnect: true,
|
||||
baseUrl: 'http://localhost:7103/',
|
||||
framework: 'jasmine',
|
||||
jasmineNodeOpts: {
|
||||
showColors: true,
|
||||
defaultTimeoutInterval: 30000,
|
||||
print: function() {},
|
||||
},
|
||||
onPrepare() {
|
||||
require('ts-node').register({
|
||||
project: require('path').join(__dirname, './tsconfig.json'),
|
||||
});
|
||||
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
|
||||
},
|
||||
};
|
||||
28
examples/angular9/e2e/src/app.e2e-spec.ts
Normal file
28
examples/angular9/e2e/src/app.e2e-spec.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import { AppPage } from './app.po';
|
||||
import { browser, logging } from 'protractor';
|
||||
|
||||
describe('workspace-project App', () => {
|
||||
let page: AppPage;
|
||||
|
||||
beforeEach(() => {
|
||||
page = new AppPage();
|
||||
});
|
||||
|
||||
it('should display welcome message', () => {
|
||||
page.navigateTo();
|
||||
expect(page.getTitleText()).toEqual('angular9 app is running!');
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
// Assert that there are no errors emitted from the browser
|
||||
const logs = await browser
|
||||
.manage()
|
||||
.logs()
|
||||
.get(logging.Type.BROWSER);
|
||||
expect(logs).not.toContain(
|
||||
jasmine.objectContaining({
|
||||
level: logging.Level.SEVERE,
|
||||
} as logging.Entry),
|
||||
);
|
||||
});
|
||||
});
|
||||
11
examples/angular9/e2e/src/app.po.ts
Normal file
11
examples/angular9/e2e/src/app.po.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { browser, by, element } from 'protractor';
|
||||
|
||||
export class AppPage {
|
||||
navigateTo(): Promise<unknown> {
|
||||
return browser.get(browser.baseUrl) as Promise<unknown>;
|
||||
}
|
||||
|
||||
getTitleText(): Promise<string> {
|
||||
return element(by.css('app-root .content span')).getText() as Promise<string>;
|
||||
}
|
||||
}
|
||||
9
examples/angular9/e2e/tsconfig.json
Normal file
9
examples/angular9/e2e/tsconfig.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../out-tsc/e2e",
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"types": ["jasmine", "jasminewd2", "node"]
|
||||
}
|
||||
}
|
||||
19
examples/angular9/extra-webpack.config.js
Normal file
19
examples/angular9/extra-webpack.config.js
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
const singleSpaAngularWebpack = require('single-spa-angular/lib/webpack').default;
|
||||
const webpackMerge = require('webpack-merge');
|
||||
const { name } = require('./package');
|
||||
|
||||
module.exports = (angularWebpackConfig, options) => {
|
||||
const singleSpaWebpackConfig = singleSpaAngularWebpack(angularWebpackConfig, options);
|
||||
|
||||
const singleSpaConfig = {
|
||||
output: {
|
||||
library: `${name}-[name]`,
|
||||
libraryTarget: 'umd',
|
||||
},
|
||||
externals: {
|
||||
'zone.js': 'Zone',
|
||||
},
|
||||
};
|
||||
const mergedConfig = webpackMerge.smart(singleSpaWebpackConfig, singleSpaConfig);
|
||||
return mergedConfig;
|
||||
};
|
||||
32
examples/angular9/karma.conf.js
Normal file
32
examples/angular9/karma.conf.js
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
// Karma configuration file, see link for more information
|
||||
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
||||
|
||||
module.exports = function(config) {
|
||||
config.set({
|
||||
basePath: '',
|
||||
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||
plugins: [
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-jasmine-html-reporter'),
|
||||
require('karma-coverage-istanbul-reporter'),
|
||||
require('@angular-devkit/build-angular/plugins/karma'),
|
||||
],
|
||||
client: {
|
||||
clearContext: false, // leave Jasmine Spec Runner output visible in browser
|
||||
},
|
||||
coverageIstanbulReporter: {
|
||||
dir: require('path').join(__dirname, './coverage/angular9'),
|
||||
reports: ['html', 'lcovonly', 'text-summary'],
|
||||
fixWebpackSourcePaths: true,
|
||||
},
|
||||
reporters: ['progress', 'kjhtml'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['Chrome'],
|
||||
singleRun: false,
|
||||
restartOnFileChange: true,
|
||||
});
|
||||
};
|
||||
51
examples/angular9/package.json
Normal file
51
examples/angular9/package.json
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"name": "angular9",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"test": "ng test",
|
||||
"lint": "ng lint",
|
||||
"e2e": "ng e2e",
|
||||
"build:qiankun": "ng build --prod --deploy-url http://localhost:7103/",
|
||||
"serve:qiankun": "ng serve --disable-host-check --port 7103 --base-href /angular9 --live-reload false"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular-builders/custom-webpack": "^9.2.0",
|
||||
"@angular/animations": "~9.0.2",
|
||||
"@angular/common": "~9.0.2",
|
||||
"@angular/compiler": "~9.0.2",
|
||||
"@angular/core": "~9.0.2",
|
||||
"@angular/forms": "~9.0.2",
|
||||
"@angular/platform-browser": "~9.0.2",
|
||||
"@angular/platform-browser-dynamic": "~9.0.2",
|
||||
"@angular/router": "~9.0.2",
|
||||
"rxjs": "~6.5.4",
|
||||
"single-spa-angular": "^4.3.1",
|
||||
"tslib": "^1.10.0",
|
||||
"zone.js": "~0.10.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "~0.900.3",
|
||||
"@angular/cli": "~9.0.3",
|
||||
"@angular/compiler-cli": "~9.0.2",
|
||||
"@angular/language-service": "~9.0.2",
|
||||
"@types/jasmine": "~3.5.0",
|
||||
"@types/jasminewd2": "~2.0.3",
|
||||
"@types/node": "^12.11.1",
|
||||
"codelyzer": "^5.1.2",
|
||||
"jasmine-core": "~3.5.0",
|
||||
"jasmine-spec-reporter": "~4.2.1",
|
||||
"karma": "~4.3.0",
|
||||
"karma-chrome-launcher": "~3.1.0",
|
||||
"karma-coverage-istanbul-reporter": "~2.1.0",
|
||||
"karma-jasmine": "~2.0.1",
|
||||
"karma-jasmine-html-reporter": "^1.4.2",
|
||||
"protractor": "~5.4.3",
|
||||
"ts-node": "~8.3.0",
|
||||
"tslint": "~5.18.0",
|
||||
"typescript": "~3.7.5"
|
||||
}
|
||||
}
|
||||
10
examples/angular9/src/app/app-routing.module.ts
Normal file
10
examples/angular9/src/app/app-routing.module.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
|
||||
const routes: Routes = [];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forRoot(routes)],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class AppRoutingModule {}
|
||||
0
examples/angular9/src/app/app.component.css
Normal file
0
examples/angular9/src/app/app.component.css
Normal file
534
examples/angular9/src/app/app.component.html
Normal file
534
examples/angular9/src/app/app.component.html
Normal file
|
|
@ -0,0 +1,534 @@
|
|||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * * The content below * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * is only a placeholder * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * and can be replaced. * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * Delete the template below * * * * * * * * * * -->
|
||||
<!-- * * * * * * * to get started with your project! * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
||||
|
||||
<style>
|
||||
:host {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
box-sizing: border-box;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #1976d2;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.toolbar img {
|
||||
margin: 0 16px;
|
||||
}
|
||||
|
||||
.toolbar #twitter-logo {
|
||||
height: 40px;
|
||||
margin: 0 16px;
|
||||
}
|
||||
|
||||
.toolbar #twitter-logo:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
margin: 82px auto 32px;
|
||||
padding: 0 16px;
|
||||
max-width: 960px;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
svg.material-icons {
|
||||
height: 24px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
svg.material-icons:not(:last-child) {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.card svg.material-icons path {
|
||||
fill: #888;
|
||||
}
|
||||
|
||||
.card-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.card {
|
||||
border-radius: 4px;
|
||||
border: 1px solid #eee;
|
||||
background-color: #fafafa;
|
||||
height: 40px;
|
||||
width: 200px;
|
||||
margin: 0 8px 16px;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transition: all 0.2s ease-in-out;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.card-container .card:not(:last-child) {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.card.card-small {
|
||||
height: 16px;
|
||||
width: 168px;
|
||||
}
|
||||
|
||||
.card-container .card:not(.highlight-card) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.card-container .card:not(.highlight-card):hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 4px 17px rgba(black, 0.35);
|
||||
}
|
||||
|
||||
.card-container .card:not(.highlight-card):hover .material-icons path {
|
||||
fill: rgb(105, 103, 103);
|
||||
}
|
||||
|
||||
.card.highlight-card {
|
||||
background-color: #1976d2;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
width: auto;
|
||||
min-width: 30%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.card.card.highlight-card span {
|
||||
margin-left: 60px;
|
||||
}
|
||||
|
||||
svg#rocket {
|
||||
width: 80px;
|
||||
position: absolute;
|
||||
left: -10px;
|
||||
top: -24px;
|
||||
}
|
||||
|
||||
svg#rocket-smoke {
|
||||
height: calc(100vh - 95px);
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 180px;
|
||||
z-index: -10;
|
||||
}
|
||||
|
||||
a,
|
||||
a:visited,
|
||||
a:hover {
|
||||
color: #1976d2;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #125699;
|
||||
}
|
||||
|
||||
.terminal {
|
||||
position: relative;
|
||||
width: 80%;
|
||||
max-width: 600px;
|
||||
border-radius: 6px;
|
||||
padding-top: 45px;
|
||||
margin-top: 8px;
|
||||
overflow: hidden;
|
||||
background-color: rgb(15, 15, 16);
|
||||
}
|
||||
|
||||
.terminal::before {
|
||||
content: "\2022 \2022 \2022";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 4px;
|
||||
background: rgb(58, 58, 58);
|
||||
color: #c2c3c4;
|
||||
width: 100%;
|
||||
font-size: 2rem;
|
||||
line-height: 0;
|
||||
padding: 14px 0;
|
||||
text-indent: 4px;
|
||||
}
|
||||
|
||||
.terminal pre {
|
||||
font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;
|
||||
color: white;
|
||||
padding: 0 1rem 1rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.circle-link {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
border-radius: 40px;
|
||||
margin: 8px;
|
||||
background-color: white;
|
||||
border: 1px solid #eeeeee;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
|
||||
transition: 1s ease-out;
|
||||
}
|
||||
|
||||
.circle-link:hover {
|
||||
transform: translateY(-0.25rem);
|
||||
box-shadow: 0px 3px 15px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
footer a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.github-star-badge {
|
||||
color: #24292e;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
padding: 3px 10px;
|
||||
border: 1px solid rgba(27,31,35,.2);
|
||||
border-radius: 3px;
|
||||
background-image: linear-gradient(-180deg,#fafbfc,#eff3f6 90%);
|
||||
margin-left: 4px;
|
||||
font-weight: 600;
|
||||
font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;
|
||||
}
|
||||
|
||||
.github-star-badge:hover {
|
||||
background-image: linear-gradient(-180deg,#f0f3f6,#e6ebf1 90%);
|
||||
border-color: rgba(27,31,35,.35);
|
||||
background-position: -.5em;
|
||||
}
|
||||
|
||||
.github-star-badge .material-icons {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
svg#clouds {
|
||||
position: fixed;
|
||||
bottom: -160px;
|
||||
left: -230px;
|
||||
z-index: -10;
|
||||
width: 1920px;
|
||||
}
|
||||
|
||||
|
||||
/* Responsive Styles */
|
||||
@media screen and (max-width: 767px) {
|
||||
|
||||
.card-container > *:not(.circle-link) ,
|
||||
.terminal {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card:not(.highlight-card) {
|
||||
height: 16px;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.card.highlight-card span {
|
||||
margin-left: 72px;
|
||||
}
|
||||
|
||||
svg#rocket-smoke {
|
||||
right: 120px;
|
||||
transform: rotate(-5deg);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 575px) {
|
||||
svg#rocket-smoke {
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Toolbar -->
|
||||
<div class="toolbar" role="banner">
|
||||
<img
|
||||
width="40"
|
||||
alt="Angular Logo"
|
||||
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg=="
|
||||
/>
|
||||
<span>Welcome</span>
|
||||
<div class="spacer"></div>
|
||||
<a aria-label="Angular on twitter" target="_blank" rel="noopener" href="https://twitter.com/angular" title="Twitter">
|
||||
<svg id="twitter-logo" height="24" data-name="Logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400">
|
||||
<rect width="400" height="400" fill="none"/>
|
||||
<path d="M153.62,301.59c94.34,0,145.94-78.16,145.94-145.94,0-2.22,0-4.43-.15-6.63A104.36,104.36,0,0,0,325,122.47a102.38,102.38,0,0,1-29.46,8.07,51.47,51.47,0,0,0,22.55-28.37,102.79,102.79,0,0,1-32.57,12.45,51.34,51.34,0,0,0-87.41,46.78A145.62,145.62,0,0,1,92.4,107.81a51.33,51.33,0,0,0,15.88,68.47A50.91,50.91,0,0,1,85,169.86c0,.21,0,.43,0,.65a51.31,51.31,0,0,0,41.15,50.28,51.21,51.21,0,0,1-23.16.88,51.35,51.35,0,0,0,47.92,35.62,102.92,102.92,0,0,1-63.7,22A104.41,104.41,0,0,1,75,278.55a145.21,145.21,0,0,0,78.62,23" fill="#fff"/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="content" role="main">
|
||||
|
||||
<!-- Highlight Card -->
|
||||
<div class="card highlight-card card-small">
|
||||
|
||||
<svg id="rocket" alt="Rocket Ship" xmlns="http://www.w3.org/2000/svg" width="101.678" height="101.678" viewBox="0 0 101.678 101.678">
|
||||
<g id="Group_83" data-name="Group 83" transform="translate(-141 -696)">
|
||||
<circle id="Ellipse_8" data-name="Ellipse 8" cx="50.839" cy="50.839" r="50.839" transform="translate(141 696)" fill="#dd0031"/>
|
||||
<g id="Group_47" data-name="Group 47" transform="translate(165.185 720.185)">
|
||||
<path id="Path_33" data-name="Path 33" d="M3.4,42.615a3.084,3.084,0,0,0,3.553,3.553,21.419,21.419,0,0,0,12.215-6.107L9.511,30.4A21.419,21.419,0,0,0,3.4,42.615Z" transform="translate(0.371 3.363)" fill="#fff"/>
|
||||
<path id="Path_34" data-name="Path 34" d="M53.3,3.221A3.09,3.09,0,0,0,50.081,0,48.227,48.227,0,0,0,18.322,13.437c-6-1.666-14.991-1.221-18.322,7.218A33.892,33.892,0,0,1,9.439,25.1l-.333.666a3.013,3.013,0,0,0,.555,3.553L23.985,43.641a2.9,2.9,0,0,0,3.553.555l.666-.333A33.892,33.892,0,0,1,32.647,53.3c8.55-3.664,8.884-12.326,7.218-18.322A48.227,48.227,0,0,0,53.3,3.221ZM34.424,9.772a6.439,6.439,0,1,1,9.106,9.106,6.368,6.368,0,0,1-9.106,0A6.467,6.467,0,0,1,34.424,9.772Z" transform="translate(0 0.005)" fill="#fff"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
<span>{{ title }} app is running!</span>
|
||||
|
||||
<svg id="rocket-smoke" alt="Rocket Ship Smoke" xmlns="http://www.w3.org/2000/svg" width="516.119" height="1083.632" viewBox="0 0 516.119 1083.632">
|
||||
<path id="Path_40" data-name="Path 40" d="M644.6,141S143.02,215.537,147.049,870.207s342.774,201.755,342.774,201.755S404.659,847.213,388.815,762.2c-27.116-145.51-11.551-384.124,271.9-609.1C671.15,139.365,644.6,141,644.6,141Z" transform="translate(-147.025 -140.939)" fill="#f5f5f5"/>
|
||||
</svg>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Resources -->
|
||||
<h2>Resources</h2>
|
||||
<p>Here are some links to help you get started:</p>
|
||||
|
||||
<div class="card-container">
|
||||
<a class="card" target="_blank" rel="noopener" href="https://angular.io/tutorial">
|
||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M5 13.18v4L12 21l7-3.82v-4L12 17l-7-3.82zM12 3L1 9l11 6 9-4.91V17h2V9L12 3z"/></svg>
|
||||
|
||||
<span>Learn Angular</span>
|
||||
|
||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg> </a>
|
||||
|
||||
<a class="card" target="_blank" rel="noopener" href="https://angular.io/cli">
|
||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z"/></svg>
|
||||
|
||||
<span>CLI Documentation</span>
|
||||
|
||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>
|
||||
</a>
|
||||
|
||||
<a class="card" target="_blank" rel="noopener" href="https://blog.angular.io/">
|
||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M13.5.67s.74 2.65.74 4.8c0 2.06-1.35 3.73-3.41 3.73-2.07 0-3.63-1.67-3.63-3.73l.03-.36C5.21 7.51 4 10.62 4 14c0 4.42 3.58 8 8 8s8-3.58 8-8C20 8.61 17.41 3.8 13.5.67zM11.71 19c-1.78 0-3.22-1.4-3.22-3.14 0-1.62 1.05-2.76 2.81-3.12 1.77-.36 3.6-1.21 4.62-2.58.39 1.29.59 2.65.59 4.04 0 2.65-2.15 4.8-4.8 4.8z"/></svg>
|
||||
|
||||
<span>Angular Blog</span>
|
||||
|
||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Next Steps -->
|
||||
<h2>Next Steps</h2>
|
||||
<p>What do you want to do next with your app?</p>
|
||||
|
||||
<input type="hidden" #selection>
|
||||
|
||||
<div class="card-container">
|
||||
<div class="card card-small" (click)="selection.value = 'component'" tabindex="0">
|
||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
|
||||
|
||||
<span>New Component</span>
|
||||
</div>
|
||||
|
||||
<div class="card card-small" (click)="selection.value = 'material'" tabindex="0">
|
||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
|
||||
|
||||
<span>Angular Material</span>
|
||||
</div>
|
||||
|
||||
<div class="card card-small" (click)="selection.value = 'pwa'" tabindex="0">
|
||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
|
||||
|
||||
<span>Add PWA Support</span>
|
||||
</div>
|
||||
|
||||
<div class="card card-small" (click)="selection.value = 'dependency'" tabindex="0">
|
||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
|
||||
|
||||
<span>Add Dependency</span>
|
||||
</div>
|
||||
|
||||
<div class="card card-small" (click)="selection.value = 'test'" tabindex="0">
|
||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
|
||||
|
||||
<span>Run and Watch Tests</span>
|
||||
</div>
|
||||
|
||||
<div class="card card-small" (click)="selection.value = 'build'" tabindex="0">
|
||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
|
||||
|
||||
<span>Build for Production</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Terminal -->
|
||||
<div class="terminal" [ngSwitch]="selection.value">
|
||||
<pre *ngSwitchDefault>ng generate component xyz</pre>
|
||||
<pre *ngSwitchCase="'material'">ng add @angular/material</pre>
|
||||
<pre *ngSwitchCase="'pwa'">ng add @angular/pwa</pre>
|
||||
<pre *ngSwitchCase="'dependency'">ng add _____</pre>
|
||||
<pre *ngSwitchCase="'test'">ng test</pre>
|
||||
<pre *ngSwitchCase="'build'">ng build --prod</pre>
|
||||
</div>
|
||||
|
||||
<!-- Links -->
|
||||
<div class="card-container">
|
||||
<a class="circle-link" title="Animations" href="https://angular.io/guide/animations" target="_blank" rel="noopener">
|
||||
<svg id="Group_20" data-name="Group 20" xmlns="http://www.w3.org/2000/svg" width="21.813" height="23.453" viewBox="0 0 21.813 23.453">
|
||||
<path id="Path_15" data-name="Path 15" d="M4099.584,972.736h0l-10.882,3.9,1.637,14.4,9.245,5.153,9.245-5.153,1.686-14.4Z" transform="translate(-4088.702 -972.736)" fill="#ffa726"/>
|
||||
<path id="Path_16" data-name="Path 16" d="M4181.516,972.736v23.453l9.245-5.153,1.686-14.4Z" transform="translate(-4170.633 -972.736)" fill="#fb8c00"/>
|
||||
<path id="Path_17" data-name="Path 17" d="M4137.529,1076.127l-7.7-3.723,4.417-2.721,7.753,3.723Z" transform="translate(-4125.003 -1058.315)" fill="#ffe0b2"/>
|
||||
<path id="Path_18" data-name="Path 18" d="M4137.529,1051.705l-7.7-3.723,4.417-2.721,7.753,3.723Z" transform="translate(-4125.003 -1036.757)" fill="#fff3e0"/>
|
||||
<path id="Path_19" data-name="Path 19" d="M4137.529,1027.283l-7.7-3.723,4.417-2.721,7.753,3.723Z" transform="translate(-4125.003 -1015.199)" fill="#fff"/>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<a class="circle-link" title="CLI" href="https://cli.angular.io/" target="_blank" rel="noopener">
|
||||
<svg alt="Angular CLI Logo" xmlns="http://www.w3.org/2000/svg" width="21.762" height="23.447" viewBox="0 0 21.762 23.447">
|
||||
<g id="Group_21" data-name="Group 21" transform="translate(0)">
|
||||
<path id="Path_20" data-name="Path 20" d="M2660.313,313.618h0l-10.833,3.9,1.637,14.4,9.2,5.152,9.244-5.152,1.685-14.4Z" transform="translate(-2649.48 -313.618)" fill="#37474f"/>
|
||||
<path id="Path_21" data-name="Path 21" d="M2741.883,313.618v23.447l9.244-5.152,1.685-14.4Z" transform="translate(-2731.05 -313.618)" fill="#263238"/>
|
||||
<path id="Path_22" data-name="Path 22" d="M2692.293,379.169h11.724V368.618h-11.724Zm11.159-.6h-10.608v-9.345h10.621v9.345Z" transform="translate(-2687.274 -362.17)" fill="#fff"/>
|
||||
<path id="Path_23" data-name="Path 23" d="M2709.331,393.688l.4.416,2.265-2.28-2.294-2.294-.4.4,1.893,1.893Z" transform="translate(-2702.289 -380.631)" fill="#fff"/>
|
||||
<rect id="Rectangle_12" data-name="Rectangle 12" width="3.517" height="0.469" transform="translate(9.709 13.744)" fill="#fff"/>
|
||||
</g>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<a class="circle-link" title="Augury" href="https://augury.rangle.io/" target="_blank" rel="noopener">
|
||||
<svg alt="Angular Augury Logo" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="21.81" height="23.447" viewBox="0 0 21.81 23.447">
|
||||
<defs>
|
||||
<clipPath id="clip-path">
|
||||
<rect id="Rectangle_13" data-name="Rectangle 13" width="10.338" height="10.27" fill="none"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g id="Group_25" data-name="Group 25" transform="translate(0)">
|
||||
<path id="Path_24" data-name="Path 24" d="M3780.155,311.417h0l-10.881,3.9,1.637,14.4,9.244,5.152,9.244-5.152,1.685-14.4Z" transform="translate(-3769.274 -311.417)" fill="#4a3493"/>
|
||||
<path id="Path_25" data-name="Path 25" d="M3862.088,311.417v23.447l9.244-5.152,1.685-14.4Z" transform="translate(-3851.207 -311.417)" fill="#311b92"/>
|
||||
<g id="Group_24" data-name="Group 24" transform="translate(6.194 6.73)" opacity="0.5">
|
||||
<g id="Group_23" data-name="Group 23" transform="translate(0 0)">
|
||||
<g id="Group_22" data-name="Group 22" clip-path="url(#clip-path)">
|
||||
<path id="Path_26" data-name="Path 26" d="M3832.4,373.252a5.168,5.168,0,1,1-5.828-4.383,5.216,5.216,0,0,1,2.574.3,3.017,3.017,0,1,0,3.252,4.086Z" transform="translate(-3822.107 -368.821)" fill="#fff"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<path id="Path_27" data-name="Path 27" d="M3830.582,370.848a5.162,5.162,0,1,1-3.254-4.086,3.017,3.017,0,1,0,3.252,4.086Z" transform="translate(-3814.311 -359.969)" fill="#fff"/>
|
||||
</g>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<a class="circle-link" title="Protractor" href="https://www.protractortest.org/" target="_blank" rel="noopener">
|
||||
<svg alt="Angular Protractor Logo" xmlns="http://www.w3.org/2000/svg" width="21.81" height="23.447" viewBox="0 0 21.81 23.447">
|
||||
<g id="Group_26" data-name="Group 26" transform="translate(0)">
|
||||
<path id="Path_28" data-name="Path 28" d="M4620.155,311.417h0l-10.881,3.9,1.637,14.4,9.244,5.152,9.244-5.152,1.685-14.4Z" transform="translate(-4609.274 -311.417)" fill="#e13439"/>
|
||||
<path id="Path_29" data-name="Path 29" d="M4702.088,311.417v23.447l9.244-5.152,1.685-14.4Z" transform="translate(-4691.207 -311.417)" fill="#b52f32"/>
|
||||
<path id="Path_30" data-name="Path 30" d="M4651.044,369.58v-.421h1.483a7.6,7.6,0,0,0-2.106-5.052l-1.123,1.123-.3-.3,1.122-1.121a7.588,7.588,0,0,0-4.946-2.055v1.482h-.421v-1.485a7.589,7.589,0,0,0-5.051,2.058l1.122,1.121-.3.3-1.123-1.123a7.591,7.591,0,0,0-2.106,5.052h1.482v.421h-1.489v1.734h15.241V369.58Zm-10.966-.263a4.835,4.835,0,0,1,9.67,0Z" transform="translate(-4634.008 -355.852)" fill="#fff"/>
|
||||
</g>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<a class="circle-link" title="Find a Local Meetup" href="https://www.meetup.com/find/?keywords=angular" target="_blank" rel="noopener">
|
||||
<svg alt="Meetup Logo" xmlns="http://www.w3.org/2000/svg" width="24.607" height="23.447" viewBox="0 0 24.607 23.447">
|
||||
<path id="logo--mSwarm" d="M21.221,14.95A4.393,4.393,0,0,1,17.6,19.281a4.452,4.452,0,0,1-.8.069c-.09,0-.125.035-.154.117a2.939,2.939,0,0,1-2.506,2.091,2.868,2.868,0,0,1-2.248-.624.168.168,0,0,0-.245-.005,3.926,3.926,0,0,1-2.589.741,4.015,4.015,0,0,1-3.7-3.347,2.7,2.7,0,0,1-.043-.38c0-.106-.042-.146-.143-.166a3.524,3.524,0,0,1-1.516-.69A3.623,3.623,0,0,1,2.23,14.557a3.66,3.66,0,0,1,1.077-3.085.138.138,0,0,0,.026-.2,3.348,3.348,0,0,1-.451-1.821,3.46,3.46,0,0,1,2.749-3.28.44.44,0,0,0,.355-.281,5.072,5.072,0,0,1,3.863-3,5.028,5.028,0,0,1,3.555.666.31.31,0,0,0,.271.03A4.5,4.5,0,0,1,18.3,4.7a4.4,4.4,0,0,1,1.334,2.751,3.658,3.658,0,0,1,.022.706.131.131,0,0,0,.1.157,2.432,2.432,0,0,1,1.574,1.645,2.464,2.464,0,0,1-.7,2.616c-.065.064-.051.1-.014.166A4.321,4.321,0,0,1,21.221,14.95ZM13.4,14.607a2.09,2.09,0,0,0,1.409,1.982,4.7,4.7,0,0,0,1.275.221,1.807,1.807,0,0,0,.9-.151.542.542,0,0,0,.321-.545.558.558,0,0,0-.359-.534,1.2,1.2,0,0,0-.254-.078c-.262-.047-.526-.086-.787-.138a.674.674,0,0,1-.617-.75,3.394,3.394,0,0,1,.218-1.109c.217-.658.509-1.286.79-1.918a15.609,15.609,0,0,0,.745-1.86,1.95,1.95,0,0,0,.06-1.073,1.286,1.286,0,0,0-1.051-1.033,1.977,1.977,0,0,0-1.521.2.339.339,0,0,1-.446-.042c-.1-.092-.2-.189-.307-.284a1.214,1.214,0,0,0-1.643-.061,7.563,7.563,0,0,1-.614.512A.588.588,0,0,1,10.883,8c-.215-.115-.437-.215-.659-.316a2.153,2.153,0,0,0-.695-.248A2.091,2.091,0,0,0,7.541,8.562a9.915,9.915,0,0,0-.405.986c-.559,1.545-1.015,3.123-1.487,4.7a1.528,1.528,0,0,0,.634,1.777,1.755,1.755,0,0,0,1.5.211,1.35,1.35,0,0,0,.824-.858c.543-1.281,1.032-2.584,1.55-3.875.142-.355.28-.712.432-1.064a.548.548,0,0,1,.851-.24.622.622,0,0,1,.185.539,2.161,2.161,0,0,1-.181.621c-.337.852-.68,1.7-1.018,2.552a2.564,2.564,0,0,0-.173.528.624.624,0,0,0,.333.71,1.073,1.073,0,0,0,.814.034,1.22,1.22,0,0,0,.657-.655q.758-1.488,1.511-2.978.35-.687.709-1.37a1.073,1.073,0,0,1,.357-.434.43.43,0,0,1,.463-.016.373.373,0,0,1,.153.387.7.7,0,0,1-.057.236c-.065.157-.127.316-.2.469-.42.883-.846,1.763-1.262,2.648A2.463,2.463,0,0,0,13.4,14.607Zm5.888,6.508a1.09,1.09,0,0,0-2.179.006,1.09,1.09,0,0,0,2.179-.006ZM1.028,12.139a1.038,1.038,0,1,0,.01-2.075,1.038,1.038,0,0,0-.01,2.075ZM13.782.528a1.027,1.027,0,1,0-.011,2.055A1.027,1.027,0,0,0,13.782.528ZM22.21,6.95a.882.882,0,0,0-1.763.011A.882.882,0,0,0,22.21,6.95ZM4.153,4.439a.785.785,0,1,0,.787-.78A.766.766,0,0,0,4.153,4.439Zm8.221,18.22a.676.676,0,1,0-.677.666A.671.671,0,0,0,12.374,22.658ZM22.872,12.2a.674.674,0,0,0-.665.665.656.656,0,0,0,.655.643.634.634,0,0,0,.655-.644A.654.654,0,0,0,22.872,12.2ZM7.171-.123A.546.546,0,0,0,6.613.43a.553.553,0,1,0,1.106,0A.539.539,0,0,0,7.171-.123ZM24.119,9.234a.507.507,0,0,0-.493.488.494.494,0,0,0,.494.494.48.48,0,0,0,.487-.483A.491.491,0,0,0,24.119,9.234Zm-19.454,9.7a.5.5,0,0,0-.488-.488.491.491,0,0,0-.487.5.483.483,0,0,0,.491.479A.49.49,0,0,0,4.665,18.936Z" transform="translate(0 0.123)" fill="#f64060"/>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<a class="circle-link" title="Join the Conversation on Gitter" href="https://gitter.im/angular/angular" target="_blank" rel="noopener">
|
||||
<svg alt="Gitter Logo" xmlns="http://www.w3.org/2000/svg" width="19.447" height="19.447" viewBox="0 0 19.447 19.447">
|
||||
<g id="Group_40" data-name="Group 40" transform="translate(-1612 -405)">
|
||||
<rect id="Rectangle_19" data-name="Rectangle 19" width="19.447" height="19.447" transform="translate(1612 405)" fill="#e60257"/>
|
||||
<g id="gitter" transform="translate(1617.795 408.636)">
|
||||
<g id="Group_33" data-name="Group 33" transform="translate(0 0)">
|
||||
<rect id="Rectangle_15" data-name="Rectangle 15" width="1.04" height="9.601" transform="translate(2.304 2.324)" fill="#fff"/>
|
||||
<rect id="Rectangle_16" data-name="Rectangle 16" width="1.04" height="9.601" transform="translate(4.607 2.324)" fill="#fff"/>
|
||||
<rect id="Rectangle_17" data-name="Rectangle 17" width="1.04" height="4.648" transform="translate(6.91 2.324)" fill="#fff"/>
|
||||
<rect id="Rectangle_18" data-name="Rectangle 18" width="1.04" height="6.971" transform="translate(0 0)" fill="#fff"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer>
|
||||
Love Angular?
|
||||
<a href="https://github.com/angular/angular" target="_blank" rel="noopener"> Give our repo a star.
|
||||
<div class="github-star-badge">
|
||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"/></svg>
|
||||
Star
|
||||
</div>
|
||||
</a>
|
||||
<a href="https://github.com/angular/angular" target="_blank" rel="noopener">
|
||||
<svg class="material-icons" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" fill="#1976d2"/><path d="M0 0h24v24H0z" fill="none"/></svg>
|
||||
</a>
|
||||
</footer>
|
||||
|
||||
<svg id="clouds" alt="Gray Clouds Background" xmlns="http://www.w3.org/2000/svg" width="2611.084" height="485.677" viewBox="0 0 2611.084 485.677">
|
||||
<path id="Path_39" data-name="Path 39" d="M2379.709,863.793c10-93-77-171-168-149-52-114-225-105-264,15-75,3-140,59-152,133-30,2.83-66.725,9.829-93.5,26.25-26.771-16.421-63.5-23.42-93.5-26.25-12-74-77-130-152-133-39-120-212-129-264-15-54.084-13.075-106.753,9.173-138.488,48.9-31.734-39.726-84.4-61.974-138.487-48.9-52-114-225-105-264,15a162.027,162.027,0,0,0-103.147,43.044c-30.633-45.365-87.1-72.091-145.206-58.044-52-114-225-105-264,15-75,3-140,59-152,133-53,5-127,23-130,83-2,42,35,72,70,86,49,20,106,18,157,5a165.625,165.625,0,0,0,120,0c47,94,178,113,251,33,61.112,8.015,113.854-5.72,150.492-29.764a165.62,165.62,0,0,0,110.861-3.236c47,94,178,113,251,33,31.385,4.116,60.563,2.495,86.487-3.311,25.924,5.806,55.1,7.427,86.488,3.311,73,80,204,61,251-33a165.625,165.625,0,0,0,120,0c51,13,108,15,157-5a147.188,147.188,0,0,0,33.5-18.694,147.217,147.217,0,0,0,33.5,18.694c49,20,106,18,157,5a165.625,165.625,0,0,0,120,0c47,94,178,113,251,33C2446.709,1093.793,2554.709,922.793,2379.709,863.793Z" transform="translate(142.69 -634.312)" fill="#eee"/>
|
||||
</svg>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * * The content above * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * is only a placeholder * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * and can be replaced. * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * End of Placeholder * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
||||
|
||||
|
||||
|
||||
<router-outlet></router-outlet>
|
||||
31
examples/angular9/src/app/app.component.spec.ts
Normal file
31
examples/angular9/src/app/app.component.spec.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import { TestBed, async } from '@angular/core/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [RouterTestingModule],
|
||||
declarations: [AppComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
it('should create the app', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should have as title 'angular9'`, () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app.title).toEqual('angular9');
|
||||
});
|
||||
|
||||
it('should render title', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.nativeElement;
|
||||
expect(compiled.querySelector('.content span').textContent).toContain('angular9 app is running!');
|
||||
});
|
||||
});
|
||||
10
examples/angular9/src/app/app.component.ts
Normal file
10
examples/angular9/src/app/app.component.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.css'],
|
||||
})
|
||||
export class AppComponent {
|
||||
title = 'angular9';
|
||||
}
|
||||
13
examples/angular9/src/app/app.module.ts
Normal file
13
examples/angular9/src/app/app.module.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [AppComponent],
|
||||
imports: [BrowserModule, AppRoutingModule],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'angular9-empty-route',
|
||||
template: '',
|
||||
})
|
||||
export class EmptyRouteComponent {}
|
||||
0
examples/angular9/src/assets/.gitkeep
Normal file
0
examples/angular9/src/assets/.gitkeep
Normal file
3
examples/angular9/src/environments/environment.prod.ts
Normal file
3
examples/angular9/src/environments/environment.prod.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export const environment = {
|
||||
production: true,
|
||||
};
|
||||
16
examples/angular9/src/environments/environment.ts
Normal file
16
examples/angular9/src/environments/environment.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
// This file can be replaced during build by using the `fileReplacements` array.
|
||||
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
|
||||
// The list of file replacements can be found in `angular.json`.
|
||||
|
||||
export const environment = {
|
||||
production: false,
|
||||
};
|
||||
|
||||
/*
|
||||
* For easier debugging in development mode, you can import the following file
|
||||
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
|
||||
*
|
||||
* This import should be commented out in production mode because it will have a negative impact
|
||||
* on performance if an error is thrown.
|
||||
*/
|
||||
// import 'zone.js/dist/zone-error'; // Included with Angular CLI.
|
||||
BIN
examples/angular9/src/favicon.ico
Normal file
BIN
examples/angular9/src/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 948 B |
14
examples/angular9/src/index.html
Normal file
14
examples/angular9/src/index.html
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>qiankun-angular9</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"></script> -->
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
||||
30
examples/angular9/src/main.qiankun.ts
Normal file
30
examples/angular9/src/main.qiankun.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import { enableProdMode, NgZone } from '@angular/core';
|
||||
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
import { Router } from '@angular/router';
|
||||
import { AppModule } from './app/app.module';
|
||||
import { environment } from './environments/environment';
|
||||
import { singleSpaAngular, getSingleSpaExtraProviders } from 'single-spa-angular';
|
||||
import { singleSpaPropsSubject } from './single-spa/single-spa-props';
|
||||
|
||||
if (environment.production) {
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
if (!(window as any).__POWERED_BY_QIANKUN__) {
|
||||
platformBrowserDynamic()
|
||||
.bootstrapModule(AppModule)
|
||||
.catch(err => console.error(err));
|
||||
}
|
||||
|
||||
const { bootstrap, mount, unmount } = singleSpaAngular({
|
||||
bootstrapFunction: singleSpaProps => {
|
||||
singleSpaPropsSubject.next(singleSpaProps);
|
||||
return platformBrowserDynamic(getSingleSpaExtraProviders()).bootstrapModule(AppModule);
|
||||
},
|
||||
template: '<app-root />',
|
||||
Router,
|
||||
NgZone: NgZone,
|
||||
});
|
||||
|
||||
export { bootstrap, mount, unmount };
|
||||
62
examples/angular9/src/polyfills.ts
Normal file
62
examples/angular9/src/polyfills.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
/**
|
||||
* This file includes polyfills needed by Angular and is loaded before the app.
|
||||
* You can add your own extra polyfills to this file.
|
||||
*
|
||||
* This file is divided into 2 sections:
|
||||
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
|
||||
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
|
||||
* file.
|
||||
*
|
||||
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
|
||||
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
|
||||
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
|
||||
*
|
||||
* Learn more in https://angular.io/guide/browser-support
|
||||
*/
|
||||
|
||||
/***************************************************************************************************
|
||||
* BROWSER POLYFILLS
|
||||
*/
|
||||
|
||||
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
|
||||
// import 'classlist.js'; // Run `npm install --save classlist.js`.
|
||||
|
||||
/**
|
||||
* Web Animations `@angular/platform-browser/animations`
|
||||
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
|
||||
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
|
||||
*/
|
||||
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
|
||||
|
||||
/**
|
||||
* By default, zone.js will patch all possible macroTask and DomEvents
|
||||
* user can disable parts of macroTask/DomEvents patch by setting following flags
|
||||
* because those flags need to be set before `zone.js` being loaded, and webpack
|
||||
* will put import in the top of bundle, so user need to create a separate file
|
||||
* in this directory (for example: zone-flags.ts), and put the following flags
|
||||
* into that file, and then add the following code before importing zone.js.
|
||||
* import './zone-flags';
|
||||
*
|
||||
* The flags allowed in zone-flags.ts are listed here.
|
||||
*
|
||||
* The following flags will work for all browsers.
|
||||
*
|
||||
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
|
||||
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
|
||||
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
|
||||
*
|
||||
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
|
||||
* with the following flag, it will bypass `zone.js` patch for IE/Edge
|
||||
*
|
||||
* (window as any).__Zone_enable_cross_context_check = true;
|
||||
*
|
||||
*/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Zone JS is required by default for Angular itself.
|
||||
*/
|
||||
import 'zone.js/dist/zone'; // Included with Angular CLI.
|
||||
|
||||
/***************************************************************************************************
|
||||
* APPLICATION IMPORTS
|
||||
*/
|
||||
12
examples/angular9/src/single-spa/asset-url.ts
Normal file
12
examples/angular9/src/single-spa/asset-url.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
// In single-spa, the assets need to be loaded from a dynamic location,
|
||||
// instead of hard coded to `/assets`. We use webpack public path for this.
|
||||
// See https://webpack.js.org/guides/public-path/#root
|
||||
|
||||
export function assetUrl(url: string): string {
|
||||
// @ts-ignore
|
||||
const publicPath = __webpack_public_path__;
|
||||
const publicPathSuffix = publicPath.endsWith('/') ? '' : '/';
|
||||
const urlPrefix = url.startsWith('/') ? '' : '/';
|
||||
|
||||
return `${publicPath}${publicPathSuffix}assets${urlPrefix}${url}`;
|
||||
}
|
||||
8
examples/angular9/src/single-spa/single-spa-props.ts
Normal file
8
examples/angular9/src/single-spa/single-spa-props.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { ReplaySubject } from 'rxjs';
|
||||
import { AppProps } from 'single-spa';
|
||||
|
||||
export const singleSpaPropsSubject = new ReplaySubject<SingleSpaProps>(1);
|
||||
|
||||
// Add any custom single-spa props you have to this type def
|
||||
// https://single-spa.js.org/docs/building-applications.html#custom-props
|
||||
export type SingleSpaProps = AppProps & {};
|
||||
1
examples/angular9/src/styles.css
Normal file
1
examples/angular9/src/styles.css
Normal file
|
|
@ -0,0 +1 @@
|
|||
/* You can add global styles to this file, and also import other style files */
|
||||
23
examples/angular9/src/test.ts
Normal file
23
examples/angular9/src/test.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
|
||||
|
||||
import 'zone.js/dist/zone-testing';
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
declare const require: {
|
||||
context(
|
||||
path: string,
|
||||
deep?: boolean,
|
||||
filter?: RegExp,
|
||||
): {
|
||||
keys(): string[];
|
||||
<T>(id: string): T;
|
||||
};
|
||||
};
|
||||
|
||||
// First, initialize the Angular testing environment.
|
||||
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
|
||||
// Then we find all the tests.
|
||||
const context = require.context('./', true, /\.spec\.ts$/);
|
||||
// And load the modules.
|
||||
context.keys().map(context);
|
||||
9
examples/angular9/tsconfig.app.json
Normal file
9
examples/angular9/tsconfig.app.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/app",
|
||||
"types": []
|
||||
},
|
||||
"files": ["src/main.qiankun.ts", "src/polyfills.ts"],
|
||||
"include": ["src/**/*.d.ts"]
|
||||
}
|
||||
21
examples/angular9/tsconfig.json
Normal file
21
examples/angular9/tsconfig.json
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"outDir": "./dist/out-tsc",
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"downlevelIteration": true,
|
||||
"experimentalDecorators": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"target": "es2015",
|
||||
"typeRoots": ["node_modules/@types"],
|
||||
"lib": ["es2018", "dom"]
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"fullTemplateTypeCheck": true,
|
||||
"strictInjectionParameters": true
|
||||
}
|
||||
}
|
||||
9
examples/angular9/tsconfig.spec.json
Normal file
9
examples/angular9/tsconfig.spec.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/spec",
|
||||
"types": ["jasmine", "node"]
|
||||
},
|
||||
"files": ["src/test.ts", "src/polyfills.ts"],
|
||||
"include": ["src/**/*.spec.ts", "src/**/*.d.ts"]
|
||||
}
|
||||
52
examples/angular9/tslint.json
Normal file
52
examples/angular9/tslint.json
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
{
|
||||
"extends": "tslint:recommended",
|
||||
"rules": {
|
||||
"array-type": false,
|
||||
"arrow-parens": false,
|
||||
"deprecation": {
|
||||
"severity": "warning"
|
||||
},
|
||||
"component-class-suffix": true,
|
||||
"contextual-lifecycle": true,
|
||||
"directive-class-suffix": true,
|
||||
"directive-selector": [true, "attribute", "angular9", "camelCase"],
|
||||
"component-selector": [true, "element", "angular9", "kebab-case"],
|
||||
"import-blacklist": [true, "rxjs/Rx"],
|
||||
"interface-name": false,
|
||||
"max-classes-per-file": false,
|
||||
"max-line-length": [true, 140],
|
||||
"member-access": false,
|
||||
"member-ordering": [
|
||||
true,
|
||||
{
|
||||
"order": ["static-field", "instance-field", "static-method", "instance-method"]
|
||||
}
|
||||
],
|
||||
"no-consecutive-blank-lines": false,
|
||||
"no-console": [true, "debug", "info", "time", "timeEnd", "trace"],
|
||||
"no-empty": false,
|
||||
"no-inferrable-types": [true, "ignore-params"],
|
||||
"no-non-null-assertion": true,
|
||||
"no-redundant-jsdoc": true,
|
||||
"no-switch-case-fall-through": true,
|
||||
"no-var-requires": false,
|
||||
"object-literal-key-quotes": [true, "as-needed"],
|
||||
"object-literal-sort-keys": false,
|
||||
"ordered-imports": false,
|
||||
"quotemark": [true, "single"],
|
||||
"trailing-comma": false,
|
||||
"no-conflicting-lifecycle": true,
|
||||
"no-host-metadata-property": true,
|
||||
"no-input-rename": true,
|
||||
"no-inputs-metadata-property": true,
|
||||
"no-output-native": true,
|
||||
"no-output-on-prefix": true,
|
||||
"no-output-rename": true,
|
||||
"no-outputs-metadata-property": true,
|
||||
"template-banana-in-box": true,
|
||||
"template-no-negated-async": true,
|
||||
"use-lifecycle-interface": true,
|
||||
"use-pipe-transform-interface": true
|
||||
},
|
||||
"rulesDirectory": ["codelyzer"]
|
||||
}
|
||||
BIN
examples/example.gif
Normal file
BIN
examples/example.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
34
examples/main/index.html
Normal file
34
examples/main/index.html
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>QianKun Example</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="mainapp">
|
||||
<!-- 标题栏 -->
|
||||
<header class="mainapp-header">
|
||||
<h1>QianKun</h1>
|
||||
</header>
|
||||
<div class="mainapp-main">
|
||||
<!-- 侧边栏 -->
|
||||
<ul class="mainapp-sidemenu">
|
||||
<li onclick="push('/react16')">React16</li>
|
||||
<li onclick="push('/react15')">React15</li>
|
||||
<li onclick="push('/vue')">Vue</li>
|
||||
<li onclick="push('/angular9')">Angular9</li>
|
||||
<li onclick="push('/purehtml')">Purehtml</li>
|
||||
</ul>
|
||||
<!-- 子应用 -->
|
||||
<main id="subapp-container"></main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function push(subapp) { history.pushState(null, subapp, subapp) }
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
107
examples/main/index.js
Normal file
107
examples/main/index.js
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
import { registerMicroApps, runAfterFirstMounted, setDefaultMountApp, start, initGlobalState } from '../../es';
|
||||
import './index.less';
|
||||
|
||||
// for angular subapp
|
||||
import 'zone.js';
|
||||
|
||||
/**
|
||||
* 主应用 **可以使用任意技术栈**
|
||||
* 以下分别是 React 和 Vue 的示例,可切换尝试
|
||||
*/
|
||||
import render from './render/ReactRender';
|
||||
// import render from './render/VueRender';
|
||||
|
||||
/**
|
||||
* Step1 初始化应用(可选)
|
||||
*/
|
||||
render({ loading: true });
|
||||
|
||||
const loader = loading => render({ loading });
|
||||
|
||||
/**
|
||||
* Step2 注册子应用
|
||||
*/
|
||||
|
||||
registerMicroApps(
|
||||
[
|
||||
{
|
||||
name: 'react16',
|
||||
entry: '//localhost:7100',
|
||||
container: '#subapp-viewport',
|
||||
loader,
|
||||
activeRule: '/react16',
|
||||
},
|
||||
{
|
||||
name: 'react15',
|
||||
entry: '//localhost:7102',
|
||||
container: '#subapp-viewport',
|
||||
loader,
|
||||
activeRule: '/react15',
|
||||
},
|
||||
{
|
||||
name: 'vue',
|
||||
entry: '//localhost:7101',
|
||||
container: '#subapp-viewport',
|
||||
loader,
|
||||
activeRule: '/vue',
|
||||
},
|
||||
{
|
||||
name: 'angular9',
|
||||
entry: '//localhost:7103',
|
||||
container: '#subapp-viewport',
|
||||
loader,
|
||||
activeRule: '/angular9',
|
||||
},
|
||||
{
|
||||
name: 'purehtml',
|
||||
entry: '//localhost:7104',
|
||||
container: '#subapp-viewport',
|
||||
loader,
|
||||
activeRule: '/purehtml',
|
||||
},
|
||||
],
|
||||
{
|
||||
beforeLoad: [
|
||||
app => {
|
||||
console.log('[LifeCycle] before load %c%s', 'color: green;', app.name);
|
||||
},
|
||||
],
|
||||
beforeMount: [
|
||||
app => {
|
||||
console.log('[LifeCycle] before mount %c%s', 'color: green;', app.name);
|
||||
},
|
||||
],
|
||||
afterUnmount: [
|
||||
app => {
|
||||
console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name);
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
const { onGlobalStateChange, setGlobalState } = initGlobalState({
|
||||
user: 'qiankun',
|
||||
});
|
||||
|
||||
onGlobalStateChange((value, prev) => console.log('[onGlobalStateChange - master]:', value, prev));
|
||||
|
||||
setGlobalState({
|
||||
ignore: 'master',
|
||||
user: {
|
||||
name: 'master',
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Step3 设置默认进入的子应用
|
||||
*/
|
||||
setDefaultMountApp('/react16');
|
||||
|
||||
/**
|
||||
* Step4 启动应用
|
||||
*/
|
||||
start();
|
||||
|
||||
runAfterFirstMounted(() => {
|
||||
console.log('[MainApp] first app mounted');
|
||||
});
|
||||
63
examples/main/index.less
Normal file
63
examples/main/index.less
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
// 主应用慎用 reset 样式
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.mainapp {
|
||||
// 防止被子应用的样式覆盖
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Helvetica Neue, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.mainapp-header {
|
||||
>h1 {
|
||||
color: #333;
|
||||
font-size: 36px;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
padding: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
.mainapp-main {
|
||||
display: flex;
|
||||
|
||||
.mainapp-sidemenu {
|
||||
width: 130px;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
margin-left: 40px;
|
||||
padding: 0;
|
||||
border-right: 2px solid #aaa;
|
||||
|
||||
>li {
|
||||
color: #aaa;
|
||||
margin: 20px 0;
|
||||
font-size: 18px;
|
||||
font-weight: 400;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 子应用区域
|
||||
#subapp-container {
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
margin: 0 40px;
|
||||
|
||||
.subapp-loading {
|
||||
color: #444;
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
21
examples/main/multiple.html
Normal file
21
examples/main/multiple.html
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>qiankun multiple demo</title>
|
||||
<style>
|
||||
body {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="react15">react loading...</div>
|
||||
<div id="vue">vue loading...</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
19
examples/main/multiple.js
Normal file
19
examples/main/multiple.js
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { loadMicroApp } from '../../es';
|
||||
|
||||
const app1 = loadMicroApp(
|
||||
{ name: 'react15', entry: '//localhost:7102', container: '#react15' },
|
||||
{
|
||||
sandbox: {
|
||||
// strictStyleIsolation: true,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const app2 = loadMicroApp(
|
||||
{ name: 'vue', entry: '//localhost:7101', container: '#vue' },
|
||||
{
|
||||
sandbox: {
|
||||
// strictStyleIsolation: true,
|
||||
},
|
||||
},
|
||||
);
|
||||
32
examples/main/package.json
Normal file
32
examples/main/package.json
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"name": "main",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "webpack-dev-server",
|
||||
"start:multiple": "cross-env MODE=multiple webpack-dev-server",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.7.2",
|
||||
"@babel/plugin-transform-react-jsx": "^7.7.0",
|
||||
"@babel/preset-env": "^7.7.1",
|
||||
"babel-loader": "^8.0.6",
|
||||
"css-loader": "^3.2.0",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"less-loader": "^6.2.0",
|
||||
"style-loader": "^1.0.0",
|
||||
"webpack": "^4.41.2",
|
||||
"webpack-cli": "^3.3.10",
|
||||
"webpack-dev-server": "^3.9.0",
|
||||
"cross-env": "^7.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"vue": "^2.6.11",
|
||||
"zone.js": "^0.10.2"
|
||||
}
|
||||
}
|
||||
21
examples/main/render/ReactRender.jsx
Normal file
21
examples/main/render/ReactRender.jsx
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
/**
|
||||
* 渲染子应用
|
||||
*/
|
||||
function Render(props) {
|
||||
const { loading } = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
{loading && <h4 className="subapp-loading">Loading...</h4>}
|
||||
<div id="subapp-viewport" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function render({ loading }) {
|
||||
const container = document.getElementById('subapp-container');
|
||||
ReactDOM.render(<Render loading={loading} />, container);
|
||||
}
|
||||
28
examples/main/render/VueRender.js
Normal file
28
examples/main/render/VueRender.js
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import Vue from 'vue/dist/vue.esm';
|
||||
|
||||
function vueRender({ loading }) {
|
||||
return new Vue({
|
||||
template: `
|
||||
<div id="subapp-container">
|
||||
<h4 v-if="loading" class="subapp-loading">Loading...</h4>
|
||||
<div id="subapp-viewport"></div>
|
||||
</div>
|
||||
`,
|
||||
el: '#subapp-container',
|
||||
data() {
|
||||
return {
|
||||
loading,
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
let app = null;
|
||||
|
||||
export default function render({ loading }) {
|
||||
if (!app) {
|
||||
app = vueRender({ loading });
|
||||
} else {
|
||||
app.loading = loading;
|
||||
}
|
||||
}
|
||||
54
examples/main/webpack.config.js
Normal file
54
examples/main/webpack.config.js
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const { name } = require('./package');
|
||||
|
||||
module.exports = {
|
||||
entry: process.env.MODE === 'multiple' ? './multiple.js' : './index.js',
|
||||
devtool: 'source-map',
|
||||
devServer: {
|
||||
port: '7099',
|
||||
clientLogLevel: 'warning',
|
||||
disableHostCheck: true,
|
||||
compress: true,
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
historyApiFallback: true,
|
||||
overlay: { warnings: false, errors: true },
|
||||
},
|
||||
output: {
|
||||
publicPath: '/',
|
||||
},
|
||||
mode: 'development',
|
||||
resolve: {
|
||||
extensions: ['.js', '.jsx', '.ts', '.tsx'],
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.jsx?$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
presets: ['@babel/preset-env'],
|
||||
plugins: ['@babel/plugin-transform-react-jsx'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.(le|c)ss$/,
|
||||
use: ['style-loader', 'css-loader', 'less-loader'],
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({
|
||||
filename: 'index.html',
|
||||
template: process.env.MODE === 'multiple' ? './multiple.html' : './index.html',
|
||||
minify: {
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
21
examples/purehtml/entry.js
Normal file
21
examples/purehtml/entry.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
const render = $ => {
|
||||
$('#purehtml-container').html('Hello, render with jQuery');
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
(global => {
|
||||
global['purehtml'] = {
|
||||
bootstrap: () => {
|
||||
console.log('purehtml bootstrap');
|
||||
return Promise.resolve();
|
||||
},
|
||||
mount: () => {
|
||||
console.log('purehtml mount');
|
||||
return render($);
|
||||
},
|
||||
unmount: () => {
|
||||
console.log('purehtml unmount');
|
||||
return Promise.resolve();
|
||||
},
|
||||
};
|
||||
})(window);
|
||||
17
examples/purehtml/index.html
Normal file
17
examples/purehtml/index.html
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Purehtml Example</title>
|
||||
<script src="//cdn.bootcss.com/jquery/3.4.1/jquery.min.js">
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div style="display: flex; justify-content: center; align-items: center; height: 200px;">
|
||||
Purehtml Example
|
||||
</div>
|
||||
<div id="purehtml-container" style="text-align:center"></div>
|
||||
<script src="//localhost:7104/entry.js" entry></script>
|
||||
</body>
|
||||
</html>
|
||||
16
examples/purehtml/package.json
Normal file
16
examples/purehtml/package.json
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "purehtml",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.html",
|
||||
"scripts": {
|
||||
"start": "cross-env PORT=7104 http-server . --cors",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"cross-env": "^7.0.2",
|
||||
"http-server": "^0.12.1"
|
||||
}
|
||||
}
|
||||
19
examples/react15/App.jsx
Normal file
19
examples/react15/App.jsx
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import React, { version as reactVersion } from 'react';
|
||||
import { version as antdVersion } from 'antd';
|
||||
|
||||
import Logo from './components/Logo'
|
||||
import HelloModal from './components/HelloModal'
|
||||
|
||||
export default class App extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="react15-main">
|
||||
<Logo />
|
||||
<p className="react15-lib">
|
||||
React version: {reactVersion}, AntD version: {antdVersion}
|
||||
</p>
|
||||
<HelloModal />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
34
examples/react15/components/HelloModal.jsx
Normal file
34
examples/react15/components/HelloModal.jsx
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import React from 'react';
|
||||
import { Button, Modal } from 'antd';
|
||||
|
||||
export default class HelloModal extends React.Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
visible: false,
|
||||
};
|
||||
this.setVisible = visible => this.setState({ visible });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { visible } = this.state;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button onClick={() => this.setVisible(true)}>
|
||||
CLICK ME
|
||||
</Button>
|
||||
<Modal
|
||||
visible={visible}
|
||||
closable={false}
|
||||
onOk={() => this.setVisible(false)}
|
||||
onCancel={() => this.setVisible(false)}
|
||||
title="Install"
|
||||
>
|
||||
<code>$ yarn add qiankun # or npm i qiankun -S</code>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
12
examples/react15/components/Logo.jsx
Normal file
12
examples/react15/components/Logo.jsx
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import React from 'react';
|
||||
|
||||
export default class ReactLogo extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<img
|
||||
className="react15-icon"
|
||||
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ii0xMS41IC0xMC4yMzE3NCAyMyAyMC40NjM0OCI+CiAgPHRpdGxlPlJlYWN0IExvZ288L3RpdGxlPgogIDxjaXJjbGUgY3g9IjAiIGN5PSIwIiByPSIyLjA1IiBmaWxsPSIjNjFkYWZiIi8+CiAgPGcgc3Ryb2tlPSIjNjFkYWZiIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiPgogICAgPGVsbGlwc2Ugcng9IjExIiByeT0iNC4yIi8+CiAgICA8ZWxsaXBzZSByeD0iMTEiIHJ5PSI0LjIiIHRyYW5zZm9ybT0icm90YXRlKDYwKSIvPgogICAgPGVsbGlwc2Ugcng9IjExIiByeT0iNC4yIiB0cmFuc2Zvcm09InJvdGF0ZSgxMjApIi8+CiAgPC9nPgo8L3N2Zz4K"
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
5
examples/react15/dynamic.css
Normal file
5
examples/react15/dynamic.css
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
/* 动态加载的样式 */
|
||||
|
||||
.react15-lib {
|
||||
color: #818ff7;
|
||||
}
|
||||
15
examples/react15/index.css
Normal file
15
examples/react15/index.css
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
.react15-main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.react15-icon {
|
||||
width: 140px;
|
||||
}
|
||||
|
||||
.react15-lib {
|
||||
margin: 32px 0 24px;
|
||||
font-size: 16px;
|
||||
color: #2c3e50;
|
||||
}
|
||||
13
examples/react15/index.html
Normal file
13
examples/react15/index.html
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Title</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="react15Root"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
38
examples/react15/index.js
Normal file
38
examples/react15/index.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* @author Kuitos
|
||||
* @since 2019-05-16
|
||||
*/
|
||||
import './public-path';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
|
||||
import 'antd/dist/antd.min.css';
|
||||
import './index.css';
|
||||
|
||||
export async function bootstrap() {
|
||||
console.log('[react15] react app bootstraped');
|
||||
}
|
||||
|
||||
export async function mount(props) {
|
||||
console.log('[react15] props from main framework', props);
|
||||
const { container } = props;
|
||||
ReactDOM.render(
|
||||
<App />,
|
||||
container ? container.querySelector('#react15Root') : document.getElementById('react15Root'),
|
||||
);
|
||||
import('./dynamic.css').then(() => {
|
||||
console.log('[react15] dynamic style load');
|
||||
});
|
||||
}
|
||||
|
||||
export async function unmount(props) {
|
||||
const { container } = props;
|
||||
ReactDOM.unmountComponentAtNode(
|
||||
container ? container.querySelector('#react15Root') : document.getElementById('react15Root'),
|
||||
);
|
||||
}
|
||||
|
||||
if (!window.__POWERED_BY_QIANKUN__) {
|
||||
bootstrap().then(mount);
|
||||
}
|
||||
31
examples/react15/package.json
Normal file
31
examples/react15/package.json
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"name": "react15",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "webpack-dev-server",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"dependencies": {
|
||||
"antd": "2.13.14",
|
||||
"react": "15.6.2",
|
||||
"react-dom": "15.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.7.2",
|
||||
"@babel/plugin-transform-react-jsx": "^7.7.0",
|
||||
"@babel/preset-env": "^7.7.1",
|
||||
"babel-loader": "^8.0.6",
|
||||
"css-loader": "^3.2.0",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"style-loader": "^1.0.0",
|
||||
"webpack": "^4.41.2",
|
||||
"webpack-cli": "^3.3.10",
|
||||
"webpack-dev-server": "^3.9.0"
|
||||
},
|
||||
"browserslist": [
|
||||
"last 2 Chrome versions"
|
||||
]
|
||||
}
|
||||
3
examples/react15/public-path.js
Normal file
3
examples/react15/public-path.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
if (window.__POWERED_BY_QIANKUN__) {
|
||||
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
|
||||
}
|
||||
55
examples/react15/webpack.config.js
Normal file
55
examples/react15/webpack.config.js
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const { name } = require('./package');
|
||||
|
||||
module.exports = {
|
||||
entry: './index.js',
|
||||
devtool: 'source-map',
|
||||
devServer: {
|
||||
port: '7102',
|
||||
clientLogLevel: 'warning',
|
||||
disableHostCheck: true,
|
||||
compress: true,
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
overlay: { warnings: false, errors: true },
|
||||
},
|
||||
output: {
|
||||
library: `${name}-[name]`,
|
||||
libraryTarget: 'umd',
|
||||
jsonpFunction: `webpackJsonp_${name}`,
|
||||
},
|
||||
mode: 'development',
|
||||
resolve: {
|
||||
extensions: ['.js', '.jsx', '.ts', '.tsx'],
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.jsx?$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
presets: ['@babel/preset-env'],
|
||||
plugins: ['@babel/plugin-transform-react-jsx'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: ['style-loader', 'css-loader'],
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({
|
||||
filename: 'index.html',
|
||||
template: './index.html',
|
||||
minify: {
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
4
examples/react16/.env
Normal file
4
examples/react16/.env
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
SKIP_PREFLIGHT_CHECK=true
|
||||
BROWSER=none
|
||||
PORT=7100
|
||||
WDS_SOCKET_PORT=7100
|
||||
28
examples/react16/.rescriptsrc.js
Normal file
28
examples/react16/.rescriptsrc.js
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
const { name } = require('./package');
|
||||
|
||||
module.exports = {
|
||||
webpack: config => {
|
||||
config.output.library = `${name}-[name]`;
|
||||
config.output.libraryTarget = 'umd';
|
||||
config.output.jsonpFunction = `webpackJsonp_${name}`;
|
||||
config.output.globalObject = 'window';
|
||||
|
||||
return config;
|
||||
},
|
||||
|
||||
devServer: _ => {
|
||||
const config = {};
|
||||
|
||||
config.port = '7100';
|
||||
config.headers = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
};
|
||||
config.historyApiFallback = true;
|
||||
|
||||
config.hot = false;
|
||||
config.watchContentBase = false;
|
||||
config.liveReload = false;
|
||||
|
||||
return config;
|
||||
},
|
||||
};
|
||||
63
examples/react16/README.md
Normal file
63
examples/react16/README.md
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||
|
||||
## Available Scripts
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `yarn start`
|
||||
|
||||
Runs the app in the development mode.<br /> Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
|
||||
|
||||
The page will reload if you make edits.<br /> You will also see any lint errors in the console.
|
||||
|
||||
### `yarn test`
|
||||
|
||||
Launches the test runner in the interactive watch mode.<br /> See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
||||
|
||||
### `yarn build`
|
||||
|
||||
Builds the app for production to the `build` folder.<br /> It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||
|
||||
The build is minified and the filenames include the hashes.<br /> Your app is ready to be deployed!
|
||||
|
||||
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
||||
|
||||
### `yarn eject`
|
||||
|
||||
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
|
||||
|
||||
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
|
||||
|
||||
Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
|
||||
|
||||
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
|
||||
|
||||
## Learn More
|
||||
|
||||
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
||||
|
||||
To learn React, check out the [React documentation](https://reactjs.org/).
|
||||
|
||||
### Code Splitting
|
||||
|
||||
This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
|
||||
|
||||
### Analyzing the Bundle Size
|
||||
|
||||
This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
|
||||
|
||||
### Making a Progressive Web App
|
||||
|
||||
This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
|
||||
|
||||
### Deployment
|
||||
|
||||
This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
|
||||
|
||||
### `yarn build` fails to minify
|
||||
|
||||
This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify
|
||||
33
examples/react16/package.json
Normal file
33
examples/react16/package.json
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"name": "react16",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"antd": "^3.25.2",
|
||||
"react": "^16.12.0",
|
||||
"react-dom": "^16.12.0",
|
||||
"react-router-dom": "^5.1.2"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "rescripts start",
|
||||
"build": "rescripts build",
|
||||
"test": "rescripts test",
|
||||
"eject": "rescripts eject"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rescripts/cli": "^0.0.14",
|
||||
"react-scripts": "^3.4.1"
|
||||
}
|
||||
}
|
||||
BIN
examples/react16/public/favicon.ico
Normal file
BIN
examples/react16/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
43
examples/react16/public/index.html
Normal file
43
examples/react16/public/index.html
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<link href="%PUBLIC_URL%/favicon.ico" rel="icon"/>
|
||||
<meta content="width=device-width, initial-scale=1" name="viewport"/>
|
||||
<meta content="#000000" name="theme-color"/>
|
||||
<meta
|
||||
content="Web site created using create-react-app"
|
||||
name="description"
|
||||
/>
|
||||
<link href="logo192.png" rel="apple-touch-icon"/>
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link href="%PUBLIC_URL%/manifest.json" rel="manifest"/>
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
BIN
examples/react16/public/logo192.png
Normal file
BIN
examples/react16/public/logo192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.4 KiB |
BIN
examples/react16/public/logo512.png
Normal file
BIN
examples/react16/public/logo512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
25
examples/react16/public/manifest.json
Normal file
25
examples/react16/public/manifest.json
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
2
examples/react16/public/robots.txt
Normal file
2
examples/react16/public/robots.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
22
examples/react16/src/App.css
Normal file
22
examples/react16/src/App.css
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
.app-main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.app-title {
|
||||
font-size: 30px;
|
||||
margin: 0;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.app-lib {
|
||||
font-size: 16px;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.app-nav-item {
|
||||
margin-top: 16px;
|
||||
padding: 12px 24px;
|
||||
border: 2px solid #2c3e50;
|
||||
}
|
||||
43
examples/react16/src/App.js
Normal file
43
examples/react16/src/App.js
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import React, { lazy, Suspense } from 'react';
|
||||
import { BrowserRouter as Router, Link, Route, Switch } from 'react-router-dom';
|
||||
import { Divider } from 'antd';
|
||||
|
||||
import 'antd/dist/antd.min.css';
|
||||
import './App.css';
|
||||
|
||||
import LibVersion from './components/LibVersion';
|
||||
import HelloModal from './components/HelloModal';
|
||||
|
||||
import Home from './pages/Home';
|
||||
const About = lazy(() => import('./pages/About'));
|
||||
|
||||
const RouteExample = () => {
|
||||
return (
|
||||
<Router basename={window.__POWERED_BY_QIANKUN__ ? '/react16' : '/'}>
|
||||
<nav>
|
||||
<Link to="/">Home</Link>
|
||||
<Divider type="vertical" />
|
||||
<Link to="/about">About</Link>
|
||||
</nav>
|
||||
<Suspense fallback={null}>
|
||||
<Switch>
|
||||
<Route path="/" exact component={Home} />
|
||||
<Route path="/about" component={About} />
|
||||
</Switch>
|
||||
</Suspense>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<div className="app-main">
|
||||
<LibVersion />
|
||||
<HelloModal />
|
||||
|
||||
<Divider />
|
||||
|
||||
<RouteExample />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
9
examples/react16/src/App.test.js
Normal file
9
examples/react16/src/App.test.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
|
||||
it('renders without crashing', () => {
|
||||
const div = document.createElement('div');
|
||||
ReactDOM.render(<App />, div);
|
||||
ReactDOM.unmountComponentAtNode(div);
|
||||
});
|
||||
30
examples/react16/src/components/HelloModal.js
Normal file
30
examples/react16/src/components/HelloModal.js
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { Button, Modal } from 'antd';
|
||||
|
||||
const dispatchUIEvent = () => {
|
||||
const $a = document.createElement('a');
|
||||
$a.onclick = () => {
|
||||
console.log('log from UIEvent');
|
||||
};
|
||||
const evt = new MouseEvent('click', {
|
||||
view: window,
|
||||
bubbles: true,
|
||||
cancelable: false,
|
||||
});
|
||||
$a.dispatchEvent(evt);
|
||||
};
|
||||
|
||||
export default function() {
|
||||
const [visible, setVisible] = useState(false);
|
||||
useEffect(() => {
|
||||
dispatchUIEvent();
|
||||
}, []);
|
||||
return (
|
||||
<>
|
||||
<Button onClick={() => setVisible(true)}>CLICK ME</Button>
|
||||
<Modal visible={visible} onOk={() => setVisible(false)} onCancel={() => setVisible(false)} title="qiankun">
|
||||
Probably the most complete micro-frontends solution you ever met
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
13
examples/react16/src/components/LibVersion.js
Normal file
13
examples/react16/src/components/LibVersion.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import React, { version as reactVersion } from 'react';
|
||||
import { version as antdVersion } from 'antd';
|
||||
|
||||
export default function() {
|
||||
return (
|
||||
<>
|
||||
<h1 className="app-title">React Demo</h1>
|
||||
<p className="app-lib">
|
||||
React version: {reactVersion}, AntD version: {antdVersion}
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user