├── 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 | 6 | -------------------------------------------------------------------------------- /applications/app-typescript/src/views/About.vue: -------------------------------------------------------------------------------- 1 | 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 | 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 | 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 | [![lerna](https://img.shields.io/badge/maintained%20with-lerna-cc00ff.svg)](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 | 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 | 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 | 16 | 17 | 51 | 52 | 76 | -------------------------------------------------------------------------------- /applications/app-javascript/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 71 | 72 | 80 | 81 | 82 | 98 | -------------------------------------------------------------------------------- /applications/app-typescript/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 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 | --------------------------------------------------------------------------------