├── .adr.json
├── .codeclimate.yml
├── .coveralls.yml
├── .editorconfig
├── .github
└── FUNDING.yml
├── .gitignore
├── .npmignore
├── .travis.yml
├── ISSUE_TEMPLATE.md
├── LICENSE
├── README.md
├── _config.yml
├── deploy
├── package.json
├── serverless.yml
└── yarn.lock
├── docs
├── README.md
├── adr
│ ├── 0001-基本的-angular-应用替换.md
│ ├── 0002-重构项目目录.md
│ ├── 0003-使用自定义创建-element-方法.md
│ ├── 0004-支持子-app-路由.md
│ ├── 0005-支持单个应用多个路由入口.md
│ ├── 0006-创建调试用命令行.md
│ ├── 0007-base-path-支持问题.md
│ ├── 0008-生成打包脚本.md
│ ├── 0009-考虑-loading-动画.md
│ ├── 0010-清空-parentelement-dom-下的所有-children.md
│ ├── 0011-支持组件之间跳转.md
│ ├── 0012-assets-目录合并问题.md
│ ├── 0013-修复-app-修改-host-url-带来的问题.md
│ ├── 0014-cli-支持创建项目.md
│ ├── 0015-支持在-cli-中使用中文.md
│ ├── 0016-使用-iframe-支持传统应用.md
│ ├── 0017-应用间切换保存最后的状态.md
│ ├── 0018-支持-iframe-与-angular-应用切换使用.md
│ ├── 0019-支持在配置文件中使用-link-和配置根据链接来区别.md
│ ├── 0020-使用-cli-来将应用设置成-mooa-应用.md
│ ├── 0021-编写-mooa-client-来支持其它非-angular-应用.md
│ ├── 0022-提供一个预加载的-api来加载对应的-dom.md
│ ├── 0023-支持-zone.min.js-可配置来兼容应用.md
│ ├── 0024-通过-index.html-中的标签来匹配根节点元素.md
│ ├── 0025-支持可选参数来过滤-js-和-css.md
│ ├── 0026-支持-lazyload-子应用.md
│ └── README.md
├── mooa-app.jpg
├── mooa-design.jpg
├── mooa-graph.jpg
├── mooa-workflow.sketch
├── mooa.png
├── mooa.sketch
└── workflow.png
├── examples
├── .angular-cli.json
├── apps.txt
├── karma.conf.js
├── package.json
├── protractor.conf.js
├── src
│ ├── app
│ │ ├── app-routing.module.ts
│ │ ├── app.component.css
│ │ ├── app.component.html
│ │ ├── app.component.spec.ts
│ │ ├── app.component.ts
│ │ ├── app.module.ts
│ │ ├── games
│ │ │ ├── games.component.css
│ │ │ ├── games.component.html
│ │ │ ├── games.component.spec.ts
│ │ │ └── games.component.ts
│ │ ├── graph
│ │ │ ├── graph.component.css
│ │ │ ├── graph.component.html
│ │ │ ├── graph.component.spec.ts
│ │ │ ├── graph.component.ts
│ │ │ └── graph.module.ts
│ │ ├── home
│ │ │ ├── home.component.css
│ │ │ ├── home.component.html
│ │ │ ├── home.component.spec.ts
│ │ │ ├── home.component.ts
│ │ │ └── home.module.ts
│ │ ├── navbar
│ │ │ ├── navbar.component.css
│ │ │ ├── navbar.component.html
│ │ │ ├── navbar.component.spec.ts
│ │ │ └── navbar.component.ts
│ │ ├── pipes
│ │ │ └── SafePipe.ts
│ │ ├── reader
│ │ │ ├── reader.component.css
│ │ │ ├── reader.component.html
│ │ │ ├── reader.component.spec.ts
│ │ │ └── reader.component.ts
│ │ └── shopping
│ │ │ ├── shopping.component.css
│ │ │ ├── shopping.component.html
│ │ │ ├── shopping.component.spec.ts
│ │ │ └── shopping.component.ts
│ ├── assets
│ │ ├── .gitkeep
│ │ ├── app1
│ │ │ ├── 0.chunk.js
│ │ │ ├── 1.chunk.js
│ │ │ ├── 3rdpartylicenses.txt
│ │ │ ├── assets
│ │ │ │ ├── avatar.jpg
│ │ │ │ └── zone.js
│ │ │ ├── cors.py
│ │ │ ├── favicon.ico
│ │ │ ├── index.html
│ │ │ ├── inline.bundle.js
│ │ │ ├── main.bundle.js
│ │ │ ├── polyfills.bundle.js
│ │ │ └── styles.bundle.css
│ │ ├── apps.json
│ │ ├── avatar.jpg
│ │ ├── data
│ │ │ └── company.json
│ │ ├── help
│ │ │ ├── avatar.jpg
│ │ │ ├── index.html
│ │ │ ├── inline.bundle.js
│ │ │ ├── main.bundle.js
│ │ │ ├── polyfills.bundle.js
│ │ │ └── styles.bundle.css
│ │ ├── iframe.html
│ │ ├── mifa.css
│ │ ├── styles.css
│ │ └── zone.min.js
│ ├── environments
│ │ ├── environment.prod.ts
│ │ └── environment.ts
│ ├── favicon.ico
│ ├── index.html
│ ├── main.ts
│ ├── polyfills.ts
│ ├── styles.css
│ ├── test.ts
│ ├── tsconfig.app.json
│ ├── tsconfig.spec.json
│ └── typings.d.ts
├── tsconfig.json
├── tslint.json
└── yarn.lock
├── jest.setup.js
├── package.json
├── rollup.config.ts
├── src
├── cli.ts
├── cli
│ ├── cli.help.ts
│ ├── create.ts
│ ├── generate.ts
│ └── update.ts
├── helper
│ ├── app.helper.ts
│ ├── assets.helper.ts
│ ├── dom.utils.ts
│ ├── loader.helper.ts
│ ├── status.helper.ts
│ └── timeouts.ts
├── lifecycles
│ ├── bootstrap.ts
│ ├── load.ts
│ ├── mount.ts
│ ├── unload.ts
│ └── unmount.ts
├── loader
│ └── mooa.loader.ts
├── model
│ ├── IAppOption.ts
│ ├── MooaOption.ts
│ └── constants.ts
├── mooa.ts
├── platform
│ └── platform.ts
└── router.ts
├── test
├── helper
│ ├── app.helper.spec.ts
│ ├── assets-loader.helper.spec.ts
│ ├── dom.utils.spec.ts
│ └── status.helper.spec.ts
├── lifecycyle
│ ├── bootstrap.spec.ts
│ ├── load.spec.ts
│ ├── mount.spec.ts
│ ├── unload.spec.ts
│ └── unmount.spec.ts
├── loader
│ ├── loader.bootstrap.spec.ts
│ ├── loader.unload.spec.ts
│ ├── loader.unmount.spec.ts
│ └── mooa.loader.spec.ts
├── mooa.spec.ts
└── platform
│ └── platform.spec.ts
├── tools
├── gh-pages-publish.ts
└── semantic-release-prepare.ts
├── tsconfig.json
├── tslint.json
└── yarn.lock
/.adr.json:
--------------------------------------------------------------------------------
1 | {"language":"zh-cn","path":"docs/adr/","prefix":"","digits":4}
--------------------------------------------------------------------------------
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | ---
2 | engines:
3 | tslint:
4 | channel: beta
5 | config: tslint.json
6 | enabled: true
7 | exclude_patterns:
8 | - "config"
9 | - "**/*.d.ts"
10 | - "**/*.spec.ts"
11 | - "examples"
12 | - "test"
13 | ratings:
14 | paths:
15 | - "src/**/*.ts"
16 |
--------------------------------------------------------------------------------
/.coveralls.yml:
--------------------------------------------------------------------------------
1 | repo_token: 3iZQAlQwhiwRlA180KWNYlIsBTUQV8Ofj
2 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | max_line_length = off
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: phodal
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | custom: # Replace with a single custom sponsorship URL
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /dist-server
6 | /tmp
7 | /out-tsc
8 |
9 | # dependencies
10 | /node_modules
11 |
12 | # IDEs and editors
13 | /.idea
14 | .project
15 | .classpath
16 | .c9/
17 | *.launch
18 | .settings/
19 | *.sublime-workspace
20 |
21 | # IDE - VSCode
22 | .vscode/*
23 | !.vscode/settings.json
24 | !.vscode/tasks.json
25 | !.vscode/launch.json
26 | !.vscode/extensions.json
27 |
28 | # misc
29 | /.sass-cache
30 | /connect.lock
31 | /coverage
32 | /libpeerconnection.log
33 | npm-debug.log
34 | testem.log
35 | /typings
36 |
37 | # e2e
38 | /e2e/*.js
39 | /e2e/*.map
40 |
41 | # System Files
42 | .DS_Store
43 | Thumbs.db
44 | apps/
45 | examples/node_modules/
46 | deploy/node_modules/
47 | .rpt2_cache/
48 | docs/mooa
49 | examples/dist
50 | apps.json
51 | .serverless/
52 | yarn-error.log
53 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | examples/
2 | apps/
3 | docs/
4 | e2e/
5 | test/
6 | coverage/
7 | deploy/
8 | tools/
9 | rollup.config.ts
10 | tsconfig.json
11 | tslint.json
12 | yarn.lock
13 | .travis.yml
14 | .editorconfig
15 | .adr.json
16 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | branches:
3 | only:
4 | - master
5 | - /^greenkeeper/.*$/
6 | cache:
7 | yarn: true
8 | directories:
9 | - node_modules
10 | notifications:
11 | email: false
12 | node_js:
13 | - node
14 | script:
15 | - npm run test:prod && npm run build
16 | after_success:
17 | - npm run report-coverage
18 | - npm run deploy-docs
19 | - npm run semantic-release
20 |
--------------------------------------------------------------------------------
/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
6 |
7 | ## I'm submitting a...
8 |
9 |
10 | [ ] Regression (a behavior that used to work and stopped working in a new release)
11 | [ ] Bug report
12 | [ ] Feature request
13 | [ ] Documentation issue or request
14 | [ ] Support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question
15 |
16 |
17 | ## Current behavior
18 |
19 |
20 |
21 | ## Expected behavior
22 |
23 |
24 |
25 | ## Minimal reproduction of the problem with instructions
26 |
30 |
31 | ## What is the motivation / use case for changing the behavior?
32 |
33 |
34 |
35 | ## Environment
36 |
37 |
38 | Angular version: X.Y.Z
39 |
40 |
41 | Browser:
42 | - [ ] Chrome (desktop) version XX
43 | - [ ] Chrome (Android) version XX
44 | - [ ] Chrome (iOS) version XX
45 | - [ ] Firefox version XX
46 | - [ ] Safari (desktop) version XX
47 | - [ ] Safari (iOS) version XX
48 | - [ ] IE version XX
49 | - [ ] Edge version XX
50 |
51 | For Tooling issues:
52 | - Node version: XX
53 | - Platform:
54 |
55 | Others:
56 |
57 |
58 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | License (MIT)
2 |
3 | Copyright (c) 2013-2014 Christopher Simpkins [single-spa](https://github.com/CanopyTax/single-spa)
4 | Copyright (c) 2018 Phodal HUANG
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | THE SOFTWARE.
23 |
24 | LICENSE(APACHE 2.0)
25 | Copyright (c) 2017-2018 Robin Coma Delperier [single-spa-angular-cli](https://github.com/PlaceMe-SAS/single-spa-angular-cli)
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Mooa
2 |
3 | [](https://travis-ci.org/phodal/mooa)
4 | [](https://coveralls.io/github/phodal/mooa?branch=master)
5 | [](https://codeclimate.com/github/phodal/mooa/maintainability)
6 | [](https://www.npmjs.com/package/mooa)
7 | [](https://www.npmjs.com/mooa)
8 |
9 | > A single SPA Utils for Angular 2+
10 |
11 | Simliar Projects: [https://github.com/worktile/ngx-planet](https://github.com/worktile/ngx-planet) (Production Ready)
12 |
13 | based on [single-spa](https://github.com/CanopyTax/single-spa) && [single-spa-angular-cli](https://github.com/PlaceMe-SAS/single-spa-angular-cli)
14 |
15 | difference:
16 |
17 | - Host <-> Apps Architecture
18 | - Configurable App (no loader require)
19 | - Independent App in Different Repo and runnable
20 |
21 | 
22 |
23 | Examples: see in [examples/](examples)
24 |
25 | Online Demo:
26 |
27 | 1. [http://mooa.pho.im/](http://mooa.pho.im/) (host in AWS S3)
28 | 2. [http://mooa.phodal.com/](http://mooa.phodal.com/) (host in GitHub Pages)
29 |
30 | Features:
31 |
32 | 1. SPA by Configurable file, ex: ``apps.json``
33 | 2. Pluggable APP
34 | 3. support Child APP navigate
35 | 4. CLI for Generate Config
36 |
37 | Goal:
38 |
39 | 1. 构建插件化的 Web 开发平台,满足业务快速变化及分布式多团队并行开发的需求
40 | 2. 构建服务化的中间件,搭建高可用及高复用的前端微服务平台
41 | 3. 支持前端的独立交付及部署
42 |
43 | Usecase
44 | ---
45 |
46 | > If you are mooa, please provide you case to help this project.
47 |
48 | ### Theory
49 |
50 | [Research and Application of Micro Frontends](https://iopscience.iop.org/article/10.1088/1757-899X/490/6/062082/meta)
51 |
52 | Boilerplate
53 | ---
54 |
55 | App Boilerplate: [https://github.com/phodal/mooa-boilerplate](https://github.com/phodal/mooa-boilerplate)
56 |
57 | Usage
58 | ---
59 |
60 | ### 1. Install mooa
61 |
62 | in Host and Child App
63 |
64 | ```sh
65 | yarn add mooa
66 | ```
67 |
68 | ### 2. Config Host
69 |
70 | 1. add get Apps logic in AppComponent (``app.component.ts``)
71 |
72 | ```typescript
73 | constructor(private renderer: Renderer2, http: HttpClient, private router: Router) {
74 | // config Mooa
75 | this.mooa = new Mooa({
76 | mode: 'iframe',
77 | debug: false,
78 | parentElement: 'app-home',
79 | urlPrefix: 'app',
80 | switchMode: 'coexist',
81 | preload: true,
82 | includeZone: true
83 | });
84 | http.get('/assets/apps.json')
85 | .subscribe(
86 | data => {
87 | this.createApps(data);
88 | },
89 | err => console.log(err)
90 | );
91 | }
92 |
93 | private createApps(data: IAppOption[]) {
94 | data.map((config) => {
95 | this.mooa.registerApplication(config.name, config, mooaRouter.matchRoute(config.prefix));
96 | });
97 |
98 | this.router.events.subscribe((event) => {
99 | if (event instanceof NavigationEnd) {
100 | this.mooa.reRouter(event);
101 | }
102 | });
103 |
104 | return this.mooa.start();
105 | }
106 | ```
107 |
108 | ### 3. Config App
109 |
110 | 1. config App ``main.ts`` for load
111 |
112 | ```typescript
113 | import { mooaPlatform } from 'mooa';
114 |
115 | if (environment.production) {
116 | enableProdMode();
117 | }
118 |
119 | mooaPlatform.mount('help').then((opts) => {
120 | platformBrowserDynamic().bootstrapModule(AppModule).then((module) => {
121 | opts['attachUnmount'](module);
122 | });
123 | });
124 |
125 | ```
126 |
127 | 2. setup app routing in ``app.module.ts``
128 |
129 | ```typescript
130 | const appRoutes: Routes = [
131 | {path: '*', component: AppComponent}
132 | ...
133 | ];
134 |
135 | @NgModule({
136 | declarations: [
137 | AppComponent,
138 | ...
139 | ],
140 | imports: [
141 | BrowserModule,
142 | RouterModule.forRoot(
143 | appRoutes
144 | )
145 | ],
146 | providers: [
147 | {provide: APP_BASE_HREF, useValue: mooaPlatform.appBase()},
148 | ],
149 | bootstrap: [AppComponent]
150 | })
151 | export class AppModule {
152 |
153 | }
154 | ```
155 |
156 | 3. Add for handle URL Change in ``app.component.ts``
157 |
158 | ```typescript
159 | constructor(private router: Router) {
160 | mooaPlatform.handleRouterUpdate(this.router, 'app1');
161 | }
162 | ```
163 |
164 | ### 4. Setup apps.json with Mooa CLI
165 |
166 | 1. install global cli
167 |
168 | ```bash
169 | npm install -g mooa
170 | ```
171 |
172 | 2. create URL list files
173 |
174 | Examples: ``apps.txt``
175 |
176 | ```
177 | http://mooa.phodal.com/assets/app1
178 | http://mooa.phodal.com/assets/help
179 | ```
180 |
181 | 3. Generate Config File
182 |
183 | ```bash
184 | mooa -g apps.txt
185 | ```
186 |
187 | Examples:
188 |
189 | ```json
190 | [
191 | {
192 | "name": "app1",
193 | "selector": "app-app1",
194 | "baseScriptUrl": "/assets/app1",
195 | "styles": [
196 | "styles.bundle.css"
197 | ],
198 | "prefix": "app/app1",
199 | "scripts": [
200 | "inline.bundle.js",
201 | "polyfills.bundle.js",
202 | "main.bundle.js"
203 | ]
204 | }
205 | ]
206 | ```
207 |
208 | Mooa Config
209 | ---
210 |
211 | config in Host app's ``app.component.ts``
212 |
213 | ```typescript
214 | this.mooa = new Mooa({
215 | mode: 'iframe',
216 | debug: false,
217 | parentElement: 'app-home',
218 | urlPrefix: 'app',
219 | switchMode: 'coexist'
220 | })
221 | ```
222 |
223 | ### mode: 'iframe'
224 |
225 | use iframe as application container:
226 |
227 | ```html
228 |
229 |
230 |
231 | ```
232 |
233 | ### switchMode: 'coexist'
234 |
235 | hidden application when inactive:
236 |
237 | ```html
238 |
239 |
240 |
241 |
242 | ```
243 |
244 | For Angular Lazyload Module
245 | ---
246 |
247 | ``inline.bundle.js`` will load script for ``/`` path.
248 |
249 | So, just copy ``*.chunk.js`` files to ``dist/``, then deploy it.
250 |
251 |
252 | API
253 | ---
254 |
255 | ### registerApplicationByLink
256 |
257 | exmples:
258 |
259 | ```typescript
260 | mooa.registerApplicationByLink('help', '/assets/help', mooaRouter.matchRoute('help'));
261 | ```
262 |
263 | ### registerApplication
264 |
265 | ```typescript
266 | mooa.registerApplication(config.name, config, mooaRouter.matchRoute(config.prefix));
267 | ```
268 |
269 | hybrid
270 |
271 | ```typescript
272 | if (config.sourceType) {
273 | that.mooa.registerApplicationByLink(config.name, config.link, mooaRouter.matchRoute(config.name));
274 | } else {
275 | that.mooa.registerApplication(config.name, config, mooaRouter.matchRoute(config.prefix));
276 | }
277 | ```
278 |
279 | ### navigateTo Custom App
280 |
281 | ```typescript
282 | mooaPlatform.navigateTo({
283 | appName: 'help',
284 | router: 'home'
285 | });
286 | ```
287 |
288 | License
289 | ---
290 |
291 | [](http://ideas.phodal.com/)
292 |
293 | Copyright (c) 2013-2014 Christopher Simpkins
294 | Copyright (c) 2017-2018 Robin Coma Delperier
295 |
296 | © 2018 A [Phodal Huang](https://www.phodal.com)'s [Idea](http://github.com/phodal/ideas). This code is distributed under the MIT license. See `LICENSE` in this directory.
297 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | remote_theme: phodal/mifa-jekyll
2 |
--------------------------------------------------------------------------------
/deploy/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mooa-example-deploy",
3 | "version": "0.0.1",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "Phodal Huang",
10 | "license": "MIT",
11 | "dependencies": {
12 | "serverless-finch": "^1.4.0"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/deploy/serverless.yml:
--------------------------------------------------------------------------------
1 | service: mooa-example-deploy
2 |
3 | plugins:
4 | - serverless-finch
5 |
6 | provider:
7 | name: aws
8 | runtime: nodejs6.10
9 |
10 | custom:
11 | client:
12 | distributionFolder: ../examples/dist
13 | bucketName: mooa.pho.im
14 |
--------------------------------------------------------------------------------
/deploy/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | async@^1.5.2:
6 | version "1.5.2"
7 | resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
8 |
9 | bluebird@^3.0.6:
10 | version "3.5.1"
11 | resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9"
12 |
13 | lodash@^4.2.1:
14 | version "4.17.5"
15 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511"
16 |
17 | mime@^1.2.11:
18 | version "1.6.0"
19 | resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
20 |
21 | serverless-finch@^1.4.0:
22 | version "1.4.0"
23 | resolved "https://registry.yarnpkg.com/serverless-finch/-/serverless-finch-1.4.0.tgz#3a45f6b69739ca2888baab4d98c8cbd23502fbd4"
24 | dependencies:
25 | async "^1.5.2"
26 | bluebird "^3.0.6"
27 | lodash "^4.2.1"
28 | mime "^1.2.11"
29 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | [Architecture Decision Record](./adr)
2 |
3 | 
4 |
5 | [文档:原理【中文】版](https://github.com/phodal/micro-frontend)
6 |
--------------------------------------------------------------------------------
/docs/adr/0001-基本的-angular-应用替换.md:
--------------------------------------------------------------------------------
1 | # 1. 基本的 Angular 应用替换
2 |
3 | 日期: 2018-03-10
4 |
5 | ## 状态
6 |
7 | 列表:提议/通过/完成/已弃用/已取代
8 |
9 | 2018-03-10 提议
10 |
11 | 2018-03-12 完成
12 |
13 | ## 背景
14 |
15 | 在这里补充上下文...
16 |
17 | ## 决策
18 |
19 | 在这里补充上决策信息...
20 |
21 | ## 后果
22 |
23 | 在这里记录结果...
24 |
--------------------------------------------------------------------------------
/docs/adr/0002-重构项目目录.md:
--------------------------------------------------------------------------------
1 | # 2. 重构项目目录
2 |
3 | 日期: 2018-03-12
4 |
5 | ## 状态
6 |
7 | 列表:提议/通过/完成/已弃用/已取代
8 |
9 | 2018-03-12 提议
10 |
11 | 2018-03-14 完成
12 |
13 | ## 背景
14 |
15 | 在这里补充上下文...
16 |
17 | ## 决策
18 |
19 | 在这里补充上决策信息...
20 |
21 | ## 后果
22 |
23 | 在这里记录结果...
24 |
--------------------------------------------------------------------------------
/docs/adr/0003-使用自定义创建-element-方法.md:
--------------------------------------------------------------------------------
1 | # 3. 使用自定义创建 element 方法
2 |
3 | 日期: 2018-03-12
4 |
5 | ## 状态
6 |
7 | 列表:提议/通过/完成/已弃用/已取代
8 |
9 | 2018-03-12 提议
10 |
11 | 2018-03-12 完成
12 |
13 | ## 背景
14 |
15 | 在这里补充上下文...
16 |
17 | ## 决策
18 |
19 | 在这里补充上决策信息...
20 |
21 | ## 后果
22 |
23 | 在这里记录结果...
24 |
--------------------------------------------------------------------------------
/docs/adr/0004-支持子-app-路由.md:
--------------------------------------------------------------------------------
1 | # 4. 支持子 APP 路由
2 |
3 | 日期: 2018-03-12
4 |
5 | ## 状态
6 |
7 | 列表:提议/通过/完成/已弃用/已取代
8 |
9 | 2018-03-12 提议
10 |
11 | 2018-03-14 完成
12 |
13 | ## 背景
14 |
15 | 当前的 Mooa 的路由比较简单,需要进一步进行优化。
16 |
17 | ## 决策
18 |
19 | 使用正则表达式形式的路由
20 |
21 | 参考:
22 |
23 | 1. https://github.com/pillarjs/path-to-regexp
24 | 2. https://gist.github.com/yorickvP/2725801
25 | 3. https://github.com/phodal/lettuce/blob/master/src/router.js
26 | 4. https://github.com/millermedeiros/crossroads.js
27 |
28 | ## 后果
29 |
30 | 在这里记录结果...
31 |
--------------------------------------------------------------------------------
/docs/adr/0005-支持单个应用多个路由入口.md:
--------------------------------------------------------------------------------
1 | # 5. 支持单个应用多个路由入口
2 |
3 | 日期: 2018-03-13
4 |
5 | ## 状态
6 |
7 | 列表:提议/通过/完成/已弃用/已取代
8 |
9 | 2018-03-13 提议
10 |
11 | 2018-03-16 完成
12 |
13 | ## 背景
14 |
15 | 同一个 APP 的路由跳转在首页有问题,即 Host 跳转 app1 的 A 链接,再跳转 app2 的 B 链接
16 |
17 | Router Guard
18 |
19 | ## 决策
20 |
21 | 参考链接:https://github.com/mgechev/dynamic-route-creator/blob/master/app/components/app/app.ts
22 |
23 | ## 后果
24 |
25 | 在这里记录结果...
26 |
--------------------------------------------------------------------------------
/docs/adr/0006-创建调试用命令行.md:
--------------------------------------------------------------------------------
1 | # 6. 创建调试用命令行
2 |
3 | 日期: 2018-03-13
4 |
5 | ## 状态
6 |
7 | 列表:提议/通过/完成/已弃用/已取代
8 |
9 | 2018-03-13 提议
10 |
11 | 2018-03-13 完成
12 |
13 | ## 背景
14 |
15 | 在这里补充上下文...
16 |
17 | ## 决策
18 |
19 | 在这里补充上决策信息...
20 |
21 | ## 后果
22 |
23 | 在这里记录结果...
24 |
--------------------------------------------------------------------------------
/docs/adr/0007-base-path-支持问题.md:
--------------------------------------------------------------------------------
1 | # 7. base path 支持问题
2 |
3 | 日期: 2018-03-13
4 |
5 | ## 状态
6 |
7 | 列表:提议/通过/完成/已弃用/已取代
8 |
9 | 2018-03-13 提议
10 |
11 | 2018-03-16 完成
12 |
13 | ## 背景
14 |
15 | 在这里补充上下文...
16 |
17 | ## 决策
18 |
19 | 在这里补充上决策信息...
20 |
21 | ## 后果
22 |
23 | 在这里记录结果...
24 |
--------------------------------------------------------------------------------
/docs/adr/0008-生成打包脚本.md:
--------------------------------------------------------------------------------
1 | # 8. CLI 生成应用配置
2 |
3 | 日期: 2018-03-14
4 |
5 | ## 状态
6 |
7 | 列表:提议/通过/完成/已弃用/已取代
8 |
9 | 2018-03-14 提议
10 |
11 | 2018-03-16 完成
12 |
13 | ## 背景
14 |
15 | 在这里补充上下文...
16 |
17 | ## 决策
18 |
19 | 对于 Host 来说,CLI 拥有自己的配置文件:
20 |
21 | - 包含每个应用的线上地址
22 | - 包含 app 的基本配置
23 |
24 | ## 后果
25 |
26 | 创建配置文件,然后安装 ``mooa``,即:``npm install -g mooa``。
27 |
28 | ```
29 | mooa -g apps.txt
30 | ```
31 |
--------------------------------------------------------------------------------
/docs/adr/0009-考虑-loading-动画.md:
--------------------------------------------------------------------------------
1 | # 9. 考虑 loading 动画
2 |
3 | 日期: 2018-03-14
4 |
5 | ## 状态
6 |
7 | 列表:提议/通过/完成/已弃用/已取代
8 |
9 | 2018-03-14 提议
10 |
11 | 2018-03-17 完成
12 |
13 | ## 背景
14 |
15 | 在这里补充上下文...
16 |
17 | ## 决策
18 |
19 | 在这里补充上决策信息...
20 |
21 | ## 后果
22 |
23 | 在主工程中使用事件来自定义动画:
24 |
25 | ```typescript
26 | host: {
27 | '(window:mooa.bootstrapping)': 'loadingStart($event)',
28 | '(window:mooa.mounting)': 'loadingEnd($event)'
29 | }
30 | ```
31 |
--------------------------------------------------------------------------------
/docs/adr/0010-清空-parentelement-dom-下的所有-children.md:
--------------------------------------------------------------------------------
1 | # 10. 清空 parentElement DOM 下的所有 children
2 |
3 | 日期: 2018-03-14
4 |
5 | ## 状态
6 |
7 | 列表:提议/通过/完成/已弃用/已取代
8 |
9 | 2018-03-14 提议
10 |
11 | 2018-03-14 完成
12 |
13 | ## 背景
14 |
15 | parentElement 下会放置其它内容
16 |
17 | ## 决策
18 |
19 | 在这里补充上决策信息...
20 |
21 | ## 后果
22 |
23 | 在这里记录结果...
24 |
--------------------------------------------------------------------------------
/docs/adr/0011-支持组件之间跳转.md:
--------------------------------------------------------------------------------
1 | # 11. 支持组件之间跳转
2 |
3 | 日期: 2018-03-14
4 |
5 | ## 状态
6 |
7 | 列表:提议/通过/完成/已弃用/已取代
8 |
9 | 2018-03-14 提议
10 |
11 | 2018-03-14 完成
12 |
13 | ## 背景
14 |
15 | 在这里补充上下文...
16 |
17 | ## 决策
18 |
19 | 在这里补充上决策信息...
20 |
21 | ## 后果
22 |
23 | 实现方式:
24 |
25 | 在子组件中执行:
26 |
27 | ```typescript
28 | mooaPlatform.navigateTo({
29 | appName: 'help',
30 | router: 'home'
31 | });
32 | ```
33 |
--------------------------------------------------------------------------------
/docs/adr/0012-assets-目录合并问题.md:
--------------------------------------------------------------------------------
1 | # 12. assets 目录合并问题
2 |
3 | 日期: 2018-03-14
4 |
5 | ## 状态
6 |
7 | 列表:提议/通过/完成/已弃用/已取代
8 |
9 | 2018-03-14 提议
10 |
11 | ## 背景
12 |
13 | 在这里补充上下文...
14 |
15 | ## 决策
16 |
17 | 在这里补充上决策信息...
18 |
19 | ## 后果
20 |
21 | 在这里记录结果...
22 |
--------------------------------------------------------------------------------
/docs/adr/0013-修复-app-修改-host-url-带来的问题.md:
--------------------------------------------------------------------------------
1 | # 13. 修复 APP 修改 Host URL 带来的问题
2 |
3 | 日期: 2018-03-15
4 |
5 | ## 状态
6 |
7 | 列表:提议/通过/完成/已弃用/已取代
8 |
9 | 2018-03-15 提议
10 |
11 | 2018-03-24 已取代
12 |
13 | ## 背景
14 |
15 | 在这里补充上下文...
16 |
17 | ## 决策
18 |
19 | 在这里补充上决策信息...
20 |
21 | ## 后果
22 |
23 | 在这里记录结果...
24 |
--------------------------------------------------------------------------------
/docs/adr/0014-cli-支持创建项目.md:
--------------------------------------------------------------------------------
1 | # 14. CLI 支持创建项目
2 |
3 | 日期: 2018-03-16
4 |
5 | ## 状态
6 |
7 | 列表:提议/通过/完成/已弃用/已取代
8 |
9 | 2018-03-16 提议
10 |
11 | ## 背景
12 |
13 |
14 | ## 决策
15 |
16 | 对于 APP 来说,CLI 应支持以下功能:
17 |
18 | - 从 ``package.json`` 中获取 appName
19 | - 从 ``dist\`` 目录/配置服务器中,获取打包完后的文件,生成 ``apps.json`` 文件
20 | - 更新 ``main.ts`` 对于 MooaPlatform 的引用
21 | - 在 ``app.module.ts`` 中更新 ``APP_BASE_HREF``
22 | - 更新对 ``zone.js`` 文件的引用
23 |
24 | 或者直接拥有创建项目的脚本
25 |
26 | ## 后果
27 |
28 | 在这里记录结果...
29 |
--------------------------------------------------------------------------------
/docs/adr/0015-支持在-cli-中使用中文.md:
--------------------------------------------------------------------------------
1 | # 15. 支持在 CLI 中使用中文
2 |
3 | 日期: 2018-03-17
4 |
5 | ## 状态
6 |
7 | 列表:提议/通过/完成/已弃用/已取代
8 |
9 | 2018-03-17 提议
10 |
11 | 2018-03-24 已弃用
12 |
13 | ## 背景
14 |
15 | 在这里补充上下文...
16 |
17 | ## 决策
18 |
19 | 在这里补充上决策信息...
20 |
21 | ## 后果
22 |
23 | 在这里记录结果...
24 |
--------------------------------------------------------------------------------
/docs/adr/0016-使用-iframe-支持传统应用.md:
--------------------------------------------------------------------------------
1 | # 16. 使用 iFrame 支持传统应用
2 |
3 | 日期: 2018-03-18
4 |
5 | ## 状态
6 |
7 | 列表:提议/通过/完成/已弃用/已取代
8 |
9 | 2018-03-18 提议
10 |
11 | 2018-03-24 通过
12 |
13 | ## 背景
14 |
15 | 当前有一些框架不能直接使用 Angular 来接入,可以考虑使用 iFrame 来支持。
16 |
17 | ## 决策
18 |
19 | 相关文档:
20 |
21 | 1. https://gist.github.com/pbojinov/8965299
22 | 2. http://www.alloyteam.com/2012/08/lightweight-solution-for-an-iframe-cross-domain-communication/
23 |
24 | 3. https://github.com/ternarylabs/porthole
25 |
26 | 2018-03-20 基本的 iFrame 已完成,需要进一下更新事件逻辑。
27 |
28 | ## 后果
29 |
30 | 在这里记录结果...
31 |
--------------------------------------------------------------------------------
/docs/adr/0017-应用间切换保存最后的状态.md:
--------------------------------------------------------------------------------
1 | # 17. 应用间切换,保存最后的状态
2 |
3 | 日期: 2018-03-20
4 |
5 | ## 状态
6 |
7 | 列表:提议/通过/完成/已弃用/已取代
8 |
9 | 2018-03-20 提议
10 |
11 | 2018-03-24 通过
12 |
13 | 2018-03-24 已取代
14 |
15 | ## 背景
16 |
17 | 对于 Tab 式应用来说,用户在 Tab 间切换时需要 Load 应用最后的状态。
18 |
19 | ## 决策
20 |
21 | 当前没有合适的授存机制,考虑使用 iframe 保存应用的最后状态。
22 |
23 | ## 后果
24 |
25 | 在这里记录结果...
26 |
--------------------------------------------------------------------------------
/docs/adr/0018-支持-iframe-与-angular-应用切换使用.md:
--------------------------------------------------------------------------------
1 | # 18. 支持 iframe 与 Angular 应用同时使用
2 |
3 | 日期: 2018-03-23
4 |
5 | ## 状态
6 |
7 | 列表:提议/通过/完成/已弃用/已取代
8 |
9 | 2018-03-23 提议
10 |
11 | 2018-03-24 完成
12 |
13 | ## 背景
14 |
15 | 在这里补充上下文...
16 |
17 | ## 决策
18 |
19 | 在这里补充上决策信息...
20 |
21 | ## 后果
22 |
23 | 在这里记录结果...
24 |
--------------------------------------------------------------------------------
/docs/adr/0019-支持在配置文件中使用-link-和配置根据链接来区别.md:
--------------------------------------------------------------------------------
1 | # 19. 支持在配置文件中使用 link 和配置,根据链接来区别
2 |
3 | 日期: 2018-03-24
4 |
5 | ## 状态
6 |
7 | 2018-03-24 提议
8 |
9 | 2018-03-24 通过
10 |
11 | 2018-03-24 完成
12 |
13 | ## 背景
14 |
15 | 在这里补充上下文...
16 |
17 | ## 决策
18 |
19 | 在这里补充上决策信息...
20 |
21 | ## 后果
22 |
23 | 在这里记录结果...
24 |
--------------------------------------------------------------------------------
/docs/adr/0020-使用-cli-来将应用设置成-mooa-应用.md:
--------------------------------------------------------------------------------
1 | # 20. 使用 CLI 来将应用设置成 mooa 应用
2 |
3 | 日期: 2018-03-24
4 |
5 | ## 状态
6 |
7 | 2018-03-24 提议
8 |
9 | ## 背景
10 |
11 | 在这里补充上下文...
12 |
13 | ## 决策
14 |
15 | 在这里补充上决策信息...
16 |
17 | ## 后果
18 |
19 | 在这里记录结果...
20 |
--------------------------------------------------------------------------------
/docs/adr/0021-编写-mooa-client-来支持其它非-angular-应用.md:
--------------------------------------------------------------------------------
1 | # 21. 编写 Mooa Client 来支持其它非 Angular 应用
2 |
3 | 日期: 2018-03-24
4 |
5 | ## 状态
6 |
7 | 2018-03-24 提议
8 |
9 | ## 背景
10 |
11 | 在这里补充上下文...
12 |
13 | ## 决策
14 |
15 | 在这里补充上决策信息...
16 |
17 | ## 后果
18 |
19 | 在这里记录结果...
20 |
--------------------------------------------------------------------------------
/docs/adr/0022-提供一个预加载的-api来加载对应的-dom.md:
--------------------------------------------------------------------------------
1 | # 22. 提供一个预加载的 API,来加载对应的 DOM
2 |
3 | 日期: 2018-03-25
4 |
5 | ## 状态
6 |
7 | 2018-03-25 提议
8 |
9 | ## 背景
10 |
11 | 在这里补充上下文...
12 |
13 | ## 决策
14 |
15 | 在这里补充上决策信息...
16 |
17 | ## 后果
18 |
19 | 在这里记录结果...
20 |
--------------------------------------------------------------------------------
/docs/adr/0023-支持-zone.min.js-可配置来兼容应用.md:
--------------------------------------------------------------------------------
1 | # 23. 支持 zone.min.js 可配置来兼容应用
2 |
3 | 日期: 2018-03-25
4 |
5 | ## 状态
6 |
7 | 2018-03-25 提议
8 |
9 | 2018-03-26 完成
10 |
11 | ## 背景
12 |
13 | 在这里补充上下文...
14 |
15 | ## 决策
16 |
17 | 在这里补充上决策信息...
18 |
19 | ## 后果
20 |
21 | 在这里记录结果...
22 |
--------------------------------------------------------------------------------
/docs/adr/0024-通过-index.html-中的标签来匹配根节点元素.md:
--------------------------------------------------------------------------------
1 | # 24. 通过 index.html 中的标签来匹配根节点元素
2 |
3 | 日期: 2018-03-26
4 |
5 | ## 状态
6 |
7 | 2018-03-26 提议
8 |
9 | ## 背景
10 |
11 | 当前使用的是 app.name 作为标签的名字,原则上可以根据 来解析出 selector
12 |
13 | ## 决策
14 |
15 | 在这里补充上决策信息...
16 |
17 | ## 后果
18 |
19 | 在这里记录结果...
20 |
--------------------------------------------------------------------------------
/docs/adr/0025-支持可选参数来过滤-js-和-css.md:
--------------------------------------------------------------------------------
1 | # 25. 支持可选参数来过滤 js 和 css
2 |
3 | 日期: 2018-04-02
4 |
5 | ## 状态
6 |
7 | 2018-04-02 提议
8 |
9 | ## 背景
10 |
11 | 在这里补充上下文...
12 |
13 | ## 决策
14 |
15 | 在这里补充上决策信息...
16 |
17 | ## 后果
18 |
19 | 在这里记录结果...
20 |
--------------------------------------------------------------------------------
/docs/adr/0026-支持-lazyload-子应用.md:
--------------------------------------------------------------------------------
1 | # 26. 支持 lazyload 子应用
2 |
3 | 日期: 2018-04-18
4 |
5 | ## 状态
6 |
7 | 2018-04-18 提议
8 |
9 | ## 背景
10 |
11 | 在这里补充上下文...
12 |
13 | ## 决策
14 |
15 | 在这里补充上决策信息...
16 |
17 | ## 后果
18 |
19 | 在这里记录结果...
20 |
--------------------------------------------------------------------------------
/docs/adr/README.md:
--------------------------------------------------------------------------------
1 | # 架构决策记录
2 |
3 | * [1. 基本的-angular-应用替换](0001-基本的-angular-应用替换.md)
4 | * [2. 重构项目目录](0002-重构项目目录.md)
5 | * [3. 使用自定义创建-element-方法](0003-使用自定义创建-element-方法.md)
6 | * [4. 支持子-app-路由](0004-支持子-app-路由.md)
7 | * [5. 支持单个应用多个路由入口](0005-支持单个应用多个路由入口.md)
8 | * [6. 创建调试用命令行](0006-创建调试用命令行.md)
9 | * [7. base-path-支持问题](0007-base-path-支持问题.md)
10 | * [8. 生成打包脚本](0008-生成打包脚本.md)
11 | * [9. 考虑-loading-动画](0009-考虑-loading-动画.md)
12 | * [10. 清空-parentelement-dom-下的所有-children](0010-清空-parentelement-dom-下的所有-children.md)
13 | * [11. 支持组件之间跳转](0011-支持组件之间跳转.md)
14 | * [12. assets-目录合并问题](0012-assets-目录合并问题.md)
15 | * [13. 修复-app-修改-host-url-带来的问题](0013-修复-app-修改-host-url-带来的问题.md)
16 | * [14. cli-支持创建项目](0014-cli-支持创建项目.md)
17 | * [15. 支持在-cli-中使用中文](0015-支持在-cli-中使用中文.md)
18 | * [16. 使用-iframe-支持传统应用](0016-使用-iframe-支持传统应用.md)
19 | * [17. 应用间切换保存最后的状态](0017-应用间切换保存最后的状态.md)
20 | * [18. 支持-iframe-与-angular-应用切换使用](0018-支持-iframe-与-angular-应用切换使用.md)
21 | * [19. 支持在配置文件中使用-link-和配置根据链接来区别](0019-支持在配置文件中使用-link-和配置根据链接来区别.md)
22 | * [20. 使用-cli-来将应用设置成-mooa-应用](0020-使用-cli-来将应用设置成-mooa-应用.md)
23 | * [21. 编写-mooa-client-来支持其它非-angular-应用](0021-编写-mooa-client-来支持其它非-angular-应用.md)
24 | * [22. 提供一个预加载的-api来加载对应的-dom](0022-提供一个预加载的-api来加载对应的-dom.md)
25 | * [23. 支持-zone.min.js-可配置来兼容应用](0023-支持-zone.min.js-可配置来兼容应用.md)
26 | * [24. 通过-index.html-中的标签来匹配根节点元素](0024-通过-index.html-中的标签来匹配根节点元素.md)
27 | * [25. 支持可选参数来过滤-js-和-css](0025-支持可选参数来过滤-js-和-css.md)
28 | * [26. 支持-lazyload-子应用](0026-支持-lazyload-子应用.md)
--------------------------------------------------------------------------------
/docs/mooa-app.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phodal/mooa/96325e643ec54b47670c469c4ce2f4cd15f7a1fc/docs/mooa-app.jpg
--------------------------------------------------------------------------------
/docs/mooa-design.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phodal/mooa/96325e643ec54b47670c469c4ce2f4cd15f7a1fc/docs/mooa-design.jpg
--------------------------------------------------------------------------------
/docs/mooa-graph.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phodal/mooa/96325e643ec54b47670c469c4ce2f4cd15f7a1fc/docs/mooa-graph.jpg
--------------------------------------------------------------------------------
/docs/mooa-workflow.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phodal/mooa/96325e643ec54b47670c469c4ce2f4cd15f7a1fc/docs/mooa-workflow.sketch
--------------------------------------------------------------------------------
/docs/mooa.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phodal/mooa/96325e643ec54b47670c469c4ce2f4cd15f7a1fc/docs/mooa.png
--------------------------------------------------------------------------------
/docs/mooa.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phodal/mooa/96325e643ec54b47670c469c4ce2f4cd15f7a1fc/docs/mooa.sketch
--------------------------------------------------------------------------------
/docs/workflow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phodal/mooa/96325e643ec54b47670c469c4ce2f4cd15f7a1fc/docs/workflow.png
--------------------------------------------------------------------------------
/examples/.angular-cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "project": {
4 | "name": "mooa-examples"
5 | },
6 | "apps": [
7 | {
8 | "root": "src",
9 | "outDir": "dist",
10 | "assets": [
11 | "assets",
12 | "favicon.ico"
13 | ],
14 | "index": "index.html",
15 | "main": "main.ts",
16 | "polyfills": "polyfills.ts",
17 | "test": "test.ts",
18 | "tsconfig": "tsconfig.app.json",
19 | "testTsconfig": "tsconfig.spec.json",
20 | "prefix": "app",
21 | "styles": [
22 | "styles.css"
23 | ],
24 | "scripts": [],
25 | "environmentSource": "environments/environment.ts",
26 | "environments": {
27 | "dev": "environments/environment.ts",
28 | "prod": "environments/environment.prod.ts"
29 | }
30 | }
31 | ],
32 | "e2e": {
33 | "protractor": {
34 | "config": "./protractor.conf.js"
35 | }
36 | },
37 | "lint": [
38 | {
39 | "project": "src/tsconfig.app.json",
40 | "exclude": "**/node_modules/**"
41 | },
42 | {
43 | "project": "src/tsconfig.spec.json",
44 | "exclude": "**/node_modules/**"
45 | },
46 | {
47 | "project": "e2e/tsconfig.e2e.json",
48 | "exclude": "**/node_modules/**"
49 | }
50 | ],
51 | "test": {
52 | "karma": {
53 | "config": "./karma.conf.js"
54 | }
55 | },
56 | "defaults": {
57 | "styleExt": "css",
58 | "component": {}
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/examples/apps.txt:
--------------------------------------------------------------------------------
1 | http://mooa.phodal.com/assets/app1
2 | http://mooa.phodal.com/assets/help
3 |
--------------------------------------------------------------------------------
/examples/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/1.0/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular/cli'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage-istanbul-reporter'),
13 | require('@angular/cli/plugins/karma')
14 | ],
15 | client:{
16 | clearContext: false // leave Jasmine Spec Runner output visible in browser
17 | },
18 | coverageIstanbulReporter: {
19 | reports: [ 'html', 'lcovonly' ],
20 | fixWebpackSourcePaths: true
21 | },
22 | angularCli: {
23 | environment: 'dev'
24 | },
25 | reporters: ['progress', 'kjhtml'],
26 | port: 9876,
27 | colors: true,
28 | logLevel: config.LOG_INFO,
29 | autoWatch: true,
30 | browsers: ['Chrome'],
31 | singleRun: false
32 | });
33 | };
34 |
--------------------------------------------------------------------------------
/examples/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mooa",
3 | "version": "0.0.1",
4 | "license": "MIT",
5 | "scripts": {
6 | "ng": "ng",
7 | "start": "ng serve --host=0.0.0.0 --disable-host-check",
8 | "build": "ng build --prod",
9 | "test": "ng test",
10 | "lint": "ng lint",
11 | "e2e": "ng e2e"
12 | },
13 | "dependencies": {
14 | "@angular/animations": "^8.0.2",
15 | "@angular/common": "^8.0.2",
16 | "@angular/compiler": "^8.0.2",
17 | "@angular/core": "^8.0.2",
18 | "@angular/forms": "^8.0.2",
19 | "@angular/http": "^7.2.15",
20 | "@angular/platform-browser": "^8.0.2",
21 | "@angular/platform-browser-dynamic": "^8.0.2",
22 | "@angular/router": "^8.0.2",
23 | "core-js": "^3.1.4",
24 | "mooa": "^0.1.2",
25 | "rebirth-echarts": "^6.0.0",
26 | "rxjs": "^6.5.2",
27 | "zone.js": "^0.9.1"
28 | },
29 | "devDependencies": {
30 | "@angular/cli": "8.0.3",
31 | "@angular/compiler-cli": "^8.0.2",
32 | "@angular/language-service": "^8.0.2",
33 | "@types/jasmine": "~3.3.13",
34 | "@types/jasminewd2": "~2.0.2",
35 | "@types/node": "~12.0.10",
36 | "codelyzer": "^5.1.0",
37 | "jasmine-core": "~3.4.0",
38 | "jasmine-spec-reporter": "~4.2.1",
39 | "karma": "~4.1.0",
40 | "karma-chrome-launcher": "~2.2.0",
41 | "karma-coverage-istanbul-reporter": "^2.0.5",
42 | "karma-jasmine": "~2.0.1",
43 | "karma-jasmine-html-reporter": "^1.4.2",
44 | "protractor": "~5.4.2",
45 | "ts-node": "~8.3.0",
46 | "tslint": "~5.18.0",
47 | "typescript": "~3.5.2"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/examples/protractor.conf.js:
--------------------------------------------------------------------------------
1 | // Protractor configuration file, see link for more information
2 | // https://github.com/angular/protractor/blob/master/lib/config.ts
3 |
4 | const { SpecReporter } = require('jasmine-spec-reporter');
5 |
6 | exports.config = {
7 | allScriptsTimeout: 11000,
8 | specs: [
9 | './e2e/**/*.e2e-spec.ts'
10 | ],
11 | capabilities: {
12 | 'browserName': 'chrome'
13 | },
14 | directConnect: true,
15 | baseUrl: 'http://localhost:4200/',
16 | framework: 'jasmine',
17 | jasmineNodeOpts: {
18 | showColors: true,
19 | defaultTimeoutInterval: 30000,
20 | print: function() {}
21 | },
22 | onPrepare() {
23 | require('ts-node').register({
24 | project: 'e2e/tsconfig.e2e.json'
25 | });
26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/examples/src/app/app-routing.module.ts:
--------------------------------------------------------------------------------
1 | import {NgModule} from '@angular/core';
2 | import {RouterModule} from '@angular/router';
3 | import {ReaderComponent} from './reader/reader.component';
4 | import {GamesComponent} from './games/games.component';
5 | import {ShoppingComponent} from './shopping/shopping.component';
6 | import {HomeComponent} from './home/home.component';
7 | import {GraphComponent} from './graph/graph.component';
8 |
9 | @NgModule({
10 | imports: [
11 | RouterModule.forRoot([
12 | {
13 | path: '',
14 | component: GraphComponent
15 | },
16 | {
17 | path: 'reader',
18 | component: ReaderComponent
19 | },
20 | {
21 | path: 'games',
22 | component: GamesComponent
23 | },
24 | {
25 | path: 'shopping',
26 | component: ShoppingComponent
27 | },
28 | {
29 | path: 'app/:appName/:route',
30 | component: HomeComponent
31 | },
32 | {
33 | path: 'app/:appName',
34 | component: HomeComponent
35 | }
36 | ])
37 | ],
38 | exports: [RouterModule]
39 | })
40 | export class AppRoutingModule {
41 | }
42 |
--------------------------------------------------------------------------------
/examples/src/app/app.component.css:
--------------------------------------------------------------------------------
1 | :host {
2 | padding: 0 0 !important;
3 | height: 100% !important;
4 | }
5 |
6 | .nav-bar {
7 | width: 10%;
8 | padding-top: 2em;
9 | float: left;
10 | height: 100%;
11 | background-color: #333;
12 | }
13 |
14 | .app {
15 | width: 90%;
16 | float: right;
17 | height: 100%;
18 | }
19 |
--------------------------------------------------------------------------------
/examples/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/examples/src/app/app.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed, async } from '@angular/core/testing';
2 | import { AppComponent } from './app.component';
3 | describe('AppComponent', () => {
4 | beforeEach(async(() => {
5 | TestBed.configureTestingModule({
6 | declarations: [
7 | AppComponent
8 | ],
9 | }).compileComponents();
10 | }));
11 | it('should create the app', async(() => {
12 | const fixture = TestBed.createComponent(AppComponent);
13 | const app = fixture.debugElement.componentInstance;
14 | expect(app).toBeTruthy();
15 | }));
16 | it(`should have as title 'app'`, async(() => {
17 | const fixture = TestBed.createComponent(AppComponent);
18 | const app = fixture.debugElement.componentInstance;
19 | expect(app.title).toEqual('app');
20 | }));
21 | it('should render title in a h1 tag', async(() => {
22 | const fixture = TestBed.createComponent(AppComponent);
23 | fixture.detectChanges();
24 | const compiled = fixture.debugElement.nativeElement;
25 | expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!');
26 | }));
27 | });
28 |
--------------------------------------------------------------------------------
/examples/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import {Component, ElementRef, OnInit} from '@angular/core';
2 | import {HttpClient} from '@angular/common/http';
3 | import {NavigationEnd, Router} from '@angular/router';
4 | import {default as Mooa, mooaRouter} from '../../../src/mooa';
5 |
6 | declare const window: any;
7 |
8 | @Component({
9 | selector: 'app-root',
10 | templateUrl: './app.component.html',
11 | styleUrls: ['./app.component.css']
12 | })
13 | export class AppComponent implements OnInit {
14 | private mooa: Mooa;
15 | private myElement: ElementRef;
16 | private http: HttpClient;
17 |
18 | constructor(private httpClient: HttpClient, private router: Router, myElement: ElementRef) {
19 | this.http = httpClient;
20 | this.myElement = myElement;
21 | this.mooa = new Mooa({
22 | mode: 'iframe',
23 | debug: false,
24 | parentElement: 'app-home',
25 | urlPrefix: 'app',
26 | switchMode: 'coexist',
27 | preload: true,
28 | includeZone: true
29 | });
30 | }
31 |
32 | ngOnInit() {
33 | // this.mooaWithLink();
34 | this.mooaWithConfig();
35 | }
36 |
37 | private mooaWithLink () {
38 | const that = this;
39 |
40 | that.mooa.registerApplicationByLink('help', '/assets/help', mooaRouter.matchRoute('help'));
41 | that.mooa.registerApplicationByLink('app1', '/assets/app1', mooaRouter.matchRoute('app1'));
42 | this.mooa.start();
43 |
44 | this.router.events.subscribe((event: any) => {
45 | if (event instanceof NavigationEnd) {
46 | that.mooa.reRouter(event);
47 | }
48 | });
49 | }
50 |
51 | private mooaWithConfig () {
52 | const that = this;
53 |
54 | this.http.get('/assets/apps.json')
55 | .subscribe(data => {
56 | data.map((config) => {
57 | if (config.sourceType) {
58 | that.mooa.registerApplicationByLink(config.name, config.link, mooaRouter.matchRoute(config.name));
59 | } else {
60 | that.mooa.registerApplication(config.name, config, mooaRouter.matchRoute(config.prefix));
61 | }
62 | });
63 | this.mooa.start();
64 | },
65 | err => console.log(err)
66 | );
67 |
68 | this.router.events.subscribe((event: any) => {
69 | if (event instanceof NavigationEnd) {
70 | that.mooa.reRouter(event);
71 | }
72 | });
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/examples/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import {BrowserModule} from '@angular/platform-browser';
2 | import {NgModule} from '@angular/core';
3 |
4 | import {AppComponent} from './app.component';
5 | import {HttpClientModule} from '@angular/common/http';
6 | import {AppRoutingModule} from './app-routing.module';
7 | import {NavbarComponent} from './navbar/navbar.component';
8 | import {ReaderComponent} from './reader/reader.component';
9 | import {ShoppingComponent} from './shopping/shopping.component';
10 | import {GamesComponent} from './games/games.component';
11 | import {HomeModule} from './home/home.module';
12 | import {GraphModule} from './graph/graph.module';
13 | import {SafePipe} from './pipes/SafePipe';
14 |
15 | @NgModule({
16 | declarations: [
17 | AppComponent,
18 | NavbarComponent,
19 | ReaderComponent,
20 | ShoppingComponent,
21 | GamesComponent,
22 | SafePipe
23 | ],
24 | imports: [
25 | BrowserModule,
26 | HttpClientModule,
27 | AppRoutingModule,
28 | HomeModule,
29 | GraphModule
30 | ],
31 | providers: [HttpClientModule],
32 | bootstrap: [AppComponent]
33 | })
34 | export class AppModule {
35 | }
36 |
--------------------------------------------------------------------------------
/examples/src/app/games/games.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phodal/mooa/96325e643ec54b47670c469c4ce2f4cd15f7a1fc/examples/src/app/games/games.component.css
--------------------------------------------------------------------------------
/examples/src/app/games/games.component.html:
--------------------------------------------------------------------------------
1 |
61 |
--------------------------------------------------------------------------------
/examples/src/app/games/games.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { GamesComponent } from './games.component';
4 |
5 | describe('GamesComponent', () => {
6 | let component: GamesComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ GamesComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(GamesComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/examples/src/app/games/games.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-games',
5 | templateUrl: './games.component.html',
6 | styleUrls: ['./games.component.css']
7 | })
8 | export class GamesComponent implements OnInit {
9 |
10 | constructor() { }
11 |
12 | ngOnInit() {
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/examples/src/app/graph/graph.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phodal/mooa/96325e643ec54b47670c469c4ce2f4cd15f7a1fc/examples/src/app/graph/graph.component.css
--------------------------------------------------------------------------------
/examples/src/app/graph/graph.component.html:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/examples/src/app/graph/graph.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { GraphComponent } from './graph.component';
4 |
5 | describe('GraphComponent', () => {
6 | let component: GraphComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ GraphComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(GraphComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/examples/src/app/graph/graph.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-graph',
5 | templateUrl: './graph.component.html',
6 | styleUrls: ['./graph.component.css']
7 | })
8 | export class GraphComponent implements OnInit {
9 | radarData = {
10 | title: {
11 | text: 'Radar Chart'
12 | },
13 | tooltip: {},
14 | legend: {
15 | data: ['预算分配(Allocated Budget)', '实际开销(Actual Spending)']
16 | },
17 | radar: {
18 | // shape: 'circle',
19 | indicator: [
20 | { name: '销售(sales)', max: 6500 },
21 | { name: '管理(Administration)', max: 16000 },
22 | { name: '信息技术(Information Techology)', max: 30000 },
23 | { name: '客服(Customer Support)', max: 38000 },
24 | { name: '研发(Development)', max: 52000 },
25 | { name: '市场(Marketing)', max: 25000 }
26 | ]
27 | },
28 | series: [{
29 | name: '预算 vs 开销(Budget vs spending)',
30 | type: 'radar',
31 | // areaStyle: {normal: {}},
32 | data: [
33 | {
34 | value: [4300, 10000, 28000, 35000, 50000, 19000],
35 | name: '预算分配(Allocated Budget)'
36 | },
37 | {
38 | value: [5000, 14000, 28000, 31000, 42000, 21000],
39 | name: '实际开销(Actual Spending)'
40 | }
41 | ]
42 | }]
43 | }
44 |
45 | constructor() { }
46 |
47 | ngOnInit() {
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/examples/src/app/graph/graph.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import {RebirthEChartsModule} from 'rebirth-echarts';
4 | import {HomeComponent} from '../home/home.component';
5 | import {GraphComponent} from './graph.component';
6 |
7 | @NgModule({
8 | imports: [
9 | CommonModule,
10 | RebirthEChartsModule
11 | ],
12 | declarations: [GraphComponent],
13 | exports: [GraphComponent]
14 | })
15 | export class GraphModule { }
16 |
--------------------------------------------------------------------------------
/examples/src/app/home/home.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phodal/mooa/96325e643ec54b47670c469c4ce2f4cd15f7a1fc/examples/src/app/home/home.component.css
--------------------------------------------------------------------------------
/examples/src/app/home/home.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/examples/src/app/home/home.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { HomeComponent } from './home.component';
4 |
5 | describe('HomeComponent', () => {
6 | let component: HomeComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ HomeComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(HomeComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/examples/src/app/home/home.component.ts:
--------------------------------------------------------------------------------
1 | import {Component, ElementRef, ViewChild} from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-home',
5 | templateUrl: './home.component.html',
6 | styleUrls: ['./home.component.css'],
7 | host: {
8 | '(window:mooa.bootstrapping)': 'loadingStart($event)',
9 | '(window:mooa.mounting)': 'loadingEnd($event)'
10 | }
11 | })
12 | export class HomeComponent {
13 | @ViewChild('child') childElement: ElementRef;
14 |
15 | loadingStart() {
16 | this.childElement.nativeElement.innerHTML = `
17 | `;
25 | }
26 |
27 | loadingEnd() {
28 | const loadingSelector = this.childElement.nativeElement.querySelector('.loading');
29 | if (loadingSelector) {
30 | loadingSelector.style.display = 'none';
31 | }
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/examples/src/app/home/home.module.ts:
--------------------------------------------------------------------------------
1 | import {NgModule} from '@angular/core';
2 | import {CommonModule} from '@angular/common';
3 | import {HomeComponent} from './home.component';
4 |
5 | @NgModule({
6 | imports: [
7 | CommonModule,
8 | ],
9 | declarations: [HomeComponent],
10 | exports: [HomeComponent]
11 | })
12 | export class HomeModule {
13 | }
14 |
--------------------------------------------------------------------------------
/examples/src/app/navbar/navbar.component.css:
--------------------------------------------------------------------------------
1 | :host {
2 | height: 100%;
3 | }
4 |
5 | a {
6 | margin: 1em;
7 | }
8 |
9 | a.active {
10 | color: #fff;
11 | }
12 |
13 | p {
14 | margin-bottom: 0;
15 | }
16 |
17 | span {
18 | color: #fff;
19 | padding-left: 1.5em;
20 | font-size: 0.66em;
21 | }
22 |
--------------------------------------------------------------------------------
/examples/src/app/navbar/navbar.component.html:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/examples/src/app/navbar/navbar.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { NavbarComponent } from './navbar.component';
4 |
5 | describe('NavbarComponent', () => {
6 | let component: NavbarComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ NavbarComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(NavbarComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/examples/src/app/navbar/navbar.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-navbar',
5 | templateUrl: './navbar.component.html',
6 | styleUrls: ['./navbar.component.css']
7 | })
8 | export class NavbarComponent implements OnInit {
9 |
10 | constructor() { }
11 |
12 | ngOnInit() {
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/examples/src/app/pipes/SafePipe.ts:
--------------------------------------------------------------------------------
1 | import {Pipe, PipeTransform} from '@angular/core';
2 | import {DomSanitizer} from '@angular/platform-browser';
3 |
4 | @Pipe({name: 'safe'})
5 | export class SafePipe implements PipeTransform {
6 | constructor(private sanitizer: DomSanitizer) {
7 | }
8 |
9 | transform(url) {
10 | return this.sanitizer.bypassSecurityTrustResourceUrl(url);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/src/app/reader/reader.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phodal/mooa/96325e643ec54b47670c469c4ce2f4cd15f7a1fc/examples/src/app/reader/reader.component.css
--------------------------------------------------------------------------------
/examples/src/app/reader/reader.component.html:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 | Name
14 | Age
15 | Height
16 | Location
17 |
18 |
19 |
20 |
21 | Kevin Durant
22 | 27
23 | 2.06
24 | Washington, DC
25 |
26 |
27 | Stephen Curry
28 | 27
29 | 1,91
30 | Akron, OH
31 |
32 |
33 | Klay Thompson
34 | 25
35 | 2,01
36 | Los Angeles, CA
37 |
38 |
39 | Draymond Green
40 | 26
41 | 2,01
42 | Saginaw, MI
43 |
44 |
45 | Andre Iguodala
46 | 32
47 | 1,98
48 | Springfield, IL
49 |
50 |
51 | Anderson Varejão
52 | 33
53 | 2,08
54 | Colatina, ES
55 |
56 |
57 | Shaun Livingston
58 | 30
59 | 2,01
60 | Peoria, CA
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/examples/src/app/reader/reader.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { ReaderComponent } from './reader.component';
4 |
5 | describe('ReaderComponent', () => {
6 | let component: ReaderComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ ReaderComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(ReaderComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/examples/src/app/reader/reader.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-reader',
5 | templateUrl: './reader.component.html',
6 | styleUrls: ['./reader.component.css']
7 | })
8 | export class ReaderComponent implements OnInit {
9 |
10 | constructor() { }
11 |
12 | ngOnInit() {
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/examples/src/app/shopping/shopping.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phodal/mooa/96325e643ec54b47670c469c4ce2f4cd15f7a1fc/examples/src/app/shopping/shopping.component.css
--------------------------------------------------------------------------------
/examples/src/app/shopping/shopping.component.html:
--------------------------------------------------------------------------------
1 |
30 |
--------------------------------------------------------------------------------
/examples/src/app/shopping/shopping.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { ShoppingComponent } from './shopping.component';
4 |
5 | describe('ShoppingComponent', () => {
6 | let component: ShoppingComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ ShoppingComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(ShoppingComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/examples/src/app/shopping/shopping.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-shopping',
5 | templateUrl: './shopping.component.html',
6 | styleUrls: ['./shopping.component.css']
7 | })
8 | export class ShoppingComponent implements OnInit {
9 |
10 | constructor() { }
11 |
12 | ngOnInit() {
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/examples/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phodal/mooa/96325e643ec54b47670c469c4ce2f4cd15f7a1fc/examples/src/assets/.gitkeep
--------------------------------------------------------------------------------
/examples/src/assets/app1/0.chunk.js:
--------------------------------------------------------------------------------
1 | webpackJsonp([0],{HRRV:function(n,l,u){"use strict";Object.defineProperty(l,"__esModule",{value:!0});var t=u("LMZF"),e=function(){},o=function(){function n(){}return n.prototype.ngOnInit=function(){},n}(),c=t.Y({encapsulation:0,styles:[[""]],data:{}});function r(n){return t._17(0,[(n()(),t._0(0,0,null,null,1,"p",[],null,null,null,null,null)),(n()(),t._16(-1,null,["\n welcome works!\n"])),(n()(),t._16(-1,null,["\n"]))],null,null)}var i=t.W("app-welcome",o,function(n){return t._17(0,[(n()(),t._0(0,0,null,null,1,"app-welcome",[],null,null,null,r,c)),t.Z(1,114688,null,0,o,[],null,null)],function(n,l){n(l,1,0)},null)},{},{},[]),_=u("Un6q"),a=u("UHIZ"),f=function(){};u.d(l,"WelcomeModuleNgFactory",function(){return p});var p=t.X(e,[],function(n){return t._6([t._7(512,t.j,t.U,[[8,[i]],[3,t.j],t.v]),t._7(4608,_.i,_.h,[t.s,[2,_.m]]),t._7(512,_.b,_.b,[]),t._7(512,a.n,a.n,[[2,a.s],[2,a.k]]),t._7(512,f,f,[]),t._7(512,e,e,[]),t._7(1024,a.i,function(){return[[{path:"",component:o}]]},[])])})}});
--------------------------------------------------------------------------------
/examples/src/assets/app1/1.chunk.js:
--------------------------------------------------------------------------------
1 | webpackJsonp([1],{TYKr:function(n,l,u){"use strict";Object.defineProperty(l,"__esModule",{value:!0});var t=u("LMZF"),o=function(){},e=function(){function n(){}return n.prototype.ngOnInit=function(){},n}(),r=t.Y({encapsulation:0,styles:[[""]],data:{}});function i(n){return t._17(0,[(n()(),t._0(0,0,null,null,1,"p",[],null,null,null,null,null)),(n()(),t._16(-1,null,["\n home works!\n"])),(n()(),t._16(-1,null,["\n\n"])),(n()(),t._0(3,0,null,null,0,"img",[["alt","image"],["src","assets/avatar.jpg"]],null,null,null,null,null)),(n()(),t._16(-1,null,["\n"]))],null,null)}var a=t.W("app-home",e,function(n){return t._17(0,[(n()(),t._0(0,0,null,null,1,"app-home",[],null,null,null,i,r)),t.Z(1,114688,null,0,e,[],null,null)],function(n,l){n(l,1,0)},null)},{},{},[]),c=u("Un6q"),_=u("UHIZ"),p=function(){};u.d(l,"HomeModuleNgFactory",function(){return s});var s=t.X(o,[],function(n){return t._6([t._7(512,t.j,t.U,[[8,[a]],[3,t.j],t.v]),t._7(4608,c.i,c.h,[t.s,[2,c.m]]),t._7(512,c.b,c.b,[]),t._7(512,_.n,_.n,[[2,_.s],[2,_.k]]),t._7(512,p,p,[]),t._7(512,o,o,[]),t._7(1024,_.i,function(){return[[{path:"",component:e}]]},[])])})}});
--------------------------------------------------------------------------------
/examples/src/assets/app1/3rdpartylicenses.txt:
--------------------------------------------------------------------------------
1 | @angular/core@5.2.8
2 | MIT
3 | MIT
4 |
5 | @angular/common@5.2.8
6 | MIT
7 | MIT
8 |
9 | css-loader@0.28.10
10 | MIT
11 | Copyright JS Foundation and other contributors
12 |
13 | Permission is hereby granted, free of charge, to any person obtaining
14 | a copy of this software and associated documentation files (the
15 | 'Software'), to deal in the Software without restriction, including
16 | without limitation the rights to use, copy, modify, merge, publish,
17 | distribute, sublicense, and/or sell copies of the Software, and to
18 | permit persons to whom the Software is furnished to do so, subject to
19 | the following conditions:
20 |
21 | The above copyright notice and this permission notice shall be
22 | included in all copies or substantial portions of the Software.
23 |
24 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
25 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
27 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
28 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
29 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
30 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 |
32 | @angular/router@5.2.8
33 | MIT
34 | MIT
35 |
36 | core-js@2.5.3
37 | MIT
38 | Copyright (c) 2014-2017 Denis Pushkarev
39 |
40 | Permission is hereby granted, free of charge, to any person obtaining a copy
41 | of this software and associated documentation files (the "Software"), to deal
42 | in the Software without restriction, including without limitation the rights
43 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
44 | copies of the Software, and to permit persons to whom the Software is
45 | furnished to do so, subject to the following conditions:
46 |
47 | The above copyright notice and this permission notice shall be included in
48 | all copies or substantial portions of the Software.
49 |
50 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
51 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
52 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
53 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
54 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
55 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
56 | THE SOFTWARE.
57 |
58 | mooa@0.0.36
59 | MIT
60 | License (MIT)
61 |
62 | Copyright (c) 2013-2014 Christopher Simpkins [single-spa](https://github.com/CanopyTax/single-spa)
63 | Copyright (c) 2018 Phodal HUANG
64 |
65 | Permission is hereby granted, free of charge, to any person obtaining a copy
66 | of this software and associated documentation files (the "Software"), to deal
67 | in the Software without restriction, including without limitation the rights
68 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
69 | copies of the Software, and to permit persons to whom the Software is
70 | furnished to do so, subject to the following conditions:
71 |
72 | The above copyright notice and this permission notice shall be included in
73 | all copies or substantial portions of the Software.
74 |
75 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
76 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
77 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
78 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
79 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
80 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
81 | THE SOFTWARE.
82 |
83 | LICENSE(APACHE 2.0)
84 | Copyright (c) 2017-2018 Robin Coma Delperier [single-spa-angular-cli](https://github.com/PlaceMe-SAS/single-spa-angular-cli)
85 |
86 | @angular/platform-browser@5.2.8
87 | MIT
88 | MIT
89 |
90 | @angular/platform-browser-dynamic@5.2.8
91 | MIT
92 | MIT
93 |
94 | webpack@3.10.0
95 | MIT
96 | Copyright JS Foundation and other contributors
97 |
98 | Permission is hereby granted, free of charge, to any person obtaining
99 | a copy of this software and associated documentation files (the
100 | 'Software'), to deal in the Software without restriction, including
101 | without limitation the rights to use, copy, modify, merge, publish,
102 | distribute, sublicense, and/or sell copies of the Software, and to
103 | permit persons to whom the Software is furnished to do so, subject to
104 | the following conditions:
105 |
106 | The above copyright notice and this permission notice shall be
107 | included in all copies or substantial portions of the Software.
108 |
109 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
110 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
111 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
112 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
113 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
114 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
115 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/examples/src/assets/app1/assets/avatar.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phodal/mooa/96325e643ec54b47670c469c4ce2f4cd15f7a1fc/examples/src/assets/app1/assets/avatar.jpg
--------------------------------------------------------------------------------
/examples/src/assets/app1/cors.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | from http.server import HTTPServer, SimpleHTTPRequestHandler, test
3 | import sys
4 |
5 | class CORSRequestHandler (SimpleHTTPRequestHandler):
6 | def end_headers (self):
7 | self.send_header('Access-Control-Allow-Origin', '*')
8 | SimpleHTTPRequestHandler.end_headers(self)
9 |
10 | if __name__ == '__main__':
11 | test(CORSRequestHandler, HTTPServer, port=int(sys.argv[1]) if len(sys.argv) > 1 else 8000)
12 |
--------------------------------------------------------------------------------
/examples/src/assets/app1/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phodal/mooa/96325e643ec54b47670c469c4ce2f4cd15f7a1fc/examples/src/assets/app1/favicon.ico
--------------------------------------------------------------------------------
/examples/src/assets/app1/index.html:
--------------------------------------------------------------------------------
1 | App1
--------------------------------------------------------------------------------
/examples/src/assets/app1/inline.bundle.js:
--------------------------------------------------------------------------------
1 | !function(e){var n=window.webpackJsonp;window.webpackJsonp=function(r,c,u){for(var i,a,f,l=0,s=[];lhelp
2 |
--------------------------------------------------------------------------------
/examples/src/assets/help/inline.bundle.js:
--------------------------------------------------------------------------------
1 | !function(e){var n=window.webpackJsonp;window.webpackJsonp=function(r,c,u){for(var i,a,f,l=0,s=[];l
2 |
3 |
4 |
5 | App1
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/src/assets/mifa.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Mifa v0.2.0
3 | * https://github.com/phodal/mifa#readme
4 | *
5 | * Copyright (c) 2018 Phodal Huang
6 | * Licensed under the MIT license
7 | */
8 |
9 | .color-primary {
10 | color: #384452;
11 | background-color: #384452;
12 | }
13 |
14 | .color-grey {
15 | color: #d1d8df;
16 | background-color: #d1d8df;
17 | }
18 |
19 | .color-green {
20 | color: #1abc9c;
21 | background-color: #1abc9c;
22 | }
23 |
24 | .color-dark-grey {
25 | color: #5e6772;
26 | background-color: #5e6772;
27 | }
28 |
29 | .color-blue {
30 | color: #23B7F3;
31 | background-color: #23B7F3;
32 | }
33 |
34 | .color-code-grey {
35 | color: #eef1f5;
36 | background-color: #eef1f5;
37 | }
38 |
39 | .color-red {
40 | color: #f53d3d;
41 | background-color: #f53d3d;
42 | }
43 |
44 | .color-yellow {
45 | color: #ffff3a;
46 | background-color: #ffff3a;
47 | }
48 |
49 | .color-light-grey {
50 | color: #fdfdfd;
51 | background-color: #fdfdfd;
52 | }
53 |
54 | *,
55 | *:after,
56 | *:before {
57 | box-sizing: inherit;
58 | }
59 |
60 | html {
61 | box-sizing: border-box;
62 | font-size: 62.5%;
63 | }
64 |
65 | body {
66 | color: #384452;
67 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimSun, sans-serif;
68 | font-size: 1.6em;
69 | font-weight: 300;
70 | letter-spacing: .01em;
71 | line-height: 1.6;
72 | }
73 |
74 | blockquote {
75 | border-left: 0.3rem solid #1abc9c;
76 | margin-left: 0;
77 | margin-right: 0;
78 | padding: 1rem 1.5rem;
79 | }
80 |
81 | blockquote *:last-child {
82 | margin-bottom: 0;
83 | }
84 |
85 | q:before {
86 | color: #1abc9c;
87 | content: open-quote;
88 | font-size: 4em;
89 | line-height: 0.1em;
90 | margin-right: 0.25em;
91 | vertical-align: -0.4em;
92 | }
93 |
94 | q:after {
95 | content: none;
96 | }
97 |
98 | .button,
99 | button,
100 | input[type='button'],
101 | input[type='reset'],
102 | input[type='submit'] {
103 | background-color: #384452;
104 | border: 0.1rem solid #384452;
105 | border-radius: .4rem;
106 | color: #fff;
107 | cursor: pointer;
108 | display: inline-block;
109 | font-size: 1.1rem;
110 | font-weight: 700;
111 | height: 3.8rem;
112 | letter-spacing: .1rem;
113 | line-height: 3.8rem;
114 | padding: 0 3.0rem;
115 | text-align: center;
116 | text-decoration: none;
117 | text-transform: uppercase;
118 | white-space: nowrap;
119 | }
120 |
121 | .button:focus, .button:hover,
122 | button:focus,
123 | button:hover,
124 | input[type='button']:focus,
125 | input[type='button']:hover,
126 | input[type='reset']:focus,
127 | input[type='reset']:hover,
128 | input[type='submit']:focus,
129 | input[type='submit']:hover {
130 | background-color: #1abc9c;
131 | border-color: #1abc9c;
132 | color: #fff;
133 | outline: 0;
134 | }
135 |
136 | .button[disabled],
137 | button[disabled],
138 | input[type='button'][disabled],
139 | input[type='reset'][disabled],
140 | input[type='submit'][disabled] {
141 | cursor: default;
142 | opacity: .5;
143 | }
144 |
145 | .button[disabled]:focus, .button[disabled]:hover,
146 | button[disabled]:focus,
147 | button[disabled]:hover,
148 | input[type='button'][disabled]:focus,
149 | input[type='button'][disabled]:hover,
150 | input[type='reset'][disabled]:focus,
151 | input[type='reset'][disabled]:hover,
152 | input[type='submit'][disabled]:focus,
153 | input[type='submit'][disabled]:hover {
154 | background-color: #384452;
155 | border-color: #384452;
156 | }
157 |
158 | .button.button-outline,
159 | button.button-outline,
160 | input[type='button'].button-outline,
161 | input[type='reset'].button-outline,
162 | input[type='submit'].button-outline {
163 | background-color: transparent;
164 | color: #384452;
165 | }
166 |
167 | .button.button-outline:focus, .button.button-outline:hover,
168 | button.button-outline:focus,
169 | button.button-outline:hover,
170 | input[type='button'].button-outline:focus,
171 | input[type='button'].button-outline:hover,
172 | input[type='reset'].button-outline:focus,
173 | input[type='reset'].button-outline:hover,
174 | input[type='submit'].button-outline:focus,
175 | input[type='submit'].button-outline:hover {
176 | background-color: transparent;
177 | border-color: #1abc9c;
178 | color: #1abc9c;
179 | }
180 |
181 | .button.button-outline[disabled]:focus, .button.button-outline[disabled]:hover,
182 | button.button-outline[disabled]:focus,
183 | button.button-outline[disabled]:hover,
184 | input[type='button'].button-outline[disabled]:focus,
185 | input[type='button'].button-outline[disabled]:hover,
186 | input[type='reset'].button-outline[disabled]:focus,
187 | input[type='reset'].button-outline[disabled]:hover,
188 | input[type='submit'].button-outline[disabled]:focus,
189 | input[type='submit'].button-outline[disabled]:hover {
190 | border-color: inherit;
191 | color: #384452;
192 | }
193 |
194 | .button.button-clear,
195 | button.button-clear,
196 | input[type='button'].button-clear,
197 | input[type='reset'].button-clear,
198 | input[type='submit'].button-clear {
199 | background-color: transparent;
200 | border-color: transparent;
201 | color: #384452;
202 | }
203 |
204 | .button.button-clear:focus, .button.button-clear:hover,
205 | button.button-clear:focus,
206 | button.button-clear:hover,
207 | input[type='button'].button-clear:focus,
208 | input[type='button'].button-clear:hover,
209 | input[type='reset'].button-clear:focus,
210 | input[type='reset'].button-clear:hover,
211 | input[type='submit'].button-clear:focus,
212 | input[type='submit'].button-clear:hover {
213 | background-color: transparent;
214 | border-color: transparent;
215 | color: #1abc9c;
216 | }
217 |
218 | .button.button-clear[disabled]:focus, .button.button-clear[disabled]:hover,
219 | button.button-clear[disabled]:focus,
220 | button.button-clear[disabled]:hover,
221 | input[type='button'].button-clear[disabled]:focus,
222 | input[type='button'].button-clear[disabled]:hover,
223 | input[type='reset'].button-clear[disabled]:focus,
224 | input[type='reset'].button-clear[disabled]:hover,
225 | input[type='submit'].button-clear[disabled]:focus,
226 | input[type='submit'].button-clear[disabled]:hover {
227 | color: #384452;
228 | }
229 |
230 | code {
231 | background: #eef1f5;
232 | border-radius: .4rem;
233 | font-size: 86%;
234 | margin: 0 .2rem;
235 | padding: .2rem .5rem;
236 | white-space: nowrap;
237 | }
238 |
239 | pre {
240 | background: #eef1f5;
241 | border-left: 0.3rem solid #1abc9c;
242 | overflow-y: hidden;
243 | }
244 |
245 | pre > code {
246 | border-radius: 0;
247 | display: block;
248 | padding: 1rem 1.5rem;
249 | white-space: pre;
250 | }
251 |
252 | hr {
253 | border: 0;
254 | border-top: 0.1rem solid #384452;
255 | margin: 3.0rem 0;
256 | }
257 |
258 | input[type='email'],
259 | input[type='number'],
260 | input[type='password'],
261 | input[type='search'],
262 | input[type='tel'],
263 | input[type='text'],
264 | input[type='url'],
265 | input:not([type]),
266 | textarea,
267 | select {
268 | -webkit-appearance: none;
269 | -moz-appearance: none;
270 | appearance: none;
271 | background-color: transparent;
272 | border: 0.1rem solid #d1d8df;
273 | border-radius: .4rem;
274 | box-shadow: none;
275 | box-sizing: inherit;
276 | height: 3.8rem;
277 | padding: .6rem 1.0rem;
278 | width: 100%;
279 | }
280 |
281 | input[type='email']:focus,
282 | input[type='number']:focus,
283 | input[type='password']:focus,
284 | input[type='search']:focus,
285 | input[type='tel']:focus,
286 | input[type='text']:focus,
287 | input[type='url']:focus,
288 | input:not([type]):focus,
289 | textarea:focus,
290 | select:focus {
291 | border-color: #1abc9c;
292 | outline: 0;
293 | }
294 |
295 | select {
296 | background: url('data:image/svg+xml;utf8, ') center right no-repeat;
297 | padding-right: 3.0rem;
298 | }
299 |
300 | select:focus {
301 | background-image: url('data:image/svg+xml;utf8, ');
302 | }
303 |
304 | textarea {
305 | min-height: 6.5rem;
306 | }
307 |
308 | label,
309 | legend {
310 | display: block;
311 | font-size: 1.6rem;
312 | font-weight: 700;
313 | margin-bottom: .5rem;
314 | }
315 |
316 | fieldset {
317 | border-width: 0;
318 | padding: 0;
319 | }
320 |
321 | input[type='checkbox'],
322 | input[type='radio'] {
323 | display: inline;
324 | }
325 |
326 | .label-inline {
327 | display: inline-block;
328 | font-weight: normal;
329 | margin-left: .5rem;
330 | }
331 |
332 | .container {
333 | margin: 0 auto;
334 | max-width: 112.0rem;
335 | padding: 0 2.0rem;
336 | position: relative;
337 | width: 100%;
338 | }
339 |
340 | .row {
341 | display: flex;
342 | flex-direction: column;
343 | padding: 0;
344 | width: 100%;
345 | }
346 |
347 | .row.row-no-padding {
348 | padding: 0;
349 | }
350 |
351 | .row.row-no-padding > .column {
352 | padding: 0;
353 | }
354 |
355 | .row.row-wrap {
356 | flex-wrap: wrap;
357 | }
358 |
359 | .row.row-top {
360 | align-items: flex-start;
361 | }
362 |
363 | .row.row-bottom {
364 | align-items: flex-end;
365 | }
366 |
367 | .row.row-center {
368 | align-items: center;
369 | }
370 |
371 | .row.row-stretch {
372 | align-items: stretch;
373 | }
374 |
375 | .row.row-baseline {
376 | align-items: baseline;
377 | }
378 |
379 | .row .column {
380 | display: block;
381 | flex: 1 1 auto;
382 | margin-left: 0;
383 | max-width: 100%;
384 | width: 100%;
385 | }
386 |
387 | .row .column.column-offset-10 {
388 | margin-left: 10%;
389 | }
390 |
391 | .row .column.column-offset-20 {
392 | margin-left: 20%;
393 | }
394 |
395 | .row .column.column-offset-25 {
396 | margin-left: 25%;
397 | }
398 |
399 | .row .column.column-offset-33, .row .column.column-offset-34 {
400 | margin-left: 33.3333%;
401 | }
402 |
403 | .row .column.column-offset-50 {
404 | margin-left: 50%;
405 | }
406 |
407 | .row .column.column-offset-66, .row .column.column-offset-67 {
408 | margin-left: 66.6666%;
409 | }
410 |
411 | .row .column.column-offset-75 {
412 | margin-left: 75%;
413 | }
414 |
415 | .row .column.column-offset-80 {
416 | margin-left: 80%;
417 | }
418 |
419 | .row .column.column-offset-90 {
420 | margin-left: 90%;
421 | }
422 |
423 | .row .column.column-10 {
424 | flex: 0 0 10%;
425 | max-width: 10%;
426 | }
427 |
428 | .row .column.column-20 {
429 | flex: 0 0 20%;
430 | max-width: 20%;
431 | }
432 |
433 | .row .column.column-25 {
434 | flex: 0 0 25%;
435 | max-width: 25%;
436 | }
437 |
438 | .row .column.column-33, .row .column.column-34 {
439 | flex: 0 0 33.3333%;
440 | max-width: 33.3333%;
441 | }
442 |
443 | .row .column.column-40 {
444 | flex: 0 0 40%;
445 | max-width: 40%;
446 | }
447 |
448 | .row .column.column-50 {
449 | flex: 0 0 50%;
450 | max-width: 50%;
451 | }
452 |
453 | .row .column.column-60 {
454 | flex: 0 0 60%;
455 | max-width: 60%;
456 | }
457 |
458 | .row .column.column-66, .row .column.column-67 {
459 | flex: 0 0 66.6666%;
460 | max-width: 66.6666%;
461 | }
462 |
463 | .row .column.column-75 {
464 | flex: 0 0 75%;
465 | max-width: 75%;
466 | }
467 |
468 | .row .column.column-80 {
469 | flex: 0 0 80%;
470 | max-width: 80%;
471 | }
472 |
473 | .row .column.column-90 {
474 | flex: 0 0 90%;
475 | max-width: 90%;
476 | }
477 |
478 | .row .column .column-top {
479 | align-self: flex-start;
480 | }
481 |
482 | .row .column .column-bottom {
483 | align-self: flex-end;
484 | }
485 |
486 | .row .column .column-center {
487 | -ms-grid-row-align: center;
488 | align-self: center;
489 | }
490 |
491 | @media (min-width: 40rem) {
492 | .row {
493 | flex-direction: row;
494 | margin-left: -1.0rem;
495 | width: calc(100% + 2.0rem);
496 | }
497 | .row .column {
498 | margin-bottom: inherit;
499 | padding: 0 1.0rem;
500 | }
501 | }
502 |
503 | img {
504 | max-width: 100%;
505 | }
506 |
507 | a {
508 | color: #1abc9c;
509 | text-decoration: none;
510 | }
511 |
512 | a:focus, a:hover {
513 | color: #23B7F3;
514 | }
515 |
516 | dl,
517 | ol,
518 | ul {
519 | list-style: none;
520 | margin-top: 0;
521 | padding-left: 0;
522 | }
523 |
524 | dl dl,
525 | dl ol,
526 | dl ul,
527 | ol dl,
528 | ol ol,
529 | ol ul,
530 | ul dl,
531 | ul ol,
532 | ul ul {
533 | font-size: 90%;
534 | margin: 1.5rem 0 1.5rem 3.0rem;
535 | }
536 |
537 | ol {
538 | list-style: decimal inside;
539 | }
540 |
541 | ul {
542 | list-style: circle inside;
543 | }
544 |
545 | li {
546 | color: #384452;
547 | }
548 |
549 | .button,
550 | button,
551 | dd,
552 | dt,
553 | li {
554 | margin-bottom: 1.0rem;
555 | }
556 |
557 | fieldset,
558 | input,
559 | select,
560 | textarea {
561 | margin-bottom: 1.5rem;
562 | }
563 |
564 | blockquote,
565 | dl,
566 | figure,
567 | form,
568 | ol,
569 | p,
570 | pre,
571 | table,
572 | ul {
573 | margin-bottom: 2.5rem;
574 | }
575 |
576 | table {
577 | border-spacing: 0;
578 | width: 100%;
579 | }
580 |
581 | table thead th,
582 | thead th {
583 | border-top: 0.1rem solid #d1d8df;
584 | background: #fdfdfd;
585 | }
586 |
587 | tbody tr:nth-child(even) {
588 | background: #fdfdfd;
589 | }
590 |
591 | tbody tr:nth-child(odd) {
592 | background: #eef1f5;
593 | }
594 |
595 | td,
596 | th {
597 | border-bottom: 0.1rem solid #d1d8df;
598 | padding: 1.2rem 1.5rem;
599 | text-align: left;
600 | }
601 |
602 | u {
603 | text-decoration: none;
604 | border-bottom: 1px dashed;
605 | }
606 |
607 | a {
608 | text-decoration: none;
609 | }
610 |
611 | u {
612 | background: #ffff3a;
613 | }
614 |
615 | b,
616 | strong {
617 | font-weight: bold;
618 | }
619 |
620 | p {
621 | margin-top: 0;
622 | }
623 |
624 | h1,
625 | h2,
626 | h3,
627 | h4,
628 | h5,
629 | h6 {
630 | font-weight: 300;
631 | letter-spacing: -.1rem;
632 | margin-bottom: 2.0rem;
633 | margin-top: 0;
634 | }
635 |
636 | h1 {
637 | font-size: 4.0rem;
638 | line-height: 1.2;
639 | }
640 |
641 | h2 {
642 | font-size: 3.2rem;
643 | line-height: 1.25;
644 | }
645 |
646 | h3 {
647 | font-size: 2.8rem;
648 | line-height: 1.3;
649 | }
650 |
651 | h4 {
652 | font-size: 2.4rem;
653 | letter-spacing: -.08rem;
654 | line-height: 1.35;
655 | }
656 |
657 | h5 {
658 | font-size: 2.0rem;
659 | letter-spacing: -.05rem;
660 | line-height: 1.5;
661 | }
662 |
663 | h6 {
664 | font-size: 1.6rem;
665 | letter-spacing: 0;
666 | line-height: 1.4;
667 | }
668 |
669 | .clearfix:after {
670 | clear: both;
671 | content: ' ';
672 | display: table;
673 | }
674 |
675 | .float-left {
676 | float: left;
677 | }
678 |
679 | .float-right {
680 | float: right;
681 | }
682 |
683 |
--------------------------------------------------------------------------------
/examples/src/assets/styles.css:
--------------------------------------------------------------------------------
1 | .loading {
2 | width: 100%;
3 | height: 100%;
4 | margin: 0 auto;
5 | }
6 |
7 | .spinner {
8 | margin: 100px auto 0;
9 | width: 200px;
10 | text-align: center;
11 | }
12 |
13 | .spinner > div {
14 | width: 36px;
15 | height: 36px;
16 | color: #000000;
17 | background-color: #333;
18 | border-radius: 100%;
19 | display: inline-block;
20 | -webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both;
21 | animation: sk-bouncedelay 1.4s infinite ease-in-out both;
22 | }
23 |
24 | .spinner .bounce1 {
25 | -webkit-animation-delay: -0.32s;
26 | animation-delay: -0.32s;
27 | }
28 |
29 | .spinner .bounce2 {
30 | -webkit-animation-delay: -0.16s;
31 | animation-delay: -0.16s;
32 | }
33 |
34 | @-webkit-keyframes sk-bouncedelay {
35 | 0%, 80%, 100% {
36 | -webkit-transform: scale(0)
37 | }
38 | 40% {
39 | -webkit-transform: scale(1.0)
40 | }
41 | }
42 |
43 | @keyframes sk-bouncedelay {
44 | 0%, 80%, 100% {
45 | -webkit-transform: scale(0);
46 | transform: scale(0);
47 | }
48 | 40% {
49 | -webkit-transform: scale(1.0);
50 | transform: scale(1.0);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/examples/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true
3 | };
4 |
--------------------------------------------------------------------------------
/examples/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // The file contents for the current environment will overwrite these during build.
2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do
3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead.
4 | // The list of which env maps to which file can be found in `.angular-cli.json`.
5 |
6 | export const environment = {
7 | production: false
8 | };
9 |
--------------------------------------------------------------------------------
/examples/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phodal/mooa/96325e643ec54b47670c469c4ce2f4cd15f7a1fc/examples/src/favicon.ico
--------------------------------------------------------------------------------
/examples/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | MooaExamples
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/examples/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 |
4 | import { AppModule } from './app/app.module';
5 | import { environment } from './environments/environment';
6 |
7 | if (environment.production) {
8 | enableProdMode();
9 | }
10 |
11 | platformBrowserDynamic().bootstrapModule(AppModule)
12 | .catch(err => console.log(err));
13 |
--------------------------------------------------------------------------------
/examples/src/polyfills.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This file includes polyfills needed by Angular and is loaded before the app.
3 | * You can add your own extra polyfills to this file.
4 | *
5 | * This file is divided into 2 sections:
6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
8 | * file.
9 | *
10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
13 | *
14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
15 | */
16 |
17 | /***************************************************************************************************
18 | * BROWSER POLYFILLS
19 | */
20 |
21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/
22 | import 'core-js/es6/symbol';
23 | import 'core-js/es6/object';
24 | import 'core-js/es6/function';
25 | import 'core-js/es6/parse-int';
26 | import 'core-js/es6/parse-float';
27 | import 'core-js/es6/number';
28 | import 'core-js/es6/math';
29 | import 'core-js/es6/string';
30 | import 'core-js/es6/date';
31 | import 'core-js/es6/array';
32 | import 'core-js/es6/regexp';
33 | import 'core-js/es6/map';
34 | import 'core-js/es6/weak-map';
35 | import 'core-js/es6/set';
36 |
37 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */
38 | // import 'classlist.js'; // Run `npm install --save classlist.js`.
39 |
40 | /** IE10 and IE11 requires the following for the Reflect API. */
41 | import 'core-js/es6/reflect';
42 |
43 |
44 | /** Evergreen browsers require these. **/
45 | // Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
46 | import 'core-js/es7/reflect';
47 |
48 |
49 | /**
50 | * Required to support Web Animations `@angular/platform-browser/animations`.
51 | * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation
52 | **/
53 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`.
54 |
55 |
56 |
57 | /***************************************************************************************************
58 | * Zone JS is required by default for Angular itself.
59 | */
60 | import 'zone.js/dist/zone'; // Included with Angular CLI.
61 |
62 |
63 |
64 | /***************************************************************************************************
65 | * APPLICATION IMPORTS
66 | */
67 |
--------------------------------------------------------------------------------
/examples/src/styles.css:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 | body, html {
3 | margin: 0;
4 | padding: 0;
5 | height: 100%;
6 | width: 100%
7 | }
8 |
--------------------------------------------------------------------------------
/examples/src/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 |
3 | import 'zone.js/dist/zone-testing';
4 | import { getTestBed } from '@angular/core/testing';
5 | import {
6 | BrowserDynamicTestingModule,
7 | platformBrowserDynamicTesting
8 | } from '@angular/platform-browser-dynamic/testing';
9 |
10 | declare const require: any;
11 |
12 | // First, initialize the Angular testing environment.
13 | getTestBed().initTestEnvironment(
14 | BrowserDynamicTestingModule,
15 | platformBrowserDynamicTesting()
16 | );
17 | // Then we find all the tests.
18 | const context = require.context('./', true, /\.spec\.ts$/);
19 | // And load the modules.
20 | context.keys().map(context);
21 |
--------------------------------------------------------------------------------
/examples/src/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/app",
5 | "baseUrl": "./",
6 | "module": "es2015",
7 | "types": []
8 | },
9 | "exclude": [
10 | "test.ts",
11 | "**/*.spec.ts"
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/examples/src/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/spec",
5 | "baseUrl": "./",
6 | "module": "commonjs",
7 | "types": [
8 | "jasmine",
9 | "node"
10 | ]
11 | },
12 | "files": [
13 | "test.ts"
14 | ],
15 | "include": [
16 | "**/*.spec.ts",
17 | "**/*.d.ts"
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/examples/src/typings.d.ts:
--------------------------------------------------------------------------------
1 | /* SystemJS module definition */
2 | declare var module: NodeModule;
3 | interface NodeModule {
4 | id: string;
5 | }
6 |
--------------------------------------------------------------------------------
/examples/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "outDir": "./dist/out-tsc",
5 | "sourceMap": true,
6 | "declaration": false,
7 | "moduleResolution": "node",
8 | "emitDecoratorMetadata": true,
9 | "experimentalDecorators": true,
10 | "target": "es5",
11 | "typeRoots": [
12 | "node_modules/@types"
13 | ],
14 | "lib": [
15 | "es2017",
16 | "dom"
17 | ]
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/examples/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rulesDirectory": [
3 | "node_modules/codelyzer"
4 | ],
5 | "rules": {
6 | "arrow-return-shorthand": true,
7 | "callable-types": true,
8 | "class-name": true,
9 | "comment-format": [
10 | true,
11 | "check-space"
12 | ],
13 | "curly": true,
14 | "deprecation": {
15 | "severity": "warn"
16 | },
17 | "eofline": true,
18 | "forin": true,
19 | "import-blacklist": [
20 | true,
21 | "rxjs",
22 | "rxjs/Rx"
23 | ],
24 | "import-spacing": true,
25 | "indent": [
26 | true,
27 | "spaces"
28 | ],
29 | "interface-over-type-literal": true,
30 | "label-position": true,
31 | "max-line-length": [
32 | true,
33 | 140
34 | ],
35 | "member-access": false,
36 | "member-ordering": [
37 | true,
38 | {
39 | "order": [
40 | "static-field",
41 | "instance-field",
42 | "static-method",
43 | "instance-method"
44 | ]
45 | }
46 | ],
47 | "no-arg": true,
48 | "no-bitwise": false,
49 | "no-console": [
50 | true,
51 | "debug",
52 | "info",
53 | "time",
54 | "timeEnd",
55 | "trace"
56 | ],
57 | "no-construct": true,
58 | "no-debugger": true,
59 | "no-duplicate-super": true,
60 | "no-empty": false,
61 | "no-empty-interface": true,
62 | "no-eval": true,
63 | "no-inferrable-types": [
64 | true,
65 | "ignore-params"
66 | ],
67 | "no-misused-new": true,
68 | "no-non-null-assertion": true,
69 | "no-shadowed-variable": true,
70 | "no-string-literal": false,
71 | "no-string-throw": true,
72 | "no-switch-case-fall-through": true,
73 | "no-trailing-whitespace": true,
74 | "no-unnecessary-initializer": true,
75 | "no-unused-expression": true,
76 | "no-use-before-declare": true,
77 | "no-var-keyword": true,
78 | "object-literal-sort-keys": false,
79 | "one-line": [
80 | true,
81 | "check-open-brace",
82 | "check-catch",
83 | "check-else",
84 | "check-whitespace"
85 | ],
86 | "prefer-const": true,
87 | "quotemark": [
88 | true,
89 | "single"
90 | ],
91 | "radix": true,
92 | "semicolon": [
93 | true,
94 | "always"
95 | ],
96 | "triple-equals": [
97 | true,
98 | "allow-null-check"
99 | ],
100 | "typedef-whitespace": [
101 | true,
102 | {
103 | "call-signature": "nospace",
104 | "index-signature": "nospace",
105 | "parameter": "nospace",
106 | "property-declaration": "nospace",
107 | "variable-declaration": "nospace"
108 | }
109 | ],
110 | "unified-signatures": true,
111 | "variable-name": false,
112 | "whitespace": [
113 | true,
114 | "check-branch",
115 | "check-decl",
116 | "check-operator",
117 | "check-separator",
118 | "check-type"
119 | ],
120 | "directive-selector": [
121 | true,
122 | "attribute",
123 | "app",
124 | "camelCase"
125 | ],
126 | "component-selector": [
127 | true,
128 | "element",
129 | "app",
130 | "kebab-case"
131 | ],
132 | "no-output-on-prefix": true,
133 | "use-input-property-decorator": true,
134 | "use-output-property-decorator": true,
135 | "use-host-property-decorator": true,
136 | "no-input-rename": true,
137 | "no-output-rename": true,
138 | "use-life-cycle-interface": true,
139 | "use-pipe-transform-interface": true,
140 | "component-class-suffix": true,
141 | "directive-class-suffix": true
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/jest.setup.js:
--------------------------------------------------------------------------------
1 | class LocalStorageMock {
2 | constructor() {
3 | this.store = {};
4 | }
5 |
6 | clear() {
7 | this.store = {};
8 | }
9 |
10 | getItem(key) {
11 | return this.store[key] || null;
12 | }
13 |
14 | setItem(key, value) {
15 | this.store[key] = value.toString();
16 | }
17 |
18 | removeItem(key) {
19 | delete this.store[key];
20 | }
21 | }
22 |
23 | global.localStorage = new LocalStorageMock;
24 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mooa",
3 | "version": "0.1.2",
4 | "description": "",
5 | "keywords": [
6 | "spa",
7 | "microfrontends",
8 | "micro-frontends",
9 | "microservices",
10 | "microframework"
11 | ],
12 | "bin": "dist/lib/cli.js",
13 | "main": "dist/mooa.umd.js",
14 | "module": "dist/mooa.es5.js",
15 | "typings": "dist/types/mooa.d.ts",
16 | "files": [
17 | "dist"
18 | ],
19 | "author": "Phodal HUANG ",
20 | "repository": {
21 | "type": "git",
22 | "url": "git+https://github.com/phodal/mooa"
23 | },
24 | "license": "MIT",
25 | "engines": {
26 | "node": ">=6.0.0"
27 | },
28 | "scripts": {
29 | "lint": "tslint -t codeFrame 'src/**/*.ts' 'test/**/*.ts'",
30 | "prebuild": "rimraf dist",
31 | "build": "tsc --module commonjs && rollup -c rollup.config.ts && typedoc --out docs/mooa --target es6 --theme minimal --mode file ./src",
32 | "build:main": "tsc --module commonjs",
33 | "start": "rollup -c rollup.config.ts -w",
34 | "test": "jest",
35 | "cli": "node dist/lib/cli.js",
36 | "test:watch": "jest --watch",
37 | "watch": "yarn build && concurrently -r --kill-others 'npm run --silent build:main -- -w'",
38 | "test:prod": "npm run lint && npm run test -- --coverage --no-cache",
39 | "deploy-docs": "ts-node tools/gh-pages-publish",
40 | "report-coverage": "cat ./coverage/lcov.info | coveralls",
41 | "commit": "git-cz",
42 | "semantic-release": "semantic-release",
43 | "semantic-release-prepare": "ts-node tools/semantic-release-prepare",
44 | "precommit": "lint-staged"
45 | },
46 | "lint-staged": {
47 | "{src,test}/**/*.ts": [
48 | "prettier --write --no-semi --single-quote",
49 | "git add"
50 | ]
51 | },
52 | "config": {
53 | "commitizen": {
54 | "path": "node_modules/cz-conventional-changelog"
55 | },
56 | "validate-commit-msg": {
57 | "types": "conventional-commit-types",
58 | "helpMessage": "Use \"npm run commit\" instead, we use conventional-changelog format :) (https://github.com/commitizen/cz-cli)"
59 | }
60 | },
61 | "jest": {
62 | "transform": {
63 | ".(ts|tsx)": "/node_modules/ts-jest/preprocessor.js"
64 | },
65 | "setupFiles": [
66 | "/jest.setup.js"
67 | ],
68 | "testRegex": "(test/.*|test/\\.(test|spec))\\.(ts|tsx|js)$",
69 | "moduleFileExtensions": [
70 | "ts",
71 | "tsx",
72 | "js"
73 | ],
74 | "coveragePathIgnorePatterns": [
75 | "/node_modules/",
76 | "/test/"
77 | ],
78 | "coverageThreshold": {
79 | "global": {
80 | "branches": 40,
81 | "functions": 60,
82 | "lines": 60,
83 | "statements": 60
84 | }
85 | },
86 | "collectCoverage": true
87 | },
88 | "dependencies": {
89 | "cheerio": "^1.0.0-rc.2",
90 | "commander": "^2.19.0",
91 | "request": "^2.88.0"
92 | },
93 | "devDependencies": {
94 | "@types/jest": "^24.0.15",
95 | "@types/node": "^12.0.10",
96 | "colors": "^1.1.2",
97 | "commitizen": "^3.1.1",
98 | "concurrently": "^4.1.0",
99 | "coveralls": "^3.0.0",
100 | "cross-env": "^5.0.1",
101 | "cz-conventional-changelog": "^2.0.0",
102 | "husky": "^2.5.0",
103 | "jest": "^24.8.0",
104 | "jsdom": "^15.1.1",
105 | "lint-staged": "^8.2.1",
106 | "lodash.camelcase": "^4.3.0",
107 | "prettier": "^1.4.4",
108 | "prompt": "^1.0.0",
109 | "replace-in-file": "^4.1.0",
110 | "rimraf": "^2.6.1",
111 | "rollup": "^1.16.2",
112 | "rollup-plugin-commonjs": "^10.0.0",
113 | "rollup-plugin-node-resolve": "^5.0.4",
114 | "rollup-plugin-sourcemaps": "^0.4.2",
115 | "rollup-plugin-typescript2": "^0.21.2",
116 | "semantic-release": "^15.0.0",
117 | "ts-jest": "^24.0.2",
118 | "ts-node": "^8.3.0",
119 | "tslint": "^5.8.0",
120 | "tslint-config-prettier": "^1.1.0",
121 | "tslint-config-standard": "^8.0.1",
122 | "typedoc": "^0.14.2",
123 | "typescript": "^3.5.2",
124 | "validate-commit-msg": "^2.12.2"
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/rollup.config.ts:
--------------------------------------------------------------------------------
1 | import resolve from 'rollup-plugin-node-resolve'
2 | import commonjs from 'rollup-plugin-commonjs'
3 | import sourceMaps from 'rollup-plugin-sourcemaps'
4 | import camelCase from 'lodash.camelcase'
5 | import typescript from 'rollup-plugin-typescript2'
6 |
7 | const pkg = require('./package.json')
8 |
9 | const libraryName = 'mooa'
10 |
11 | export default {
12 | input: `src/${libraryName}.ts`,
13 | output: [
14 | { file: pkg.main, name: camelCase(libraryName), format: 'umd' },
15 | { file: pkg.module, format: 'es' },
16 | ],
17 | sourcemap: true,
18 | // Indicate here external modules you don't wanna include in your bundle (i.e.: 'lodash')
19 | external: [],
20 | watch: {
21 | include: 'src/**',
22 | },
23 | plugins: [
24 | // Compile TypeScript files
25 | typescript({ useTsconfigDeclarationDir: true }),
26 | // Allow bundling cjs modules (unlike webpack, rollup doesn't understand cjs)
27 | commonjs(),
28 | // Allow node_modules resolution, so you can use 'external' to control
29 | // which external modules to include in the bundle
30 | // https://github.com/rollup/rollup-plugin-node-resolve#usage
31 | resolve(),
32 |
33 | // Resolve source maps to the original source
34 | sourceMaps(),
35 | ],
36 | }
37 |
--------------------------------------------------------------------------------
/src/cli.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import generate from './cli/generate'
4 | import create from './cli/create'
5 | import update from './cli/update'
6 |
7 | const program = require('commander')
8 |
9 | let version = require('../../package.json').version
10 |
11 | program
12 | .version(version)
13 | .option('-c, --create [type]', 'Create [type] host || app')
14 | .option('-u, --update [host]', 'Update [type] host || app')
15 | .option('-g, --generate', 'Generate Mooa App Config')
16 | .parse(process.argv)
17 |
18 | if (program.create) {
19 | create(program)
20 | }
21 |
22 | if (program.update) {
23 | update(program)
24 | }
25 |
26 | if (program.generate) {
27 | generate(program)
28 | }
29 |
30 | if (!process.argv.slice(2).length || !process.argv.length) {
31 | program.outputHelp()
32 | }
33 |
--------------------------------------------------------------------------------
/src/cli/cli.help.ts:
--------------------------------------------------------------------------------
1 | const request = require('request')
2 | const cheerio = require('cheerio')
3 | const NODEURL = require('url')
4 |
5 | let apps: {}[] = []
6 |
7 | function buildScripts($scripts: any) {
8 | let scripts: string[] = []
9 | if ($scripts.length > 0) {
10 | $scripts.map((index: any) => {
11 | let scriptSrc = $scripts[index].attribs.src
12 | if (!scriptSrc.endsWith('zone.js')) {
13 | scripts.push(scriptSrc)
14 | }
15 | })
16 | }
17 |
18 | return scripts
19 | }
20 |
21 | function buildLink($link: any) {
22 | let styles: string[] = []
23 | if ($link.length > 0) {
24 | $link.map((index: any) => {
25 | if ($link[index].attribs.rel === 'stylesheet') {
26 | styles.push($link[index].attribs.href)
27 | }
28 | })
29 | }
30 |
31 | return styles
32 | }
33 |
34 | function getAppName(appUrl: string) {
35 | const myURL = new NODEURL.URL(appUrl)
36 | let pathName = myURL.pathname
37 | let urlResources = pathName.split('/')
38 | let lastPath = urlResources[urlResources.length - 1]
39 | return { pathName, lastPath }
40 | }
41 |
42 | function getSelector($body: any) {
43 | let selector: string = ''
44 | if ($body.length > 0) {
45 | selector = $body.children()['0'].name
46 | }
47 | return selector
48 | }
49 |
50 | export async function generateAppConfigByUrl(appUrl: string) {
51 | return new Promise(function(resolve, reject) {
52 | request(appUrl, (error: any, response: any, body: any) => {
53 | if (error) {
54 | reject(appUrl)
55 | return console.log('URL Error', appUrl)
56 | }
57 | const $ = cheerio.load(body)
58 |
59 | let scripts: string[] = buildScripts($('script'))
60 | let styles: string[] = buildLink($('link'))
61 | let selector: string = getSelector($('body'))
62 | let { pathName, lastPath } = getAppName(appUrl)
63 |
64 | let app = {
65 | name: lastPath,
66 | selector: selector,
67 | baseScriptUrl: pathName,
68 | styles: styles,
69 | prefix: lastPath,
70 | scripts: scripts
71 | }
72 |
73 | apps.push(app)
74 | resolve(app)
75 | })
76 | })
77 | }
78 |
79 | export async function getAppsConfig(urls: string[]) {
80 | let appPromises: any = []
81 |
82 | urls.map((url: string) => {
83 | if (url) {
84 | appPromises.push(generateAppConfigByUrl(url))
85 | }
86 | })
87 | return Promise.all(appPromises)
88 | }
89 |
--------------------------------------------------------------------------------
/src/cli/create.ts:
--------------------------------------------------------------------------------
1 | export default function create(program: any) {
2 | if (program.create === 'host') {
3 | console.log('create host')
4 | } else if (program.create === 'app') {
5 | console.log('create app')
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/cli/generate.ts:
--------------------------------------------------------------------------------
1 | import { getAppsConfig } from './cli.help'
2 | const fs = require('fs')
3 |
4 | export default function generate(program: any) {
5 | let urlListFilePath = program.args[0]
6 |
7 | let filePath = process.cwd() + '/' + urlListFilePath
8 | if (fs.existsSync(filePath)) {
9 | let urlFile = fs.readFileSync(filePath, 'utf8')
10 | let urls = urlFile.split(/\r?\n/)
11 | getAppsConfig(urls).then(apps => {
12 | fs.writeFileSync(
13 | process.cwd() + '/apps.json',
14 | JSON.stringify(apps, null, 2)
15 | )
16 | })
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/cli/update.ts:
--------------------------------------------------------------------------------
1 | export default function update(program: any) {
2 | if (program.update === 'host') {
3 | console.log('update host')
4 | } else if (program.update === 'app') {
5 | console.log('update app')
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/helper/app.helper.ts:
--------------------------------------------------------------------------------
1 | import { StatusEnum } from '../model/constants'
2 |
3 | declare const history: History
4 | declare const window: any
5 |
6 | export function find(arr: any, func: any) {
7 | for (let i = 0; i < arr.length; i++) {
8 | if (func(arr[i])) {
9 | return arr[i]
10 | }
11 | }
12 |
13 | return null
14 | }
15 |
16 | export function mooaLog(...args: any[]) {
17 | if (window['mooa'] && window['mooa']['debug']) {
18 | console.log(args)
19 | }
20 | }
21 |
22 | // Fixed for IE Custom Event
23 | function MooaCustomEvent(event: any, params: any): any {
24 | params = params || { bubbles: false, cancelable: false, detail: undefined }
25 | let evt = document.createEvent('CustomEvent')
26 | evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail)
27 | return evt
28 | }
29 |
30 | export function customEvent(eventName: any, eventArgs?: any) {
31 | if (typeof window.CustomEvent !== 'function') {
32 | MooaCustomEvent.prototype = window.Event.prototype
33 | window.CustomEvent = MooaCustomEvent
34 | }
35 |
36 | window.dispatchEvent(new CustomEvent(eventName, { detail: eventArgs }))
37 | }
38 |
39 | export function navigateAppByName(opts: any): void {
40 | let navigateToApp: any
41 | window.apps.map((app: any) => {
42 | app.status = StatusEnum.MOUNTED
43 | if (app.name === opts.appName) {
44 | app.status = StatusEnum.NOT_LOADED
45 | navigateToApp = app
46 | return app
47 | }
48 | })
49 |
50 | if (navigateToApp) {
51 | let prefix = navigateToApp.appConfig.prefix
52 | history.pushState(null, '', prefix + '/' + opts.router)
53 | return window.mooa.instance.reRouter()
54 | }
55 | }
56 |
57 | export function hashCode(str: string) {
58 | let hash = 0
59 | if (str.length === 0) {
60 | return hash.toString()
61 | }
62 | for (let i = 0; i < str.length; i++) {
63 | hash = (hash << 5) - hash + str.charCodeAt(i)
64 | hash = hash & hash
65 | hash = hash >>> 1
66 | }
67 |
68 | return hash.toString()
69 | }
70 |
--------------------------------------------------------------------------------
/src/helper/assets.helper.ts:
--------------------------------------------------------------------------------
1 | import { hashCode } from './app.helper'
2 |
3 | const createScriptTag = function(src: string) {
4 | const script = document.createElement('script')
5 | script.type = 'text/javascript'
6 | script.src = src
7 | script.charset = 'UTF-8'
8 | script.id = hashCode(src)
9 | return script
10 | }
11 |
12 | const createLinkTag = function(src: string) {
13 | const link = document.createElement('link')
14 | link.rel = 'stylesheet'
15 | link.href = src
16 | link.type = 'text/css'
17 | link.charset = 'UTF-8'
18 | link.id = hashCode(src)
19 | return link
20 | }
21 |
22 | const AssetsHelper = {
23 | createScriptTag: createScriptTag,
24 | createLinkTag: createLinkTag
25 | }
26 |
27 | export default AssetsHelper
28 |
--------------------------------------------------------------------------------
/src/helper/dom.utils.ts:
--------------------------------------------------------------------------------
1 | import { hashCode, navigateAppByName } from './app.helper'
2 | import { MooaApp } from '../model/IAppOption'
3 | import { MOOA_EVENT } from '../model/constants'
4 |
5 | declare const Element: any
6 | declare const document: Document
7 |
8 | export function createApplicationContainer(mooaApp: MooaApp) {
9 | const opts = mooaApp.appConfig
10 | let el: any = document.querySelector(opts.selector)
11 | if (mooaApp.switchMode === 'coexist') {
12 | if (el) {
13 | el.style.display = 'block'
14 | return el
15 | }
16 | }
17 |
18 | el = document.createElement(opts.selector)
19 |
20 | if (opts.parentElement) {
21 | let parentEl = document.querySelector(opts.parentElement)
22 | if (parentEl) {
23 | parentEl.appendChild(el)
24 | } else {
25 | document.body.appendChild(el)
26 | }
27 | } else {
28 | document.body.appendChild(el)
29 | }
30 |
31 | return el
32 | }
33 |
34 | export function removeApplicationContainer(app: MooaApp) {
35 | const opts = app.appConfig
36 | let el: any = document.querySelector(opts.selector)
37 | if (!el) {
38 | return
39 | }
40 |
41 | if (app.switchMode === 'coexist') {
42 | el.style.display = 'none'
43 | return
44 | }
45 |
46 | if (!('remove' in Element.prototype)) {
47 | Element.prototype.remove = function() {
48 | if (el && el.parentNode) {
49 | el.parentNode.removeChild(el)
50 | }
51 | }
52 | }
53 |
54 | return el.remove()
55 | }
56 |
57 | export function isIframeElementExist(mooaApp: MooaApp) {
58 | return document.getElementById(generateIFrameID(mooaApp.appConfig.name))
59 | }
60 |
61 | export function isElementExist(appName: string): any {
62 | return document.querySelector(`app-${appName}`)
63 | }
64 |
65 | export function createApplicationIframeContainer(mooaApp: MooaApp) {
66 | const opts = mooaApp.appConfig
67 | if (mooaApp.switchMode === 'coexist') {
68 | let iframeElement: any = isIframeElementExist(mooaApp)
69 | if (iframeElement) {
70 | iframeElement.style.display = 'block'
71 | return iframeElement
72 | }
73 | }
74 |
75 | const iframe: any = document.createElement('iframe')
76 | iframe.frameBorder = ''
77 | iframe.width = '100%'
78 | iframe.height = '100%'
79 | iframe.src = window.location.origin + '/assets/iframe.html'
80 | iframe.id = generateIFrameID(mooaApp.appConfig.name)
81 |
82 | const el = document.createElement(opts.selector)
83 |
84 | if (opts.parentElement) {
85 | let parentEl = document.querySelector(opts.parentElement)
86 | if (parentEl) {
87 | parentEl.appendChild(iframe)
88 | }
89 | } else {
90 | document.body.appendChild(iframe)
91 | }
92 |
93 | let iframeEl: any = document.getElementById(iframe.id)
94 | iframeEl.contentWindow.document.write('
')
95 | iframeEl.contentWindow.document.body.appendChild(el)
96 | iframeEl.contentWindow.document.head.innerHTML =
97 | iframeEl.contentWindow.document.head.innerHTML + " "
98 | iframeEl.contentWindow.mooa = {
99 | isSingleSpa: true
100 | }
101 | iframeEl.contentWindow.addEventListener(MOOA_EVENT.ROUTING_NAVIGATE, function(
102 | event: CustomEvent
103 | ) {
104 | if (event.detail) {
105 | navigateAppByName(event.detail)
106 | }
107 | })
108 | }
109 |
110 | export function removeApplicationIframeContainer(app: MooaApp) {
111 | const iframeId = generateIFrameID(app.appConfig.name)
112 | let iframeEl = document.getElementById(iframeId)
113 | if (!iframeEl) {
114 | return
115 | }
116 |
117 | if (app.switchMode === 'coexist') {
118 | iframeEl.style.display = 'none'
119 | return
120 | }
121 |
122 | if (!('remove' in Element.prototype)) {
123 | Element.prototype.remove = function() {
124 | if (iframeEl && iframeEl.parentNode) {
125 | iframeEl.parentNode.removeChild(iframeEl)
126 | }
127 | }
128 | }
129 |
130 | return iframeEl.remove()
131 | }
132 |
133 | export function generateIFrameID(name: string) {
134 | return name + '_' + hashCode(name)
135 | }
136 |
--------------------------------------------------------------------------------
/src/helper/loader.helper.ts:
--------------------------------------------------------------------------------
1 | import assetsLoaderHelper from './assets.helper'
2 | import { IAppOption } from '../model/IAppOption'
3 | import { hashCode } from './app.helper'
4 | import { generateIFrameID } from './dom.utils'
5 |
6 | function loadScriptPromise(src: string, iframeEl: any) {
7 | return new Promise((resolve, reject) => {
8 | if (document.getElementById(hashCode(src))) {
9 | return resolve()
10 | }
11 | const script = assetsLoaderHelper.createScriptTag(src)
12 | script.onload = () => {
13 | resolve()
14 | }
15 | script.onerror = err => {
16 | reject(err)
17 | }
18 | if (iframeEl) {
19 | if (iframeEl && iframeEl.contentWindow) {
20 | iframeEl.contentWindow.document.head.appendChild(script)
21 | }
22 | } else {
23 | document.head.appendChild(script)
24 | }
25 | })
26 | }
27 |
28 | const loadScriptTag = (src: string, iframeEl?: any) => {
29 | return () => {
30 | return loadScriptPromise(src, iframeEl)
31 | }
32 | }
33 |
34 | const loadLinkTag = (url: string, iframeEl?: any) => {
35 | return () => {
36 | return new Promise((resolve, reject) => {
37 | if (document.getElementById(hashCode(url))) {
38 | return resolve()
39 | }
40 |
41 | const link = assetsLoaderHelper.createLinkTag(url)
42 | link.onload = () => {
43 | resolve()
44 | }
45 | link.onerror = err => {
46 | reject(err)
47 | }
48 |
49 | if (iframeEl) {
50 | if (iframeEl && iframeEl.contentWindow) {
51 | iframeEl.contentWindow.document.head.appendChild(link)
52 | }
53 | } else {
54 | document.head.appendChild(link)
55 | }
56 | })
57 | }
58 | }
59 |
60 | function loadAllAssets(opts: any) {
61 | return new Promise((resolve, reject) => {
62 | const scriptsPromise = opts.scripts.reduce(
63 | (prev: Promise, fileName: string) =>
64 | prev.then(loadScriptTag(`${opts.baseScriptUrl}/${fileName}`)),
65 | Promise.resolve(undefined)
66 | )
67 | const stylesPromise = opts.styles.reduce(
68 | (prev: Promise, fileName: string) =>
69 | prev.then(loadLinkTag(`${opts.baseScriptUrl}/${fileName}`)),
70 | Promise.resolve(undefined)
71 | )
72 | Promise.all([scriptsPromise, stylesPromise]).then(resolve, reject)
73 | })
74 | }
75 |
76 | function loadAllAssetsForIframe(opts: any) {
77 | const iframeId = generateIFrameID(opts.name)
78 | let iframeEl: any = document.getElementById(iframeId)
79 | if (!iframeEl) {
80 | return new Promise((resolve, reject) => {
81 | reject()
82 | })
83 | }
84 |
85 | return new Promise((resolve, reject) => {
86 | const scriptsPromise = opts.scripts.reduce(
87 | (prev: Promise, fileName: string) =>
88 | prev.then(loadScriptTag(`${opts.baseScriptUrl}/${fileName}`, iframeEl)),
89 | Promise.resolve(undefined)
90 | )
91 |
92 | const stylesPromise = opts.styles.reduce(
93 | (prev: Promise, fileName: string) =>
94 | prev.then(loadLinkTag(`${opts.baseScriptUrl}/${fileName}`)),
95 | Promise.resolve(undefined)
96 | )
97 |
98 | let promiseArray = [scriptsPromise, stylesPromise]
99 | if (opts.includeZone) {
100 | const zonejsPromise = loadScriptPromise(`/assets/zone.min.js`, iframeEl)
101 | promiseArray.push(zonejsPromise)
102 | }
103 |
104 | Promise.all(promiseArray).then(resolve, reject)
105 | })
106 | }
107 |
108 | function loadAllAssetsForIframeAndUrl(opts: any) {
109 | const iframeId = generateIFrameID(opts.name)
110 | let iframeEl: any = document.getElementById(iframeId)
111 | if (!iframeEl) {
112 | return new Promise((resolve, reject) => {
113 | reject()
114 | })
115 | }
116 |
117 | return new Promise((resolve, reject) => {
118 | transformOptsWithAssets(opts).then(() => {
119 | const scriptsPromise = opts.scripts.reduce(
120 | (prev: Promise, fileName: string) =>
121 | prev.then(
122 | loadScriptTag(`${opts.baseScriptUrl}/${fileName}`, iframeEl)
123 | ),
124 | Promise.resolve(undefined)
125 | )
126 |
127 | const stylesPromise = opts.styles.reduce(
128 | (prev: Promise, fileName: string) =>
129 | prev.then(loadLinkTag(`${opts.baseScriptUrl}/${fileName}`)),
130 | Promise.resolve(undefined)
131 | )
132 |
133 | let promiseArray = [scriptsPromise, stylesPromise]
134 |
135 | if (opts.includeZone) {
136 | const zonejsPromise = loadScriptPromise(`/assets/zone.min.js`, iframeEl)
137 | promiseArray.push(zonejsPromise)
138 | }
139 |
140 | Promise.all(promiseArray).then(resolve, reject)
141 | })
142 | })
143 | }
144 |
145 | const xmlToAssets = (
146 | xml: string
147 | ): { styles: (string | null)[]; scripts: (string | null)[] } => {
148 | let dom = document.createElement('html')
149 | let urlRegex = /^(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/
150 | dom.innerHTML = xml
151 | const linksEls = dom.querySelectorAll('link[rel="stylesheet"]')
152 | const scriptsEls = dom.querySelectorAll('script[type="text/javascript"]')
153 | return {
154 | styles: Array.from(linksEls)
155 | .map(el => el.getAttribute('href'))
156 | .filter(src => {
157 | if (src) {
158 | return !urlRegex.test(src)
159 | }
160 | }),
161 | scripts: Array.from(scriptsEls)
162 | .map(el => el.getAttribute('src'))
163 | .filter(src => {
164 | if (src) {
165 | return !(/zone\.js/.test(src) && urlRegex.test(src))
166 | }
167 | })
168 | }
169 | }
170 |
171 | const transformOptsWithAssets = (opts: any): Promise => {
172 | const url = `${opts.baseScriptUrl}/index.html`
173 | return new Promise((resolve, reject) => {
174 | const req = new XMLHttpRequest()
175 | req.onreadystatechange = () => {
176 | if (req.readyState === XMLHttpRequest.DONE) {
177 | if (req.status >= 200 && req.status < 400) {
178 | const res = xmlToAssets(req.responseText)
179 | opts.styles = res.styles
180 | opts.scripts = res.scripts
181 | resolve(null)
182 | } else {
183 | reject(
184 | `Try to load ${url}, status : ${req.status} => ${req.statusText}`
185 | )
186 | }
187 | }
188 | }
189 | req.open('GET', url, true)
190 | req.send(null)
191 | })
192 | }
193 |
194 | const loadAllAssetsByUrl = (opts: any) => {
195 | return new Promise((resolve, reject) => {
196 | transformOptsWithAssets(opts).then(() => {
197 | const scriptsPromise = opts.scripts.reduce(
198 | (prev: Promise, fileName: string) =>
199 | prev.then(loadScriptTag(`${opts.baseScriptUrl}/${fileName}`)),
200 | Promise.resolve(undefined)
201 | )
202 | const stylesPromise = opts.styles.reduce(
203 | (prev: Promise, fileName: string) =>
204 | prev.then(loadLinkTag(`${opts.baseScriptUrl}/${fileName}`)),
205 | Promise.resolve(undefined)
206 | )
207 | Promise.all([scriptsPromise, stylesPromise]).then(resolve, reject)
208 | }, reject)
209 | })
210 | }
211 |
212 | function unloadTag(opts: IAppOption, scriptName: string) {
213 | return () => {
214 | return new Promise((resolve, reject) => {
215 | const tag = document.getElementById(
216 | hashCode(`${opts.baseScriptUrl}/${scriptName}`)
217 | )
218 | if (tag) {
219 | document.head.removeChild(tag)
220 | }
221 | resolve()
222 | })
223 | }
224 | }
225 |
226 | const LoaderHelper = {
227 | loadAllAssets: loadAllAssets,
228 | loadAllAssetsByUrl: loadAllAssetsByUrl,
229 | loadAllAssetsForIframe: loadAllAssetsForIframe,
230 | loadAllAssetsForIframeAndUrl: loadAllAssetsForIframeAndUrl,
231 | unloadTag: unloadTag
232 | }
233 |
234 | export default LoaderHelper
235 |
--------------------------------------------------------------------------------
/src/helper/status.helper.ts:
--------------------------------------------------------------------------------
1 | import { StatusEnum } from '../model/constants'
2 | import { getUnloadApps } from '../lifecycles/unload'
3 |
4 | function isActive(app: any) {
5 | return app.status === StatusEnum.MOUNTED
6 | }
7 |
8 | function InActive(app: any) {
9 | return !isActive(app)
10 | }
11 |
12 | function shouldNotBeActive(app: any) {
13 | try {
14 | return !app.activeWhen(window.location)
15 | } catch (err) {
16 | app.status = StatusEnum.SKIP_BECAUSE_BROKEN
17 | throw new Error(err)
18 | }
19 | }
20 |
21 | function notSkipped(item: any) {
22 | return (
23 | item !== StatusEnum.SKIP_BECAUSE_BROKEN &&
24 | (!item || item.status !== StatusEnum.SKIP_BECAUSE_BROKEN)
25 | )
26 | }
27 |
28 | function isLoaded(app: any) {
29 | return (
30 | app.status !== StatusEnum.NOT_LOADED &&
31 | app.status !== StatusEnum.LOADING_SOURCE_CODE
32 | )
33 | }
34 |
35 | function notLoaded(app: any) {
36 | return !isLoaded(app)
37 | }
38 |
39 | function shouldBeActive(app: any) {
40 | try {
41 | return app.activeWhen(window.location)
42 | } catch (err) {
43 | app.status = StatusEnum.SKIP_BECAUSE_BROKEN
44 | throw new Error(err)
45 | }
46 | }
47 |
48 | const StatusHelper = {
49 | getAppsToLoad: (apps: any) => {
50 | return apps
51 | .filter(notSkipped)
52 | .filter(notLoaded)
53 | .filter(shouldBeActive)
54 | },
55 | getAppsToUnload: () => {
56 | const appsToUnload = getUnloadApps()
57 | return Object.keys(appsToUnload)
58 | .map(appName => appsToUnload[appName].app)
59 | .filter(InActive)
60 | },
61 | getAppUnloadInfo: (appName: any) => {
62 | const appsToUnload = getUnloadApps()
63 | return appsToUnload[appName]
64 | },
65 | getAppsToUnmount: (apps: any) => {
66 | return apps
67 | .filter(notSkipped)
68 | .filter(isActive)
69 | .filter(shouldNotBeActive)
70 | },
71 | getAppsToMount: (apps: any) => {
72 | return apps
73 | .filter(notSkipped)
74 | .filter(InActive)
75 | .filter(isLoaded)
76 | .filter(shouldBeActive)
77 | },
78 | getActiveApps: (apps: any) => {
79 | return apps.filter(notSkipped).filter(isActive)
80 | }
81 | }
82 |
83 | export default StatusHelper
84 |
--------------------------------------------------------------------------------
/src/helper/timeouts.ts:
--------------------------------------------------------------------------------
1 | // License (MIT)
2 | // Copyright (c) 2013-2014 Christopher Simpkins
3 |
4 | const globalTimeoutConfig = {
5 | bootstrap: {
6 | millis: 4000,
7 | dieOnTimeout: false
8 | },
9 | mount: {
10 | millis: 3000,
11 | dieOnTimeout: false
12 | },
13 | unmount: {
14 | millis: 3000,
15 | dieOnTimeout: false
16 | },
17 | unload: {
18 | millis: 3000,
19 | dieOnTimeout: false
20 | }
21 | }
22 |
23 | export function reasonableTime(
24 | promise: any,
25 | description: any,
26 | timeoutConfig: any,
27 | app?: any
28 | ) {
29 | const warningPeriod = 1000
30 |
31 | return new Promise((resolve, reject) => {
32 | let finished = false
33 | let errored = false
34 |
35 | promise
36 | .then((val: any) => {
37 | finished = true
38 | resolve(val)
39 | })
40 | .catch((val: any) => {
41 | finished = true
42 | reject(val)
43 | })
44 |
45 | setTimeout(() => maybeTimingOut(1), warningPeriod)
46 | setTimeout(() => maybeTimingOut(true), timeoutConfig.millis)
47 |
48 | function maybeTimingOut(shouldError: any) {
49 | if (!finished) {
50 | if (shouldError === true) {
51 | errored = true
52 | if (timeoutConfig.dieOnTimeout) {
53 | reject(
54 | `${description} did not resolve or reject for ${
55 | timeoutConfig.millis
56 | } milliseconds`
57 | )
58 | } else {
59 | // don't resolve or reject, we're waiting this one out
60 | console.error(
61 | `${description} did not resolve or reject for ${
62 | timeoutConfig.millis
63 | }` +
64 | `milliseconds -- \n we're no longer going to warn you about it.`
65 | )
66 | }
67 | } else if (!errored) {
68 | const numWarnings = shouldError
69 | const numMillis = numWarnings * warningPeriod
70 | console.warn(
71 | `${description} did not resolve or reject within ${numMillis} milliseconds`
72 | )
73 | if (numMillis + warningPeriod < timeoutConfig.millis) {
74 | setTimeout(() => maybeTimingOut(numWarnings + 1), warningPeriod)
75 | }
76 | }
77 | }
78 | }
79 | })
80 | }
81 |
82 | export function ensureValidAppTimeouts(timeouts = {}) {
83 | return {
84 | ...globalTimeoutConfig,
85 | ...timeouts
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/lifecycles/bootstrap.ts:
--------------------------------------------------------------------------------
1 | import { reasonableTime } from '../helper/timeouts'
2 | import { MOOA_EVENT, StatusEnum } from '../model/constants'
3 | import { customEvent, mooaLog } from '../helper/app.helper'
4 |
5 | export async function toBootstrapPromise(app: any) {
6 | if (app.status !== StatusEnum.NOT_BOOTSTRAPPED) {
7 | return app
8 | }
9 |
10 | app.status = StatusEnum.BOOTSTRAPPING
11 |
12 | try {
13 | mooaLog('Bootstrapping application', app.name, app.status)
14 | customEvent(MOOA_EVENT.BOOTSTRAPPING, { app: app })
15 | await reasonableTime(
16 | app.bootstrap(),
17 | `Bootstrapping app '${app.name}'`,
18 | app.timeouts.bootstrap
19 | )
20 | app.status = StatusEnum.NOT_MOUNTED
21 | } catch (err) {
22 | console.error(err)
23 | app.status = StatusEnum.SKIP_BECAUSE_BROKEN
24 | throw new Error(err)
25 | }
26 |
27 | return app
28 | }
29 |
--------------------------------------------------------------------------------
/src/lifecycles/load.ts:
--------------------------------------------------------------------------------
1 | import { MOOA_EVENT, StatusEnum } from '../model/constants'
2 | import loader from '../loader/mooa.loader'
3 | import { ensureValidAppTimeouts } from '../helper/timeouts'
4 | import { customEvent, mooaLog } from '../helper/app.helper'
5 | import { MooaApp } from '../model/IAppOption'
6 |
7 | export async function toLoadPromise(app: any) {
8 | if (app.status !== StatusEnum.NOT_LOADED) {
9 | return app
10 | }
11 |
12 | createApp(app)
13 |
14 | customEvent(MOOA_EVENT.LOADING, { app: app })
15 | mooaLog('Loading application', app.name, app.status)
16 | app.status = StatusEnum.NOT_BOOTSTRAPPED
17 | return app
18 | }
19 |
20 | function createApp(appOpt: any): MooaApp {
21 | const _loader = loader(appOpt)
22 | appOpt.bootstrap = _loader.bootstrap
23 | appOpt.load = _loader.load
24 | appOpt.mount = _loader.mount
25 | appOpt.unload = _loader.unload
26 | appOpt.unmount = _loader.unmount
27 | appOpt.timeouts = ensureValidAppTimeouts(appOpt.timeouts)
28 | return appOpt
29 | }
30 |
--------------------------------------------------------------------------------
/src/lifecycles/mount.ts:
--------------------------------------------------------------------------------
1 | import { MOOA_EVENT, StatusEnum } from '../model/constants'
2 | import { reasonableTime } from '../helper/timeouts'
3 | import { customEvent, mooaLog } from '../helper/app.helper'
4 |
5 | export async function toMountPromise(app: any) {
6 | if (app.status !== StatusEnum.NOT_MOUNTED) {
7 | return app
8 | }
9 |
10 | try {
11 | mooaLog('Mounting application', app.name, app.status)
12 | customEvent(MOOA_EVENT.MOUNTING, { app: app })
13 | await reasonableTime(
14 | app.mount(),
15 | `Mounting application '${app.name}'`,
16 | app.timeouts.mount
17 | )
18 | app.status = StatusEnum.MOUNTED
19 | } catch (err) {
20 | app.status = StatusEnum.SKIP_BECAUSE_BROKEN
21 | throw new Error(err)
22 | }
23 |
24 | return app
25 | }
26 |
--------------------------------------------------------------------------------
/src/lifecycles/unload.ts:
--------------------------------------------------------------------------------
1 | import { MOOA_EVENT, StatusEnum } from '../model/constants'
2 | import { reasonableTime } from '../helper/timeouts'
3 | import { customEvent, mooaLog } from '../helper/app.helper'
4 |
5 | const appsToUnload: any = {}
6 |
7 | export async function toUnloadPromise(app: any, mooaInstance?: any) {
8 | const unloadInfo = appsToUnload[app.name]
9 |
10 | if (app.status === StatusEnum.NOT_LOADED) {
11 | unloadingApp(app, unloadInfo)
12 | return app
13 | }
14 |
15 | if (app.status === StatusEnum.UNLOADING) {
16 | await unloadInfo.promise
17 | return app
18 | }
19 |
20 | if (app.status !== StatusEnum.NOT_MOUNTED) {
21 | return app
22 | }
23 |
24 | if (!unloadInfo) {
25 | return app
26 | }
27 |
28 | try {
29 | app.status = StatusEnum.UNLOADING
30 | customEvent(MOOA_EVENT.UNLOADING, { app: app })
31 | mooaLog('Unloading application', app.name, app.status)
32 | await reasonableTime(
33 | app.unload(),
34 | `Unloading application '${app.name}'`,
35 | app.timeouts.unload
36 | )
37 | } catch (err) {
38 | console.error('Unloading Error', err)
39 | return app
40 | }
41 |
42 | unloadingApp(app, unloadInfo)
43 |
44 | return app
45 | }
46 |
47 | function unloadingApp(app: any, unloadInfo: any) {
48 | delete appsToUnload[app.name]
49 |
50 | delete app.bootstrap
51 | delete app.mount
52 | delete app.unmount
53 | delete app.unload
54 |
55 | app.status = StatusEnum.NOT_LOADED
56 |
57 | unloadInfo.resolve()
58 | }
59 |
60 | export function getUnloadApps() {
61 | return appsToUnload
62 | }
63 |
64 | export function addAppToUnload(
65 | app: any,
66 | promiseGetter: any,
67 | resolve: any,
68 | reject: any
69 | ) {
70 | appsToUnload[app.name] = { app, resolve, reject }
71 | Object.defineProperty(appsToUnload[app.name], 'promise', {
72 | get: promiseGetter
73 | })
74 | }
75 |
--------------------------------------------------------------------------------
/src/lifecycles/unmount.ts:
--------------------------------------------------------------------------------
1 | import { MOOA_EVENT, StatusEnum } from '../model/constants'
2 | import { reasonableTime } from '../helper/timeouts'
3 | import StatusHelper from '../helper/status.helper'
4 | import { addAppToUnload, toUnloadPromise } from './unload'
5 | import { customEvent, find, mooaLog } from '../helper/app.helper'
6 | import { MooaApp } from '../model/IAppOption'
7 |
8 | declare const window: any
9 |
10 | export function getAppNames() {
11 | return window.apps.map((app: any) => app.name)
12 | }
13 |
14 | function immediatelyUnloadApp(app: any, resolve: any, reject: any) {
15 | toUnmountPromise(app)
16 | .then(toUnloadPromise)
17 | .then(() => {
18 | resolve()
19 | setTimeout(() => {
20 | // reroute, but the unload promise is done
21 | return window.mooa.instance.reRouter()
22 | })
23 | })
24 | .catch(reject)
25 | }
26 |
27 | export function unloadApplication(
28 | appName: any,
29 | opts = { waitForUnmount: false }
30 | ) {
31 | if (typeof appName !== 'string') {
32 | throw new Error(`unloadApplication requires a string 'appName'`)
33 | }
34 | const app = find(window.apps, (app: any) => app.name === appName)
35 | if (!app) {
36 | throw new Error(
37 | `Could not unload application '${appName}' because no such application has been declared`
38 | )
39 | }
40 |
41 | const appUnloadInfo = StatusHelper.getAppUnloadInfo(app.name)
42 | if (opts && opts.waitForUnmount) {
43 | if (appUnloadInfo) {
44 | return appUnloadInfo.promise
45 | } else {
46 | const promise = new Promise((resolve, reject) => {
47 | addAppToUnload(app, () => promise, resolve, reject)
48 | })
49 | return promise
50 | }
51 | } else {
52 | let resultPromise: any
53 |
54 | if (appUnloadInfo) {
55 | resultPromise = appUnloadInfo.promise
56 | immediatelyUnloadApp(app, appUnloadInfo.resolve, appUnloadInfo.reject)
57 | } else {
58 | resultPromise = new Promise((resolve, reject) => {
59 | addAppToUnload(app, () => resultPromise, resolve, reject)
60 | immediatelyUnloadApp(app, resolve, reject)
61 | })
62 | }
63 |
64 | return resultPromise
65 | }
66 | }
67 |
68 | export function appendFunc(app: MooaApp) {
69 | app.unloadApplication = unloadApplication
70 | app.getAppNames = getAppNames
71 | return app
72 | }
73 |
74 | export async function toUnmountPromise(app: any) {
75 | if (app.status !== StatusEnum.MOUNTED) {
76 | return app
77 | }
78 |
79 | try {
80 | mooaLog('Unmounting application', app.name, app.status)
81 | customEvent(MOOA_EVENT.UNMOUNTING, { app: app })
82 | await reasonableTime(
83 | app.unmount(appendFunc(app)),
84 | `Unmounting application ${app.name}'`,
85 | app.timeouts.unmount
86 | )
87 | app.status = StatusEnum.NOT_MOUNTED
88 | } catch (err) {
89 | console.error(err)
90 | app.status = StatusEnum.SKIP_BECAUSE_BROKEN
91 | throw new Error(err)
92 | }
93 |
94 | return app
95 | }
96 |
--------------------------------------------------------------------------------
/src/loader/mooa.loader.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Robin Coma Delperier
3 | * Licensed under the Apache-2.0 License
4 | * https://github.com/PlaceMe-SAS/single-spa-angular-cli/blob/master/LICENSE
5 | *
6 | * modified by Phodal HUANG
7 | *
8 | */
9 |
10 | import LoaderHelper from '../helper/loader.helper'
11 | import { MooaApp } from '../model/IAppOption'
12 | import {
13 | createApplicationContainer,
14 | createApplicationIframeContainer,
15 | generateIFrameID,
16 | isElementExist,
17 | isIframeElementExist,
18 | removeApplicationContainer,
19 | removeApplicationIframeContainer
20 | } from '../helper/dom.utils'
21 |
22 | declare const window: any
23 | declare const document: any
24 |
25 | function bootstrap(app: MooaApp) {
26 | if (!window['mooa']) {
27 | window.mooa = {}
28 | }
29 | window.mooa.isSingleSpa = true
30 | window.mooa.name = app.name
31 |
32 | if (app.mode && app.mode === 'iframe') {
33 | let iframeElementExist = isIframeElementExist(app)
34 | if (app.switchMode === 'coexist' && iframeElementExist) {
35 | iframeElementExist.style.display = 'block'
36 | return new Promise((resolve, reject) => {
37 | resolve()
38 | })
39 | }
40 |
41 | createApplicationIframeContainer(app)
42 |
43 | if (app.sourceType === 'link') {
44 | return new Promise((resolve, reject) => {
45 | LoaderHelper.loadAllAssetsForIframeAndUrl(app.appConfig).then(
46 | resolve,
47 | reject
48 | )
49 | })
50 | } else {
51 | return new Promise((resolve, reject) => {
52 | LoaderHelper.loadAllAssetsForIframe(app.appConfig).then(resolve, reject)
53 | })
54 | }
55 | } else if (app.sourceType && app.sourceType === 'link') {
56 | let hasElement = isElementExist(app.appConfig.name)
57 | if (app.switchMode === 'coexist' && hasElement) {
58 | hasElement.style.display = 'block'
59 | return new Promise((resolve, reject) => {
60 | resolve()
61 | })
62 | }
63 |
64 | createApplicationContainer(app)
65 |
66 | return new Promise((resolve, reject) => {
67 | LoaderHelper.loadAllAssetsByUrl(app.appConfig).then(resolve, reject)
68 | })
69 | } else {
70 | let hasElement = isElementExist(app.appConfig.name)
71 | if (app.switchMode === 'coexist' && hasElement) {
72 | hasElement.style.display = 'block'
73 | return new Promise((resolve, reject) => {
74 | resolve()
75 | })
76 | }
77 |
78 | createApplicationContainer(app)
79 |
80 | return new Promise((resolve, reject) => {
81 | LoaderHelper.loadAllAssets(app.appConfig).then(resolve, reject)
82 | })
83 | }
84 | }
85 |
86 | function load() {
87 | return Promise.resolve()
88 | }
89 |
90 | function mount(app: MooaApp, props?: any) {
91 | return new Promise((resolve, reject) => {
92 | let aliasWindow = window
93 | if (app.mode === 'iframe') {
94 | let iframe = document.getElementById(generateIFrameID(app.name))
95 | if (iframe && iframe.contentWindow) {
96 | aliasWindow = iframe.contentWindow
97 | }
98 | }
99 |
100 | if (aliasWindow.mooa[app.name]) {
101 | aliasWindow.mooa[app.name].mount(props)
102 | resolve()
103 | } else {
104 | console.error(`Cannot mount ${app.name} because that is not bootstraped`)
105 | reject()
106 | }
107 | })
108 | }
109 |
110 | function unmount(app: MooaApp, props: any) {
111 | const { unloadApplication, getAppNames } = props
112 | return new Promise((resolve, reject) => {
113 | if (app.mode === 'iframe') {
114 | unloadApplication(app.name, { waitForUnmount: true })
115 | removeApplicationIframeContainer(app)
116 | resolve()
117 | }
118 |
119 | if (window.mooa[app.name]) {
120 | if (app.switchMode !== 'coexist') {
121 | window.mooa[app.name].unmount()
122 | }
123 | removeApplicationContainer(app)
124 | if (getAppNames().indexOf(app.name) !== -1) {
125 | unloadApplication(app.name, { waitForUnmount: true })
126 | resolve()
127 | } else {
128 | reject(`Cannot unmount ${app.name} because that ${app.name}
129 | is not part of the decalred applications : ${getAppNames()}`)
130 | }
131 | } else {
132 | reject(`Cannot unmount ${app.name} because that is not bootstraped`)
133 | }
134 | })
135 | }
136 |
137 | function unload(app: MooaApp) {
138 | if (app.switchMode === 'coexist') {
139 | return new Promise(resolve => {
140 | resolve()
141 | })
142 | }
143 |
144 | return new Promise(resolve => {
145 | app.appConfig.scripts
146 | .concat(app.appConfig.styles)
147 | .reduce((prev: Promise, scriptName: string) => {
148 | return prev.then(LoaderHelper.unloadTag(app.appConfig, scriptName))
149 | }, Promise.resolve({}))
150 | resolve()
151 | })
152 | }
153 |
154 | export default function mooaLoader(opts: any) {
155 | return {
156 | bootstrap: bootstrap.bind(null, opts),
157 | load: load.bind(null, opts),
158 | mount: mount.bind(null, opts),
159 | unload: unload.bind(null, opts),
160 | unmount: unmount.bind(null, opts)
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/src/model/IAppOption.ts:
--------------------------------------------------------------------------------
1 | export interface IAppOption {
2 | name: string
3 | selector: string
4 | baseScriptUrl: string
5 | styles: string[]
6 | scripts: string[]
7 | prefix: string
8 | parentElement: string
9 | }
10 |
11 | export interface MooaApp extends IAppOption {
12 | appConfig: IAppOption
13 | sourceType?: string
14 | mode: string
15 | switchMode: string
16 | status: string
17 | bootstrap: any
18 | load: any
19 | mount: any
20 | unload: any
21 | unmount: any
22 | timeouts: any
23 | unloadApplication: any
24 | getAppNames: any
25 | }
26 |
--------------------------------------------------------------------------------
/src/model/MooaOption.ts:
--------------------------------------------------------------------------------
1 | export interface MooaOption {
2 | mode?: string
3 | debug?: boolean
4 | preload?: boolean
5 | includeZone?: boolean
6 | switchMode?: string
7 | parentElement?: string
8 | urlPrefix?: string
9 | }
10 |
--------------------------------------------------------------------------------
/src/model/constants.ts:
--------------------------------------------------------------------------------
1 | export enum StatusEnum {
2 | NOT_LOADED = 'NOT_LOADED',
3 | LOADING_SOURCE_CODE = 'LOADING_SOURCE_CODE',
4 | NOT_BOOTSTRAPPED = 'NOT_BOOTSTRAPPED',
5 | BOOTSTRAPPING = 'BOOTSTRAPPING',
6 | NOT_MOUNTED = 'NOT_MOUNTED',
7 | MOUNTING = 'MOUNTING',
8 | MOUNTED = 'MOUNTED',
9 | UNMOUNTING = 'UNMOUNTING',
10 | UNLOADING = 'UNLOADING',
11 | SKIP_BECAUSE_BROKEN = 'SKIP_BECAUSE_BROKEN'
12 | }
13 |
14 | export enum MOOA_EVENT {
15 | LOADING = 'mooa.loading',
16 | BOOTSTRAPPING = 'mooa.bootstrapping',
17 | MOUNTING = 'mooa.mounting',
18 | UNLOADING = 'mooa.unloading',
19 | UNMOUNTING = 'mooa.unmounting',
20 | ROUTING_NAVIGATE = 'mooa.routing.navigate',
21 | ROUTING_CHANGE = 'mooa.routing.change',
22 | ROUTING_BEFORE = 'mooa.routing.before',
23 | CHILD_MOUNT = 'mooa.child.mount',
24 | CHILD_UNMOUNT = 'mooa.child.unmount'
25 | }
26 |
--------------------------------------------------------------------------------
/src/mooa.ts:
--------------------------------------------------------------------------------
1 | import { MOOA_EVENT, StatusEnum } from './model/constants'
2 | import { toLoadPromise } from './lifecycles/load'
3 | import { toBootstrapPromise } from './lifecycles/bootstrap'
4 | import { toMountPromise } from './lifecycles/mount'
5 | import StatusHelper from './helper/status.helper'
6 | import { toUnloadPromise } from './lifecycles/unload'
7 | import { toUnmountPromise } from './lifecycles/unmount'
8 | import { MooaOption } from './model/MooaOption'
9 | import MooaRouter from './router'
10 | import { MooaPlatform } from './platform/platform'
11 | import { customEvent, navigateAppByName } from './helper/app.helper'
12 | import { generateIFrameID } from './helper/dom.utils'
13 |
14 | declare const window: any
15 |
16 | const apps: any[] = []
17 | window.mooa = window.mooa || {}
18 |
19 | class Mooa {
20 | started = false
21 | private option: MooaOption
22 |
23 | constructor(option: MooaOption) {
24 | window.mooa.instance = this
25 |
26 | if (option) {
27 | window.mooa.option = option
28 | window.mooa.debug = option.debug
29 | }
30 |
31 | if (localStorage.getItem('mooa.debug') === 'true') {
32 | window.mooa.debug = true
33 | }
34 |
35 | this.option = option
36 | }
37 |
38 | registerApplication(
39 | appName: string,
40 | appConfig?: any,
41 | activeWhen?: {},
42 | customProps: object = {}
43 | ) {
44 | this.checkActiveWhen(activeWhen)
45 |
46 | appConfig.includeZone = true
47 | appConfig = this.mergeAppConfigOption(appConfig)
48 |
49 | if (this.option.urlPrefix) {
50 | appConfig.prefix = this.option.urlPrefix + '/' + appConfig.prefix
51 | }
52 |
53 | const appOpt = {
54 | name: appName,
55 | appConfig,
56 | activeWhen,
57 | mode: '',
58 | switchMode: '',
59 | status: StatusEnum.NOT_LOADED,
60 | customProps: customProps
61 | }
62 |
63 | if (this.option.mode) {
64 | appOpt.mode = this.option.mode
65 | }
66 |
67 | if (appConfig && appConfig.mode) {
68 | appOpt.mode = appConfig.mode
69 | }
70 |
71 | if (this.option.switchMode) {
72 | appOpt.switchMode = this.option.switchMode
73 | }
74 |
75 | apps.push(appOpt)
76 | window.apps = apps
77 | }
78 |
79 | registerApplicationByLink(
80 | appName: string,
81 | link?: string,
82 | activeWhen?: {},
83 | customProps: object = {}
84 | ) {
85 | this.checkActiveWhen(activeWhen)
86 |
87 | let appConfig = {
88 | name: appName,
89 | scripts: [],
90 | selector: `app-${appName}`,
91 | baseScriptUrl: link,
92 | styles: [],
93 | parentElement: '',
94 | prefix: '',
95 | preload: false,
96 | includeZone: true
97 | }
98 |
99 | appConfig = this.mergeAppConfigOption(appConfig)
100 |
101 | if (this.option.urlPrefix) {
102 | appConfig.prefix = this.option.urlPrefix + '/' + appConfig.name
103 | }
104 |
105 | const appOpt = {
106 | name: appName,
107 | appConfig: appConfig,
108 | activeWhen,
109 | sourceType: 'link',
110 | mode: '',
111 | switchMode: '',
112 | status: StatusEnum.NOT_LOADED,
113 | customProps: customProps
114 | }
115 |
116 | if (this.option.mode) {
117 | appOpt.mode = this.option.mode
118 | }
119 |
120 | if (this.option.switchMode) {
121 | appOpt.switchMode = this.option.switchMode
122 | }
123 |
124 | apps.push(appOpt)
125 | window.apps = apps
126 | }
127 |
128 | start() {
129 | this.started = true
130 | window.addEventListener(MOOA_EVENT.ROUTING_NAVIGATE, function(
131 | event: CustomEvent
132 | ) {
133 | if (event.detail) {
134 | navigateAppByName(event.detail)
135 | }
136 | })
137 | return this.reRouter()
138 | }
139 |
140 | reRouter(eventArguments?: any) {
141 | const that = this
142 | async function performAppChanges() {
143 | customEvent(MOOA_EVENT.ROUTING_BEFORE)
144 | const unloadPromises = StatusHelper.getAppsToUnload().map(toUnloadPromise)
145 |
146 | const unmountUnloadPromises = StatusHelper.getAppsToUnmount(apps)
147 | .map(toUnmountPromise)
148 | .map((unmountPromise: any) => unmountPromise.then(toUnloadPromise))
149 |
150 | const allUnmountPromises = unmountUnloadPromises.concat(unloadPromises)
151 |
152 | const unmountAllPromise = Promise.all(allUnmountPromises)
153 |
154 | const appsToLoad = StatusHelper.getAppsToLoad(apps)
155 | const loadThenMountPromises = appsToLoad.map((app: any) => {
156 | return toLoadPromise(app)
157 | .then(toBootstrapPromise)
158 | .then(async function(toMountApp) {
159 | await unmountAllPromise
160 | return toMountPromise(toMountApp)
161 | })
162 | })
163 |
164 | const mountPromises = StatusHelper.getAppsToMount(apps)
165 | .filter((appToMount: any) => appsToLoad.indexOf(appToMount) < 0)
166 | .map(async function(appToMount: any) {
167 | await toBootstrapPromise(appToMount)
168 | await unmountAllPromise
169 | return toMountPromise(appToMount)
170 | })
171 |
172 | try {
173 | await unmountAllPromise
174 | } catch (err) {
175 | throw err
176 | }
177 |
178 | await Promise.all(loadThenMountPromises.concat(mountPromises))
179 | if (eventArguments) {
180 | let activeApp = StatusHelper.getActiveApps(apps)[0]
181 | if (activeApp && activeApp['appConfig']) {
182 | that.createRoutingChangeEvent(eventArguments, activeApp)
183 | }
184 | }
185 | }
186 |
187 | return performAppChanges()
188 | }
189 |
190 | createRoutingChangeEvent(eventArguments: any, activeApp: any) {
191 | let eventArgs = {
192 | url: eventArguments.url,
193 | app: activeApp['appConfig']
194 | }
195 |
196 | if (activeApp.mode === 'iframe') {
197 | const iframeId = generateIFrameID(activeApp.name)
198 | let iframeEl: any = document.getElementById(iframeId)
199 | if (iframeEl && iframeEl.contentWindow) {
200 | iframeEl.contentWindow.mooa.option = window.mooa.option
201 | iframeEl.contentWindow.dispatchEvent(
202 | new CustomEvent(MOOA_EVENT.ROUTING_CHANGE, { detail: eventArgs })
203 | )
204 | }
205 | } else {
206 | customEvent(MOOA_EVENT.ROUTING_CHANGE, eventArgs)
207 | }
208 | }
209 |
210 | private mergeAppConfigOption(appConfig: any) {
211 | if (this.option.parentElement) {
212 | appConfig.parentElement = this.option.parentElement
213 | }
214 |
215 | if (this.option.preload) {
216 | appConfig.preload = true
217 | }
218 |
219 | if (this.option.includeZone === false) {
220 | appConfig.includeZone = false
221 | }
222 |
223 | return appConfig
224 | }
225 |
226 | private checkActiveWhen(activeWhen: any) {
227 | if (!activeWhen) {
228 | throw new Error(`Lost Loader`)
229 | }
230 |
231 | if (typeof activeWhen !== 'function') {
232 | throw new Error(`The activeWhen argument must be a function`)
233 | }
234 | }
235 | }
236 |
237 | export default Mooa
238 |
239 | export const mooaRouter = new MooaRouter()
240 | export const mooaPlatform = new MooaPlatform()
241 |
--------------------------------------------------------------------------------
/src/platform/platform.ts:
--------------------------------------------------------------------------------
1 | import { customEvent } from '../helper/app.helper'
2 | import { MOOA_EVENT } from '../model/constants'
3 |
4 | declare const window: any
5 | window.mooa = window.mooa || {}
6 |
7 | export class MooaPlatform {
8 | name: string = ''
9 | router: any
10 |
11 | mount(name: string, router?: any) {
12 | this.name = name
13 | this.router = router
14 | return new Promise((resolve, reject) => {
15 | if (this.isSingleSpaApp()) {
16 | customEvent(MOOA_EVENT.CHILD_MOUNT, { name: this.name })
17 | window.mooa[this.name] = window.mooa[this.name] || {}
18 | window.mooa[this.name].mount = (props: any) => {
19 | resolve({ props, attachUnmount: this.unmount.bind(this) })
20 | }
21 | } else {
22 | resolve({ props: {}, attachUnmount: this.unmount.bind(this) })
23 | }
24 | })
25 | }
26 |
27 | unmount(module: any) {
28 | if (this.isSingleSpaApp()) {
29 | customEvent(MOOA_EVENT.CHILD_UNMOUNT, { name: this.name })
30 | window.mooa[this.name].unmount = () => {
31 | if (module) {
32 | module.destroy()
33 | if (this.router) {
34 | module.injector.get(this.router).dispose()
35 | }
36 | }
37 | }
38 | }
39 | }
40 |
41 | appBase(): string {
42 | if (this.isSingleSpaApp()) {
43 | const pathNames = window.location.pathname.split('/')
44 | if (pathNames.length < 2) {
45 | return '/'
46 | }
47 | const parentRouter = pathNames[1]
48 | const appName = pathNames[2]
49 | const locationPath = '/' + parentRouter + '/' + appName
50 | window.mooa.basePath = locationPath
51 | return locationPath
52 | } else {
53 | return '/'
54 | }
55 | }
56 |
57 | navigateTo(opts: any) {
58 | customEvent(MOOA_EVENT.ROUTING_NAVIGATE, opts)
59 | }
60 |
61 | handleRouterUpdate(router: any, appName: string) {
62 | window.addEventListener(MOOA_EVENT.ROUTING_CHANGE, (event: CustomEvent) => {
63 | if (event.detail.app.name === appName) {
64 | let urlPrefix = 'app'
65 | if (urlPrefix) {
66 | urlPrefix = `/${window.mooa.option.urlPrefix}/`
67 | }
68 | router.navigate([event.detail.url.replace(urlPrefix + appName, '')])
69 | }
70 | })
71 | }
72 |
73 | private isSingleSpaApp(): boolean {
74 | return window.mooa.isSingleSpa
75 | }
76 | }
77 |
78 | export default MooaPlatform
79 |
--------------------------------------------------------------------------------
/src/router.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Robin Coma Delperier
3 | * Licensed under the Apache-2.0 License
4 | * https://github.com/PlaceMe-SAS/single-spa-angular-cli/blob/master/LICENSE
5 | *
6 | * modified by Phodal HUANG
7 | *
8 | */
9 | import { find } from './helper/app.helper'
10 |
11 | declare const history: History
12 |
13 | export class MooaRouter {
14 | routes: string[]
15 | lastPathName: string
16 | defaultRoute: string = ''
17 |
18 | constructor() {
19 | this.routes = []
20 | this.lastPathName = '/'
21 | }
22 |
23 | matchRoute(
24 | prefix: string,
25 | isDefaultPage?: boolean
26 | ): (location: Location) => boolean {
27 | this.routes.push(prefix)
28 | if (isDefaultPage) {
29 | this.defaultRoute = prefix
30 | }
31 | return (location: Location): boolean => {
32 | if (prefix === '/') {
33 | return location.pathname === '/'
34 | }
35 | const route = find(this.routes, (r: any) => this.pathMatch(location, r))
36 | if (route) {
37 | return this.pathMatch(location, prefix)
38 | } else {
39 | this.lastPathName = location.pathname
40 | this.navigate(this.defaultRoute)
41 | return false
42 | }
43 | }
44 | }
45 |
46 | public navigate(path: string): void {
47 | history.pushState(null, '', path)
48 | }
49 |
50 | private pathMatch(location: Location, path: string): boolean {
51 | const loc = location.pathname + location.search
52 | return loc.indexOf(path) !== -1
53 | }
54 | }
55 |
56 | export default MooaRouter
57 |
--------------------------------------------------------------------------------
/test/helper/app.helper.spec.ts:
--------------------------------------------------------------------------------
1 | import { hashCode } from '../../src/helper/app.helper'
2 |
3 | const AppHelper = require('../../src/helper/app.helper')
4 |
5 | test('should be able to find app name', () => {
6 | let apps = [{ name: 'app1' }, { name: 'help' }]
7 | let findApp = AppHelper.find(apps, (app: any) => {
8 | return app.name === 'help'
9 | })
10 | expect(findApp.name).toBe('help')
11 | })
12 |
13 | test('should be unable to find app name', () => {
14 | let apps = [{ name: 'app1' }, { name: 'help' }]
15 | let isFind = AppHelper.find(apps, (app: any) => {
16 | return app.name === 'help1'
17 | })
18 | expect(isFind).toBe(null)
19 | })
20 |
21 | test('should be able to call console log', () => {
22 | global.console.log = jest.fn()
23 | Object.defineProperty(window, 'mooa', () => {
24 | return true
25 | })
26 |
27 | AppHelper.mooaLog('hello', 'world')
28 | // expect(global.console.log).toHaveBeenCalled()
29 | })
30 |
31 | test('should get hash code', () => {
32 | let hash = hashCode('hello, world')
33 | expect(hash).toBe('543394210')
34 | })
35 |
36 | test('should not get hash code', () => {
37 | let hash = hashCode('')
38 | expect(hash).toBe('0')
39 | })
40 |
--------------------------------------------------------------------------------
/test/helper/assets-loader.helper.spec.ts:
--------------------------------------------------------------------------------
1 | const assetsLoaderHelper = require('../../src/helper/assets.helper').default
2 |
3 | test('should be able create script tag', () => {
4 | let scriptTag = assetsLoaderHelper.createScriptTag('/test.js')
5 | let tmp = document.createElement('div')
6 | tmp.appendChild(scriptTag)
7 | expect(tmp.innerHTML).toBe(
8 | ''
9 | )
10 | })
11 |
12 | test('should be able create style tag', () => {
13 | let scriptTag = assetsLoaderHelper.createLinkTag('/style.css')
14 | let tmp = document.createElement('div')
15 | tmp.appendChild(scriptTag)
16 | expect(tmp.innerHTML).toBe(
17 | ' '
18 | )
19 | })
20 |
--------------------------------------------------------------------------------
/test/helper/dom.utils.spec.ts:
--------------------------------------------------------------------------------
1 | import {
2 | createApplicationIframeContainer,
3 | generateIFrameID,
4 | isIframeElementExist,
5 | removeApplicationIframeContainer
6 | } from '../../src/helper/dom.utils'
7 | import { StatusEnum } from '../../src/model/constants'
8 |
9 | const jsdom = require('jsdom')
10 | const { JSDOM } = jsdom
11 | const { window, document } = new JSDOM()
12 |
13 | const globalAny: any = global
14 | globalAny.window = window
15 | globalAny.document = document
16 |
17 | test('should be able generate iframe id', () => {
18 | let iframeID = generateIFrameID('app')
19 | expect(iframeID).toEqual('app_12456')
20 | })
21 |
22 | test('should be able generate iframe id', () => {
23 | let opts: any = {
24 | name: 'help',
25 | appConfig: {
26 | name: 'help',
27 | selector: 'app-help',
28 | baseScriptUrl: '/assets/help',
29 | styles: ['styles.bundle.css'],
30 | prefix: 'help',
31 | scripts: ['inline.bundle.js', 'polyfills.bundle.js', 'main.bundle.js'],
32 | mode: 'iframe'
33 | },
34 | selector: 'app-help',
35 | switchMode: 'coexist',
36 | mode: 'iframe',
37 | bootstrap: jest.fn(),
38 | load: jest.fn(),
39 | mount: jest.fn(),
40 | unload: jest.fn(),
41 | unmount: () => null,
42 | timeouts: () => null,
43 | unloadApplication: () => null,
44 | getAppNames: () => null,
45 | status: StatusEnum.MOUNTED
46 | }
47 |
48 | createApplicationIframeContainer(opts)
49 | let htmlElement = isIframeElementExist(opts)
50 | let tmp = globalAny.document.createElement('div')
51 | tmp.appendChild(htmlElement)
52 | expect(tmp.innerHTML).toBe(
53 | ''
54 | )
55 | })
56 |
57 | test('should be able to remove iframe container', () => {
58 | let opts: any = {
59 | name: 'help',
60 | appConfig: {
61 | name: 'help',
62 | selector: 'app-help',
63 | baseScriptUrl: '/assets/help',
64 | link: '/assets/help',
65 | styles: ['styles.bundle.css'],
66 | prefix: 'help',
67 | scripts: ['inline.bundle.js', 'polyfills.bundle.js', 'main.bundle.js'],
68 | sourceType: 'link'
69 | },
70 | sourceType: 'link',
71 | selector: 'app-help',
72 | mode: 'iframe',
73 | bootstrap: jest.fn(),
74 | load: jest.fn(),
75 | mount: jest.fn(),
76 | unload: jest.fn(),
77 | unmount: () => null,
78 | timeouts: () => null,
79 | unloadApplication: () => null,
80 | getAppNames: () => null,
81 | status: StatusEnum.MOUNTED
82 | }
83 |
84 | createApplicationIframeContainer(opts)
85 | removeApplicationIframeContainer(opts)
86 | let htmlElement = isIframeElementExist(opts)
87 | expect(htmlElement).toBe(null)
88 | })
89 |
90 | test('should be able to hidden iframe when coexist mode', () => {
91 | let opts: any = {
92 | name: 'help',
93 | appConfig: {
94 | name: 'help',
95 | selector: 'app-help',
96 | baseScriptUrl: '/assets/help',
97 | link: '/assets/help',
98 | styles: ['styles.bundle.css'],
99 | prefix: 'help',
100 | scripts: ['inline.bundle.js', 'polyfills.bundle.js', 'main.bundle.js'],
101 | sourceType: 'link'
102 | },
103 | switchMode: 'coexist',
104 | sourceType: 'link',
105 | selector: 'app-help',
106 | mode: 'iframe',
107 | bootstrap: jest.fn(),
108 | load: jest.fn(),
109 | mount: jest.fn(),
110 | unload: jest.fn(),
111 | unmount: () => null,
112 | timeouts: () => null,
113 | unloadApplication: () => null,
114 | getAppNames: () => null,
115 | status: StatusEnum.MOUNTED
116 | }
117 |
118 | createApplicationIframeContainer(opts)
119 | removeApplicationIframeContainer(opts)
120 | let htmlElement = isIframeElementExist(opts)
121 | let tmp = globalAny.document.createElement('div')
122 | tmp.appendChild(htmlElement)
123 | expect(tmp.innerHTML).toBe(
124 | ''
125 | )
126 | })
127 |
--------------------------------------------------------------------------------
/test/helper/status.helper.spec.ts:
--------------------------------------------------------------------------------
1 | import StatusHelper from '../../src/helper/status.helper'
2 | import { StatusEnum } from '../../src/model/constants'
3 |
4 | test('should be able to find app name', () => {
5 | let activeApps = StatusHelper.getActiveApps([
6 | {
7 | status: StatusEnum.SKIP_BECAUSE_BROKEN
8 | },
9 | {
10 | status: StatusEnum.MOUNTED
11 | }
12 | ])
13 | expect(activeApps).toEqual([{ status: 'MOUNTED' }])
14 | })
15 |
16 | test('should be able to find app name', () => {
17 | let activeApps = StatusHelper.getAppsToMount([
18 | {
19 | status: StatusEnum.MOUNTED
20 | },
21 | {
22 | status: StatusEnum.SKIP_BECAUSE_BROKEN
23 | },
24 | {
25 | status: StatusEnum.NOT_LOADED
26 | },
27 | {
28 | status: StatusEnum.LOADING_SOURCE_CODE
29 | },
30 | {
31 | status: StatusEnum.NOT_MOUNTED,
32 | activeWhen: (parms: any) => {
33 | return true
34 | }
35 | }
36 | ])
37 | expect(activeApps[0].status).toEqual('NOT_MOUNTED')
38 | })
39 |
40 | test('should able to get app to unmount', () => {
41 | let activeApps = StatusHelper.getAppsToUnmount([
42 | {
43 | status: StatusEnum.MOUNTED,
44 | activeWhen: (parms: any) => {
45 | return true
46 | }
47 | },
48 | {
49 | status: StatusEnum.NOT_MOUNTED,
50 | activeWhen: (parms: any) => {
51 | return true
52 | }
53 | },
54 | {
55 | status: StatusEnum.MOUNTED,
56 | activeWhen: (parms: any) => {
57 | return false
58 | }
59 | }
60 | ])
61 | expect(activeApps[0].status).toEqual('MOUNTED')
62 | })
63 |
64 | test('should unable to get app to unload', () => {
65 | let activeApps = StatusHelper.getAppsToUnload()
66 | expect(activeApps).toEqual([])
67 | })
68 |
69 | test('should unable to get app to unload info', () => {
70 | let info = StatusHelper.getAppUnloadInfo('test')
71 | expect(info).toEqual(undefined)
72 | })
73 |
74 | test('should able get apps to load ', () => {
75 | let activeApps = StatusHelper.getAppsToLoad([
76 | {
77 | status: StatusEnum.LOADING_SOURCE_CODE,
78 | activeWhen: (parms: any) => {
79 | return true
80 | }
81 | }
82 | ])
83 | expect(activeApps[0].status).toEqual('LOADING_SOURCE_CODE')
84 | })
85 |
--------------------------------------------------------------------------------
/test/lifecycyle/bootstrap.spec.ts:
--------------------------------------------------------------------------------
1 | import { StatusEnum } from '../../src/model/constants'
2 | import { toBootstrapPromise } from '../../src/lifecycles/bootstrap'
3 | import { ensureValidAppTimeouts } from '../../src/helper/timeouts'
4 |
5 | test('bootstrap mooa success', () => {
6 | let bootstrapFlag = false
7 | toBootstrapPromise({
8 | name: 'mooa',
9 | status: StatusEnum.NOT_BOOTSTRAPPED,
10 | bootstrap: () => {
11 | bootstrapFlag = true
12 | return new Promise((resolve, reject) => {
13 | resolve()
14 | })
15 | },
16 | timeouts: ensureValidAppTimeouts(3000)
17 | }).then(app => {
18 | expect(bootstrapFlag).toBe(true)
19 | expect(app.status).toBe(StatusEnum.NOT_MOUNTED)
20 | })
21 | })
22 |
--------------------------------------------------------------------------------
/test/lifecycyle/load.spec.ts:
--------------------------------------------------------------------------------
1 | import { StatusEnum } from '../../src/model/constants'
2 | import { ensureValidAppTimeouts } from '../../src/helper/timeouts'
3 | import { toLoadPromise } from '../../src/lifecycles/load'
4 |
5 | test('load mooa success', () => {
6 | toLoadPromise({
7 | name: 'mooa',
8 | status: StatusEnum.NOT_LOADED,
9 | bootstrap: () => {
10 | return new Promise((resolve, reject) => {
11 | resolve()
12 | })
13 | },
14 | timeouts: ensureValidAppTimeouts(3000)
15 | }).then(app => {
16 | expect(app.status).toBe(StatusEnum.NOT_BOOTSTRAPPED)
17 | expect(typeof app.bootstrap).toBe('function')
18 | expect(typeof app.load).toBe('function')
19 | expect(typeof app.mount).toBe('function')
20 | expect(typeof app.unload).toBe('function')
21 | expect(typeof app.unmount).toBe('function')
22 | expect(typeof app.timeouts).toBe('object')
23 | })
24 | })
25 |
--------------------------------------------------------------------------------
/test/lifecycyle/mount.spec.ts:
--------------------------------------------------------------------------------
1 | import { StatusEnum } from '../../src/model/constants'
2 | import { ensureValidAppTimeouts } from '../../src/helper/timeouts'
3 | import { toMountPromise } from '../../src/lifecycles/mount'
4 |
5 | test('load mooa success', () => {
6 | let mountFlag = false
7 | toMountPromise({
8 | name: 'mooa',
9 | status: StatusEnum.NOT_MOUNTED,
10 | mount: () => {
11 | mountFlag = true
12 | return new Promise((resolve, reject) => {
13 | resolve()
14 | })
15 | },
16 | timeouts: ensureValidAppTimeouts(3000)
17 | }).then(app => {
18 | // expect(mountFlag).toBe(true)
19 | expect(app.status).toBe(StatusEnum.MOUNTED)
20 | })
21 | })
22 |
--------------------------------------------------------------------------------
/test/lifecycyle/unload.spec.ts:
--------------------------------------------------------------------------------
1 | import { StatusEnum } from '../../src/model/constants'
2 | import { ensureValidAppTimeouts } from '../../src/helper/timeouts'
3 | import { addAppToUnload, toUnloadPromise } from '../../src/lifecycles/unload'
4 |
5 | test('unload mooa success', () => {
6 | let unloadFlag = false
7 | let app = {
8 | name: 'mooa',
9 | status: StatusEnum.NOT_MOUNTED,
10 | unload: () => {
11 | unloadFlag = true
12 | return new Promise((resolve, reject) => {
13 | resolve()
14 | })
15 | },
16 | timeouts: ensureValidAppTimeouts(3000)
17 | }
18 | const resultPromise = new Promise((resolve, reject) => {
19 | addAppToUnload(app, () => resultPromise, resolve, reject)
20 | })
21 |
22 | toUnloadPromise(app).then(app => {
23 | expect(unloadFlag).toBe(true)
24 | expect(app.status).toBe(StatusEnum.NOT_LOADED)
25 | })
26 | })
27 |
--------------------------------------------------------------------------------
/test/lifecycyle/unmount.spec.ts:
--------------------------------------------------------------------------------
1 | import { addAppToUnload } from '../../src/lifecycles/unload'
2 | import { StatusEnum } from '../../src/model/constants'
3 | import { ensureValidAppTimeouts } from '../../src/helper/timeouts'
4 | import {
5 | getAppNames,
6 | toUnmountPromise,
7 | unloadApplication
8 | } from '../../src/lifecycles/unmount'
9 |
10 | const jsdom = require('jsdom')
11 | const { JSDOM } = jsdom
12 | const { window, document } = new JSDOM()
13 |
14 | const globalAny: any = global
15 | globalAny.window = window
16 | globalAny.document = document
17 |
18 | test('unmount mooa success', () => {
19 | let mockApp = {
20 | name: 'help',
21 | status: StatusEnum.MOUNTED,
22 | unmount: () => null
23 | }
24 | globalAny.window.apps = [mockApp]
25 | let unmountPromise = false
26 | let app = {
27 | name: 'help',
28 | status: StatusEnum.MOUNTED,
29 | unloadApplication: jest.fn(),
30 | unmount: (app: any) => {
31 | unmountPromise = true
32 | return new Promise((resolve, reject) => {
33 | resolve()
34 | })
35 | },
36 | timeouts: ensureValidAppTimeouts(3000)
37 | }
38 | let appNames = getAppNames()
39 | expect(appNames).toEqual(['help'])
40 | toUnmountPromise(app).then(app => {
41 | expect(unmountPromise).toBe(true)
42 | expect(app.status).toBe(StatusEnum.NOT_MOUNTED)
43 | })
44 | })
45 |
46 | test('unloadApplication', () => {
47 | let unmountPromise = false
48 |
49 | let mockApp = {
50 | name: 'help',
51 | status: StatusEnum.MOUNTED,
52 | unmount: (app: any) => {
53 | unmountPromise = true
54 | return new Promise((resolve, reject) => {
55 | resolve()
56 | })
57 | },
58 | unload: () => {
59 | return new Promise((resolve, reject) => {
60 | resolve()
61 | })
62 | },
63 | timeouts: ensureValidAppTimeouts(3000)
64 | }
65 | globalAny.window.apps = [mockApp]
66 | let app = {
67 | name: 'help',
68 | status: StatusEnum.MOUNTED
69 | }
70 | const resultPromise = new Promise((resolve, reject) => {
71 | addAppToUnload(app, () => resultPromise, resolve, reject)
72 | })
73 | unloadApplication('help')
74 |
75 | resultPromise.then(() => {
76 | expect(unmountPromise).toBe(true)
77 | })
78 | })
79 |
--------------------------------------------------------------------------------
/test/loader/loader.bootstrap.spec.ts:
--------------------------------------------------------------------------------
1 | const jsdom = require('jsdom')
2 | const { JSDOM } = jsdom
3 | const { window, document } = new JSDOM()
4 |
5 | const globalAny: any = global
6 | globalAny.window = window
7 | globalAny.document = document
8 |
9 | import mooaLoader from '../../src/loader/mooa.loader'
10 | import { StatusEnum } from '../../src/model/constants'
11 |
12 | test('bootstrap test', () => {
13 | let opts = {
14 | name: 'help',
15 | appConfig: {
16 | name: 'help',
17 | selector: 'app-help',
18 | baseScriptUrl: '/assets/help',
19 | styles: ['styles.bundle.css'],
20 | prefix: 'help',
21 | scripts: ['inline.bundle.js', 'polyfills.bundle.js', 'main.bundle.js'],
22 | mode: 'iframe'
23 | },
24 | bootstrap: jest.fn(),
25 | load: jest.fn(),
26 | mount: jest.fn(),
27 | unload: jest.fn(),
28 | unmount: () => null,
29 | status: StatusEnum.MOUNTED
30 | }
31 |
32 | const loader = mooaLoader(opts)
33 |
34 | loader.bootstrap.call(opts)
35 | expect(loader.bootstrap.call(opts)).toEqual(
36 | new Promise((resolve, reject) => {
37 | resolve()
38 | })
39 | )
40 | })
41 |
42 | test('bootstrap iframe test', () => {
43 | let opts = {
44 | name: 'help',
45 | appConfig: {
46 | name: 'help',
47 | selector: 'app-help',
48 | baseScriptUrl: '/assets/help',
49 | styles: ['styles.bundle.css'],
50 | prefix: 'help',
51 | scripts: ['inline.bundle.js', 'polyfills.bundle.js', 'main.bundle.js'],
52 | mode: 'iframe'
53 | },
54 | mode: 'iframe',
55 | bootstrap: jest.fn(),
56 | load: jest.fn(),
57 | mount: jest.fn(),
58 | unload: jest.fn(),
59 | unmount: () => null,
60 | status: StatusEnum.MOUNTED
61 | }
62 |
63 | const loader = mooaLoader(opts)
64 |
65 | expect(loader.bootstrap.call(opts)).toEqual(
66 | new Promise((resolve, reject) => {
67 | resolve()
68 | })
69 | )
70 | })
71 |
72 | test('bootstrap link test', () => {
73 | let opts = {
74 | name: 'help',
75 | appConfig: {
76 | name: 'help',
77 | selector: 'app-help',
78 | baseScriptUrl: '/assets/help',
79 | styles: ['styles.bundle.css'],
80 | prefix: 'help',
81 | scripts: ['inline.bundle.js', 'polyfills.bundle.js', 'main.bundle.js'],
82 | sourceType: 'link'
83 | },
84 | sourceType: 'link',
85 | bootstrap: jest.fn(),
86 | load: jest.fn(),
87 | mount: jest.fn(),
88 | unload: jest.fn(),
89 | unmount: () => null,
90 | status: StatusEnum.MOUNTED
91 | }
92 |
93 | const loader = mooaLoader(opts)
94 |
95 | expect(loader.bootstrap.call(opts)).toEqual(
96 | new Promise((resolve, reject) => {
97 | resolve()
98 | })
99 | )
100 | })
101 |
--------------------------------------------------------------------------------
/test/loader/loader.unload.spec.ts:
--------------------------------------------------------------------------------
1 | const jsdom = require('jsdom')
2 | const { JSDOM } = jsdom
3 | const { window, document } = new JSDOM()
4 |
5 | const globalAny: any = global
6 | globalAny.window = window
7 | globalAny.document = document
8 |
9 | import mooaLoader from '../../src/loader/mooa.loader'
10 | import { StatusEnum } from '../../src/model/constants'
11 |
12 | test('loader unload test', () => {
13 | globalAny.window.mooa = {
14 | help: {
15 | mount: () => {
16 | return
17 | },
18 | unmount: () => {
19 | return
20 | }
21 | }
22 | }
23 |
24 | let opts = {
25 | name: 'help',
26 | appConfig: {
27 | name: 'help',
28 | selector: 'app-help',
29 | baseScriptUrl: '/assets/help',
30 | styles: ['styles.bundle.css'],
31 | prefix: 'help',
32 | scripts: ['inline.bundle.js', 'polyfills.bundle.js', 'main.bundle.js'],
33 | mode: 'iframe'
34 | },
35 | switchMode: 'coexist',
36 | bootstrap: jest.fn(),
37 | load: jest.fn(),
38 | mount: jest.fn(),
39 | unload: jest.fn(),
40 | unmount: () => {
41 | return
42 | },
43 | status: StatusEnum.MOUNTED
44 | }
45 |
46 | const loader = mooaLoader(opts)
47 |
48 | loader.load()
49 | loader.bootstrap()
50 | loader.mount()
51 | loader.unmount({
52 | unloadApplication: () => {
53 | return
54 | },
55 | getAppNames: () => {
56 | return ['help']
57 | }
58 | })
59 | expect(loader.unload()).toEqual(
60 | new Promise((resolve, reject) => {
61 | resolve()
62 | })
63 | )
64 | })
65 |
66 | test('loader unload coexist test', () => {
67 | globalAny.window.mooa = {
68 | help: {
69 | mount: () => {
70 | return
71 | },
72 | unmount: () => {
73 | return
74 | }
75 | }
76 | }
77 |
78 | let opts = {
79 | name: 'help',
80 | appConfig: {
81 | name: 'help',
82 | selector: 'app-help',
83 | baseScriptUrl: '/assets/help',
84 | styles: ['styles.bundle.css'],
85 | prefix: 'help',
86 | scripts: ['inline.bundle.js', 'polyfills.bundle.js', 'main.bundle.js'],
87 | mode: 'iframe'
88 | },
89 | bootstrap: jest.fn(),
90 | load: jest.fn(),
91 | mount: jest.fn(),
92 | unload: jest.fn(),
93 | unmount: () => {
94 | return
95 | },
96 | status: StatusEnum.MOUNTED
97 | }
98 |
99 | const loader = mooaLoader(opts)
100 |
101 | loader.load()
102 | loader.bootstrap()
103 | loader.mount()
104 | loader.unmount({
105 | unloadApplication: () => {
106 | return
107 | },
108 | getAppNames: () => {
109 | return ['help']
110 | }
111 | })
112 | loader.unload().then(() => {
113 | expect(opts.appConfig.scripts).toEqual([
114 | 'inline.bundle.js',
115 | 'polyfills.bundle.js',
116 | 'main.bundle.js'
117 | ])
118 | })
119 | })
120 |
--------------------------------------------------------------------------------
/test/loader/loader.unmount.spec.ts:
--------------------------------------------------------------------------------
1 | const jsdom = require('jsdom')
2 | const { JSDOM } = jsdom
3 | const { window, document } = new JSDOM()
4 |
5 | const globalAny: any = global
6 | globalAny.window = window
7 | globalAny.document = document
8 |
9 | import mooaLoader from '../../src/loader/mooa.loader'
10 | import { StatusEnum } from '../../src/model/constants'
11 |
12 | test('unmount test', () => {
13 | globalAny.window.mooa = {
14 | help: {
15 | mount: () => {
16 | return
17 | },
18 | unmount: () => {
19 | return
20 | }
21 | }
22 | }
23 |
24 | let opts = {
25 | name: 'help',
26 | appConfig: {
27 | name: 'help',
28 | selector: 'app-help',
29 | baseScriptUrl: '/assets/help',
30 | styles: ['styles.bundle.css'],
31 | prefix: 'help',
32 | scripts: ['inline.bundle.js', 'polyfills.bundle.js', 'main.bundle.js'],
33 | mode: 'iframe'
34 | },
35 | bootstrap: jest.fn(),
36 | load: jest.fn(),
37 | mount: jest.fn(),
38 | unload: jest.fn(),
39 | unmount: () => {
40 | return
41 | },
42 | status: StatusEnum.MOUNTED
43 | }
44 |
45 | const loader = mooaLoader(opts)
46 |
47 | loader.load()
48 | loader.bootstrap()
49 | loader.mount()
50 | expect(
51 | loader.unmount({
52 | unloadApplication: () => {
53 | return
54 | },
55 | getAppNames: () => {
56 | return ['help']
57 | }
58 | })
59 | ).toEqual(
60 | new Promise((resolve, reject) => {
61 | resolve()
62 | })
63 | )
64 | })
65 |
--------------------------------------------------------------------------------
/test/loader/mooa.loader.spec.ts:
--------------------------------------------------------------------------------
1 | const jsdom = require('jsdom')
2 | const { JSDOM } = jsdom
3 | const { window, document } = new JSDOM()
4 |
5 | const globalAny: any = global
6 | globalAny.window = window
7 | globalAny.document = document
8 |
9 | import mooaLoader from '../../src/loader/mooa.loader'
10 | import { StatusEnum } from '../../src/model/constants'
11 |
12 | test('load test', () => {
13 | const loader = mooaLoader({
14 | name: '',
15 | appConfig: {
16 | name: 'help',
17 | selector: 'app-help',
18 | baseScriptUrl: '/assets/help',
19 | styles: ['styles.bundle.css'],
20 | prefix: 'help',
21 | scripts: ['inline.bundle.js', 'polyfills.bundle.js', 'main.bundle.js'],
22 | mode: 'iframe'
23 | }
24 | })
25 |
26 | expect(loader.load()).toEqual(
27 | new Promise((resolve, reject) => {
28 | resolve()
29 | })
30 | )
31 | })
32 |
33 | test('mount test', () => {
34 | globalAny.window.mooa = {
35 | help: {
36 | mount: jest.fn()
37 | }
38 | }
39 |
40 | let opts = {
41 | name: 'help',
42 | appConfig: {
43 | name: 'help',
44 | selector: 'app-help',
45 | baseScriptUrl: '/assets/help',
46 | styles: ['styles.bundle.css'],
47 | prefix: 'help',
48 | scripts: ['inline.bundle.js', 'polyfills.bundle.js', 'main.bundle.js'],
49 | mode: 'iframe'
50 | },
51 | bootstrap: jest.fn(),
52 | load: jest.fn(),
53 | mount: jest.fn(),
54 | unload: jest.fn(),
55 | unmount: jest.fn(),
56 | status: StatusEnum.MOUNTED
57 | }
58 |
59 | const loader = mooaLoader(opts)
60 |
61 | loader.load()
62 | loader.bootstrap()
63 | expect(loader.mount()).toEqual(
64 | new Promise((resolve, reject) => {
65 | resolve()
66 | })
67 | )
68 | })
69 | //
70 | // test('mount iframe test', () => {
71 | // globalAny.window.mooa = {
72 | // help: {
73 | // mount: jest.fn()
74 | // }
75 | // }
76 | //
77 | // let opts = {
78 | // name: 'help',
79 | // appConfig: {
80 | // name: 'help',
81 | // selector: 'app-help',
82 | // baseScriptUrl: '/assets/help',
83 | // styles: ['styles.bundle.css'],
84 | // prefix: 'help',
85 | // scripts: ['inline.bundle.js', 'polyfills.bundle.js', 'main.bundle.js']
86 | // },
87 | // mode: 'iframe',
88 | // bootstrap: jest.fn(),
89 | // load: jest.fn(),
90 | // mount: jest.fn(),
91 | // unload: jest.fn(),
92 | // unmount: jest.fn(),
93 | // status: StatusEnum.MOUNTED
94 | // }
95 | //
96 | // const loader = mooaLoader(opts)
97 | //
98 | // loader.load()
99 | // loader.bootstrap()
100 | // expect(loader.mount()).toEqual(
101 | // new Promise((resolve, reject) => {
102 | // resolve()
103 | // })
104 | // )
105 | // })
106 |
--------------------------------------------------------------------------------
/test/mooa.spec.ts:
--------------------------------------------------------------------------------
1 | const jsdom = require('jsdom')
2 | const { JSDOM } = jsdom
3 | const { window, document } = new JSDOM()
4 |
5 | const globalAny: any = global
6 | globalAny.window = window
7 | globalAny.document = document
8 |
9 | import { default as Mooa, mooaRouter } from '../src/mooa'
10 |
11 | beforeEach(() => {
12 | globalAny.apps = []
13 | globalAny.mooa = {}
14 | })
15 |
16 | afterEach(() => {
17 | globalAny.apps = []
18 | globalAny.mooa = {}
19 | })
20 |
21 | test('new mooa test', () => {
22 | const mooa = new Mooa({
23 | mode: 'iframe',
24 | debug: false,
25 | parentElement: 'app-home',
26 | urlPrefix: 'app',
27 | switchMode: 'coexist',
28 | preload: true,
29 | includeZone: false
30 | })
31 |
32 | mooa.registerApplicationByLink(
33 | 'help',
34 | '/assets/help',
35 | mooaRouter.matchRoute('help')
36 | )
37 | mooa.start()
38 | })
39 |
40 | test('new mooa by register', () => {
41 | const mooa = new Mooa({
42 | mode: 'iframe',
43 | debug: false,
44 | parentElement: 'app-home',
45 | urlPrefix: 'app',
46 | switchMode: 'coexist',
47 | preload: true,
48 | includeZone: true
49 | })
50 |
51 | mooa.registerApplication(
52 | 'help',
53 | {
54 | name: 'help',
55 | selector: 'app-help',
56 | baseScriptUrl: '/assets/help',
57 | styles: ['styles.bundle.css'],
58 | prefix: 'help',
59 | scripts: ['inline.bundle.js', 'polyfills.bundle.js', 'main.bundle.js'],
60 | mode: 'iframe'
61 | },
62 | mooaRouter.matchRoute('help')
63 | )
64 | mooa.start()
65 |
66 | let currentApp = globalAny.apps[globalAny.apps.length - 1]
67 | expect(currentApp.name).toEqual('help')
68 | expect(currentApp.appConfig).toEqual({
69 | baseScriptUrl: '/assets/help',
70 | includeZone: true,
71 | mode: 'iframe',
72 | name: 'help',
73 | parentElement: 'app-home',
74 | prefix: 'app/help',
75 | preload: true,
76 | scripts: ['inline.bundle.js', 'polyfills.bundle.js', 'main.bundle.js'],
77 | selector: 'app-help',
78 | styles: ['styles.bundle.css']
79 | })
80 | expect(typeof globalAny.mooa.instance).toEqual('object')
81 | })
82 |
83 | test('new mooa by with options', () => {
84 | globalAny.apps = []
85 | const mooa = new Mooa({
86 | mode: 'iframe',
87 | debug: false,
88 | parentElement: 'app-home',
89 | urlPrefix: 'app',
90 | switchMode: 'coexist',
91 | preload: true,
92 | includeZone: false
93 | })
94 |
95 | let appConfig = {
96 | name: 'help',
97 | selector: 'app-help',
98 | baseScriptUrl: '/assets/help',
99 | styles: ['styles.bundle.css'],
100 | prefix: 'help',
101 | scripts: ['inline.bundle.js', 'polyfills.bundle.js', 'main.bundle.js'],
102 | mode: 'iframe'
103 | }
104 |
105 | mooa.registerApplication('help', appConfig, mooaRouter.matchRoute('help'))
106 | mooa.start()
107 |
108 | let currentApp = globalAny.apps[globalAny.apps.length - 1]
109 |
110 | expect(currentApp.name).toEqual('help')
111 | expect(currentApp.mode).toEqual('iframe')
112 | expect(currentApp.switchMode).toEqual('coexist')
113 | expect(currentApp.appConfig).toEqual({
114 | baseScriptUrl: '/assets/help',
115 | includeZone: false,
116 | mode: 'iframe',
117 | name: 'help',
118 | parentElement: 'app-home',
119 | prefix: 'app/help',
120 | preload: true,
121 | scripts: ['inline.bundle.js', 'polyfills.bundle.js', 'main.bundle.js'],
122 | selector: 'app-help',
123 | styles: ['styles.bundle.css']
124 | })
125 | expect(typeof globalAny.mooa.instance).toEqual('object')
126 | })
127 |
--------------------------------------------------------------------------------
/test/platform/platform.spec.ts:
--------------------------------------------------------------------------------
1 | import MooaPlatform from '../../src/platform/platform'
2 | import { MOOA_EVENT } from '../../src/model/constants'
3 | const jsdom = require('jsdom')
4 | const { JSDOM } = jsdom
5 | const { window, document } = new JSDOM()
6 |
7 | const globalAny: any = global
8 | globalAny.window = window
9 | globalAny.document = document
10 |
11 | let mooaPlatform = new MooaPlatform()
12 |
13 | test('platform base path 1', () => {
14 | let basePath = mooaPlatform.appBase()
15 | expect(basePath).toBe('/')
16 | })
17 |
18 | test('platform base path 2', () => {
19 | globalAny.window.mooa = {
20 | isSingleSpa: true
21 | }
22 | let mooaPlatform = new MooaPlatform()
23 | let basePath = mooaPlatform.appBase()
24 | expect(basePath).toBe('//undefined')
25 | })
26 |
27 | test('platform base mount', () => {
28 | let mooaPlatform = new MooaPlatform()
29 | let basePath = mooaPlatform.mount('app1', '/assets/app1')
30 | expect(basePath).toEqual(
31 | new Promise((resolve, reject) => {
32 | resolve()
33 | })
34 | )
35 | })
36 |
37 | test('platform base mount', () => {
38 | let mooaPlatform = new MooaPlatform()
39 | mooaPlatform.mount('app1', '/assets/app1')
40 | globalAny.window['app1'] = {
41 | unmount: () => {
42 | return
43 | }
44 | }
45 | let func = {
46 | destroy: () => {
47 | return
48 | },
49 | injector: () => {
50 | return {
51 | get: (params: any) => {
52 | return {
53 | dispose: () => {
54 | return
55 | }
56 | }
57 | }
58 | }
59 | }
60 | }
61 | mooaPlatform.unmount(func)
62 | })
63 |
64 | test('platform navigateTo', () => {
65 | let mooaPlatform = new MooaPlatform()
66 | mooaPlatform.navigateTo({
67 | name: 'helo',
68 | route: ''
69 | })
70 |
71 | window.dispatchEvent = jest.fn()
72 | })
73 |
74 | test('platform handleRouterUpdate', () => {
75 | let eventOpt: any = ''
76 | let mooaPlatform = new MooaPlatform()
77 | mooaPlatform.handleRouterUpdate(
78 | {
79 | navigate: (opt: any) => {
80 | eventOpt = opt
81 | }
82 | },
83 | 'help'
84 | )
85 |
86 | window.dispatchEvent(
87 | new CustomEvent(MOOA_EVENT.ROUTING_CHANGE, {
88 | detail: {
89 | url: '',
90 | app: {
91 | name: 'help'
92 | }
93 | }
94 | })
95 | )
96 | })
97 |
--------------------------------------------------------------------------------
/tools/gh-pages-publish.ts:
--------------------------------------------------------------------------------
1 | const { cd, exec, echo, touch } = require("shelljs")
2 | const { readFileSync } = require("fs")
3 | const url = require("url")
4 |
5 | let repoUrl
6 | let pkg = JSON.parse(readFileSync("package.json") as any)
7 | if (typeof pkg.repository === "object") {
8 | if (!pkg.repository.hasOwnProperty("url")) {
9 | throw new Error("URL does not exist in repository section")
10 | }
11 | repoUrl = pkg.repository.url
12 | } else {
13 | repoUrl = pkg.repository
14 | }
15 |
16 | let parsedUrl = url.parse(repoUrl)
17 | let repository = (parsedUrl.host || "") + (parsedUrl.path || "")
18 | let ghToken = process.env.GH_TOKEN
19 |
20 | echo("Deploying docs!!!")
21 | cd("docs")
22 | touch(".nojekyll")
23 | exec("git init")
24 | exec("git add .")
25 | exec('git config user.name "Phodal HUANG"')
26 | exec('git config user.email "h@phodal.com"')
27 | exec('git commit -m "docs(docs): update gh-pages"')
28 | exec(
29 | `git push --force --quiet "https://${ghToken}@${repository}" master:gh-pages`
30 | )
31 | echo("Docs deployed!!")
32 |
--------------------------------------------------------------------------------
/tools/semantic-release-prepare.ts:
--------------------------------------------------------------------------------
1 | const path = require("path")
2 | const { fork } = require("child_process")
3 | const colors = require("colors")
4 |
5 | const { readFileSync, writeFileSync } = require("fs")
6 | const pkg = JSON.parse(
7 | readFileSync(path.resolve(__dirname, "..", "package.json"))
8 | )
9 |
10 | pkg.scripts.prepush = "npm run test:prod && npm run build"
11 | pkg.scripts.commitmsg = "validate-commit-msg"
12 |
13 | writeFileSync(
14 | path.resolve(__dirname, "..", "package.json"),
15 | JSON.stringify(pkg, null, 2)
16 | )
17 |
18 | // Call husky to set up the hooks
19 | fork(path.resolve(__dirname, "..", "node_modules", "husky", "bin", "install"))
20 |
21 | console.log()
22 | console.log(colors.green("Done!!"))
23 | console.log()
24 |
25 | if (pkg.repository.url.trim()) {
26 | console.log(colors.cyan("Now run:"))
27 | console.log(colors.cyan(" npm install -g semantic-release-cli"))
28 | console.log(colors.cyan(" semantic-release-cli setup"))
29 | console.log()
30 | console.log(
31 | colors.cyan('Important! Answer NO to "Generate travis.yml" question')
32 | )
33 | console.log()
34 | console.log(
35 | colors.gray(
36 | 'Note: Make sure "repository.url" in your package.json is correct before'
37 | )
38 | )
39 | } else {
40 | console.log(
41 | colors.red(
42 | 'First you need to set the "repository.url" property in package.json'
43 | )
44 | )
45 | console.log(colors.cyan("Then run:"))
46 | console.log(colors.cyan(" npm install -g semantic-release-cli"))
47 | console.log(colors.cyan(" semantic-release-cli setup"))
48 | console.log()
49 | console.log(
50 | colors.cyan('Important! Answer NO to "Generate travis.yml" question')
51 | )
52 | }
53 |
54 | console.log()
55 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "moduleResolution": "node",
4 | "target": "es5",
5 | "module":"es2015",
6 | "lib": ["es2015", "es2016", "es2017", "dom"],
7 | "strict": true,
8 | "sourceMap": true,
9 | "declaration": true,
10 | "allowSyntheticDefaultImports": true,
11 | "experimentalDecorators": true,
12 | "emitDecoratorMetadata": true,
13 | "declarationDir": "dist/types",
14 | "outDir": "dist/lib",
15 | "typeRoots": [
16 | "node_modules/@types"
17 | ]
18 | },
19 | "include": [
20 | "./src"
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "tslint-config-standard",
4 | "tslint-config-prettier"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------