├── applications
├── app-entry
│ ├── src
│ │ ├── assets
│ │ │ └── .gitkeep
│ │ ├── views
│ │ │ └── .gitkeep
│ │ ├── components
│ │ │ └── .gitkeep
│ │ ├── modules.js
│ │ ├── store.js
│ │ ├── load-helper.js
│ │ ├── main.js
│ │ ├── App.vue
│ │ └── router.js
│ ├── public
│ │ ├── favicon.ico
│ │ └── index.html
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ └── vue.config.js
├── app-typescript
│ ├── src
│ │ ├── shims-vue.d.ts
│ │ ├── views
│ │ │ ├── About.vue
│ │ │ └── Home.vue
│ │ ├── assets
│ │ │ └── logo.png
│ │ ├── global.d.ts
│ │ ├── base.ts
│ │ ├── store.ts
│ │ ├── main.ts
│ │ ├── shims-tsx.d.ts
│ │ ├── routes.ts
│ │ ├── App.vue
│ │ └── components
│ │ │ └── HelloWorld.vue
│ ├── .gitignore
│ ├── README.md
│ ├── tsconfig.json
│ ├── package.json
│ └── vue.config.js
└── app-javascript
│ ├── src
│ ├── views
│ │ ├── About.vue
│ │ └── Home.vue
│ ├── assets
│ │ └── logo.png
│ ├── base.js
│ ├── store.js
│ ├── main.js
│ ├── routes.js
│ ├── App.vue
│ └── components
│ │ └── HelloWorld.vue
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ └── vue.config.js
├── .prettierrc
├── .gitignore
├── lerna.json
├── .travis.yml
├── package.json
├── move.js
├── README.md
└── detail.md
/applications/app-entry/src/assets/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/applications/app-entry/src/views/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/applications/app-entry/src/components/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/applications/app-typescript/src/shims-vue.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.vue' {
2 | import Vue from 'vue';
3 | export default Vue;
4 | }
5 |
--------------------------------------------------------------------------------
/applications/app-entry/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/micro-frontends-vue/async-routes/HEAD/applications/app-entry/public/favicon.ico
--------------------------------------------------------------------------------
/applications/app-javascript/src/views/About.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
This is an about page
4 |
5 |
6 |
--------------------------------------------------------------------------------
/applications/app-typescript/src/views/About.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
This is an about page
4 |
5 |
6 |
--------------------------------------------------------------------------------
/applications/app-javascript/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/micro-frontends-vue/async-routes/HEAD/applications/app-javascript/src/assets/logo.png
--------------------------------------------------------------------------------
/applications/app-typescript/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/micro-frontends-vue/async-routes/HEAD/applications/app-typescript/src/assets/logo.png
--------------------------------------------------------------------------------
/applications/app-entry/src/modules.js:
--------------------------------------------------------------------------------
1 | export const modules = {
2 | 'app-typescript': './app-typescript/main.js',
3 | 'app-javascript': './app-javascript/main.js',
4 | };
5 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 80,
3 | "singleQuote": true,
4 | "semi": true,
5 | "trailingComma": "all",
6 | "bracketSpacing": true,
7 | "arrowParens": "always"
8 | }
9 |
--------------------------------------------------------------------------------
/applications/app-typescript/src/global.d.ts:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 |
3 | declare module 'vue/types/vue' {
4 | interface VueConstructor {
5 | __share_pool__: any;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/applications/app-javascript/src/base.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import store from './store';
3 |
4 | // 子项目异步注册 store module
5 | Vue.__share_pool__.store.registerModule(process.env.VUE_APP_NAME, store);
6 |
7 | export default null;
8 |
--------------------------------------------------------------------------------
/applications/app-typescript/src/base.ts:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import store from './store';
3 |
4 | // 子项目异步注册 store module
5 | Vue.__share_pool__.store.registerModule(process.env.VUE_APP_NAME, store);
6 |
7 | export default null;
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | docs
4 |
5 | # Log files
6 | *.log
7 |
8 | # lock files
9 | yarn.lock
10 |
11 | # Editor directories and files
12 | .idea
13 | .vscode
14 | *.suo
15 | *.ntvs*
16 | *.njsproj
17 | *.sln
18 | *.sw*
19 |
--------------------------------------------------------------------------------
/applications/app-entry/src/store.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Vuex from 'vuex';
3 |
4 | Vue.use(Vuex);
5 |
6 | export default new Vuex.Store({
7 | state: {
8 | name: 'entry-application',
9 | },
10 | mutations: {},
11 | actions: {},
12 | });
13 |
--------------------------------------------------------------------------------
/applications/app-javascript/src/store.js:
--------------------------------------------------------------------------------
1 | /* store module */
2 |
3 | export default {
4 | namespaced: true, // namespaced must be true in module app.
5 | state: {
6 | name: process.env.VUE_APP_NAME,
7 | },
8 | mutations: {},
9 | actions: {},
10 | };
11 |
--------------------------------------------------------------------------------
/applications/app-typescript/src/store.ts:
--------------------------------------------------------------------------------
1 | /* store module */
2 |
3 | export default {
4 | namespaced: true, // namespaced must be true in module app.
5 | state: {
6 | name: process.env.VUE_APP_NAME,
7 | },
8 | mutations: {},
9 | actions: {},
10 | };
11 |
--------------------------------------------------------------------------------
/applications/app-javascript/src/main.js:
--------------------------------------------------------------------------------
1 | import routes from './routes';
2 |
3 | export default {
4 | name: 'javascript',
5 | routes,
6 | beforeEach(from, to, next) {
7 | console.log('javascript:', from.path, to.path);
8 | next();
9 | },
10 | init() { },
11 | }
12 |
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.0.0",
3 | "packages": [
4 | "applications/*"
5 | ],
6 | "command": {
7 | "bootstrap": {
8 | "hoist": true,
9 | "ignoreScripts": ["prepublish", "prepare"]
10 | },
11 | "clean": {
12 | "yes": true
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/applications/app-typescript/src/main.ts:
--------------------------------------------------------------------------------
1 | import routes from './routes';
2 |
3 | export default {
4 | name: 'typescript',
5 | routes,
6 | beforeEach(from: any, to: any, next: any) {
7 | console.log('typescript:', from.path, to.path);
8 | next();
9 | },
10 | init() { },
11 | }
12 |
--------------------------------------------------------------------------------
/applications/app-entry/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | # lock files
15 | yarn.lock
16 | package-lock.json
17 |
18 | # Editor directories and files
19 | .idea
20 | .vscode
21 | *.suo
22 | *.ntvs*
23 | *.njsproj
24 | *.sln
25 | *.sw*
26 |
--------------------------------------------------------------------------------
/applications/app-typescript/src/shims-tsx.d.ts:
--------------------------------------------------------------------------------
1 | import Vue, { VNode } from 'vue';
2 |
3 | declare global {
4 | namespace JSX {
5 | // tslint:disable no-empty-interface
6 | interface Element extends VNode {}
7 | // tslint:disable no-empty-interface
8 | interface ElementClass extends Vue {}
9 | interface IntrinsicElements {
10 | [elem: string]: any;
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/applications/app-javascript/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | # lock files
15 | yarn.lock
16 | package-lock.json
17 |
18 | # Editor directories and files
19 | .idea
20 | .vscode
21 | *.suo
22 | *.ntvs*
23 | *.njsproj
24 | *.sln
25 | *.sw*
26 |
--------------------------------------------------------------------------------
/applications/app-typescript/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | # lock files
15 | yarn.lock
16 | package-lock.json
17 |
18 | # Editor directories and files
19 | .idea
20 | .vscode
21 | *.suo
22 | *.ntvs*
23 | *.njsproj
24 | *.sln
25 | *.sw*
26 |
--------------------------------------------------------------------------------
/applications/app-javascript/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |

4 |
5 |
6 |
7 |
8 |
19 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | notifications:
4 | email: false
5 |
6 | node_js:
7 | - "10"
8 |
9 | script:
10 | - npm run bootstrap && npm run build
11 |
12 | branches:
13 | only:
14 | - master
15 |
16 | deploy:
17 | provider: pages
18 | local-dir: docs
19 | skip-cleanup: true
20 | github-token: $CI_TOKEN # Set in the settings page of your repository, as a secure variable
21 | keep-history: true
22 | on:
23 | branch: master
24 |
--------------------------------------------------------------------------------
/applications/app-entry/src/load-helper.js:
--------------------------------------------------------------------------------
1 | export function loadModule(url) {
2 | return new Promise((resolve) => {
3 | const script = document.createElement('script');
4 | script.type = 'text/javascript';
5 | // script.async = true;
6 | script.onload = ({ type }) => resolve({ status: type, url });
7 | script.onerror = ({ type }) => resolve({ status: type, url });
8 | script.src = url;
9 | document.body.appendChild(script);
10 | });
11 | }
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "root",
3 | "private": true,
4 | "scripts": {
5 | "bootstrap": "npm install && lerna bootstrap",
6 | "serve": "lerna run serve --parallel",
7 | "build-package": "lerna run build --parallel",
8 | "build": "npm run build-package && npm run move",
9 | "move": "node move.js",
10 | "clean": "lerna clean && rm -rf node_modules"
11 | },
12 | "devDependencies": {
13 | "fs-extra": "^8.1.0",
14 | "lerna": "^3.19.0"
15 | },
16 | "license": "MIT"
17 | }
18 |
--------------------------------------------------------------------------------
/applications/app-entry/README.md:
--------------------------------------------------------------------------------
1 | # app-entry
2 |
3 | ## Project setup
4 | ```
5 | yarn install
6 | ```
7 |
8 | ### Compiles and hot-reloads for development
9 | ```
10 | yarn run serve
11 | ```
12 |
13 | ### Compiles and minifies for production
14 | ```
15 | yarn run build
16 | ```
17 |
18 | ### Run your tests
19 | ```
20 | yarn run test
21 | ```
22 |
23 | ### Lints and fixes files
24 | ```
25 | yarn run lint
26 | ```
27 |
28 | ### Customize configuration
29 | See [Configuration Reference](https://cli.vuejs.org/config/).
30 |
--------------------------------------------------------------------------------
/applications/app-javascript/README.md:
--------------------------------------------------------------------------------
1 | # app-javascript
2 |
3 | ## Project setup
4 | ```
5 | yarn install
6 | ```
7 |
8 | ### Compiles and hot-reloads for development
9 | ```
10 | yarn run serve
11 | ```
12 |
13 | ### Compiles and minifies for production
14 | ```
15 | yarn run build
16 | ```
17 |
18 | ### Run your tests
19 | ```
20 | yarn run test
21 | ```
22 |
23 | ### Lints and fixes files
24 | ```
25 | yarn run lint
26 | ```
27 |
28 | ### Customize configuration
29 | See [Configuration Reference](https://cli.vuejs.org/config/).
30 |
--------------------------------------------------------------------------------
/applications/app-typescript/README.md:
--------------------------------------------------------------------------------
1 | # app-typescript
2 |
3 | ## Project setup
4 | ```
5 | yarn install
6 | ```
7 |
8 | ### Compiles and hot-reloads for development
9 | ```
10 | yarn run serve
11 | ```
12 |
13 | ### Compiles and minifies for production
14 | ```
15 | yarn run build
16 | ```
17 |
18 | ### Run your tests
19 | ```
20 | yarn run test
21 | ```
22 |
23 | ### Lints and fixes files
24 | ```
25 | yarn run lint
26 | ```
27 |
28 | ### Customize configuration
29 | See [Configuration Reference](https://cli.vuejs.org/config/).
30 |
--------------------------------------------------------------------------------
/applications/app-typescript/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |

4 |
5 |
6 |
7 |
8 |
19 |
--------------------------------------------------------------------------------
/applications/app-entry/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import VueProgressBar from 'vue-progressbar'
3 | import App from './App.vue';
4 | import router from './router';
5 | import store from './store';
6 |
7 | Vue.config.productionTip = false;
8 |
9 | Vue.use(VueProgressBar)
10 |
11 | // 挂载主项目的 store 和 router 实例
12 | Reflect.defineProperty(Vue, '__share_pool__', {
13 | value: {
14 | store,
15 | router,
16 | },
17 | });
18 |
19 | new Vue({
20 | router,
21 | store,
22 | render: function(h) {
23 | return h(App);
24 | },
25 | }).$mount('#app');
26 |
--------------------------------------------------------------------------------
/move.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const fs = require('fs-extra')
3 |
4 | const tasks = [
5 | {
6 | src: 'applications/app-entry/dist',
7 | dest: 'docs',
8 | },
9 | {
10 | src: 'applications/app-typescript/dist',
11 | dest: 'docs/app-typescript',
12 | },
13 | {
14 | src: 'applications/app-javascript/dist',
15 | dest: 'docs/app-javascript',
16 | },
17 | ]
18 |
19 | fs.removeSync(path.resolve(__dirname, 'dist'))
20 |
21 | for (const { src, dest } of tasks) {
22 | fs.moveSync(path.resolve(__dirname, src), path.resolve(__dirname, dest))
23 | console.log('\x1b[1m%s\x1b[31m%s\x1b[0m', '[Copy]: ', `src: ${src}, dest: ${dest}`, 'success.')
24 | }
25 |
--------------------------------------------------------------------------------
/applications/app-javascript/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "app-javascript",
3 | "devPort": 10242,
4 | "version": "0.1.0",
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build --report"
8 | },
9 | "dependencies": {
10 | "vue": "^2.6.10",
11 | "vue-router": "^3.1.3",
12 | "vuex": "^3.1.2"
13 | },
14 | "devDependencies": {
15 | "@vue/cli-service": "^4.1.0",
16 | "vue-template-compiler": "^2.6.10"
17 | },
18 | "postcss": {
19 | "plugins": {
20 | "autoprefixer": {}
21 | }
22 | },
23 | "browserslist": [
24 | "> 1%",
25 | "last 2 versions",
26 | "not ie <= 8"
27 | ],
28 | "license": "MIT"
29 | }
--------------------------------------------------------------------------------
/applications/app-javascript/src/routes.js:
--------------------------------------------------------------------------------
1 | /* routes-list */
2 |
3 | const APP_NAME = process.env.VUE_APP_NAME;
4 |
5 | const App = () => import('./App.vue');
6 | const Home = () => import('./views/Home.vue');
7 | const About = () => import('./views/About.vue');
8 |
9 | export default [
10 | {
11 | path: `/${APP_NAME}`,
12 | name: APP_NAME,
13 | redirect: { name: `${APP_NAME}.home` },
14 | component: App,
15 | children: [
16 | {
17 | path: 'home',
18 | name: `${APP_NAME}.home`,
19 | component: Home,
20 | },
21 | {
22 | path: 'about',
23 | name: `${APP_NAME}.about`,
24 | component: About,
25 | },
26 | ],
27 | },
28 | ];
29 |
--------------------------------------------------------------------------------
/applications/app-typescript/src/routes.ts:
--------------------------------------------------------------------------------
1 | /* routes-list */
2 |
3 | const APP_NAME = process.env.VUE_APP_NAME;
4 |
5 | const App = () => import('./App.vue');
6 | const Home = () => import('./views/Home.vue');
7 | const About = () => import('./views/About.vue');
8 |
9 | export default [
10 | {
11 | path: `/${APP_NAME}`,
12 | name: APP_NAME,
13 | redirect: { name: `${APP_NAME}.home` },
14 | component: App,
15 | children: [
16 | {
17 | path: 'home',
18 | name: `${APP_NAME}.home`,
19 | component: Home,
20 | },
21 | {
22 | path: 'about',
23 | name: `${APP_NAME}.about`,
24 | component: About,
25 | },
26 | ],
27 | },
28 | ];
29 |
--------------------------------------------------------------------------------
/applications/app-entry/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "app-entry",
3 | "devPort": 10240,
4 | "version": "0.1.0",
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build --report"
8 | },
9 | "dependencies": {
10 | "vue": "^2.6.10",
11 | "vue-progressbar": "^0.7.5",
12 | "vue-router": "^3.1.3",
13 | "vuex": "^3.1.2"
14 | },
15 | "devDependencies": {
16 | "@vue/cli-service": "^4.1.0",
17 | "vue-template-compiler": "^2.6.10"
18 | },
19 | "postcss": {
20 | "plugins": {
21 | "autoprefixer": {}
22 | }
23 | },
24 | "browserslist": [
25 | "> 1%",
26 | "last 2 versions",
27 | "not ie <= 8"
28 | ],
29 | "license": "MIT"
30 | }
31 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 异步加载子项目路由
2 |
3 | [](https://lernajs.io/)
4 |
5 | 灵感来自于 [用微前端的方式搭建类单页应用](https://tech.meituan.com/fe_tiny_spa.html)(@[美团技术团队](https://tech.meituan.com))
6 |
7 | ## 关联项目
8 |
9 | - 预加载子项目路由 -- [传送门](https://github.com/micro-frontends-vue/preload-routes)
10 |
11 | ## 设计目标
12 |
13 | 1. 子项目支持单独开发,单独部署(避免前端巨无霸,多团队同时参与)
14 | 2. 单一的入口 HTML(不同项目之间切换时无白屏现象)
15 | 3. 支持多语言开发(JavaScript、TypeScript)
16 |
17 | ## 使用
18 |
19 | [详细教程](./detail.md)
20 |
21 | ### 开发
22 |
23 | ```bash
24 | # 安装依赖
25 | npm run bootstrap
26 |
27 | # 开发模式
28 | npm run serve
29 |
30 | # 打开: http://localhost:10240/
31 | ```
32 |
33 | ### 构建
34 |
35 | ```bash
36 | # 生产构建
37 | npm run build
38 | ```
39 |
--------------------------------------------------------------------------------
/applications/app-typescript/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "esnext",
5 | "strict": true,
6 | "jsx": "preserve",
7 | "importHelpers": true,
8 | "moduleResolution": "node",
9 | "experimentalDecorators": true,
10 | "esModuleInterop": true,
11 | "allowSyntheticDefaultImports": true,
12 | "sourceMap": true,
13 | "baseUrl": ".",
14 | "types": ["webpack-env"],
15 | "paths": {
16 | "@/*": ["src/*"]
17 | },
18 | "lib": ["esnext", "dom", "dom.iterable", "scripthost"]
19 | },
20 | "include": [
21 | "src/**/*.ts",
22 | "src/**/*.tsx",
23 | "src/**/*.vue",
24 | "tests/**/*.ts",
25 | "tests/**/*.tsx"
26 | ],
27 | "exclude": ["node_modules"]
28 | }
29 |
--------------------------------------------------------------------------------
/applications/app-javascript/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Home |
5 | About
6 |
7 |
8 |
9 |
10 |
11 |
16 |
17 |
18 |
39 |
--------------------------------------------------------------------------------
/applications/app-typescript/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "app-typescript",
3 | "devPort": 10241,
4 | "version": "0.1.0",
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build --report"
8 | },
9 | "dependencies": {
10 | "vue": "^2.6.10",
11 | "vue-class-component": "^6.0.0",
12 | "vue-property-decorator": "^7.0.0",
13 | "vue-router": "^3.1.3",
14 | "vuex": "^3.1.2"
15 | },
16 | "devDependencies": {
17 | "@vue/cli-plugin-typescript": "^3.3.0",
18 | "@vue/cli-service": "^4.1.0",
19 | "typescript": "^3.0.0",
20 | "vue-template-compiler": "^2.6.10"
21 | },
22 | "postcss": {
23 | "plugins": {
24 | "autoprefixer": {}
25 | }
26 | },
27 | "browserslist": [
28 | "> 1%",
29 | "last 2 versions",
30 | "not ie <= 8"
31 | ],
32 | "license": "MIT"
33 | }
34 |
--------------------------------------------------------------------------------
/applications/app-typescript/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Home |
5 | About
6 |
7 |
8 |
9 |
10 |
11 |
18 |
19 |
20 |
41 |
--------------------------------------------------------------------------------
/applications/app-entry/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | app-entry
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/applications/app-entry/vue.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack')
2 | const APP_NAME = require('./package.json').name
3 | const PORT = require('./package.json').devPort
4 |
5 | const PROXY = {
6 | '/app-typescript/': {
7 | target: 'http://localhost:10241/'
8 | },
9 | '/app-javascript/': {
10 | target: 'http://localhost:10242/'
11 | }
12 | }
13 |
14 | const NODE_ENV = process.env.NODE_ENV || 'development'
15 |
16 | log('APP_NAME: ', APP_NAME)
17 | log('NODE_ENV: ', NODE_ENV)
18 |
19 | module.exports = {
20 | publicPath: './',
21 |
22 | productionSourceMap: false,
23 |
24 | configureWebpack: {
25 | externals: {
26 | vue: 'Vue'
27 | },
28 |
29 | plugins: [
30 | new webpack.DefinePlugin({
31 | 'process.env.VUE_APP_NAME': JSON.stringify(APP_NAME)
32 | })
33 | ]
34 | },
35 |
36 | devServer: {
37 | port: PORT,
38 | proxy: PROXY
39 | }
40 | }
41 |
42 | function log(label, content, options) {
43 | console.log('\x1b[1m%s\x1b[31m%s\x1b[0m', label, content)
44 | }
45 |
--------------------------------------------------------------------------------
/applications/app-javascript/vue.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const APP_NAME = require('./package.json').name;
3 | const PORT = require('./package.json').devPort;
4 | const NODE_ENV = process.env.NODE_ENV || 'development';
5 |
6 | log('APP_NAME: ', APP_NAME);
7 | log('NODE_ENV: ', NODE_ENV);
8 |
9 | module.exports = {
10 | publicPath: `${NODE_ENV === 'development' ? '' : '.'}/${APP_NAME}/`,
11 |
12 | css: {
13 | extract: false
14 | },
15 |
16 | productionSourceMap: false,
17 |
18 | chainWebpack: (config) => {
19 | config.externals({
20 | 'vue': 'Vue'
21 | })
22 |
23 | config.output
24 | .filename('main.js')
25 | .chunkFilename('[name].[chunkhash:8].js')
26 | .jsonpFunction(`webpackJsonp-${APP_NAME}`)
27 | .library(`app-${APP_NAME}`)
28 | .libraryExport('default')
29 | .libraryTarget('umd')
30 |
31 | config.optimization.splitChunks(false)
32 |
33 | config.plugin('define').use(webpack.DefinePlugin, [{
34 | 'process.env.VUE_APP_NAME': JSON.stringify(APP_NAME)
35 | }])
36 |
37 | config.plugins
38 | .delete('html')
39 | .delete('preload')
40 | .delete('prefetch')
41 | },
42 |
43 | devServer: {
44 | port: PORT,
45 | },
46 | };
47 |
48 | function log(label, content, options) {
49 | console.log('\x1b[1m%s\x1b[31m%s\x1b[0m', label, content);
50 | }
51 |
--------------------------------------------------------------------------------
/applications/app-typescript/vue.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const APP_NAME = require('./package.json').name;
3 | const PORT = require('./package.json').devPort;
4 | const NODE_ENV = process.env.NODE_ENV || 'development';
5 |
6 | log('APP_NAME: ', APP_NAME);
7 | log('NODE_ENV: ', NODE_ENV);
8 |
9 | module.exports = {
10 | publicPath: `${NODE_ENV === 'development' ? '' : '.'}/${APP_NAME}/`,
11 |
12 | css: {
13 | extract: false
14 | },
15 |
16 | productionSourceMap: false,
17 |
18 | chainWebpack: (config) => {
19 | config.externals({
20 | 'vue': 'Vue'
21 | })
22 |
23 | config.output
24 | .filename('main.js')
25 | .chunkFilename('[name].[chunkhash:8].js')
26 | .jsonpFunction(`webpackJsonp-${APP_NAME}`)
27 | .library(`app-${APP_NAME}`)
28 | .libraryExport('default')
29 | .libraryTarget('umd')
30 |
31 | config.optimization.splitChunks(false)
32 |
33 | config.plugin('define').use(webpack.DefinePlugin, [{
34 | 'process.env.VUE_APP_NAME': JSON.stringify(APP_NAME)
35 | }])
36 |
37 | config.plugins
38 | .delete('html')
39 | .delete('preload')
40 | .delete('prefetch')
41 | },
42 |
43 | devServer: {
44 | port: PORT,
45 | },
46 | };
47 |
48 | function log(label, content, options) {
49 | console.log('\x1b[1m%s\x1b[31m%s\x1b[0m', label, content);
50 | }
51 |
--------------------------------------------------------------------------------
/applications/app-entry/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ r.title }}
12 |
13 |
14 |
15 |
16 |
17 |
18 |
52 |
53 |
77 |
--------------------------------------------------------------------------------
/applications/app-entry/src/router.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Router from 'vue-router';
3 | import { modules } from './modules';
4 |
5 | Vue.use(Router);
6 |
7 | const router = new Router({
8 | routes: [
9 | {
10 | path: '/',
11 | redirect: '/app-typescript',
12 | },
13 | {
14 | path: '/404',
15 | component: {
16 | name: 'not-found',
17 | template: 'Not Found
',
18 | }
19 | },
20 | ],
21 | });
22 |
23 | const cachedModules = new Set();
24 |
25 | const sleep = (time) => new Promise((resolve) => void setTimeout(resolve, time));
26 |
27 | router.beforeEach(async (to, from, next) => {
28 |
29 | console.log('entry:', to.path, from.path);
30 | const [, module] = to.path.split('/');
31 |
32 | if (Reflect.has(modules, module)) {
33 | if (!cachedModules.has(module)) {
34 | Vue.prototype.$Progress.start();
35 |
36 | const { default: application } = await window.System.import(modules[module])
37 | console.log('load application:', application);
38 |
39 | if (application && application.routes) {
40 | // 动态添加子项目的 route-list
41 | router.addRoutes(application.routes);
42 | }
43 |
44 | if (application && application.beforeEach) {
45 | router.beforeEach((to, from, next) => {
46 | if (module === to.path.split('/')[1]) {
47 | application.beforeEach(to, from, next);
48 | } else {
49 | next();
50 | }
51 | })
52 | }
53 |
54 | if (application && application.init) {
55 | await application.init({});
56 | }
57 |
58 | await sleep(300); // 模拟延迟
59 | cachedModules.add(module);
60 | Vue.prototype.$Progress.finish();
61 | next(to.path);
62 | } else {
63 | next();
64 | }
65 | return;
66 | }
67 |
68 | if (to.matched.length) {
69 | next();
70 | } else {
71 | next('/404');
72 | }
73 | });
74 |
75 | export default router;
76 |
--------------------------------------------------------------------------------
/applications/app-javascript/src/components/HelloWorld.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ msg }}
4 |
5 | For a guide and recipes on how to configure / customize this project,
6 | check out the
7 | vue-cli documentation.
10 |
11 |
Installed CLI Plugins
12 |
13 |
Essential Links
14 |
37 |
Ecosystem
38 |
69 |
70 |
71 |
72 |
80 |
81 |
82 |
98 |
--------------------------------------------------------------------------------
/applications/app-typescript/src/components/HelloWorld.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ msg }}
4 |
5 | For a guide and recipes on how to configure / customize this project,
6 | check out the
7 | vue-cli documentation.
10 |
11 |
Installed CLI Plugins
12 |
22 |
Essential Links
23 |
46 |
Ecosystem
47 |
78 |
79 |
80 |
81 |
89 |
90 |
91 |
107 |
--------------------------------------------------------------------------------
/detail.md:
--------------------------------------------------------------------------------
1 | # 懒加载子项目
2 |
3 | ## 思路
4 |
5 | 1. 将子项目打包 UMD 模块,[教程](https://cli.vuejs.org/zh/guide/build-targets.html#库)
6 | 2. 在主项目中加载子项目的入口文件
7 | 3. 使用 vue-router 的 [`router.addRoutes`](https://github.com/zh-rocco/fe-notes/issues/29) 将子项目的路由动态注册到主项目中
8 | 4. 使用 Vuex 的 [`store.registerModule`](https://github.com/zh-rocco/fe-notes/issues/31) 将子项目的 `store module` 动态注册到主项目中
9 |
10 | ## 具体实现
11 |
12 | ### 一、主项目(app-entry)
13 |
14 | #### 1. 配置 `vue.config.js`
15 |
16 | `app-entry/vue.config.js`
17 |
18 | ```js
19 | const webpack = require('webpack');
20 | const APP_NAME = require('./package.json').name;
21 | const PORT = require('./package.json').devPort; // 开发模式下项目的启动端口
22 | const PROXY = require('./config/proxy'); // 开发模式下的 proxy 配置
23 |
24 | module.exports = {
25 | baseUrl: './',
26 | configureWebpack: {
27 | // 提取公共依赖
28 | externals: {
29 | vue: 'Vue',
30 | 'element-ui': 'ELEMENT',
31 | },
32 | plugins: [
33 | // 定义全局变量
34 | new webpack.DefinePlugin({
35 | 'process.env.VUE_APP_NAME': JSON.stringify(APP_NAME),
36 | }),
37 | ],
38 | },
39 | devServer: {
40 | port: PORT,
41 | proxy: PROXY,
42 | },
43 | };
44 | ```
45 |
46 | #### 2. 手动引入外部依赖
47 |
48 | `app-entry/public/index.html`
49 |
50 | ```html
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | ```
60 |
61 | #### 3. 配置开发代理
62 |
63 | `app-entry/config/proxy.js`
64 |
65 | ```js
66 | module.exports = {
67 | '/app-typescript/': {
68 | target: 'http://localhost:10241/', // 指向 app-typescript 开发服务
69 | },
70 | 'app-javascript/': {
71 | target: 'http://localhost:10242/', // 指向 app-javascript 开发服务
72 | },
73 | };
74 | ```
75 |
76 | #### 4. 将主项目的 router 实例和 store 实例挂载到 `Vue['__share_pool__']` 上
77 |
78 | `app-entry/src/main.js`
79 |
80 | ```js
81 | import router from './router'; // router 实例
82 | import store from './store'; // store 实例
83 |
84 | // 挂载主项目的 store 和 router 实例
85 | Reflect.defineProperty(Vue, '__share_pool__', {
86 | value: {
87 | store,
88 | router,
89 | },
90 | });
91 | ```
92 |
93 | #### 5. 异步加载子项目
94 |
95 | `app-entry/src/router.js`
96 |
97 | ```js
98 | import { loadModule } from './load-helper';
99 | import { modules } from './modules';
100 |
101 | router.beforeEach(async (to, from, next) => {
102 | const [, module] = to.path.split('/');
103 |
104 | if (Reflect.has(modules, module)) {
105 | loadModule(modules[module]);
106 | Reflect.deleteProperty(modules, module);
107 | }
108 |
109 | next();
110 | });
111 | ```
112 |
113 | `app-entry/src/modules.js`
114 |
115 | ```js
116 | export const modules = {
117 | 'app-typescript': './app-typescript/main.js',
118 | 'app-javascript': './app-javascript/main.js',
119 | };
120 | ```
121 |
122 | `app-entry/src/load-helper.js`
123 |
124 | ```js
125 | export function loadModule(url) {
126 | return new Promise((resolve) => {
127 | const script = document.createElement('script');
128 | script.type = 'text/javascript';
129 | script.async = true;
130 | script.onload = ({ type }) => resolve({ status: type, url });
131 | script.onerror = ({ type }) => resolve({ status: type, url });
132 | script.src = url;
133 | document.body.appendChild(script);
134 | });
135 | }
136 | ```
137 |
138 | ### 二、子项目(app-javascript)
139 |
140 | 以 app-javascript 为例
141 |
142 | #### 1. 添加 mfv-cli-service 依赖
143 |
144 | ```bash
145 | yarn add mfv-cli-service -D
146 | ```
147 |
148 | #### 2. 修改 build script
149 |
150 | `app-javascript/package.json`
151 |
152 | ```json
153 | {
154 | "scripts": {
155 | "build": "mfv-cli-service build --report --target lib --formats umd-min ./src/main.js"
156 | }
157 | }
158 | ```
159 |
160 | 这样可以将 app-javascript 构建成一个 UMD 文件,然后在 app-entry 中引用,[参考](https://cli.vuejs.org/zh/guide/build-targets.html#库)
161 |
162 | #### 3. 配置 `vue.config.js`
163 |
164 | `app-javascript/vue.config.js`
165 |
166 | ```js
167 | const webpack = require('webpack');
168 | const APP_NAME = require('./package.json').name;
169 | const PORT = require('./package.json').devPort; // 开发模式下项目的启动端口
170 |
171 | module.exports = {
172 | publicPath: `/${APP_NAME}/`, // 必须为绝对路径;配合 app-entry 中的 proxy 配置,配合生产环境下的 Nginx 配置
173 | configureWebpack: {
174 | // 提取公共依赖
175 | externals: {
176 | vue: 'Vue',
177 | 'element-ui': 'ELEMENT',
178 | },
179 | entry: './src/main.js',
180 | output: {
181 | libraryExport: 'default', // https://cli.vuejs.org/zh/guide/build-targets.html#应用
182 | jsonpFunction: `webpackJsonp-${APP_NAME}`, // 解决默认情况下子项目 chunkname 冲突的问题
183 | },
184 | plugins: [
185 | // 定义全局变量,项目中会使用
186 | new webpack.DefinePlugin({
187 | 'process.env.VUE_APP_NAME': JSON.stringify(APP_NAME),
188 | }),
189 | ],
190 | },
191 |
192 | devServer: {
193 | port: PORT,
194 | },
195 | };
196 | ```
197 |
198 | #### 4. 修改入口文件,异步挂载 routes
199 |
200 | `app-javascript/src/main.js`
201 |
202 | ```js
203 | import Vue from 'vue';
204 | import routes from './routes';
205 |
206 | // 动态添加子项目的 route-list
207 | Vue.__share_pool__.router.addRoutes(routes);
208 | ```
209 |
210 | `app-javascript/src/routes.js`
211 |
212 | ```js
213 | /* routes-list */
214 |
215 | const APP_NAME = process.env.VUE_APP_NAME;
216 |
217 | const App = () => import('./App.vue');
218 | const Home = () => import('./views/Home.vue');
219 | const About = () => import('./views/About.vue');
220 |
221 | export default [
222 | {
223 | path: `/${APP_NAME}`,
224 | name: APP_NAME,
225 | redirect: { name: `${APP_NAME}.home` },
226 | component: App,
227 | children: [
228 | {
229 | path: 'home',
230 | name: `${APP_NAME}.home`,
231 | component: Home,
232 | },
233 | {
234 | path: 'about',
235 | name: `${APP_NAME}.about`,
236 | component: About,
237 | },
238 | ],
239 | },
240 | ];
241 | ```
242 |
243 | #### 5. 异步挂载 store module
244 |
245 | `app-javascript/src/base.js`
246 |
247 | ```js
248 | import Vue from 'vue';
249 | import store from './store';
250 |
251 | // 子项目异步注册 store module
252 | Vue.__share_pool__.store.registerModule(process.env.VUE_APP_NAME, store);
253 |
254 | export default null;
255 | ```
256 |
257 | `app-javascript/src/store.js`
258 |
259 | ```js
260 | /* store module */
261 |
262 | export default {
263 | namespaced: true, // namespaced must be true in module app.
264 | state: {
265 | name: process.env.VUE_APP_NAME,
266 | },
267 | mutations: {},
268 | actions: {},
269 | };
270 | ```
271 |
272 | ## 关于部署
273 |
274 | - 方式一:通过 CI 合并主项目和子项目的 dist 目录,然后部署
275 | - 方式二:将子项目当成单独服务对待,独立部署,然后通过 Nginx 反向代理合并主项目和子项目
276 |
277 | ## 不足 & 待解决的问题
278 |
279 | ### 1. 鉴权问题
280 |
281 | 由于子项目的路由是异步加载 / 注册的,导致子项目无法使用 `meta` 判断路由是否需要鉴权
282 |
283 | **优化方向: 在主项目实例化之前加载子项目的路由**,参考:[预加载子项目入口文件](https://github.com/micro-frontends-vue/preload-routes)
284 |
285 | ### 2. 子项目缓存问题
286 |
287 | 推荐在服务端为子项目入口文件添加协商缓存。
288 |
289 | ## 参考
290 |
291 | - [微前端的那些事儿](https://github.com/phodal/microfrontends)
292 | - [用微前端的方式搭建类单页应用](https://tech.meituan.com/fe_tiny_spa.html)
293 | - [微前端 - 将微服务理念延伸到前端开发中](http://zzfe.org/#/detail/59dc6e1f91d3e35ba880fd0d)
294 | - [Micro Frontends](https://micro-frontends.org/) ([Github](https://github.com/neuland/micro-frontends))
295 | - [微前端 - 将微服务理念扩展到前端开发(实战篇)](http://insights.thoughtworkers.org/micro-frontends-2/)
296 | - [Medium: micro frontends](https://medium.com/search?q=micro%20frontends)
297 | - [single-spa: 在一个页面将多个不同的框架整合](https://github.com/CanopyTax/single-spa)
298 | - [Ueact: 渐进式,多调和策略,多渲染终端的乐高式微前端框架,以及可复用的多框架碎片化组件](https://github.com/wxyyxc1992/Ueact)
299 | - [Web Components](https://developer.mozilla.org/zh-CN/docs/Web/Web_Components)
300 | - [微前端: Alili](https://alili.tech/tags/%E5%BE%AE%E5%89%8D%E7%AB%AF/)
301 |
--------------------------------------------------------------------------------