├── applications
├── app-entry
│ ├── src
│ │ ├── assets
│ │ │ └── .gitkeep
│ │ ├── views
│ │ │ └── .gitkeep
│ │ ├── components
│ │ │ └── .gitkeep
│ │ ├── store.js
│ │ ├── router.js
│ │ ├── main.js
│ │ └── App.vue
│ ├── public
│ │ ├── favicon.ico
│ │ └── index.html
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── scripts
│ │ └── InsertScriptWebpackPlugin.js
│ └── 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
├── copy.js
├── 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/preload-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/preload-routes/HEAD/applications/app-javascript/src/assets/logo.png
--------------------------------------------------------------------------------
/applications/app-typescript/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/micro-frontends-vue/preload-routes/HEAD/applications/app-typescript/src/assets/logo.png
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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-javascript/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import routes from './routes';
3 |
4 | const sharePool = (Vue.__share_pool__ = Vue.__share_pool__ || {});
5 | const routesPool = (sharePool.routes = sharePool.routes || {});
6 |
7 | // 挂载子项目的 route-list
8 | routesPool[process.env.VUE_APP_NAME] = routes;
9 |
--------------------------------------------------------------------------------
/applications/app-typescript/src/main.ts:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import routes from './routes';
3 |
4 | const sharePool = (Vue.__share_pool__ = Vue.__share_pool__ || {});
5 | const routesPool = (sharePool.routes = sharePool.routes || {});
6 |
7 | // 挂载子项目的 route-list
8 | routesPool[process.env.VUE_APP_NAME] = routes;
9 |
--------------------------------------------------------------------------------
/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-entry/src/router.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Router from 'vue-router';
3 |
4 | Vue.use(Router);
5 |
6 | // 获取子项目的 route-list
7 | const routes = Vue.__share_pool__.routes;
8 |
9 | export default new Router({
10 | routes: Object.values(routes).reduce((acc, prev) => acc.concat(prev), [
11 | {
12 | path: '/',
13 | redirect: '/app-typescript',
14 | },
15 | ]),
16 | });
17 |
--------------------------------------------------------------------------------
/applications/app-entry/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import App from './App.vue';
3 | import router from './router';
4 | import store from './store';
5 |
6 | Vue.config.productionTip = false;
7 |
8 | // 挂载主项目的 store 实例
9 | (Vue.__share_pool__ = Vue.__share_pool__ || {}).store = store;
10 |
11 | new Vue({
12 | router,
13 | store,
14 | render: function(h) {
15 | return h(App);
16 | },
17 | }).$mount('#app');
18 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/copy.js:
--------------------------------------------------------------------------------
1 | const copy = require('recursive-copy');
2 |
3 | const task = [
4 | {
5 | src: 'applications/app-entry/dist',
6 | dest: 'docs',
7 | },
8 | {
9 | src: 'applications/app-typescript/dist',
10 | dest: 'docs/app-typescript',
11 | },
12 | {
13 | src: 'applications/app-javascript/dist',
14 | dest: 'docs/app-javascript',
15 | },
16 | ];
17 |
18 | task.forEach(({ src, dest }) => {
19 | copy(src, dest, (error) => {
20 | error &&
21 | console.error('[Copy failed]', `src: ${src}, dest: ${dest}`, error);
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/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-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-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 | }
30 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/async-routes)
10 |
11 | ## 设计目标
12 |
13 | 1. 子项目支持单独开发,单独部署(避免前端巨无霸,多团队同时参与)
14 | 2. 单一的入口 HTML(不同项目之间切换时无白屏现象)
15 | 3. 支持多语言开发(JavaScript、TypeScript)
16 |
17 | ## 使用
18 |
19 | [详细教程](./detail.md)
20 |
21 | ```bash
22 | # 安装依赖
23 | npm run bootstrap
24 |
25 | # 开发模式
26 | npm run serve
27 |
28 | # 打开: http://localhost:10240/
29 | ```
30 |
31 | ### 构建
32 |
33 | ```bash
34 | # 生产构建
35 | npm run build
36 | ```
37 |
--------------------------------------------------------------------------------
/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 |
12 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/applications/app-entry/scripts/InsertScriptWebpackPlugin.js:
--------------------------------------------------------------------------------
1 | class InsertScriptWebpackPlugin {
2 | constructor(options = {}) {
3 | const { files = [] } = options;
4 | this.files = files;
5 | }
6 |
7 | apply(compiler) {
8 | const self = this;
9 | compiler.hooks.compilation.tap(
10 | 'InsertScriptWebpackPlugin',
11 | (compilation) => {
12 | if (compilation.hooks.htmlWebpackPluginBeforeHtmlProcessing) {
13 | compilation.hooks.htmlWebpackPluginBeforeHtmlProcessing.tap(
14 | 'InsertScriptWebpackPlugin',
15 | (htmlPluginData) => {
16 | const {
17 | assets: { js },
18 | } = htmlPluginData;
19 | js.unshift(...self.files);
20 | },
21 | );
22 | } else {
23 | console.log('\n');
24 | console.log(
25 | '\x1b[41m%s\x1b[0m',
26 | 'Error:',
27 | '`insert-script-webpack-plugin` dependent on `html-webpack-plugin`',
28 | );
29 | }
30 | },
31 | );
32 | }
33 | }
34 |
35 | module.exports = InsertScriptWebpackPlugin;
36 |
--------------------------------------------------------------------------------
/applications/app-entry/vue.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const InsertScriptPlugin = require('./scripts/InsertScriptWebpackPlugin');
3 | const APP_NAME = require('./package.json').name;
4 | const PORT = require('./package.json').devPort;
5 |
6 | const PROXY = {
7 | '/app-typescript/': {
8 | target: 'http://localhost:10241/'
9 | },
10 | '/app-javascript/': {
11 | target: 'http://localhost:10242/'
12 | }
13 | }
14 |
15 | const modules = [
16 | // sub apps
17 | './app-typescript/main.js',
18 | './app-javascript/main.js',
19 | ];
20 |
21 |
22 | const NODE_ENV = process.env.NODE_ENV || 'development';
23 |
24 | log('APP_NAME: ', APP_NAME);
25 | log('NODE_ENV: ', NODE_ENV);
26 |
27 | module.exports = {
28 | publicPath: './',
29 |
30 | productionSourceMap: false,
31 |
32 | configureWebpack: {
33 | externals: {
34 | vue: 'Vue'
35 | },
36 |
37 | plugins: [
38 | new webpack.DefinePlugin({
39 | 'process.env.VUE_APP_NAME': JSON.stringify(APP_NAME),
40 | }),
41 | new InsertScriptPlugin({ files: modules }),
42 | ],
43 | },
44 |
45 | devServer: {
46 | port: PORT,
47 | proxy: PROXY,
48 | },
49 | };
50 |
51 | function log(label, content, options) {
52 | console.log('\x1b[1m%s\x1b[31m%s\x1b[0m', label, content);
53 | }
54 |
--------------------------------------------------------------------------------
/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 |
51 |
52 |
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. 在主项目中预先(主项目实例化 Vue 之前)挂载子项目的 routes
7 | 3. 合并主项目和子项目的路由表
8 | 4. 实例化主项目 Vue
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 | new InsertScriptPlugin({ files: modules }), // 自动添加子项目入口文件至 index.html
38 | ],
39 | },
40 | devServer: {
41 | port: PORT,
42 | proxy: PROXY,
43 | },
44 | };
45 | ```
46 |
47 | `app-entry/scripts/InsertScriptWebpackPlugin.js`
48 |
49 | ```js
50 | class InsertScriptWebpackPlugin {
51 | constructor(options = {}) {
52 | const { files = [] } = options;
53 | this.files = files;
54 | }
55 |
56 | apply(compiler) {
57 | const self = this;
58 | compiler.hooks.compilation.tap(
59 | 'InsertScriptWebpackPlugin',
60 | (compilation) => {
61 | if (compilation.hooks.htmlWebpackPluginBeforeHtmlProcessing) {
62 | compilation.hooks.htmlWebpackPluginBeforeHtmlProcessing.tap(
63 | 'InsertScriptWebpackPlugin',
64 | (htmlPluginData) => {
65 | const {
66 | assets: { js },
67 | } = htmlPluginData;
68 | js.unshift(...self.files); // 优先加载 files 文件
69 | },
70 | );
71 | } else {
72 | console.log('\n');
73 | console.log(
74 | '\x1b[41m%s\x1b[0m',
75 | 'Error:',
76 | '`insert-script-webpack-plugin` dependent on `html-webpack-plugin`',
77 | );
78 | }
79 | },
80 | );
81 | }
82 | }
83 |
84 | module.exports = InsertScriptWebpackPlugin;
85 | ```
86 |
87 | #### 2. 手动引入外部依赖
88 |
89 | `app-entry/public/index.html`
90 |
91 | ```html
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 | ```
101 |
102 | #### 3. 配置开发代理
103 |
104 | `app-entry/config/proxy.js`
105 |
106 | ```js
107 | module.exports = {
108 | '/app-typescript/': {
109 | target: 'http://localhost:10241/', // 指向 app-typescript 开发服务
110 | },
111 | 'app-javascript/': {
112 | target: 'http://localhost:10242/', // 指向 app-javascript 开发服务
113 | },
114 | };
115 | ```
116 |
117 | #### 4. 将主项目的 store 实例挂载到 `Vue['__share_pool__']` 上
118 |
119 | `app-entry/src/main.js`
120 |
121 | ```js
122 | import store from './store'; // store 实例
123 |
124 | // 挂载主项目的 store 和 router 实例
125 | Reflect.defineProperty(Vue, '__share_pool__', {
126 | value: {
127 | store,
128 | },
129 | });
130 | ```
131 |
132 | #### 5. 合并主项目 / 子项目的 routes
133 |
134 | ```js
135 | import Vue from 'vue';
136 | import Router from 'vue-router';
137 |
138 | Vue.use(Router);
139 |
140 | // 获取子项目的 route-list
141 | const routes = Vue.__share_pool__.routes;
142 |
143 | export default new Router({
144 | routes: Object.values(routes).reduce((acc, prev) => acc.concat(prev), [
145 | {
146 | path: '/',
147 | redirect: '/app-typescript',
148 | },
149 | ]),
150 | });
151 | ```
152 |
153 | ### 二、子项目(app-javascript)
154 |
155 | 以 app-javascript 为例
156 |
157 | #### 1. 添加 mfv-cli-service 依赖
158 |
159 | ```bash
160 | yarn add mfv-cli-service -D
161 | ```
162 |
163 | #### 2. 修改 build script
164 |
165 | `app-javascript/package.json`
166 |
167 | ```json
168 | {
169 | "scripts": {
170 | "build": "mfv-cli-service build --report --target lib --formats umd-min ./src/main.js"
171 | }
172 | }
173 | ```
174 |
175 | 这样可以将 app-javascript 构建成一个 UMD 文件,然后在 app-entry 中引用,[参考](https://cli.vuejs.org/zh/guide/build-targets.html#库)
176 |
177 | #### 3. 配置 `vue.config.js`
178 |
179 | `app-javascript/vue.config.js`
180 |
181 | ```js
182 | const webpack = require('webpack');
183 | const APP_NAME = require('./package.json').name;
184 | const PORT = require('./package.json').devPort; // 开发模式下项目的启动端口
185 |
186 | module.exports = {
187 | publicPath: `/${APP_NAME}/`, // 必须为绝对路径;配合 app-entry 中的 proxy 配置,配合生产环境下的 Nginx 配置
188 | configureWebpack: {
189 | // 提取公共依赖
190 | externals: {
191 | vue: 'Vue',
192 | 'element-ui': 'ELEMENT',
193 | },
194 | entry: './src/main.js',
195 | output: {
196 | libraryExport: 'default', // https://cli.vuejs.org/zh/guide/build-targets.html#应用
197 | jsonpFunction: `webpackJsonp-${APP_NAME}`, // 解决默认情况下子项目 chunkname 冲突的问题
198 | },
199 | plugins: [
200 | // 定义全局变量,项目中会使用
201 | new webpack.DefinePlugin({
202 | 'process.env.VUE_APP_NAME': JSON.stringify(APP_NAME),
203 | }),
204 | ],
205 | },
206 |
207 | devServer: {
208 | port: PORT,
209 | },
210 | };
211 | ```
212 |
213 | #### 4. 修改入口文件,挂载 routes
214 |
215 | `app-javascript/src/main.js`
216 |
217 | ```js
218 | import Vue from 'vue';
219 | import routes from './routes';
220 |
221 | const sharePool = (Vue.__share_pool__ = Vue.__share_pool__ || {});
222 | const routesPool = (sharePool.routes = sharePool.routes || {});
223 |
224 | // 挂载子项目的 routes
225 | routesPool[process.env.VUE_APP_NAME] = routes;
226 | ```
227 |
228 | `app-javascript/src/routes.js`
229 |
230 | ```js
231 | /* routes-list */
232 |
233 | const APP_NAME = process.env.VUE_APP_NAME;
234 |
235 | const App = () => import('./App.vue');
236 | const Home = () => import('./views/Home.vue');
237 | const About = () => import('./views/About.vue');
238 |
239 | export default [
240 | {
241 | path: `/${APP_NAME}`,
242 | name: APP_NAME,
243 | redirect: { name: `${APP_NAME}.home` },
244 | component: App,
245 | children: [
246 | {
247 | path: 'home',
248 | name: `${APP_NAME}.home`,
249 | component: Home,
250 | },
251 | {
252 | path: 'about',
253 | name: `${APP_NAME}.about`,
254 | component: About,
255 | },
256 | ],
257 | },
258 | ];
259 | ```
260 |
261 | #### 4. 异步挂载 store module
262 |
263 | `app-javascript/src/base.js`
264 |
265 | ```js
266 | import Vue from 'vue';
267 | import store from './store';
268 |
269 | // 子项目异步注册 store module
270 | Vue.__share_pool__.store.registerModule(process.env.VUE_APP_NAME, store);
271 |
272 | export default null;
273 | ```
274 |
275 | `app-javascript/src/store.js`
276 |
277 | ```js
278 | /* store module */
279 |
280 | export default {
281 | namespaced: true, // namespaced must be true in module app.
282 | state: {
283 | name: process.env.VUE_APP_NAME,
284 | },
285 | mutations: {},
286 | actions: {},
287 | };
288 | ```
289 |
290 | ## 关于部署
291 |
292 | - 方式一:通过 CI 合并主项目和子项目的 dist 目录,然后部署
293 | - 方式二:将子项目当成单独服务对待,独立部署,然后通过 Nginx 反向代理合并主项目和子项目
294 |
295 | ## 不足 & 待解决的问题
296 |
297 | ### 1. 子项目缓存问题
298 |
299 | 推荐在服务端为子项目入口文件添加协商缓存。
300 |
301 | ## 参考
302 |
303 | - [微前端的那些事儿](https://github.com/phodal/microfrontends)
304 | - [用微前端的方式搭建类单页应用](https://tech.meituan.com/fe_tiny_spa.html)
305 | - [微前端 - 将微服务理念延伸到前端开发中](http://zzfe.org/#/detail/59dc6e1f91d3e35ba880fd0d)
306 | - [Micro Frontends](https://micro-frontends.org/) ([Github](https://github.com/neuland/micro-frontends))
307 | - [微前端 - 将微服务理念扩展到前端开发(实战篇)](http://insights.thoughtworkers.org/micro-frontends-2/)
308 | - [Medium: micro frontends](https://medium.com/search?q=micro%20frontends)
309 | - [single-spa: 在一个页面将多个不同的框架整合](https://github.com/CanopyTax/single-spa)
310 | - [Ueact: 渐进式,多调和策略,多渲染终端的乐高式微前端框架,以及可复用的多框架碎片化组件](https://github.com/wxyyxc1992/Ueact)
311 | - [Web Components](https://developer.mozilla.org/zh-CN/docs/Web/Web_Components)
312 | - [微前端: Alili](https://alili.tech/tags/%E5%BE%AE%E5%89%8D%E7%AB%AF/)
313 |
--------------------------------------------------------------------------------