├── packages ├── vite-micro │ ├── types │ │ ├── vue.d.ts │ │ ├── server.d.ts │ │ ├── pluginHooks.d.ts │ │ ├── client.d.ts │ │ ├── viteDevServer.d.ts │ │ ├── global.d.ts │ │ ├── index.d.ts │ │ └── federation.d.ts │ ├── src │ │ ├── node │ │ │ ├── index.ts │ │ │ ├── utils.ts │ │ │ ├── parseOptions.ts │ │ │ ├── template │ │ │ │ ├── createRemoteEntrys.ts │ │ │ │ ├── __federation_fn_import.ts │ │ │ │ ├── __remoteEntryHelper__.ts │ │ │ │ └── __federation__.ts │ │ │ ├── federation.ts │ │ │ ├── hostAddress.ts │ │ │ ├── vite-plugin-federaion │ │ │ │ ├── src │ │ │ │ │ └── dev │ │ │ │ │ │ ├── shared-development │ │ │ │ │ │ ├── parseShared.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── handleShare.ts │ │ │ │ │ │ ├── expose-development.ts │ │ │ │ │ │ └── remote-development │ │ │ │ │ │ ├── parseRemotes.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── tansformImportForRemote.ts │ │ │ │ ├── public.ts │ │ │ │ ├── index.ts │ │ │ │ └── utils │ │ │ │ │ └── index.ts │ │ │ ├── cli.ts │ │ │ ├── semver │ │ │ │ ├── constants.ts │ │ │ │ ├── utils.ts │ │ │ │ ├── compare.ts │ │ │ │ ├── satisfy.ts │ │ │ │ └── parser.ts │ │ │ └── server.ts │ │ └── client │ │ │ ├── index.ts │ │ │ ├── import.ts │ │ │ ├── shadow.ts │ │ │ ├── importShared.ts │ │ │ ├── entryImportVue.ts │ │ │ ├── semver │ │ │ ├── constants.ts │ │ │ ├── utils.ts │ │ │ ├── compare.ts │ │ │ ├── satisfy.ts │ │ │ └── parser.ts │ │ │ └── proxy.ts │ ├── .babelrc │ ├── rollup.config.js │ ├── build.config.ts │ ├── vite.config.ts │ ├── tsconfig.json │ ├── bin │ │ ├── micro.js │ │ └── openChrome.applescript │ ├── rollup.base.config.js │ ├── package.json │ └── README-zh.md └── example │ ├── packages │ ├── user │ │ ├── src │ │ │ ├── bootstrap.ts │ │ │ ├── views │ │ │ │ └── Base.vue │ │ │ ├── store │ │ │ │ └── index.ts │ │ │ ├── router │ │ │ │ ├── modules │ │ │ │ │ ├── guard.ts │ │ │ │ │ └── user.ts │ │ │ │ └── index.ts │ │ │ ├── App.vue │ │ │ └── main.ts │ │ ├── types │ │ │ ├── shims-tsx.d.ts │ │ │ └── shims-vue.d.ts │ │ ├── tsconfig.json │ │ ├── package.json │ │ └── vite.config.js │ ├── login │ │ ├── src │ │ │ ├── main.ts │ │ │ ├── views │ │ │ │ ├── Login.vue │ │ │ │ └── Button.vue │ │ │ ├── App.vue │ │ │ ├── store │ │ │ │ └── index.ts │ │ │ ├── router │ │ │ │ ├── modules │ │ │ │ │ ├── guard.ts │ │ │ │ │ └── login.ts │ │ │ │ └── index.ts │ │ │ └── bootstrap.ts │ │ ├── types │ │ │ ├── shims-tsx.d.ts │ │ │ └── shims-vue.d.ts │ │ ├── index.html │ │ ├── tsconfig.json │ │ ├── package.json │ │ └── vite.config.js │ └── main │ │ ├── src │ │ ├── plugins │ │ │ └── remote.ts │ │ ├── store │ │ │ └── index.ts │ │ ├── router │ │ │ ├── modules │ │ │ │ ├── guard.ts │ │ │ │ └── app.ts │ │ │ └── index.ts │ │ ├── views │ │ │ ├── User.vue │ │ │ ├── test.tsx │ │ │ └── Home.vue │ │ ├── App.vue │ │ └── main.ts │ │ ├── types │ │ ├── shims-tsx.d.ts │ │ └── shims-vue.d.ts │ │ ├── index.html │ │ ├── tsconfig.json │ │ ├── sw.js │ │ ├── package.json │ │ └── vite.config.js │ ├── public │ ├── lib │ │ └── test.js │ └── images │ │ └── logo.svg │ ├── server.mjs │ ├── index.html │ ├── tsconfig.json │ └── package.json ├── .gitignore ├── .editorconfig ├── .npmignore ├── pnpm-workspace.yaml ├── .prettierrc ├── package.json ├── LICENSE ├── README-zh.md └── README.md /packages/vite-micro/types/vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'vue' 2 | -------------------------------------------------------------------------------- /packages/example/packages/user/src/bootstrap.ts: -------------------------------------------------------------------------------- 1 | export { mount, unMount } from './main' 2 | -------------------------------------------------------------------------------- /packages/example/public/lib/test.js: -------------------------------------------------------------------------------- 1 | function test() { 2 | console.log('this is test') 3 | } -------------------------------------------------------------------------------- /packages/vite-micro/src/node/index.ts: -------------------------------------------------------------------------------- 1 | export { federationMicro as federation } from './federation' 2 | -------------------------------------------------------------------------------- /packages/vite-micro/src/node/utils.ts: -------------------------------------------------------------------------------- 1 | export function debug(msg: string) { 2 | console.error('--error--::', msg) 3 | } -------------------------------------------------------------------------------- /packages/example/packages/login/src/main.ts: -------------------------------------------------------------------------------- 1 | import { mount, unMount } from './bootstrap' 2 | 3 | mount('#app', '/login') 4 | -------------------------------------------------------------------------------- /packages/vite-micro/src/node/parseOptions.ts: -------------------------------------------------------------------------------- 1 | interface OptionType {} 2 | 3 | export function parseSharedToShareMap(option: any) {} 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .cache/ 3 | .vscode/ 4 | node_modules/ 5 | /public/static 6 | *yarn* 7 | *.log* 8 | .history 9 | dist/ 10 | example/dist/ 11 | -------------------------------------------------------------------------------- /packages/example/packages/login/src/views/Login.vue: -------------------------------------------------------------------------------- 1 | 2 | this is pwd 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /packages/vite-micro/src/client/index.ts: -------------------------------------------------------------------------------- 1 | export { remoteImport } from './import' 2 | export { entryImportVue } from './entryImportVue' 3 | export * from './importShared' 4 | -------------------------------------------------------------------------------- /packages/example/packages/main/src/plugins/remote.ts: -------------------------------------------------------------------------------- 1 | // import { registerRemote } from 'vite-micro' 2 | 3 | // registerRemote({ 4 | // login: '/login', 5 | // user: '/user' 6 | // }) 7 | -------------------------------------------------------------------------------- /packages/example/packages/user/src/views/Base.vue: -------------------------------------------------------------------------------- 1 | 2 | this is User.vue 3 | 4 | 5 | 7 | 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .cache/ 3 | .vscode/ 4 | node_modules/ 5 | /build 6 | /public 7 | *yarn* 8 | *.log* 9 | .prettierrc 10 | package-lock.json 11 | rollup.base.config.js 12 | rollup.config.js 13 | -------------------------------------------------------------------------------- /packages/vite-micro/src/node/template/createRemoteEntrys.ts: -------------------------------------------------------------------------------- 1 | export function generateCommonRemoteEntryFile(version: string) { 2 | return ` 3 | export * from './${version}/remoteEntry.js' 4 | ` 5 | } 6 | -------------------------------------------------------------------------------- /packages/example/packages/login/src/views/Button.vue: -------------------------------------------------------------------------------- 1 | 2 | 这是login模块等一个按钮 3 | 4 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | # all packages in direct subdirs of packages/ 3 | - 'packages/*' 4 | - 'packages/example/packages/*' 5 | # exclude packages that are inside test directories 6 | - '!**/test/**' 7 | -------------------------------------------------------------------------------- /packages/example/packages/login/src/App.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | this is login module 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/example/packages/login/src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { createPinia } from 'pinia' 2 | 3 | const store = createPinia() 4 | 5 | export function setupStore(app: any) { 6 | app.use(store) 7 | } 8 | 9 | export { store } 10 | -------------------------------------------------------------------------------- /packages/vite-micro/types/server.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'koa' 2 | 3 | declare module 'koa-static' 4 | 5 | declare module 'koa-mount' 6 | 7 | declare module 'koa-static-cache' 8 | 9 | declare module '@originjs/vite-plugin-federation' 10 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "jsxSingleQuote": false, 5 | "tabWidth": 2, 6 | "printWidth": 140, 7 | "trailingComma": "es5", 8 | "bracketSpacing": true, 9 | "jsxBracketSameLine": false, 10 | "endOfLine": "auto" 11 | } -------------------------------------------------------------------------------- /packages/example/packages/main/src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { createPinia } from 'pinia'; 2 | import type { App } from 'vue'; 3 | 4 | const store = createPinia(); 5 | 6 | export function setupStore(app: App) { 7 | app.use(store); 8 | } 9 | 10 | export { store }; 11 | -------------------------------------------------------------------------------- /packages/example/packages/user/src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { createPinia } from 'pinia'; 2 | import type { App } from 'vue'; 3 | 4 | const store = createPinia(); 5 | 6 | export function setupStore(app: App) { 7 | app.use(store); 8 | } 9 | 10 | export { store }; 11 | -------------------------------------------------------------------------------- /packages/vite-micro/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [[ 3 | "@babel/preset-env", 4 | { 5 | "modules": false 6 | } 7 | ]] 8 | // "plugins": [[ 9 | // "@babel/plugin-transform-runtime", { 10 | // "corejs": 3 11 | // } 12 | // ]] 13 | } -------------------------------------------------------------------------------- /packages/example/packages/main/src/router/modules/guard.ts: -------------------------------------------------------------------------------- 1 | import router from ".."; 2 | /** 3 | * Vue-router Navigation Guard 4 | */ 5 | //External Route Guard Before Routing 6 | router.beforeEach((to, from, next) => { }) 7 | 8 | //External Route Guard After Routing 9 | router.afterEach((to, from) => { }) -------------------------------------------------------------------------------- /packages/example/packages/user/src/router/modules/guard.ts: -------------------------------------------------------------------------------- 1 | import router from ".."; 2 | /** 3 | * Vue-router Navigation Guard 4 | */ 5 | //External Route Guard Before Routing 6 | router.beforeEach((to, from, next) => { }) 7 | 8 | //External Route Guard After Routing 9 | router.afterEach((to, from) => { }) -------------------------------------------------------------------------------- /packages/example/packages/user/src/router/modules/user.ts: -------------------------------------------------------------------------------- 1 | const userRoute = [ 2 | { 3 | path: '', 4 | redirect: '/base', 5 | }, 6 | { 7 | path: '/base', 8 | component: () => import('../../views/Base.vue'), 9 | }, 10 | ] 11 | 12 | export default userRoute 13 | -------------------------------------------------------------------------------- /packages/example/packages/login/src/router/modules/guard.ts: -------------------------------------------------------------------------------- 1 | import router from ".."; 2 | /** 3 | * Vue-router Navigation Guard 4 | */ 5 | //External Route Guard Before Routing 6 | router.beforeEach((to, from, next) => { }) 7 | 8 | //External Route Guard After Routing 9 | router.afterEach((to, from) => { }) -------------------------------------------------------------------------------- /packages/example/packages/user/src/App.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | this is User module 4 | 这是sandbox里面的颜色 5 | 6 | 7 | 8 | 9 | 10 | 11 | 16 | -------------------------------------------------------------------------------- /packages/example/packages/main/src/views/User.vue: -------------------------------------------------------------------------------- 1 | import { RouterView } from 'vue-router'; 2 | 3 | 4 | 这是sandbox外面的颜色 5 | 6 | 7 | 8 | 13 | -------------------------------------------------------------------------------- /packages/vite-micro/rollup.config.js: -------------------------------------------------------------------------------- 1 | import { createNodeConfig, createClientConfig } from './rollup.base.config' 2 | 3 | export default [ 4 | // createNodeConfig({ input: 'src/node/index.ts' }), 5 | createNodeConfig({ input: 'src/node/cli.ts', isCli: true }), 6 | createClientConfig({ input: 'src/client/index.ts', file: 'dist/client/index.js' }), 7 | ] 8 | -------------------------------------------------------------------------------- /packages/example/packages/main/src/views/test.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent, ref } from 'vue' 2 | 3 | export default defineComponent({ 4 | setup() { 5 | const count = ref(0) 6 | 7 | const handle = () => { 8 | count.value++ 9 | } 10 | 11 | return () => { 12 | return {count.value} 13 | } 14 | }, 15 | }) 16 | -------------------------------------------------------------------------------- /packages/example/packages/login/src/router/modules/login.ts: -------------------------------------------------------------------------------- 1 | import LoginPage from '../../views/Login.vue' 2 | 3 | const loginRoute = [ 4 | { 5 | path: '', 6 | redirect: '/pwd', 7 | }, 8 | { 9 | path: '/pwd', 10 | component: () => { 11 | return import('../../views/Login.vue') 12 | }, 13 | }, 14 | ] 15 | 16 | export default loginRoute 17 | -------------------------------------------------------------------------------- /packages/example/packages/main/src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | this is Home.vue 4 | 5 | 6 | 7 | 8 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /packages/vite-micro/types/pluginHooks.d.ts: -------------------------------------------------------------------------------- 1 | import { Plugin as VitePlugin } from 'vite' 2 | import type { TransformResult as TransformResult_2 } from 'rollup' 3 | 4 | export interface PluginHooks extends VitePlugin { 5 | virtualFile?: Record 6 | transform: ( 7 | this: TransformPluginContext, 8 | code: string, 9 | id: string, 10 | options?: { 11 | ssr?: boolean 12 | } 13 | ) => Promise | TransformResult_2 14 | } 15 | -------------------------------------------------------------------------------- /packages/example/packages/main/types/shims-tsx.d.ts: -------------------------------------------------------------------------------- 1 | import Vue, { VNode } from 'vue'; 2 | 3 | declare module '*.tsx' { 4 | import Vue from 'compatible-vue'; 5 | export default Vue; 6 | } 7 | 8 | declare global { 9 | namespace JSX { 10 | // tslint:disable no-empty-interface 11 | type Element = VNode; 12 | // tslint:disable no-empty-interface 13 | type ElementClass = Vue; 14 | interface IntrinsicElements { 15 | [elem: string]: any; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/example/packages/user/types/shims-tsx.d.ts: -------------------------------------------------------------------------------- 1 | import Vue, { VNode } from 'vue'; 2 | 3 | declare module '*.tsx' { 4 | import Vue from 'compatible-vue'; 5 | export default Vue; 6 | } 7 | 8 | declare global { 9 | namespace JSX { 10 | // tslint:disable no-empty-interface 11 | type Element = VNode; 12 | // tslint:disable no-empty-interface 13 | type ElementClass = Vue; 14 | interface IntrinsicElements { 15 | [elem: string]: any; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/example/packages/login/types/shims-tsx.d.ts: -------------------------------------------------------------------------------- 1 | import Vue, { VNode } from 'vue'; 2 | 3 | declare module '*.tsx' { 4 | import Vue from 'compatible-vue'; 5 | export default Vue; 6 | } 7 | 8 | declare global { 9 | namespace JSX { 10 | // tslint:disable no-empty-interface 11 | type Element = VNode; 12 | // tslint:disable no-empty-interface 13 | type ElementClass = Vue; 14 | interface IntrinsicElements { 15 | [elem: string]: any; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/example/packages/main/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /packages/vite-micro/src/node/template/__federation_fn_import.ts: -------------------------------------------------------------------------------- 1 | export const federationFnImportFunc = (moduleMapStr: string) => ` 2 | import { importSharedMicro } from 'vite-micro/client' 3 | 4 | 5 | 6 | export async function importShared(name, shareScope = 'default') { 7 | 8 | if (!window._federation_shared_moduleMap) { 9 | window._federation_shared_moduleMap = ${moduleMapStr} 10 | } 11 | 12 | return importSharedMicro(name, window._federation_shared_moduleMap, shareScope) 13 | } 14 | ` 15 | -------------------------------------------------------------------------------- /packages/vite-micro/build.config.ts: -------------------------------------------------------------------------------- 1 | import { defineBuildConfig } from 'unbuild' 2 | import pkg from '../../package.json' 3 | 4 | // externals 不好使,所以废弃 5 | 6 | export default defineBuildConfig({ 7 | entries: ['src/node/index'], 8 | clean: true, 9 | declaration: true, 10 | failOnWarn: false, 11 | outDir: 'dist/node', 12 | externals: [], //['path', ...Object.keys(pkg.dependencies), ...Object.keys(pkg.devDependencies)].filter((item) => item !== 'estree-walker'), 13 | rollup: { 14 | emitCJS: true, 15 | }, 16 | }) 17 | -------------------------------------------------------------------------------- /packages/vite-micro/src/client/import.ts: -------------------------------------------------------------------------------- 1 | import { 2 | __federation_method_ensure, 3 | __federation_method_getRemote, 4 | __federation_method_wrapDefault, 5 | __federation_method_unwrapDefault, 6 | } from '__federation__' 7 | 8 | export function splitName(name: string) { 9 | return name.split('/') 10 | } 11 | 12 | export const remoteImport: Function = async (name: string) => { 13 | const [remoteName, remoteScript] = splitName(name) 14 | // 加载脚步 15 | return __federation_method_getRemote(remoteName, remoteScript) 16 | } 17 | -------------------------------------------------------------------------------- /packages/vite-micro/types/client.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * entryImport(Vue/React/*) 引入组件时,加载组件的配置 3 | */ 4 | export declare interface ImportCompConfig { 5 | shadow?: Boolean // 是否开启 shadow模式 6 | strictShadow?: Boolean // css 是否完全隔离,shadow内的样式不会受主样式的影响 7 | mounted?: Function 8 | unMounted?: Function 9 | destroyed?: Function 10 | base?: string // 组件的挂载,绑定远程应用路由的根路径,由父(host)应用传入 11 | remoteScriptName?: string // 父(host)应用 对远程组件的命名标识 12 | } 13 | 14 | export declare interface MicroShadowRoot extends ShadowRoot { 15 | head?: Element 16 | body?: Element 17 | } 18 | -------------------------------------------------------------------------------- /packages/example/packages/login/src/router/index.ts: -------------------------------------------------------------------------------- 1 | import * as VueRouter from 'vue-router' 2 | import loginRoute from './modules/login' 3 | import type { App } from 'vue' 4 | 5 | const createRouter = (base: string) => { 6 | return VueRouter.createRouter({ 7 | history: VueRouter.createWebHistory(base || 'login'), 8 | routes: loginRoute, 9 | }) 10 | } 11 | 12 | export default createRouter 13 | 14 | export async function setupRouter(app: App, base: string) { 15 | const router = createRouter(base) 16 | app.use(router) 17 | return router 18 | } 19 | -------------------------------------------------------------------------------- /packages/vite-micro/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | 3 | // https://vitejs.dev/config/ 4 | export default defineConfig({ 5 | build: { 6 | lib: { 7 | entry: ['./src/node/index.ts'], 8 | formats: ['es', 'cjs'], 9 | }, 10 | outDir: 'dist/node', 11 | target: 'node14', 12 | minify: false, 13 | rollupOptions: { 14 | external: ['rollup', 'fs', 'fs-extra', 'os', 'path', 'crypto', 'magic-string', '@originjs/vite-plugin-federation'], 15 | output: { 16 | minifyInternalExports: false, 17 | }, 18 | }, 19 | }, 20 | }) 21 | -------------------------------------------------------------------------------- /packages/example/packages/main/src/router/index.ts: -------------------------------------------------------------------------------- 1 | import * as VueRouter from 'vue-router' 2 | import mainRoute from './modules/app' 3 | import type { App } from 'vue' 4 | 5 | const createRouter = (base: string) => 6 | VueRouter.createRouter({ 7 | mode: 'history', 8 | history: VueRouter.createWebHistory(base || '/'), 9 | routes: mainRoute, 10 | }) 11 | 12 | export default createRouter 13 | 14 | export async function setupRouter(app: App, base: string) { 15 | const router = createRouter(base) 16 | app.use(router) 17 | await router.isReady() 18 | return router 19 | } 20 | -------------------------------------------------------------------------------- /packages/vite-micro/types/viteDevServer.d.ts: -------------------------------------------------------------------------------- 1 | import { ViteDevServer as Config } from 'vite' 2 | 3 | export interface ViteDevServer extends Config { 4 | _optimizeDepsMetadata?: { 5 | hash: string 6 | browserHash: string 7 | optimized: Map 8 | } 9 | } 10 | 11 | declare interface Optimized { 12 | file: string 13 | src: string 14 | needsInterop: boolean 15 | } 16 | 17 | export interface Hostname { 18 | // undefined sets the default behaviour of server.listen 19 | host: string | undefined 20 | // resolve to localhost when possible 21 | name: string 22 | } 23 | -------------------------------------------------------------------------------- /packages/example/packages/user/src/router/index.ts: -------------------------------------------------------------------------------- 1 | import * as VueRouter from 'vue-router' 2 | import userRoute from './modules/user' 3 | import type { App } from 'vue' 4 | 5 | const createRouter = (base: string) => 6 | VueRouter.createRouter({ 7 | mode: 'history', 8 | history: VueRouter.createWebHistory(base || '/user'), 9 | routes: userRoute, 10 | }) 11 | 12 | export default createRouter 13 | 14 | export async function setupRouter(app: App, base: string) { 15 | const router = createRouter(base) 16 | app.use(router) 17 | await router.isReady() 18 | return router 19 | } 20 | -------------------------------------------------------------------------------- /packages/vite-micro/types/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'fs-extra' 2 | 3 | declare module 'path' 4 | 5 | declare module 'os' 6 | 7 | declare module '__federation__' { 8 | function __federation_method_ensure() 9 | function __federation_method_getRemote(remoteName: string, remoteScript: string) 10 | function __federation_method_wrapDefault() 11 | function __federation_method_unwrapDefault() 12 | 13 | export { __federation_method_ensure, __federation_method_getRemote, __federation_method_wrapDefault, __federation_method_unwrapDefault } 14 | } 15 | 16 | declare interface Window { 17 | [p: string]: any 18 | } 19 | -------------------------------------------------------------------------------- /packages/example/packages/user/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import { setupStore } from './store/index' 3 | import { setupRouter } from './router/index' 4 | import App from './App.vue' 5 | 6 | let app: any = null 7 | export async function mount(name: string, base: string) { 8 | app = createApp(App) 9 | 10 | // 配置store 11 | setupStore(app) 12 | 13 | // 配置router 14 | setupRouter(app, base) 15 | 16 | app.mount(name) 17 | 18 | console.log('start mount!!!', name) 19 | 20 | return app 21 | } 22 | 23 | export function unMount() { 24 | console.log('start unmount --->') 25 | app && app.$destroy() 26 | } 27 | -------------------------------------------------------------------------------- /packages/example/server.mjs: -------------------------------------------------------------------------------- 1 | import Koa from 'koa' 2 | import Static from 'koa-static' 3 | import fs from 'fs' 4 | import path from 'path' 5 | 6 | const app = new Koa() 7 | 8 | app.use(Static('./dist')) 9 | 10 | app.use(async (ctx) => { 11 | try { 12 | let template 13 | // 读取index.html模板文件 14 | template = fs.readFileSync(path.resolve('./dist', 'index.html'), 'utf-8') 15 | // template = await viteMain.transformIndexHtml(ctx.originalUrl, template) 16 | 17 | ctx.body = template 18 | } catch (e) { 19 | console.log(e.stack) 20 | } 21 | }) 22 | 23 | app.listen(8080, () => { 24 | console.log('http://localhost:8080') 25 | }) 26 | -------------------------------------------------------------------------------- /packages/example/packages/main/src/App.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Go to Home 5 | Go to Login 6 | Go to User 7 | Go to Button 8 | 9 | this is Main module 10 | 11 | 12 | 13 | 14 | 21 | 22 | -------------------------------------------------------------------------------- /packages/vite-micro/src/node/federation.ts: -------------------------------------------------------------------------------- 1 | import type { ConfigEnv, Plugin, UserConfig, PluginOption, ViteDevServer, ResolvedConfig } from 'vite' 2 | import path from 'path' 3 | import type { federationOptions, RemotesOption } from '../../types' 4 | import host from './hostAddress' 5 | import devFederation from './vite-plugin-federaion/index' 6 | 7 | const federationDefaultOption = { 8 | isRootService: true, // 判断启动服务器的模式,是否为按需启动的模式 9 | shared: [], 10 | } 11 | 12 | export function federationMicro(options: federationOptions): PluginOption { 13 | options = Object.assign(federationDefaultOption, options) 14 | 15 | return devFederation(options) 16 | } 17 | -------------------------------------------------------------------------------- /packages/vite-micro/src/node/hostAddress.ts: -------------------------------------------------------------------------------- 1 | import os from 'os' 2 | 3 | // 获取本机电脑IP 4 | function getIPAdress() { 5 | const interfaces = os.networkInterfaces() 6 | for (const devName in interfaces) { 7 | const iface: any = interfaces[devName] 8 | for (let i = 0; i < iface.length; i++) { 9 | const alias = iface[i] 10 | if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) { 11 | // console.log(alias.address); 12 | 13 | return alias.address 14 | } 15 | } 16 | } 17 | } 18 | 19 | const host = `http://${getIPAdress() || 'localhost'}:8080` //'http://localhost:8080' 20 | 21 | export default host 22 | -------------------------------------------------------------------------------- /packages/example/packages/main/types/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | // import type { 2 | // ComponentRenderProxy, 3 | // VNode, 4 | // VNodeChild, 5 | // ComponentPublicInstance, 6 | // FunctionalComponent, 7 | // PropType as VuePropType, 8 | // } from 'vue'; 9 | 10 | declare module '*.vue' { 11 | import { defineComponent } from 'vue'; 12 | const component: ReturnType; 13 | export default component; 14 | } 15 | 16 | 17 | 18 | // declare module 'vue' { 19 | // export type JSXComponent = 20 | // | { new (): ComponentPublicInstance } 21 | // | FunctionalComponent; 22 | // } 23 | 24 | declare module 'vue' 25 | 26 | declare module 'vue-router' -------------------------------------------------------------------------------- /packages/example/packages/user/types/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | // import type { 2 | // ComponentRenderProxy, 3 | // VNode, 4 | // VNodeChild, 5 | // ComponentPublicInstance, 6 | // FunctionalComponent, 7 | // PropType as VuePropType, 8 | // } from 'vue'; 9 | 10 | declare module '*.vue' { 11 | import { defineComponent } from 'vue'; 12 | const component: ReturnType; 13 | export default component; 14 | } 15 | 16 | 17 | 18 | // declare module 'vue' { 19 | // export type JSXComponent = 20 | // | { new (): ComponentPublicInstance } 21 | // | FunctionalComponent; 22 | // } 23 | 24 | declare module 'vue' 25 | 26 | declare module 'vue-router' -------------------------------------------------------------------------------- /packages/example/packages/login/types/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | // import type { 2 | // ComponentRenderProxy, 3 | // VNode, 4 | // VNodeChild, 5 | // ComponentPublicInstance, 6 | // FunctionalComponent, 7 | // PropType as VuePropType, 8 | // } from 'vue'; 9 | 10 | declare module '*.vue' { 11 | import { defineComponent } from 'vue'; 12 | const component: ReturnType; 13 | export default component; 14 | } 15 | 16 | 17 | 18 | // declare module 'vue' { 19 | // export type JSXComponent = 20 | // | { new (): ComponentPublicInstance } 21 | // | FunctionalComponent; 22 | // } 23 | 24 | declare module 'vue' 25 | 26 | declare module 'vue-router' -------------------------------------------------------------------------------- /packages/example/packages/main/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp, defineEmits } from 'vue' 2 | import { setupStore } from './store/index' 3 | import { setupRouter } from './router/index' 4 | import App from './App.vue' 5 | 6 | console.log('====defineEmits111====', defineEmits) 7 | 8 | createApp.test = true 9 | 10 | let app: any = null 11 | export async function mount(name: string, base: string) { 12 | app = createApp(App) 13 | 14 | // 配置store 15 | setupStore(app) 16 | 17 | // 配置router 18 | setupRouter(app, base) 19 | 20 | app.mount(name) 21 | 22 | console.log('start mount!!!', name) 23 | 24 | return app 25 | } 26 | 27 | export function unMount() { 28 | console.log('start unmount --->') 29 | app && app.$destroy() 30 | } 31 | 32 | mount('#app', '/') 33 | -------------------------------------------------------------------------------- /packages/example/packages/login/src/bootstrap.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import { setupStore } from './store/index' 3 | import { setupRouter } from './router/index' 4 | import App from './App.vue' 5 | 6 | console.log('======createApp.test===', createApp.test) 7 | 8 | let app: any = null 9 | export async function mount(name: string, base: string) { 10 | /** 11 | * 这个方法里面可以创建标识:window.isMicro = true 来判断是否是微应用启动 12 | * 或者直接使用window.__federation_shared__来判断也可 13 | * */ 14 | console.log('=======base======', base) 15 | app = createApp(App) 16 | 17 | // 配置store 18 | setupStore(app) 19 | 20 | // 配置router 21 | setupRouter(app, base) 22 | 23 | app.mount(name) 24 | 25 | console.log('start mount!!!', name) 26 | 27 | return app 28 | } 29 | 30 | export function unMount() { 31 | console.log('start unmount --->') 32 | app && app.$destroy() 33 | } 34 | -------------------------------------------------------------------------------- /packages/example/packages/main/src/router/modules/app.ts: -------------------------------------------------------------------------------- 1 | import { entryImportVue, remoteImport } from 'vite-micro/client' 2 | 3 | const mainRoute = [ 4 | { 5 | path: '', 6 | redirect: '/home', 7 | }, 8 | { 9 | path: '/test', 10 | component: () => import('../../views/test'), 11 | }, 12 | { 13 | path: '/home', 14 | component: () => import('../../views/Home.vue'), 15 | }, 16 | { 17 | path: '/login/:chapters*', 18 | component: () => entryImportVue('loginRemote/entry'), 19 | }, 20 | { 21 | path: '/user', 22 | component: () => import('../../views/User.vue'), 23 | children: [ 24 | { 25 | path: '/user/:chapters*', 26 | component: () => entryImportVue('userRemote/entry', { shadow: true }), 27 | }, 28 | ], 29 | }, 30 | { 31 | path: '/button', 32 | component: () => remoteImport('loginRemote/Button'), 33 | }, 34 | ] 35 | 36 | export default mainRoute 37 | -------------------------------------------------------------------------------- /packages/example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /packages/example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "sourceMap": true, 14 | "baseUrl": ".", 15 | "types": [ 16 | "vite/client", 17 | "node" 18 | ], 19 | "typeRoots": ["./node_modules/@types/", "types"], 20 | "paths": { 21 | "@/*": [ 22 | "./src*" 23 | ] 24 | }, 25 | "lib": [ 26 | "esnext", 27 | "dom", 28 | "dom.iterable", 29 | "scripthost" 30 | ] 31 | }, 32 | "include": [ 33 | "src/**/*.ts", 34 | "src/**/*.d.ts", 35 | "src/**/*.tsx", 36 | "src/**/*.vue", 37 | "**/types/*.d.ts" 38 | ], 39 | "exclude": [ 40 | "node_modules", 41 | "mock", 42 | "build", 43 | "dist", 44 | ] 45 | } -------------------------------------------------------------------------------- /packages/example/packages/login/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /packages/example/packages/main/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "sourceMap": true, 14 | "baseUrl": ".", 15 | "types": [ 16 | "vite/client", 17 | "node" 18 | ], 19 | "typeRoots": ["./node_modules/@types/", "types"], 20 | "paths": { 21 | "@/*": [ 22 | "./src*" 23 | ] 24 | }, 25 | "lib": [ 26 | "esnext", 27 | "dom", 28 | "dom.iterable", 29 | "scripthost" 30 | ] 31 | }, 32 | "include": [ 33 | "src/**/*.ts", 34 | "src/**/*.d.ts", 35 | "src/**/*.tsx", 36 | "src/**/*.vue", 37 | "**/types/*.d.ts" 38 | ], 39 | "exclude": [ 40 | "node_modules", 41 | "mock", 42 | "build", 43 | "dist", 44 | ".kiwi" 45 | ] 46 | } -------------------------------------------------------------------------------- /packages/example/packages/user/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "sourceMap": true, 14 | "baseUrl": ".", 15 | "types": [ 16 | "vite/client", 17 | "node" 18 | ], 19 | "typeRoots": ["./node_modules/@types/", "types"], 20 | "paths": { 21 | "@/*": [ 22 | "./src*" 23 | ] 24 | }, 25 | "lib": [ 26 | "esnext", 27 | "dom", 28 | "dom.iterable", 29 | "scripthost" 30 | ] 31 | }, 32 | "include": [ 33 | "src/**/*.ts", 34 | "src/**/*.d.ts", 35 | "src/**/*.tsx", 36 | "src/**/*.vue", 37 | "**/types/*.d.ts" 38 | ], 39 | "exclude": [ 40 | "node_modules", 41 | "mock", 42 | "build", 43 | "dist", 44 | ".kiwi" 45 | ] 46 | } -------------------------------------------------------------------------------- /packages/example/packages/login/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "sourceMap": true, 14 | "baseUrl": ".", 15 | "types": [ 16 | "vite/client", 17 | "node" 18 | ], 19 | "typeRoots": ["./node_modules/@types/", "types"], 20 | "paths": { 21 | "@/*": [ 22 | "./src*" 23 | ] 24 | }, 25 | "lib": [ 26 | "esnext", 27 | "dom", 28 | "dom.iterable", 29 | "scripthost" 30 | ] 31 | }, 32 | "include": [ 33 | "src/**/*.ts", 34 | "src/**/*.d.ts", 35 | "src/**/*.tsx", 36 | "src/**/*.vue", 37 | "**/types/*.d.ts" 38 | ], 39 | "exclude": [ 40 | "node_modules", 41 | "mock", 42 | "build", 43 | "dist", 44 | ".kiwi" 45 | ] 46 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@flyrain/vite-micro", 3 | "version": "0.1.0", 4 | "private": true, 5 | "description": "基于vite 微前端框架", 6 | "workspaces": [ 7 | "packages/**" 8 | ], 9 | "scripts": { 10 | "preinstall": "npx only-allow pnpm", 11 | "build": "cd packages/vite-micro && pnpm build", 12 | "start": "cd packages/example && pnpm start" 13 | }, 14 | "engines": { 15 | "node": "^14.18.0 || >=16.0.0" 16 | }, 17 | "keywords": [ 18 | "vite", 19 | "micro", 20 | "Micro frontends", 21 | "Micro Applications" 22 | ], 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/flyrain917/vite-micro" 26 | }, 27 | "author": "flyrain", 28 | "license": "MIT", 29 | "devDependencies": { 30 | "cross-env": "^7.0.2", 31 | "install": "^0.13.0", 32 | "rimraf": "^3.0.2", 33 | "rollup": "^2.79.1", 34 | "rollup-plugin-copy": "^3.4.0", 35 | "typescript": "^5.1.6", 36 | "unbuild": "^1.2.1" 37 | }, 38 | "dependencies": { 39 | "vite-plugin-externals": "^0.6.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/vite-micro/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "noImplicitOverride": true, 5 | "noUnusedLocals": false, // error: is declared but its value is never read. 6 | "noImplicitAny": false, 7 | 8 | 9 | "target": "esnext", 10 | "module": "esnext", 11 | "strict": true, 12 | "jsx": "preserve", 13 | "importHelpers": true, 14 | "moduleResolution": "node", 15 | "allowJs": true, 16 | "skipLibCheck": true, 17 | "esModuleInterop": true, 18 | "allowSyntheticDefaultImports": true, 19 | "sourceMap": true, 20 | "baseUrl": ".", 21 | "types": [], 22 | "typeRoots": [], 23 | "paths": { 24 | "@/*": [ 25 | "./src*" 26 | ], 27 | }, 28 | "lib": [ 29 | "esnext", 30 | "dom", 31 | "dom.iterable", 32 | "scripthost" 33 | ] 34 | }, 35 | "include": [ 36 | "src/**/*.ts", 37 | "src/**/*.d.ts", 38 | "src/**/*.tsx", 39 | "src/**/*.vue", 40 | "types/*.d.ts" 41 | ], 42 | "exclude": [ 43 | "node_modules", 44 | "mock", 45 | "build", 46 | "dist", 47 | ] 48 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023-present, flyrain 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/vite-micro/src/node/vite-plugin-federaion/src/dev/shared-development/parseShared.ts: -------------------------------------------------------------------------------- 1 | import type { federationOptions, RemotesOption } from 'types' 2 | import host from '../../../../hostAddress' 3 | import { parseOptions } from '../../../utils' 4 | import type { ConfigTypeSet, Exposes, Remotes, RemotesConfig, Shared, VitePluginFederationOptions } from 'types/federation' 5 | 6 | export function parseSharedOptions(options: VitePluginFederationOptions): [string, string | ConfigTypeSet][] { 7 | const shared = parseOptions( 8 | options.shared || {}, 9 | (value, key) => ({ 10 | import: true, 11 | shareScope: 'default', 12 | packagePath: key, 13 | // Whether the path is set manually 14 | manuallyPackagePathSetting: false, 15 | generate: true, 16 | }), 17 | (value, key) => { 18 | value.import = value.import ?? true 19 | value.shareScope = value.shareScope || 'default' 20 | value.packagePath = value.packagePath || key 21 | value.manuallyPackagePathSetting = value.packagePath !== key 22 | value.generate = value.generate ?? true 23 | return value 24 | } 25 | ) 26 | 27 | return shared 28 | } 29 | -------------------------------------------------------------------------------- /packages/example/packages/main/sw.js: -------------------------------------------------------------------------------- 1 | self._share_federation_ = {} 2 | // self.clients.matchAll({type: 'window', includeUncontrolled: true}) 3 | // https://www.jianshu.com/p/8c0fc2866b82 4 | 5 | self.addEventListener('install', function (e) { 6 | console.log('[Service Worker] Install') 7 | }) 8 | 9 | self.addEventListener('message', function (event) { 10 | if (event.data.type === 'window-unloaded') { 11 | self._share_federation_ = {} 12 | console.log('======clients===', self.clients) 13 | } 14 | }) 15 | 16 | self.addEventListener('fetch', function (e) { 17 | // console.log('[Service Worker] Fetched resource ' + e.request.url) 18 | const url = e.request.url 19 | 20 | if (!url.includes('vue.js')) return 21 | 22 | e.respondWith( 23 | (async () => { 24 | self._share_federation_ = self._share_federation_ || {} 25 | const vuePromise = self._share_federation_.vue 26 | console.log('=====vuePromise===', vuePromise) 27 | if (vuePromise) { 28 | self._share_federation_.vue = vuePromise.clone() 29 | return vuePromise 30 | } 31 | 32 | const vueResponse = await fetch(e.request) 33 | 34 | self._share_federation_.vue = vueResponse.clone() 35 | 36 | return vueResponse 37 | })() 38 | ) 39 | }) 40 | -------------------------------------------------------------------------------- /packages/example/packages/user/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-user", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vite --mode development", 7 | "build": "vite build" 8 | }, 9 | "dependencies": { 10 | "@vueuse/core": "9.3.0", 11 | "axios": "^0.25.0", 12 | "pinia": "^2.1.4", 13 | "vue": "3.3.0", 14 | "vue-router": "^4.0.16" 15 | }, 16 | "devDependencies": { 17 | "@babel/core": "^7.19.6", 18 | "@babel/node": "^7.19.1", 19 | "@babel/preset-env": "^7.19.4", 20 | "@types/node": "^18.7.16", 21 | "@vitejs/plugin-vue": "^4.2.3", 22 | "@vitejs/plugin-vue-jsx": "^3.0.1", 23 | "cross-env": "^7.0.3", 24 | "fast-glob": "^3.2.12", 25 | "prettier": "^2.8.0", 26 | "rimraf": "^3.0.2", 27 | "source-map": "^0.7.4", 28 | "typescript": "^4.7.4", 29 | "vite": "^4.0.0", 30 | "vite-plugin-babel-import": "^2.0.5", 31 | "vite-plugin-compression": "^0.4.0", 32 | "vite-plugin-html": "3.2.0", 33 | "vite-plugin-importer": "^0.2.5", 34 | "vite-plugin-require-transform": "^1.0.9", 35 | "vite-plugin-style-import": "^2.0.0", 36 | "vite-plugin-svg-icons": "^2.0.1", 37 | "vite-plugin-top-level-await": "^1.1.1", 38 | "vue-tsc": "^0.40.1" 39 | } 40 | } -------------------------------------------------------------------------------- /packages/example/packages/main/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-main", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vite --mode development", 7 | "start": "", 8 | "build": "vite build" 9 | }, 10 | "dependencies": { 11 | "@vueuse/core": "9.3.0", 12 | "axios": "^0.25.0", 13 | "pinia": "^2.1.4", 14 | "vue": "3.3.0", 15 | "vue-router": "^4.0.16" 16 | }, 17 | "devDependencies": { 18 | "@babel/core": "^7.19.6", 19 | "@babel/node": "^7.19.1", 20 | "@babel/preset-env": "^7.19.4", 21 | "@types/node": "^18.7.16", 22 | "@vitejs/plugin-vue": "^4.2.3", 23 | "@vitejs/plugin-vue-jsx": "^3.0.1", 24 | "cross-env": "^7.0.3", 25 | "fast-glob": "^3.2.12", 26 | "prettier": "^2.8.0", 27 | "rimraf": "^3.0.2", 28 | "source-map": "^0.7.4", 29 | "typescript": "^4.7.4", 30 | "vite": "^4.0.0", 31 | "vite-plugin-babel-import": "^2.0.5", 32 | "vite-plugin-compression": "^0.4.0", 33 | "vite-plugin-html": "3.2.0", 34 | "vite-plugin-importer": "^0.2.5", 35 | "vite-plugin-require-transform": "^1.0.9", 36 | "vite-plugin-style-import": "^2.0.0", 37 | "vite-plugin-svg-icons": "^2.0.1", 38 | "vite-plugin-top-level-await": "^1.1.1", 39 | "vue-tsc": "^0.40.1" 40 | } 41 | } -------------------------------------------------------------------------------- /packages/example/packages/login/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-login", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vite --mode development", 7 | "build": "vite build" 8 | }, 9 | "dependencies": { 10 | "@vueuse/core": "9.3.0", 11 | "axios": "^0.25.0", 12 | "pinia": "^2.1.4", 13 | "vue": "3.3.0", 14 | "vue-router": "^4.0.16" 15 | }, 16 | "devDependencies": { 17 | "@babel/core": "^7.19.6", 18 | "@babel/node": "^7.19.1", 19 | "@babel/preset-env": "^7.19.4", 20 | "@types/node": "^18.7.16", 21 | "@vitejs/plugin-vue": "^4.2.3", 22 | "@vitejs/plugin-vue-jsx": "^3.0.1", 23 | "cross-env": "^7.0.3", 24 | "fast-glob": "^3.2.12", 25 | "prettier": "^2.8.0", 26 | "rimraf": "^3.0.2", 27 | "source-map": "^0.7.4", 28 | "typescript": "^4.7.4", 29 | "vite": "^4.0.0", 30 | "vite-plugin-babel-import": "^2.0.5", 31 | "vite-plugin-compression": "^0.4.0", 32 | "vite-plugin-html": "3.2.0", 33 | "vite-plugin-importer": "^0.2.5", 34 | "vite-plugin-require-transform": "^1.0.9", 35 | "vite-plugin-style-import": "^2.0.0", 36 | "vite-plugin-svg-icons": "^2.0.1", 37 | "vite-plugin-top-level-await": "^1.1.1", 38 | "vite-plugin-externals": "^0.6.2", 39 | "vue-tsc": "^0.40.1" 40 | } 41 | } -------------------------------------------------------------------------------- /packages/example/public/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /packages/example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-vite-micro", 3 | "version": "0.0.1", 4 | "private": true, 5 | "workspaces": [ 6 | "packages/main", 7 | "packages/login", 8 | "packages/user" 9 | ], 10 | "scripts": { 11 | "start": "micro serve", 12 | "prod": "node ./server.mjs", 13 | "build": "pnpm --parallel --filter='./packages/*' build" 14 | }, 15 | "dependencies": { 16 | "@vueuse/core": "9.3.0", 17 | "axios": "^0.25.0", 18 | "pinia": "^2.1.4", 19 | "vue": "3.3.0", 20 | "vue-router": "^4.0.16" 21 | }, 22 | "devDependencies": { 23 | "@babel/core": "^7.19.6", 24 | "@babel/node": "^7.19.1", 25 | "@babel/preset-env": "^7.19.4", 26 | "@types/node": "^18.7.16", 27 | "@vitejs/plugin-vue": "^4.2.3", 28 | "@vitejs/plugin-vue-jsx": "^3.0.1", 29 | "cross-env": "^7.0.3", 30 | "fast-glob": "^3.2.12", 31 | "koa": "^2.13.4", 32 | "koa-static": "^5.0.0", 33 | "prettier": "^2.8.0", 34 | "rimraf": "^3.0.2", 35 | "source-map": "^0.7.4", 36 | "ts-node": "^10.9.1", 37 | "tslib": "^2.6.1", 38 | "typescript": "^4.7.4", 39 | "vite": "^4.0.0", 40 | "vite-micro": "workspace:*", 41 | "vite-plugin-babel-import": "^2.0.5", 42 | "vite-plugin-compression": "^0.4.0", 43 | "vite-plugin-externals": "^0.6.2", 44 | "vite-plugin-html": "3.2.0", 45 | "vite-plugin-importer": "^0.2.5", 46 | "vite-plugin-require-transform": "^1.0.9", 47 | "vite-plugin-style-import": "^2.0.0", 48 | "vite-plugin-svg-icons": "^2.0.1", 49 | "vite-plugin-top-level-await": "^1.1.1", 50 | "vue-tsc": "^0.40.1" 51 | } 52 | } -------------------------------------------------------------------------------- /packages/vite-micro/src/node/cli.ts: -------------------------------------------------------------------------------- 1 | import { cac } from 'cac' // 命令行工具: node micro.js rm div --version 2 | // import colors from 'picocolors' // cmd color 3 | 4 | const cli = cac() 5 | 6 | // global options 7 | interface GlobalCLIOptions { 8 | '--'?: string[] 9 | c?: boolean | string 10 | config?: string 11 | base?: string 12 | clearScreen?: boolean 13 | d?: boolean | string 14 | debug?: boolean | string 15 | f?: string 16 | filter?: string 17 | m?: string 18 | mode?: string 19 | force?: boolean 20 | } 21 | 22 | /** 23 | * removing global flags before passing as command specific sub-configs 24 | */ 25 | // function cleanOptions(options: Options): Omit { 26 | // const ret = { ...options } 27 | // delete ret['--'] 28 | // delete ret.c 29 | // delete ret.config 30 | // delete ret.base 31 | // delete ret.clearScreen 32 | // delete ret.d 33 | // delete ret.debug 34 | // delete ret.f 35 | // delete ret.filter 36 | // delete ret.m 37 | // delete ret.mode 38 | // return ret 39 | // } 40 | console.log('====11=') 41 | // dev 42 | cli 43 | .command('[root]', 'start dev server') // default command 44 | .alias('serve') 45 | .action(async (root: string, options: GlobalCLIOptions) => { 46 | // output structure is preserved even after bundling so require() 47 | // is ok here 48 | 49 | const { createMicroServer } = await import('./server') 50 | try { 51 | const { app } = await createMicroServer() 52 | 53 | app.listen(8080, () => { 54 | console.log('http://localhost:8080') 55 | }) 56 | } catch (e) { 57 | console.log('==ecreateMicroServer====', e) 58 | process.exit(1) 59 | } 60 | }) 61 | 62 | cli.help() 63 | 64 | cli.parse() 65 | -------------------------------------------------------------------------------- /packages/vite-micro/src/client/shadow.ts: -------------------------------------------------------------------------------- 1 | import type { ImportCompConfig } from '../../types/client' 2 | import { MicroShadowRoot } from 'types' 3 | 4 | function createHead(shadow) { 5 | const div = document.createElement('div') 6 | div.classList.add('head') 7 | shadow.appendChild(div) 8 | shadow.head = div 9 | } 10 | 11 | function createBody(shadow) { 12 | const div = document.createElement('div') 13 | div.classList.add('body') 14 | shadow.appendChild(div) 15 | shadow.body = div 16 | } 17 | 18 | /** 19 | * https://cloud.tencent.com/developer/article/1761306 20 | * webcomponent & css shadow 21 | * 22 | * 23 | 24 | 1. shadow dom 里面的样式不会影响到外面 25 | 2. 外面的样式会影响到shadow dom里面的 26 | 3. 下面如何防止外面的样式影响里面的 27 | :host { 28 | all: initial !important; 29 | } 30 | 31 | */ 32 | 33 | export function createShadow(appname: string, config: ImportCompConfig) { 34 | const appWrapper = document.getElementById(appname) 35 | const shadow: MicroShadowRoot | undefined = appWrapper?.attachShadow({ mode: 'open' }) 36 | 37 | createHead(shadow) 38 | createBody(shadow) 39 | 40 | // 阻止父元素的影响 41 | if (config.strictShadow) { 42 | const style = document.createElement('style') 43 | style.innerText = ':host {all: initial !important;}' 44 | shadow?.head?.appendChild(style) 45 | } 46 | 47 | return shadow 48 | } 49 | 50 | export function deleteShadow(appname: string, config: ImportCompConfig) { 51 | try { 52 | const appWrapper = document.getElementById(appname) 53 | if (!appWrapper || !appWrapper.shadowRoot) return 54 | 55 | const cloneDom = appWrapper.cloneNode() 56 | const parentDom = appWrapper.parentNode 57 | 58 | parentDom?.removeChild(appWrapper) 59 | 60 | parentDom?.appendChild(cloneDom) 61 | } catch (e) { 62 | console.error(e) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /packages/vite-micro/types/index.d.ts: -------------------------------------------------------------------------------- 1 | import type { RemotesObject } from '@originjs/vite-plugin-federation' 2 | 3 | export declare interface federationOptions { 4 | /** 5 | * 判断启动服务器的模式,是否为按需启动的模式 6 | */ 7 | isRootService?: boolean 8 | 9 | /** 10 | * 远程模块入口文件,本地模块可通过vite.config.ts的remotes引入 11 | */ 12 | filename?: string 13 | 14 | /** 15 | * 需要共享的模块 16 | */ 17 | shared?: SharedOption[] 18 | 19 | /** 20 | * 开发模式或生产模式 的微应用加载方式有区别 21 | */ 22 | mode?: 'development' | 'production' 23 | 24 | /** 25 | * 注册远程模块的相关配置 26 | */ 27 | remotes?: RemotesOption 28 | 29 | /** 30 | * 应用透出接口的配置 31 | */ 32 | exposes?: ExposesOption 33 | 34 | /** 35 | * 需要转译的文件类型 36 | */ 37 | transformFileTypes?: string[] 38 | } 39 | 40 | /** 41 | * 需要分享的全局组件的配置 42 | */ 43 | export type SharedOption = string | SharedObject 44 | 45 | export declare interface SharedObject { 46 | /** 47 | * 组件的名称 48 | */ 49 | name: string 50 | 51 | /** 52 | * 组件可以共享的版本号 53 | */ 54 | requiredVersion?: string 55 | } 56 | 57 | /** 58 | * 远程模块的相关配置 59 | */ 60 | export declare interface RemotesOption { 61 | [index: string]: MicroRemoteObject | RemotesObject 62 | } 63 | 64 | export declare interface MicroRemoteObject { 65 | /** 66 | * 远程模块的地址,用于生产环境 67 | */ 68 | url: string 69 | 70 | /** 71 | * 远程模块的地址,用于开发环境项目外链,不提供则默认根据项目结构来获取 72 | */ 73 | devUrl?: string 74 | 75 | external?: string 76 | 77 | /** 78 | * 远程模块入口文件,本地模块可通过vite.config.ts的remotes引入, 默认为remoteEntrys.js 引入最新的版本文件 79 | */ 80 | filename?: string 81 | } 82 | 83 | /** 84 | * 应用透出接口的配置 85 | */ 86 | export declare interface ExposesOption { 87 | /** 88 | * 组件名称:组件相对根目录地址 89 | */ 90 | [index: string]: string 91 | } 92 | 93 | export type * from './client' 94 | -------------------------------------------------------------------------------- /packages/vite-micro/src/node/template/__remoteEntryHelper__.ts: -------------------------------------------------------------------------------- 1 | import type { federationOptions, RemotesOption } from '../../../types' 2 | import type { ConfigEnv, Plugin, UserConfig, ViteDevServer, ResolvedConfig } from 'vite' 3 | import { normalizePath } from '../vite-plugin-federaion/utils' 4 | import path from 'path' 5 | 6 | export function getRemoteEntryFile(options: federationOptions, viteConfig: UserConfig) { 7 | if (!options.exposes) return '' 8 | if (!viteConfig.root) return '' 9 | 10 | let moduleMap = '' 11 | const exposes = options.exposes || {} 12 | 13 | Object.keys(options.exposes).forEach((item) => { 14 | const exposeFilepath = normalizePath(path.resolve(viteConfig.root || '', exposes[item])) 15 | moduleMap += `\n"${item}":()=> import('${exposeFilepath}').then(module => () => module),` 16 | }) 17 | 18 | return ` 19 | const exportSet = new Set(['Module', '__esModule', 'default', '_export_sfc']); 20 | let moduleMap = {${moduleMap}} 21 | const seen = {} 22 | 23 | export const get =(module) => { 24 | console.log('===get=', module) 25 | return moduleMap[module](); 26 | }; 27 | export const init =(shareScope) => { 28 | globalThis.__federation_shared__= globalThis.__federation_shared__|| {}; 29 | Object.entries(shareScope).forEach(([key, value]) => { 30 | const versionKey = Object.keys(value)[0]; 31 | const versionValue = Object.values(value)[0]; 32 | const scope = versionValue.scope || 'default' 33 | globalThis.__federation_shared__[scope] = globalThis.__federation_shared__[scope] || {}; 34 | const shared= globalThis.__federation_shared__[scope]; 35 | (shared[key] = shared[key]||{})[versionKey] = versionValue; 36 | }); 37 | }` 38 | } 39 | -------------------------------------------------------------------------------- /packages/vite-micro/bin/micro.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import { performance } from 'node:perf_hooks' 3 | 4 | if (!import.meta.url.includes('node_modules')) { 5 | try { 6 | // only available as dev dependency 7 | await import('source-map-support').then((r) => r.default.install()) 8 | } catch (e) {} 9 | } 10 | 11 | global.__vite_start_time = performance.now() 12 | 13 | // check debug mode first before requiring the CLI. 14 | const debugIndex = process.argv.findIndex((arg) => /^(?:-d|--debug)$/.test(arg)) 15 | const filterIndex = process.argv.findIndex((arg) => /^(?:-f|--filter)$/.test(arg)) 16 | const profileIndex = process.argv.indexOf('--profile') 17 | 18 | if (debugIndex > 0) { 19 | let value = process.argv[debugIndex + 1] 20 | if (!value || value.startsWith('-')) { 21 | value = 'vite:*' 22 | } else { 23 | // support debugging multiple flags with comma-separated list 24 | value = value 25 | .split(',') 26 | .map((v) => `vite:${v}`) 27 | .join(',') 28 | } 29 | process.env.DEBUG = `${process.env.DEBUG ? process.env.DEBUG + ',' : ''}${value}` 30 | 31 | if (filterIndex > 0) { 32 | const filter = process.argv[filterIndex + 1] 33 | if (filter && !filter.startsWith('-')) { 34 | process.env.VITE_DEBUG_FILTER = filter 35 | } 36 | } 37 | } 38 | function start() { 39 | return import('../dist/node/cli.js') 40 | } 41 | 42 | if (profileIndex > 0) { 43 | process.argv.splice(profileIndex, 1) 44 | const next = process.argv[profileIndex] 45 | if (next && !next.startsWith('-')) { 46 | process.argv.splice(profileIndex, 1) 47 | } 48 | const inspector = await import('node:inspector').then((r) => r.default) 49 | const session = (global.__vite_profile_session = new inspector.Session()) 50 | session.connect() 51 | session.post('Profiler.enable', () => { 52 | session.post('Profiler.start', start) 53 | }) 54 | } else { 55 | start() 56 | } 57 | -------------------------------------------------------------------------------- /packages/vite-micro/src/node/vite-plugin-federaion/public.ts: -------------------------------------------------------------------------------- 1 | // ***************************************************************************** 2 | // Copyright (C) 2022 Origin.js and others. 3 | // 4 | // This program and the accompanying materials are licensed under Mulan PSL v2. 5 | // You can use this software according to the terms and conditions of the Mulan PSL v2. 6 | // You may obtain a copy of Mulan PSL v2 at: 7 | // http://license.coscl.org.cn/MulanPSL2 8 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, 9 | // EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, 10 | // MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 11 | // See the Mulan PSL v2 for more details. 12 | // 13 | // SPDX-License-Identifier: MulanPSL-2.0 14 | // ***************************************************************************** 15 | 16 | import type { ConfigTypeSet } from 'types/federation' 17 | // for generateBundle Hook replace 18 | export const EXPOSES_MAP = new Map() 19 | export const EXPOSES_KEY_MAP = new Map() 20 | export const SHARED = 'shared' 21 | export const DYNAMIC_LOADING_CSS = 'dynamicLoadingCss' 22 | export const DYNAMIC_LOADING_CSS_PREFIX = '__v__css__' 23 | export const DEFAULT_ENTRY_FILENAME = 'remoteEntry.js' 24 | export const EXTERNALS: string[] = [] 25 | export const ROLLUP = 'rollup' 26 | export const VITE = 'vite' 27 | export const builderInfoFactory = () => ({ 28 | builder: 'rollup', 29 | version: '', 30 | assetsDir: '', 31 | isHost: false, 32 | isRemote: false, 33 | isShared: false, 34 | }) 35 | export const parsedOptions = { 36 | prodExpose: [] as (string | ConfigTypeSet)[], 37 | prodRemote: [] as (string | ConfigTypeSet)[], 38 | prodShared: [] as (string | ConfigTypeSet)[], 39 | devShared: [] as [string, string | ConfigTypeSet][], 40 | devExpose: [] as (string | ConfigTypeSet)[], 41 | devRemote: [] as [string, string | ConfigTypeSet][], 42 | } 43 | -------------------------------------------------------------------------------- /packages/vite-micro/src/client/importShared.ts: -------------------------------------------------------------------------------- 1 | import { satisfy } from './semver/satisfy' 2 | 3 | interface moduleMapType { 4 | [index: string]: { 5 | requiredVersion: string 6 | get: Function 7 | } 8 | } 9 | 10 | export async function importSharedMicro(name: string, moduleMap: moduleMapType, shareScope = 'default') { 11 | return (await getSharedFromRuntime(name, moduleMap, shareScope)) || getSharedFromLocal(name, moduleMap, shareScope) 12 | } 13 | 14 | async function getSharedFromRuntime(name: string, moduleMap: moduleMapType, shareScope = 'default'): Promise { 15 | let module: any = null 16 | window.__federation_shared__ = window.__federation_shared__ || { [shareScope]: {} } 17 | if (window?.__federation_shared__?.[shareScope]?.[name]) { 18 | const sharedModule = window.__federation_shared__[shareScope][name] 19 | 20 | if (moduleMap[name]?.requiredVersion) { 21 | if (sharedModule.version && satisfy(sharedModule.version, moduleMap[name].requiredVersion)) { 22 | module = await sharedModule.module 23 | } else { 24 | console.error(`${name} requiredVersion error`) 25 | } 26 | } else { 27 | module = await sharedModule.module 28 | } 29 | } 30 | 31 | if (module) { 32 | if (module.default && Object.keys(module).length < 2) module = module.default 33 | return module 34 | } 35 | } 36 | 37 | async function getSharedFromLocal(name: string, moduleMap: moduleMapType, shareScope = 'default') { 38 | if (moduleMap[name]) { 39 | let module = await moduleMap[name].get() 40 | if (module.default && Object.keys(module).length < 2) module = module.default 41 | window.__federation_shared__ = window.__federation_shared__ || { [shareScope]: {} } 42 | window.__federation_shared__[shareScope][name] = { 43 | module, 44 | version: module.version || moduleMap[name].requiredVersion, 45 | } 46 | 47 | return module 48 | } else { 49 | console.error(`${name} shared module from local error`) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/vite-micro/rollup.base.config.js: -------------------------------------------------------------------------------- 1 | // import babel from '@rollup/plugin-babel' 2 | import nodeResolve from '@rollup/plugin-node-resolve' 3 | import typescript from '@rollup/plugin-typescript' 4 | // import { terser } from 'rollup-plugin-terser' 5 | import commonjs from '@rollup/plugin-commonjs' 6 | import copy from 'rollup-plugin-copy' 7 | import pkg from './package.json' 8 | import path from 'path' 9 | 10 | export function createNodeConfig(config) { 11 | return { 12 | input: config.input, 13 | output: { 14 | dir: 'dist/node', 15 | name: config.name, 16 | // file: config.file, 17 | format: 'esm', 18 | sourcemap: true, 19 | exports: 'named', 20 | }, 21 | 22 | external: ['fs', 'path', ...Object.keys(pkg.dependencies), ...Object.keys(pkg.devDependencies)], 23 | 24 | plugins: [ 25 | nodeResolve({ preferBuiltins: true }), 26 | typescript({ 27 | outDir: 'dist/node', // 确保outDir在dir之内 28 | tsconfig: './tsconfig.json', // 你的tsconfig.json文件的路径 29 | }), 30 | commonjs(), 31 | config.isCli && 32 | copy({ 33 | targets: [ 34 | { 35 | src: './src/bin', 36 | dest: 'dist', 37 | }, 38 | ], 39 | }), 40 | // babel({ 41 | // babelHelpers: 'bundled', 42 | // exclude: ['node_modules/**'], 43 | // }), 44 | // config.env === 'prod' && terser(), 45 | ], 46 | } 47 | } 48 | 49 | export function createClientConfig(config) { 50 | return { 51 | input: config.input, 52 | output: { 53 | name: config.name, 54 | file: config.file, 55 | format: 'esm', 56 | sourcemap: true, 57 | exports: 'named', 58 | }, 59 | 60 | external: ['vue', '__federation__'], 61 | plugins: [ 62 | nodeResolve({ preferBuiltins: true }), 63 | typescript(), 64 | commonjs(), 65 | // babel({ 66 | // babelHelpers: 'bundled', 67 | // exclude: ['node_modules/**'], 68 | // }), 69 | // config.env === 'prod' && terser(), 70 | ], 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /packages/vite-micro/src/client/entryImportVue.ts: -------------------------------------------------------------------------------- 1 | import { defineComponent, h } from 'vue' 2 | import { remoteImport, splitName } from './import' 3 | import { createShadow, deleteShadow } from './shadow' 4 | import type { ImportCompConfig } from '../../types/client' 5 | import { registerShadowProxy, unRegisterShadowProxy } from './proxy' 6 | import { MicroShadowRoot } from 'types' 7 | 8 | interface RemoteEntry { 9 | mount?: Function 10 | unMount?: Function 11 | } 12 | 13 | /** 14 | * 15 | * @param name : '{remoteName}/{remoteScript}' 16 | * @param config 17 | * @returns 18 | */ 19 | export function getComp(name: string, config: ImportCompConfig) { 20 | const [remoteName, remoteScript] = splitName(name) 21 | 22 | if (!config) config = {} 23 | config.remoteScriptName = remoteScript 24 | 25 | let remote: RemoteEntry = {} 26 | 27 | return defineComponent({ 28 | async mounted() { 29 | let shadow: MicroShadowRoot | null = null 30 | if (config.shadow) { 31 | shadow = createShadow(remoteName, config) as MicroShadowRoot 32 | console.log('=======shadow11', shadow) 33 | const register = registerShadowProxy(remoteName, shadow) 34 | console.log('======shadow22', register) 35 | await register 36 | } 37 | 38 | console.log('=======shadow', shadow) 39 | 40 | remote = await remoteImport(name) 41 | 42 | await (remote.mount && remote.mount(shadow?.body || `#${remoteName}`, config.base, `#${remoteName}`)) 43 | 44 | unRegisterShadowProxy() 45 | 46 | return config.mounted && config.mounted() 47 | }, 48 | async beforeDestroy() { 49 | await (remote.unMount && remote.unMount()) 50 | const res = await (config.unMounted && config.unMounted()) 51 | config.shadow && deleteShadow(name, config) 52 | return res 53 | }, 54 | destroyed() { 55 | return config.destroyed && config.destroyed() 56 | }, 57 | render() { 58 | return h('div', { style: 'height: 100%' }, [h('div', { id: remoteName })]) 59 | }, 60 | }) 61 | } 62 | 63 | export async function entryImportVue(name: string, config?: ImportCompConfig) { 64 | return getComp(name, config || {}) 65 | } 66 | -------------------------------------------------------------------------------- /packages/example/packages/user/vite.config.js: -------------------------------------------------------------------------------- 1 | import { ConfigEnv, loadEnv, UserConfig, defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import vueJsx from '@vitejs/plugin-vue-jsx' 4 | import viteCompression from 'vite-plugin-compression' 5 | 6 | import path from 'path' 7 | import packageJson from './package.json' 8 | import { federation } from 'vite-micro/node' 9 | 10 | const HOST = '0.0.0.0' 11 | 12 | export default ({ mode, root, base }) => { 13 | return defineConfig({ 14 | base: base || './', 15 | root: root || './', 16 | build: { 17 | // target: 'modules', 18 | target: ['chrome89', 'edge89', 'firefox89', 'safari15'], 19 | outDir: `${path.resolve(__dirname, '../../dist')}`, 20 | assetsDir: `assets/user/${packageJson.version}`, 21 | sourcemap: mode !== 'production', 22 | minify: false, // mode !== 'development' ? 'esbuild' : false, // 'esbuild', 23 | cssCodeSplit: false, 24 | rollupOptions: { 25 | // input: [['test.html', `${path.resolve(__dirname, './index.html')}`]], 26 | input: { 27 | // main: `${path.resolve(__dirname, './src/main.ts')}`, 28 | }, 29 | output: { 30 | plugins: [], 31 | }, 32 | }, 33 | }, 34 | optimizedeps: { 35 | esbuildoptions: { 36 | target: 'esnext', 37 | }, 38 | }, 39 | server: { 40 | host: HOST, 41 | port: 3001, //process.env.PORT, 42 | fs: { 43 | strict: false, 44 | allow: ['../packages'], 45 | }, 46 | }, 47 | css: { 48 | devSourcemap: true, // 不启用这个,vite对带css的vue文件不支持sourcemap 49 | preprocessorOptions: { 50 | // less: { 51 | // modifyVars: theme, 52 | // javascriptEnabled: true, 53 | // }, 54 | }, 55 | }, 56 | resolve: { 57 | alias: [ 58 | { 59 | find: '@', 60 | replacement: `${path.resolve(__dirname, 'src')}`, 61 | }, 62 | ], 63 | }, 64 | plugins: [ 65 | vue({ 66 | jsx: true, 67 | }), 68 | 69 | vueJsx(), 70 | 71 | federation({ 72 | mode, 73 | exposes: { 74 | //远程模块对外暴露的组件列表,远程模块必填 75 | entry: './src/bootstrap.ts', 76 | }, 77 | shared: ['vue'], //本地模块和远程模块共享的依赖。本地模块需配置所有使用到的远端模块的依赖;远端模块需要配置对外提供的组件的依赖。 78 | }), 79 | 80 | // mode !== 'development' && viteCompression(), 81 | ], 82 | }) 83 | } 84 | -------------------------------------------------------------------------------- /packages/example/packages/main/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import vueJsx from '@vitejs/plugin-vue-jsx' 4 | import viteCompression from 'vite-plugin-compression' 5 | 6 | import path from 'path' 7 | import packageJson from './package.json' 8 | import { federation } from 'vite-micro/node' 9 | 10 | const HOST = '0.0.0.0' 11 | 12 | export default ({ mode, root, base }) => { 13 | return defineConfig({ 14 | base: '/', 15 | root: root || './', 16 | publicDir: '../../public/', 17 | build: { 18 | mode, 19 | // target: 'modules', 20 | target: ['chrome89', 'edge89', 'firefox89', 'safari15'], 21 | outDir: `${path.resolve(__dirname, '../../dist')}`, 22 | assetsDir: `assets/main/${packageJson.version}`, 23 | sourcemap: mode !== 'production', 24 | minify: false, //mode !== 'development' ? 'esbuild' : false, // 'esbuild', 25 | cssCodeSplit: false, 26 | rollupOptions: { 27 | // input: root ? '../../index.html' : '', 28 | // external: ['vue', 'vue-router', 'vuex'], 29 | // input: { 30 | // // main: `${path.resolve(__dirname, './src/main.ts')}`, 31 | // }, 32 | external: ['vue'], 33 | }, 34 | }, 35 | optimizedeps: { 36 | esbuildoptions: { 37 | target: 'esnext', 38 | }, 39 | }, 40 | server: { 41 | host: HOST, 42 | port: 3001, //process.env.PORT, 43 | fs: { 44 | strict: false, 45 | allow: ['../packages'], 46 | }, 47 | }, 48 | css: { 49 | devSourcemap: true, // 不启用这个,vite对带css的vue文件不支持sourcemap 50 | preprocessorOptions: { 51 | // less: { 52 | // modifyVars: theme, 53 | // javascriptEnabled: true, 54 | // }, 55 | }, 56 | }, 57 | resolve: { 58 | alias: [ 59 | { 60 | find: '@', 61 | replacement: `${path.resolve(__dirname, 'src')}`, 62 | }, 63 | ], 64 | }, 65 | plugins: [ 66 | vue({ 67 | jsx: true, 68 | }), 69 | 70 | vueJsx(), 71 | 72 | federation({ 73 | remotes: { 74 | loginRemote: { 75 | url: `/assets/login`, 76 | }, 77 | userRemote: { 78 | url: '/assets/user', 79 | }, 80 | }, 81 | shared: ['vue'], 82 | }), 83 | 84 | // mode !== 'development' && viteCompression(), 85 | ], 86 | }) 87 | } 88 | -------------------------------------------------------------------------------- /packages/vite-micro/src/node/vite-plugin-federaion/src/dev/expose-development.ts: -------------------------------------------------------------------------------- 1 | // ***************************************************************************** 2 | // Copyright (C) 2022 Origin.js and others. 3 | // 4 | // This program and the accompanying materials are licensed under Mulan PSL v2. 5 | // You can use this software according to the terms and conditions of the Mulan PSL v2. 6 | // You may obtain a copy of Mulan PSL v2 at: 7 | // http://license.coscl.org.cn/MulanPSL2 8 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, 9 | // EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, 10 | // MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 11 | // See the Mulan PSL v2 for more details. 12 | // 13 | // SPDX-License-Identifier: MulanPSL-2.0 14 | // ***************************************************************************** 15 | 16 | import { parseExposeOptions } from '../../utils' 17 | import { parsedOptions } from '../../public' 18 | import type { VitePluginFederationOptions } from 'types/federation' 19 | import type { PluginHooks } from 'types/pluginHooks' 20 | import { getRemoteEntryFile } from '../../../template/__remoteEntryHelper__' 21 | import type { federationOptions, RemotesOption } from 'types' 22 | 23 | const virtualModuleId = '__remoteEntryHelper__' 24 | const resolvedVirtualModuleId = '\0' + virtualModuleId 25 | 26 | function parseExposes(options: federationOptions) { 27 | options.exposes = options.exposes || {} 28 | let exposes = {} 29 | Object.keys(options.exposes).forEach((exposesName) => { 30 | //@ts-ignore 31 | exposes['./' + exposesName] = options.exposes[exposesName] 32 | }) 33 | 34 | options.exposes = exposes 35 | 36 | return options 37 | } 38 | 39 | export function devExposePlugin(options: federationOptions): PluginHooks { 40 | // parsedOptions.devExpose = parseExposeOptions(parseExposes(options) as VitePluginFederationOptions) 41 | 42 | let viteConfig: any = {} 43 | 44 | return { 45 | name: 'originjs:expose-development', 46 | // virtualFile: { 47 | // __remoteEntryHelper__: getRemoteEntryFile(), 48 | // }, 49 | config(config: any) { 50 | viteConfig = config 51 | }, 52 | async resolveId(...args) { 53 | if (args[0] === virtualModuleId) { 54 | return resolvedVirtualModuleId 55 | } 56 | }, 57 | load(id: string) { 58 | if (id === resolvedVirtualModuleId) return getRemoteEntryFile(options, viteConfig) 59 | }, 60 | transform(code: string, id: string) {}, 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /packages/vite-micro/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-micro", 3 | "version": "0.0.5", 4 | "private": true, 5 | "description": "基于vite 微前端框架", 6 | "type": "module", 7 | "bin": { 8 | "micro": "./bin/micro.js" 9 | }, 10 | "main": "./dist/node/index.cjs", 11 | "module": "./dist/node/index.mjs", 12 | "types": "./types/index.d.ts", 13 | "exports": { 14 | "./client": "./dist/client/index.js", 15 | "./node": { 16 | "types": "./types/index.d.ts", 17 | "import": "./dist/node/index.mjs", 18 | "require": "./dist/node/index.cjs" 19 | } 20 | }, 21 | "scripts": { 22 | "build": "rimraf dist && vite build && rollup --config rollup.config.js", 23 | "unbuild": "unbuild" 24 | }, 25 | "files": [ 26 | "bin", 27 | "dist", 28 | "src", 29 | "types", 30 | "README.md", 31 | "README-zh.md", 32 | "package.json" 33 | ], 34 | "engines": { 35 | "node": "^14.18.0 || >=16.0.0" 36 | }, 37 | "keywords": [ 38 | "vite", 39 | "micro", 40 | "Micro frontends", 41 | "Micro Applications" 42 | ], 43 | "repository": { 44 | "type": "git", 45 | "url": "git+https://github.com/flyrain917/vite-micro" 46 | }, 47 | "author": "flyrain", 48 | "license": "MIT", 49 | "peerDependencies": { 50 | "koa": "^2.13.4", 51 | "vite": "^3.0.9" 52 | }, 53 | "dependencies": { 54 | "@originjs/vite-plugin-federation": "^1.1.9", 55 | "@rollup/plugin-virtual": "^3.0.2", 56 | "cac": "^6.7.14", 57 | "estree-walker": "^3.0.3", 58 | "fs-extra": "latest", 59 | "koa-connect": "^2.1.0", 60 | "koa-mount": "^4.0.0", 61 | "koa-static": "^5.0.0", 62 | "koa-static-cache": "^5.1.4", 63 | "magic-string": "^0.30.2", 64 | "tslib": "^2.6.1" 65 | }, 66 | "devDependencies": { 67 | "@babel/core": "^7.12.3", 68 | "@babel/plugin-external-helpers": "^7.12.1", 69 | "@babel/plugin-transform-runtime": "^7.18.2", 70 | "@babel/preset-env": "^7.12.1", 71 | "@babel/runtime-corejs3": "^7.18.3", 72 | "@rollup/plugin-alias": "^3.1.9", 73 | "@rollup/plugin-babel": "^5.2.1", 74 | "@rollup/plugin-commonjs": "^11.0.2", 75 | "@rollup/plugin-image": "^2.0.5", 76 | "@rollup/plugin-node-resolve": "^7.1.1", 77 | "@rollup/plugin-terser": "^0.4.3", 78 | "@rollup/plugin-typescript": "^11.1.2", 79 | "@rollup/plugin-url": "^7.0.0", 80 | "cross-env": "^7.0.2", 81 | "install": "^0.13.0", 82 | "koa": "^2.13.4", 83 | "rimraf": "^3.0.2", 84 | "rollup": "^2.79.1", 85 | "rollup-plugin-copy": "^3.4.0", 86 | "typescript": "^5.1.6", 87 | "unbuild": "^1.2.1", 88 | "vite": "^4.0.0" 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /packages/vite-micro/bin/openChrome.applescript: -------------------------------------------------------------------------------- 1 | (* 2 | Copyright (c) 2015-present, Facebook, Inc. 3 | 4 | This source code is licensed under the MIT license found in the 5 | LICENSE file at 6 | https://github.com/facebookincubator/create-react-app/blob/master/LICENSE 7 | *) 8 | 9 | property targetTab: null 10 | property targetTabIndex: -1 11 | property targetWindow: null 12 | 13 | on run argv 14 | set theURL to item 1 of argv 15 | 16 | with timeout of 2 seconds 17 | tell application "Chrome" 18 | 19 | if (count every window) = 0 then 20 | make new window 21 | end if 22 | 23 | -- 1: Looking for tab running debugger 24 | -- then, Reload debugging tab if found 25 | -- then return 26 | set found to my lookupTabWithUrl(theURL) 27 | if found then 28 | set targetWindow's active tab index to targetTabIndex 29 | tell targetTab to reload 30 | tell targetWindow to activate 31 | set index of targetWindow to 1 32 | return 33 | end if 34 | 35 | -- 2: Looking for Empty tab 36 | -- In case debugging tab was not found 37 | -- We try to find an empty tab instead 38 | set found to my lookupTabWithUrl("chrome://newtab/") 39 | if found then 40 | set targetWindow's active tab index to targetTabIndex 41 | set URL of targetTab to theURL 42 | tell targetWindow to activate 43 | return 44 | end if 45 | 46 | -- 3: Create new tab 47 | -- both debugging and empty tab were not found 48 | -- make a new tab with url 49 | tell window 1 50 | activate 51 | make new tab with properties {URL:theURL} 52 | end tell 53 | end tell 54 | end timeout 55 | end run 56 | 57 | -- Function: 58 | -- Lookup tab with given url 59 | -- if found, store tab, index, and window in properties 60 | -- (properties were declared on top of file) 61 | on lookupTabWithUrl(lookupUrl) 62 | tell application "Chrome" 63 | -- Find a tab with the given url 64 | set found to false 65 | set theTabIndex to -1 66 | repeat with theWindow in every window 67 | set theTabIndex to 0 68 | repeat with theTab in every tab of theWindow 69 | set theTabIndex to theTabIndex + 1 70 | if (theTab's URL as string) contains lookupUrl then 71 | -- assign tab, tab index, and window to properties 72 | set targetTab to theTab 73 | set targetTabIndex to theTabIndex 74 | set targetWindow to theWindow 75 | set found to true 76 | exit repeat 77 | end if 78 | end repeat 79 | 80 | if found then 81 | exit repeat 82 | end if 83 | end repeat 84 | end tell 85 | return found 86 | end lookupTabWithUrl 87 | -------------------------------------------------------------------------------- /packages/vite-micro/src/node/semver/constants.ts: -------------------------------------------------------------------------------- 1 | // ***************************************************************************** 2 | // Copyright (C) 2022 Origin.js and others. 3 | // 4 | // This program and the accompanying materials are licensed under Mulan PSL v2. 5 | // You can use this software according to the terms and conditions of the Mulan PSL v2. 6 | // You may obtain a copy of Mulan PSL v2 at: 7 | // http://license.coscl.org.cn/MulanPSL2 8 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, 9 | // EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, 10 | // MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 11 | // See the Mulan PSL v2 for more details. 12 | // 13 | // SPDX-License-Identifier: MulanPSL-2.0 14 | // ***************************************************************************** 15 | 16 | // those constants are based on https://www.rubydoc.info/gems/semantic_range/3.0.0/SemanticRange#BUILDIDENTIFIER-constant 17 | 18 | const buildIdentifier = '[0-9A-Za-z-]+' 19 | const build = `(?:\\+(${buildIdentifier}(?:\\.${buildIdentifier})*))` 20 | const numericIdentifier = '0|[1-9]\\d*' 21 | const numericIdentifierLoose = '[0-9]+' 22 | const nonNumericIdentifier = '\\d*[a-zA-Z-][a-zA-Z0-9-]*' 23 | const preReleaseIdentifierLoose = `(?:${numericIdentifierLoose}|${nonNumericIdentifier})` 24 | const preReleaseLoose = `(?:-?(${preReleaseIdentifierLoose}(?:\\.${preReleaseIdentifierLoose})*))` 25 | const preReleaseIdentifier = `(?:${numericIdentifier}|${nonNumericIdentifier})` 26 | const preRelease = `(?:-(${preReleaseIdentifier}(?:\\.${preReleaseIdentifier})*))` 27 | const xRangeIdentifier = `${numericIdentifier}|x|X|\\*` 28 | const xRangePlain = `[v=\\s]*(${xRangeIdentifier})(?:\\.(${xRangeIdentifier})(?:\\.(${xRangeIdentifier})(?:${preRelease})?${build}?)?)?` 29 | export const hyphenRange = `^\\s*(${xRangePlain})\\s+-\\s+(${xRangePlain})\\s*$` 30 | const mainVersionLoose = `(${numericIdentifierLoose})\\.(${numericIdentifierLoose})\\.(${numericIdentifierLoose})` 31 | const loosePlain = `[v=\\s]*${mainVersionLoose}${preReleaseLoose}?${build}?` 32 | const gtlt = '((?:<|>)?=?)' 33 | export const comparatorTrim = `(\\s*)${gtlt}\\s*(${loosePlain}|${xRangePlain})` 34 | const loneTilde = '(?:~>?)' 35 | export const tildeTrim = `(\\s*)${loneTilde}\\s+` 36 | const loneCaret = '(?:\\^)' 37 | export const caretTrim = `(\\s*)${loneCaret}\\s+` 38 | export const star = '(<|>)?=?\\s*\\*' 39 | export const caret = `^${loneCaret}${xRangePlain}$` 40 | const mainVersion = `(${numericIdentifier})\\.(${numericIdentifier})\\.(${numericIdentifier})` 41 | const fullPlain = `v?${mainVersion}${preRelease}?${build}?` 42 | export const tilde = `^${loneTilde}${xRangePlain}$` 43 | export const xRange = `^${gtlt}\\s*${xRangePlain}$` 44 | export const comparator = `^${gtlt}\\s*(${fullPlain})$|^$` 45 | // copy from semver package 46 | export const gte0 = '^\\s*>=\\s*0.0.0\\s*$' 47 | -------------------------------------------------------------------------------- /packages/vite-micro/src/client/semver/constants.ts: -------------------------------------------------------------------------------- 1 | // ***************************************************************************** 2 | // Copyright (C) 2022 Origin.js and others. 3 | // 4 | // This program and the accompanying materials are licensed under Mulan PSL v2. 5 | // You can use this software according to the terms and conditions of the Mulan PSL v2. 6 | // You may obtain a copy of Mulan PSL v2 at: 7 | // http://license.coscl.org.cn/MulanPSL2 8 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, 9 | // EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, 10 | // MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 11 | // See the Mulan PSL v2 for more details. 12 | // 13 | // SPDX-License-Identifier: MulanPSL-2.0 14 | // ***************************************************************************** 15 | 16 | // those constants are based on https://www.rubydoc.info/gems/semantic_range/3.0.0/SemanticRange#BUILDIDENTIFIER-constant 17 | 18 | const buildIdentifier = '[0-9A-Za-z-]+' 19 | const build = `(?:\\+(${buildIdentifier}(?:\\.${buildIdentifier})*))` 20 | const numericIdentifier = '0|[1-9]\\d*' 21 | const numericIdentifierLoose = '[0-9]+' 22 | const nonNumericIdentifier = '\\d*[a-zA-Z-][a-zA-Z0-9-]*' 23 | const preReleaseIdentifierLoose = `(?:${numericIdentifierLoose}|${nonNumericIdentifier})` 24 | const preReleaseLoose = `(?:-?(${preReleaseIdentifierLoose}(?:\\.${preReleaseIdentifierLoose})*))` 25 | const preReleaseIdentifier = `(?:${numericIdentifier}|${nonNumericIdentifier})` 26 | const preRelease = `(?:-(${preReleaseIdentifier}(?:\\.${preReleaseIdentifier})*))` 27 | const xRangeIdentifier = `${numericIdentifier}|x|X|\\*` 28 | const xRangePlain = `[v=\\s]*(${xRangeIdentifier})(?:\\.(${xRangeIdentifier})(?:\\.(${xRangeIdentifier})(?:${preRelease})?${build}?)?)?` 29 | export const hyphenRange = `^\\s*(${xRangePlain})\\s+-\\s+(${xRangePlain})\\s*$` 30 | const mainVersionLoose = `(${numericIdentifierLoose})\\.(${numericIdentifierLoose})\\.(${numericIdentifierLoose})` 31 | const loosePlain = `[v=\\s]*${mainVersionLoose}${preReleaseLoose}?${build}?` 32 | const gtlt = '((?:<|>)?=?)' 33 | export const comparatorTrim = `(\\s*)${gtlt}\\s*(${loosePlain}|${xRangePlain})` 34 | const loneTilde = '(?:~>?)' 35 | export const tildeTrim = `(\\s*)${loneTilde}\\s+` 36 | const loneCaret = '(?:\\^)' 37 | export const caretTrim = `(\\s*)${loneCaret}\\s+` 38 | export const star = '(<|>)?=?\\s*\\*' 39 | export const caret = `^${loneCaret}${xRangePlain}$` 40 | const mainVersion = `(${numericIdentifier})\\.(${numericIdentifier})\\.(${numericIdentifier})` 41 | const fullPlain = `v?${mainVersion}${preRelease}?${build}?` 42 | export const tilde = `^${loneTilde}${xRangePlain}$` 43 | export const xRange = `^${gtlt}\\s*${xRangePlain}$` 44 | export const comparator = `^${gtlt}\\s*(${fullPlain})$|^$` 45 | // copy from semver package 46 | export const gte0 = '^\\s*>=\\s*0.0.0\\s*$' 47 | -------------------------------------------------------------------------------- /packages/vite-micro/src/node/semver/utils.ts: -------------------------------------------------------------------------------- 1 | // ***************************************************************************** 2 | // Copyright (C) 2022 Origin.js and others. 3 | // 4 | // This program and the accompanying materials are licensed under Mulan PSL v2. 5 | // You can use this software according to the terms and conditions of the Mulan PSL v2. 6 | // You may obtain a copy of Mulan PSL v2 at: 7 | // http://license.coscl.org.cn/MulanPSL2 8 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, 9 | // EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, 10 | // MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 11 | // See the Mulan PSL v2 for more details. 12 | // 13 | // SPDX-License-Identifier: MulanPSL-2.0 14 | // ***************************************************************************** 15 | 16 | import { comparator } from './constants' 17 | 18 | export function parseRegex(source: string): RegExp { 19 | return new RegExp(source) 20 | } 21 | 22 | export function isXVersion(version: string): boolean { 23 | return !version || version.toLowerCase() === 'x' || version === '*' 24 | } 25 | 26 | export function pipe( 27 | f1: (...args: TArgs) => R1, 28 | f2: (a: R1) => R2, 29 | f3: (a: R2) => R3, 30 | f4: (a: R3) => R4, 31 | f5: (a: R4) => R5, 32 | f6: (a: R5) => R6, 33 | f7: (a: R6) => R7 34 | ): (...args: TArgs) => R7 35 | export function pipe( 36 | f1: (...args: TArgs) => R1, 37 | f2: (a: R1) => R2, 38 | f3: (a: R2) => R3, 39 | f4: (a: R3) => R4, 40 | f5: (a: R4) => R5, 41 | f6: (a: R5) => R6 42 | ): (...args: TArgs) => R6 43 | export function pipe( 44 | f1: (...args: TArgs) => R1, 45 | f2: (a: R1) => R2, 46 | f3: (a: R2) => R3, 47 | f4: (a: R3) => R4, 48 | f5: (a: R4) => R5 49 | ): (...args: TArgs) => R5 50 | export function pipe( 51 | f1: (...args: TArgs) => R1, 52 | f2: (a: R1) => R2, 53 | f3: (a: R2) => R3, 54 | f4: (a: R3) => R4 55 | ): (...args: TArgs) => R4 56 | export function pipe( 57 | f1: (...args: TArgs) => R1, 58 | f2: (a: R1) => R2, 59 | f3: (a: R2) => R3 60 | ): (...args: TArgs) => R3 61 | export function pipe( 62 | f1: (...args: TArgs) => R1, 63 | f2: (a: R1) => R2 64 | ): (...args: TArgs) => R2 65 | export function pipe( 66 | f1: (...args: TArgs) => R1 67 | ): (...args: TArgs) => R1 68 | export function pipe(...fns: ((params: any) => any)[]) { 69 | return (x: unknown): any => { 70 | return fns.reduce((v, f) => f(v), x) 71 | } 72 | } 73 | 74 | export function extractComparator( 75 | comparatorString: string 76 | ): RegExpMatchArray | null { 77 | return comparatorString.match(parseRegex(comparator)) 78 | } 79 | 80 | export function combineVersion( 81 | major: string, 82 | minor: string, 83 | patch: string, 84 | preRelease: string 85 | ): string { 86 | const mainVersion = `${major}.${minor}.${patch}` 87 | 88 | if (preRelease) { 89 | return `${mainVersion}-${preRelease}` 90 | } 91 | 92 | return mainVersion 93 | } 94 | -------------------------------------------------------------------------------- /packages/vite-micro/src/client/semver/utils.ts: -------------------------------------------------------------------------------- 1 | // ***************************************************************************** 2 | // Copyright (C) 2022 Origin.js and others. 3 | // 4 | // This program and the accompanying materials are licensed under Mulan PSL v2. 5 | // You can use this software according to the terms and conditions of the Mulan PSL v2. 6 | // You may obtain a copy of Mulan PSL v2 at: 7 | // http://license.coscl.org.cn/MulanPSL2 8 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, 9 | // EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, 10 | // MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 11 | // See the Mulan PSL v2 for more details. 12 | // 13 | // SPDX-License-Identifier: MulanPSL-2.0 14 | // ***************************************************************************** 15 | 16 | import { comparator } from './constants' 17 | 18 | export function parseRegex(source: string): RegExp { 19 | return new RegExp(source) 20 | } 21 | 22 | export function isXVersion(version: string): boolean { 23 | return !version || version.toLowerCase() === 'x' || version === '*' 24 | } 25 | 26 | export function pipe( 27 | f1: (...args: TArgs) => R1, 28 | f2: (a: R1) => R2, 29 | f3: (a: R2) => R3, 30 | f4: (a: R3) => R4, 31 | f5: (a: R4) => R5, 32 | f6: (a: R5) => R6, 33 | f7: (a: R6) => R7 34 | ): (...args: TArgs) => R7 35 | export function pipe( 36 | f1: (...args: TArgs) => R1, 37 | f2: (a: R1) => R2, 38 | f3: (a: R2) => R3, 39 | f4: (a: R3) => R4, 40 | f5: (a: R4) => R5, 41 | f6: (a: R5) => R6 42 | ): (...args: TArgs) => R6 43 | export function pipe( 44 | f1: (...args: TArgs) => R1, 45 | f2: (a: R1) => R2, 46 | f3: (a: R2) => R3, 47 | f4: (a: R3) => R4, 48 | f5: (a: R4) => R5 49 | ): (...args: TArgs) => R5 50 | export function pipe( 51 | f1: (...args: TArgs) => R1, 52 | f2: (a: R1) => R2, 53 | f3: (a: R2) => R3, 54 | f4: (a: R3) => R4 55 | ): (...args: TArgs) => R4 56 | export function pipe( 57 | f1: (...args: TArgs) => R1, 58 | f2: (a: R1) => R2, 59 | f3: (a: R2) => R3 60 | ): (...args: TArgs) => R3 61 | export function pipe( 62 | f1: (...args: TArgs) => R1, 63 | f2: (a: R1) => R2 64 | ): (...args: TArgs) => R2 65 | export function pipe( 66 | f1: (...args: TArgs) => R1 67 | ): (...args: TArgs) => R1 68 | export function pipe(...fns: ((params: any) => any)[]) { 69 | return (x: unknown): any => { 70 | return fns.reduce((v, f) => f(v), x) 71 | } 72 | } 73 | 74 | export function extractComparator( 75 | comparatorString: string 76 | ): RegExpMatchArray | null { 77 | return comparatorString.match(parseRegex(comparator)) 78 | } 79 | 80 | export function combineVersion( 81 | major: string, 82 | minor: string, 83 | patch: string, 84 | preRelease: string 85 | ): string { 86 | const mainVersion = `${major}.${minor}.${patch}` 87 | 88 | if (preRelease) { 89 | return `${mainVersion}-${preRelease}` 90 | } 91 | 92 | return mainVersion 93 | } 94 | -------------------------------------------------------------------------------- /packages/vite-micro/src/node/vite-plugin-federaion/src/dev/remote-development/parseRemotes.ts: -------------------------------------------------------------------------------- 1 | import type { federationOptions, RemotesOption } from 'types' 2 | import host from '../../../../hostAddress' 3 | import { parseOptions } from '../../../utils' 4 | import type { ConfigTypeSet, Exposes, Remotes, RemotesConfig, Shared, VitePluginFederationOptions } from 'types/federation' 5 | 6 | export function parseRemotes(options: federationOptions): VitePluginFederationOptions { 7 | const remotes = options.remotes || {} 8 | Object.keys(remotes).forEach((remoteName) => { 9 | let base = remoteName.split('Remote')[0] 10 | 11 | if (options.mode === 'development') { 12 | remotes[remoteName].external = getDevRemoteFileUrl(options, remoteName, base) 13 | } else { 14 | remotes[remoteName].url = remotes[remoteName].url || `/assets/${base}` 15 | remotes[remoteName].external = 16 | remotes[remoteName].external || 17 | remotes[remoteName].url + `/${remotes[remoteName].filename || 'remoteEntrys.js'}?version=v${Date.now()}` 18 | } 19 | remotes[remoteName].from = 'vite' 20 | remotes[remoteName].format = 'esm' 21 | }) 22 | 23 | options.remotes = remotes 24 | 25 | return options as VitePluginFederationOptions 26 | } 27 | 28 | export function getDevRemoteFileUrl(options: federationOptions | { remotes: any }, remoteName: string, base: string) { 29 | // 这里外部其他项目组件引入配置devUrl 30 | const { devUrl, url, filename } = options.remotes[remoteName] 31 | 32 | if (devUrl) return `${devUrl}/@id/__remoteEntryHelper__` 33 | 34 | if (url.startsWith('http')) return url + `/${filename || 'remoteEntrys.js'}?version=v${Date.now()}` 35 | 36 | return `${host}/${base}/@id/__remoteEntryHelper__` 37 | } 38 | 39 | export function parseRemoteOptions(options: federationOptions): [string, string | ConfigTypeSet][] { 40 | const federationOptions: VitePluginFederationOptions = parseRemotes(options) 41 | return parseOptions( 42 | options.remotes ? options.remotes : {}, 43 | (item) => ({ 44 | external: Array.isArray(item) ? item : [item], 45 | shareScope: federationOptions.shareScope || 'default', 46 | format: 'esm', 47 | from: 'vite', 48 | externalType: 'url', 49 | }), 50 | (item) => ({ 51 | external: Array.isArray(item.external) ? item.external : [item.external], 52 | shareScope: item.shareScope || federationOptions.shareScope || 'default', 53 | format: item.format || 'esm', 54 | from: item.from ?? 'vite', 55 | externalType: item.externalType || 'url', 56 | }) 57 | ) 58 | } 59 | 60 | export type Remote = { id: string; regexp: RegExp; config: RemotesConfig } 61 | 62 | export function createRemotesMap(remotes: Remote[]): string { 63 | const createUrl = (remote: Remote) => { 64 | const external = remote.config.external[0] 65 | const externalType = remote.config.externalType 66 | if (externalType === 'promise') { 67 | return `()=>${external}` 68 | } else { 69 | return `'${external}'` 70 | } 71 | } 72 | return `const remotesMap = { 73 | ${remotes 74 | .map((remote) => `'${remote.id}':{url:${createUrl(remote)},format:'${remote.config.format}',from:'${remote.config.from}'}`) 75 | .join(',\n ')} 76 | };` 77 | } 78 | -------------------------------------------------------------------------------- /packages/example/packages/login/vite.config.js: -------------------------------------------------------------------------------- 1 | import { ConfigEnv, loadEnv, UserConfig, defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import vueJsx from '@vitejs/plugin-vue-jsx' 4 | import viteCompression from 'vite-plugin-compression' 5 | import { viteExternalsPlugin } from 'vite-plugin-externals' 6 | 7 | import path from 'path' 8 | import packageJson from './package.json' 9 | import { federation } from 'vite-micro/node' 10 | 11 | let exampleOnLoadPlugin = { 12 | name: 'example', 13 | setup(build) { 14 | // Load ".txt" files and return an array of words 15 | build.onLoad({ filter: /.*/ }, async (args) => { 16 | console.log('========text', text) 17 | let text = await fs.promises.readFile(args.path, 'utf8') 18 | debugger 19 | return { 20 | contents: text, 21 | loader: 'js', 22 | } 23 | }) 24 | }, 25 | } 26 | 27 | const HOST = '0.0.0.0' 28 | 29 | export default ({ mode }) => { 30 | return defineConfig({ 31 | base: './', 32 | root: './', 33 | build: { 34 | // target: 'modules', 35 | target: ['chrome89', 'edge89', 'firefox89', 'safari15'], 36 | outDir: `${path.resolve(__dirname, '../../dist')}`, 37 | assetsDir: `assets/login/${packageJson.version}`, 38 | sourcemap: mode !== 'production', 39 | minify: false, //mode !== 'development' ? 'esbuild' : false, // 'esbuild', 40 | cssCodeSplit: false, 41 | rollupOptions: { 42 | // input: [['test.html', `${path.resolve(__dirname, './index.html')}`]], 43 | input: { 44 | main: `${path.resolve(__dirname, './src/main.ts')}`, 45 | }, 46 | output: { 47 | minifyInternalExports: false, 48 | plugins: [], 49 | }, 50 | external: ['vue'], 51 | }, 52 | external: ['vue'], 53 | }, 54 | optimizeDeps: { 55 | esbuildoptions: { 56 | target: 'esnext', 57 | // external: ['vue'], 58 | // plugins: [exampleOnLoadPlugin], 59 | }, 60 | force: true, 61 | }, 62 | server: { 63 | host: HOST, 64 | port: 3001, //process.env.PORT, 65 | fs: { 66 | strict: false, 67 | allow: ['../packages'], 68 | }, 69 | force: true, 70 | }, 71 | css: { 72 | devSourcemap: true, // 不启用这个,vite对带css的vue文件不支持sourcemap 73 | preprocessorOptions: { 74 | // less: { 75 | // modifyVars: theme, 76 | // javascriptEnabled: true, 77 | // }, 78 | }, 79 | }, 80 | resolve: { 81 | alias: [ 82 | { 83 | find: '@', 84 | replacement: `${path.resolve(__dirname, 'src')}`, 85 | }, 86 | ], 87 | }, 88 | plugins: [ 89 | vue({ 90 | jsx: true, 91 | }), 92 | 93 | vueJsx(), 94 | 95 | // viteExternalsPlugin({ 96 | // vue: 'Vue', 97 | // }), 98 | 99 | federation({ 100 | mode, 101 | exposes: { 102 | //远程模块对外暴露的组件列表,远程模块必填 103 | entry: './src/bootstrap.ts', 104 | Button: './src/views/Button.vue', 105 | }, 106 | shared: ['vue'], //本地模块和远程模块共享的依赖。本地模块需配置所有使用到的远端模块的依赖;远端模块需要配置对外提供的组件的依赖。 107 | }), 108 | 109 | // mode !== 'development' && viteCompression(), 110 | ], 111 | }) 112 | } 113 | -------------------------------------------------------------------------------- /packages/vite-micro/src/node/template/__federation__.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 加载远程脚本 3 | * @param remoteMap 4 | * @param shareScope 5 | * @returns 6 | */ 7 | 8 | export default function remoteFederationTemplate(remoteMap: string) { 9 | return ` 10 | ${remoteMap} 11 | const loadJS = async (url, fn) => { 12 | const resolvedUrl = typeof url === 'function' ? await url() : url; 13 | const script = document.createElement('script') 14 | script.type = 'text/javascript'; 15 | script.onload = fn; 16 | script.src = resolvedUrl; 17 | document.getElementsByTagName('head')[0].appendChild(script); 18 | } 19 | function get(name){ 20 | return import(/* @vite-ignore */ name).then(module => ()=> { 21 | return module 22 | }) 23 | } 24 | const wrapShareScope = () => { 25 | return { 26 | __shareScope__ 27 | } 28 | } 29 | const initMap = Object.create(null); 30 | async function __federation_method_ensure(remoteId) { 31 | const remote = remotesMap[remoteId]; 32 | console.log('======remote', remoteId, remote) 33 | if (!remote.inited) { 34 | if ('var' === remote.format) { 35 | // loading js with script tag 36 | return new Promise(resolve => { 37 | const callback = () => { 38 | if (!remote.inited) { 39 | remote.lib = window[remoteId]; 40 | remote.lib.init(wrapShareScope(remote.from)) 41 | remote.inited = true; 42 | } 43 | resolve(remote.lib); 44 | } 45 | return loadJS(remote.url, callback); 46 | }); 47 | } else if (['esm', 'systemjs'].includes(remote.format)) { 48 | // loading js with import(...) 49 | return new Promise((resolve, reject) => { 50 | const getUrl = typeof remote.url === 'function' ? remote.url : () => Promise.resolve(remote.url); 51 | getUrl().then(url => { 52 | import(/* @vite-ignore */ url).then(lib => { 53 | if (!remote.inited) { 54 | const shareScope = wrapShareScope(remote.from) 55 | lib.init(shareScope); 56 | remote.lib = lib; 57 | remote.lib.init(shareScope); 58 | remote.inited = true; 59 | } 60 | resolve(remote.lib); 61 | }).catch(reject) 62 | }) 63 | }) 64 | } 65 | } else { 66 | return remote.lib; 67 | } 68 | } 69 | 70 | function __federation_method_unwrapDefault(module) { 71 | return (module?.__esModule || module?.[Symbol.toStringTag] === 'Module')?module.default:module 72 | } 73 | 74 | function __federation_method_wrapDefault(module ,need){ 75 | if (!module?.default && need) { 76 | let obj = Object.create(null); 77 | obj.default = module; 78 | obj.__esModule = true; 79 | return obj; 80 | } 81 | return module; 82 | } 83 | 84 | function __federation_method_getRemote(remoteName, componentName){ 85 | return __federation_method_ensure(remoteName).then((remote) => remote.get(componentName).then(factory => factory())); 86 | } 87 | export {__federation_method_ensure, __federation_method_getRemote , __federation_method_unwrapDefault , __federation_method_wrapDefault} 88 | ;` 89 | } 90 | -------------------------------------------------------------------------------- /packages/vite-micro/src/client/proxy.ts: -------------------------------------------------------------------------------- 1 | import { MicroShadowRoot } from 'types' 2 | 3 | const document: any = window.document 4 | 5 | const shadowMaps: any = {} 6 | const promiseArr: any[] = [] 7 | 8 | export async function registerShadowProxy(name: string, shadow: MicroShadowRoot) { 9 | // shadowMaps[name] = shadow 10 | 11 | const curTask: { promise?: Promise | string; resolve?: Function } = {} 12 | 13 | if (promiseArr.length === 0) { 14 | curTask.promise = 'resolved' 15 | proxyDocument(shadow) 16 | console.log('=====proxy11', curTask.promise, name) 17 | } else { 18 | curTask.promise = new Promise((reolve: Function) => { 19 | curTask.resolve = reolve 20 | }).then(() => { 21 | proxyDocument(shadow) 22 | }) 23 | console.log('=====proxy22', curTask.promise, name) 24 | } 25 | 26 | promiseArr.push(curTask) 27 | 28 | console.log('=====proxy33', curTask.promise, name) 29 | 30 | return curTask.promise 31 | } 32 | 33 | export function unRegisterShadowProxy() { 34 | document.getElementById = document.originGetElementById || document.getElementById 35 | document.head.appendChild = document.head.originAppendChild || document.head.appendChild 36 | document.isProxyed = false 37 | 38 | // 任务列表里面的第一个任务永远代表当前的任务,且当前任务已结束 39 | promiseArr.shift() 40 | if (promiseArr.length === 0) return 41 | // 执行下一步任务 42 | const nextTask = promiseArr[0] 43 | nextTask.resolve && nextTask.resolve() 44 | } 45 | 46 | export function proxyDocument(shadow: MicroShadowRoot) { 47 | proxyDocumentGetElementById(shadow) 48 | proxyHeadAppendChild(shadow) 49 | } 50 | 51 | /** 52 | * 在shadow里面 使用document.getElementById,使用shadow的父dom代替 53 | */ 54 | export function proxyDocumentGetElementById(shadow: MicroShadowRoot) { 55 | document.originGetElementById = document.originGetElementById || document.getElementById 56 | document.getElementById = function (selector: string) { 57 | return shadow.getElementById(selector) 58 | } 59 | document.isProxyed = true 60 | } 61 | 62 | export function proxyDocumentQuerySelector(shadow: MicroShadowRoot) { 63 | document.originQuerySelector = document.originQuerySelector || document.querySelector 64 | document.querySelector = function (selector: string) { 65 | return shadow.getElementById(selector) 66 | } 67 | document.isProxyed = true 68 | } 69 | 70 | /** 71 | * 在shadow里面 document.head.appendChild(style) 需要替换为shadow.appendChild(style) 72 | */ 73 | export function proxyHeadAppendChild(shadow: MicroShadowRoot) { 74 | document.head.originAppendChild = document.head.originAppendChild || document.head.appendChild 75 | console.log('======proxy-head11', document.head.originAppendChild) 76 | document.head.appendChild = function (style: HTMLElement) { 77 | console.log('======proxy-head', style) 78 | return shadow.head?.appendChild(style) 79 | } 80 | document.isProxyed = true 81 | } 82 | 83 | export function getCurrentFileUrl() { 84 | let url = '' 85 | try { 86 | url = import.meta.url || document.currentScript?.src 87 | } catch (e) { 88 | url = document.currentScript?.src 89 | } 90 | 91 | console.log('getCurrentFileUrl====', url) 92 | 93 | if (!url) { 94 | try { 95 | throw new Error() 96 | } catch (e: Error | any) { 97 | url = e?.sourceURL 98 | console.log('e.sourceURL====', e?.sourceURL) 99 | } 100 | } 101 | 102 | return url 103 | } 104 | -------------------------------------------------------------------------------- /packages/vite-micro/src/node/vite-plugin-federaion/src/dev/shared-development/index.ts: -------------------------------------------------------------------------------- 1 | // ***************************************************************************** 2 | // Copyright (C) 2022 Origin.js and others. 3 | // 4 | // This program and the accompanying materials are licensed under Mulan PSL v2. 5 | // You can use this software according to the terms and conditions of the Mulan PSL v2. 6 | // You may obtain a copy of Mulan PSL v2 at: 7 | // http://license.coscl.org.cn/MulanPSL2 8 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, 9 | // EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, 10 | // MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 11 | // See the Mulan PSL v2 for more details. 12 | // 13 | // SPDX-License-Identifier: MulanPSL-2.0 14 | // ***************************************************************************** 15 | 16 | import type { PluginHooks } from 'types/pluginHooks' 17 | import { parseSharedOptions } from './parseShared' 18 | import { parsedOptions } from '../../../public' 19 | import type { VitePluginFederationOptions } from 'types/federation' 20 | import type { federationOptions, RemotesOption } from 'types' 21 | import { transformImportForSahre, parseSharedToShareMap } from './handleShare' 22 | import { federationFnImportFunc } from '../../../../template/__federation_fn_import' 23 | import type { AcornNode, TransformPluginContext } from 'rollup' 24 | import { devSharedScopeCode } from './handleShare' 25 | import type { ViteDevServer } from 'types/viteDevServer' 26 | import { NAME_CHAR_REG, removeNonRegLetter, isObject } from '../../../utils' 27 | import type { Alias, Plugin } from 'vite' 28 | 29 | const shareVirsualModuleId = 'virtual:__federation_fn_import' 30 | const resolveShareVirsualModuleId = '\0' + shareVirsualModuleId 31 | 32 | export function devSharedPlugin(options: federationOptions): PluginHooks { 33 | parsedOptions.devShared = parseSharedOptions(options as VitePluginFederationOptions) 34 | 35 | let viteConfig: any = {} 36 | let viteDevServer: ViteDevServer 37 | 38 | const shareName2Prop = parsedOptions.devShared.map((devSharedItem) => devSharedItem[0]) 39 | 40 | const sharedMap = parseSharedToShareMap(options.shared || []) 41 | const federationFnImport = federationFnImportFunc(sharedMap) 42 | 43 | return { 44 | name: 'originjs:shared-development', 45 | options(inputOptions) { 46 | // vite-plugin-externals 47 | inputOptions.external = inputOptions.external || [] 48 | inputOptions.external = [...(inputOptions.external as any), ...shareName2Prop] 49 | 50 | return inputOptions 51 | }, 52 | config(config: any) { 53 | viteConfig = config 54 | config.optimizeDeps = config.optimizeDeps || {} 55 | config.optimizeDeps.exclude = config.optimizeDeps.exclude || [] 56 | config.optimizeDeps.exclude = [...config.optimizeDeps.exclude, ...shareName2Prop] 57 | }, 58 | async resolveId(...args) { 59 | if (args[0] === shareVirsualModuleId) { 60 | return resolveShareVirsualModuleId 61 | } 62 | }, 63 | configureServer(server: ViteDevServer) { 64 | // get moduleGraph for dev mode dynamic reference 65 | viteDevServer = server 66 | }, 67 | load(id: string) { 68 | if (id === resolveShareVirsualModuleId) return federationFnImport 69 | }, 70 | // @ts-ignore 71 | async transform(this: TransformPluginContext, code: string, id: string) { 72 | if (id === '\0virtual:__federation__') { 73 | const scopeCode = await devSharedScopeCode.call(this, parsedOptions.devShared as any, viteDevServer) 74 | return code.replace('__shareScope__', scopeCode.join(',')) 75 | } 76 | 77 | const result = transformImportForSahre.call(this, code, parsedOptions.devShared) 78 | 79 | return result 80 | }, 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /packages/vite-micro/src/node/semver/compare.ts: -------------------------------------------------------------------------------- 1 | // ***************************************************************************** 2 | // Copyright (C) 2022 Origin.js and others. 3 | // 4 | // This program and the accompanying materials are licensed under Mulan PSL v2. 5 | // You can use this software according to the terms and conditions of the Mulan PSL v2. 6 | // You may obtain a copy of Mulan PSL v2 at: 7 | // http://license.coscl.org.cn/MulanPSL2 8 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, 9 | // EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, 10 | // MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 11 | // See the Mulan PSL v2 for more details. 12 | // 13 | // SPDX-License-Identifier: MulanPSL-2.0 14 | // ***************************************************************************** 15 | 16 | export interface CompareAtom { 17 | operator: string 18 | version: string 19 | major: string 20 | minor: string 21 | patch: string 22 | preRelease?: string[] 23 | } 24 | 25 | function compareAtom( 26 | rangeAtom: string | number, 27 | versionAtom: string | number 28 | ): number { 29 | rangeAtom = +rangeAtom || rangeAtom 30 | versionAtom = +versionAtom || versionAtom 31 | 32 | if (rangeAtom > versionAtom) { 33 | return 1 34 | } 35 | 36 | if (rangeAtom === versionAtom) { 37 | return 0 38 | } 39 | 40 | return -1 41 | } 42 | 43 | function comparePreRelease( 44 | rangeAtom: CompareAtom, 45 | versionAtom: CompareAtom 46 | ): number { 47 | const { preRelease: rangePreRelease } = rangeAtom 48 | const { preRelease: versionPreRelease } = versionAtom 49 | 50 | if (rangePreRelease === undefined && !!versionPreRelease) { 51 | return 1 52 | } 53 | 54 | if (!!rangePreRelease && versionPreRelease === undefined) { 55 | return -1 56 | } 57 | 58 | if (rangePreRelease === undefined && versionPreRelease === undefined) { 59 | return 0 60 | } 61 | 62 | for (let i = 0, n = rangePreRelease!.length; i <= n; i++) { 63 | const rangeElement = rangePreRelease![i] 64 | const versionElement = versionPreRelease![i] 65 | 66 | if (rangeElement === versionElement) { 67 | continue 68 | } 69 | 70 | if (rangeElement === undefined && versionElement === undefined) { 71 | return 0 72 | } 73 | 74 | if (!rangeElement) { 75 | return 1 76 | } 77 | 78 | if (!versionElement) { 79 | return -1 80 | } 81 | 82 | return compareAtom(rangeElement, versionElement) 83 | } 84 | 85 | return 0 86 | } 87 | 88 | function compareVersion( 89 | rangeAtom: CompareAtom, 90 | versionAtom: CompareAtom 91 | ): number { 92 | return ( 93 | compareAtom(rangeAtom.major, versionAtom.major) || 94 | compareAtom(rangeAtom.minor, versionAtom.minor) || 95 | compareAtom(rangeAtom.patch, versionAtom.patch) || 96 | comparePreRelease(rangeAtom, versionAtom) 97 | ) 98 | } 99 | 100 | function eq(rangeAtom: CompareAtom, versionAtom: CompareAtom): boolean { 101 | return rangeAtom.version === versionAtom.version 102 | } 103 | 104 | export function compare( 105 | rangeAtom: CompareAtom, 106 | versionAtom: CompareAtom 107 | ): boolean { 108 | switch (rangeAtom.operator) { 109 | case '': 110 | case '=': 111 | return eq(rangeAtom, versionAtom) 112 | case '>': 113 | return compareVersion(rangeAtom, versionAtom) < 0 114 | case '>=': 115 | return ( 116 | eq(rangeAtom, versionAtom) || compareVersion(rangeAtom, versionAtom) < 0 117 | ) 118 | case '<': 119 | return compareVersion(rangeAtom, versionAtom) > 0 120 | case '<=': 121 | return ( 122 | eq(rangeAtom, versionAtom) || compareVersion(rangeAtom, versionAtom) > 0 123 | ) 124 | case undefined: { 125 | // mean * or x -> all versions 126 | return true 127 | } 128 | default: 129 | return false 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /packages/vite-micro/src/client/semver/compare.ts: -------------------------------------------------------------------------------- 1 | // ***************************************************************************** 2 | // Copyright (C) 2022 Origin.js and others. 3 | // 4 | // This program and the accompanying materials are licensed under Mulan PSL v2. 5 | // You can use this software according to the terms and conditions of the Mulan PSL v2. 6 | // You may obtain a copy of Mulan PSL v2 at: 7 | // http://license.coscl.org.cn/MulanPSL2 8 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, 9 | // EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, 10 | // MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 11 | // See the Mulan PSL v2 for more details. 12 | // 13 | // SPDX-License-Identifier: MulanPSL-2.0 14 | // ***************************************************************************** 15 | 16 | export interface CompareAtom { 17 | operator: string 18 | version: string 19 | major: string 20 | minor: string 21 | patch: string 22 | preRelease?: string[] 23 | } 24 | 25 | function compareAtom( 26 | rangeAtom: string | number, 27 | versionAtom: string | number 28 | ): number { 29 | rangeAtom = +rangeAtom || rangeAtom 30 | versionAtom = +versionAtom || versionAtom 31 | 32 | if (rangeAtom > versionAtom) { 33 | return 1 34 | } 35 | 36 | if (rangeAtom === versionAtom) { 37 | return 0 38 | } 39 | 40 | return -1 41 | } 42 | 43 | function comparePreRelease( 44 | rangeAtom: CompareAtom, 45 | versionAtom: CompareAtom 46 | ): number { 47 | const { preRelease: rangePreRelease } = rangeAtom 48 | const { preRelease: versionPreRelease } = versionAtom 49 | 50 | if (rangePreRelease === undefined && !!versionPreRelease) { 51 | return 1 52 | } 53 | 54 | if (!!rangePreRelease && versionPreRelease === undefined) { 55 | return -1 56 | } 57 | 58 | if (rangePreRelease === undefined && versionPreRelease === undefined) { 59 | return 0 60 | } 61 | 62 | for (let i = 0, n = rangePreRelease!.length; i <= n; i++) { 63 | const rangeElement = rangePreRelease![i] 64 | const versionElement = versionPreRelease![i] 65 | 66 | if (rangeElement === versionElement) { 67 | continue 68 | } 69 | 70 | if (rangeElement === undefined && versionElement === undefined) { 71 | return 0 72 | } 73 | 74 | if (!rangeElement) { 75 | return 1 76 | } 77 | 78 | if (!versionElement) { 79 | return -1 80 | } 81 | 82 | return compareAtom(rangeElement, versionElement) 83 | } 84 | 85 | return 0 86 | } 87 | 88 | function compareVersion( 89 | rangeAtom: CompareAtom, 90 | versionAtom: CompareAtom 91 | ): number { 92 | return ( 93 | compareAtom(rangeAtom.major, versionAtom.major) || 94 | compareAtom(rangeAtom.minor, versionAtom.minor) || 95 | compareAtom(rangeAtom.patch, versionAtom.patch) || 96 | comparePreRelease(rangeAtom, versionAtom) 97 | ) 98 | } 99 | 100 | function eq(rangeAtom: CompareAtom, versionAtom: CompareAtom): boolean { 101 | return rangeAtom.version === versionAtom.version 102 | } 103 | 104 | export function compare( 105 | rangeAtom: CompareAtom, 106 | versionAtom: CompareAtom 107 | ): boolean { 108 | switch (rangeAtom.operator) { 109 | case '': 110 | case '=': 111 | return eq(rangeAtom, versionAtom) 112 | case '>': 113 | return compareVersion(rangeAtom, versionAtom) < 0 114 | case '>=': 115 | return ( 116 | eq(rangeAtom, versionAtom) || compareVersion(rangeAtom, versionAtom) < 0 117 | ) 118 | case '<': 119 | return compareVersion(rangeAtom, versionAtom) > 0 120 | case '<=': 121 | return ( 122 | eq(rangeAtom, versionAtom) || compareVersion(rangeAtom, versionAtom) > 0 123 | ) 124 | case undefined: { 125 | // mean * or x -> all versions 126 | return true 127 | } 128 | default: 129 | return false 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /packages/vite-micro/src/node/vite-plugin-federaion/src/dev/remote-development/index.ts: -------------------------------------------------------------------------------- 1 | // ***************************************************************************** 2 | // Copyright (C) 2022 Origin.js and others. 3 | // 4 | // This program and the accompanying materials are licensed under Mulan PSL v2. 5 | // You can use this software according to the terms and conditions of the Mulan PSL v2. 6 | // You may obtain a copy of Mulan PSL v2 at: 7 | // http://license.coscl.org.cn/MulanPSL2 8 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, 9 | // EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, 10 | // MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 11 | // See the Mulan PSL v2 for more details. 12 | // 13 | // SPDX-License-Identifier: MulanPSL-2.0 14 | // ***************************************************************************** 15 | 16 | import type { UserConfig } from 'vite' 17 | import type { ConfigTypeSet, RemotesConfig, VitePluginFederationOptions } from 'types/federation' 18 | import type { federationOptions, RemotesOption } from 'types' 19 | import type { AcornNode, TransformPluginContext, TransformResult as TransformResult_2 } from 'rollup' 20 | import type { ViteDevServer } from 'types/viteDevServer' 21 | import { getFileExtname, getModuleMarker, normalizePath, REMOTE_FROM_PARAMETER } from '../../../utils' 22 | import { builderInfoFactory, parsedOptions } from '../../../public' 23 | import type { PluginHooks } from 'types/pluginHooks' 24 | import { parseRemoteOptions, createRemotesMap, Remote } from './parseRemotes' 25 | import { parseSharedOptions } from '../shared-development/parseShared' 26 | import remoteFederationTemplate from '../../../../template/__federation__' 27 | import { transformImportForRemote } from './tansformImportForRemote' 28 | 29 | export function devRemotePlugin(options: federationOptions): PluginHooks { 30 | parsedOptions.devRemote = parseRemoteOptions(options) 31 | 32 | parsedOptions.devShared = parseSharedOptions(options as VitePluginFederationOptions) 33 | 34 | const remotes: { id: string; regexp: RegExp; config: string | ConfigTypeSet }[] = [] 35 | for (const item of parsedOptions.devRemote) { 36 | remotes.push({ 37 | id: item[0], 38 | regexp: new RegExp(`^${item[0]}/.+?`), 39 | config: item[1], 40 | }) 41 | } 42 | 43 | const needHandleFileType = ['.js', '.ts', '.jsx', '.tsx', '.mjs', '.cjs', '.vue', '.svelte'] 44 | options.transformFileTypes = (options.transformFileTypes ?? []).concat(needHandleFileType).map((item) => item.toLowerCase()) 45 | const transformFileTypeSet = new Set(options.transformFileTypes) 46 | 47 | let viteDevServer: ViteDevServer 48 | 49 | const builderInfo = builderInfoFactory() 50 | 51 | const remoteMap = createRemotesMap(remotes as Remote[]) 52 | 53 | const __federation__Temp = remoteFederationTemplate(remoteMap) 54 | 55 | return { 56 | name: 'originjs:remote-development', 57 | virtualFile: { 58 | __federation__: __federation__Temp, 59 | }, 60 | config(config: UserConfig) { 61 | // need to include remotes in the optimizeDeps.exclude 62 | if (parsedOptions.devRemote.length) { 63 | const excludeRemotes: string[] = [] 64 | parsedOptions.devRemote.forEach((item) => excludeRemotes.push(item[0])) 65 | let optimizeDeps = config.optimizeDeps 66 | if (!optimizeDeps) { 67 | optimizeDeps = config.optimizeDeps = {} 68 | } 69 | if (!optimizeDeps.exclude) { 70 | optimizeDeps.exclude = [] 71 | } 72 | optimizeDeps.exclude = optimizeDeps.exclude.concat(excludeRemotes) 73 | } 74 | }, 75 | 76 | async resolveId(...args) {}, 77 | 78 | async load(id: string) {}, 79 | 80 | configureServer(server: ViteDevServer) { 81 | // get moduleGraph for dev mode dynamic reference 82 | viteDevServer = server 83 | }, 84 | 85 | transform(this: TransformPluginContext, code: string, id: string): any { 86 | // ignore some not need to handle file types 87 | const fileExtname = getFileExtname(id) 88 | if (!transformFileTypeSet.has((fileExtname ?? '').toLowerCase())) { 89 | return 90 | } 91 | 92 | const result = transformImportForRemote.call(this, code, remotes as Remote[]) 93 | 94 | return result 95 | }, 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /packages/vite-micro/src/node/semver/satisfy.ts: -------------------------------------------------------------------------------- 1 | // ***************************************************************************** 2 | // Copyright (C) 2022 Origin.js and others. 3 | // 4 | // This program and the accompanying materials are licensed under Mulan PSL v2. 5 | // You can use this software according to the terms and conditions of the Mulan PSL v2. 6 | // You may obtain a copy of Mulan PSL v2 at: 7 | // http://license.coscl.org.cn/MulanPSL2 8 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, 9 | // EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, 10 | // MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 11 | // See the Mulan PSL v2 for more details. 12 | // 13 | // SPDX-License-Identifier: MulanPSL-2.0 14 | // ***************************************************************************** 15 | 16 | import { combineVersion, extractComparator, pipe } from './utils' 17 | import { 18 | parseHyphen, 19 | parseComparatorTrim, 20 | parseTildeTrim, 21 | parseCaretTrim, 22 | parseCarets, 23 | parseTildes, 24 | parseXRanges, 25 | parseStar, 26 | parseGTE0 27 | } from './parser' 28 | import { compare } from './compare' 29 | import type { CompareAtom } from './compare' 30 | 31 | function parseComparatorString(range: string): string { 32 | return pipe( 33 | // handle caret 34 | // ^ --> * (any, kinda silly) 35 | // ^2, ^2.x, ^2.x.x --> >=2.0.0 <3.0.0-0 36 | // ^2.0, ^2.0.x --> >=2.0.0 <3.0.0-0 37 | // ^1.2, ^1.2.x --> >=1.2.0 <2.0.0-0 38 | // ^1.2.3 --> >=1.2.3 <2.0.0-0 39 | // ^1.2.0 --> >=1.2.0 <2.0.0-0 40 | parseCarets, 41 | // handle tilde 42 | // ~, ~> --> * (any, kinda silly) 43 | // ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0 <3.0.0-0 44 | // ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0 <2.1.0-0 45 | // ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0 <1.3.0-0 46 | // ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0-0 47 | // ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0-0 48 | parseTildes, 49 | parseXRanges, 50 | parseStar 51 | )(range) 52 | } 53 | 54 | function parseRange(range: string) { 55 | return pipe( 56 | // handle hyphenRange 57 | // `1.2.3 - 1.2.4` => `>=1.2.3 <=1.2.4` 58 | parseHyphen, 59 | // handle trim comparator 60 | // `> 1.2.3 < 1.2.5` => `>1.2.3 <1.2.5` 61 | parseComparatorTrim, 62 | // handle trim tilde 63 | // `~ 1.2.3` => `~1.2.3` 64 | parseTildeTrim, 65 | // handle trim caret 66 | // `^ 1.2.3` => `^1.2.3` 67 | parseCaretTrim 68 | )(range.trim()) 69 | .split(/\s+/) 70 | .join(' ') 71 | } 72 | 73 | export function satisfy(version: string, range: string): boolean { 74 | if (!version) { 75 | return false 76 | } 77 | 78 | const parsedRange = parseRange(range) 79 | const parsedComparator = parsedRange 80 | .split(' ') 81 | .map((rangeVersion) => parseComparatorString(rangeVersion)) 82 | .join(' ') 83 | const comparators = parsedComparator 84 | .split(/\s+/) 85 | .map((comparator) => parseGTE0(comparator)) 86 | const extractedVersion = extractComparator(version) 87 | 88 | if (!extractedVersion) { 89 | return false 90 | } 91 | 92 | const [ 93 | , 94 | versionOperator, 95 | , 96 | versionMajor, 97 | versionMinor, 98 | versionPatch, 99 | versionPreRelease 100 | ] = extractedVersion 101 | const versionAtom: CompareAtom = { 102 | operator: versionOperator, 103 | version: combineVersion( 104 | versionMajor, 105 | versionMinor, 106 | versionPatch, 107 | versionPreRelease 108 | ), // exclude build atom 109 | major: versionMajor, 110 | minor: versionMinor, 111 | patch: versionPatch, 112 | preRelease: versionPreRelease?.split('.') 113 | } 114 | 115 | for (const comparator of comparators) { 116 | const extractedComparator = extractComparator(comparator) 117 | 118 | if (!extractedComparator) { 119 | return false 120 | } 121 | 122 | const [ 123 | , 124 | rangeOperator, 125 | , 126 | rangeMajor, 127 | rangeMinor, 128 | rangePatch, 129 | rangePreRelease 130 | ] = extractedComparator 131 | const rangeAtom: CompareAtom = { 132 | operator: rangeOperator, 133 | version: combineVersion( 134 | rangeMajor, 135 | rangeMinor, 136 | rangePatch, 137 | rangePreRelease 138 | ), // exclude build atom 139 | major: rangeMajor, 140 | minor: rangeMinor, 141 | patch: rangePatch, 142 | preRelease: rangePreRelease?.split('.') 143 | } 144 | 145 | if (!compare(rangeAtom, versionAtom)) { 146 | return false // early return 147 | } 148 | } 149 | 150 | return true 151 | } 152 | -------------------------------------------------------------------------------- /packages/vite-micro/src/client/semver/satisfy.ts: -------------------------------------------------------------------------------- 1 | // ***************************************************************************** 2 | // Copyright (C) 2022 Origin.js and others. 3 | // 4 | // This program and the accompanying materials are licensed under Mulan PSL v2. 5 | // You can use this software according to the terms and conditions of the Mulan PSL v2. 6 | // You may obtain a copy of Mulan PSL v2 at: 7 | // http://license.coscl.org.cn/MulanPSL2 8 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, 9 | // EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, 10 | // MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 11 | // See the Mulan PSL v2 for more details. 12 | // 13 | // SPDX-License-Identifier: MulanPSL-2.0 14 | // ***************************************************************************** 15 | 16 | import { combineVersion, extractComparator, pipe } from './utils' 17 | import { 18 | parseHyphen, 19 | parseComparatorTrim, 20 | parseTildeTrim, 21 | parseCaretTrim, 22 | parseCarets, 23 | parseTildes, 24 | parseXRanges, 25 | parseStar, 26 | parseGTE0 27 | } from './parser' 28 | import { compare } from './compare' 29 | import type { CompareAtom } from './compare' 30 | 31 | function parseComparatorString(range: string): string { 32 | return pipe( 33 | // handle caret 34 | // ^ --> * (any, kinda silly) 35 | // ^2, ^2.x, ^2.x.x --> >=2.0.0 <3.0.0-0 36 | // ^2.0, ^2.0.x --> >=2.0.0 <3.0.0-0 37 | // ^1.2, ^1.2.x --> >=1.2.0 <2.0.0-0 38 | // ^1.2.3 --> >=1.2.3 <2.0.0-0 39 | // ^1.2.0 --> >=1.2.0 <2.0.0-0 40 | parseCarets, 41 | // handle tilde 42 | // ~, ~> --> * (any, kinda silly) 43 | // ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0 <3.0.0-0 44 | // ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0 <2.1.0-0 45 | // ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0 <1.3.0-0 46 | // ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0-0 47 | // ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0-0 48 | parseTildes, 49 | parseXRanges, 50 | parseStar 51 | )(range) 52 | } 53 | 54 | function parseRange(range: string) { 55 | return pipe( 56 | // handle hyphenRange 57 | // `1.2.3 - 1.2.4` => `>=1.2.3 <=1.2.4` 58 | parseHyphen, 59 | // handle trim comparator 60 | // `> 1.2.3 < 1.2.5` => `>1.2.3 <1.2.5` 61 | parseComparatorTrim, 62 | // handle trim tilde 63 | // `~ 1.2.3` => `~1.2.3` 64 | parseTildeTrim, 65 | // handle trim caret 66 | // `^ 1.2.3` => `^1.2.3` 67 | parseCaretTrim 68 | )(range.trim()) 69 | .split(/\s+/) 70 | .join(' ') 71 | } 72 | 73 | export function satisfy(version: string, range: string): boolean { 74 | if (!version) { 75 | return false 76 | } 77 | 78 | const parsedRange = parseRange(range) 79 | const parsedComparator = parsedRange 80 | .split(' ') 81 | .map((rangeVersion) => parseComparatorString(rangeVersion)) 82 | .join(' ') 83 | const comparators = parsedComparator 84 | .split(/\s+/) 85 | .map((comparator) => parseGTE0(comparator)) 86 | const extractedVersion = extractComparator(version) 87 | 88 | if (!extractedVersion) { 89 | return false 90 | } 91 | 92 | const [ 93 | , 94 | versionOperator, 95 | , 96 | versionMajor, 97 | versionMinor, 98 | versionPatch, 99 | versionPreRelease 100 | ] = extractedVersion 101 | const versionAtom: CompareAtom = { 102 | operator: versionOperator, 103 | version: combineVersion( 104 | versionMajor, 105 | versionMinor, 106 | versionPatch, 107 | versionPreRelease 108 | ), // exclude build atom 109 | major: versionMajor, 110 | minor: versionMinor, 111 | patch: versionPatch, 112 | preRelease: versionPreRelease?.split('.') 113 | } 114 | 115 | for (const comparator of comparators) { 116 | const extractedComparator = extractComparator(comparator) 117 | 118 | if (!extractedComparator) { 119 | return false 120 | } 121 | 122 | const [ 123 | , 124 | rangeOperator, 125 | , 126 | rangeMajor, 127 | rangeMinor, 128 | rangePatch, 129 | rangePreRelease 130 | ] = extractedComparator 131 | const rangeAtom: CompareAtom = { 132 | operator: rangeOperator, 133 | version: combineVersion( 134 | rangeMajor, 135 | rangeMinor, 136 | rangePatch, 137 | rangePreRelease 138 | ), // exclude build atom 139 | major: rangeMajor, 140 | minor: rangeMinor, 141 | patch: rangePatch, 142 | preRelease: rangePreRelease?.split('.') 143 | } 144 | 145 | if (!compare(rangeAtom, versionAtom)) { 146 | return false // early return 147 | } 148 | } 149 | 150 | return true 151 | } 152 | -------------------------------------------------------------------------------- /packages/vite-micro/src/node/server.ts: -------------------------------------------------------------------------------- 1 | import Koa from 'koa' 2 | import { createServer } from 'vite' 3 | import koaConnect from 'koa-connect' 4 | import Static from 'koa-static' 5 | import Mount from 'koa-mount' 6 | import staticCache from 'koa-static-cache' 7 | import { debug } from './utils' 8 | import fs from 'fs' 9 | import path from 'path' 10 | 11 | const app = new Koa() 12 | 13 | const configRoot = process.cwd() // node执行根目录及项目根目录 14 | const packageJsonPath = path.resolve(configRoot, 'package.json') 15 | 16 | if (!packageJsonPath || !fs.existsSync(packageJsonPath)) { 17 | debug('config not found!') 18 | } 19 | 20 | const pkgContent = fs.readFileSync(packageJsonPath, 'utf-8') 21 | 22 | const pdg = JSON.parse(pkgContent) 23 | 24 | const workspace = pdg.workspaces 25 | 26 | const modules = pdg.workspaces.map((space: string) => space.split('/')[1]) 27 | const mainModules = modules[0] 28 | 29 | // 判断url是否去拉取index.html 30 | function isRootIndexHtml(url: string) { 31 | let pathname = url 32 | if (url.includes('http')) { 33 | const urlObj = new URL(url) 34 | pathname = urlObj.pathname 35 | } 36 | 37 | if (pathname.includes('.')) return false 38 | 39 | return true 40 | } 41 | 42 | async function getViteConfig(viteConfigPath: string, module: string, base: string) { 43 | const viteConfigFactory = await import(viteConfigPath) 44 | const viteConfig = viteConfigFactory({ mode: 'development', root: `./packages/${module}`, base }) 45 | 46 | viteConfig.server = { 47 | middlewareMode: true, 48 | fs: { 49 | strict: false, 50 | allow: [], // ['../packages'], 51 | }, 52 | } 53 | viteConfig.configFile = false 54 | 55 | return viteConfig 56 | } 57 | 58 | function getViteConnect(module: string): Function { 59 | const base = module === mainModules ? undefined : `/${module}/` 60 | const viteConfigPath = path.resolve(configRoot, `./packages/${module}/vite.config.js`) 61 | 62 | const viteConfigPrams = { 63 | configFile: viteConfigPath, 64 | mode: 'development', 65 | root: `./packages/${module}`, 66 | base, 67 | server: { 68 | middlewareMode: true, 69 | fs: { 70 | strict: false, 71 | allow: [], // ['../packages'], 72 | }, 73 | }, 74 | } 75 | 76 | if (module === mainModules) { 77 | return async function () { 78 | console.log('open main server!!') 79 | const viteServer = await createServer(viteConfigPrams) 80 | return [koaConnect(viteServer.middlewares), viteServer] 81 | } 82 | } 83 | 84 | let viteServerConnect: any = null 85 | 86 | return async function (ctx: any, next: any) { 87 | if (ctx.originalUrl.startsWith(`/${module}/`)) { 88 | if (!viteServerConnect || (viteServerConnect && viteServerConnect.then)) { 89 | if (viteServerConnect && viteServerConnect.then) { 90 | console.log('wating--- server!!', module) 91 | viteServerConnect = await viteServerConnect 92 | } else { 93 | viteServerConnect = createServer(viteConfigPrams).then((viteServer) => koaConnect(viteServer.middlewares)) 94 | 95 | console.log('open--- server!!', module) 96 | viteServerConnect = await viteServerConnect 97 | } 98 | } 99 | 100 | if (ctx.originalUrl.includes('@id') || ctx.originalUrl.includes('@vite')) { 101 | ctx.url = ctx.url.replace(`/${module}/`, '/') 102 | return viteServerConnect(ctx, next) 103 | } 104 | 105 | if (isRootIndexHtml(ctx.originalUrl)) return next() 106 | 107 | ctx.url = ctx.url.replace(`/${module}/`, '/') 108 | return viteServerConnect(ctx, next) 109 | } else { 110 | return next() 111 | } 112 | } 113 | } 114 | 115 | function isPublicAssets(url: string) { 116 | var assets = ['.html', 'antd.css', 'vender.js', 'antd.min.js', 'antd-with-locales.min.js'] 117 | let flag = assets.find((assetsSurfix) => url.endsWith(assetsSurfix)) 118 | return flag 119 | } 120 | 121 | export async function createMicroServer() { 122 | const [mainConnect, viteMain] = await getViteConnect(mainModules)() 123 | 124 | modules.forEach((module: string) => { 125 | if (module === mainModules) return 126 | app.use(getViteConnect(module)) 127 | }) 128 | 129 | app.use(async (ctx: any, next: any) => { 130 | if (ctx.originalUrl.includes('@id') || ctx.originalUrl.includes('@vite')) { 131 | return mainConnect(ctx, next) 132 | } 133 | 134 | if (isRootIndexHtml(ctx.originalUrl)) return next() 135 | 136 | return mainConnect(ctx, next) 137 | }) 138 | 139 | app.use(async (ctx: any) => { 140 | try { 141 | let template 142 | // 读取index.html模板文件 143 | template = fs.readFileSync(path.resolve(configRoot, 'index.html'), 'utf-8') 144 | template = await viteMain.transformIndexHtml(ctx.originalUrl, template) 145 | 146 | ctx.body = template 147 | } catch (e: any) { 148 | console.log(e.stack) 149 | } 150 | }) 151 | 152 | return { app } 153 | } 154 | -------------------------------------------------------------------------------- /packages/vite-micro/src/node/vite-plugin-federaion/src/dev/remote-development/tansformImportForRemote.ts: -------------------------------------------------------------------------------- 1 | import { walk } from 'estree-walker' 2 | import MagicString from 'magic-string' 3 | import type { AcornNode, TransformPluginContext, TransformResult as TransformResult_2 } from 'rollup' 4 | import type { Remote } from './parseRemotes' 5 | 6 | export function transformImportForRemote(this: TransformPluginContext, code: string, remotes: Remote[]) { 7 | let ast: AcornNode | null = null 8 | try { 9 | ast = this.parse(code) 10 | } catch (err) { 11 | console.error(err) 12 | } 13 | if (!ast) { 14 | return null 15 | } 16 | 17 | const magicString = new MagicString(code) 18 | const hasStaticImported = new Map() 19 | 20 | let requiresRuntime = false 21 | let modify = false 22 | 23 | walk(ast as any, { 24 | enter(node: any) { 25 | if ( 26 | (node.type === 'ImportExpression' || node.type === 'ImportDeclaration' || node.type === 'ExportNamedDeclaration') && 27 | node.source?.value?.indexOf('/') > -1 28 | ) { 29 | const moduleId = node.source.value 30 | const remote = remotes.find((r) => r.regexp.test(moduleId)) 31 | const needWrap = remote?.config.from === 'vite' 32 | if (remote) { 33 | requiresRuntime = true 34 | const modName = `.${moduleId.slice(remote.id.length)}` 35 | switch (node.type) { 36 | case 'ImportExpression': { 37 | magicString.overwrite( 38 | node.start, 39 | node.end, 40 | `__federation_method_getRemote(${JSON.stringify(remote.id)} , ${JSON.stringify( 41 | modName 42 | )}).then(module=>__federation_method_wrapDefault(module, ${needWrap}))` 43 | ) 44 | break 45 | } 46 | case 'ImportDeclaration': { 47 | if (node.specifiers?.length) { 48 | const afterImportName = `__federation_var_${moduleId.replace(/[@/\\.-]/g, '')}` 49 | if (!hasStaticImported.has(moduleId)) { 50 | magicString.overwrite( 51 | node.start, 52 | node.end, 53 | `const ${afterImportName} = await __federation_method_getRemote(${JSON.stringify(remote.id)} , ${JSON.stringify( 54 | modName 55 | )});` 56 | ) 57 | hasStaticImported.set(moduleId, afterImportName) 58 | } 59 | let deconstructStr = '' 60 | node.specifiers.forEach((spec) => { 61 | // default import , like import a from 'lib' 62 | if (spec.type === 'ImportDefaultSpecifier') { 63 | magicString.appendRight(node.end, `\n let ${spec.local.name} = __federation_method_unwrapDefault(${afterImportName}) `) 64 | } else if (spec.type === 'ImportSpecifier') { 65 | // like import {a as b} from 'lib' 66 | const importedName = spec.imported.name 67 | const localName = spec.local.name 68 | deconstructStr += `${importedName === localName ? localName : `${importedName} : ${localName}`},` 69 | } else if (spec.type === 'ImportNamespaceSpecifier') { 70 | // like import * as a from 'lib' 71 | magicString.appendRight(node.end, `let {${spec.local.name}} = ${afterImportName}`) 72 | } 73 | }) 74 | if (deconstructStr.length > 0) { 75 | magicString.appendRight(node.end, `\n let {${deconstructStr.slice(0, -1)}} = ${afterImportName}`) 76 | } 77 | } 78 | break 79 | } 80 | case 'ExportNamedDeclaration': { 81 | // handle export like export {a} from 'remotes/lib' 82 | const afterImportName = `__federation_var_${moduleId.replace(/[@/\\.-]/g, '')}` 83 | if (!hasStaticImported.has(moduleId)) { 84 | hasStaticImported.set(moduleId, afterImportName) 85 | magicString.overwrite( 86 | node.start, 87 | node.end, 88 | `const ${afterImportName} = await __federation_method_getRemote(${JSON.stringify(remote.id)} , ${JSON.stringify( 89 | modName 90 | )});` 91 | ) 92 | } 93 | if (node.specifiers.length > 0) { 94 | const specifiers = node.specifiers 95 | let exportContent = '' 96 | let deconstructContent = '' 97 | specifiers.forEach((spec) => { 98 | const localName = spec.local.name 99 | const exportName = spec.exported.name 100 | const variableName = `${afterImportName}_${localName}` 101 | deconstructContent = deconstructContent.concat(`${localName}:${variableName},`) 102 | exportContent = exportContent.concat(`${variableName} as ${exportName},`) 103 | }) 104 | magicString.append(`\n const {${deconstructContent.slice(0, deconstructContent.length - 1)}} = ${afterImportName}; \n`) 105 | magicString.append(`\n export {${exportContent.slice(0, exportContent.length - 1)}}; `) 106 | } 107 | break 108 | } 109 | } 110 | } 111 | } 112 | }, 113 | }) 114 | 115 | if (requiresRuntime) { 116 | magicString.prepend( 117 | `import {__federation_method_ensure, __federation_method_getRemote , __federation_method_wrapDefault , __federation_method_unwrapDefault} from '__federation__';\n\n` 118 | ) 119 | } 120 | 121 | if (requiresRuntime || modify) { 122 | return { 123 | code: magicString.toString(), 124 | map: magicString.generateMap({ hires: true }), 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /packages/vite-micro/src/node/vite-plugin-federaion/src/dev/shared-development/handleShare.ts: -------------------------------------------------------------------------------- 1 | import type { AcornNode, TransformPluginContext, TransformResult as TransformResult_2 } from 'rollup' 2 | import { readFileSync } from 'fs' 3 | import type { ConfigTypeSet, SharedConfig } from 'types/federation' 4 | import type { SharedOption, federationOptions } from 'types' 5 | import { createRemotesMap, getFileExtname, getModuleMarker, normalizePath, REMOTE_FROM_PARAMETER } from '../../../utils' 6 | import { walk } from 'estree-walker' 7 | import MagicString from 'magic-string' 8 | 9 | export async function handleShareVersion(this: TransformPluginContext, devShared: [string, string | ConfigTypeSet][]) { 10 | for (const arr of devShared) { 11 | const config = arr[1] as SharedConfig 12 | if (!config.version && !config.manuallyPackagePathSetting) { 13 | const packageJsonPath = (await this.resolve(`${arr[0]}/package.json`))?.id 14 | if (!packageJsonPath) { 15 | this.error( 16 | `No description file or no version in description file (usually package.json) of ${arr[0]}(${packageJsonPath}). Add version to description file, or manually specify version in shared config.` 17 | ) 18 | } else { 19 | const json = JSON.parse(readFileSync(packageJsonPath, { encoding: 'utf-8' })) 20 | config.version = json.version 21 | } 22 | } 23 | } 24 | } 25 | 26 | export async function devSharedScopeCode( 27 | this: TransformPluginContext, 28 | shared: [string, string | ConfigTypeSet][], 29 | viteDevServer 30 | ): Promise { 31 | await handleShareVersion.call(this, shared) 32 | 33 | const res: string[] = [] 34 | if (shared.length) { 35 | const serverConfiguration = viteDevServer.config.server 36 | const cwdPath = normalizePath(process.cwd()) 37 | 38 | for (const item of shared) { 39 | const obj = item[1] as SharedConfig 40 | const moduleInfo = await this.resolve(obj.packagePath as string, undefined, { 41 | skipSelf: true, 42 | }) 43 | 44 | if (!moduleInfo) continue 45 | 46 | const moduleFilePath = normalizePath(moduleInfo.id) 47 | const idx = moduleFilePath.indexOf(cwdPath) 48 | 49 | const relativePath = idx === 0 ? moduleFilePath.slice(cwdPath.length) : null 50 | 51 | const sharedName = item[0] 52 | 53 | let str = '' 54 | if (typeof obj === 'object') { 55 | const origin = serverConfiguration.origin 56 | const pathname = relativePath ?? `/@fs/${moduleInfo.id}` 57 | const url = origin ? `'${origin}${pathname}'` : `window.location.origin+'${pathname}'` 58 | str += `get:()=> get(${url}, ${REMOTE_FROM_PARAMETER})` 59 | res.push(`'${sharedName}':{'${obj.version}':{${str}}}`) 60 | } 61 | } 62 | } 63 | return res 64 | } 65 | 66 | export function parseSharedToShareMap(sharedOption: SharedOption[] | []) { 67 | let modulesMap = ` 68 | { 69 | ` 70 | sharedOption.forEach((shared) => { 71 | if (typeof shared === 'string') { 72 | modulesMap += `${shared}: { 73 | get: () => import('${shared}'), 74 | },` 75 | } else { 76 | modulesMap += `${shared.name}: { 77 | get: () => import('${shared.name}'), 78 | requiredVersion: ${shared.requiredVersion} 79 | },` 80 | } 81 | }) 82 | 83 | modulesMap += '}' 84 | 85 | return modulesMap 86 | } 87 | 88 | export function transformImportForSahre(this: TransformPluginContext, code: string, devShared: [string, string | ConfigTypeSet][]) { 89 | let ast: AcornNode | null = null 90 | try { 91 | ast = this.parse(code) 92 | } catch (err) { 93 | console.error(err) 94 | } 95 | if (!ast) { 96 | return null 97 | } 98 | 99 | const magicString = new MagicString(code) 100 | 101 | let hasImportShared = false 102 | let modify = false 103 | 104 | walk(ast as any, { 105 | enter(node: any) { 106 | // handle share, eg. replace import {a} from b -> const a = importShared('b') 107 | if (node.type === 'ImportDeclaration') { 108 | let moduleName = node.source.value 109 | if (devShared.some((sharedInfo) => sharedInfo[0] === moduleName)) { 110 | const namedImportDeclaration: (string | never)[] = [] 111 | let defaultImportDeclaration: string | null = null 112 | if (!node.specifiers?.length) { 113 | // invalid import , like import './__federation_shared_lib.js' , and remove it 114 | magicString.remove(node.start, node.end) 115 | modify = true 116 | } else { 117 | node.specifiers.forEach((specify) => { 118 | if (specify.imported?.name) { 119 | namedImportDeclaration.push( 120 | `${ 121 | specify.imported.name === specify.local.name ? specify.imported.name : `${specify.imported.name}:${specify.local.name}` 122 | }` 123 | ) 124 | } else { 125 | defaultImportDeclaration = specify.local.name 126 | } 127 | }) 128 | 129 | hasImportShared = true 130 | 131 | if (defaultImportDeclaration && namedImportDeclaration.length) { 132 | // import a, {b} from 'c' -> const a = await importShared('c'); const {b} = a; 133 | const imports = namedImportDeclaration.join(',') 134 | const line = `const ${defaultImportDeclaration} = await importShared('${moduleName}');\nconst {${imports}} = ${defaultImportDeclaration};\n` 135 | // magicString.overwrite(node.start, node.end, line) 136 | magicString.overwrite(node.start, node.end, '') 137 | magicString.prepend(line) 138 | } else if (defaultImportDeclaration) { 139 | // magicString.overwrite(node.start, node.end, `const ${defaultImportDeclaration} = await importShared('${moduleName}');\n`) 140 | magicString.overwrite(node.start, node.end, '') 141 | magicString.prepend(`const ${defaultImportDeclaration} = await importShared('${moduleName}');\n`) 142 | } else if (namedImportDeclaration.length) { 143 | magicString.overwrite(node.start, node.end, '') 144 | magicString.prepend(`const {${namedImportDeclaration.join(',')}} = await importShared('${moduleName}');\n`) 145 | // magicString.overwrite( 146 | // node.start, 147 | // node.end, 148 | // `const {${namedImportDeclaration.join(',')}} = await importShared('${moduleName}');\n` 149 | // ) 150 | } 151 | } 152 | } 153 | } 154 | }, 155 | }) 156 | 157 | if (hasImportShared) { 158 | magicString.prepend(`import {importShared} from 'virtual:__federation_fn_import';\n`) 159 | } 160 | 161 | if (hasImportShared || modify) { 162 | return { 163 | code: magicString.toString(), 164 | map: magicString.generateMap({ hires: true }), 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /packages/vite-micro/src/node/vite-plugin-federaion/index.ts: -------------------------------------------------------------------------------- 1 | // ***************************************************************************** 2 | // Copyright (C) 2022 Origin.js and others. 3 | // 4 | // This program and the accompanying materials are licensed under Mulan PSL v2. 5 | // You can use this software according to the terms and conditions of the Mulan PSL v2. 6 | // You may obtain a copy of Mulan PSL v2 at: 7 | // http://license.coscl.org.cn/MulanPSL2 8 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, 9 | // EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, 10 | // MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 11 | // See the Mulan PSL v2 for more details. 12 | // 13 | // SPDX-License-Identifier: MulanPSL-2.0 14 | // ***************************************************************************** 15 | 16 | import type { ConfigEnv, Plugin, UserConfig, ViteDevServer, ResolvedConfig } from 'vite' 17 | import virtual from '@rollup/plugin-virtual' 18 | import path, { dirname } from 'path' 19 | import fs from 'fs-extra' 20 | import type { VitePluginFederationOptions } from 'types/federation' 21 | import { builderInfoFactory, DEFAULT_ENTRY_FILENAME, parsedOptions } from './public' 22 | import type { PluginHooks } from 'types/pluginHooks' 23 | import type { ModuleInfo } from 'rollup' 24 | import { devSharedPlugin } from './src/dev/shared-development' 25 | import { devRemotePlugin } from './src/dev/remote-development' 26 | import { devExposePlugin } from './src/dev/expose-development' 27 | import type { federationOptions, RemotesOption } from 'types' 28 | import type { AcornNode, TransformPluginContext, TransformResult as TransformResult_2 } from 'rollup' 29 | import prodFederation from '@originjs/vite-plugin-federation' 30 | import { pluginsTransformCall, parseRemotes, parseExposes } from './utils' 31 | // @ts-ignore 32 | import federation_satisfy from '@originjs/vite-plugin-federation/dist/satisfy.js?raw' 33 | import { generateCommonRemoteEntryFile } from '../template/createRemoteEntrys' 34 | 35 | export default function federation(options: federationOptions): Plugin { 36 | options.filename = options.filename ? options.filename : DEFAULT_ENTRY_FILENAME 37 | 38 | let pluginList: PluginHooks[] = [] 39 | let virtualMod: any 40 | let registerCount = 0 41 | const builderInfo = builderInfoFactory() 42 | let viteConfig: any = {} 43 | 44 | function registerPlugins(mode: 'development' | 'production') { 45 | options.mode = mode 46 | parseRemotes(options) 47 | // parseExposes(options) 48 | 49 | if (mode === 'development') { 50 | pluginList = [devSharedPlugin(options), devExposePlugin(options), devRemotePlugin(options)] 51 | } else { 52 | pluginList = [prodFederation(options) as PluginHooks] 53 | } 54 | builderInfo.isHost = !!parsedOptions.devRemote?.length 55 | builderInfo.isRemote = !!parsedOptions.devExpose?.length 56 | builderInfo.isShared = !!parsedOptions.devShared?.length 57 | 58 | let virtualFiles = {} 59 | pluginList.forEach((plugin) => { 60 | if (plugin.virtualFile) { 61 | virtualFiles = Object.assign(virtualFiles, plugin.virtualFile) 62 | } 63 | }) 64 | virtualMod = virtual(virtualFiles) 65 | } 66 | 67 | return { 68 | name: 'vite-micro-federation', 69 | // for scenario vite.config.js build.cssCodeSplit: false 70 | // vite:css-post plugin will summarize all the styles in the style.xxxxxx.css file 71 | // so, this plugin need run after vite:css-post in post plugin list 72 | enforce: 'post', 73 | // apply:'build', 74 | options(_options) { 75 | // rollup doesnt has options.mode and options.command 76 | if (!registerCount++) { 77 | registerPlugins((options.mode = options.mode ?? 'production')) 78 | } 79 | 80 | if (typeof _options.input === 'string') { 81 | _options.input = { index: _options.input } 82 | } 83 | _options.external = _options.external || [] 84 | if (!Array.isArray(_options.external)) { 85 | _options.external = [_options.external as string] 86 | } 87 | 88 | for (const pluginHook of pluginList) { 89 | ;(pluginHook.options as Function)?.call(this, _options) 90 | } 91 | return _options 92 | }, 93 | config(config: UserConfig, env: ConfigEnv) { 94 | viteConfig = config 95 | options.mode = options.mode ?? env.mode 96 | registerPlugins(options.mode) 97 | registerCount++ 98 | for (const pluginHook of pluginList) { 99 | ;(pluginHook.config as Function)?.call(this, config, env) 100 | } 101 | 102 | // only run when builder is vite,rollup doesnt has hook named `config` 103 | builderInfo.builder = 'vite' 104 | builderInfo.assetsDir = config?.build?.assetsDir ?? 'assets' 105 | }, 106 | configureServer(server: ViteDevServer) { 107 | for (const pluginHook of pluginList) { 108 | ;(pluginHook.configureServer as Function)?.call(this, server) 109 | } 110 | }, 111 | configResolved(config: ResolvedConfig) { 112 | for (const pluginHook of pluginList) { 113 | ;(pluginHook.configResolved as Function)?.call(this, config) 114 | } 115 | }, 116 | buildStart(inputOptions) { 117 | for (const pluginHook of pluginList) { 118 | ;(pluginHook.buildStart as Function)?.call(this, inputOptions) 119 | } 120 | }, 121 | 122 | async resolveId(...args) { 123 | const v = virtualMod.resolveId.call(this, ...args) 124 | if (v) { 125 | return v 126 | } 127 | 128 | for (const pluginHook of pluginList) { 129 | const result = await (pluginHook.resolveId as Function)?.call(this, ...args) 130 | if (result) { 131 | return result 132 | } 133 | } 134 | 135 | return null 136 | }, 137 | 138 | load(...args) { 139 | const v = virtualMod.load.call(this, ...args) 140 | if (v) { 141 | return v 142 | } 143 | 144 | for (const pluginHook of pluginList) { 145 | const result = (pluginHook.load as Function)?.call(this, ...args) 146 | if (result) { 147 | return result 148 | } 149 | } 150 | 151 | return null 152 | }, 153 | 154 | async transform(code: string, id: string) { 155 | // 解决插件里面无法解析二级依赖的问题this.resolve('@originjs/vite-plugin-federation') 156 | if (id === '\0virtual:__federation_lib_semver') return federation_satisfy 157 | 158 | return pluginsTransformCall.call(this, pluginList, [code, id]) 159 | }, 160 | moduleParsed(moduleInfo: ModuleInfo): void { 161 | for (const pluginHook of pluginList) { 162 | ;(pluginHook.moduleParsed as Function)?.call(this, moduleInfo) 163 | } 164 | }, 165 | 166 | outputOptions(outputOptions) { 167 | for (const pluginHook of pluginList) { 168 | ;(pluginHook.outputOptions as Function)?.call(this, outputOptions) 169 | } 170 | return outputOptions 171 | }, 172 | 173 | renderChunk(code, chunkInfo, _options) { 174 | for (const pluginHook of pluginList) { 175 | const result = (pluginHook.renderChunk as Function)?.call(this, code, chunkInfo, _options) 176 | if (result) { 177 | return result 178 | } 179 | } 180 | return null 181 | }, 182 | 183 | generateBundle: function (_options, bundle, isWrite) { 184 | for (const pluginHook of pluginList) { 185 | ;(pluginHook.generateBundle as Function)?.call(this, _options, bundle, isWrite) 186 | } 187 | }, 188 | 189 | closeBundle() { 190 | if (options.exposes) { 191 | const dirArrs = viteConfig.build.assetsDir.split('/') 192 | const version = dirArrs[dirArrs.length - 1] 193 | const commonRemotePath = path.resolve(viteConfig.build.outDir + '/' + viteConfig.build.assetsDir, '../remoteEntrys.js') 194 | fs.ensureDir(path.resolve(viteConfig.build.outDir + '/' + viteConfig.build.assetsDir), () => { 195 | fs.writeFileSync(commonRemotePath, generateCommonRemoteEntryFile(version)) 196 | }) 197 | } 198 | }, 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /README-zh.md: -------------------------------------------------------------------------------- 1 | [English](./README.md) | 简体中文 2 | 3 | ## vite-micro 微前端框架 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 基于 vite 的微应用架构,每一个微应用相当于一个微服务,提供微组件 api 相互调用,底层基于@originjs/vite-plugin-federation, 15 | 微组件的调用和执行方式按照模块联邦的思想,具有开发和生产 2 种执行方式。 16 | vite-micro 架构 在项目上采用 monorapo 的架构方式,只需在外层启动一次根服务器,后续的微应用可按需自动启动 17 | 18 | ## 运行效果 19 | 20 | 1. 生产模式: 21 | 22 | ``` 23 | cd example 24 | 25 | pnpm && pnpm run build 26 | 27 | node server.mjs 28 | ``` 29 | 30 | 2. 开发模式: 31 | 32 | ``` 33 | cd example 34 | 35 | pnpm && pnpm run start 36 | ``` 37 | 38 | ## 安装 39 | 40 | ``` 41 | npm install vite-micro 42 | ``` 43 | 44 | 或者 45 | 46 | ``` 47 | yarn add vite-micro 48 | ``` 49 | 50 | ## 使用 51 | 52 | vite-micro 架构需要采用 monorapo 项目结构,可参考 example 的项目结构, 53 | packages 里面通常会有 2 个或 2 个以上的微应用,一个作为 Host 端,一个作为 Remote 端。 54 | 55 | #### 步骤一:Remote 端配置暴露的模块 56 | 57 | ```js 58 | // vite.config.js 59 | import { federation } from 'vite-micro/node' 60 | export default { 61 | build: { 62 | // 如果出现top level await问题,则需使用import topLevelAwait from 'vite-plugin-top-level-await' 63 | target: ['chrome89', 'edge89', 'firefox89', 'safari15'], 64 | // 输出目录 65 | outDir: `${path.resolve(__dirname, '../../dist')}`, 66 | // 资源存放目录 67 | assetsDir: `assets/user/${packageJson.version}`, 68 | }, 69 | plugins: [ 70 | federation({ 71 | mode 72 | // 需要暴露的模块, 73 | //远程模块对外暴露的组件列表,远程模块必填 74 | exposes: { 75 | Button: './src/Button.vue', 76 | entry: './src/bootstrap.ts', 77 | }, 78 | shared: ['vue'], 79 | }), 80 | ], 81 | } 82 | 83 | ``` 84 | 85 | - 这里的 entry 对应的 bootstrap.ts 来源于 main.ts(项目的入口文件) ,如果有以下配置,则需使用 bootstrap.ts,否则会产生冲突错误 86 | 87 | ``` 88 | rollupOptions: { 89 | input: main: `${path.resolve(__dirname, './src/main.ts')}`, 90 | } 91 | // bootstrap.ts 92 | export { mount, unMount } from './main' 93 | ``` 94 | 95 | #### 步骤二:Remote 端配置应用入口文件(如果 Host 端需要调用 Remote 微应用) 96 | 97 | ``` 98 | // main.ts 99 | import { createApp } from 'vue' 100 | import App from './App.vue' 101 | 102 | let app: any = null 103 | export async function mount(name: string, base: string) { 104 | app = createApp(App) 105 | 106 | // 其他配置...... 107 | 108 | app.mount(name) 109 | 110 | console.log('start mount!!!', name) 111 | 112 | return app 113 | } 114 | 115 | export function unMount() { 116 | console.log('start unmount --->') 117 | app && app.$destroy() 118 | } 119 | 120 | ``` 121 | 122 | - Host 端拿到 Remote 微应用入口文件后,会执行里面的 mount 方法初始化并挂载微应用 123 | - mount 和 unmount 方法 约定导出 124 | 125 | #### 步骤三:Host 端配置暴露的模块 126 | 127 | ```js 128 | // vite.config.js 129 | import { federation } from 'vite-micro/node' 130 | export default { 131 | build: { 132 | // 如果出现top level await问题,则需使用import topLevelAwait from 'vite-plugin-top-level-await' 133 | target: ['chrome89', 'edge89', 'firefox89', 'safari15'], 134 | // 输出目录 135 | outDir: `${path.resolve(__dirname, '../../dist')}`, 136 | // 资源存放目录 137 | assetsDir: `assets/main/${packageJson.version}`, 138 | }, 139 | plugins: [ 140 | federation({ 141 | mode 142 | remotes: { 143 | loginRemote: { 144 | url: `/assets/login`, 145 | }, 146 | userRemote: { 147 | url: '/assets/user', 148 | }, 149 | }, 150 | shared: ['vue'], 151 | }), 152 | ], 153 | } 154 | ``` 155 | 156 | #### 步骤四:Host 端使用远程模块 157 | 158 | - 使用微组件方式 159 | 160 | ```js 161 | import { createApp, defineAsyncComponent } from "vue"; 162 | import { remoteImport } from 'vite-micro/client' 163 | const app = createApp(Layout); 164 | ... 165 | const RemoteButton = defineAsyncComponent(() => remoteImport("remote_app/Button")); 166 | app.component("RemoteButton", RemoteButton); 167 | app.mount("#root"); 168 | ``` 169 | 170 | - 使用微应用入口方式 171 | 172 | ```js 173 | import { entryImportVue, remoteImport } from 'vite-micro/client' 174 | 175 | const mainRoute = [ 176 | { 177 | path: '/home', 178 | component: () => import('../../views/Home.vue'), 179 | }, 180 | { 181 | path: '/user', 182 | component: () => entryImportVue('remote_app/entry'), 183 | }, 184 | { 185 | path: '/button', 186 | component: () => remoteImport('remote_app/Button'), 187 | }, 188 | ] 189 | ``` 190 | 191 | - entryImportVue('remote_app/entry') 在本质上也是一个微组件,同样可以使用微组件方式调用 192 | - 对于 Remote 模块暴露的脚本有时并非 vue 组件,也可能是 React 组件或其他,也可能是远程应用的入口文件,这种类型的脚本很明显是无法直接被 Host 模块 vue 项目所消费的,entryImportVue 的内部使用一个简单的 vue 组件将这些脚本包裹进来形成一个可以直接被 vue 项目使用的组件 193 | - 对于可以直接被 Host 模块直接引用的远程组件,直接使用 remoteImport 即可 194 | 195 | #### 版本管理 196 | 197 | - 提供远程引入组件的版本控制的 2 种方式,默认引入最新版 198 | 199 | ```js 200 | remotes: { 201 | // 默认会引入loginRemote 应用的remoteEntrys.js , 这个文件会去加载该应用最新版本的remoteEntry文件 202 | 'loginRemote': { 203 | url: `/assets/login` 204 | }, 205 | // 会将 `/assets/login/0.0.1/remoteEntry.js` 作为入口文件引入 206 | 'userRemote': { 207 | url: `/assets/login`, 208 | filename: '0.0.1/remoteEntry.js' 209 | }, 210 | } 211 | ``` 212 | 213 | ## 配置项说明 214 | 215 | ### `mode:string` 216 | 217 | - 控制开发模式或生产模式,必填。 218 | 219 | ### `exposes` 220 | 221 | - 作为远程模块,对外暴露的组件列表,远程模块必填。 222 | 223 | ```js 224 | exposes: { 225 | // '对外暴露的组件名称':'对外暴露的组件地址' 226 | 'Button': './src/components/Button.vue', 227 | 'Section': './src/components/Section.vue' 228 | } 229 | ``` 230 | 231 | --- 232 | 233 | ### `remotes` 234 | 235 | Host 端引用 Remote 端的资源入口文件配置 236 | 237 | #### `url:string` 238 | 239 | - 远程模块地址,例如:`/assets/login`, `https://localhost:5011`, 该配置必填 240 | - url 在内部会生成 external 地址,`${url}/remoteEntrys.js`, 该地址会约定为远程模块的入口地址 241 | - url 可以是一个根据打包结构确定的相对地址,也可以是一个带有 http 的完整的外部地址 242 | 243 | ```js 244 | remotes: { 245 | // '{远端模块名称}Remote':'远端模块入口文件地址' 246 | 'loginRemote': { 247 | url: `/assets/login` 248 | }, 249 | } 250 | ``` 251 | 252 | #### `devUrl:string` 253 | 254 | - 远程模块开发环境地址,例如:`/assets/login`, `https://localhost:5011`, 该配置非必填 255 | - devUrl 如果不配置,则默认取 url 地址或者项目的相对路径 256 | - **\*\***当 url 为相对地址且未配置 devUrl 时,远端模块名称格式需约定为 '{远端模块名称}Remote',, 在开发环境下,会根据远端模块名称生成远程模块入口地址 257 | 258 | ```js 259 | remotes: { 260 | // '远端模块名称':'远端模块入口文件地址' 261 | 'loginRemote': { 262 | url: `https://www.vite-micro.com`, 263 | devUrl: `https://localhost:5011` 264 | }, 265 | } 266 | ``` 267 | 268 | #### `filename:string` 269 | 270 | - 作为远程模块的入口文件,非必填,默认为`remoteEntrys.js`, 具体使用方式参考版本管理 271 | 272 | ### `shared` 273 | 274 | 本地模块和远程模块共享的依赖。本地模块需配置所有使用到的远端模块的依赖;远端模块需要配置对外提供的组件的依赖。 275 | 276 | - 是一个数组,可以是['vue',....], 也可以是[{...}] 277 | 278 | #### `name: string` 279 | 280 | - 共享组件的名称, 必填 281 | 282 | #### `requiredVersion: string` 283 | 284 | - 仅对 `remote` 端生效,规定所使用的 `host shared` 所需要的版本,当 `host` 端的版本不符合 `requiredVersion` 要求时,会使用自己的 `shared` 模块,默认不启用该功能 285 | 286 | ## Browsers support 287 | 288 | Modern browsers does not support IE browser 289 | 290 | | IEdge / IE | Firefox | Chrome | Safari | 291 | | ---------- | --------------- | --------------- | --------------- | 292 | | Edge | last 2 versions | last 2 versions | last 2 versions | 293 | 294 | ## 其他 295 | 296 | - 目前加载的远程脚步暂未支持沙箱功能,代码需要靠规范约束 297 | - 如果您认可此框架并对觉得对您有帮助,希望能给我点颗星 ^\_^ 298 | - 如果此框架给您的工作事业带来价值,希望能给我一些捐助,创作不易。 299 | 300 |  301 | -------------------------------------------------------------------------------- /packages/vite-micro/README-zh.md: -------------------------------------------------------------------------------- 1 | [English](./README.md) | 简体中文 2 | 3 | ## vite-micro 微前端框架 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 基于 vite 的微应用架构,每一个微应用相当于一个微服务,提供微组件 api 相互调用,底层基于@originjs/vite-plugin-federation, 15 | 微组件的调用和执行方式按照模块联邦的思想,具有开发和生产 2 种执行方式。 16 | vite-micro 架构 在项目上采用 monorapo 的架构方式,只需在外层启动一次根服务器,后续的微应用可按需自动启动 17 | 18 | ## 运行效果 19 | 20 | 1. 生产模式: 21 | 22 | ``` 23 | cd example 24 | 25 | pnpm && pnpm run build 26 | 27 | node server.mjs 28 | ``` 29 | 30 | 2. 开发模式: 31 | 32 | ``` 33 | cd example 34 | 35 | pnpm && pnpm run start 36 | ``` 37 | 38 | ## 安装 39 | 40 | ``` 41 | npm install vite-micro 42 | ``` 43 | 44 | 或者 45 | 46 | ``` 47 | yarn add vite-micro 48 | ``` 49 | 50 | ## 使用 51 | 52 | vite-micro 架构需要采用 monorapo 项目结构,可参考 example 的项目结构, 53 | packages 里面通常会有 2 个或 2 个以上的微应用,一个作为 Host 端,一个作为 Remote 端。 54 | 55 | #### 步骤一:Remote 端配置暴露的模块 56 | 57 | ```js 58 | // vite.config.js 59 | import { federation } from 'vite-micro/node' 60 | export default { 61 | build: { 62 | // 如果出现top level await问题,则需使用import topLevelAwait from 'vite-plugin-top-level-await' 63 | target: ['chrome89', 'edge89', 'firefox89', 'safari15'], 64 | // 输出目录 65 | outDir: `${path.resolve(__dirname, '../../dist')}`, 66 | // 资源存放目录 67 | assetsDir: `assets/user/${packageJson.version}`, 68 | }, 69 | plugins: [ 70 | federation({ 71 | mode 72 | // 需要暴露的模块, 73 | //远程模块对外暴露的组件列表,远程模块必填 74 | exposes: { 75 | Button: './src/Button.vue', 76 | entry: './src/bootstrap.ts', 77 | }, 78 | shared: ['vue'], 79 | }), 80 | ], 81 | } 82 | 83 | ``` 84 | 85 | - 这里的 entry 对应的 bootstrap.ts 来源于 main.ts(项目的入口文件) ,如果有以下配置,则需使用 bootstrap.ts,否则会产生冲突错误 86 | 87 | ``` 88 | rollupOptions: { 89 | input: main: `${path.resolve(__dirname, './src/main.ts')}`, 90 | } 91 | // bootstrap.ts 92 | export { mount, unMount } from './main' 93 | ``` 94 | 95 | #### 步骤二:Remote 端配置应用入口文件(如果 Host 端需要调用 Remote 微应用) 96 | 97 | ``` 98 | // main.ts 99 | import { createApp } from 'vue' 100 | import App from './App.vue' 101 | 102 | let app: any = null 103 | export async function mount(name: string, base: string) { 104 | app = createApp(App) 105 | 106 | // 其他配置...... 107 | 108 | app.mount(name) 109 | 110 | console.log('start mount!!!', name) 111 | 112 | return app 113 | } 114 | 115 | export function unMount() { 116 | console.log('start unmount --->') 117 | app && app.$destroy() 118 | } 119 | 120 | ``` 121 | 122 | - Host 端拿到 Remote 微应用入口文件后,会执行里面的 mount 方法初始化并挂载微应用 123 | - mount 和 unmount 方法 约定导出 124 | 125 | #### 步骤三:Host 端配置暴露的模块 126 | 127 | ```js 128 | // vite.config.js 129 | import { federation } from 'vite-micro/node' 130 | export default { 131 | build: { 132 | // 如果出现top level await问题,则需使用import topLevelAwait from 'vite-plugin-top-level-await' 133 | target: ['chrome89', 'edge89', 'firefox89', 'safari15'], 134 | // 输出目录 135 | outDir: `${path.resolve(__dirname, '../../dist')}`, 136 | // 资源存放目录 137 | assetsDir: `assets/main/${packageJson.version}`, 138 | }, 139 | plugins: [ 140 | federation({ 141 | mode 142 | remotes: { 143 | loginRemote: { 144 | url: `/assets/login`, 145 | }, 146 | userRemote: { 147 | url: '/assets/user', 148 | }, 149 | }, 150 | shared: ['vue'], 151 | }), 152 | ], 153 | } 154 | ``` 155 | 156 | #### 步骤四:Host 端使用远程模块 157 | 158 | - 使用微组件方式 159 | 160 | ```js 161 | import { createApp, defineAsyncComponent } from "vue"; 162 | import { remoteImport } from 'vite-micro/client' 163 | const app = createApp(Layout); 164 | ... 165 | const RemoteButton = defineAsyncComponent(() => remoteImport("remote_app/Button")); 166 | app.component("RemoteButton", RemoteButton); 167 | app.mount("#root"); 168 | ``` 169 | 170 | - 使用微应用入口方式 171 | 172 | ```js 173 | import { entryImportVue, remoteImport } from 'vite-micro/client' 174 | 175 | const mainRoute = [ 176 | { 177 | path: '/home', 178 | component: () => import('../../views/Home.vue'), 179 | }, 180 | { 181 | path: '/user', 182 | component: () => entryImportVue('remote_app/entry'), 183 | }, 184 | { 185 | path: '/button', 186 | component: () => remoteImport('remote_app/Button'), 187 | }, 188 | ] 189 | ``` 190 | 191 | - entryImportVue('remote_app/entry') 在本质上也是一个微组件,同样可以使用微组件方式调用 192 | - 对于 Remote 模块暴露的脚本有时并非 vue 组件,也可能是 React 组件或其他,也可能是远程应用的入口文件,这种类型的脚本很明显是无法直接被 Host 模块 vue 项目所消费的,entryImportVue 的内部使用一个简单的 vue 组件将这些脚本包裹进来形成一个可以直接被 vue 项目使用的组件 193 | - 对于可以直接被 Host 模块直接引用的远程组件,直接使用 remoteImport 即可 194 | 195 | #### 版本管理 196 | 197 | - 提供远程引入组件的版本控制的 2 种方式,默认引入最新版 198 | 199 | ```js 200 | remotes: { 201 | // 默认会引入loginRemote 应用的remoteEntrys.js , 这个文件会去加载该应用最新版本的remoteEntry文件 202 | 'loginRemote': { 203 | url: `/assets/login` 204 | }, 205 | // 会将 `/assets/login/0.0.1/remoteEntry.js` 作为入口文件引入 206 | 'userRemote': { 207 | url: `/assets/login`, 208 | filename: '0.0.1/remoteEntry.js' 209 | }, 210 | } 211 | ``` 212 | 213 | ## 配置项说明 214 | 215 | ### `mode:string` 216 | 217 | - 控制开发模式或生产模式,必填。 218 | 219 | ### `exposes` 220 | 221 | - 作为远程模块,对外暴露的组件列表,远程模块必填。 222 | 223 | ```js 224 | exposes: { 225 | // '对外暴露的组件名称':'对外暴露的组件地址' 226 | 'Button': './src/components/Button.vue', 227 | 'Section': './src/components/Section.vue' 228 | } 229 | ``` 230 | 231 | --- 232 | 233 | ### `remotes` 234 | 235 | Host 端引用 Remote 端的资源入口文件配置 236 | 237 | #### `url:string` 238 | 239 | - 远程模块地址,例如:`/assets/login`, `https://localhost:5011`, 该配置必填 240 | - url 在内部会生成 external 地址,`${url}/remoteEntrys.js`, 该地址会约定为远程模块的入口地址 241 | - url 可以是一个根据打包结构确定的相对地址,也可以是一个带有 http 的完整的外部地址 242 | 243 | ```js 244 | remotes: { 245 | // '{远端模块名称}Remote':'远端模块入口文件地址' 246 | 'loginRemote': { 247 | url: `/assets/login` 248 | }, 249 | } 250 | ``` 251 | 252 | #### `devUrl:string` 253 | 254 | - 远程模块开发环境地址,例如:`/assets/login`, `https://localhost:5011`, 该配置非必填 255 | - devUrl 如果不配置,则默认取 url 地址或者项目的相对路径 256 | - **\*\***当 url 为相对地址且未配置 devUrl 时,远端模块名称格式需约定为 '{远端模块名称}Remote',, 在开发环境下,会根据远端模块名称生成远程模块入口地址 257 | 258 | ```js 259 | remotes: { 260 | // '远端模块名称':'远端模块入口文件地址' 261 | 'loginRemote': { 262 | url: `https://www.vite-micro.com`, 263 | devUrl: `https://localhost:5011` 264 | }, 265 | } 266 | ``` 267 | 268 | #### `filename:string` 269 | 270 | - 作为远程模块的入口文件,非必填,默认为`remoteEntrys.js`, 具体使用方式参考版本管理 271 | 272 | ### `shared` 273 | 274 | 本地模块和远程模块共享的依赖。本地模块需配置所有使用到的远端模块的依赖;远端模块需要配置对外提供的组件的依赖。 275 | 276 | - 是一个数组,可以是['vue',....], 也可以是[{...}] 277 | 278 | #### `name: string` 279 | 280 | - 共享组件的名称, 必填 281 | 282 | #### `requiredVersion: string` 283 | 284 | - 仅对 `remote` 端生效,规定所使用的 `host shared` 所需要的版本,当 `host` 端的版本不符合 `requiredVersion` 要求时,会使用自己的 `shared` 模块,默认不启用该功能 285 | 286 | ## Browsers support 287 | 288 | Modern browsers does not support IE browser 289 | 290 | | IEdge / IE | Firefox | Chrome | Safari | 291 | | ---------- | --------------- | --------------- | --------------- | 292 | | Edge | last 2 versions | last 2 versions | last 2 versions | 293 | 294 | ## 其他 295 | 296 | - 目前加载的远程脚步暂未支持沙箱功能,代码需要靠规范约束 297 | - 如果您认可此框架并对觉得对您有帮助,希望能给我点颗星 ^\_^ 298 | - 如果此框架给您的工作事业带来价值,希望能给我一些捐助,创作不易。 299 | 300 |  301 | -------------------------------------------------------------------------------- /packages/vite-micro/src/client/semver/parser.ts: -------------------------------------------------------------------------------- 1 | // ***************************************************************************** 2 | // Copyright (C) 2022 Origin.js and others. 3 | // 4 | // This program and the accompanying materials are licensed under Mulan PSL v2. 5 | // You can use this software according to the terms and conditions of the Mulan PSL v2. 6 | // You may obtain a copy of Mulan PSL v2 at: 7 | // http://license.coscl.org.cn/MulanPSL2 8 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, 9 | // EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, 10 | // MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 11 | // See the Mulan PSL v2 for more details. 12 | // 13 | // SPDX-License-Identifier: MulanPSL-2.0 14 | // ***************************************************************************** 15 | 16 | import { isXVersion, parseRegex } from './utils' 17 | import { 18 | caret, 19 | caretTrim, 20 | comparatorTrim, 21 | gte0, 22 | hyphenRange, 23 | star, 24 | tilde, 25 | tildeTrim, 26 | xRange 27 | } from './constants' 28 | 29 | export function parseHyphen(range: string): string { 30 | return range.replace( 31 | parseRegex(hyphenRange), 32 | ( 33 | _range, 34 | from, 35 | fromMajor, 36 | fromMinor, 37 | fromPatch, 38 | _fromPreRelease, 39 | _fromBuild, 40 | to, 41 | toMajor, 42 | toMinor, 43 | toPatch, 44 | toPreRelease 45 | ) => { 46 | if (isXVersion(fromMajor)) { 47 | from = '' 48 | } else if (isXVersion(fromMinor)) { 49 | from = `>=${fromMajor}.0.0` 50 | } else if (isXVersion(fromPatch)) { 51 | from = `>=${fromMajor}.${fromMinor}.0` 52 | } else { 53 | from = `>=${from}` 54 | } 55 | 56 | if (isXVersion(toMajor)) { 57 | to = '' 58 | } else if (isXVersion(toMinor)) { 59 | to = `<${+toMajor + 1}.0.0-0` 60 | } else if (isXVersion(toPatch)) { 61 | to = `<${toMajor}.${+toMinor + 1}.0-0` 62 | } else if (toPreRelease) { 63 | to = `<=${toMajor}.${toMinor}.${toPatch}-${toPreRelease}` 64 | } else { 65 | to = `<=${to}` 66 | } 67 | 68 | return `${from} ${to}`.trim() 69 | } 70 | ) 71 | } 72 | 73 | export function parseComparatorTrim(range: string): string { 74 | return range.replace(parseRegex(comparatorTrim), '$1$2$3') 75 | } 76 | 77 | export function parseTildeTrim(range: string): string { 78 | return range.replace(parseRegex(tildeTrim), '$1~') 79 | } 80 | 81 | export function parseCaretTrim(range: string): string { 82 | return range.replace(parseRegex(caretTrim), '$1^') 83 | } 84 | 85 | export function parseCarets(range: string): string { 86 | return range 87 | .trim() 88 | .split(/\s+/) 89 | .map((rangeVersion) => { 90 | return rangeVersion.replace( 91 | parseRegex(caret), 92 | (_, major, minor, patch, preRelease) => { 93 | if (isXVersion(major)) { 94 | return '' 95 | } else if (isXVersion(minor)) { 96 | return `>=${major}.0.0 <${+major + 1}.0.0-0` 97 | } else if (isXVersion(patch)) { 98 | if (major === '0') { 99 | return `>=${major}.${minor}.0 <${major}.${+minor + 1}.0-0` 100 | } else { 101 | return `>=${major}.${minor}.0 <${+major + 1}.0.0-0` 102 | } 103 | } else if (preRelease) { 104 | if (major === '0') { 105 | if (minor === '0') { 106 | return `>=${major}.${minor}.${patch}-${preRelease} <${major}.${minor}.${ 107 | +patch + 1 108 | }-0` 109 | } else { 110 | return `>=${major}.${minor}.${patch}-${preRelease} <${major}.${ 111 | +minor + 1 112 | }.0-0` 113 | } 114 | } else { 115 | return `>=${major}.${minor}.${patch}-${preRelease} <${ 116 | +major + 1 117 | }.0.0-0` 118 | } 119 | } else { 120 | if (major === '0') { 121 | if (minor === '0') { 122 | return `>=${major}.${minor}.${patch} <${major}.${minor}.${ 123 | +patch + 1 124 | }-0` 125 | } else { 126 | return `>=${major}.${minor}.${patch} <${major}.${ 127 | +minor + 1 128 | }.0-0` 129 | } 130 | } 131 | 132 | return `>=${major}.${minor}.${patch} <${+major + 1}.0.0-0` 133 | } 134 | } 135 | ) 136 | }) 137 | .join(' ') 138 | } 139 | 140 | export function parseTildes(range: string): string { 141 | return range 142 | .trim() 143 | .split(/\s+/) 144 | .map((rangeVersion) => { 145 | return rangeVersion.replace( 146 | parseRegex(tilde), 147 | (_, major, minor, patch, preRelease) => { 148 | if (isXVersion(major)) { 149 | return '' 150 | } else if (isXVersion(minor)) { 151 | return `>=${major}.0.0 <${+major + 1}.0.0-0` 152 | } else if (isXVersion(patch)) { 153 | return `>=${major}.${minor}.0 <${major}.${+minor + 1}.0-0` 154 | } else if (preRelease) { 155 | return `>=${major}.${minor}.${patch}-${preRelease} <${major}.${ 156 | +minor + 1 157 | }.0-0` 158 | } 159 | 160 | return `>=${major}.${minor}.${patch} <${major}.${+minor + 1}.0-0` 161 | } 162 | ) 163 | }) 164 | .join(' ') 165 | } 166 | 167 | export function parseXRanges(range: string): string { 168 | return range 169 | .split(/\s+/) 170 | .map((rangeVersion) => { 171 | return rangeVersion 172 | .trim() 173 | .replace( 174 | parseRegex(xRange), 175 | (ret, gtlt, major, minor, patch, preRelease) => { 176 | const isXMajor = isXVersion(major) 177 | const isXMinor = isXMajor || isXVersion(minor) 178 | const isXPatch = isXMinor || isXVersion(patch) 179 | 180 | if (gtlt === '=' && isXPatch) { 181 | gtlt = '' 182 | } 183 | 184 | preRelease = '' 185 | 186 | if (isXMajor) { 187 | if (gtlt === '>' || gtlt === '<') { 188 | // nothing is allowed 189 | return '<0.0.0-0' 190 | } else { 191 | // nothing is forbidden 192 | return '*' 193 | } 194 | } else if (gtlt && isXPatch) { 195 | // replace X with 0 196 | if (isXMinor) { 197 | minor = 0 198 | } 199 | 200 | patch = 0 201 | 202 | if (gtlt === '>') { 203 | // >1 => >=2.0.0 204 | // >1.2 => >=1.3.0 205 | gtlt = '>=' 206 | 207 | if (isXMinor) { 208 | major = +major + 1 209 | minor = 0 210 | patch = 0 211 | } else { 212 | minor = +minor + 1 213 | patch = 0 214 | } 215 | } else if (gtlt === '<=') { 216 | // <=0.7.x is actually <0.8.0, since any 0.7.x should pass 217 | // Similarly, <=7.x is actually <8.0.0, etc. 218 | gtlt = '<' 219 | 220 | if (isXMinor) { 221 | major = +major + 1 222 | } else { 223 | minor = +minor + 1 224 | } 225 | } 226 | 227 | if (gtlt === '<') { 228 | preRelease = '-0' 229 | } 230 | 231 | return `${gtlt + major}.${minor}.${patch}${preRelease}` 232 | } else if (isXMinor) { 233 | return `>=${major}.0.0${preRelease} <${+major + 1}.0.0-0` 234 | } else if (isXPatch) { 235 | return `>=${major}.${minor}.0${preRelease} <${major}.${ 236 | +minor + 1 237 | }.0-0` 238 | } 239 | 240 | return ret 241 | } 242 | ) 243 | }) 244 | .join(' ') 245 | } 246 | 247 | export function parseStar(range: string): string { 248 | return range.trim().replace(parseRegex(star), '') 249 | } 250 | 251 | export function parseGTE0(comparatorString: string): string { 252 | return comparatorString.trim().replace(parseRegex(gte0), '') 253 | } 254 | -------------------------------------------------------------------------------- /packages/vite-micro/src/node/semver/parser.ts: -------------------------------------------------------------------------------- 1 | // ***************************************************************************** 2 | // Copyright (C) 2022 Origin.js and others. 3 | // 4 | // This program and the accompanying materials are licensed under Mulan PSL v2. 5 | // You can use this software according to the terms and conditions of the Mulan PSL v2. 6 | // You may obtain a copy of Mulan PSL v2 at: 7 | // http://license.coscl.org.cn/MulanPSL2 8 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, 9 | // EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, 10 | // MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 11 | // See the Mulan PSL v2 for more details. 12 | // 13 | // SPDX-License-Identifier: MulanPSL-2.0 14 | // ***************************************************************************** 15 | 16 | import { isXVersion, parseRegex } from './utils' 17 | import { 18 | caret, 19 | caretTrim, 20 | comparatorTrim, 21 | gte0, 22 | hyphenRange, 23 | star, 24 | tilde, 25 | tildeTrim, 26 | xRange 27 | } from './constants' 28 | 29 | export function parseHyphen(range: string): string { 30 | return range.replace( 31 | parseRegex(hyphenRange), 32 | ( 33 | _range, 34 | from, 35 | fromMajor, 36 | fromMinor, 37 | fromPatch, 38 | _fromPreRelease, 39 | _fromBuild, 40 | to, 41 | toMajor, 42 | toMinor, 43 | toPatch, 44 | toPreRelease 45 | ) => { 46 | if (isXVersion(fromMajor)) { 47 | from = '' 48 | } else if (isXVersion(fromMinor)) { 49 | from = `>=${fromMajor}.0.0` 50 | } else if (isXVersion(fromPatch)) { 51 | from = `>=${fromMajor}.${fromMinor}.0` 52 | } else { 53 | from = `>=${from}` 54 | } 55 | 56 | if (isXVersion(toMajor)) { 57 | to = '' 58 | } else if (isXVersion(toMinor)) { 59 | to = `<${+toMajor + 1}.0.0-0` 60 | } else if (isXVersion(toPatch)) { 61 | to = `<${toMajor}.${+toMinor + 1}.0-0` 62 | } else if (toPreRelease) { 63 | to = `<=${toMajor}.${toMinor}.${toPatch}-${toPreRelease}` 64 | } else { 65 | to = `<=${to}` 66 | } 67 | 68 | return `${from} ${to}`.trim() 69 | } 70 | ) 71 | } 72 | 73 | export function parseComparatorTrim(range: string): string { 74 | return range.replace(parseRegex(comparatorTrim), '$1$2$3') 75 | } 76 | 77 | export function parseTildeTrim(range: string): string { 78 | return range.replace(parseRegex(tildeTrim), '$1~') 79 | } 80 | 81 | export function parseCaretTrim(range: string): string { 82 | return range.replace(parseRegex(caretTrim), '$1^') 83 | } 84 | 85 | export function parseCarets(range: string): string { 86 | return range 87 | .trim() 88 | .split(/\s+/) 89 | .map((rangeVersion) => { 90 | return rangeVersion.replace( 91 | parseRegex(caret), 92 | (_, major, minor, patch, preRelease) => { 93 | if (isXVersion(major)) { 94 | return '' 95 | } else if (isXVersion(minor)) { 96 | return `>=${major}.0.0 <${+major + 1}.0.0-0` 97 | } else if (isXVersion(patch)) { 98 | if (major === '0') { 99 | return `>=${major}.${minor}.0 <${major}.${+minor + 1}.0-0` 100 | } else { 101 | return `>=${major}.${minor}.0 <${+major + 1}.0.0-0` 102 | } 103 | } else if (preRelease) { 104 | if (major === '0') { 105 | if (minor === '0') { 106 | return `>=${major}.${minor}.${patch}-${preRelease} <${major}.${minor}.${ 107 | +patch + 1 108 | }-0` 109 | } else { 110 | return `>=${major}.${minor}.${patch}-${preRelease} <${major}.${ 111 | +minor + 1 112 | }.0-0` 113 | } 114 | } else { 115 | return `>=${major}.${minor}.${patch}-${preRelease} <${ 116 | +major + 1 117 | }.0.0-0` 118 | } 119 | } else { 120 | if (major === '0') { 121 | if (minor === '0') { 122 | return `>=${major}.${minor}.${patch} <${major}.${minor}.${ 123 | +patch + 1 124 | }-0` 125 | } else { 126 | return `>=${major}.${minor}.${patch} <${major}.${ 127 | +minor + 1 128 | }.0-0` 129 | } 130 | } 131 | 132 | return `>=${major}.${minor}.${patch} <${+major + 1}.0.0-0` 133 | } 134 | } 135 | ) 136 | }) 137 | .join(' ') 138 | } 139 | 140 | export function parseTildes(range: string): string { 141 | return range 142 | .trim() 143 | .split(/\s+/) 144 | .map((rangeVersion) => { 145 | return rangeVersion.replace( 146 | parseRegex(tilde), 147 | (_, major, minor, patch, preRelease) => { 148 | if (isXVersion(major)) { 149 | return '' 150 | } else if (isXVersion(minor)) { 151 | return `>=${major}.0.0 <${+major + 1}.0.0-0` 152 | } else if (isXVersion(patch)) { 153 | return `>=${major}.${minor}.0 <${major}.${+minor + 1}.0-0` 154 | } else if (preRelease) { 155 | return `>=${major}.${minor}.${patch}-${preRelease} <${major}.${ 156 | +minor + 1 157 | }.0-0` 158 | } 159 | 160 | return `>=${major}.${minor}.${patch} <${major}.${+minor + 1}.0-0` 161 | } 162 | ) 163 | }) 164 | .join(' ') 165 | } 166 | 167 | export function parseXRanges(range: string): string { 168 | return range 169 | .split(/\s+/) 170 | .map((rangeVersion) => { 171 | return rangeVersion 172 | .trim() 173 | .replace( 174 | parseRegex(xRange), 175 | (ret, gtlt, major, minor, patch, preRelease) => { 176 | const isXMajor = isXVersion(major) 177 | const isXMinor = isXMajor || isXVersion(minor) 178 | const isXPatch = isXMinor || isXVersion(patch) 179 | 180 | if (gtlt === '=' && isXPatch) { 181 | gtlt = '' 182 | } 183 | 184 | preRelease = '' 185 | 186 | if (isXMajor) { 187 | if (gtlt === '>' || gtlt === '<') { 188 | // nothing is allowed 189 | return '<0.0.0-0' 190 | } else { 191 | // nothing is forbidden 192 | return '*' 193 | } 194 | } else if (gtlt && isXPatch) { 195 | // replace X with 0 196 | if (isXMinor) { 197 | minor = 0 198 | } 199 | 200 | patch = 0 201 | 202 | if (gtlt === '>') { 203 | // >1 => >=2.0.0 204 | // >1.2 => >=1.3.0 205 | gtlt = '>=' 206 | 207 | if (isXMinor) { 208 | major = +major + 1 209 | minor = 0 210 | patch = 0 211 | } else { 212 | minor = +minor + 1 213 | patch = 0 214 | } 215 | } else if (gtlt === '<=') { 216 | // <=0.7.x is actually <0.8.0, since any 0.7.x should pass 217 | // Similarly, <=7.x is actually <8.0.0, etc. 218 | gtlt = '<' 219 | 220 | if (isXMinor) { 221 | major = +major + 1 222 | } else { 223 | minor = +minor + 1 224 | } 225 | } 226 | 227 | if (gtlt === '<') { 228 | preRelease = '-0' 229 | } 230 | 231 | return `${gtlt + major}.${minor}.${patch}${preRelease}` 232 | } else if (isXMinor) { 233 | return `>=${major}.0.0${preRelease} <${+major + 1}.0.0-0` 234 | } else if (isXPatch) { 235 | return `>=${major}.${minor}.0${preRelease} <${major}.${ 236 | +minor + 1 237 | }.0-0` 238 | } 239 | 240 | return ret 241 | } 242 | ) 243 | }) 244 | .join(' ') 245 | } 246 | 247 | export function parseStar(range: string): string { 248 | return range.trim().replace(parseRegex(star), '') 249 | } 250 | 251 | export function parseGTE0(comparatorString: string): string { 252 | return comparatorString.trim().replace(parseRegex(gte0), '') 253 | } 254 | -------------------------------------------------------------------------------- /packages/vite-micro/src/node/vite-plugin-federaion/utils/index.ts: -------------------------------------------------------------------------------- 1 | // ***************************************************************************** 2 | // Copyright (C) 2022 Origin.js and others. 3 | // 4 | // This program and the accompanying materials are licensed under Mulan PSL v2. 5 | // You can use this software according to the terms and conditions of the Mulan PSL v2. 6 | // You may obtain a copy of Mulan PSL v2 at: 7 | // http://license.coscl.org.cn/MulanPSL2 8 | // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, 9 | // EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, 10 | // MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 11 | // See the Mulan PSL v2 for more details. 12 | // 13 | // SPDX-License-Identifier: MulanPSL-2.0 14 | // ***************************************************************************** 15 | 16 | import type { ConfigTypeSet, Exposes, Remotes, RemotesConfig, Shared, VitePluginFederationOptions } from 'types/federation' 17 | import { readFileSync } from 'fs' 18 | import { createHash } from 'crypto' 19 | import path, { parse, posix } from 'path' 20 | import type { AcornNode, TransformPluginContext, PluginContext, TransformResult as TransformResult_2 } from 'rollup' 21 | import type { PluginHooks } from 'types/pluginHooks' 22 | import { federationOptions } from 'types' 23 | import host from '../../hostAddress' 24 | 25 | export function findDependencies( 26 | this: PluginContext, 27 | id: string, 28 | sets: Set, 29 | sharedModuleIds: Map, 30 | usedSharedModuleIds: Set 31 | ): void { 32 | if (!sets.has(id)) { 33 | sets.add(id) 34 | const moduleInfo = this.getModuleInfo(id) 35 | if (moduleInfo?.importedIds) { 36 | moduleInfo.importedIds.forEach((id) => { 37 | findDependencies.apply(this, [id, sets, sharedModuleIds, usedSharedModuleIds]) 38 | }) 39 | } 40 | if (sharedModuleIds.has(id)) { 41 | usedSharedModuleIds.add(sharedModuleIds.get(id) as string) 42 | } 43 | } 44 | } 45 | 46 | export function parseExposeOptions(options: VitePluginFederationOptions): [string, string | ConfigTypeSet][] { 47 | return parseOptions( 48 | options.exposes, 49 | (item) => { 50 | return { 51 | import: item, 52 | name: undefined, 53 | } 54 | }, 55 | (item) => ({ 56 | import: item.import, 57 | name: item.name || undefined, 58 | }) 59 | ) 60 | } 61 | 62 | export function createContentHash(path: string): string { 63 | const content = readFileSync(path, { encoding: 'utf-8' }) 64 | return createHash('md5').update(content).digest('hex').toString().slice(0, 8) 65 | } 66 | 67 | export function parseOptions( 68 | options: Exposes | Remotes | Shared | undefined, 69 | normalizeSimple: (value: any, key: any) => ConfigTypeSet, 70 | normalizeOptions: (value: any, key: any) => ConfigTypeSet 71 | ): [string, string | ConfigTypeSet][] { 72 | if (!options) { 73 | return [] 74 | } 75 | const list: [string, string | ConfigTypeSet][] = [] 76 | 77 | const array = (items: (string | ConfigTypeSet)[]) => { 78 | for (const item of items) { 79 | if (typeof item === 'string') { 80 | list.push([item, normalizeSimple(item, item)]) 81 | } else if (item && typeof item === 'object') { 82 | object(item) 83 | } else { 84 | throw new Error('Unexpected options format') 85 | } 86 | } 87 | } 88 | const object = (obj) => { 89 | for (const [key, value] of Object.entries(obj)) { 90 | if (typeof value === 'string' || Array.isArray(value)) { 91 | list.push([key, normalizeSimple(value, key)]) 92 | } else { 93 | list.push([key, normalizeOptions(value, key)]) 94 | } 95 | } 96 | } 97 | if (Array.isArray(options)) { 98 | array(options) 99 | } else if (typeof options === 'object') { 100 | object(options) 101 | } else { 102 | throw new Error('Unexpected options format') 103 | } 104 | 105 | return list 106 | } 107 | 108 | const letterReg = new RegExp('[0-9a-zA-Z]+') 109 | 110 | export function removeNonRegLetter(str: string, reg = letterReg): string { 111 | let needUpperCase = false 112 | let ret = '' 113 | for (const c of str) { 114 | if (reg.test(c)) { 115 | ret += needUpperCase ? c.toUpperCase() : c 116 | needUpperCase = false 117 | } else { 118 | needUpperCase = true 119 | } 120 | } 121 | return ret 122 | } 123 | 124 | export function getModuleMarker(value: string, type?: string): string { 125 | return type ? `__rf_${type}__${value}` : `__rf_placeholder__${value}` 126 | } 127 | 128 | export function normalizePath(id: string): string { 129 | return posix.normalize(id.replace(/\\/g, '/')) 130 | } 131 | 132 | export function uniqueArr(arr: T[]): T[] { 133 | return Array.from(new Set(arr)) 134 | } 135 | 136 | export function isSameFilepath(src: string, dest: string): boolean { 137 | if (!src || !dest) { 138 | return false 139 | } 140 | src = normalizePath(src) 141 | dest = normalizePath(dest) 142 | const srcExt = parse(src).ext 143 | const destExt = parse(dest).ext 144 | if (srcExt && destExt && srcExt !== destExt) { 145 | return false 146 | } 147 | if (srcExt) { 148 | src = src.slice(0, -srcExt.length) 149 | } 150 | if (destExt) { 151 | dest = dest.slice(0, -destExt.length) 152 | } 153 | return src === dest 154 | } 155 | 156 | export type Remote = { id: string; regexp: RegExp; config: RemotesConfig } 157 | 158 | export function createRemotesMap(remotes: Remote[]): string { 159 | const createUrl = (remote: Remote) => { 160 | const external = remote.config.external[0] 161 | const externalType = remote.config.externalType 162 | if (externalType === 'promise') { 163 | return `()=>${external}` 164 | } else { 165 | return `'${external}'` 166 | } 167 | } 168 | return `const remotesMap = { 169 | ${remotes 170 | .map((remote) => `'${remote.id}':{url:${createUrl(remote)},format:'${remote.config.format}',from:'${remote.config.from}'}`) 171 | .join(',\n ')} 172 | };` 173 | } 174 | 175 | export async function pluginsTransformCall( 176 | this: TransformPluginContext | any, 177 | pluginList: PluginHooks[], 178 | [code, id]: string[], 179 | flag = false 180 | ): Promise { 181 | let result: string = code 182 | 183 | const hooksType = 'transform' 184 | 185 | for (const pluginHook of pluginList) { 186 | if (!pluginHook[hooksType]) continue 187 | 188 | const currentResult: TransformResult_2 = await pluginHook[hooksType].call(this, result, id) 189 | 190 | if (!currentResult) continue 191 | 192 | if (typeof currentResult === 'string') { 193 | result = currentResult 194 | } else { 195 | result = currentResult.code || result 196 | } 197 | 198 | if (flag) return result 199 | } 200 | 201 | return result 202 | } 203 | 204 | /** 205 | * get file extname from url 206 | * @param url 207 | */ 208 | export function getFileExtname(url: string): string { 209 | const fileNameAndParamArr = normalizePath(url).split('/') 210 | const fileNameAndParam = fileNameAndParamArr[fileNameAndParamArr.length - 1] 211 | const fileName = fileNameAndParam.split('?')[0] 212 | return path.extname(fileName) 213 | } 214 | 215 | export const REMOTE_FROM_PARAMETER = 'remoteFrom' 216 | export const NAME_CHAR_REG = new RegExp('[0-9a-zA-Z@_-]+') 217 | 218 | export function isObject(o: unknown): boolean { 219 | return Object.prototype.toString.call(o) === '[object Object]' 220 | } 221 | 222 | export function getDevRemoteFileUrl(options: federationOptions | { remotes: any }, remoteName: string, base: string) { 223 | // 这里外部其他项目组件引入配置devUrl 224 | const { devUrl, url, filename } = options.remotes[remoteName] 225 | 226 | if (devUrl) return `${devUrl}/@id/__remoteEntryHelper__` 227 | 228 | if (url.startsWith('http')) return url + `/${filename || 'remoteEntrys.js'}?version=v${Date.now()}` 229 | 230 | return `${host}/${base}/@id/__remoteEntryHelper__` 231 | } 232 | 233 | export function parseRemotes(options: federationOptions) { 234 | const remotes = options.remotes || {} 235 | Object.keys(remotes).forEach((remoteName) => { 236 | let base = remoteName.split('Remote')[0] 237 | 238 | if (options.mode === 'development') { 239 | remotes[remoteName].external = getDevRemoteFileUrl(options, remoteName, base) 240 | } else { 241 | remotes[remoteName].url = remotes[remoteName].url || `/assets/${base}` 242 | remotes[remoteName].external = 243 | remotes[remoteName].external || 244 | remotes[remoteName].url + `/${remotes[remoteName].filename || 'remoteEntrys.js'}?version=v${Date.now()}` 245 | } 246 | remotes[remoteName].from = 'vite' 247 | remotes[remoteName].format = 'esm' 248 | }) 249 | 250 | return remotes 251 | } 252 | 253 | export function parseExposes(options: federationOptions) { 254 | options.exposes = options.exposes || {} 255 | let exposes = {} 256 | Object.keys(options.exposes).forEach((exposesName) => { 257 | //@ts-ignore 258 | exposes['./' + exposesName] = options.exposes[exposesName] 259 | }) 260 | return exposes 261 | } 262 | -------------------------------------------------------------------------------- /packages/vite-micro/types/federation.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The following code is adapted from https://github.com/webpack/webpack/types.d.ts 3 | * MIT License https://github.com/webpack/webpack/LICENSE 4 | */ 5 | import { RenderedChunk } from 'rollup' 6 | 7 | export default function federation(options: VitePluginFederationOptions): Plugin 8 | 9 | declare interface VitePluginFederationOptions { 10 | /** 11 | * Modules that should be exposed by this container. When provided, property name is used as public name, otherwise public name is automatically inferred from request. 12 | */ 13 | exposes?: Exposes 14 | 15 | /** 16 | * The filename of the container as relative path inside the `output.path` directory. 17 | */ 18 | filename?: string 19 | 20 | /** 21 | * transform hook need to handle file types 22 | * default ['.js','.ts','.jsx','.tsx','.mjs','.cjs','.vue','.svelte'] 23 | */ 24 | transformFileTypes?: string[] 25 | 26 | /** 27 | * Options for library. 28 | */ 29 | // library?: LibraryOptions 30 | 31 | /** 32 | * The name of the container. 33 | */ 34 | name?: string 35 | 36 | /** 37 | * The external type of the remote containers. 38 | */ 39 | remoteType?: 40 | | 'var' 41 | | 'module' 42 | | 'assign' 43 | | 'this' 44 | | 'window' 45 | | 'self' 46 | | 'global' 47 | | 'commonjs' 48 | | 'commonjs2' 49 | | 'commonjs-module' 50 | | 'amd' 51 | | 'amd-require' 52 | | 'umd' 53 | | 'umd2' 54 | | 'jsonp' 55 | | 'system' 56 | | 'promise' 57 | | 'import' 58 | | 'script' 59 | | 'node-commonjs' 60 | 61 | /** 62 | * Container locations and request scopes from which modules should be resolved and loaded at runtime. When provided, property name is used as request scope, otherwise request scope is automatically inferred from container location. 63 | */ 64 | remotes?: Remotes 65 | 66 | /** 67 | * The name of the runtime chunk. If set a runtime chunk with this name is created or an existing entrypoint is used as runtime. 68 | */ 69 | // runtime?: string | false 70 | 71 | /** 72 | * Share scope name used for all shared modules (defaults to 'default'). 73 | */ 74 | shareScope?: string 75 | 76 | /** 77 | * Modules that should be shared in the share scope. When provided, property names are used to match requested modules in this compilation. 78 | */ 79 | shared?: Shared 80 | 81 | /** 82 | * Current operating mode 83 | */ 84 | mode?: string 85 | } 86 | 87 | type Exposes = (string | ExposesObject)[] | ExposesObject 88 | 89 | type Remotes = (string | RemotesObject)[] | RemotesObject 90 | 91 | type Shared = (string | SharedObject)[] | SharedObject 92 | 93 | type ConfigTypeSet = ExposesConfig | RemotesConfig | SharedConfig 94 | 95 | declare interface SharedRuntimeInfo { 96 | id: string 97 | dependencies: string[] 98 | fileName: string 99 | fileDir: string 100 | filePath: string 101 | chunk: RenderedChunk 102 | } 103 | 104 | /** 105 | * Modules that should be exposed by this container. Property names are used as public paths. 106 | */ 107 | declare interface ExposesObject { 108 | [index: string]: ExposesConfig | string | string[] 109 | } 110 | 111 | /** 112 | * Advanced configuration for modules that should be exposed by this container. 113 | */ 114 | declare interface ExposesConfig { 115 | /** 116 | * Request to a module that should be exposed by this container. 117 | */ 118 | import: string 119 | 120 | /** 121 | * Custom chunk name for the exposed module. 122 | */ 123 | name?: string 124 | } 125 | 126 | /** 127 | * Options for library. 128 | */ 129 | declare interface LibraryOptions { 130 | /** 131 | * Add a comment in the UMD wrapper. 132 | * 133 | */ 134 | auxiliaryComment?: string | LibraryCustomUmdCommentObject 135 | 136 | /** 137 | * Specify which export should be exposed as library. 138 | * 139 | */ 140 | export?: string | string[] 141 | 142 | /** 143 | * The name of the library (some types allow unnamed libraries too). 144 | * 145 | */ 146 | name?: string | string[] | LibraryCustomUmdObject 147 | 148 | /** 149 | * Type of library (types included by default are 'var', 'module', 'assign', 'assign-properties', 'this', 'window', 'self', 'global', 'commonjs', 'commonjs2', 'commonjs-module', 'amd', 'amd-require', 'umd', 'umd2', 'jsonp', 'system', but others might be added by plugins). 150 | * 151 | */ 152 | type: string 153 | 154 | /** 155 | * If `output.libraryTarget` is set to umd and `output.library` is set, setting this to true will name the AMD module. 156 | * 157 | */ 158 | umdNamedDefine?: boolean 159 | } 160 | 161 | /** 162 | * Set explicit comments for `commonjs`, `commonjs2`, `amd`, and `root`. 163 | */ 164 | declare interface LibraryCustomUmdCommentObject { 165 | /** 166 | * Set comment for `amd` section in UMD. 167 | */ 168 | amd?: string 169 | 170 | /** 171 | * Set comment for `commonjs` (exports) section in UMD. 172 | */ 173 | commonjs?: string 174 | 175 | /** 176 | * Set comment for `commonjs2` (module.exports) section in UMD. 177 | */ 178 | commonjs2?: string 179 | 180 | /** 181 | * Set comment for `root` (global variable) section in UMD. 182 | */ 183 | root?: string 184 | } 185 | 186 | /** 187 | * Description object for all UMD variants of the library name. 188 | */ 189 | declare interface LibraryCustomUmdObject { 190 | /** 191 | * Name of the exposed AMD library in the UMD. 192 | */ 193 | amd?: string 194 | 195 | /** 196 | * Name of the exposed commonjs export in the UMD. 197 | */ 198 | commonjs?: string 199 | 200 | /** 201 | * Name of the property exposed globally by a UMD library. 202 | */ 203 | root?: string | string[] 204 | } 205 | 206 | /** 207 | * Container locations from which modules should be resolved and loaded at runtime. Property names are used as request scopes. 208 | */ 209 | declare interface RemotesObject { 210 | [index: string]: string | RemotesConfig | string[] | Promise 211 | } 212 | 213 | /** 214 | * Advanced configuration for container locations from which modules should be resolved and loaded at runtime. 215 | */ 216 | declare interface RemotesConfig { 217 | /** 218 | * Container locations from which modules should be resolved and loaded at runtime. 219 | */ 220 | external: string 221 | 222 | /** 223 | * The format of the specified external 224 | */ 225 | externalType: 'url' | 'promise' 226 | 227 | /** 228 | * The name of the share scope shared with this remote. 229 | */ 230 | shareScope?: string 231 | 232 | /** 233 | * the remote format 234 | */ 235 | format?: 'esm' | 'systemjs' | 'var' 236 | 237 | /** 238 | * from 239 | */ 240 | from?: 'vite' | 'webpack' 241 | } 242 | 243 | /** 244 | * Modules that should be shared in the share scope. Property names are used to match requested modules in this compilation. Relative requests are resolved, module requests are matched unresolved, absolute paths will match resolved requests. A trailing slash will match all requests with this prefix. In this case shareKey must also have a trailing slash. 245 | */ 246 | declare interface SharedObject { 247 | [index: string]: string | SharedConfig 248 | } 249 | 250 | /** 251 | * Advanced configuration for modules that should be shared in the share scope. 252 | */ 253 | declare interface SharedConfig { 254 | /** 255 | * Include the provided and fallback module directly instead behind an async request. This allows to use this shared module in initial load too. All possible shared modules need to be eager too. 256 | */ 257 | // eager?: boolean 258 | 259 | /** 260 | * Provided module that should be provided to share scope. Also acts as fallback module if no shared module is found in share scope or version isn't valid. Defaults to the property name. 261 | */ 262 | import?: boolean 263 | 264 | /** 265 | * Package name to determine required version from description file. This is only needed when package name can't be automatically determined from request. 266 | */ 267 | // packageName?: string 268 | 269 | /** 270 | * Specify the path to the custom package, the field is not supported in dev mode 271 | */ 272 | packagePath?: string | undefined 273 | 274 | /** 275 | * Version requirement from module in share scope. 276 | */ 277 | requiredVersion?: string | false 278 | 279 | /** 280 | * Module is looked up under this key from the share scope. 281 | */ 282 | // shareKey?: string 283 | 284 | /** 285 | * Share scope name. 286 | */ 287 | shareScope?: string 288 | 289 | /** 290 | * Allow only a single version of the shared module in share scope (disabled by default). 291 | */ 292 | // singleton?: boolean 293 | 294 | /** 295 | * Do not accept shared module if version is not valid (defaults to yes, if local fallback module is available and shared module is not a singleton, otherwise no, has no effect if there is no required version specified). 296 | */ 297 | // strictVersion?: boolean 298 | 299 | /** 300 | * Version of the provided module. Will replace lower matching versions, but not higher. 301 | */ 302 | version?: string | false 303 | 304 | /** 305 | * determine whether to include the shared in the chunk, true is included, false will not generate a shared chunk, only the remote side of the parameter is valid, the host side will definitely generate a shared chunk 306 | */ 307 | generate?: boolean 308 | 309 | manuallyPackagePathSetting?: false 310 | } 311 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [简体中文](./README-zh.md) | English 2 | 3 | ## [vite-micro - Micro front-end framework](https://github.com/zhutao315/videoRTP) 4 | 5 | [](https://www.npmjs.com/package/vite-micro) 6 | 7 | Based on the vite micro application architecture, each micro application is equivalent to a micro service, providing micro component APIs for mutual invocation. The underlying layer is based on @ originjs/vite plugin education. 8 | The invocation and execution methods of microcomponents follow the concept of module federation, with two execution methods: development and production. 9 | The vite-micro front-end framework adopts the front-end architecture of Monorapo in the project, which only requires starting the root server once in the outer layer, and subsequent micro applications can be automatically started as needed. 10 | 11 | ## The running effect 12 | 13 | 1. production: 14 | 15 | ``` 16 | cd example 17 | 18 | pnpm && pnpm run build 19 | 20 | node server.mjs 21 | ``` 22 | 23 | 2. development: 24 | 25 | ``` 26 | cd example 27 | 28 | pnpm && pnpm run start 29 | ``` 30 | 31 | ## Install 32 | 33 | ``` 34 | npm install vite-micro 35 | ``` 36 | 37 | Or 38 | 39 | ``` 40 | yarn add vite-micro 41 | ``` 42 | 43 | ## How to Use 44 | 45 | The vite-micro front-end framework needs to adopt the Monorapo project structure, which can be referenced in the example project conclusion, 46 | There are usually two or more micro applications in packages, one as the Host side and one as the Remote side. 47 | 48 | #### Step 1: Configure exposed modules on the Remote side 49 | 50 | ```js 51 | // vite.config.js 52 | import { federation } from 'vite-micro/node' 53 | export default { 54 | build: { 55 | // If there is a top level await issue, you need to use import topLevelAwait from 'vite plugin top level await' 56 | target: ['chrome89', 'edge89', 'firefox89', 'safari15'], 57 | // Output Directory 58 | outDir: `${path.resolve(__dirname, '../../dist')}`, 59 | // Resource storage directory 60 | assetsDir: `assets/user/${packageJson.version}`, 61 | }, 62 | plugins: [ 63 | federation({ 64 | mode 65 | // Modules that need to be exposed, 66 | // The list of components exposed by remote modules to the public is mandatory for remote modules 67 | exposes: { 68 | Button: './src/Button.vue', 69 | entry: './src/bootstrap.ts', 70 | }, 71 | shared: ['vue'], 72 | }), 73 | ], 74 | } 75 | 76 | ``` 77 | 78 | - The "bootstrap.ts" corresponding to the entry here comes from "main.ts" (the entry file of the project). If there are the following configurations, "bootstrap.ts" needs to be used, otherwise conflicting errors will occur 79 | 80 | ``` 81 | rollupOptions: { 82 | input: main: `${path.resolve(__dirname, './src/main.ts')}`, 83 | } 84 | // bootstrap.ts 85 | export { mount, unMount } from './main' 86 | ``` 87 | 88 | #### Step 2: Configure the application entry file on the Remote side (if the Host side needs to call the Remote micro application) 89 | 90 | ``` 91 | // main.ts 92 | import { createApp } from 'vue' 93 | import App from './App.vue' 94 | 95 | let app: any = null 96 | export async function mount(name: string, base: string) { 97 | app = createApp(App) 98 | 99 | // Other configurations...... 100 | 101 | app.mount(name) 102 | 103 | console.log('start mount!!!', name) 104 | 105 | return app 106 | } 107 | 108 | export function unMount() { 109 | console.log('start unmount --->') 110 | app && app.$destroy() 111 | } 112 | 113 | ``` 114 | 115 | - After receiving the Remote micro application entry file, the host side will execute the mount method inside to initialize and mount the micro application 116 | - mExport according to the conventions of the mount method and unmount method 117 | 118 | #### Step 3: Configure exposed modules on the host side 119 | 120 | ```js 121 | // vite.config.js 122 | import { federation } from 'vite-micro/node' 123 | export default { 124 | build: { 125 | // If there is a top level await issue, you need to use import topLevelAwait from 'vite plugin top level await' 126 | target: ['chrome89', 'edge89', 'firefox89', 'safari15'], 127 | // Output Directory 128 | outDir: `${path.resolve(__dirname, '../../dist')}`, 129 | // Resource storage directory 130 | assetsDir: `assets/main/${packageJson.version}`, 131 | }, 132 | plugins: [ 133 | federation({ 134 | mode 135 | remotes: { 136 | loginRemote: { 137 | url: `/assets/login`, 138 | }, 139 | userRemote: { 140 | url: '/assets/user', 141 | }, 142 | }, 143 | shared: ['vue'], 144 | }), 145 | ], 146 | } 147 | ``` 148 | 149 | #### Step 4: Using Remote Modules on the Host Side 150 | 151 | - Using micro components 152 | 153 | ```js 154 | import { createApp, defineAsyncComponent } from "vue"; 155 | import { remoteImport } from 'vite-micro/client' 156 | const app = createApp(Layout); 157 | ... 158 | const RemoteButton = defineAsyncComponent(() => remoteImport("remote_app/Button")); 159 | app.component("RemoteButton", RemoteButton); 160 | app.mount("#root"); 161 | ``` 162 | 163 | - Using micro application entry 164 | 165 | ```js 166 | import { entryImportVue, remoteImport } from 'vite-micro/client' 167 | 168 | const mainRoute = [ 169 | { 170 | path: '/home', 171 | component: () => import('../../views/Home.vue'), 172 | }, 173 | { 174 | path: '/user', 175 | component: () => entryImportVue('remote_app/entry'), 176 | }, 177 | { 178 | path: '/button', 179 | component: () => remoteImport('remote_app/Button'), 180 | }, 181 | ] 182 | ``` 183 | 184 | - EntryImportVue ('remote_app/entry ') is essentially a microcomponent that can also be called using the microcomponent method 185 | - For scripts exposed by the Remote module, sometimes they are not Vue components, may be React components or other, or may be entry files for remote applications. This type of script is clearly not directly consumed by the Host module Vue project. The entryImportVue internal uses a simple Vue component to wrap these scripts together to form a component that can be directly used in the Vue project 186 | - For remote components that can be directly referenced by the Host module, simply use remoteImport 187 | 188 | #### Version management 189 | 190 | - There are two ways to remotely introduce version control for components, with the latest version being introduced by default 191 | 192 | ```js 193 | remotes: { 194 | // By default, “the remoteEntries.js” file of the loginRemote application will be introduced, which will load the latest version of the remoteEntry file of the application 195 | 'loginRemote': { 196 | url: `/assets/login` 197 | }, 198 | // Will import '/assets/login/0.0.1/remoteEntry. js' as the entry file 199 | 'userRemote': { 200 | url: `/assets/login`, 201 | filename: '0.0.1/remoteEntry.js' 202 | }, 203 | } 204 | ``` 205 | 206 | ## Configuration Item Description 207 | 208 | ### `mode:string` 209 | 210 | - Control development mode or production mode, required. 211 | 212 | ### `exposes` 213 | 214 | - As a remote module, the list of exposed components must be filled out by the remote module. 215 | 216 | ```js 217 | exposes: { 218 | // 'Name of exposed component': 'Address of exposed component' 219 | 'Button': './src/components/Button.vue', 220 | 'Section': './src/components/Section.vue' 221 | } 222 | ``` 223 | 224 | --- 225 | 226 | ### `remotes` 227 | 228 | Host side references the resource entry file configuration of the Remote side 229 | 230 | #### `url:string` 231 | 232 | - Remote module address, for example: `/sets/login` `https://localhost:5011`, this configuration is required 233 | - The URL internally generates an external address, `${URL}/remoteEntries. js`, which will be used as the entry address for the remote module 234 | - The URL can be a relative address determined based on the packaging structure, or it can be a complete external address starting with http 235 | 236 | ```js 237 | remotes: { 238 | // '{Remote Module Name} Remote': 'Remote Module Entry File Address' 239 | 'loginRemote': { 240 | url: `/assets/login` 241 | }, 242 | } 243 | ``` 244 | 245 | #### `devUrl:string` 246 | 247 | - Remote module development environment address, for example: '/sets/login'`https://localhost:5011`, this configuration is not required 248 | - If devUrl is not configured, it defaults to the URL address or the relative path of the project 249 | - **\*\***When the URL is a relative address and devUrl is not configured, the format of the remote module name needs to be specified as `{Remote Module Name} Remote`. In the development environment, the remote module entry address will be generated based on the remote module name 250 | 251 | ```js 252 | remotes: { 253 | // '{Remote Module Name} Remote': 'Remote Module Entry File Address' 254 | 'loginRemote': { 255 | url: `https://www.vite-micro.com`, 256 | devUrl: `https://localhost:5011` 257 | }, 258 | } 259 | ``` 260 | 261 | #### `filename:string` 262 | 263 | - As the entry file for the remote module, it is not required and defaults to 'remoteEntries.js'. Please refer to version management for specific usage methods 264 | 265 | ### `shared` 266 | 267 | The shared dependencies between the Host and Remote modules. The host module needs to configure the dependencies of all remote modules used; The remote module needs to configure dependencies on externally provided components. 268 | 269 | - It is an array, which can be ['vue ',...], or [{...}] 270 | 271 | #### `name: string` 272 | 273 | - The name of the shared component, required 274 | 275 | #### `requiredVersion: string` 276 | 277 | - Only effective for the `remote` module, specifying the required version of the `host shared` used. When the version of the `host` module does not meet the 'required Version' requirements, the `shared` module will be used, and this feature will not be enabled by default 278 | 279 | ## Browsers support 280 | 281 | Modern browsers does not support IE browser 282 | 283 | | IEdge / IE | Firefox | Chrome | Safari | 284 | | ---------- | --------------- | --------------- | --------------- | 285 | | Edge | last 2 versions | last 2 versions | last 2 versions | 286 | 287 | ## 其他 288 | 289 | - The remote footsteps currently loaded do not support sandbox functionality, and the code needs to be constrained by specifications. 290 | - If you agree with this framework and find it helpful to you, I hope you can give me a star ^\_^ 291 | - If this framework brings value to your work career, I hope you can give me some donations, as it is not easy to create. 292 | 293 |  294 | --------------------------------------------------------------------------------
Go to Home
Go to Login
Go to User
Go to Button
6 | 7 | 8 | 9 | 10 | 11 | 12 |