├── .all-contributorsrc
├── .circleci
└── config.yml
├── .coveralls.yml
├── .docgeni
└── public
│ ├── assets
│ ├── favicon.ico
│ └── images
│ │ ├── banner-origin.png
│ │ ├── banner.png
│ │ ├── home
│ │ ├── feature1.png
│ │ ├── feature2.png
│ │ ├── feature3.png
│ │ ├── feature4.png
│ │ ├── feature5.png
│ │ └── feature6.png
│ │ └── planet.png
│ ├── index.html
│ └── styles.scss
├── .docgenirc.js
├── .dockerignore
├── .editorconfig
├── .eslintrc.json
├── .github
├── FUNDING.yml
└── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── .gitignore
├── .prettierignore
├── .prettierrc.js
├── .travis.yml
├── .wpmrc.js
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── Dockerfile
├── LICENSE
├── README.md
├── README.zh-CN.md
├── SECURITY.md
├── angular.json
├── commitlint.config.js
├── docs
├── api
│ ├── define-application.md
│ ├── global-event-dispatcher.md
│ ├── index.md
│ ├── planet-component-loader.md
│ └── planet.md
├── guides
│ ├── advanced
│ │ ├── index.md
│ │ ├── sandbox-isolation.md
│ │ └── style-isolation.md
│ ├── communication.md
│ ├── cross-app-comp-rendering.md
│ ├── dev-build.md
│ ├── faq.md
│ ├── getting-started.md
│ ├── index.md
│ ├── intro.md
│ └── migration-v17.md
└── index.md
├── examples
├── app1
│ ├── .eslintrc.json
│ ├── browserslist
│ ├── extra-webpack.config.js
│ ├── karma.conf.js
│ ├── postcss.config.js
│ ├── src
│ │ ├── app
│ │ │ ├── app.module.ts
│ │ │ ├── app.routing.ts
│ │ │ ├── counter.service.ts
│ │ │ ├── dashboard
│ │ │ │ ├── dashboard.component.html
│ │ │ │ └── dashboard.component.ts
│ │ │ ├── detail
│ │ │ │ ├── detail.component.html
│ │ │ │ └── detail.component.ts
│ │ │ ├── overlay.ts
│ │ │ ├── projects
│ │ │ │ ├── dialog
│ │ │ │ │ ├── projects-dialog.component.html
│ │ │ │ │ └── projects-dialog.component.ts
│ │ │ │ └── projects.component.ts
│ │ │ ├── root
│ │ │ │ ├── root.component.css
│ │ │ │ ├── root.component.html
│ │ │ │ └── root.component.ts
│ │ │ ├── services
│ │ │ │ └── initialized-data.resolver.ts
│ │ │ ├── shared.module.ts
│ │ │ └── user
│ │ │ │ ├── detail
│ │ │ │ ├── user-detail.component.html
│ │ │ │ └── user-detail.component.ts
│ │ │ │ ├── user-list.component.html
│ │ │ │ ├── user-list.component.ts
│ │ │ │ └── user.module.ts
│ │ ├── assets
│ │ │ ├── .gitkeep
│ │ │ ├── github.png
│ │ │ └── main.css
│ │ ├── environments
│ │ │ ├── environment.prod.ts
│ │ │ └── environment.ts
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ ├── main.ts
│ │ ├── polyfills.ts
│ │ ├── styles.scss
│ │ └── test.ts
│ ├── temp.js
│ ├── tsconfig.app.json
│ ├── tsconfig.spec.json
│ └── webpack.config.js
├── app2
│ ├── .eslintrc.json
│ ├── browserslist
│ ├── extra-webpack.config.js
│ ├── karma.conf.js
│ ├── src
│ │ ├── app
│ │ │ ├── app.module.ts
│ │ │ ├── dashboard
│ │ │ │ ├── dashboard.component.html
│ │ │ │ └── dashboard.component.ts
│ │ │ ├── overlay.ts
│ │ │ ├── projects
│ │ │ │ ├── detail
│ │ │ │ │ ├── detail.component.html
│ │ │ │ │ └── detail.component.ts
│ │ │ │ ├── project-list.component.html
│ │ │ │ ├── project-list.component.ts
│ │ │ │ ├── project.resolver.ts
│ │ │ │ ├── projects.service.ts
│ │ │ │ ├── tasks
│ │ │ │ │ ├── tasks.component.html
│ │ │ │ │ └── tasks.component.ts
│ │ │ │ └── view
│ │ │ │ │ ├── view.component.html
│ │ │ │ │ └── view.component.ts
│ │ │ ├── root
│ │ │ │ ├── root.component.html
│ │ │ │ ├── root.component.scss
│ │ │ │ └── root.component.ts
│ │ │ ├── shared.module.ts
│ │ │ └── user
│ │ │ │ ├── user-list.component.html
│ │ │ │ ├── user-list.component.ts
│ │ │ │ └── user.module.ts
│ │ ├── assets
│ │ │ └── .gitkeep
│ │ ├── environments
│ │ │ ├── environment.prod.ts
│ │ │ └── environment.ts
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ ├── main.ts
│ │ ├── polyfills.ts
│ │ ├── styles.scss
│ │ └── test.ts
│ ├── tsconfig.app.json
│ ├── tsconfig.spec.json
│ └── webpack.config.js
├── common
│ ├── app-root-context.ts
│ ├── cache.ts
│ ├── index.ts
│ ├── module.ts
│ ├── overlay-container.service.ts
│ ├── section-card
│ │ ├── section-card.component.html
│ │ ├── section-card.component.scss
│ │ └── section-card.component.ts
│ └── utils.ts
├── portal
│ └── src
│ │ ├── .eslintrc.json
│ │ ├── app
│ │ ├── a-detail
│ │ │ ├── a-detail.component.html
│ │ │ └── a-detail.component.ts
│ │ ├── about
│ │ │ ├── about.component.html
│ │ │ ├── about.component.scss
│ │ │ ├── about.component.spec.ts
│ │ │ ├── about.component.ts
│ │ │ └── components
│ │ │ │ ├── portal-custom.component.html
│ │ │ │ ├── portal-custom.component.scss
│ │ │ │ └── portal-custom.component.ts
│ │ ├── app-routing.module.ts
│ │ ├── app.component.html
│ │ ├── app.component.scss
│ │ ├── app.component.spec.ts
│ │ ├── app.component.ts
│ │ ├── app.module.ts
│ │ ├── custom-settings.service.ts
│ │ ├── overlay.ts
│ │ └── settings
│ │ │ ├── settings.component.html
│ │ │ ├── settings.component.scss
│ │ │ └── settings.component.ts
│ │ ├── assets
│ │ ├── .gitkeep
│ │ ├── apps.json
│ │ ├── icons
│ │ │ └── sprite.defs.svg
│ │ ├── images
│ │ │ └── logo.svg
│ │ └── ngx-planet-micro-front-end.gif
│ │ ├── browserslist
│ │ ├── environments
│ │ ├── environment.prod.ts
│ │ └── environment.ts
│ │ ├── extra-webpack.config.js
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ ├── karma.conf.js
│ │ ├── main.ts
│ │ ├── polyfills.ts
│ │ ├── reboot.scss
│ │ ├── styles.scss
│ │ ├── test.ts
│ │ ├── tsconfig.app.json
│ │ └── tsconfig.spec.json
└── standalone-app
│ ├── .eslintrc.json
│ ├── plugin-manifest.js
│ ├── src
│ ├── app
│ │ ├── about
│ │ │ ├── about.component.html
│ │ │ └── about.component.ts
│ │ ├── app.component.html
│ │ ├── app.component.scss
│ │ ├── app.component.spec.ts
│ │ ├── app.component.ts
│ │ ├── app.config.ts
│ │ ├── app.routes.ts
│ │ └── dashboard
│ │ │ ├── dashboard.component.html
│ │ │ └── dashboard.component.ts
│ ├── assets
│ │ └── .gitkeep
│ ├── favicon.ico
│ ├── index.html
│ ├── main.ts
│ └── styles.scss
│ ├── tsconfig.app.json
│ └── tsconfig.spec.json
├── extra-webpack.config.js
├── nginx.conf
├── package-lock.json
├── package.json
├── packages
└── planet
│ ├── .eslintrc.json
│ ├── karma.conf.js
│ ├── ng-package.json
│ ├── package.json
│ ├── src
│ ├── application
│ │ ├── ng-planet-application-ref.ts
│ │ ├── planet-application-loader.spec.ts
│ │ ├── planet-application-loader.ts
│ │ ├── planet-application-ref.spec.ts
│ │ ├── planet-application-ref.ts
│ │ ├── planet-application.service.spec.ts
│ │ ├── planet-application.service.ts
│ │ ├── portal-application.spec.ts
│ │ └── portal-application.ts
│ ├── assets-loader.spec.ts
│ ├── assets-loader.ts
│ ├── component
│ │ ├── planet-component-loader.spec.ts
│ │ ├── planet-component-loader.ts
│ │ ├── planet-component-outlet.spec.ts
│ │ ├── planet-component-outlet.ts
│ │ ├── planet-component-types.ts
│ │ └── plant-component.config.ts
│ ├── debug.spec.ts
│ ├── debug.ts
│ ├── empty
│ │ ├── empty.component.spec.ts
│ │ └── empty.component.ts
│ ├── global-event-dispatcher.spec.ts
│ ├── global-event-dispatcher.ts
│ ├── global-planet.spec.ts
│ ├── global-planet.ts
│ ├── helpers.spec.ts
│ ├── helpers.ts
│ ├── inner-types.ts
│ ├── module.spec.ts
│ ├── module.ts
│ ├── planet.class.ts
│ ├── planet.spec.ts
│ ├── planet.ts
│ ├── public-api.ts
│ ├── router
│ │ ├── route-redirect.spec.ts
│ │ └── route-redirect.ts
│ ├── sandbox
│ │ ├── constants.ts
│ │ ├── exec.ts
│ │ ├── index.ts
│ │ ├── proxy
│ │ │ ├── patches
│ │ │ │ ├── document.ts
│ │ │ │ ├── eventListener.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── storage.ts
│ │ │ │ └── timer.ts
│ │ │ ├── proxies
│ │ │ │ ├── common.ts
│ │ │ │ ├── document.ts
│ │ │ │ └── window.ts
│ │ │ ├── proxy-sandbox.ts
│ │ │ └── types.ts
│ │ ├── sandbox.ts
│ │ ├── snapshot
│ │ │ └── snapshot-sandbox.ts
│ │ └── types.ts
│ ├── test.ts
│ └── testing
│ │ ├── app1.module.ts
│ │ ├── app2.module.ts
│ │ ├── applications.ts
│ │ ├── index.ts
│ │ └── utils.ts
│ ├── tsconfig.lib.json
│ ├── tsconfig.lib.prod.json
│ └── tsconfig.spec.json
├── postcss.config.js
├── proxy.conf.js
└── tsconfig.json
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "projectName": "ngx-planet",
3 | "projectOwner": "worktile",
4 | "repoType": "github",
5 | "repoHost": "https://github.com",
6 | "files": [
7 | "README.md"
8 | ],
9 | "imageSize": 100,
10 | "commit": true,
11 | "commitConvention": "angular",
12 | "contributors": [
13 | {
14 | "login": "why520crazy",
15 | "name": "why520crazy",
16 | "avatar_url": "https://avatars2.githubusercontent.com/u/3959960?v=4",
17 | "profile": "https://www.zhihu.com/people/why520crazy/activities",
18 | "contributions": [
19 | "question",
20 | "business",
21 | "code",
22 | "design",
23 | "doc",
24 | "eventOrganizing",
25 | "infra",
26 | "maintenance",
27 | "projectManagement",
28 | "review"
29 | ]
30 | },
31 | {
32 | "login": "walkerkay",
33 | "name": "Walker",
34 | "avatar_url": "https://avatars1.githubusercontent.com/u/15701592?v=4",
35 | "profile": "https://github.com/walkerkay",
36 | "contributions": [
37 | "code",
38 | "example",
39 | "maintenance",
40 | "review"
41 | ]
42 | },
43 | {
44 | "login": "whyour",
45 | "name": "whyour",
46 | "avatar_url": "https://avatars3.githubusercontent.com/u/22700758?v=4",
47 | "profile": "https://whyour.cn",
48 | "contributions": [
49 | "code"
50 | ]
51 | },
52 | {
53 | "login": "aoilti",
54 | "name": "张威",
55 | "avatar_url": "https://avatars0.githubusercontent.com/u/19969080?v=4",
56 | "profile": "http://www.231jx.cn",
57 | "contributions": [
58 | "code"
59 | ]
60 | },
61 | {
62 | "login": "luxiaobei",
63 | "name": "luxiaobei",
64 | "avatar_url": "https://avatars1.githubusercontent.com/u/13583957?v=4",
65 | "profile": "https://github.com/luxiaobei",
66 | "contributions": [
67 | "infra",
68 | "test",
69 | "code"
70 | ]
71 | },
72 | {
73 | "login": "mario56",
74 | "name": "mario_ma",
75 | "avatar_url": "https://avatars2.githubusercontent.com/u/7720722?v=4",
76 | "profile": "https://github.com/mario56",
77 | "contributions": [
78 | "code"
79 | ]
80 | }
81 | ],
82 | "contributorsPerLine": 7,
83 | "skipCi": true
84 | }
85 |
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2.1
2 | orbs:
3 | browser-tools: circleci/browser-tools@1.4.3
4 | jobs:
5 | build:
6 | working_directory: ~/ngx-planet
7 | docker:
8 | - image: cimg/node:20.16.0-browsers
9 | steps:
10 | - browser-tools/install-chrome
11 | - checkout
12 | - run: |
13 | node --version
14 | google-chrome --version
15 | which google-chrome
16 | - restore_cache:
17 | key: ngx-planet-{{ .Branch }}-{{ checksum "package-lock.json" }}
18 | - run: npm install --force
19 | - save_cache:
20 | key: ngx-planet-{{ .Branch }}-{{ checksum "package-lock.json" }}
21 | paths:
22 | - 'node_modules'
23 | - run: npm run test -- --no-watch --no-progress --browsers=ChromeHeadless
24 | - run: npm run lint
25 | - run: npm run report-coverage
26 |
--------------------------------------------------------------------------------
/.coveralls.yml:
--------------------------------------------------------------------------------
1 | repo_token: UF5coppIPMTIQIOxZTxzP1o18O9Z9QWD0
2 |
--------------------------------------------------------------------------------
/.docgeni/public/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/worktile/ngx-planet/2f297dec761e74afe93233d69f9cf6a46efac2b2/.docgeni/public/assets/favicon.ico
--------------------------------------------------------------------------------
/.docgeni/public/assets/images/banner-origin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/worktile/ngx-planet/2f297dec761e74afe93233d69f9cf6a46efac2b2/.docgeni/public/assets/images/banner-origin.png
--------------------------------------------------------------------------------
/.docgeni/public/assets/images/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/worktile/ngx-planet/2f297dec761e74afe93233d69f9cf6a46efac2b2/.docgeni/public/assets/images/banner.png
--------------------------------------------------------------------------------
/.docgeni/public/assets/images/home/feature1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/worktile/ngx-planet/2f297dec761e74afe93233d69f9cf6a46efac2b2/.docgeni/public/assets/images/home/feature1.png
--------------------------------------------------------------------------------
/.docgeni/public/assets/images/home/feature2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/worktile/ngx-planet/2f297dec761e74afe93233d69f9cf6a46efac2b2/.docgeni/public/assets/images/home/feature2.png
--------------------------------------------------------------------------------
/.docgeni/public/assets/images/home/feature3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/worktile/ngx-planet/2f297dec761e74afe93233d69f9cf6a46efac2b2/.docgeni/public/assets/images/home/feature3.png
--------------------------------------------------------------------------------
/.docgeni/public/assets/images/home/feature4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/worktile/ngx-planet/2f297dec761e74afe93233d69f9cf6a46efac2b2/.docgeni/public/assets/images/home/feature4.png
--------------------------------------------------------------------------------
/.docgeni/public/assets/images/home/feature5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/worktile/ngx-planet/2f297dec761e74afe93233d69f9cf6a46efac2b2/.docgeni/public/assets/images/home/feature5.png
--------------------------------------------------------------------------------
/.docgeni/public/assets/images/home/feature6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/worktile/ngx-planet/2f297dec761e74afe93233d69f9cf6a46efac2b2/.docgeni/public/assets/images/home/feature6.png
--------------------------------------------------------------------------------
/.docgeni/public/assets/images/planet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/worktile/ngx-planet/2f297dec761e74afe93233d69f9cf6a46efac2b2/.docgeni/public/assets/images/planet.png
--------------------------------------------------------------------------------
/.docgeni/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Planet
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.docgeni/public/styles.scss:
--------------------------------------------------------------------------------
1 | @import '@docgeni/template/styles/index.scss';
2 |
3 | .dg-home .dg-hero .dg-hero-launch {
4 | align-items: start;
5 | padding-left: 16%;
6 | text-align: unset;
7 | }
8 |
9 | .dg-doc-content {
10 | ul,
11 | ol {
12 | font-size: 1rem;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/.docgenirc.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @type {import('@docgeni/core').DocgeniConfig}
3 | */
4 | module.exports = {
5 | mode: 'full',
6 | title: 'Planet',
7 | description: '微前端解决方案',
8 | docsDir: 'docs',
9 | defaultLocale: 'zh-cn',
10 | repoUrl: 'https://github.com/worktile/ngx-planet',
11 | logoUrl: 'assets/images/planet.png',
12 | navs: [
13 | null,
14 | {
15 | title: '示例',
16 | path: 'http://planet-examples.ngnice.com',
17 | isExternal: true
18 | },
19 | {
20 | title: 'GitHub',
21 | path: 'https://github.com/worktile/ngx-planet',
22 | isExternal: true
23 | },
24 | {
25 | title: '更新日志',
26 | path: 'https://github.com/worktile/ngx-planet/blob/master/CHANGELOG.md',
27 | isExternal: true,
28 | locales: {
29 | 'en-us': {
30 | title: 'Changelog'
31 | }
32 | }
33 | }
34 | ],
35 | footer: 'Open-source MIT Licensed | Copyright © 2020-present
Powered by PingCode'
36 | };
37 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://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 | [*.js]
12 | indent_size = 4
13 |
14 | [*.ts]
15 | indent_size = 4
16 |
17 | [*.scss]
18 | indent_size = 4
19 |
20 | [*.md]
21 | max_line_length = off
22 | trim_trailing_whitespace = false
23 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "ignorePatterns": ["projects/**/*"],
4 | "overrides": [
5 | {
6 | "files": ["*.ts"],
7 | "parserOptions": {
8 | "project": ["tsconfig.json", "e2e/tsconfig.json"],
9 | "createDefaultProgram": true
10 | },
11 | "extends": ["plugin:@angular-eslint/recommended", "plugin:@angular-eslint/template/process-inline-templates"],
12 | "rules": {
13 | "@angular-eslint/component-selector": [
14 | "error",
15 | {
16 | "prefix": "planet",
17 | "style": "kebab-case",
18 | "type": "element"
19 | }
20 | ],
21 | "@angular-eslint/directive-selector": [
22 | "error",
23 | {
24 | "prefix": "planet",
25 | "style": "camelCase",
26 | "type": "attribute"
27 | }
28 | ],
29 | "@angular-eslint/prefer-standalone": "off"
30 | }
31 | },
32 | {
33 | "files": ["*.html"],
34 | "extends": ["plugin:@angular-eslint/template/recommended"],
35 | "rules": {}
36 | }
37 | ]
38 | }
39 |
--------------------------------------------------------------------------------
/.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: # Replace with a single Patreon username
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 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 |
8 | # dependencies
9 | node_modules
10 |
11 | # profiling files
12 | chrome-profiler-events.json
13 | speed-measure-plugin.json
14 |
15 | # IDEs and editors
16 | /.idea
17 | .project
18 | .classpath
19 | .c9/
20 | *.launch
21 | .settings/
22 | *.sublime-workspace
23 |
24 | # IDE - VSCode
25 | .vscode/*
26 | !.vscode/settings.json
27 | !.vscode/tasks.json
28 | !.vscode/launch.json
29 | !.vscode/extensions.json
30 |
31 | # misc
32 | /.angular/cache
33 | /.sass-cache
34 | /connect.lock
35 | /coverage
36 | /libpeerconnection.log
37 | npm-debug.log
38 | yarn-error.log
39 | testem.log
40 | /typings
41 |
42 | # System Files
43 | .DS_Store
44 | Thumbs.db
45 | dist
46 |
47 | .history
48 | .docgeni/site
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | package.json
2 | package-lock.json
3 | coverage/
4 | **/*.md
5 | **/*.svg
6 | dist/
7 | docs/
8 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | eslintIntegration: true,
3 | stylelintIntegration: true,
4 | tabWidth: 2,
5 | semi: true,
6 | printWidth: 140,
7 | proseWrap: 'preserve',
8 | trailingComma: 'none',
9 | singleQuote: true,
10 | arrowParens: 'avoid',
11 | bracketSameLine: true,
12 | overrides: [
13 | {
14 | files: '*.js',
15 | options: {
16 | tabWidth: 4
17 | }
18 | },
19 | {
20 | files: '*.ts',
21 | options: {
22 | tabWidth: 4
23 | }
24 | },
25 | {
26 | files: '*.scss',
27 | options: {
28 | tabWidth: 4
29 | }
30 | },
31 | {
32 | files: '*.css',
33 | options: {
34 | tabWidth: 4
35 | }
36 | }
37 | ]
38 | };
39 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | dist: trusty
2 | sudo: false
3 |
4 | language: node_js
5 | node_js:
6 | - 10.16.0
7 |
8 | addons:
9 | apt:
10 | sources:
11 | - google-chrome
12 | packages:
13 | - google-chrome-stable
14 |
15 | cache:
16 | directories:
17 | - ./node_modules
18 |
19 | install:
20 | - npm install
21 |
22 | script:
23 | - npm run test -- --no-watch --no-progress --browsers=ChromeHeadlessCI
24 |
25 | after_success:
26 | - npm run report-coverage
27 |
--------------------------------------------------------------------------------
/.wpmrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | allowBranch: ['master', 'v*'],
3 | bumpFiles: ['package.json', 'package-lock.json', 'packages/planet/package.json'],
4 | tagPrefix: '',
5 | commitAll: true,
6 | header: 'Changelog\nAll notable changes to ngx-planet will be documented in this file.\n',
7 | hooks: {
8 | prepublish: 'npm run build',
9 | postreleaseBranch: 'git add .'
10 | }
11 | };
12 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM nginx:mainline-alpine
2 | RUN rm /etc/nginx/conf.d/*
3 |
4 | ADD nginx.conf /etc/nginx/conf.d/
5 | RUN mkdir -p /etc/nginx/html/static/app1
6 | RUN mkdir -p /etc/nginx/html/static/app2
7 | RUN mkdir -p /etc/nginx/html/static/portal
8 |
9 | COPY dist/app1 /etc/nginx/html/static/app1/
10 | COPY dist/app2 /etc/nginx/html/static/app2/
11 | COPY dist/portal /etc/nginx/html/static/portal/
12 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright (c) 2018 - 2019 Worktile Inc. https://worktile.com
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Versions
4 |
5 | Use this section to tell people about which versions of your project are
6 | currently being supported with security updates.
7 |
8 | | Version | Supported |
9 | | ------- | ------------------ |
10 | | 5.1.x | :white_check_mark: |
11 | | 5.0.x | :x: |
12 | | 4.0.x | :white_check_mark: |
13 | | < 4.0 | :x: |
14 |
15 | ## Reporting a Vulnerability
16 |
17 | Use this section to tell people how to report a vulnerability.
18 |
19 | Tell them where to go, how often they can expect to get an update on a
20 | reported vulnerability, what to expect if the vulnerability is accepted or
21 | declined, etc.
22 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['@commitlint/config-conventional'],
3 | rules: {
4 | 'header-max-length': [0, 'always', 120]
5 | }
6 | };
7 |
--------------------------------------------------------------------------------
/docs/api/define-application.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: defineApplication
3 | order: 20
4 | toc: false
5 | ---
6 |
7 | ## defineApplication(name, options)
8 | - 参数
9 | - `name:string` - 必填,子应用名称
10 | - `options: BootstrapOptions` - 必填,子应用启动函数以及配置信息
11 | - 类型 BootstrapOptions
12 | - `template: string` - 必填,子应用的根组件模板,例如: ``
13 | - `bootstrap: (portalApp?: PlanetPortalApplication) => Promise` - 必填,子应用的启动函数,支持模块应用启动和独立应用,启动函数会把主应用通过参数传递给启动函数,可以在子应用中设置 Provider 方便通过依赖注入获取
14 | - 示例
15 | - 模块应用
16 | ```ts
17 | defineApplication('app1', {
18 | template: ``,
19 | bootstrap: (portalApp: PlanetPortalApplication) => {
20 | return platformBrowserDynamic([
21 | {
22 | provide: PlanetPortalApplication,
23 | useValue: portalApp
24 | }
25 | ])
26 | .bootstrapModule(AppModule)
27 | .then(appModule => {
28 | return appModule;
29 | })
30 | .catch(error => {
31 | console.error(error);
32 | return null;
33 | });
34 | }
35 | });
36 | ```
37 | - 独立应用
38 | ```ts
39 | defineApplication('standalone-app', {
40 | template: ``,
41 | bootstrap: (portalApp: PlanetPortalApplication) => {
42 | return bootstrapApplication(AppRootComponent, {
43 | providers: [
44 | {
45 | provide: PlanetPortalApplication,
46 | useValue: portalApp
47 | }
48 | ]
49 | }).catch(error => {
50 | console.error(error);
51 | return null;
52 | });
53 | }
54 | });
55 | ```
56 |
--------------------------------------------------------------------------------
/docs/api/global-event-dispatcher.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: GlobalEventDispatcher
3 | order: 30
4 | toc: menu
5 | ---
6 |
7 | ```ts
8 | import { GlobalEventDispatcher } from "@worktile/planet";
9 |
10 | class SomeClass {
11 | globalEventDispatcher = inject(GlobalEventDispatcher);
12 | }
13 | ```
14 |
15 | ## register(eventName)
16 | 注册事件并订阅,当其他应用派发事件时会执行回调函数。
17 | - 参数 `eventName: string` - 必填,注册的事件名称
18 | - 返回参数 `Observable`
19 | - 示例
20 |
21 | ```ts
22 | this.globalEventDispatcher.register('open-a-detail').subscribe(event => {
23 | // do somethings
24 | });
25 | ```
26 |
27 | ## dispatch(name, payload)
28 | 派发事件给所有注册且订阅相同事件名的应用。
29 | - 参数
30 | - `name: string` - 必填,唯一的事件名称
31 | - `payload: T` - 可选,事件附带数据
32 | - 示例
33 |
34 | ```ts
35 | this.globalEventDispatcher.dispatch('open-a-detail', {
36 | from: "app2"
37 | });
38 | ```
39 |
--------------------------------------------------------------------------------
/docs/api/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: API
3 | path: api
4 | order: 20
5 | ---
6 |
--------------------------------------------------------------------------------
/docs/api/planet-component-loader.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: PlanetComponentLoader
3 | order: 30
4 | toc: menu
5 | ---
6 |
7 | ```ts
8 | import { PlanetComponentLoader } from "@worktile/planet";
9 |
10 | class SomeClass {
11 | planetComponentLoader = inject(PlanetComponentLoader);
12 | }
13 | ```
14 |
15 | ## register(components)
16 | 注册组件,注册后的组件可在其他子应用中渲染
17 | - 参数
18 | - components - PlanetComponent | PlanetComponent[] | Type | Type[] - 必填,注册的组件
19 | - 类型 `PlanetComponent`
20 | - `name: string` - 必填,组件名称
21 | - `component: Type` - 必填,组件类
22 | - 示例
23 |
24 | ```ts
25 | this.planetComponentLoader.register(ProjectsComponent);
26 | // =
27 | this.planetComponentLoader.register({
28 | name: "project-list",
29 | component: ProjectsComponent
30 | });
31 |
32 | this.planetComponentLoader.register([ProjectsComponent, ProjectDetailComponent]);
33 | ```
34 |
35 | ## load(name, componentName, config)
36 | 渲染其他应用中注册过的组件。
37 | - 参数
38 | - `name: string` - 必填,加载组件所在的应用名称,例如:"app1"
39 | - `componentName: string` - 必填,加载组件的名称
40 | - `config: PlantComponentConfig` - 必填,组件参数
41 | - 类型 `PlantComponentConfig`
42 | - `container: HTMLElement | ElementRef | Comment` - 必填,渲染组件的容器
43 | - `hostClass?: string` - 可选,渲染组件的宿主样式类
44 | - `initialState: TData` - 可选,渲染组件的初始化状态
45 | - `projectableNodes: (Node[] | TemplateRef)[]` - 可选,渲染组件的投影元素,支持 Dom 节点二维数组和 TemplateRef 数组,数组的第一个元素为渲染组件中的第一个 ng-content
46 | - 示例
47 |
48 | ```ts
49 | this.planetComponentLoader.load("app1", "project-list", {
50 | container: this.elementRef,
51 | initialState: {
52 | uid: "xxx"
53 | }
54 | });
55 | ```
56 |
--------------------------------------------------------------------------------
/docs/guides/advanced/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 高级
3 | order: 70
4 | ---
5 |
--------------------------------------------------------------------------------
/docs/guides/advanced/sandbox-isolation.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 沙箱隔离
3 | order: 20
4 | ---
5 |
6 | WIP.
7 |
--------------------------------------------------------------------------------
/docs/guides/advanced/style-isolation.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 样式隔离
3 | order: 10
4 | ---
5 |
6 | WIP.
7 |
--------------------------------------------------------------------------------
/docs/guides/cross-app-comp-rendering.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 跨应用渲染
3 | order: 60
4 | ---
5 |
6 | 跨应用渲染主要是子应用 A 的某个组件可以在其他应用的某个元素中渲染并展示,Planet 提供了`PlanetComponentLoader`服务和`planetComponentOutlet`指令实现跨应用的组件渲染。
7 |
8 | ## 注册组件
9 |
10 | 在 App1 子应用的根组件或者根模块注册可以被其他应用渲染的组件:
11 | ```ts
12 | import { PlanetComponentLoader } from "@worktile/planet";
13 |
14 | class AppComponent {
15 | planetComponentLoader = inject(PlanetComponentLoader);
16 |
17 | constructor() {
18 | this.planetComponentLoader.register(ProjectsComponent);
19 | }
20 | }
21 | ```
22 |
23 | ## 渲染组件
24 |
25 | 在其他应用,比如 App2 中通过`planetComponentLoader.load`渲染 App1 的`ProjectsComponent`:
26 |
27 | ```ts
28 | const this.planetComponentLoader.load("app1", "app1-project-list", {
29 | container: this.elementRef,
30 | initialState: {
31 | term: "xxx"
32 | }
33 | });
34 | @Component({
35 | ...
36 | })
37 | export class OneComponent {
38 | private componentRef: PlanetComponentRef;
39 |
40 | private planetComponentLoader = inject(PlanetComponentLoader);
41 |
42 | loadProjects() {
43 | this.planetComponentLoader.load('app1', 'app1-project-list', {
44 | container: this.elementRef,
45 | initialState: {
46 | term: 'x'
47 | }
48 | }).subscribe((componentRef) => {
49 | this.componentRef = componentRef;
50 | });
51 | }
52 |
53 | ngOnDestroy() {
54 | this.componentRef?.dispose();
55 | }
56 | }
57 | ```
58 | 这里的第二个参数为组件的选择器,container 为组件渲染的容器元素,API 更多详情参考 [PlanetComponentLoader](api/planet-component-loader)。
59 |
60 | 注意:跨应用渲染组件后在当前组件销毁时一定要记得调用 dispose 函数销毁已经渲染的组件,否则会有性能泄露。
61 |
62 | ## 通过指令渲染组件
63 |
64 | `planetComponentOutlet`是 Planet 提供跨应用渲染的结构性指令,无需通过`PlanetComponentLoader`手动渲染和销毁。
65 |
66 | ```html
67 |
68 |
69 |
70 | // or
71 |
75 |
76 | ```
77 |
78 | 不管是通过 PlanetComponentLoader 服务还是 planetComponentOutlet 指令,子应用渲染主应用的组件 app 传递固定字符串 portal 即可。
79 |
--------------------------------------------------------------------------------
/docs/guides/faq.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 常见问题
3 | order: 100
4 | ---
5 |
6 | ## 支持其他框架吗?
7 |
8 | 暂时不支持。
9 |
10 | ## error TS6053 '...polyfills.ngtypecheck.ts' not found.
11 |
12 | ```
13 | Error: error TS6053: File '/Users/haifeng/IT/01_Study/ngnice/ngnice.com/src/polyfills.ngtypecheck.ts' not found.
14 | The file is in the program because:
15 | Root file specified for compilation
16 | ```
17 |
18 | 这是`tsconfig.json`中的`files`设置了`"src/polyfills.ts"`导致的,在 docgeni 新版本中 site 已经不会生成 polyfills.ts 文件了,如果过去自定义了`tsconfig.json`可能会包含,所以需要去掉:
19 |
20 | ```json
21 | {
22 | "files": [
23 | "src/main.ts",
24 | - "src/polyfills.ts"
25 | ],
26 | }
27 | ```
--------------------------------------------------------------------------------
/docs/guides/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 指南
3 | path: guides
4 | order: 10
5 | ---
6 |
--------------------------------------------------------------------------------
/docs/guides/intro.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 介绍
3 | order: 20
4 | ---
5 |
6 | Planet 是一个基于 Angular 的[微前端](https://micro-frontends.org/)框架,旨在帮助开发者简单、快速的构建一个生产可用且稳定可靠的微前端架构应用。Planet 是 [PingCode](https://pingcode.com/) 团队于 2019 年自研的 Angular 微前端框架,主要用于构建稳定多产品的企业级 SaaS 应用。
7 |
8 | ## 特性
9 | - 同时渲染多个应用
10 | - 支持两种模式,并存和默认模式,默认模式下,应用切换会自动销毁,并存模式下不会销毁会隐藏
11 | - 支持应用预加载
12 | - 支持样式和沙箱隔离
13 | - 内置应用间通信功能
14 | - 支持跨应用的组件渲染
15 | - 详细以及完备的示例,包含路由配置、延迟加载等等特性使用
16 |
17 | ## 什么是微前端
18 |
19 | 微前端最早提出这个概念的是 ThoughtWork 的技术雷达,主要是把微服务的概念引入到了前端,让前端的多个模块或者应用解耦,做到让前端的子应用独立仓储,独立运行,独立部署。
20 | 以下是微前端相关文章:
21 | - [Micro Frontends](https://micro-frontends.org/)
22 | - [Micro Frontends from martinfowler.com](https://martinfowler.com/articles/micro-frontends.html)
23 | - [使用 Angular 打造微前端架构的 ToB 企业级应用](https://zhuanlan.zhihu.com/p/93813936)
24 |
25 | ## 为什么要微前端
26 |
27 | 1. 系统模块增多,单体应用变得臃肿,开发效率低下,构建速度变慢;
28 | 1. 人员扩大,需要多个前端团队独立开发,独立部署,如果都在一个仓储中开发会带来一些列问题;
29 | 1. 解决遗留系统,新模块需要使用最新的框架和技术,旧系统还继续使用。
30 |
31 | ## 微前端的几种方案
32 |
33 | | 方式 | 描述 | 优点 | 缺点 | 难度系数 |
34 | | -------------- | -------------------------------------------------------------------------- | ------------------------------------ | ------------------------------------------------------------------------------ | -------- |
35 | | 路由转发 | 路由转发严格意义上不属于微前端,多个子模块之间共享一个导航即可 | 简单,易实现 | 体验不好,切换应用整个页面刷新 | 🌟 |
36 | | 嵌套 iframe | 每个子应用一个 iframe 嵌套 | 应用之间自带沙箱隔离 | 重复加载脚本和样式 | 🌟🌟 |
37 | | 构建时组合 | 独立仓储,独立开发,构建时整体打包,合并应用 | 方便依赖管理,抽取公共模块 | 无法独立部署,技术栈,依赖版本必须统一 | 🌟🌟 |
38 | | 运行时组合 | 每个子应用独立构建,运行时由主应用负责应用管理,加载,启动,卸载,通信机制 | 良好的体验,真正的独立开发,独立部署 | 复杂,需要设计加载,通信机制,无法做到彻底隔离,需要解决依赖冲突,样式冲突问题 | 🌟🌟🌟 |
39 | | Web Components | 每个子应用需要使用 Web Components 技术编写组件或者使用框架生成 | 面向未来 | 不成熟,需要踩坑 | 🌟🌟🌟 |
40 |
41 |
42 |
43 | Planet 采用的是 `运行时组合` 方案。
44 |
45 | ## 为什么要打造 Planet
46 |
47 | 2018 年市面上的微前端解决方案并不多,关注度和成熟度最高的应该就是 [single-spa](https://single-spa.js.org/),我们在做技术选型的时候首要考虑的就是 `single-spa` 和 `mooa`, `single-spa` 成熟度应该最高,示例文档很完善,`mooa` 为 Angular 打造的主从结构的微前端框架,和我们的业务和技术符合度最高,研究一段时间后最终我们还是选择了自研一套符合自己的微前端框架,主要是因为我们的业务有以下几个需求在以上的框架中不满足或者说很难满足, 甚至需要高度定制。
48 |
49 | - 产品是主从结构的,Portal 包含左侧导航,消息通知以及子应用管理
50 | - 需要在多个子应用之间通信,主应用或者某个子应用需要打开其他子应用的详情页或者路由跳转
51 | - 子应用 A 的某个页面中需要加载子应用 B 的某个组件
52 | - 支持并存模式,即当前显示的虽然是 B 应用,但是要保证可以被 A 应用正常调用,如果销毁了就无法被其他应用调用
53 | - 需要提供预加载功能
54 | - 子应用的样式需要独立加载
55 | - 不管是在主应用还是子应用,路由体验要和单体应用一致
56 |
57 | 尝试了 `single-spa` 和 `mooa` 的示例,主要是一些简单的渲染展示,一旦需要满足以上一些特性还是需要修改很多东西,`mooa` 实现应该还是比较全面也比较适合我们的,但是它的示例中路由有一些问题,且并未在生产环境使用过,最终决定自研一套符合 Angular 框架的微前端框架。
58 |
59 |
--------------------------------------------------------------------------------
/docs/guides/migration-v17.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: v17 升级指南
3 | order: 1000
4 | ---
5 |
6 | ## 开始之前
7 | - 首先确保你 `Node.js >= 18.10.0`
8 | - 创建新的分支进行升级,或者把当前分支备份
9 |
10 | ## 开始升级
11 | - 执行`ng update @worktile/planet`命令自动升级
12 | - 此版本新增了`entry`参数代替之前的`manifest`, `scripts`, `styles`和`resourcePathPrefix`
13 |
14 | ```
15 | {
16 | - manifest: "static/app1/assets-manifest.json",
17 | - scripts: ["main.js"],
18 | - styles: ["styles.css"],
19 | - resourcePathPrefix: "static/app1"
20 | + entry: {
21 | + manifest: "static/app1/assets-manifest.json",
22 | + scripts: ["main.js"],
23 | + styles: ["styles.css"],
24 | + bashPath: "static/app1"
25 | + }
26 | }
27 | ```
28 | - 在 17 版本中,manifest 除了可以配置`json`文件外还可以配置`index.html`,这样就不用单独使用`webpack-assets-manifest`插件生成 manifest JSON 文件,同时支持字符串简化配置
29 | ```
30 | {
31 | entry: "static/app1/index.html"
32 | }
33 | ```
34 | 这样会加载`index.html`解析所有的 scripts 和 styles 加载。
35 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | hero:
3 | title: Planet
4 | description: Angular 框架下无懈可击的微前端框架和一体化解决方案
5 | banner: https://cdn.pingcode.com/open-sources/ngx-planet/banner.png
6 | actions:
7 | - text: 快速开始 →
8 | link: guides/getting-started
9 | btnShape: round
10 | features:
11 | - icon: ./assets/images/home/feature1.png
12 | title: 开箱即用
13 | description: 内置 Angular 应用路由同步、应用生命周期管理等微前端架构下的难点解决方案
14 | - icon: ./assets/images/home/feature2.png
15 | title: Angular Style
16 | description: 符合 Angular 哲学的 APIs,Angular 用户友好且亲切
17 | - icon: ./assets/images/home/feature3.png
18 | title: 配置简单
19 | description: 简单的配置,快速搭建微前端架构的前端应用,让开发者聚焦于业务本身
20 | - icon: ./assets/images/home/feature4.png
21 | title: 稳定可用
22 | description: 采用 Planet 微前端架构搭建的企业级应用的 PingCode 产品稳定上线多年,满足复杂交互的业务系统
23 | - icon: ./assets/images/home/feature5.png
24 | title: 功能全面
25 | description: 支持同时渲染多个应用、并存模式、预加载、样式/沙箱隔离、跨应用通信和组件渲染等高级功能
26 | - icon: ./assets/images/home/feature6.png
27 | title: 示例丰富
28 | description: 提供包含几乎所有功能的完整示例,项目配置以及功能实现,同时提供详细的文档介绍所有特性
29 | footer: Open-source MIT Licensed | Copyright © 2020-present
Powered by PingCode
30 | ---
31 |
32 | ## 安装
33 |
34 | ```
35 | $ npm i @worktile/planet --save
36 | ```
37 |
38 |
--------------------------------------------------------------------------------
/examples/app1/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../.eslintrc.json",
3 | "ignorePatterns": ["!**/*"],
4 | "overrides": [
5 | {
6 | "files": ["*.ts"],
7 | "parserOptions": {
8 | "project": ["examples/app1//tsconfig.app.json", "examples/app1//tsconfig.spec.json"],
9 | "createDefaultProgram": true
10 | },
11 | "rules": {
12 | "@angular-eslint/directive-selector": [
13 | "error",
14 | {
15 | "type": "attribute",
16 | "prefix": "app1",
17 | "style": "camelCase"
18 | }
19 | ],
20 | "@angular-eslint/component-selector": [
21 | "error",
22 | {
23 | "type": "element",
24 | "prefix": "app1",
25 | "style": "kebab-case"
26 | }
27 | ]
28 | }
29 | },
30 | {
31 | "files": ["*.html"],
32 | "rules": {}
33 | }
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/examples/app1/browserslist:
--------------------------------------------------------------------------------
1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers
2 | # For additional information regarding the format and rule options, please see:
3 | # https://github.com/browserslist/browserslist#queries
4 | #
5 | # For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed
6 |
7 | > 0.5%
8 | last 2 versions
9 | Firefox ESR
10 | not dead
11 | not IE 9-11
--------------------------------------------------------------------------------
/examples/app1/extra-webpack.config.js:
--------------------------------------------------------------------------------
1 | const WebpackAssetsManifest = require('webpack-assets-manifest');
2 | const PrefixWrap = require('@worktile/planet-postcss-prefixwrap');
3 | // const PrefixWrap = require('postcss-prefixwrap');
4 |
5 | module.exports = {
6 | // optimization: {
7 | // runtimeChunk: false
8 | // },
9 | plugins: [new WebpackAssetsManifest()],
10 | output: {
11 | publicPath: '/static/app1/'
12 | },
13 | module: {
14 | rules: [
15 | {
16 | test: /\.scss$/,
17 | use: [
18 | {
19 | loader: 'postcss-loader',
20 | options: {
21 | postcssOptions: {
22 | plugins: [
23 | PrefixWrap('.app1', {
24 | hasAttribute: 'planet-inline',
25 | prefixRootTags: true
26 | })
27 | ]
28 | }
29 | }
30 | },
31 | 'sass-loader'
32 | ]
33 | }
34 | ]
35 | }
36 | };
37 |
--------------------------------------------------------------------------------
/examples/app1/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-devkit/build-angular'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage'),
13 | require('@angular-devkit/build-angular/plugins/karma')
14 | ],
15 | client: {
16 | clearContext: false // leave Jasmine Spec Runner output visible in browser
17 | },
18 | coverageIstanbulReporter: {
19 | dir: require('path').join(__dirname, '../../coverage'),
20 | reports: ['html', 'lcovonly', 'text-summary'],
21 | fixWebpackSourcePaths: true
22 | },
23 | reporters: ['progress', 'kjhtml'],
24 | port: 9876,
25 | colors: true,
26 | logLevel: config.LOG_INFO,
27 | autoWatch: true,
28 | browsers: ['Chrome'],
29 | singleRun: false
30 | });
31 | };
32 |
--------------------------------------------------------------------------------
/examples/app1/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {};
2 |
--------------------------------------------------------------------------------
/examples/app1/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { BrowserModule } from '@angular/platform-browser';
2 | import { NgModule } from '@angular/core';
3 | import { NavigationStart, Router, RouterModule } from '@angular/router';
4 | import { DashboardComponent } from './dashboard/dashboard.component';
5 | import { routers } from './app.routing';
6 | import { AppRootComponent, AppActualRootComponent } from './root/root.component';
7 | import { DemoCommonModule, OVERLAY_PROVIDER } from '@demo/common';
8 | import { NgxPlanetModule } from '@worktile/planet';
9 | import { ProjectsComponent } from './projects/projects.component';
10 | import { App1DetailComponent } from './detail/detail.component';
11 | import { Overlay, OverlayModule } from '@angular/cdk/overlay';
12 | import { FormsModule } from '@angular/forms';
13 | import { AppOverlay } from './overlay';
14 | import { ProjectsDialogComponent } from './projects/dialog/projects-dialog.component';
15 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
16 | import { SharedModule } from './shared.module';
17 |
18 | @NgModule({
19 | declarations: [
20 | AppRootComponent,
21 | AppActualRootComponent,
22 | DashboardComponent,
23 | ProjectsComponent,
24 | App1DetailComponent,
25 | ProjectsDialogComponent
26 | ],
27 | imports: [
28 | BrowserModule,
29 | BrowserAnimationsModule,
30 | RouterModule.forRoot(routers),
31 | // UserModule,
32 | FormsModule,
33 | SharedModule,
34 | DemoCommonModule,
35 | NgxPlanetModule,
36 | OverlayModule
37 | ],
38 | providers: [OVERLAY_PROVIDER, { provide: Overlay, useClass: AppOverlay }],
39 | bootstrap: [AppRootComponent]
40 | })
41 | export class AppModule {
42 | constructor(private router: Router) {
43 | this.router.events.subscribe(event => {
44 | if (event instanceof NavigationStart) {
45 | console.log(`[App1] url: ${event.url}, id: ${event.id}, navigationTrigger: ${event.navigationTrigger}`);
46 | }
47 | });
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/examples/app1/src/app/app.routing.ts:
--------------------------------------------------------------------------------
1 | import { InitializedDataResolver } from './services/initialized-data.resolver';
2 | import { DashboardComponent } from './dashboard/dashboard.component';
3 | import { Route } from '@angular/router';
4 | import { AppActualRootComponent } from './root/root.component';
5 | import { EmptyComponent, redirectToRoute } from '@worktile/planet';
6 | import { ProjectsComponent } from './projects/projects.component';
7 |
8 | export const routers: Route[] = [
9 | {
10 | path: 'app1',
11 | component: AppActualRootComponent,
12 | children: [
13 | redirectToRoute('dashboard'),
14 | {
15 | path: 'dashboard',
16 | component: DashboardComponent
17 | },
18 | {
19 | path: 'users',
20 | loadChildren: () => import('./user/user.module').then(mod => mod.UserModule)
21 | },
22 | {
23 | path: 'projects',
24 | resolve: {
25 | data: InitializedDataResolver
26 | },
27 | component: ProjectsComponent
28 | }
29 | // {
30 | // path: 'users',
31 | // component: UserListComponent,
32 | // children: [
33 | // {
34 | // path: ':id',
35 | // component: UserDetailComponent
36 | // }
37 | // ]
38 | // }
39 | ]
40 | },
41 | {
42 | path: '**',
43 | component: EmptyComponent
44 | }
45 | ];
46 |
--------------------------------------------------------------------------------
/examples/app1/src/app/counter.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 |
3 | @Injectable({
4 | providedIn: 'root'
5 | })
6 | export class CounterService {
7 | count = 0;
8 |
9 | increase() {
10 | this.count++;
11 | }
12 |
13 | decrease() {
14 | this.count--;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/examples/app1/src/app/dashboard/dashboard.component.html:
--------------------------------------------------------------------------------
1 |
2 | appRootContext.name: {{ appRootContext.name }}
3 | set new name
4 |
5 |
6 |
7 | count: {{ counter.count }}
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/examples/app1/src/app/dashboard/dashboard.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Inject, ViewChild, ElementRef, OnInit } from '@angular/core';
2 | import { Router } from '@angular/router';
3 | import { CounterService } from '../counter.service';
4 | import { AppRootContext } from '@demo/common';
5 | import { PlanetComponentRef, PlanetComponentLoader, PlanetPortalApplication, GlobalEventDispatcher } from '@worktile/planet';
6 | import { ThyDialog, ThyDialogSizes } from 'ngx-tethys/dialog';
7 | import { App1DetailComponent } from '../detail/detail.component';
8 | import { ProjectsDialogComponent } from '../projects/dialog/projects-dialog.component';
9 |
10 | @Component({
11 | selector: 'app-dashboard',
12 | templateUrl: './dashboard.component.html',
13 | standalone: false
14 | })
15 | export class DashboardComponent {
16 | @ViewChild('container', { static: true }) containerElementRef: ElementRef;
17 |
18 | public size: ThyDialogSizes = ThyDialogSizes.md;
19 |
20 | private componentRef: PlanetComponentRef;
21 |
22 | constructor(
23 | private planetPortal: PlanetPortalApplication,
24 | private router: Router,
25 | public counter: CounterService,
26 | private globalEventDispatcher: GlobalEventDispatcher,
27 | public appRootContext: AppRootContext,
28 | private dialog: ThyDialog,
29 | private planetComponentLoader: PlanetComponentLoader
30 | ) {}
31 |
32 | openADetail() {
33 | this.globalEventDispatcher.dispatch('openADetail');
34 | }
35 |
36 | openApp1Dialog() {
37 | this.dialog.open(App1DetailComponent, {
38 | size: this.size
39 | });
40 | }
41 |
42 | openApp2Dialog() {
43 | this.dialog.open(ProjectsDialogComponent, {
44 | size: ThyDialogSizes.superLg
45 | });
46 | }
47 |
48 | openApp2Component() {
49 | this.planetComponentLoader
50 | .load('app2', 'app-project-list', {
51 | container: this.containerElementRef,
52 | initialState: {}
53 | })
54 | .subscribe(componentRef => {
55 | this.componentRef = componentRef;
56 | });
57 | }
58 |
59 | disposeApp2Component() {
60 | if (this.componentRef) {
61 | this.componentRef.dispose();
62 | }
63 | }
64 |
65 | changeName(newName: string) {
66 | this.appRootContext.setName(newName);
67 | this.planetPortal.tick();
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/examples/app1/src/app/detail/detail.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | This is Micro Front-end application example.
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/examples/app1/src/app/detail/detail.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { ThyDialogRef } from 'ngx-tethys/dialog';
3 |
4 | @Component({
5 | selector: 'app-detail',
6 | templateUrl: './detail.component.html',
7 | standalone: false
8 | })
9 | export class App1DetailComponent {
10 | constructor(private dialogRef: ThyDialogRef) {}
11 |
12 | ok() {
13 | this.dialogRef.close();
14 | }
15 |
16 | close() {
17 | this.dialogRef.close();
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/examples/app1/src/app/overlay.ts:
--------------------------------------------------------------------------------
1 | import { Directionality } from '@angular/cdk/bidi';
2 | import {
3 | Overlay,
4 | OverlayConfig,
5 | OverlayContainer,
6 | OverlayKeyboardDispatcher,
7 | OverlayPositionBuilder,
8 | OverlayRef,
9 | ScrollStrategyOptions
10 | } from '@angular/cdk/overlay';
11 | import { DOCUMENT, Location } from '@angular/common';
12 | import { ComponentFactoryResolver, Inject, Injectable, Injector, NgZone } from '@angular/core';
13 | import { isArray, concatArray } from 'ngx-tethys/util';
14 |
15 | @Injectable({ providedIn: 'root' })
16 | export class AppOverlay extends Overlay {
17 | create(config?: OverlayConfig): OverlayRef {
18 | const overlayConfig: OverlayConfig = {
19 | ...config,
20 | panelClass: concatArray(config.panelClass, 'app1')
21 | };
22 | const overlayRef = super.create(overlayConfig);
23 | overlayRef.addPanelClass(overlayConfig.panelClass);
24 | return overlayRef;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/examples/app1/src/app/projects/dialog/projects-dialog.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | this is a app2's component
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/examples/app1/src/app/projects/dialog/projects-dialog.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, ElementRef, EventEmitter, HostBinding, OnInit, ViewChild } from '@angular/core';
2 | import { PlanetComponentLoader } from '@worktile/planet';
3 | import { ThyDialogRef } from 'ngx-tethys/dialog';
4 |
5 | @Component({
6 | selector: 'app-projects-dialog',
7 | templateUrl: `./projects-dialog.component.html`,
8 | standalone: false
9 | })
10 | export class ProjectsDialogComponent implements OnInit {
11 | @HostBinding('class.thy-dialog-content') container = true;
12 |
13 | @ViewChild('container', { static: true }) elementRef: ElementRef;
14 |
15 | loadingDone = false;
16 |
17 | constructor(
18 | private planetComponentLoader: PlanetComponentLoader,
19 | private dialogRef: ThyDialogRef
20 | ) {}
21 |
22 | ngOnInit() {
23 | this.planetComponentLoader
24 | .load<{ click: EventEmitter }>('app2', 'app-project-list', {
25 | container: this.elementRef
26 | })
27 | .subscribe(componentRef => {
28 | this.loadingDone = true;
29 | componentRef.componentInstance.click.subscribe(() => {
30 | console.log('project item clicked');
31 | });
32 | });
33 | }
34 |
35 | ok() {
36 | this.dialogRef.close();
37 | }
38 |
39 | close() {
40 | this.dialogRef.close();
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/examples/app1/src/app/root/root.component.css:
--------------------------------------------------------------------------------
1 | .header {
2 | margin: 10px 20px;
3 | }
4 |
--------------------------------------------------------------------------------
/examples/app1/src/app/root/root.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/examples/app1/src/app/root/root.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, HostBinding, NgZone, inject, ChangeDetectorRef, Injectable, OnInit, ViewRef } from '@angular/core';
2 | import { GlobalEventDispatcher, routeRedirect } from '@worktile/planet';
3 | import { ThyDialog } from 'ngx-tethys/dialog';
4 | import { UserDetailComponent } from '../user/detail/user-detail.component';
5 |
6 | @Component({
7 | selector: 'app1-root-actual',
8 | templateUrl: './root.component.html',
9 | styleUrls: ['./root.component.css'],
10 | standalone: false
11 | })
12 | export class AppActualRootComponent {
13 | @HostBinding(`class.thy-layout`) isLayout = true;
14 |
15 | constructor() {}
16 | }
17 |
18 | @Component({
19 | selector: 'app1-root',
20 | template: '',
21 | standalone: false
22 | })
23 | export class AppRootComponent {
24 | @HostBinding(`class.thy-layout`) isLayout = true;
25 |
26 | constructor(
27 | private globalEventDispatcher: GlobalEventDispatcher,
28 | private thyDialog: ThyDialog,
29 | private ngZone: NgZone
30 | ) {
31 | this.globalEventDispatcher.register('openUserDetail').subscribe((payload: number) => {
32 | thyDialog.open(UserDetailComponent, {
33 | initialState: {
34 | userId: payload
35 | }
36 | });
37 | });
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/examples/app1/src/app/services/initialized-data.resolver.ts:
--------------------------------------------------------------------------------
1 | import { delay } from 'rxjs/operators';
2 | import { Injectable } from '@angular/core';
3 | import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
4 | import { of } from 'rxjs';
5 |
6 | @Injectable({
7 | providedIn: 'root'
8 | })
9 | export class InitializedDataResolver {
10 | constructor() {}
11 |
12 | resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
13 | return of(null).pipe(delay(200));
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/examples/app1/src/app/shared.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { RouterModule } from '@angular/router';
4 | import { ThyLayoutModule } from 'ngx-tethys/layout';
5 | import { ThyButtonModule } from 'ngx-tethys/button';
6 | import { ThyListModule } from 'ngx-tethys/list';
7 | import { ThyDialogModule } from 'ngx-tethys/dialog';
8 | import { ThyMenuModule } from 'ngx-tethys/menu';
9 | import { ThyCardModule } from 'ngx-tethys/card';
10 | import { ThyLoadingModule } from 'ngx-tethys/loading';
11 | import { ThyIconModule } from 'ngx-tethys/icon';
12 | import { ThyInputModule } from 'ngx-tethys/input';
13 | import { ThyFormModule } from 'ngx-tethys/form';
14 | import { ThySelectModule } from 'ngx-tethys/select';
15 | import { ThyTabsModule } from 'ngx-tethys/tabs';
16 |
17 | import { DemoCommonModule } from '@demo/common';
18 | import { NgxPlanetModule } from '@worktile/planet';
19 |
20 | @NgModule({
21 | declarations: [],
22 | imports: [
23 | CommonModule,
24 | RouterModule,
25 | ThyLayoutModule,
26 | ThyButtonModule,
27 | ThyListModule,
28 | ThyDialogModule,
29 | ThyMenuModule,
30 | ThyCardModule,
31 | ThyLoadingModule,
32 | ThyIconModule,
33 | ThyInputModule,
34 | ThyFormModule,
35 | ThySelectModule,
36 | ThyTabsModule,
37 | DemoCommonModule,
38 | NgxPlanetModule
39 | ],
40 | exports: [
41 | CommonModule,
42 | RouterModule,
43 | ThyLayoutModule,
44 | ThyButtonModule,
45 | ThyListModule,
46 | ThyDialogModule,
47 | ThyMenuModule,
48 | ThyCardModule,
49 | ThyLoadingModule,
50 | ThyIconModule,
51 | ThyInputModule,
52 | ThyFormModule,
53 | ThySelectModule,
54 | ThyTabsModule,
55 | DemoCommonModule,
56 | NgxPlanetModule
57 | ],
58 | providers: []
59 | })
60 | export class SharedModule {}
61 |
--------------------------------------------------------------------------------
/examples/app1/src/app/user/detail/user-detail.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | User Detail
4 | uid: {{ userId }}
5 |
6 |
--------------------------------------------------------------------------------
/examples/app1/src/app/user/detail/user-detail.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, inject, InjectionToken, OnInit } from '@angular/core';
2 | import { GlobalEventDispatcher } from '@worktile/planet';
3 | import { ActivatedRoute } from '@angular/router';
4 |
5 | @Component({
6 | selector: 'app-user-detail',
7 | templateUrl: './user-detail.component.html',
8 | standalone: false
9 | })
10 | export class UserDetailComponent implements OnInit {
11 | userId: string;
12 |
13 | constructor(
14 | private route: ActivatedRoute,
15 | private globalEventDispatcher: GlobalEventDispatcher
16 | ) {}
17 |
18 | ngOnInit() {
19 | // this.route.paramMap.subscribe(params => {
20 | // const userId = params.get(`id`);
21 | // this.userId = userId;
22 | // });
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/examples/app1/src/app/user/user-list.component.html:
--------------------------------------------------------------------------------
1 | This is user list page, and this module is loaded lazy.
2 |
3 | Peter
4 | Lily
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/examples/app1/src/app/user/user-list.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, inject, InjectionToken } from '@angular/core';
2 | import { GlobalEventDispatcher } from '@worktile/planet';
3 | import { ThyDialog } from 'ngx-tethys/dialog';
4 | import { UserDetailComponent } from './detail/user-detail.component';
5 |
6 | @Component({
7 | selector: 'app-user-list',
8 | templateUrl: './user-list.component.html',
9 | standalone: false
10 | })
11 | export class UserListComponent {
12 | constructor(
13 | private globalEventDispatcher: GlobalEventDispatcher,
14 | private thyDialog: ThyDialog
15 | ) {}
16 |
17 | openUserDetail(id: number) {
18 | this.thyDialog.open(UserDetailComponent, {
19 | initialState: {
20 | userId: id
21 | }
22 | });
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/examples/app1/src/app/user/user.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { UserListComponent } from './user-list.component';
4 | import { UserDetailComponent } from './detail/user-detail.component';
5 | import { DemoCommonModule } from '@demo/common';
6 | import { NgxPlanetModule } from '@worktile/planet';
7 | import { RouterModule } from '@angular/router';
8 | import { SharedModule } from '../shared.module';
9 |
10 | @NgModule({
11 | declarations: [UserListComponent, UserDetailComponent],
12 | imports: [
13 | SharedModule,
14 | RouterModule.forChild([
15 | {
16 | path: '',
17 | component: UserListComponent,
18 | children: [
19 | {
20 | path: ':id',
21 | component: UserDetailComponent
22 | }
23 | ]
24 | }
25 | ])
26 | ],
27 | exports: [UserListComponent, UserDetailComponent],
28 | providers: []
29 | })
30 | export class UserModule {}
31 |
--------------------------------------------------------------------------------
/examples/app1/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/worktile/ngx-planet/2f297dec761e74afe93233d69f9cf6a46efac2b2/examples/app1/src/assets/.gitkeep
--------------------------------------------------------------------------------
/examples/app1/src/assets/github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/worktile/ngx-planet/2f297dec761e74afe93233d69f9cf6a46efac2b2/examples/app1/src/assets/github.png
--------------------------------------------------------------------------------
/examples/app1/src/assets/main.css:
--------------------------------------------------------------------------------
1 | .red {
2 | color: red;
3 | }
4 |
--------------------------------------------------------------------------------
/examples/app1/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true
3 | };
4 |
--------------------------------------------------------------------------------
/examples/app1/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // This file can be replaced during build by using the `fileReplacements` array.
2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
3 | // The list of file replacements can be found in `angular.json`.
4 |
5 | export const environment = {
6 | production: false
7 | };
8 |
9 | /*
10 | * For easier debugging in development mode, you can import the following file
11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
12 | *
13 | * This import should be commented out in production mode because it will have a negative impact
14 | * on performance if an error is thrown.
15 | */
16 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI.
17 |
--------------------------------------------------------------------------------
/examples/app1/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/worktile/ngx-planet/2f297dec761e74afe93233d69f9cf6a46efac2b2/examples/app1/src/favicon.ico
--------------------------------------------------------------------------------
/examples/app1/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | App1
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/examples/app1/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode, NgModuleRef, Type, NgZone } 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 | import { PlanetPortalApplication, defineApplication } from '@worktile/planet';
7 | import { AppRootContext } from '@demo/common';
8 |
9 | if (environment.production) {
10 | enableProdMode();
11 | }
12 |
13 | defineApplication('app1', {
14 | template: ``,
15 | bootstrap: (portalApp: PlanetPortalApplication) => {
16 | return platformBrowserDynamic([
17 | {
18 | provide: PlanetPortalApplication,
19 | useValue: portalApp
20 | },
21 | {
22 | provide: AppRootContext,
23 | useValue: portalApp.data.appRootContext
24 | }
25 | ])
26 | .bootstrapModule(AppModule)
27 | .then(appModule => {
28 | return appModule;
29 | })
30 | .catch(error => {
31 | console.error(error);
32 | return null;
33 | });
34 | }
35 | });
36 |
--------------------------------------------------------------------------------
/examples/app1/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/guide/browser-support
15 | */
16 |
17 | /***************************************************************************************************
18 | * BROWSER POLYFILLS
19 | */
20 |
21 | /** IE9, IE10, IE11, and Chrome <55 requires all of the following polyfills.
22 | * This also includes Android Emulators with older versions of Chrome and Google Search/Googlebot
23 | */
24 |
25 | // import 'core-js/es6/symbol';
26 | // import 'core-js/es6/object';
27 | // import 'core-js/es6/function';
28 | // import 'core-js/es6/parse-int';
29 | // import 'core-js/es6/parse-float';
30 | // import 'core-js/es6/number';
31 | // import 'core-js/es6/math';
32 | // import 'core-js/es6/string';
33 | // import 'core-js/es6/date';
34 | // import 'core-js/es6/array';
35 | // import 'core-js/es6/regexp';
36 | // import 'core-js/es6/map';
37 | // import 'core-js/es6/weak-map';
38 | // import 'core-js/es6/set';
39 |
40 | /** IE10 and IE11 requires the following for the Reflect API. */
41 | // import 'core-js/es6/reflect';
42 |
43 | /**
44 | * By default, zone.js will patch all possible macroTask and DomEvents
45 | * user can disable parts of macroTask/DomEvents patch by setting following flags
46 | * because those flags need to be set before `zone.js` being loaded, and webpack
47 | * will put import in the top of bundle, so user need to create a separate file
48 | * in this directory (for example: zone-flags.ts), and put the following flags
49 | * into that file, and then add the following code before importing zone.js.
50 | * import './zone-flags.ts';
51 | *
52 | * The flags allowed in zone-flags.ts are listed here.
53 | *
54 | * The following flags will work for all browsers.
55 | *
56 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
57 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
58 | * (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
59 | *
60 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
61 | * with the following flag, it will bypass `zone.js` patch for IE/Edge
62 | *
63 | * (window as any).__Zone_enable_cross_context_check = true;
64 | *
65 | */
66 |
67 | /***************************************************************************************************
68 | * Zone JS is required by default for Angular itself.
69 | */
70 | // import 'zone.js'; // Included with Angular CLI.
71 |
72 | /***************************************************************************************************
73 | * APPLICATION IMPORTS
74 | */
75 |
--------------------------------------------------------------------------------
/examples/app1/src/styles.scss:
--------------------------------------------------------------------------------
1 | // /* You can add global styles to this file, and also import other style files */
2 | @use 'ngx-tethys/styles/variables.scss' with (
3 | $layout-content-background: #fff,
4 | );
5 | @use 'ngx-tethys/styles/index.scss';
6 | .section-card {
7 | display: block;
8 | position: relative;
9 | padding: 15px;
10 | border: 2px variables.$gray-300 dashed;
11 |
12 | .section-card-header {
13 | position: absolute;
14 | padding: 0 10px;
15 | background: variables.$white;
16 | top: -10px;
17 | left: 5px;
18 | color: variables.$gray-600; // $gray-500;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/examples/app1/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/testing';
4 | import { getTestBed } from '@angular/core/testing';
5 | import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
6 |
7 | // First, initialize the Angular testing environment.
8 | getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
9 | teardown: { destroyAfterEach: false }
10 | });
11 |
--------------------------------------------------------------------------------
/examples/app1/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../out-tsc/app",
5 | "types": []
6 | },
7 | "files": ["src/main.ts"],
8 | "include": ["src/**/*.d.ts"],
9 | "exclude": ["test.ts", "**/*.spec.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/examples/app1/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../out-tsc/spec",
5 | "types": [
6 | "jasmine",
7 | "node"
8 | ]
9 | },
10 | "files": [
11 | "src/test.ts",
12 | "src/polyfills.ts"
13 | ],
14 | "include": [
15 | "**/*.spec.ts",
16 | "**/*.d.ts"
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/examples/app2/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../.eslintrc.json",
3 | "ignorePatterns": ["!**/*"],
4 | "overrides": [
5 | {
6 | "files": ["*.ts"],
7 | "parserOptions": {
8 | "project": ["examples/app2//tsconfig.app.json", "examples/app2//tsconfig.spec.json"],
9 | "createDefaultProgram": true
10 | },
11 | "rules": {
12 | "@angular-eslint/directive-selector": [
13 | "error",
14 | {
15 | "type": "attribute",
16 | "prefix": "app2",
17 | "style": "camelCase"
18 | }
19 | ],
20 | "@angular-eslint/component-selector": [
21 | "error",
22 | {
23 | "type": "element",
24 | "prefix": "app2",
25 | "style": "kebab-case"
26 | }
27 | ]
28 | }
29 | },
30 | {
31 | "files": ["*.html"],
32 | "rules": {}
33 | }
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/examples/app2/browserslist:
--------------------------------------------------------------------------------
1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers
2 | # For additional information regarding the format and rule options, please see:
3 | # https://github.com/browserslist/browserslist#queries
4 | #
5 | # For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed
6 |
7 | > 0.5%
8 | last 2 versions
9 | Firefox ESR
10 | not dead
11 | not IE 9-11
--------------------------------------------------------------------------------
/examples/app2/extra-webpack.config.js:
--------------------------------------------------------------------------------
1 | const WebpackAssetsManifest = require('webpack-assets-manifest');
2 | const PrefixWrap = require('@worktile/planet-postcss-prefixwrap');
3 |
4 | module.exports = {
5 | // optimization: {
6 | // runtimeChunk: false
7 | // },
8 | output: {
9 | publicPath: '/static/app2/'
10 | },
11 | plugins: [new WebpackAssetsManifest()],
12 | module: {
13 | rules: [
14 | {
15 | test: /\.scss$/,
16 | use: [
17 | {
18 | loader: 'postcss-loader',
19 | options: {
20 | postcssOptions: {
21 | plugins: [
22 | PrefixWrap('.app2', {
23 | hasAttribute: 'planet-inline',
24 | prefixRootTags: true
25 | })
26 | ]
27 | }
28 | }
29 | },
30 | 'sass-loader'
31 | ]
32 | }
33 | ]
34 | }
35 | };
36 |
--------------------------------------------------------------------------------
/examples/app2/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-devkit/build-angular'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage'),
13 | require('@angular-devkit/build-angular/plugins/karma')
14 | ],
15 | client: {
16 | clearContext: false // leave Jasmine Spec Runner output visible in browser
17 | },
18 | coverageIstanbulReporter: {
19 | dir: require('path').join(__dirname, '../../coverage'),
20 | reports: ['html', 'lcovonly', 'text-summary'],
21 | fixWebpackSourcePaths: true
22 | },
23 | reporters: ['progress', 'kjhtml'],
24 | port: 9876,
25 | colors: true,
26 | logLevel: config.LOG_INFO,
27 | autoWatch: true,
28 | browsers: ['Chrome'],
29 | singleRun: false
30 | });
31 | };
32 |
--------------------------------------------------------------------------------
/examples/app2/src/app/dashboard/dashboard.component.html:
--------------------------------------------------------------------------------
1 |
2 | This is app2's dashboard
3 |
4 |
5 |
6 | About use routerLink
7 |
8 |
9 |
10 |
11 | 主应用 Lodash 版本: {{ portalLodashVersion }}
12 |
13 |
14 | 当前应用 Lodash 版本: {{ app2LodashVersion }}
15 |
16 |
17 |
18 |
21 |
22 |
--------------------------------------------------------------------------------
/examples/app2/src/app/dashboard/dashboard.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Inject, HostBinding } from '@angular/core';
2 | import { Router } from '@angular/router';
3 | import { PlanetPortalApplication, GlobalEventDispatcher } from '@worktile/planet';
4 |
5 | @Component({
6 | selector: 'app-dashboard',
7 | templateUrl: './dashboard.component.html',
8 | standalone: false
9 | })
10 | export class DashboardComponent {
11 | @HostBinding('class') class = 'thy-layout';
12 |
13 | portalLodashVersion = (0, eval)('window')['lodash'].version;
14 |
15 | app2LodashVersion = window['lodash'].version;
16 |
17 | constructor(
18 | private portalApp: PlanetPortalApplication,
19 | private globalEventDispatcher: GlobalEventDispatcher
20 | ) {
21 | // console.log(window);
22 | }
23 |
24 | toAbout() {
25 | this.portalApp.navigateByUrl('/about');
26 | }
27 |
28 | openApp1UserDetail() {
29 | this.globalEventDispatcher.dispatch('openUserDetail', 1);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/examples/app2/src/app/overlay.ts:
--------------------------------------------------------------------------------
1 | import { Directionality } from '@angular/cdk/bidi';
2 | import {
3 | Overlay,
4 | OverlayConfig,
5 | OverlayContainer,
6 | OverlayKeyboardDispatcher,
7 | OverlayPositionBuilder,
8 | OverlayRef,
9 | ScrollStrategyOptions
10 | } from '@angular/cdk/overlay';
11 | import { DOCUMENT, Location } from '@angular/common';
12 | import { ComponentFactoryResolver, Inject, Injectable, Injector, NgZone } from '@angular/core';
13 | import { isArray, concatArray } from 'ngx-tethys/util';
14 |
15 | @Injectable({ providedIn: 'root' })
16 | export class AppOverlay extends Overlay {
17 | create(config?: OverlayConfig): OverlayRef {
18 | const overlayConfig: OverlayConfig = {
19 | ...config,
20 | panelClass: concatArray(config.panelClass, 'app2')
21 | };
22 | const overlayRef = super.create(overlayConfig);
23 | overlayRef.addPanelClass(overlayConfig.panelClass);
24 | return overlayRef;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/examples/app2/src/app/projects/detail/detail.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Project Detail {{ projectId }}
6 |
7 |
8 |
9 | Tasks
10 | Defects
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/examples/app2/src/app/projects/detail/detail.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, HostBinding } from '@angular/core';
2 | import { ActivatedRoute, Router } from '@angular/router';
3 |
4 | @Component({
5 | selector: 'app2-project-detail',
6 | templateUrl: './detail.component.html',
7 | standalone: false
8 | })
9 | export class ProjectDetailComponent implements OnInit {
10 | @HostBinding('class') class = 'thy-layout';
11 |
12 | projectId: string;
13 |
14 | constructor(
15 | private route: ActivatedRoute,
16 | router: Router
17 | ) {
18 | this.route.paramMap.subscribe(params => {
19 | this.projectId = params.get('id');
20 | router.navigateByUrl(`/app2/projects/${this.projectId}/tasks`);
21 | });
22 | }
23 |
24 | ngOnInit() {}
25 | }
26 |
--------------------------------------------------------------------------------
/examples/app2/src/app/projects/project-list.component.html:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
16 |
17 |
18 |
19 |
20 | 设置
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/examples/app2/src/app/projects/project-list.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, EventEmitter, OnInit, DoCheck, ApplicationRef, HostBinding } from '@angular/core';
2 | import { GlobalEventDispatcher, routeRedirect } from '@worktile/planet';
3 | import { ProjectService } from './projects.service';
4 | import { ThyDialog } from 'ngx-tethys/dialog';
5 | import { ProjectDetailComponent } from './detail/detail.component';
6 | import { Router } from '@angular/router';
7 | import { ThyTableRowEvent } from 'ngx-tethys/table';
8 |
9 | @Component({
10 | selector: 'app-project-list',
11 | templateUrl: './project-list.component.html',
12 | standalone: false
13 | })
14 | export class ProjectListComponent implements OnInit, DoCheck {
15 | @HostBinding('class') class = 'thy-layout';
16 |
17 | constructor(
18 | private router: Router,
19 | private globalEventDispatcher: GlobalEventDispatcher,
20 | private projectService: ProjectService,
21 | private dialog: ThyDialog,
22 | private applicationRef: ApplicationRef
23 | ) {}
24 |
25 | click = new EventEmitter();
26 |
27 | projects: any[];
28 |
29 | loadingDone: boolean;
30 |
31 | search: string;
32 |
33 | ngOnInit() {
34 | this.getProjects();
35 | }
36 |
37 | getProjects() {
38 | this.loadingDone = false;
39 | setTimeout(() => {
40 | this.projects = this.projectService.getProjects();
41 | this.loadingDone = true;
42 | }, 500);
43 | }
44 |
45 | openDetail(event: ThyTableRowEvent) {
46 | this.router.navigateByUrl(`/app2/projects/${event.row.id}`);
47 | this.click.emit();
48 | // this.dialog.open(ProjectDetailComponent, {
49 | // hasBackdrop: true,
50 | // backdropClosable: true,
51 | // closeOnNavigation: true
52 | // });
53 | }
54 |
55 | ngDoCheck() {
56 | // this.applicationRef.tick();
57 | // console.log(window['appRef'] === this.applicationRef);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/examples/app2/src/app/projects/project.resolver.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Router, ActivatedRouteSnapshot } from '@angular/router';
3 | import { ProjectService } from './projects.service';
4 |
5 | @Injectable({
6 | providedIn: 'root'
7 | })
8 | export class ProjectResolver {
9 | constructor(private projectService: ProjectService, private router: Router) {}
10 |
11 | resolve(route: ActivatedRouteSnapshot) {
12 | const projectId = route.params.id;
13 | return this.projectService.fetchProject(projectId);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/examples/app2/src/app/projects/projects.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { of } from 'rxjs';
3 | import { delay } from 'rxjs/operators';
4 |
5 | @Injectable({
6 | providedIn: 'root'
7 | })
8 | export class ProjectService {
9 | constructor() {}
10 |
11 | getProjects() {
12 | return Array.from({ length: 10 }).map((item, index) => {
13 | return {
14 | id: index,
15 | name: `Data ${index}`,
16 | desc: 'This is test data'
17 | };
18 | });
19 | }
20 |
21 | fetchProject(projectId: string) {
22 | return of({
23 | id: projectId,
24 | name: `Data ${projectId}`,
25 | desc: 'This is test data'
26 | }).pipe(delay(100));
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/examples/app2/src/app/projects/tasks/tasks.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | View1
5 | View2
6 | View3
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/examples/app2/src/app/projects/tasks/tasks.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, HostBinding } from '@angular/core';
2 | import { Router, ActivatedRoute } from '@angular/router';
3 |
4 | @Component({
5 | selector: 'app2-tasks',
6 | templateUrl: './tasks.component.html',
7 | standalone: false
8 | })
9 | export class TasksComponent implements OnInit {
10 | @HostBinding('class') class = 'thy-layout';
11 |
12 | constructor(router: Router, route: ActivatedRoute) {
13 | // router.navigate(['./view-1'], { relativeTo: route });
14 | router.navigate(['./view-1'], { relativeTo: route, browserUrl: '/app2/t/xxx' });
15 | }
16 |
17 | ngOnInit(): void {}
18 | }
19 |
--------------------------------------------------------------------------------
/examples/app2/src/app/projects/view/view.component.html:
--------------------------------------------------------------------------------
1 | viewId: {{ viewId }}
2 |
--------------------------------------------------------------------------------
/examples/app2/src/app/projects/view/view.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, HostBinding } from '@angular/core';
2 | import { ActivatedRoute } from '@angular/router';
3 |
4 | @Component({
5 | selector: 'app2-view',
6 | templateUrl: './view.component.html',
7 | standalone: false
8 | })
9 | export class ViewComponent implements OnInit {
10 | @HostBinding('class') class = 'thy-layout-content';
11 |
12 | viewId: string;
13 |
14 | constructor(private route: ActivatedRoute) {
15 | route.paramMap.subscribe(param => {
16 | this.viewId = param.get('viewId');
17 | });
18 | }
19 |
20 | ngOnInit(): void {}
21 | }
22 |
--------------------------------------------------------------------------------
/examples/app2/src/app/root/root.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/examples/app2/src/app/root/root.component.scss:
--------------------------------------------------------------------------------
1 | .header {
2 | margin: 10px 20px;
3 | }
4 |
5 | .same-name {
6 | color: green;
7 | :not([planet]) {
8 | .aa {
9 | color: #aaa;
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/app2/src/app/root/root.component.ts:
--------------------------------------------------------------------------------
1 | import { PlanetComponentLoader, routeRedirect } from '@worktile/planet';
2 | import { Component, HostBinding, OnInit } from '@angular/core';
3 | import { ProjectListComponent } from '../projects/project-list.component';
4 |
5 | @Component({
6 | selector: 'app2-root-actual',
7 | templateUrl: './root.component.html',
8 | styleUrls: ['./root.component.scss'],
9 | standalone: false
10 | })
11 | export class AppActualRootComponent {
12 | @HostBinding(`class.thy-layout`) isThyLayout = true;
13 | @HostBinding(`class.thy-layout--has-sidebar`) isThyHasSidebarLayout = true;
14 |
15 | constructor() {}
16 | }
17 |
18 | @Component({
19 | selector: 'app2-root',
20 | template: '',
21 | standalone: false
22 | })
23 | export class AppRootComponent implements OnInit {
24 | @HostBinding(`class.thy-layout`) isThyLayout = true;
25 |
26 | constructor(private planetComponentLoader: PlanetComponentLoader) {}
27 |
28 | ngOnInit() {
29 | this.planetComponentLoader.register(ProjectListComponent);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/examples/app2/src/app/shared.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { RouterModule } from '@angular/router';
4 | import { ThyLayoutModule } from 'ngx-tethys/layout';
5 | import { ThyButtonModule } from 'ngx-tethys/button';
6 | import { ThyListModule } from 'ngx-tethys/list';
7 | import { ThyDialogModule } from 'ngx-tethys/dialog';
8 | import { ThyMenuModule } from 'ngx-tethys/menu';
9 | import { ThyCardModule } from 'ngx-tethys/card';
10 | import { ThyLoadingModule } from 'ngx-tethys/loading';
11 | import { ThyTableModule } from 'ngx-tethys/table';
12 | import { ThyNavModule } from 'ngx-tethys/nav';
13 | import { ThyInputModule } from 'ngx-tethys/input';
14 | import { ThyIconModule } from 'ngx-tethys/icon';
15 |
16 | import { DemoCommonModule } from '@demo/common';
17 | import { NgxPlanetModule } from '@worktile/planet';
18 | import { ThyTagModule } from 'ngx-tethys/tag';
19 |
20 | @NgModule({
21 | declarations: [],
22 | imports: [
23 | CommonModule,
24 | RouterModule,
25 | ThyLayoutModule,
26 | ThyButtonModule,
27 | ThyListModule,
28 | ThyDialogModule,
29 | ThyMenuModule,
30 | ThyCardModule,
31 | ThyLoadingModule,
32 | ThyTableModule,
33 | ThyNavModule,
34 | ThyInputModule,
35 | ThyIconModule,
36 | ThyTagModule,
37 | DemoCommonModule,
38 | NgxPlanetModule
39 | ],
40 | exports: [
41 | CommonModule,
42 | RouterModule,
43 | ThyLayoutModule,
44 | ThyButtonModule,
45 | ThyListModule,
46 | ThyDialogModule,
47 | ThyMenuModule,
48 | ThyCardModule,
49 | ThyLoadingModule,
50 | ThyTableModule,
51 | ThyNavModule,
52 | ThyInputModule,
53 | ThyIconModule,
54 | ThyTagModule,
55 | DemoCommonModule,
56 | NgxPlanetModule
57 | ],
58 | providers: []
59 | })
60 | export class SharedModule {}
61 |
--------------------------------------------------------------------------------
/examples/app2/src/app/user/user-list.component.html:
--------------------------------------------------------------------------------
1 | This is user list page of App2, and this module is loaded lazy.
2 |
3 | Peter
4 | Lily
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/examples/app2/src/app/user/user-list.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, inject, InjectionToken } from '@angular/core';
2 | import { GlobalEventDispatcher } from '@worktile/planet';
3 | import { ThyDialog } from 'ngx-tethys/dialog';
4 |
5 | @Component({
6 | selector: 'app-user-list',
7 | templateUrl: './user-list.component.html',
8 | standalone: false
9 | })
10 | export class UserListComponent {
11 | constructor(
12 | private globalEventDispatcher: GlobalEventDispatcher,
13 | private thyDialog: ThyDialog
14 | ) {}
15 | }
16 |
--------------------------------------------------------------------------------
/examples/app2/src/app/user/user.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { UserListComponent } from './user-list.component';
4 | import { DemoCommonModule } from '@demo/common';
5 | import { NgxPlanetModule } from '@worktile/planet';
6 | import { RouterModule } from '@angular/router';
7 | import { SharedModule } from '../shared.module';
8 |
9 | @NgModule({
10 | declarations: [UserListComponent],
11 | imports: [
12 | SharedModule,
13 | RouterModule.forChild([
14 | {
15 | path: '',
16 | component: UserListComponent
17 | }
18 | ])
19 | ],
20 | exports: [UserListComponent],
21 | providers: []
22 | })
23 | export class UserModule {}
24 |
--------------------------------------------------------------------------------
/examples/app2/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/worktile/ngx-planet/2f297dec761e74afe93233d69f9cf6a46efac2b2/examples/app2/src/assets/.gitkeep
--------------------------------------------------------------------------------
/examples/app2/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true
3 | };
4 |
--------------------------------------------------------------------------------
/examples/app2/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // This file can be replaced during build by using the `fileReplacements` array.
2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
3 | // The list of file replacements can be found in `angular.json`.
4 |
5 | export const environment = {
6 | production: false
7 | };
8 |
9 | /*
10 | * For easier debugging in development mode, you can import the following file
11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
12 | *
13 | * This import should be commented out in production mode because it will have a negative impact
14 | * on performance if an error is thrown.
15 | */
16 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI.
17 |
--------------------------------------------------------------------------------
/examples/app2/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/worktile/ngx-planet/2f297dec761e74afe93233d69f9cf6a46efac2b2/examples/app2/src/favicon.ico
--------------------------------------------------------------------------------
/examples/app2/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | App1
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/examples/app2/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { bootstrapApplication } from '@angular/platform-browser';
3 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
4 |
5 | import { AppModule } from './app/app.module';
6 | import { environment } from './environments/environment';
7 | import { PlanetPortalApplication, PlanetRouterEvent, GlobalEventDispatcher, defineApplication } from '@worktile/planet';
8 |
9 | if (environment.production) {
10 | enableProdMode();
11 | }
12 |
13 | // 测试沙箱模拟APP2 引入低版本的 lodash
14 | window['lodash'] = {
15 | version: '1.0.0'
16 | };
17 |
18 | defineApplication('app2', {
19 | template: ``,
20 | bootstrap: (portalApp: PlanetPortalApplication) => {
21 | return platformBrowserDynamic([
22 | {
23 | provide: PlanetPortalApplication,
24 | useValue: portalApp
25 | }
26 | ])
27 | .bootstrapModule(AppModule)
28 | .then(appModule => {
29 | return appModule;
30 | })
31 | .catch(error => {
32 | console.error(error);
33 | return null;
34 | });
35 | }
36 | });
37 |
--------------------------------------------------------------------------------
/examples/app2/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/guide/browser-support
15 | */
16 |
17 | /***************************************************************************************************
18 | * BROWSER POLYFILLS
19 | */
20 |
21 | /** IE9, IE10, IE11, and Chrome <55 requires all of the following polyfills.
22 | * This also includes Android Emulators with older versions of Chrome and Google Search/Googlebot
23 | */
24 |
25 | // import 'core-js/es6/symbol';
26 | // import 'core-js/es6/object';
27 | // import 'core-js/es6/function';
28 | // import 'core-js/es6/parse-int';
29 | // import 'core-js/es6/parse-float';
30 | // import 'core-js/es6/number';
31 | // import 'core-js/es6/math';
32 | // import 'core-js/es6/string';
33 | // import 'core-js/es6/date';
34 | // import 'core-js/es6/array';
35 | // import 'core-js/es6/regexp';
36 | // import 'core-js/es6/map';
37 | // import 'core-js/es6/weak-map';
38 | // import 'core-js/es6/set';
39 |
40 | /** IE10 and IE11 requires the following for the Reflect API. */
41 | // import 'core-js/es6/reflect';
42 |
43 | /**
44 | * By default, zone.js will patch all possible macroTask and DomEvents
45 | * user can disable parts of macroTask/DomEvents patch by setting following flags
46 | * because those flags need to be set before `zone.js` being loaded, and webpack
47 | * will put import in the top of bundle, so user need to create a separate file
48 | * in this directory (for example: zone-flags.ts), and put the following flags
49 | * into that file, and then add the following code before importing zone.js.
50 | * import './zone-flags.ts';
51 | *
52 | * The flags allowed in zone-flags.ts are listed here.
53 | *
54 | * The following flags will work for all browsers.
55 | *
56 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
57 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
58 | * (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
59 | *
60 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
61 | * with the following flag, it will bypass `zone.js` patch for IE/Edge
62 | *
63 | * (window as any).__Zone_enable_cross_context_check = true;
64 | *
65 | */
66 |
67 | /***************************************************************************************************
68 | * Zone JS is required by default for Angular itself.
69 | */
70 | // import 'zone.js'; // Included with Angular CLI.
71 |
72 | /***************************************************************************************************
73 | * APPLICATION IMPORTS
74 | */
75 |
--------------------------------------------------------------------------------
/examples/app2/src/styles.scss:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 | @use 'ngx-tethys/styles/variables.scss' with (
3 | $layout-content-background: #fff,
4 | );
5 | @use 'ngx-tethys/styles/index.scss';
6 | @use 'ngx-tethys/styles/basic.scss';
7 | @import './app/root/root.component.scss';
8 |
9 | .section-card {
10 | display: block;
11 | position: relative;
12 | padding: 15px;
13 | border: 2px variables.$gray-300 dashed;
14 |
15 | .section-card-header {
16 | position: absolute;
17 | padding: 0 10px;
18 | background: variables.$white;
19 | top: -10px;
20 | left: 5px;
21 | color: variables.$gray-600; // $gray-500;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/app2/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/testing';
4 | import { getTestBed } from '@angular/core/testing';
5 | import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
6 |
7 | // First, initialize the Angular testing environment.
8 | getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
9 | teardown: { destroyAfterEach: false }
10 | });
11 |
--------------------------------------------------------------------------------
/examples/app2/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../out-tsc/app",
5 | "types": []
6 | },
7 | "files": ["src/main.ts"],
8 | "include": ["src/**/*.d.ts"],
9 | "exclude": ["test.ts", "**/*.spec.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/examples/app2/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../out-tsc/spec",
5 | "types": [
6 | "jasmine",
7 | "node"
8 | ]
9 | },
10 | "files": [
11 | "src/test.ts",
12 | "src/polyfills.ts"
13 | ],
14 | "include": [
15 | "**/*.spec.ts",
16 | "**/*.d.ts"
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/examples/common/app-root-context.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 |
3 | @Injectable()
4 | export class AppRootContext {
5 | name: string;
6 |
7 | setName(name: string) {
8 | this.name = name;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/examples/common/cache.ts:
--------------------------------------------------------------------------------
1 | import { helpers } from 'ngx-tethys/util';
2 | const { isString, isNumber } = helpers;
3 | const NUMBER_PREFIX = `____n____`;
4 |
5 | const supportedStorage = window && window.localStorage;
6 | const storageSource = window.localStorage || window.sessionStorage;
7 |
8 | const cache = {
9 | /**
10 | * set item to local storage
11 | *
12 | * @example
13 | * cache.set('key1', 'key1-value');
14 | * cache.set('key1', 10);
15 | * cache.set('key1', { id: 1, name: 'name 1'});
16 | * cache.set('key1', 'key1-value', false);
17 | * @param key string
18 | * @param value string | number | object
19 | */
20 | set(key: string, value: TValue) {
21 | const itemValue = isString(value)
22 | ? value
23 | : isNumber(value)
24 | ? `${NUMBER_PREFIX}${value}`
25 | : JSON.stringify(value);
26 | if (supportedStorage) {
27 | storageSource.setItem(key, itemValue as string);
28 | }
29 | },
30 | /**
31 | * get item from local storage
32 | *
33 | * @example
34 | * cache.get('key1');
35 | * cache.get('key1');
36 | * cache.get('key1');
37 | * cache.get('key1');
38 | * cache.get('key1', false);
39 | *
40 | * @param key string
41 | */
42 | get(key: string): TValue {
43 | if (supportedStorage) {
44 | const value = storageSource.getItem(key);
45 | if (value) {
46 | try {
47 | const result = JSON.parse(value);
48 | return result;
49 | } catch (error) {
50 | if (isString(value) && value.includes(NUMBER_PREFIX)) {
51 | return parseInt(value.replace(NUMBER_PREFIX, ''), 10) as any;
52 | } else {
53 | return value as any;
54 | }
55 | }
56 | } else {
57 | return undefined;
58 | }
59 | } else {
60 | return undefined;
61 | }
62 | },
63 | /**
64 | * remove key from storage
65 | * @param key cache key
66 | */
67 | remove(key: string) {
68 | if (supportedStorage) {
69 | storageSource.removeItem(key);
70 | }
71 | },
72 | /**
73 | * clear all storage
74 | */
75 | clear() {
76 | if (supportedStorage) {
77 | storageSource.clear();
78 | }
79 | }
80 | };
81 |
82 | export { cache };
83 |
--------------------------------------------------------------------------------
/examples/common/index.ts:
--------------------------------------------------------------------------------
1 | import { Overlay, OverlayContainer } from '@angular/cdk/overlay';
2 | import { Provider } from '@angular/core';
3 | import { PlanetOverlayContainer } from './overlay-container.service';
4 |
5 | export * from './app-root-context';
6 | export * from './module';
7 | export * from './cache';
8 | export * from './overlay-container.service';
9 |
10 | export const OVERLAY_PROVIDER: Provider = {
11 | provide: OverlayContainer,
12 | useClass: PlanetOverlayContainer
13 | };
14 |
--------------------------------------------------------------------------------
/examples/common/module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { SectionCardComponent } from './section-card/section-card.component';
3 | import { CommonModule } from '@angular/common';
4 |
5 | @NgModule({
6 | declarations: [],
7 | imports: [CommonModule, SectionCardComponent],
8 | exports: [SectionCardComponent]
9 | })
10 | export class DemoCommonModule {}
11 |
--------------------------------------------------------------------------------
/examples/common/overlay-container.service.ts:
--------------------------------------------------------------------------------
1 | import { OverlayContainer } from '@angular/cdk/overlay';
2 | import { Platform } from '@angular/cdk/platform';
3 | import { DOCUMENT } from '@angular/common';
4 | import { Inject, Injectable } from '@angular/core';
5 |
6 | @Injectable({ providedIn: 'root' })
7 | export class PlanetOverlayContainer extends OverlayContainer {
8 | constructor(@Inject(DOCUMENT) document: any, protected _platform: Platform) {
9 | super(document, _platform);
10 | }
11 |
12 | private _getContainerElement(): HTMLElement {
13 | if (!this._containerElement) {
14 | this._containerElement = this._document.querySelector('.cdk-overlay-container');
15 | return this._containerElement;
16 | } else {
17 | return this._containerElement;
18 | }
19 | }
20 |
21 | getContainerElement(): HTMLElement {
22 | if (!this._getContainerElement()) {
23 | this._createContainer();
24 | }
25 |
26 | return this._containerElement;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/examples/common/section-card/section-card.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/examples/common/section-card/section-card.component.scss:
--------------------------------------------------------------------------------
1 | @use 'ngx-tethys/styles/variables.scss' as thy;
2 | .section-card {
3 | display: block;
4 | position: relative;
5 | padding: 15px;
6 | border: 2px thy.$gray-300 dashed;
7 |
8 | .section-card-header {
9 | position: absolute;
10 | padding: 0 10px;
11 | background: thy.$white;
12 | top: -10px;
13 | left: 5px;
14 | color: thy.$gray-600; // $gray-500;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/examples/common/section-card/section-card.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, HostBinding, Input } from '@angular/core';
2 | import { NgIf } from '@angular/common';
3 |
4 | @Component({
5 | selector: 'section-card',
6 | templateUrl: './section-card.component.html',
7 | imports: [NgIf]
8 | })
9 | export class SectionCardComponent {
10 | @HostBinding('class.section-card') addSectionCard = true;
11 |
12 | @Input() title: string;
13 | }
14 |
--------------------------------------------------------------------------------
/examples/common/utils.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/worktile/ngx-planet/2f297dec761e74afe93233d69f9cf6a46efac2b2/examples/common/utils.ts
--------------------------------------------------------------------------------
/examples/portal/src/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../.eslintrc.json",
3 | "ignorePatterns": ["!**/*"],
4 | "overrides": [
5 | {
6 | "files": ["*.ts"],
7 | "parserOptions": {
8 | "project": ["examples/portal/src/tsconfig.app.json", "examples/portal/src/tsconfig.spec.json"],
9 | "createDefaultProgram": true
10 | },
11 | "rules": {
12 | "@angular-eslint/directive-selector": [
13 | "error",
14 | {
15 | "type": "attribute",
16 | "prefix": "app",
17 | "style": "camelCase"
18 | }
19 | ],
20 | "@angular-eslint/component-selector": [
21 | "error",
22 | {
23 | "type": "element",
24 | "prefix": "app",
25 | "style": "kebab-case"
26 | }
27 | ]
28 | }
29 | },
30 | {
31 | "files": ["*.html"],
32 | "rules": {}
33 | }
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/examples/portal/src/app/a-detail/a-detail.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | This is Micro Front-end application example.
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/examples/portal/src/app/a-detail/a-detail.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { ThyDialogRef } from 'ngx-tethys/dialog';
3 |
4 | @Component({
5 | selector: 'app-a-detail',
6 | templateUrl: './a-detail.component.html',
7 | standalone: false
8 | })
9 | export class ADetailComponent {
10 | constructor(private dialogRef: ThyDialogRef) {}
11 |
12 | ok() {
13 | this.dialogRef.close();
14 | }
15 |
16 | close() {
17 | this.dialogRef.close();
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/examples/portal/src/app/about/about.component.html:
--------------------------------------------------------------------------------
1 |
2 | ngx-planet
is a powerful and easy micro front-end library only for angular.
3 | This is live demo for planet which contains portal, app1, app2.
4 | Github Repository
5 | https://github.com/worktile/ngx-planet
6 |
7 |
8 |
9 | appRootContext.name: {{ appRootContext.name }}
10 |
11 |
12 |
13 | To App1's Dashboard
14 | To App2's Dashboard
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | portal 特有样式: font-size: 20px
23 |
24 |
25 |
26 | 同名样式: color: blue, font-size:12px
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/examples/portal/src/app/about/about.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/worktile/ngx-planet/2f297dec761e74afe93233d69f9cf6a46efac2b2/examples/portal/src/app/about/about.component.scss
--------------------------------------------------------------------------------
/examples/portal/src/app/about/about.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 |
3 | import { AboutComponent } from './about.component';
4 |
5 | describe('AboutComponent', () => {
6 | let component: AboutComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(
10 | waitForAsync(() => {
11 | TestBed.configureTestingModule({
12 | declarations: [AboutComponent]
13 | }).compileComponents();
14 | })
15 | );
16 |
17 | beforeEach(() => {
18 | fixture = TestBed.createComponent(AboutComponent);
19 | component = fixture.componentInstance;
20 | fixture.detectChanges();
21 | });
22 |
23 | it('should create', () => {
24 | expect(component).toBeTruthy();
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/examples/portal/src/app/about/about.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, HostBinding } from '@angular/core';
2 | import { Router } from '@angular/router';
3 | import { ThyDialog } from 'ngx-tethys/dialog';
4 | import { ADetailComponent } from '../a-detail/a-detail.component';
5 | import { AppRootContext } from '@demo/common';
6 |
7 | @Component({
8 | selector: 'app-about',
9 | templateUrl: './about.component.html',
10 | styleUrls: ['./about.component.scss'],
11 | standalone: false
12 | })
13 | export class AboutComponent implements OnInit {
14 | @HostBinding(`class.thy-layout-content`) isThyLayoutContent = true;
15 |
16 | constructor(
17 | private router: Router,
18 | private thyDialog: ThyDialog,
19 | public appRootContext: AppRootContext
20 | ) {}
21 |
22 | ngOnInit() {}
23 |
24 | toApp2Dashboard() {
25 | this.router.navigateByUrl(`/app2/dashboard`);
26 | }
27 |
28 | openADetail() {
29 | this.thyDialog.open(ADetailComponent);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/examples/portal/src/app/about/components/portal-custom.component.html:
--------------------------------------------------------------------------------
1 |
2 | I am Portal's Component
3 |
4 |
--------------------------------------------------------------------------------
/examples/portal/src/app/about/components/portal-custom.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/worktile/ngx-planet/2f297dec761e74afe93233d69f9cf6a46efac2b2/examples/portal/src/app/about/components/portal-custom.component.scss
--------------------------------------------------------------------------------
/examples/portal/src/app/about/components/portal-custom.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-portal-custom',
5 | templateUrl: './portal-custom.component.html',
6 | styleUrls: ['./portal-custom.component.scss'],
7 | standalone: false
8 | })
9 | export class PortalCustomComponent implements OnInit {
10 | constructor() {}
11 |
12 | ngOnInit() {}
13 | }
14 |
--------------------------------------------------------------------------------
/examples/portal/src/app/app-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { Routes, RouterModule } from '@angular/router';
3 | import { AboutComponent } from './about/about.component';
4 | import { SettingsComponent } from './settings/settings.component';
5 | import { EmptyComponent } from '@worktile/planet';
6 |
7 | const routes: Routes = [
8 | {
9 | path: '',
10 | redirectTo: 'about',
11 | pathMatch: 'full'
12 | },
13 | {
14 | path: 'about',
15 | component: AboutComponent
16 | },
17 | {
18 | path: 'settings',
19 | component: SettingsComponent
20 | },
21 | {
22 | path: 'app1',
23 | component: EmptyComponent,
24 | children: [
25 | {
26 | path: '**',
27 | component: EmptyComponent
28 | }
29 | ]
30 | },
31 | {
32 | path: 'app2',
33 | component: EmptyComponent,
34 | children: [
35 | {
36 | path: '**',
37 | component: EmptyComponent
38 | }
39 | ]
40 | },
41 | {
42 | path: 'standalone-app',
43 | component: EmptyComponent,
44 | children: [
45 | {
46 | path: '**',
47 | component: EmptyComponent
48 | }
49 | ]
50 | }
51 | ];
52 |
53 | @NgModule({
54 | imports: [RouterModule.forRoot(routes, { paramsInheritanceStrategy: 'always' })],
55 | exports: [RouterModule]
56 | })
57 | export class AppRoutingModule {}
58 |
--------------------------------------------------------------------------------
/examples/portal/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
42 |
47 | @if (loading(); as loadingState) {
48 |
49 | }
50 |
51 |
--------------------------------------------------------------------------------
/examples/portal/src/app/app.component.scss:
--------------------------------------------------------------------------------
1 | @use 'ngx-tethys/styles/variables.scss';
2 |
3 | $app-nav-width: 70px;
4 | $app-nav-bg-color: #48525c;
5 |
6 | .portal-container {
7 | display: contents;
8 | height: 100%ß;
9 | }
10 | .app-nav {
11 | width: $app-nav-width;
12 | background: $app-nav-bg-color;
13 | height: 100%;
14 |
15 | .logo {
16 | height: 50px;
17 | margin-top: 15px;
18 | }
19 |
20 | &.thy-layout-sidebar {
21 | border-right: none;
22 | }
23 |
24 | .thy-nav-item {
25 | opacity: 0.8;
26 | min-width: $app-nav-width;
27 | color: variables.$white;
28 | height: $app-nav-width;
29 | text-align: center;
30 | display: flex;
31 | align-items: center;
32 | justify-content: center;
33 | .thy-icon {
34 | color: variables.$white;
35 | font-size: variables.$font-size-xlg;
36 | margin-left: -2px;
37 | }
38 | &.active,
39 | &:hover {
40 | background: rgb(57, 65, 76);
41 | color: variables.$white;
42 | .thy-icon {
43 | color: variables.$white;
44 | }
45 | }
46 | }
47 |
48 | // .thy-nav--vertical {
49 | // .nav-link {
50 | // border-left-width: 0px;
51 | // }
52 | // }
53 | }
54 |
55 | .same-name {
56 | color: blue;
57 | font-size: 12px;
58 | }
59 |
60 | .portal-special {
61 | font-size: 20px;
62 | }
63 |
--------------------------------------------------------------------------------
/examples/portal/src/app/app.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed, waitForAsync } from '@angular/core/testing';
2 | import { RouterTestingModule } from '@angular/router/testing';
3 | import { AppComponent } from './app.component';
4 |
5 | describe('AppComponent', () => {
6 | beforeEach(
7 | waitForAsync(() => {
8 | TestBed.configureTestingModule({
9 | imports: [RouterTestingModule],
10 | declarations: [AppComponent]
11 | }).compileComponents();
12 | })
13 | );
14 |
15 | it('should create the app', () => {
16 | const fixture = TestBed.createComponent(AppComponent);
17 | const app = fixture.debugElement.componentInstance;
18 | expect(app).toBeTruthy();
19 | });
20 |
21 | it(`should have as title 'ngx-micro-frontend'`, () => {
22 | const fixture = TestBed.createComponent(AppComponent);
23 | const app = fixture.debugElement.componentInstance;
24 | expect(app.title).toEqual('ngx-micro-frontend');
25 | });
26 |
27 | it('should render title in a h1 tag', () => {
28 | const fixture = TestBed.createComponent(AppComponent);
29 | fixture.detectChanges();
30 | const compiled = fixture.debugElement.nativeElement;
31 | expect(compiled.querySelector('h1').textContent).toContain('Welcome to ngx-micro-frontend!');
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/examples/portal/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import { NgModule } from '@angular/core';
3 | import { BrowserModule, DomSanitizer } from '@angular/platform-browser';
4 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
5 |
6 | import { Overlay } from '@angular/cdk/overlay';
7 | import { FormsModule } from '@angular/forms';
8 | import { NavigationStart, Router } from '@angular/router';
9 | import { AppRootContext, DemoCommonModule, OVERLAY_PROVIDER } from '@demo/common';
10 | import { NgxPlanetModule } from '@worktile/planet';
11 | import { ThyButtonModule } from 'ngx-tethys/button';
12 | import { ThyCheckboxModule } from 'ngx-tethys/checkbox';
13 | import { ThyDialogModule } from 'ngx-tethys/dialog';
14 | import { ThyDropdownModule } from 'ngx-tethys/dropdown';
15 | import { ThyFormModule } from 'ngx-tethys/form';
16 | import { ThyIconModule, ThyIconRegistry } from 'ngx-tethys/icon';
17 | import { ThyLayoutModule } from 'ngx-tethys/layout';
18 | import { ThyLoadingModule } from 'ngx-tethys/loading';
19 | import { ThyNavModule } from 'ngx-tethys/nav';
20 | import { ThyNotifyModule } from 'ngx-tethys/notify';
21 | import { ThyPopoverModule } from 'ngx-tethys/popover';
22 | import { ThyRadioModule } from 'ngx-tethys/radio';
23 | import { ThyTableModule } from 'ngx-tethys/table';
24 | import { ThyTooltipModule } from 'ngx-tethys/tooltip';
25 | import { ADetailComponent } from './a-detail/a-detail.component';
26 | import { AboutComponent } from './about/about.component';
27 | import { PortalCustomComponent } from './about/components/portal-custom.component';
28 | import { AppRoutingModule } from './app-routing.module';
29 | import { AppComponent } from './app.component';
30 | import { AppOverlay } from './overlay';
31 | import { SettingsComponent } from './settings/settings.component';
32 |
33 | @NgModule({
34 | declarations: [AppComponent, AboutComponent, PortalCustomComponent, SettingsComponent, ADetailComponent],
35 | imports: [
36 | BrowserModule,
37 | BrowserAnimationsModule,
38 | FormsModule,
39 | CommonModule,
40 | ThyIconModule,
41 | ThyButtonModule,
42 | ThyDialogModule,
43 | ThyTableModule,
44 | ThyLayoutModule,
45 | ThyNotifyModule,
46 | ThyLoadingModule,
47 | ThyNavModule,
48 | ThyDropdownModule,
49 | ThyTooltipModule,
50 | ThyPopoverModule,
51 | ThyFormModule,
52 | ThyRadioModule,
53 | ThyCheckboxModule,
54 | AppRoutingModule,
55 | NgxPlanetModule,
56 | DemoCommonModule
57 | ],
58 | providers: [AppRootContext, OVERLAY_PROVIDER, { provide: Overlay, useClass: AppOverlay }],
59 | bootstrap: [AppComponent]
60 | })
61 | export class AppModule {
62 | constructor(
63 | iconRegistry: ThyIconRegistry,
64 | domSanitizer: DomSanitizer,
65 | private router: Router
66 | ) {
67 | iconRegistry.addSvgIconSet(domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/sprite.defs.svg'));
68 |
69 | this.router.events.subscribe(event => {
70 | if (event instanceof NavigationStart) {
71 | console.log(`[Portal] url: ${event.url}, id: ${event.id}, navigationTrigger: ${event.navigationTrigger}`);
72 | }
73 | });
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/examples/portal/src/app/custom-settings.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { cache } from '@demo/common';
3 | import { SwitchModes } from '@worktile/planet';
4 | const SETTINGS_KEY = 'custom-settings';
5 |
6 | export interface CustomSettingsInfo {
7 | app1: {
8 | preload: boolean;
9 | switchMode?: SwitchModes;
10 | };
11 | app2: {
12 | preload: boolean;
13 | switchMode?: SwitchModes;
14 | };
15 | standaloneApp: {
16 | preload: boolean;
17 | switchMode?: SwitchModes;
18 | };
19 | }
20 |
21 | const DEFAULT_SETTINGS: CustomSettingsInfo = {
22 | app1: {
23 | preload: true,
24 | switchMode: SwitchModes.coexist
25 | },
26 | app2: {
27 | preload: true,
28 | switchMode: SwitchModes.coexist
29 | },
30 | standaloneApp: {
31 | preload: true,
32 | switchMode: SwitchModes.coexist
33 | }
34 | };
35 |
36 | @Injectable({
37 | providedIn: 'root'
38 | })
39 | export class CustomSettingsService {
40 | get(): CustomSettingsInfo {
41 | const settings = cache.get(SETTINGS_KEY);
42 | return settings || JSON.parse(JSON.stringify(DEFAULT_SETTINGS));
43 | }
44 |
45 | save(settings: CustomSettingsInfo) {
46 | cache.set(SETTINGS_KEY, settings);
47 | }
48 |
49 | restore() {
50 | cache.set(SETTINGS_KEY, DEFAULT_SETTINGS);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/examples/portal/src/app/overlay.ts:
--------------------------------------------------------------------------------
1 | import { Directionality } from '@angular/cdk/bidi';
2 | import {
3 | Overlay,
4 | OverlayConfig,
5 | OverlayContainer,
6 | OverlayKeyboardDispatcher,
7 | OverlayPositionBuilder,
8 | OverlayRef,
9 | ScrollStrategyOptions
10 | } from '@angular/cdk/overlay';
11 | import { DOCUMENT, Location } from '@angular/common';
12 | import { ComponentFactoryResolver, Inject, Injectable, Injector, NgZone } from '@angular/core';
13 | import { isArray, concatArray } from 'ngx-tethys/util';
14 |
15 | @Injectable({ providedIn: 'root' })
16 | export class AppOverlay extends Overlay {
17 | create(config?: OverlayConfig): OverlayRef {
18 | const overlayConfig: OverlayConfig = {
19 | ...config,
20 | panelClass: concatArray(config.panelClass, 'portal')
21 | };
22 | const overlayRef = super.create(overlayConfig);
23 | overlayRef.addPanelClass(overlayConfig.panelClass);
24 | return overlayRef;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/examples/portal/src/app/settings/settings.component.html:
--------------------------------------------------------------------------------
1 |
2 |
31 |
32 |
--------------------------------------------------------------------------------
/examples/portal/src/app/settings/settings.component.scss:
--------------------------------------------------------------------------------
1 | // .help {
2 | // color: blue;
3 | // }
4 |
--------------------------------------------------------------------------------
/examples/portal/src/app/settings/settings.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, HostBinding } from '@angular/core';
2 | import { Router } from '@angular/router';
3 | import { ThyNotifyService } from 'ngx-tethys/notify';
4 | import { ADetailComponent } from '../a-detail/a-detail.component';
5 | import { AppRootContext } from '@demo/common';
6 | import { CustomSettingsService, CustomSettingsInfo } from '../custom-settings.service';
7 |
8 | @Component({
9 | selector: 'app-settings',
10 | templateUrl: './settings.component.html',
11 | styleUrls: ['./settings.component.scss'],
12 | standalone: false
13 | })
14 | export class SettingsComponent implements OnInit {
15 | @HostBinding(`class.thy-layout-content`) isThyLayoutContent = true;
16 |
17 | settings: CustomSettingsInfo;
18 |
19 | constructor(
20 | public appRootContext: AppRootContext,
21 | private customSettingsService: CustomSettingsService,
22 | private thyNotifyService: ThyNotifyService
23 | ) {}
24 |
25 | ngOnInit() {
26 | this.reset();
27 | }
28 |
29 | save() {
30 | this.customSettingsService.save(this.settings);
31 | this.thyNotifyService.success(`Save success`);
32 | }
33 |
34 | reset() {
35 | this.settings = {
36 | ...this.customSettingsService.get()
37 | };
38 | }
39 |
40 | restore() {
41 | this.customSettingsService.restore();
42 | this.reset();
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/examples/portal/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/worktile/ngx-planet/2f297dec761e74afe93233d69f9cf6a46efac2b2/examples/portal/src/assets/.gitkeep
--------------------------------------------------------------------------------
/examples/portal/src/assets/apps.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "app1",
4 | "host": "#app-host-container",
5 | "selector": "app1-root-container",
6 | "routerPathPrefix": "/app1",
7 | "hostClass": "thy-layout",
8 | "preload": false,
9 | "switchMode": "default",
10 | "assetsUrl": "/app1/assets/app.json",
11 | "stylePathPrefix": "",
12 | "styles": [],
13 | "scriptPathPrefix": "/app1/assets/",
14 | "scripts": ["main.js"]
15 | },
16 | {
17 | "name": "app2",
18 | "host": "#app-host-container",
19 | "selector": "app2-root-container",
20 | "routerPathPrefix": "/app2",
21 | "hostClass": "thy-layout",
22 | "preload": false,
23 | "switchMode": "default",
24 | "stylePathPrefix": "",
25 | "styles": [],
26 | "scriptPathPrefix": "/app2/assets/",
27 | "scripts": ["main.js"]
28 | }
29 | ]
30 |
--------------------------------------------------------------------------------
/examples/portal/src/assets/images/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
--------------------------------------------------------------------------------
/examples/portal/src/assets/ngx-planet-micro-front-end.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/worktile/ngx-planet/2f297dec761e74afe93233d69f9cf6a46efac2b2/examples/portal/src/assets/ngx-planet-micro-front-end.gif
--------------------------------------------------------------------------------
/examples/portal/src/browserslist:
--------------------------------------------------------------------------------
1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers
2 | # For additional information regarding the format and rule options, please see:
3 | # https://github.com/browserslist/browserslist#queries
4 | #
5 | # For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed
6 |
7 | > 0.5%
8 | last 2 versions
9 | Firefox ESR
10 | not dead
11 | not IE 9-11
--------------------------------------------------------------------------------
/examples/portal/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true
3 | };
4 |
--------------------------------------------------------------------------------
/examples/portal/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // This file can be replaced during build by using the `fileReplacements` array.
2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
3 | // The list of file replacements can be found in `angular.json`.
4 |
5 | export const environment = {
6 | production: false
7 | };
8 |
9 | /*
10 | * For easier debugging in development mode, you can import the following file
11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
12 | *
13 | * This import should be commented out in production mode because it will have a negative impact
14 | * on performance if an error is thrown.
15 | */
16 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI.
17 |
--------------------------------------------------------------------------------
/examples/portal/src/extra-webpack.config.js:
--------------------------------------------------------------------------------
1 | const WebpackAssetsManifest = require('webpack-assets-manifest');
2 | const PrefixWrap = require('@worktile/planet-postcss-prefixwrap');
3 |
4 | module.exports = {
5 | optimization: {
6 | runtimeChunk: false
7 | },
8 | plugins: [new WebpackAssetsManifest()],
9 | module: {
10 | rules: [
11 | {
12 | test: /\.scss$/,
13 | use: [
14 | {
15 | loader: 'postcss-loader',
16 | options: {
17 | postcssOptions: {
18 | plugins: [
19 | PrefixWrap('.portal', {
20 | blacklist: ['./reboot.scss'],
21 | prefixRootTags: true
22 | })
23 | ]
24 | }
25 | }
26 | },
27 | 'sass-loader'
28 | ]
29 | }
30 | ]
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/examples/portal/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/worktile/ngx-planet/2f297dec761e74afe93233d69f9cf6a46efac2b2/examples/portal/src/favicon.ico
--------------------------------------------------------------------------------
/examples/portal/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Ngx Planet - Micro Front-end for Angular
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/examples/portal/src/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-devkit/build-angular'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage'),
13 | require('@angular-devkit/build-angular/plugins/karma')
14 | ],
15 | client: {
16 | clearContext: false // leave Jasmine Spec Runner output visible in browser
17 | },
18 | coverageIstanbulReporter: {
19 | dir: require('path').join(__dirname, '../coverage'),
20 | reports: ['html', 'lcovonly', 'text-summary'],
21 | fixWebpackSourcePaths: true
22 | },
23 | reporters: ['progress', 'kjhtml'],
24 | port: 9876,
25 | colors: true,
26 | logLevel: config.LOG_INFO,
27 | autoWatch: true,
28 | browsers: ['Chrome'],
29 | singleRun: false
30 | });
31 | };
32 |
--------------------------------------------------------------------------------
/examples/portal/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 | // 测试沙箱模拟主应用引入版本为3.0.0的lodash
12 | window['lodash'] = {
13 | version: '3.0.0'
14 | };
15 |
16 | platformBrowserDynamic()
17 | .bootstrapModule(AppModule)
18 | .catch(err => console.error(err));
19 |
--------------------------------------------------------------------------------
/examples/portal/src/reboot.scss:
--------------------------------------------------------------------------------
1 | @use 'ngx-tethys/styles/variables.scss' with (
2 | $layout-content-background: #fff,
3 | );
4 | @use 'ngx-tethys/styles/index.scss';
5 | // @import 'ngx-tethys/styles/modules/reboot.scss';
6 | // @import 'ngx-tethys/styles/modules/cdk/index';
7 | // @import 'ngx-tethys/layout/styles/layout.scss';
8 |
9 | .portal-test {
10 | color: #aaa;
11 | }
12 | body,
13 | html {
14 | height: 100%;
15 | }
16 |
--------------------------------------------------------------------------------
/examples/portal/src/styles.scss:
--------------------------------------------------------------------------------
1 | @use 'ngx-tethys/styles/variables.scss' with (
2 | $layout-content-background: #fff,
3 | );
4 | @use 'ngx-tethys/styles/index.scss';
5 |
6 | @use './app/app.component.scss';
7 |
8 | @use '../../common/section-card/section-card.component.scss';
9 |
--------------------------------------------------------------------------------
/examples/portal/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/testing';
4 | import { getTestBed } from '@angular/core/testing';
5 | import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
6 |
7 | // First, initialize the Angular testing environment.
8 | getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
9 | teardown: { destroyAfterEach: false }
10 | });
11 |
--------------------------------------------------------------------------------
/examples/portal/src/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/app",
5 | "types": []
6 | },
7 | "files": ["main.ts", "polyfills.ts"],
8 | "include": ["**/*.d.ts"],
9 | "exclude": ["test.ts", "**/*.spec.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/examples/portal/src/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/spec",
5 | "types": ["jasmine", "node"]
6 | },
7 | "files": ["test.ts", "polyfills.ts"],
8 | "include": ["**/*.spec.ts", "**/*.d.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/examples/standalone-app/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../.eslintrc.json",
3 | "ignorePatterns": ["!**/*"],
4 | "overrides": [
5 | {
6 | "files": ["*.ts"],
7 | "parserOptions": {
8 | "project": ["examples/standalone-app/tsconfig.(app|spec).json"]
9 | },
10 | "rules": {
11 | "@angular-eslint/directive-selector": [
12 | "error",
13 | {
14 | "type": "attribute",
15 | "prefix": "app",
16 | "style": "camelCase"
17 | }
18 | ],
19 | "@angular-eslint/component-selector": [
20 | "error",
21 | {
22 | "type": "element",
23 | "prefix": "app",
24 | "style": "kebab-case"
25 | }
26 | ]
27 | }
28 | },
29 | {
30 | "files": ["*.html"],
31 | "rules": {}
32 | }
33 | ]
34 | }
35 |
--------------------------------------------------------------------------------
/examples/standalone-app/plugin-manifest.js:
--------------------------------------------------------------------------------
1 | const manifestPlugin = require('esbuild-plugin-manifest');
2 |
3 | module.exports = manifestPlugin({
4 | filename: 'assets-manifest.json'
5 | });
6 |
--------------------------------------------------------------------------------
/examples/standalone-app/src/app/about/about.component.html:
--------------------------------------------------------------------------------
1 | This is About
2 |
--------------------------------------------------------------------------------
/examples/standalone-app/src/app/about/about.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Inject, ViewChild, ElementRef, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-about',
5 | templateUrl: './about.component.html',
6 | standalone: true
7 | })
8 | export class AboutComponent implements OnInit {
9 | ngOnInit(): void {}
10 | }
11 |
--------------------------------------------------------------------------------
/examples/standalone-app/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/examples/standalone-app/src/app/app.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/worktile/ngx-planet/2f297dec761e74afe93233d69f9cf6a46efac2b2/examples/standalone-app/src/app/app.component.scss
--------------------------------------------------------------------------------
/examples/standalone-app/src/app/app.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 | import { AppComponent } from './app.component';
3 |
4 | describe('AppComponent', () => {
5 | beforeEach(async () => {
6 | await TestBed.configureTestingModule({
7 | imports: [AppComponent]
8 | }).compileComponents();
9 | });
10 |
11 | it('should create the app', () => {
12 | const fixture = TestBed.createComponent(AppComponent);
13 | const app = fixture.componentInstance;
14 | expect(app).toBeTruthy();
15 | });
16 |
17 | it(`should have the 'standalone-app' title`, () => {
18 | const fixture = TestBed.createComponent(AppComponent);
19 | const app = fixture.componentInstance;
20 | expect(app.title).toEqual('standalone-app');
21 | });
22 |
23 | it('should render title', () => {
24 | const fixture = TestBed.createComponent(AppComponent);
25 | fixture.detectChanges();
26 | const compiled = fixture.nativeElement as HTMLElement;
27 | expect(compiled.querySelector('h1')?.textContent).toContain('Hello, standalone-app');
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/examples/standalone-app/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { RouterOutlet, RouterLink, RouterLinkActive } from '@angular/router';
3 | import { ThyLayout, ThyLayoutDirective, ThySidebar } from 'ngx-tethys/layout';
4 | import { ThyMenu, ThyMenuItem } from 'ngx-tethys/menu';
5 |
6 | @Component({
7 | selector: 'standalone-app-root',
8 | imports: [RouterOutlet],
9 | template: ''
10 | })
11 | export class AppRootComponent {
12 | title = 'standalone-app';
13 | }
14 |
15 | @Component({
16 | selector: 'standalone-app-root-actual',
17 | imports: [RouterOutlet, RouterLink, RouterLinkActive, ThyLayout, ThySidebar, ThyMenu, ThyMenuItem],
18 | templateUrl: './app.component.html',
19 | host: {
20 | class: 'thy-layout'
21 | },
22 | hostDirectives: [ThyLayoutDirective]
23 | })
24 | export class AppRootActualComponent {}
25 |
--------------------------------------------------------------------------------
/examples/standalone-app/src/app/app.config.ts:
--------------------------------------------------------------------------------
1 | import { ApplicationConfig } from '@angular/core';
2 | import { provideRouter } from '@angular/router';
3 |
4 | import { routes } from './app.routes';
5 |
6 | export const appConfig: ApplicationConfig = {
7 | providers: [provideRouter(routes)]
8 | };
9 |
--------------------------------------------------------------------------------
/examples/standalone-app/src/app/app.routes.ts:
--------------------------------------------------------------------------------
1 | import { Routes } from '@angular/router';
2 | import { AppRootActualComponent } from './app.component';
3 | import { DashboardComponent } from './dashboard/dashboard.component';
4 | import { EmptyComponent, redirectToRoute } from '@worktile/planet';
5 | import { AboutComponent } from './about/about.component';
6 |
7 | export const routes: Routes = [
8 | {
9 | path: 'standalone-app',
10 | component: AppRootActualComponent,
11 | children: [
12 | redirectToRoute('dashboard'),
13 | {
14 | path: 'dashboard',
15 | component: DashboardComponent
16 | },
17 | {
18 | path: 'about',
19 | loadComponent: () => import('./about/about.component').then(m => m.AboutComponent)
20 | }
21 | ]
22 | },
23 | {
24 | path: '**',
25 | component: EmptyComponent
26 | }
27 | ];
28 |
--------------------------------------------------------------------------------
/examples/standalone-app/src/app/dashboard/dashboard.component.html:
--------------------------------------------------------------------------------
1 |
2 | This is Standalone App's dashboard
3 |
4 |
5 | About use routerLink
6 |
7 |
8 |
--------------------------------------------------------------------------------
/examples/standalone-app/src/app/dashboard/dashboard.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Inject, ViewChild, ElementRef, OnInit } from '@angular/core';
2 | import { Router, RouterLink } from '@angular/router';
3 | import { AppRootContext } from '@demo/common';
4 | import { PlanetComponentRef, PlanetComponentLoader, PlanetPortalApplication, GlobalEventDispatcher } from '@worktile/planet';
5 | import { ThyDialog } from 'ngx-tethys/dialog';
6 | import { ThyContent, ThyLayoutDirective } from 'ngx-tethys/layout';
7 | import { DemoCommonModule } from '../../../../common/module';
8 |
9 | @Component({
10 | selector: 'app-dashboard',
11 | templateUrl: './dashboard.component.html',
12 | hostDirectives: [ThyLayoutDirective],
13 | imports: [ThyContent, DemoCommonModule, RouterLink]
14 | })
15 | export class DashboardComponent implements OnInit {
16 | @ViewChild('container', { static: true }) containerElementRef: ElementRef;
17 |
18 | private componentRef: PlanetComponentRef;
19 |
20 | constructor(
21 | private router: Router,
22 | private portalApp: PlanetPortalApplication,
23 | private globalEventDispatcher: GlobalEventDispatcher
24 | ) {}
25 |
26 | ngOnInit() {}
27 |
28 | toAbout() {
29 | this.portalApp.navigateByUrl('/about');
30 | }
31 |
32 | openADetail() {
33 | this.globalEventDispatcher.dispatch('openADetail');
34 | }
35 |
36 | openApp1Dialog() {}
37 | }
38 |
--------------------------------------------------------------------------------
/examples/standalone-app/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/worktile/ngx-planet/2f297dec761e74afe93233d69f9cf6a46efac2b2/examples/standalone-app/src/assets/.gitkeep
--------------------------------------------------------------------------------
/examples/standalone-app/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/worktile/ngx-planet/2f297dec761e74afe93233d69f9cf6a46efac2b2/examples/standalone-app/src/favicon.ico
--------------------------------------------------------------------------------
/examples/standalone-app/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | StandaloneApp
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/standalone-app/src/main.ts:
--------------------------------------------------------------------------------
1 | import { bootstrapApplication } from '@angular/platform-browser';
2 | import { appConfig } from './app/app.config';
3 | import { AppRootComponent } from './app/app.component';
4 | import { defineApplication, PlanetPortalApplication } from '@worktile/planet';
5 | import { AppRootContext } from '../../common';
6 |
7 | defineApplication('standalone-app', {
8 | template: ``,
9 | bootstrap: (portalApp: PlanetPortalApplication) => {
10 | appConfig.providers.push([
11 | {
12 | provide: PlanetPortalApplication,
13 | useValue: portalApp
14 | },
15 | {
16 | provide: AppRootContext,
17 | useValue: portalApp.data.appRootContext
18 | }
19 | ]);
20 | return bootstrapApplication(AppRootComponent, appConfig).catch(error => {
21 | console.error(error);
22 | return null;
23 | });
24 | }
25 | });
26 |
27 | // bootstrapApplication(AppRootComponent, appConfig);
28 |
--------------------------------------------------------------------------------
/examples/standalone-app/src/styles.scss:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 |
3 | @use 'ngx-tethys/styles/variables.scss' with (
4 | $layout-content-background: #fff
5 | );
6 | @use 'ngx-tethys/styles/index.scss';
7 | @use 'ngx-tethys/styles/basic.scss';
8 |
9 | .section-card {
10 | display: block;
11 | position: relative;
12 | padding: 15px;
13 | border: 2px variables.$gray-300 dashed;
14 |
15 | .section-card-header {
16 | position: absolute;
17 | padding: 0 10px;
18 | background: variables.$white;
19 | top: -10px;
20 | left: 5px;
21 | color: variables.$gray-600; // $gray-500;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/standalone-app/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "../../tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "../../out-tsc/app",
6 | "types": []
7 | },
8 | "files": ["src/main.ts"],
9 | "include": ["src/**/*.d.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/examples/standalone-app/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "../../tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "../../out-tsc/spec",
6 | "types": ["jasmine"]
7 | },
8 | "include": ["src/**/*.spec.ts", "src/**/*.d.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/extra-webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | optimization: {
3 | runtimeChunk: false
4 | }
5 | };
6 |
--------------------------------------------------------------------------------
/nginx.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 8080;
3 | proxy_redirect off;
4 | proxy_set_header Host $host;
5 | proxy_set_header X-Real-IP $remote_addr;
6 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
7 | proxy_set_header X-Forwarded-Proto http;
8 |
9 | location / {
10 | root /etc/nginx/html/static/portal;
11 | try_files $uri $uri/ /index.html;
12 | }
13 |
14 | location /static {
15 | root /etc/nginx/html;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/planet/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../.eslintrc.json",
3 | "ignorePatterns": ["!**/*"],
4 | "overrides": [
5 | {
6 | "files": ["*.ts"],
7 | "parserOptions": {
8 | "project": ["packages/planet/tsconfig.lib.json", "packages/planet/tsconfig.spec.json"],
9 | "createDefaultProgram": true
10 | },
11 | "rules": {
12 | "@angular-eslint/directive-selector": [
13 | "error",
14 | {
15 | "type": "attribute",
16 | "prefix": "planet",
17 | "style": "camelCase"
18 | }
19 | ],
20 | "@angular-eslint/component-selector": [
21 | "error",
22 | {
23 | "type": "element",
24 | "prefix": "planet",
25 | "style": "kebab-case"
26 | }
27 | ]
28 | }
29 | },
30 | {
31 | "files": ["*.html"],
32 | "rules": {}
33 | }
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/packages/planet/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-devkit/build-angular'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage'),
13 | require('@angular-devkit/build-angular/plugins/karma')
14 | ],
15 | client: {
16 | clearContext: false // leave Jasmine Spec Runner output visible in browser
17 | },
18 | coverageReporter: {
19 | dir: require('path').join(__dirname, '../../coverage'),
20 | subdir: '.',
21 | reporters: [{ type: 'html' }, { type: 'text-summary' }, { type: 'lcovonly' }]
22 | },
23 | reporters: ['progress', 'kjhtml'],
24 | port: 9876,
25 | colors: true,
26 | logLevel: config.LOG_INFO,
27 | autoWatch: true,
28 | browsers: ['Chrome', 'ChromeHeadless', 'ChromeHeadlessCI'],
29 | singleRun: false,
30 | customLaunchers: {
31 | ChromeHeadlessCI: {
32 | base: 'ChromeHeadless',
33 | flags: ['--no-sandbox']
34 | }
35 | }
36 | });
37 | };
38 |
--------------------------------------------------------------------------------
/packages/planet/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3 | "dest": "../../dist/planet",
4 | "lib": {
5 | "entryFile": "src/public-api.ts"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/packages/planet/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@worktile/planet",
3 | "version": "19.0.0",
4 | "private": false,
5 | "peerDependencies": {
6 | "@angular/common": "^19.0.0",
7 | "@angular/core": "^19.0.0"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/packages/planet/src/application/planet-application-ref.ts:
--------------------------------------------------------------------------------
1 | import { Observable } from 'rxjs';
2 | import { PlanetPortalApplication } from './portal-application';
3 |
4 | export interface PlanetApplicationRef {
5 | template?: string;
6 | get bootstrapped(): boolean;
7 | get selector(): string;
8 | bootstrap(app: PlanetPortalApplication): Observable;
9 | navigateByUrl(url: string): void;
10 | getCurrentRouterStateUrl(): string;
11 | destroy(): void;
12 | }
13 |
--------------------------------------------------------------------------------
/packages/planet/src/application/planet-application.service.ts:
--------------------------------------------------------------------------------
1 | import { PlanetApplication } from '../planet.class';
2 | import { Injectable } from '@angular/core';
3 | import { HttpClient } from '@angular/common/http';
4 | import { shareReplay, map } from 'rxjs/operators';
5 | import { coerceArray } from '../helpers';
6 | import { Observable } from 'rxjs';
7 | import { AssetsLoader } from '../assets-loader';
8 | import { getApplicationService } from '../global-planet';
9 |
10 | @Injectable({
11 | providedIn: 'root'
12 | })
13 | export class PlanetApplicationService {
14 | private apps: PlanetApplication[] = [];
15 |
16 | private appsMap: { [key: string]: PlanetApplication } = {};
17 |
18 | constructor(
19 | private http: HttpClient,
20 | private assetsLoader: AssetsLoader
21 | ) {
22 | if (getApplicationService()) {
23 | throw new Error('PlanetApplicationService has been injected in the portal, repeated injection is not allowed');
24 | }
25 | }
26 |
27 | register(appOrApps: PlanetApplication | PlanetApplication[]) {
28 | const apps = coerceArray(appOrApps);
29 | apps.forEach(app => {
30 | if (this.appsMap[app.name]) {
31 | throw new Error(`${app.name} has be registered.`);
32 | }
33 | this.apps.push(app);
34 | this.appsMap[app.name] = app;
35 | });
36 | }
37 |
38 | registerByUrl(url: string): Observable {
39 | const result = this.http.get(`${url}`).pipe(
40 | map(apps => {
41 | if (apps && Array.isArray(apps)) {
42 | this.register(apps);
43 | } else {
44 | this.register(apps as PlanetApplication);
45 | }
46 | }),
47 | shareReplay()
48 | );
49 | result.subscribe();
50 | return result;
51 | }
52 |
53 | unregister(name: string) {
54 | if (this.appsMap[name]) {
55 | delete this.appsMap[name];
56 | this.apps = this.apps.filter(app => {
57 | return app.name !== name;
58 | });
59 | }
60 | }
61 |
62 | getAppsByMatchedUrl(url: string): PlanetApplication[] {
63 | return this.getApps().filter(app => {
64 | if (app.routerPathPrefix instanceof RegExp) {
65 | return app.routerPathPrefix.test(url);
66 | } else {
67 | return (
68 | url === app.routerPathPrefix ||
69 | (url.startsWith(app.routerPathPrefix) && ['/', '?'].includes(url[app.routerPathPrefix.length]))
70 | );
71 | }
72 | });
73 | }
74 |
75 | getAppByName(name: string) {
76 | return this.appsMap[name];
77 | }
78 |
79 | getAppsToPreload(excludeAppNames?: string[]) {
80 | return this.getApps().filter(app => {
81 | if (excludeAppNames) {
82 | return app.preload && !excludeAppNames.includes(app.name);
83 | } else {
84 | return app.preload;
85 | }
86 | });
87 | }
88 |
89 | getApps() {
90 | return this.apps;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/packages/planet/src/application/portal-application.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 | import { Router } from '@angular/router';
3 | import { PlanetPortalApplication } from './portal-application';
4 | import { NgZone, ApplicationRef } from '@angular/core';
5 | import { RouterTestingModule } from '@angular/router/testing';
6 |
7 | describe('PlanetPortalApplication', () => {
8 | let router: Router;
9 | let planetPortalApplication: PlanetPortalApplication;
10 | beforeEach(() => {
11 | TestBed.configureTestingModule({
12 | imports: [RouterTestingModule.withRoutes([])]
13 | });
14 | router = TestBed.inject(Router);
15 | planetPortalApplication = new PlanetPortalApplication();
16 | planetPortalApplication.ngZone = TestBed.inject(NgZone);
17 | planetPortalApplication.applicationRef = TestBed.inject(ApplicationRef);
18 | planetPortalApplication.router = TestBed.inject(Router);
19 | });
20 |
21 | it(`should create PlanetPortalApplication`, () => {
22 | expect(planetPortalApplication).toBeTruthy();
23 | });
24 |
25 | it(`should run function`, () => {
26 | const spy = jasmine.createSpy('spy');
27 | expect(spy).not.toHaveBeenCalled();
28 | planetPortalApplication.run(spy);
29 | expect(spy).toHaveBeenCalled();
30 | });
31 |
32 | it(`should run function in ngZone`, () => {
33 | const ngZoneRunSpy = spyOn(planetPortalApplication.ngZone, 'run');
34 | expect(ngZoneRunSpy).not.toHaveBeenCalled();
35 | planetPortalApplication.run(() => {});
36 | expect(ngZoneRunSpy).toHaveBeenCalled();
37 | });
38 |
39 | it(`should run tick`, () => {
40 | const tickSpy = spyOn(planetPortalApplication.applicationRef, 'tick');
41 | expect(tickSpy).not.toHaveBeenCalled();
42 | planetPortalApplication.tick();
43 | expect(tickSpy).toHaveBeenCalled();
44 | });
45 |
46 | it(`should run navigateByUrl`, () => {
47 | const navigateByUrlSpy = spyOn(planetPortalApplication.router, 'navigateByUrl');
48 | expect(navigateByUrlSpy).not.toHaveBeenCalled();
49 | planetPortalApplication.navigateByUrl('/app1/dashboard', { skipLocationChange: true });
50 | expect(navigateByUrlSpy).toHaveBeenCalled();
51 | expect(navigateByUrlSpy).toHaveBeenCalledWith('/app1/dashboard', { skipLocationChange: true });
52 | });
53 |
54 | it(`should run registerComponentFactory & getComponentFactory`, () => {
55 | const spy = jasmine.createSpy('spy');
56 | expect(spy).not.toHaveBeenCalled();
57 | planetPortalApplication.registerComponentFactory(spy);
58 | const componentFactory = planetPortalApplication.getComponentFactory();
59 | componentFactory('', {} as any);
60 | expect(spy).toHaveBeenCalled();
61 | });
62 | });
63 |
--------------------------------------------------------------------------------
/packages/planet/src/application/portal-application.ts:
--------------------------------------------------------------------------------
1 | import { Router, UrlTree, NavigationExtras } from '@angular/router';
2 | import { NgZone, Injector, ApplicationRef } from '@angular/core';
3 | import { PlantComponentFactory } from '../component/planet-component-types';
4 | import { GlobalEventDispatcher } from '../global-event-dispatcher';
5 |
6 | export class PlanetPortalApplication {
7 | applicationRef?: ApplicationRef;
8 | injector?: Injector;
9 | router?: Router;
10 | ngZone?: NgZone;
11 | globalEventDispatcher?: GlobalEventDispatcher;
12 | data?: TData;
13 | name?: string = 'portal';
14 | private componentFactory?: PlantComponentFactory;
15 |
16 | navigateByUrl(url: string | UrlTree, extras?: NavigationExtras): Promise {
17 | return this.ngZone!.run(() => {
18 | return this.router!.navigateByUrl(url, extras);
19 | });
20 | }
21 |
22 | run(fn: (...args: any[]) => T): T {
23 | return this.ngZone!.run(() => {
24 | return fn();
25 | });
26 | }
27 |
28 | tick() {
29 | this.applicationRef!.tick();
30 | }
31 |
32 | getComponentFactory() {
33 | return this.componentFactory;
34 | }
35 |
36 | registerComponentFactory(componentFactory: PlantComponentFactory) {
37 | this.componentFactory = componentFactory;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/packages/planet/src/component/planet-component-outlet.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Directive,
3 | ViewContainerRef,
4 | OnDestroy,
5 | OnChanges,
6 | SimpleChanges,
7 | Input,
8 | AfterViewInit,
9 | ElementRef,
10 | NgZone,
11 | Output,
12 | EventEmitter
13 | } from '@angular/core';
14 | import { Subject } from 'rxjs';
15 | import { takeUntil } from 'rxjs/operators';
16 | import { PlanetComponentLoader } from './planet-component-loader';
17 | import { PlanetComponentRef } from './planet-component-types';
18 | import { PlantComponentProjectableNode } from './plant-component.config';
19 |
20 | @Directive({
21 | selector: '[planetComponentOutlet]',
22 | standalone: true
23 | })
24 | // eslint-disable-next-line @angular-eslint/directive-class-suffix
25 | export class PlanetComponentOutlet implements OnChanges, OnDestroy, AfterViewInit {
26 | @Input() planetComponentOutlet: string;
27 |
28 | @Input() planetComponentOutletApp: string;
29 |
30 | @Input() planetComponentOutletProjectableNodes: PlantComponentProjectableNode[];
31 |
32 | @Input() planetComponentOutletInitialState: any;
33 |
34 | @Output() planetComponentLoaded = new EventEmitter();
35 |
36 | private componentRef: PlanetComponentRef;
37 |
38 | private destroyed$ = new Subject();
39 |
40 | constructor(
41 | private elementRef: ElementRef,
42 | private planetComponentLoader: PlanetComponentLoader,
43 | private ngZone: NgZone
44 | ) {}
45 |
46 | ngOnChanges(changes: SimpleChanges) {
47 | if (this.planetComponentOutlet && !changes['planetComponentOutlet'].isFirstChange()) {
48 | this.loadComponent();
49 | }
50 | }
51 |
52 | ngAfterViewInit(): void {
53 | this.loadComponent();
54 | }
55 |
56 | loadComponent() {
57 | this.clear();
58 | if (this.planetComponentOutlet && this.planetComponentOutletApp) {
59 | this.planetComponentLoader
60 | .load(this.planetComponentOutletApp, this.planetComponentOutlet, {
61 | container: this.elementRef.nativeElement,
62 | initialState: this.planetComponentOutletInitialState,
63 | projectableNodes: this.planetComponentOutletProjectableNodes
64 | })
65 | .pipe(takeUntil(this.destroyed$))
66 | .subscribe(componentRef => {
67 | this.componentRef = componentRef;
68 | this.ngZone.run(() => {
69 | Promise.resolve().then(() => {
70 | this.planetComponentLoaded.emit(this.componentRef);
71 | });
72 | });
73 | });
74 | }
75 | }
76 |
77 | ngOnDestroy(): void {
78 | this.clear();
79 | this.destroyed$.complete();
80 | }
81 |
82 | clear() {
83 | this.componentRef?.dispose();
84 | this.destroyed$.next();
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/packages/planet/src/component/planet-component-types.ts:
--------------------------------------------------------------------------------
1 | import { ComponentRef } from '@angular/core';
2 | import { PlantComponentConfig } from './plant-component.config';
3 |
4 | export class PlanetComponentRef {
5 | wrapperElement: HTMLElement;
6 | hostElement: HTMLElement;
7 | componentInstance: TComp;
8 | componentRef: ComponentRef;
9 | dispose: () => void;
10 | }
11 |
12 | export type PlantComponentFactory = (componentName: string, config: PlantComponentConfig) => PlanetComponentRef;
13 |
--------------------------------------------------------------------------------
/packages/planet/src/component/plant-component.config.ts:
--------------------------------------------------------------------------------
1 | import { TemplateRef, ElementRef } from '@angular/core';
2 |
3 | export type PlantComponentProjectableNode = Node[] | TemplateRef;
4 |
5 | export class PlantComponentConfig {
6 | /** Load target container */
7 | container: HTMLElement | ElementRef | Comment;
8 | /**
9 | * Wrapper class of plant component
10 | * @deprecated please use hostClass
11 | */
12 | wrapperClass?: string;
13 | /**
14 | * Host class of plant component
15 | */
16 | hostClass?: string;
17 | /** Data being injected into the child component. */
18 | initialState?: TData | null = null;
19 | /**
20 | * Projectable nodes
21 | */
22 | projectableNodes?: PlantComponentProjectableNode[];
23 | }
24 |
--------------------------------------------------------------------------------
/packages/planet/src/debug.spec.ts:
--------------------------------------------------------------------------------
1 | import { createDebug, clearDebugFactory, setDebugFactory, getDebugFactory, Debug, Debugger } from './debug';
2 |
3 | describe('debug', () => {
4 | afterEach(() => {
5 | clearDebugFactory();
6 | });
7 |
8 | it('should bypass create debug and log message', () => {
9 | const debug = createDebug('app-loader');
10 | debug('this is debug message');
11 | expect(true).toBe(true);
12 | });
13 |
14 | it('should set debug factory success', () => {
15 | const mockDebugFactory = function(namespace: string): Debugger {
16 | return undefined;
17 | } as Debug;
18 | setDebugFactory(mockDebugFactory);
19 | const debugFactory = getDebugFactory();
20 | expect(debugFactory).toBe(mockDebugFactory);
21 | });
22 |
23 | it('should set debug factory fail', () => {
24 | const mockDebugFactory = {} as Debug;
25 | expect(() => {
26 | setDebugFactory(mockDebugFactory);
27 | }).toThrowError('debug factory type is invalid, must be function');
28 | });
29 |
30 | it('should use custom debug factory to debug', () => {
31 | const mockDebug = jasmine.createSpy('mock debug');
32 | const mockDebugFactory = jasmine.createSpy('mock debug factory').and.callFake(namespace => {
33 | expect(namespace).toEqual('planet:app-loader');
34 | return mockDebug;
35 | });
36 | setDebugFactory(mockDebugFactory as any);
37 | const debug = createDebug('app-loader');
38 | expect(mockDebug).not.toHaveBeenCalled();
39 | debug('this is debug message');
40 | expect(mockDebug).toHaveBeenCalled();
41 |
42 | expect(mockDebug).toHaveBeenCalledWith('this is debug message', []);
43 | });
44 |
45 | it('should use cache debug for same namespace', () => {
46 | const mockDebugFactory = jasmine.createSpy('mock debug factory').and.callFake(namespace => {
47 | expect(namespace).toEqual('planet:app-loader');
48 | return jasmine.createSpy('mock debug');
49 | });
50 | setDebugFactory(mockDebugFactory as any);
51 | expect(mockDebugFactory).toHaveBeenCalledTimes(0);
52 | const debug1 = createDebug('app-loader');
53 | const debug2 = createDebug('app-loader');
54 | debug1('message1');
55 | debug2('message2');
56 | expect(mockDebugFactory).toHaveBeenCalledTimes(1);
57 | });
58 | });
59 |
--------------------------------------------------------------------------------
/packages/planet/src/debug.ts:
--------------------------------------------------------------------------------
1 | import { isFunction } from './helpers';
2 |
3 | export interface Debugger {
4 | (formatter: any, ...args: any[]): void;
5 |
6 | // color: string;
7 | // enabled: boolean;
8 | // log: (...args: any[]) => any;
9 | // namespace: string;
10 | // destroy: () => boolean;
11 | // extend: (namespace: string, delimiter?: string) => Debugger;
12 | }
13 |
14 | export interface Formatters {
15 | [formatter: string]: (v: any) => string;
16 | }
17 |
18 | export interface Debug {
19 | (namespace: string): Debugger;
20 | coerce: (val: any) => any;
21 | disable: () => string;
22 | enable: (namespaces: string) => void;
23 | enabled: (namespaces: string) => boolean;
24 | log: (...args: any[]) => any;
25 |
26 | names: RegExp[];
27 | skips: RegExp[];
28 |
29 | formatters: Formatters;
30 | }
31 |
32 | /**
33 | * Debug factory for debug module
34 | */
35 | let _debugFactory: Debug | undefined;
36 | let _debuggerMap: Record = {};
37 |
38 | export function createDebug(namespace: string): Debugger {
39 | const key = `planet:${namespace}`;
40 | return function(formatter: any, ...args: any[]) {
41 | if (_debugFactory) {
42 | let debugDebugger = _debuggerMap[key];
43 | if (!debugDebugger) {
44 | debugDebugger = _debugFactory(key);
45 | _debuggerMap[key] = debugDebugger;
46 | }
47 | debugDebugger(formatter, args);
48 | }
49 | };
50 | }
51 |
52 | export function setDebugFactory(debug: Debug) {
53 | if (debug && !isFunction(debug)) {
54 | throw new Error('debug factory type is invalid, must be function');
55 | }
56 | _debugFactory = debug;
57 | }
58 |
59 | export function clearDebugFactory() {
60 | _debugFactory = undefined;
61 | _debuggerMap = {};
62 | }
63 |
64 | export function getDebugFactory() {
65 | return _debugFactory;
66 | }
67 |
--------------------------------------------------------------------------------
/packages/planet/src/empty/empty.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed, ComponentFixture } from '@angular/core/testing';
2 | import { NgxPlanetModule } from '../module';
3 | import { Component } from '@angular/core';
4 | import { By } from '@angular/platform-browser';
5 | import { EmptyComponent } from './empty.component';
6 |
7 | @Component({
8 | // eslint-disable-next-line @angular-eslint/component-selector
9 | selector: 'empty-component-basic',
10 | template: '',
11 | standalone: false
12 | })
13 | class EmptyComponentBasicComponent {}
14 |
15 | describe('empty-component', () => {
16 | let fixture: ComponentFixture;
17 | beforeEach(() => {
18 | TestBed.configureTestingModule({
19 | declarations: [EmptyComponentBasicComponent],
20 | imports: [NgxPlanetModule]
21 | });
22 | fixture = TestBed.createComponent(EmptyComponentBasicComponent);
23 | TestBed.compileComponents();
24 | });
25 |
26 | it(`should create empty component`, () => {
27 | const emptyComponentDebugElement = fixture.debugElement.query(By.directive(EmptyComponent));
28 | expect(emptyComponentDebugElement).toBeTruthy();
29 | expect(emptyComponentDebugElement.componentInstance).toBeTruthy();
30 | });
31 |
32 | it(`should content is empty in empty component`, () => {
33 | const emptyComponentDebugElement = fixture.debugElement.query(By.directive(EmptyComponent));
34 | expect((emptyComponentDebugElement.nativeElement as HTMLElement).children.length).toBe(0);
35 | });
36 | });
37 |
--------------------------------------------------------------------------------
/packages/planet/src/empty/empty.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, ChangeDetectionStrategy } from '@angular/core';
2 |
3 | @Component({
4 | // eslint-disable-next-line @angular-eslint/component-selector
5 | selector: 'empty-component',
6 | template: ``,
7 | changeDetection: ChangeDetectionStrategy.OnPush,
8 | standalone: true
9 | })
10 | export class EmptyComponent {}
11 |
--------------------------------------------------------------------------------
/packages/planet/src/global-event-dispatcher.ts:
--------------------------------------------------------------------------------
1 | import { Subject, Observable } from 'rxjs';
2 | import { filter, map } from 'rxjs/operators';
3 | import { Injectable, NgZone } from '@angular/core';
4 |
5 | export interface GlobalDispatcherEvent {
6 | name: string;
7 | payload: any;
8 | }
9 |
10 | const CUSTOM_EVENT_NAME = 'PLANET_GLOBAL_EVENT_DISPATCHER';
11 |
12 | @Injectable({
13 | providedIn: 'root'
14 | })
15 | export class GlobalEventDispatcher {
16 | private subject$: Subject = new Subject();
17 |
18 | private hasAddGlobalEventListener = false;
19 |
20 | private subscriptionCount = 0;
21 |
22 | private globalEventListener = (event: CustomEvent) => {
23 | this.subject$.next(event.detail);
24 | };
25 |
26 | private addGlobalEventListener() {
27 | this.hasAddGlobalEventListener = true;
28 | window.addEventListener(CUSTOM_EVENT_NAME, this.globalEventListener);
29 | }
30 |
31 | private removeGlobalEventListener() {
32 | this.hasAddGlobalEventListener = false;
33 | window.removeEventListener(CUSTOM_EVENT_NAME, this.globalEventListener);
34 | }
35 |
36 | constructor(private ngZone: NgZone) {}
37 |
38 | dispatch(name: string, payload?: TPayload) {
39 | window.dispatchEvent(
40 | new CustomEvent(CUSTOM_EVENT_NAME, {
41 | detail: {
42 | name: name,
43 | payload: payload
44 | }
45 | })
46 | );
47 | }
48 |
49 | register(eventName: string): Observable {
50 | return new Observable(observer => {
51 | if (!this.hasAddGlobalEventListener) {
52 | this.addGlobalEventListener();
53 | }
54 | this.subscriptionCount++;
55 | const subscription = this.subject$
56 | .pipe(
57 | filter(event => {
58 | return event.name === eventName;
59 | }),
60 | map(event => {
61 | return event.payload;
62 | })
63 | )
64 | .subscribe(payload => {
65 | this.ngZone.run(() => {
66 | observer.next(payload);
67 | });
68 | });
69 | return () => {
70 | this.subscriptionCount--;
71 | subscription.unsubscribe();
72 | if (!this.subscriptionCount) {
73 | this.removeGlobalEventListener();
74 | }
75 | };
76 | });
77 | }
78 |
79 | getSubscriptionCount() {
80 | return this.subscriptionCount;
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/packages/planet/src/global-planet.spec.ts:
--------------------------------------------------------------------------------
1 | import { defineApplication, clearGlobalPlanet } from './global-planet';
2 | import { PlanetPortalApplication } from './application/portal-application';
3 |
4 | describe('defineApplication', () => {
5 | afterEach(() => {
6 | clearGlobalPlanet();
7 | });
8 |
9 | it('should define application success', () => {
10 | defineApplication('app1', {
11 | template: '',
12 | bootstrap: (portalApp?: PlanetPortalApplication) => {
13 | return new Promise(() => {});
14 | }
15 | });
16 | expect(window['planet'].apps['app1']).toBeTruthy();
17 | });
18 |
19 | it('should define application compatible success', () => {
20 | defineApplication('app1', (portalApp?: PlanetPortalApplication) => {
21 | return new Promise(() => {});
22 | });
23 | expect(window['planet'].apps['app1']).toBeTruthy();
24 | });
25 |
26 | it('should throw error when define application has exist', () => {
27 | defineApplication('app1', {
28 | template: '',
29 | bootstrap: (portalApp?: PlanetPortalApplication) => {
30 | return new Promise(() => {});
31 | }
32 | });
33 | expect(() => {
34 | defineApplication('app1', (portalApp?: PlanetPortalApplication) => {
35 | return new Promise(() => {});
36 | });
37 | }).toThrowError('app1 application has exist.');
38 | });
39 | });
40 |
--------------------------------------------------------------------------------
/packages/planet/src/global-planet.ts:
--------------------------------------------------------------------------------
1 | import { PlanetApplicationRef } from './application/planet-application-ref';
2 | import { PlanetPortalApplication } from './application/portal-application';
3 | import { PlanetApplicationLoader } from './application/planet-application-loader';
4 | import { PlanetApplicationService } from './application/planet-application.service';
5 | import { isFunction } from './helpers';
6 | import { NgBootstrapAppModule, NgBootstrapOptions, NgPlanetApplicationRef } from './application/ng-planet-application-ref';
7 |
8 | declare const window: any;
9 |
10 | export interface GlobalPlanet {
11 | apps: { [key: string]: PlanetApplicationRef };
12 | portalApplication?: PlanetPortalApplication;
13 | applicationLoader: PlanetApplicationLoader;
14 | applicationService: PlanetApplicationService;
15 | }
16 |
17 | export const globalPlanet: GlobalPlanet = (window.planet = window.planet || {
18 | apps: {}
19 | });
20 |
21 | export function defineApplication(name: string, options: TOptions) {
22 | if (globalPlanet.apps[name]) {
23 | throw new Error(`${name} application has exist.`);
24 | }
25 | if (isFunction(options)) {
26 | options = {
27 | template: '',
28 | bootstrap: options as NgBootstrapAppModule
29 | } as TOptions;
30 | }
31 | const appRef = new NgPlanetApplicationRef(name, options as NgBootstrapOptions);
32 | globalPlanet.apps[name] = appRef;
33 | }
34 |
35 | export function getPlanetApplicationRef(appName: string): PlanetApplicationRef | null {
36 | if (globalPlanet && globalPlanet.apps && globalPlanet.apps[appName]) {
37 | return globalPlanet.apps[appName];
38 | } else {
39 | return null;
40 | }
41 | }
42 |
43 | export function getBootstrappedPlanetApplicationRef(appName: string): PlanetApplicationRef | null {
44 | const plantAppRef = getPlanetApplicationRef(appName);
45 | if (plantAppRef) {
46 | // 兼容之前的版本,之前是通过 appModuleRef 来判断是否启用的
47 | if (plantAppRef.bootstrapped || plantAppRef['appModuleRef']) {
48 | return plantAppRef;
49 | }
50 | }
51 | return null;
52 | }
53 |
54 | export function setPortalApplicationData(data: T) {
55 | if (globalPlanet.portalApplication) {
56 | globalPlanet.portalApplication.data = data;
57 | }
58 | }
59 |
60 | export function getPortalApplicationData(): TData {
61 | return globalPlanet.portalApplication?.data as TData;
62 | }
63 |
64 | export function setApplicationLoader(loader: PlanetApplicationLoader) {
65 | globalPlanet.applicationLoader = loader;
66 | }
67 |
68 | export function getApplicationLoader() {
69 | return globalPlanet.applicationLoader;
70 | }
71 |
72 | export function setApplicationService(service: PlanetApplicationService) {
73 | globalPlanet.applicationService = service;
74 | }
75 |
76 | export function getApplicationService() {
77 | return globalPlanet.applicationService;
78 | }
79 |
80 | export function clearGlobalPlanet() {
81 | window.planet.apps = {};
82 | window.planet.portalApplication = null;
83 | window.planet.applicationLoader = null;
84 | window.planet.applicationService = null;
85 | }
86 |
--------------------------------------------------------------------------------
/packages/planet/src/inner-types.ts:
--------------------------------------------------------------------------------
1 | export interface ScriptTagAttributes {
2 | type?: 'module' | 'text/script' | undefined;
3 | async?: string;
4 | defer?: string;
5 | }
6 |
7 | export interface LinkTagAttributes {
8 | rel?: string;
9 | }
10 |
11 | export type ScriptOrLinkTagAttributes = ScriptTagAttributes | LinkTagAttributes;
12 |
13 | export interface AssetsTagItem {
14 | src: string;
15 | tagName?: 'link' | 'script';
16 | attributes?: ScriptOrLinkTagAttributes;
17 | }
18 |
--------------------------------------------------------------------------------
/packages/planet/src/module.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 | import { NgxPlanetModule } from './module';
3 | import { NgModule } from '@angular/core';
4 | import { Planet } from './planet';
5 | import { clearGlobalPlanet } from './global-planet';
6 | import { PlanetApplicationService } from './application/planet-application.service';
7 | import { RouterTestingModule } from '@angular/router/testing';
8 |
9 | const app1 = {
10 | name: 'app1',
11 | hostParent: '.host-selector',
12 | selector: 'app1-root',
13 | routerPathPrefix: '/app1',
14 | hostClass: 'app1-host',
15 | preload: false,
16 | resourcePathPrefix: '/static/app1/',
17 | styles: ['styles/main.css'],
18 | scripts: ['vendor.js', 'main.js'],
19 | loadSerial: false,
20 | manifest: '',
21 | extra: {
22 | appName: '应用1'
23 | }
24 | };
25 |
26 | @NgModule({
27 | imports: [NgxPlanetModule, RouterTestingModule.withRoutes([])]
28 | })
29 | class AppModule {}
30 |
31 | @NgModule({
32 | imports: [NgxPlanetModule.forRoot([app1]), RouterTestingModule.withRoutes([])]
33 | })
34 | class AppModuleWithApps {}
35 |
36 | describe('NgxPlanetModule', () => {
37 | afterEach(() => {
38 | clearGlobalPlanet();
39 | const applicationService = TestBed.inject(PlanetApplicationService);
40 | applicationService['apps'] = [];
41 | applicationService['appsMap'] = {};
42 | });
43 |
44 | describe('basic', () => {
45 | beforeEach(() => {
46 | TestBed.configureTestingModule({
47 | imports: [AppModule]
48 | });
49 | });
50 |
51 | it('should get NgxPlanetModule', () => {
52 | expect(TestBed.inject(NgxPlanetModule)).toBeTruthy();
53 | });
54 |
55 | it('should get planet', () => {
56 | const planet: Planet = TestBed.inject(Planet);
57 | expect(planet).toBeTruthy();
58 | expect(planet.getApps()).toEqual([]);
59 | });
60 | });
61 |
62 | describe('forRoot', () => {
63 | beforeEach(() => {
64 | TestBed.configureTestingModule({
65 | imports: [AppModuleWithApps]
66 | });
67 | });
68 |
69 | it('should get NgxPlanetModule', () => {
70 | expect(TestBed.inject(NgxPlanetModule)).toBeTruthy();
71 | });
72 |
73 | it('should register app1 when use forRoot', () => {
74 | const planet: Planet = TestBed.inject(Planet);
75 | expect(planet).toBeTruthy();
76 | expect(planet.getApps()).toEqual([app1]);
77 | });
78 | });
79 | });
80 |
--------------------------------------------------------------------------------
/packages/planet/src/module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule, ModuleWithProviders } from '@angular/core';
2 | import { PlanetApplication, PLANET_APPLICATIONS } from './planet.class';
3 | import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
4 | import { EmptyComponent } from './empty/empty.component';
5 | import { PlanetComponentOutlet } from './component/planet-component-outlet';
6 | import { RedirectToRouteComponent } from './router/route-redirect';
7 |
8 | @NgModule({
9 | declarations: [],
10 | exports: [EmptyComponent, PlanetComponentOutlet],
11 | imports: [PlanetComponentOutlet, EmptyComponent, RedirectToRouteComponent],
12 | providers: [provideHttpClient(withInterceptorsFromDi())]
13 | })
14 | export class NgxPlanetModule {
15 | static forRoot(apps: PlanetApplication[]): ModuleWithProviders {
16 | return {
17 | ngModule: NgxPlanetModule,
18 | providers: [
19 | {
20 | provide: PLANET_APPLICATIONS,
21 | useValue: apps
22 | }
23 | ]
24 | };
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/packages/planet/src/public-api.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Public API Surface of core
3 | */
4 |
5 | export * from './planet';
6 | export * from './global-event-dispatcher';
7 | export * from './application/portal-application';
8 | export * from './planet.class';
9 | export * from './module';
10 | export { PlanetApplicationRef } from './application/planet-application-ref';
11 | export { defineApplication, getPortalApplicationData, getPlanetApplicationRef } from './global-planet';
12 | export { PlanetApplicationService } from './application/planet-application.service';
13 | export * from './application/planet-application-loader';
14 | export * from './assets-loader';
15 | export { PlanetComponent, PlanetComponentLoader } from './component/planet-component-loader';
16 | export { PlanetComponentRef } from './component/planet-component-types';
17 | export { PlanetComponentOutlet } from './component/planet-component-outlet';
18 | export { PlantComponentConfig } from './component/plant-component.config';
19 | export * from './empty/empty.component';
20 | export * from './router/route-redirect';
21 |
--------------------------------------------------------------------------------
/packages/planet/src/router/route-redirect.spec.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { TestBed } from '@angular/core/testing';
3 | import { RouterOutlet, provideRouter } from '@angular/router';
4 | import { RouterTestingHarness } from '@angular/router/testing';
5 | import { routeRedirect, redirectToRoute } from './route-redirect';
6 |
7 | describe('route-redirect', () => {
8 | it('should redirect to success use redirectToRoute', async () => {
9 | @Component({
10 | template: '',
11 | imports: [RouterOutlet]
12 | })
13 | class TestComponent {}
14 |
15 | @Component({ standalone: true, template: 'hello world' })
16 | class HelloComponent {}
17 |
18 | TestBed.configureTestingModule({
19 | providers: [
20 | provideRouter([
21 | {
22 | path: 'app1',
23 | component: TestComponent,
24 | children: [redirectToRoute('hello'), { path: 'hello', component: HelloComponent }]
25 | }
26 | ])
27 | ]
28 | });
29 |
30 | const harness = await RouterTestingHarness.create();
31 | const activatedComponent = await harness.navigateByUrl('/app1');
32 | expect(activatedComponent).toBeInstanceOf(TestComponent);
33 | expect(harness.routeNativeElement?.innerHTML).toContain('hello world');
34 | });
35 |
36 | it('should redirect to success use routeRedirect', async () => {
37 | @Component({ standalone: true, template: '' })
38 | class TestComponent {
39 | routeRedirect = routeRedirect('hello');
40 | }
41 |
42 | @Component({ standalone: true, template: 'hello world' })
43 | class HelloComponent {}
44 |
45 | TestBed.configureTestingModule({
46 | providers: [
47 | provideRouter([
48 | { path: '', component: TestComponent },
49 | { path: 'hello', component: HelloComponent }
50 | ])
51 | ]
52 | });
53 |
54 | const harness = await RouterTestingHarness.create();
55 | const activatedComponent = await harness.navigateByUrl('/');
56 | expect(activatedComponent).toBeInstanceOf(HelloComponent);
57 | expect(harness.routeNativeElement?.innerHTML).toContain('hello world');
58 | });
59 | });
60 |
--------------------------------------------------------------------------------
/packages/planet/src/router/route-redirect.ts:
--------------------------------------------------------------------------------
1 | import { Component, inject } from '@angular/core';
2 | import { ActivatedRoute, Route, Router, UrlTree } from '@angular/router';
3 |
4 | class RouteRedirect {
5 | activatedRoute = inject(ActivatedRoute);
6 | router = inject(Router);
7 |
8 | constructor(redirectTo: string) {
9 | const finalRedirectTo = redirectTo || this.activatedRoute.snapshot.data['redirectTo'];
10 | if (finalRedirectTo) {
11 | const activatedRouteUrl = this.activatedRoute.pathFromRoot
12 | .filter(route => {
13 | return route.snapshot.url?.length > 0;
14 | })
15 | .map(route => {
16 | return route.snapshot.url.join('/');
17 | })
18 | .join('/');
19 | if (
20 | this.router.isActive(activatedRouteUrl, {
21 | matrixParams: 'exact',
22 | paths: 'exact',
23 | queryParams: 'exact',
24 | fragment: 'exact'
25 | })
26 | ) {
27 | this.router.navigate(
28 | [`${finalRedirectTo}`],
29 | // By replacing the current URL in the history, we keep the Browser's Back
30 | // Button behavior in tact. This will allow the user to easily navigate back
31 | // to the previous URL without getting caught in a redirect.
32 | {
33 | replaceUrl: true,
34 | relativeTo: this.activatedRoute
35 | }
36 | );
37 | }
38 | }
39 | }
40 | }
41 |
42 | export function routeRedirect(redirectTo?: string) {
43 | return new RouteRedirect(redirectTo);
44 | }
45 |
46 | @Component({
47 | selector: 'planet-redirect-to-route',
48 | template: '',
49 | standalone: true
50 | })
51 | export class RedirectToRouteComponent {
52 | routeRedirect = routeRedirect();
53 | }
54 |
55 | export function redirectToRoute(redirectTo: string): Route {
56 | return {
57 | path: '',
58 | component: RedirectToRouteComponent,
59 | data: {
60 | redirectTo: redirectTo
61 | }
62 | };
63 | }
64 |
--------------------------------------------------------------------------------
/packages/planet/src/sandbox/constants.ts:
--------------------------------------------------------------------------------
1 | export const PLANET_SANDBOX_WINDOW_WHITELIST = window['___PLANET_SANDBOX_WINDOW_WHITELIST___'];
2 | export const PLANET_SANDBOX_DOCUMENT_WHITELIST = window['___PLANET_SANDBOX_DOCUMENT_WHITELIST___'];
3 | export const SANDBOX_INSTANCE = Symbol.for('PLANET_SANDBOX_INSTANCE');
4 | export const RAW_NODE = Symbol.for('PLANET_RAW_NODE');
5 | export const WINDOW_BIND_FN = Symbol.for('PLANET_WINDOW_BIND_FN');
6 | export const DOCUMENT_BIND_FN = Symbol.for('PLANET_DOCUMENT_BIND_FN');
7 |
--------------------------------------------------------------------------------
/packages/planet/src/sandbox/exec.ts:
--------------------------------------------------------------------------------
1 | import { Global } from './types';
2 |
3 | export function execScript(code: string, url: string, global: Global, strictGlobal: boolean) {
4 | const sourceUrl = '//# sourceURL='.concat(url, '\n');
5 | const window = (0, eval)('window');
6 | window.tempGlobal = global;
7 | code = strictGlobal
8 | ? ';(function(window, self, globalThis){with(window){;'
9 | .concat(code, '\n')
10 | .concat(
11 | sourceUrl,
12 | '}}).bind(window.tempGlobal)(window.tempGlobal, window.tempGlobal, window.tempGlobal);'
13 | )
14 | : ';(function(window, self, globalThis){;'
15 | .concat(code, '\n')
16 | .concat(
17 | sourceUrl,
18 | '}).bind(window.tempGlobal)(window.tempGlobal, window.tempGlobal, window.tempGlobal);'
19 | );
20 | (0, eval)(code);
21 | delete window.tempGlobal;
22 | }
23 |
--------------------------------------------------------------------------------
/packages/planet/src/sandbox/index.ts:
--------------------------------------------------------------------------------
1 | import { SandboxOptions } from './sandbox';
2 | import { ProxySandbox } from './proxy/proxy-sandbox';
3 | import { SnapshotSandbox } from './snapshot/snapshot-sandbox';
4 | import { SANDBOX_INSTANCE } from './constants';
5 |
6 | export { Sandbox } from './sandbox';
7 |
8 | const defaultOptions: Partial = {
9 | strictGlobal: false
10 | };
11 |
12 | export function createSandbox(app: string, options?: SandboxOptions) {
13 | options = Object.assign({}, defaultOptions, options || {});
14 |
15 | if (window.Proxy) {
16 | return new ProxySandbox(app, options);
17 | } else {
18 | return new SnapshotSandbox(app, options);
19 | }
20 | }
21 |
22 | export function getSandboxInstance() {
23 | return window[SANDBOX_INSTANCE];
24 | }
25 |
--------------------------------------------------------------------------------
/packages/planet/src/sandbox/proxy/patches/document.ts:
--------------------------------------------------------------------------------
1 | import { ProxyDocument } from '../proxies/document';
2 | import { SandboxPatchHandler } from '../types';
3 |
4 | export function documentPatch(): SandboxPatchHandler {
5 | const proxyDocument = new ProxyDocument();
6 | const { document, Document } = proxyDocument.create();
7 | return {
8 | rewrite: {
9 | document,
10 | Document
11 | }
12 | };
13 | }
14 |
--------------------------------------------------------------------------------
/packages/planet/src/sandbox/proxy/patches/eventListener.ts:
--------------------------------------------------------------------------------
1 | import { SandboxPatchHandler, ProxySandboxInstance } from '../types';
2 | import { Observable, Subscription } from 'rxjs';
3 |
4 | export function eventListenerPatch(sandbox: ProxySandboxInstance): SandboxPatchHandler {
5 | const rawAddEventListener = window.addEventListener;
6 | const rawRemoveEventListener = window.removeEventListener;
7 | const listenerSubscriptions = new Map();
8 |
9 | function addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions) {
10 | const observable = new Observable(() => {
11 | rawAddEventListener.call(this, type, listener, options);
12 | return () => {
13 | rawRemoveEventListener.call(this, type, listener, options);
14 | };
15 | });
16 | const subscription = observable.subscribe(() => {});
17 | listenerSubscriptions.set(listener, subscription);
18 | }
19 |
20 | function removeEventListener(type: string, listener: EventListenerOrEventListenerObject) {
21 | const subscription = listenerSubscriptions.get(listener);
22 | if (subscription) {
23 | subscription.unsubscribe();
24 | listenerSubscriptions.delete(listener);
25 | }
26 | }
27 |
28 | return {
29 | rewrite: {
30 | addEventListener: addEventListener.bind(window),
31 | removeEventListener: removeEventListener.bind(window)
32 | },
33 | init() {
34 | const fakeDocument = sandbox.global['document'];
35 | if (fakeDocument) {
36 | fakeDocument.addEventListener = addEventListener.bind(document);
37 | fakeDocument.removeEventListener = removeEventListener.bind(document);
38 | }
39 | },
40 | destroy() {
41 | listenerSubscriptions.forEach(subscription => subscription.unsubscribe());
42 | listenerSubscriptions.clear();
43 | }
44 | };
45 | }
46 |
--------------------------------------------------------------------------------
/packages/planet/src/sandbox/proxy/patches/index.ts:
--------------------------------------------------------------------------------
1 | import { ProxySandboxInstance, SandboxPatch, SandboxPatchHandler } from '../types';
2 | import { eventListenerPatch } from './eventListener';
3 | import { documentPatch } from './document';
4 | import { timerPatch } from './timer';
5 | import { storagePatch } from './storage';
6 |
7 | const sandboxPatches: SandboxPatch[] = [documentPatch, eventListenerPatch, timerPatch, storagePatch];
8 |
9 | export function getSandboxPatchHandlers(sandbox: ProxySandboxInstance): SandboxPatchHandler[] {
10 | return sandboxPatches.map(patch => {
11 | return patch(sandbox);
12 | });
13 | }
14 |
--------------------------------------------------------------------------------
/packages/planet/src/sandbox/proxy/patches/storage.ts:
--------------------------------------------------------------------------------
1 | import { ProxySandboxInstance, SandboxPatchHandler } from '../types';
2 |
3 | const PLANET_STORAGE_PREFIX = '__planet-storage-';
4 |
5 | export class RewriteStorage {
6 | prefix: string;
7 | rawStorage: Storage;
8 |
9 | constructor(app: string, rawStorage: Storage) {
10 | this.rawStorage = rawStorage;
11 | this.prefix = `${PLANET_STORAGE_PREFIX}${app}__:`;
12 | }
13 |
14 | get length() {
15 | return this.getKeys().length;
16 | }
17 |
18 | private getKeys() {
19 | return Object.keys(this.rawStorage).filter(key => key.startsWith(this.prefix));
20 | }
21 |
22 | key(n: number) {
23 | const key = this.getKeys()[n];
24 | return key ? key.substring(this.prefix.length) : null;
25 | }
26 |
27 | getItem(key: string) {
28 | return this.rawStorage.getItem(`${this.prefix + key}`);
29 | }
30 |
31 | setItem(key: string, value: string) {
32 | this.rawStorage.setItem(`${this.prefix + key}`, value);
33 | }
34 |
35 | removeItem(key: string) {
36 | this.rawStorage.removeItem(`${this.prefix + key}`);
37 | }
38 |
39 | clear() {
40 | this.getKeys().forEach(key => {
41 | this.rawStorage.removeItem(key);
42 | });
43 | }
44 | }
45 |
46 | export function storagePatch(sandbox: ProxySandboxInstance): SandboxPatchHandler {
47 | return {
48 | rewrite: {
49 | localStorage: new RewriteStorage(sandbox.app, localStorage),
50 | sessionStorage: new RewriteStorage(sandbox.app, sessionStorage)
51 | }
52 | };
53 | }
54 |
--------------------------------------------------------------------------------
/packages/planet/src/sandbox/proxy/patches/timer.ts:
--------------------------------------------------------------------------------
1 | import { SandboxPatchHandler } from '../types';
2 |
3 | const rawSetTimeout = window.setTimeout;
4 | const rawClearTimeout = window.clearTimeout;
5 | const rawSetInterval = window.setInterval;
6 | const rawClearInterval = window.clearInterval;
7 |
8 | export function timerPatch(): SandboxPatchHandler {
9 | const timeout = new Set();
10 | const interval = new Set();
11 |
12 | function rewriteSetTimeout(handler: TimerHandler, ms?: number, ...args: any[]) {
13 | const timeoutId = rawSetTimeout(handler, ms, ...args);
14 | timeout.add(timeoutId);
15 | return timeoutId;
16 | }
17 |
18 | function rewriteClearTimeout(timeoutId: number) {
19 | timeout.delete(timeoutId);
20 | rawClearTimeout(timeoutId);
21 | }
22 |
23 | function rewriteSetInterval(handler: TimerHandler, ms: number, ...args: any[]) {
24 | const intervalId = rawSetInterval(handler, ms, ...args);
25 | interval.add(intervalId);
26 | return intervalId;
27 | }
28 |
29 | function rewriteClearInterval(intervalId: number) {
30 | interval.delete(intervalId);
31 | rawClearInterval(intervalId);
32 | }
33 |
34 | return {
35 | rewrite: {
36 | setTimeout: rewriteSetTimeout,
37 | clearTimeout: rewriteClearTimeout,
38 | setInterval: rewriteSetInterval,
39 | clearInterval: rewriteClearInterval
40 | },
41 | destroy: () => {
42 | timeout.forEach(timeoutId => {
43 | rawClearTimeout(timeoutId);
44 | });
45 | interval.forEach(intervalId => {
46 | rawClearInterval(intervalId);
47 | });
48 | }
49 | };
50 | }
51 |
--------------------------------------------------------------------------------
/packages/planet/src/sandbox/proxy/proxy-sandbox.ts:
--------------------------------------------------------------------------------
1 | import { ProxyWindow } from './proxies/window';
2 | import { getSandboxPatchHandlers } from './patches';
3 | import { SandboxPatchHandler } from './types';
4 | import { Global } from '../types';
5 | import { Sandbox, SandboxOptions } from '../sandbox';
6 |
7 | export class ProxySandbox extends Sandbox {
8 | public running = false;
9 |
10 | public override global!: Global;
11 |
12 | public rewriteVariables!: PropertyKey[];
13 |
14 | private patchHandlers: SandboxPatchHandler[] = [];
15 |
16 | constructor(
17 | public app: string,
18 | public override options: SandboxOptions
19 | ) {
20 | super();
21 | this.patchHandlers = getSandboxPatchHandlers(this);
22 | this.start();
23 | }
24 |
25 | start() {
26 | this.running = true;
27 | this.rewriteVariables = this.getPatchRewriteVariables();
28 | const proxyWindow = new ProxyWindow(this);
29 | this.global = proxyWindow.create();
30 | this.execPatchHandlers();
31 | }
32 |
33 | destroy() {
34 | this.running = false;
35 | this.patchHandlers.forEach(handler => {
36 | if (handler.destroy) {
37 | handler.destroy();
38 | }
39 | });
40 | }
41 |
42 | private getPatchRewriteVariables() {
43 | return this.patchHandlers.reduce((pre, cur) => {
44 | return [...pre, ...(cur.rewrite ? Object.keys(cur.rewrite) : [])];
45 | }, []);
46 | }
47 |
48 | private execPatchHandlers() {
49 | this.patchHandlers.forEach(handler => {
50 | if (handler.rewrite) {
51 | for (const key in handler.rewrite) {
52 | if (handler.rewrite[key]) {
53 | this.global[key] = handler.rewrite[key];
54 | }
55 | }
56 | }
57 | if (handler.init) {
58 | handler?.init();
59 | }
60 | });
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/packages/planet/src/sandbox/proxy/types.ts:
--------------------------------------------------------------------------------
1 | import { Global } from '../types';
2 |
3 | // SandboxInstance 主要是为了解决循环依赖
4 | export interface ProxySandboxInstance {
5 | app: string;
6 |
7 | running: boolean;
8 |
9 | global: Global;
10 |
11 | rewriteVariables: PropertyKey[];
12 | }
13 |
14 | export interface SandboxPatchHandler {
15 | rewrite?: Record;
16 | init?: () => void;
17 | destroy?: () => void;
18 | }
19 |
20 | export type SandboxPatch = (sandbox?: ProxySandboxInstance) => SandboxPatchHandler;
21 |
--------------------------------------------------------------------------------
/packages/planet/src/sandbox/sandbox.ts:
--------------------------------------------------------------------------------
1 | import { execScript } from './exec';
2 | import { Global } from './types';
3 |
4 | export interface SandboxOptions {
5 | strictGlobal?: boolean;
6 | }
7 |
8 | export abstract class Sandbox {
9 | options!: SandboxOptions;
10 |
11 | global!: Global;
12 |
13 | abstract start(): void;
14 |
15 | abstract destroy(): void;
16 |
17 | execScript(code: string, url = '') {
18 | execScript(code, url, this.global, !!this.options.strictGlobal);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/packages/planet/src/sandbox/snapshot/snapshot-sandbox.ts:
--------------------------------------------------------------------------------
1 | import { Sandbox, SandboxOptions } from '../sandbox';
2 |
3 | export class SnapshotSandbox extends Sandbox {
4 | constructor(
5 | public app: string,
6 | public override options: SandboxOptions
7 | ) {
8 | super();
9 | this.start();
10 | }
11 |
12 | start() {}
13 |
14 | destroy() {}
15 | }
16 |
--------------------------------------------------------------------------------
/packages/planet/src/sandbox/types.ts:
--------------------------------------------------------------------------------
1 | export type Global = Record;
2 |
--------------------------------------------------------------------------------
/packages/planet/src/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 |
3 | // import 'core-js/es7/reflect';
4 | import 'zone.js';
5 | import 'zone.js/testing';
6 | import { getTestBed } from '@angular/core/testing';
7 | import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
8 |
9 | // First, initialize the Angular testing environment.
10 | getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
11 | teardown: { destroyAfterEach: false }
12 | });
13 |
--------------------------------------------------------------------------------
/packages/planet/src/testing/app1.module.ts:
--------------------------------------------------------------------------------
1 | import { Component, NgModule, OnDestroy } from '@angular/core';
2 | import { RouterModule } from '@angular/router';
3 | import { PlanetComponentLoader } from '../component/planet-component-loader';
4 | import { PlanetApplicationLoader } from '../application/planet-application-loader';
5 |
6 | export const app1Name = 'app1';
7 |
8 | @Component({
9 | // eslint-disable-next-line @angular-eslint/component-selector
10 | selector: 'app1-projects,[app1Projects]',
11 | template: ` projects is work `,
12 | standalone: false
13 | })
14 | export class App1ProjectsComponent implements OnDestroy {
15 | static state: 'initialized' | 'destroyed' | '' = '';
16 |
17 | constructor() {
18 | App1ProjectsComponent.state = 'initialized';
19 | }
20 |
21 | ngOnDestroy(): void {
22 | App1ProjectsComponent.state = 'destroyed';
23 | }
24 | }
25 |
26 | @NgModule({
27 | declarations: [App1ProjectsComponent],
28 | imports: [RouterModule.forChild([])],
29 | providers: [
30 | PlanetComponentLoader,
31 | {
32 | provide: PlanetApplicationLoader,
33 | useValue: {
34 | preload: () => {}
35 | }
36 | }
37 | ]
38 | })
39 | export class App1Module {
40 | constructor(private componentLoader: PlanetComponentLoader) {
41 | // componentLoader.register([{ name: 'app1-projects', component: App1ProjectsComponent }]);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/packages/planet/src/testing/app2.module.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Component,
3 | NgModule,
4 | EnvironmentInjector,
5 | ApplicationRef,
6 | viewChild,
7 | createComponent,
8 | inject,
9 | TemplateRef,
10 | ChangeDetectorRef
11 | } from '@angular/core';
12 | import { RouterModule } from '@angular/router';
13 | import { PlanetApplicationLoader } from '../application/planet-application-loader';
14 | import { PlanetComponentLoader } from '../component/planet-component-loader';
15 |
16 | export const app2Name = 'app2';
17 |
18 | @Component({
19 | // eslint-disable-next-line @angular-eslint/component-selector
20 | selector: 'app2-comp-with-template-ref',
21 | template: `This is templateRef from app2`,
22 | standalone: false
23 | })
24 | // eslint-disable-next-line @angular-eslint/component-class-suffix
25 | class ComponentWithTemplateRef {
26 | templateRef = viewChild>('ref');
27 |
28 | showDynamic = false;
29 |
30 | changeDetectorRef = inject(ChangeDetectorRef);
31 |
32 | constructor() {}
33 | }
34 |
35 | @NgModule({
36 | declarations: [ComponentWithTemplateRef],
37 | imports: [RouterModule.forChild([])],
38 | providers: [
39 | PlanetComponentLoader,
40 | {
41 | provide: PlanetApplicationLoader,
42 | useValue: {
43 | preload: () => {}
44 | }
45 | }
46 | ]
47 | })
48 | export class App2Module {
49 | environmentInjector = inject(EnvironmentInjector);
50 |
51 | applicationRef = inject(ApplicationRef);
52 |
53 | constructor() {}
54 |
55 | getComponentRefWithTemplateRef() {
56 | const componentRef = createComponent(ComponentWithTemplateRef, {
57 | environmentInjector: this.environmentInjector
58 | });
59 | this.applicationRef.attachView(componentRef.hostView);
60 | return componentRef.instance;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/packages/planet/src/testing/applications.ts:
--------------------------------------------------------------------------------
1 | import { SwitchModes } from '../planet.class';
2 | const app1 = {
3 | name: 'app1',
4 | hostParent: '.host-selector',
5 | selector: 'app1-root',
6 | stylePrefix: 'app1-prefix',
7 | routerPathPrefix: '/app1',
8 | hostClass: 'app1-host',
9 | preload: false,
10 | switchMode: SwitchModes.default,
11 | resourcePathPrefix: '/static/app1/',
12 | styles: ['styles/main.css'],
13 | scripts: ['vendor.js', 'main.js'],
14 | loadSerial: false,
15 | manifest: '',
16 | extra: {
17 | appName: '应用1'
18 | }
19 | };
20 |
21 | const app1WithManifest = {
22 | ...app1,
23 | manifest: '/static/app/manifest.json'
24 | };
25 |
26 | const app1WithPreload = {
27 | ...app1,
28 | preload: true
29 | };
30 |
31 | const app2 = {
32 | name: 'app2',
33 | hostParent: '.host-selector',
34 | selector: 'app2-root',
35 | routerPathPrefix: '/app2',
36 | hostClass: 'app2-host',
37 | preload: false,
38 | switchMode: SwitchModes.coexist,
39 | resourcePathPrefix: '/static/app2',
40 | styles: ['styles/main.css'],
41 | scripts: ['vendor.js', 'main.js'],
42 | loadSerial: false,
43 | extra: {
44 | appName: '应用2'
45 | }
46 | };
47 |
48 | const app2WithManifest = {
49 | ...app2,
50 | manifest: '/static/app/manifest.json'
51 | };
52 |
53 | const app2WithPreload = {
54 | ...app2,
55 | preload: true
56 | };
57 |
58 | export { app1, app1WithManifest, app1WithPreload, app2, app2WithManifest, app2WithPreload };
59 |
--------------------------------------------------------------------------------
/packages/planet/src/testing/index.ts:
--------------------------------------------------------------------------------
1 | export * from './applications';
2 | export * from './utils';
3 |
--------------------------------------------------------------------------------
/packages/planet/src/testing/utils.ts:
--------------------------------------------------------------------------------
1 | import { setDebugFactory, clearDebugFactory } from '../debug';
2 |
3 | export function roundNumber(minNum: number, maxNum?: number): number {
4 | switch (arguments.length) {
5 | case 1:
6 | return parseInt((Math.random() * minNum + 1) as any, 10);
7 | case 2:
8 | return parseInt((Math.random() * (maxNum! - minNum + 1) + minNum) as any, 10);
9 | default:
10 | return 0;
11 | }
12 | }
13 |
14 | export function sample(items: T[]): T {
15 | const num = roundNumber(1, items.length);
16 | return items[num - 1];
17 | }
18 |
19 | export function enableDebugForTest() {
20 | const debugFactory = (namespace: string) => {
21 | return (formatter: string) => {
22 | console.log(`[${namespace}] ${formatter}`);
23 | };
24 | };
25 |
26 | setDebugFactory(debugFactory as any);
27 | }
28 |
29 | export function disableDebugForTest() {
30 | clearDebugFactory();
31 | }
32 |
--------------------------------------------------------------------------------
/packages/planet/tsconfig.lib.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../out-tsc/lib",
5 | "declarationMap": true,
6 | "module": "es2015",
7 | "moduleResolution": "node",
8 | "declaration": true,
9 | "sourceMap": true,
10 | "inlineSources": true,
11 | "emitDecoratorMetadata": true,
12 | "experimentalDecorators": true,
13 | "importHelpers": true,
14 | "types": [],
15 | "lib": ["dom", "es2018"]
16 | },
17 | "angularCompilerOptions": {
18 | "compilationMode": "partial",
19 | "skipTemplateCodegen": true,
20 | "strictMetadataEmit": true,
21 | "fullTemplateTypeCheck": true,
22 | "strictInjectionParameters": true,
23 | "enableResourceInlining": true
24 | },
25 | "exclude": ["src/test.ts", "**/*.spec.ts"]
26 | }
27 |
--------------------------------------------------------------------------------
/packages/planet/tsconfig.lib.prod.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.lib.json",
3 | "compilerOptions": {
4 | "declarationMap": false
5 | },
6 | "angularCompilerOptions": {
7 | "compilationMode": "partial"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/packages/planet/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../out-tsc/spec",
5 | "types": [
6 | "jasmine",
7 | "node"
8 | ]
9 | },
10 | "files": [
11 | "src/test.ts"
12 | ],
13 | "include": [
14 | "**/*.spec.ts",
15 | "**/*.d.ts"
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [require('autoprefixer')]
3 | };
4 |
--------------------------------------------------------------------------------
/proxy.conf.js:
--------------------------------------------------------------------------------
1 | const PROXY_CONFIG = {};
2 |
3 | PROXY_CONFIG['/static/app1'] = {
4 | target: 'http://localhost:3001',
5 | secure: false,
6 | changeOrigin: false
7 | };
8 |
9 | PROXY_CONFIG['/static/app2'] = {
10 | target: 'http://localhost:3002',
11 | secure: false,
12 | changeOrigin: true
13 | };
14 |
15 | PROXY_CONFIG['/static/standalone'] = {
16 | target: 'http://localhost:3003',
17 | secure: false,
18 | changeOrigin: true
19 | };
20 |
21 | module.exports = PROXY_CONFIG;
22 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "baseUrl": "./",
5 | "outDir": "./dist/out-tsc",
6 | "sourceMap": true,
7 | "esModuleInterop": true,
8 | "declaration": false,
9 | "module": "esnext",
10 | "moduleResolution": "node",
11 | "emitDecoratorMetadata": true,
12 | "experimentalDecorators": true,
13 | "importHelpers": true,
14 | "target": "ES2022",
15 | "typeRoots": ["node_modules/@types"],
16 | "lib": ["es2020", "dom"],
17 | "paths": {
18 | "@demo/common": ["examples/common"],
19 | "ngx-planet": ["packages/planet/src/public-api.ts"],
20 | "ngx-planet/*": ["packages/planet/src/*"],
21 | "@worktile/planet": ["packages/planet/src/public-api.ts"],
22 | "@worktile/planet/*": ["packages/planet/src/*"]
23 | },
24 | "useDefineForClassFields": false
25 | },
26 | "angularCompilerOptions": {}
27 | }
28 |
--------------------------------------------------------------------------------