diff --git a/.gitignore b/.gitignore
index 587af51..2ee0033 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,4 @@
node_modules
-/lib
dist
.DS_Store
.idea
diff --git a/README.md b/README.md
index 6755b5a..4dd60d0 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,7 @@
# Vite plugin for Module Federation
+[![npm](https://img.shields.io/npm/v/@module-federation/vite.svg)](https://www.npmjs.com/package/@module-federation/vite)
+
## Reason why 🤔
[Microservices](https://martinfowler.com/articles/microservices.html) nowadays is a well-known concept and maybe you are using it in your current company.
@@ -15,6 +17,14 @@ This plugin makes Module Federation work together with [Vite](https://vitejs.dev
### [More examples here](https://github.com/module-federation/vite/tree/main/examples)
+```
+pnpm install && pnpm run dev-vv # vite+vite dev demo
+```
+
+```
+pnpm install && pnpm run preview-vv # vite+vite build demo
+```
+
## Getting started 🚀
https://module-federation.io/guide/basic/webpack.html
@@ -69,10 +79,11 @@ export default defineConfig({
## roadmap
-- feat: generate mf-manifest.json
-- feat: support chrome plugin
+- ✅ ~~feat: generate mf-manifest.json~~
+- ✅ ~~feat: support chrome plugin~~
* ✅ ~~feat: support runtime plugins~~
+* feat: nuxt ssr
- feat: download remote d.ts
- feat: generate d.ts
diff --git a/examples/nuxt-vite/nuxt-host/app.vue b/examples/nuxt-vite/nuxt-host/app.vue
index 0303fe1..04857f5 100644
--- a/examples/nuxt-vite/nuxt-host/app.vue
+++ b/examples/nuxt-vite/nuxt-host/app.vue
@@ -3,7 +3,7 @@
Nuxt host
- Mode: {{ runtimeConfig.app.buildId ? ' dev ' : ' prod ' }} mode
+ Mode1: {{ runtimeConfig.app.buildId ? ' dev ' : ' prod ' }} mode
diff --git a/examples/nuxt-vite/nuxt-host/nuxt.config.ts b/examples/nuxt-vite/nuxt-host/nuxt.config.ts
index 3215116..0a98f9d 100644
--- a/examples/nuxt-vite/nuxt-host/nuxt.config.ts
+++ b/examples/nuxt-vite/nuxt-host/nuxt.config.ts
@@ -1,24 +1,30 @@
import { federation } from '@module-federation/vite';
+import TopAwait from 'vite-plugin-top-level-await';
export default defineNuxtConfig({
compatibilityDate: '2024-04-03',
+ debug: true,
devtools: { enabled: true },
vite: {
plugins: [
federation({
- name: 'viteViteHost',
+ name: 'nuxhost',
remotes: {
- '@namespace/viteViteRemote': {
- entry: 'http://localhost:5176/remoteEntry.js',
- type: 'module',
- },
+ '@namespace/viteViteRemote': 'viteRemote@http://localhost:3000/_nuxt/mf-manifest.json',
},
filename: 'remoteEntry.js',
shared: {
- vue: {},
+ // vue: {},
},
runtimePlugins: ['./utils/mfPlugins'],
+ // exposes: {
+ // "./App": "./App.vue"
+ // }
+ // manifest: {
+ // fileName: "_nuxt/mf-manifest.json",
+ // }
}),
+ new TopAwait(),
],
build: {
target: 'chrome89',
diff --git a/examples/nuxt-vite/nuxt-host/package.json b/examples/nuxt-vite/nuxt-host/package.json
index a3dc010..558a8c9 100644
--- a/examples/nuxt-vite/nuxt-host/package.json
+++ b/examples/nuxt-vite/nuxt-host/package.json
@@ -4,14 +4,17 @@
"type": "module",
"scripts": {
"build": "nuxt build",
- "dev": "nuxt dev",
+ "dev": "NODE_OPTIONS=--experimental-vm-modules nuxt dev --experimental-vm-modules --port=3001",
"generate": "nuxt generate",
- "preview": "nuxt preview",
+ "preview": "NITRO_PORT=3001 nuxt preview",
"postinstall": "nuxt prepare"
},
"dependencies": {
"nuxt": "^3.13.0",
"vue": "latest",
"@module-federation/vite": "workspace:*"
+ },
+ "devDependencies": {
+ "vite-plugin-top-level-await": "^1.4.4"
}
}
diff --git a/examples/nuxt-vite/nuxt-remote/.gitignore b/examples/nuxt-vite/nuxt-remote/.gitignore
new file mode 100644
index 0000000..4a7f73a
--- /dev/null
+++ b/examples/nuxt-vite/nuxt-remote/.gitignore
@@ -0,0 +1,24 @@
+# Nuxt dev/build outputs
+.output
+.data
+.nuxt
+.nitro
+.cache
+dist
+
+# Node dependencies
+node_modules
+
+# Logs
+logs
+*.log
+
+# Misc
+.DS_Store
+.fleet
+.idea
+
+# Local env files
+.env
+.env.*
+!.env.example
diff --git a/examples/nuxt-vite/nuxt-remote/app.vue b/examples/nuxt-vite/nuxt-remote/app.vue
new file mode 100644
index 0000000..34930cb
--- /dev/null
+++ b/examples/nuxt-vite/nuxt-remote/app.vue
@@ -0,0 +1,14 @@
+
+
+
Nuxt host
+
+
+ Mode: {{ runtimeConfig.app.buildId ? ' dev ' : ' prod ' }} mode
+
+
+
+
+
+
diff --git a/examples/nuxt-vite/nuxt-remote/nuxt.config.ts b/examples/nuxt-vite/nuxt-remote/nuxt.config.ts
new file mode 100644
index 0000000..99cdd3a
--- /dev/null
+++ b/examples/nuxt-vite/nuxt-remote/nuxt.config.ts
@@ -0,0 +1,30 @@
+import { federation } from '@module-federation/vite';
+import TopAwait from 'vite-plugin-top-level-await';
+
+export default defineNuxtConfig({
+ compatibilityDate: '2024-04-03',
+ debug: true,
+ devtools: { enabled: true },
+ vite: {
+ plugins: [
+ federation({
+ name: 'nuxremote',
+ filename: 'remoteEntry.js',
+ shared: {
+ // vue: {},
+ },
+ runtimePlugins: ['./utils/mfPlugins'],
+ exposes: {
+ './app': './app.vue',
+ },
+ manifest: {
+ fileName: '_nuxt/mf-manifest.json',
+ },
+ }),
+ new TopAwait(),
+ ],
+ build: {
+ target: 'chrome89',
+ },
+ },
+});
diff --git a/examples/nuxt-vite/nuxt-remote/package.json b/examples/nuxt-vite/nuxt-remote/package.json
new file mode 100644
index 0000000..25e6f10
--- /dev/null
+++ b/examples/nuxt-vite/nuxt-remote/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "examples-nuxt-vite-remote",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "build": "nuxt build",
+ "dev": "NODE_OPTIONS=--experimental-vm-modules nuxt dev --experimental-vm-modules",
+ "generate": "nuxt generate",
+ "preview": "nuxt preview --port=3001",
+ "postinstall": "nuxt prepare"
+ },
+ "dependencies": {
+ "@module-federation/vite": "workspace:*",
+ "nuxt": "^3.13.0",
+ "vue": "latest"
+ },
+ "devDependencies": {
+ "vite-plugin-top-level-await": "^1.4.4"
+ }
+}
diff --git a/examples/nuxt-vite/nuxt-remote/public/favicon.ico b/examples/nuxt-vite/nuxt-remote/public/favicon.ico
new file mode 100644
index 0000000..18993ad
Binary files /dev/null and b/examples/nuxt-vite/nuxt-remote/public/favicon.ico differ
diff --git a/examples/nuxt-vite/nuxt-remote/public/robots.txt b/examples/nuxt-vite/nuxt-remote/public/robots.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/examples/nuxt-vite/nuxt-remote/public/robots.txt
@@ -0,0 +1 @@
+
diff --git a/examples/nuxt-vite/nuxt-remote/server/tsconfig.json b/examples/nuxt-vite/nuxt-remote/server/tsconfig.json
new file mode 100644
index 0000000..b9ed69c
--- /dev/null
+++ b/examples/nuxt-vite/nuxt-remote/server/tsconfig.json
@@ -0,0 +1,3 @@
+{
+ "extends": "../.nuxt/tsconfig.server.json"
+}
diff --git a/examples/nuxt-vite/nuxt-remote/tsconfig.json b/examples/nuxt-vite/nuxt-remote/tsconfig.json
new file mode 100644
index 0000000..a746f2a
--- /dev/null
+++ b/examples/nuxt-vite/nuxt-remote/tsconfig.json
@@ -0,0 +1,4 @@
+{
+ // https://nuxt.com/docs/guide/concepts/typescript
+ "extends": "./.nuxt/tsconfig.json"
+}
diff --git a/examples/vite-vite/vite-host/vite.config.js b/examples/vite-vite/vite-host/vite.config.js
index a4a6003..062e38d 100644
--- a/examples/vite-vite/vite-host/vite.config.js
+++ b/examples/vite-vite/vite-host/vite.config.js
@@ -22,15 +22,13 @@ export default defineConfig({
remote2: 'mfapp02@https://unpkg.com/mf-app-02/dist/remoteEntry.js',
remote3:
'remote1@https://unpkg.com/react-manifest-example_remote1@1.0.6/dist/mf-manifest.json',
- '@namespace/viteViteRemote': {
- entry: 'http://localhost:5176/remoteEntry.js',
- type: 'module',
- },
+ '@namespace/viteViteRemote': 'http://localhost:5176/mf-manifest.json',
},
- filename: 'remoteEntry.js',
+ filename: 'remoteEntry-[hash].js',
+ manifest: true,
shared: {
vue: {},
- react: {
+ 'react/': {
requiredVersion: '18',
},
'react-dom': {},
diff --git a/examples/vite-vite/vite-remote/vite.config.js b/examples/vite-vite/vite-remote/vite.config.js
index 7aefe5e..46624fa 100644
--- a/examples/vite-vite/vite-remote/vite.config.js
+++ b/examples/vite-vite/vite-remote/vite.config.js
@@ -15,14 +15,16 @@ export default defineConfig({
},
// base: 'http://localhost:5176',
experimental: {
- renderBuiltUrl() { return { relative: true } }
+ renderBuiltUrl() {
+ return { relative: true };
+ },
},
plugins: [
react({ jsxImportSource: '@emotion/react' }),
federation({
name: '@namespace/viteViteRemote',
exposes: {
- './App1': './src/App1.jsx',
+ './App1': './src/App1',
'./App2': './src/App2.jsx',
'./AgGridDemo': './src/AgGridDemo.jsx',
'./MuiDemo': './src/MuiDemo.jsx',
@@ -30,7 +32,8 @@ export default defineConfig({
'./EmotionDemo': './src/EmotionDemo.jsx',
'.': './src/App.jsx',
},
- filename: 'remoteEntry.js',
+ filename: 'remoteEntry-[hash].js',
+ manifest: true,
shared: {
vue: {},
'react/': {},
@@ -52,5 +55,12 @@ export default defineConfig({
],
build: {
target: 'chrome89',
+ rollupOptions: {
+ output: {
+ chunkFileNames: 'static/js/[name]-[hash].js',
+ entryFileNames: 'static/js/[name]-[hash].js',
+ assetFileNames: 'static/[ext]/[name]-[hash].[ext]',
+ },
+ },
},
});
diff --git a/lib/index.cjs b/lib/index.cjs
new file mode 100644
index 0000000..e3fd316
--- /dev/null
+++ b/lib/index.cjs
@@ -0,0 +1,1262 @@
+var fs = require('fs');
+var path = require('pathe');
+var pluginutils = require('@rollup/pluginutils');
+var estreeWalker = require('estree-walker');
+var MagicString = require('magic-string');
+var defu = require('defu');
+
+function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
+
+function _interopNamespace(e) {
+ if (e && e.__esModule) return e;
+ var n = Object.create(null);
+ if (e) {
+ Object.keys(e).forEach(function (k) {
+ if (k !== 'default') {
+ var d = Object.getOwnPropertyDescriptor(e, k);
+ Object.defineProperty(n, k, d.get ? d : {
+ enumerable: true,
+ get: function () { return e[k]; }
+ });
+ }
+ });
+ }
+ n["default"] = e;
+ return n;
+}
+
+var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
+var path__namespace = /*#__PURE__*/_interopNamespace(path);
+var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
+var MagicString__default = /*#__PURE__*/_interopDefaultLegacy(MagicString);
+
+var addEntry = function addEntry(_ref) {
+ var entryName = _ref.entryName,
+ entryPath = _ref.entryPath,
+ fileName = _ref.fileName;
+ var devEntryPath = entryPath.startsWith("virtual:mf") ? "/@id/" + entryPath : entryPath;
+ var entryFiles = [];
+ var htmlFilePath;
+ var _command;
+ return [{
+ name: 'add-entry',
+ apply: "serve",
+ config: function config(_config, _ref2) {
+ var command = _ref2.command;
+ _command = command;
+ },
+ configureServer: function configureServer(server) {
+ var _server$httpServer;
+ (_server$httpServer = server.httpServer) == null || _server$httpServer.once == null || _server$httpServer.once('listening', function () {
+ var port = server.config.server.port;
+ fetch(path__namespace.join("http://localhost:" + port, "" + devEntryPath))["catch"](function (e) {});
+ });
+ server.middlewares.use(function (req, res, next) {
+ if (!fileName) {
+ next();
+ return;
+ }
+ if (req.url && req.url.startsWith(fileName.replace(/^\/?/, '/'))) {
+ req.url = devEntryPath;
+ }
+ next();
+ });
+ },
+ transformIndexHtml: function transformIndexHtml(c) {
+ return c.replace('', "");
+ }
+ }, {
+ name: "add-entry",
+ enforce: "post",
+ configResolved: function configResolved(config) {
+ var inputOptions = config.build.rollupOptions.input;
+ if (!inputOptions) {
+ htmlFilePath = path__namespace.resolve(config.root, 'index.html');
+ } else if (typeof inputOptions === 'string') {
+ entryFiles = [inputOptions];
+ htmlFilePath = path__namespace.resolve(config.root, inputOptions);
+ } else if (Array.isArray(inputOptions)) {
+ entryFiles = inputOptions;
+ htmlFilePath = path__namespace.resolve(config.root, inputOptions[0]);
+ } else if (typeof inputOptions === 'object') {
+ entryFiles = Object.values(inputOptions);
+ htmlFilePath = path__namespace.resolve(config.root, Object.values(inputOptions)[0]);
+ }
+ },
+ buildStart: function buildStart() {
+ var _this$emitFile;
+ if (_command === "serve") return;
+ var hasHash = fileName == null || fileName.includes == null ? void 0 : fileName.includes("[hash");
+ this.emitFile((_this$emitFile = {
+ name: entryName
+ }, _this$emitFile[hasHash ? "name" : "fileName"] = fileName, _this$emitFile.type = 'chunk', _this$emitFile.id = entryPath, _this$emitFile.preserveSignature = 'strict', _this$emitFile));
+ if (htmlFilePath) {
+ var htmlContent = fs__namespace.readFileSync(htmlFilePath, 'utf-8');
+ var scriptRegex = /");
+ }
+ }, {
+ name: "add-entry",
+ enforce: "post",
+ configResolved: function configResolved(config) {
+ var inputOptions = config.build.rollupOptions.input;
+ if (!inputOptions) {
+ htmlFilePath = path.resolve(config.root, 'index.html');
+ } else if (typeof inputOptions === 'string') {
+ entryFiles = [inputOptions];
+ htmlFilePath = path.resolve(config.root, inputOptions);
+ } else if (Array.isArray(inputOptions)) {
+ entryFiles = inputOptions;
+ htmlFilePath = path.resolve(config.root, inputOptions[0]);
+ } else if (typeof inputOptions === 'object') {
+ entryFiles = Object.values(inputOptions);
+ htmlFilePath = path.resolve(config.root, Object.values(inputOptions)[0]);
+ }
+ },
+ buildStart: function buildStart() {
+ var _this$emitFile;
+ if (_command === "serve") return;
+ var hasHash = fileName == null || fileName.includes == null ? void 0 : fileName.includes("[hash");
+ this.emitFile((_this$emitFile = {
+ name: entryName
+ }, _this$emitFile[hasHash ? "name" : "fileName"] = fileName, _this$emitFile.type = 'chunk', _this$emitFile.id = entryPath, _this$emitFile.preserveSignature = 'strict', _this$emitFile));
+ if (htmlFilePath) {
+ var htmlContent = fs.readFileSync(htmlFilePath, 'utf-8');
+ var scriptRegex = /`);
+ }
+ }, {
+ name: "add-entry",
+ enforce: "post",
+ configResolved(config) {
+ const inputOptions = config.build.rollupOptions.input;
+ if (!inputOptions) {
+ htmlFilePath = path.resolve(config.root, 'index.html');
+ } else if (typeof inputOptions === 'string') {
+ entryFiles = [inputOptions];
+ htmlFilePath = path.resolve(config.root, inputOptions);
+ } else if (Array.isArray(inputOptions)) {
+ entryFiles = inputOptions;
+ htmlFilePath = path.resolve(config.root, inputOptions[0]);
+ } else if (typeof inputOptions === 'object') {
+ entryFiles = Object.values(inputOptions);
+ htmlFilePath = path.resolve(config.root, Object.values(inputOptions)[0]);
+ }
+ },
+ buildStart() {
+ if (_command === "serve") return;
+ const hasHash = fileName == null || fileName.includes == null ? void 0 : fileName.includes("[hash");
+ this.emitFile({
+ name: entryName,
+ [hasHash ? "name" : "fileName"]: fileName,
+ type: 'chunk',
+ id: entryPath,
+ preserveSignature: 'strict'
+ });
+ if (htmlFilePath) {
+ const htmlContent = fs.readFileSync(htmlFilePath, 'utf-8');
+ const scriptRegex = /");
+ }
+ }, {
+ name: "add-entry",
+ enforce: "post",
+ configResolved: function configResolved(config) {
+ var inputOptions = config.build.rollupOptions.input;
+ if (!inputOptions) {
+ htmlFilePath = path__namespace.resolve(config.root, 'index.html');
+ } else if (typeof inputOptions === 'string') {
+ entryFiles = [inputOptions];
+ htmlFilePath = path__namespace.resolve(config.root, inputOptions);
+ } else if (Array.isArray(inputOptions)) {
+ entryFiles = inputOptions;
+ htmlFilePath = path__namespace.resolve(config.root, inputOptions[0]);
+ } else if (typeof inputOptions === 'object') {
+ entryFiles = Object.values(inputOptions);
+ htmlFilePath = path__namespace.resolve(config.root, Object.values(inputOptions)[0]);
+ }
+ },
+ buildStart: function buildStart() {
+ var _this$emitFile;
+ if (_command === "serve") return;
+ var hasHash = fileName == null || fileName.includes == null ? void 0 : fileName.includes("[hash");
+ this.emitFile((_this$emitFile = {
+ name: entryName
+ }, _this$emitFile[hasHash ? "name" : "fileName"] = fileName, _this$emitFile.type = 'chunk', _this$emitFile.id = entryPath, _this$emitFile.preserveSignature = 'strict', _this$emitFile));
+ if (htmlFilePath) {
+ var htmlContent = fs__namespace.readFileSync(htmlFilePath, 'utf-8');
+ var scriptRegex = /