@esmx/core
Version:
A high-performance microfrontend framework supporting Vue, React, Preact, Solid, and Svelte with SSR and Module Federation capabilities.
609 lines (608 loc) • 19.4 kB
JavaScript
import path from "node:path";
import { describe, expect, it, vi } from "vitest";
import {
parseModuleConfig
} from "./module-config.mjs";
describe("module-config", () => {
const testModuleName = "test-module";
const testRoot = "/test/root";
describe("parseModuleConfig", () => {
it("should parse empty configuration with defaults", () => {
const config = {};
const result = parseModuleConfig(testModuleName, testRoot, config);
expect(result.name).toBe(testModuleName);
expect(result.root).toBe(testRoot);
expect(result.imports).toEqual({});
expect(result.links).toHaveProperty(testModuleName);
expect(result.exports).toHaveProperty("src/entry.client");
expect(result.exports).toHaveProperty("src/entry.server");
});
it("should parse configuration without config parameter", () => {
const result = parseModuleConfig(testModuleName, testRoot);
expect(result.name).toBe(testModuleName);
expect(result.root).toBe(testRoot);
expect(result.imports).toEqual({});
});
it("should parse complete configuration", () => {
const config = {
links: {
"shared-lib": "../shared-lib/dist",
"api-utils": "/absolute/path/api-utils/dist"
},
imports: {
axios: "shared-lib/axios",
lodash: "shared-lib/lodash"
},
exports: {
axios: "axios",
"src/utils/format": "./src/utils/format.ts",
"custom-api": "./src/api/custom.ts"
}
};
const result = parseModuleConfig(testModuleName, testRoot, config);
expect(result.name).toBe(testModuleName);
expect(result.root).toBe(testRoot);
expect(result.imports).toEqual(config.imports);
expect(result.links).toHaveProperty("shared-lib");
expect(result.links).toHaveProperty("api-utils");
expect(result.exports).toHaveProperty("axios");
expect(result.exports).toHaveProperty("src/utils/format");
expect(result.exports).toHaveProperty("custom-api");
});
});
describe("links processing", () => {
it("should create self-link with default dist path", () => {
const config = {};
const result = parseModuleConfig(testModuleName, testRoot, config);
const selfLink = result.links[testModuleName];
expect(selfLink).toBeDefined();
expect(selfLink.name).toBe(testModuleName);
expect(selfLink.root).toBe(path.resolve(testRoot, "dist"));
expect(selfLink.client).toBe(path.resolve(testRoot, "dist/client"));
expect(selfLink.server).toBe(path.resolve(testRoot, "dist/server"));
expect(selfLink.clientManifestJson).toBe(
path.resolve(testRoot, "dist/client/manifest.json")
);
expect(selfLink.serverManifestJson).toBe(
path.resolve(testRoot, "dist/server/manifest.json")
);
});
it("should process relative path links", () => {
const config = {
links: {
"shared-lib": "../shared-lib/dist"
}
};
const result = parseModuleConfig(testModuleName, testRoot, config);
const sharedLibLink = result.links["shared-lib"];
expect(sharedLibLink.name).toBe("shared-lib");
expect(sharedLibLink.root).toBe("../shared-lib/dist");
expect(sharedLibLink.client).toBe(
path.resolve(testRoot, "../shared-lib/dist/client")
);
expect(sharedLibLink.server).toBe(
path.resolve(testRoot, "../shared-lib/dist/server")
);
});
it("should process absolute path links", () => {
const absolutePath = "/absolute/path/api-utils/dist";
const config = {
links: {
"api-utils": absolutePath
}
};
const result = parseModuleConfig(testModuleName, testRoot, config);
const apiUtilsLink = result.links["api-utils"];
expect(apiUtilsLink.name).toBe("api-utils");
expect(apiUtilsLink.root).toBe(absolutePath);
expect(apiUtilsLink.client).toBe(
path.resolve(absolutePath, "client")
);
expect(apiUtilsLink.server).toBe(
path.resolve(absolutePath, "server")
);
});
it("should handle multiple links", () => {
const config = {
links: {
lib1: "../lib1/dist",
lib2: "/absolute/lib2/dist",
lib3: "./relative/lib3/dist"
}
};
const result = parseModuleConfig(testModuleName, testRoot, config);
expect(Object.keys(result.links)).toHaveLength(4);
expect(result.links).toHaveProperty("lib1");
expect(result.links).toHaveProperty("lib2");
expect(result.links).toHaveProperty("lib3");
expect(result.links).toHaveProperty(testModuleName);
});
});
describe("exports processing", () => {
describe("default exports", () => {
it("should add default entry exports", () => {
const config = {};
const result = parseModuleConfig(
testModuleName,
testRoot,
config
);
expect(result.exports["src/entry.client"]).toEqual({
name: "src/entry.client",
rewrite: true,
inputTarget: {
client: "./src/entry.client",
server: false
}
});
expect(result.exports["src/entry.server"]).toEqual({
name: "src/entry.server",
rewrite: true,
inputTarget: {
client: false,
server: "./src/entry.server"
}
});
});
});
describe("array format", () => {
it("should process npm: prefix exports", () => {
const config = {
exports: ["npm:axios", "npm:lodash"]
};
const result = parseModuleConfig(
testModuleName,
testRoot,
config
);
expect(result.exports.axios).toEqual({
name: "axios",
rewrite: false,
inputTarget: {
client: "axios",
server: "axios"
}
});
expect(result.exports.lodash).toEqual({
name: "lodash",
rewrite: false,
inputTarget: {
client: "lodash",
server: "lodash"
}
});
});
it("should process root: prefix exports with file extensions", () => {
const config = {
exports: [
"root:src/utils/format.ts",
"root:src/components/Button.jsx",
"root:src/api/client.js"
]
};
const result = parseModuleConfig(
testModuleName,
testRoot,
config
);
expect(result.exports["src/utils/format"]).toEqual({
name: "src/utils/format",
rewrite: true,
inputTarget: {
client: "./src/utils/format",
server: "./src/utils/format"
}
});
expect(result.exports["src/components/Button"]).toEqual({
name: "src/components/Button",
rewrite: true,
inputTarget: {
client: "./src/components/Button",
server: "./src/components/Button"
}
});
expect(result.exports["src/api/client"]).toEqual({
name: "src/api/client",
rewrite: true,
inputTarget: {
client: "./src/api/client",
server: "./src/api/client"
}
});
});
it("should handle all supported file extensions", () => {
const extensions = [
"js",
"mjs",
"cjs",
"jsx",
"mjsx",
"cjsx",
"ts",
"mts",
"cts",
"tsx",
"mtsx",
"ctsx"
];
const config = {
exports: extensions.map((ext) => `root:src/test.${ext}`)
};
const result = parseModuleConfig(
testModuleName,
testRoot,
config
);
extensions.forEach((ext) => {
expect(result.exports["src/test"]).toBeDefined();
});
});
it("should handle object exports in array", () => {
const config = {
exports: [
"npm:axios",
{
"custom-api": "./src/api/custom.ts",
utils: {
input: "./src/utils/index.ts",
rewrite: true
}
}
]
};
const result = parseModuleConfig(
testModuleName,
testRoot,
config
);
expect(result.exports["custom-api"]).toEqual({
name: "custom-api",
rewrite: true,
inputTarget: {
client: "./src/api/custom.ts",
server: "./src/api/custom.ts"
}
});
expect(result.exports.utils).toEqual({
name: "utils",
rewrite: true,
inputTarget: {
client: "./src/utils/index.ts",
server: "./src/utils/index.ts"
}
});
});
it("should handle invalid export strings", () => {
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {
});
const config = {
exports: ["invalid-export", "another-invalid"]
};
parseModuleConfig(testModuleName, testRoot, config);
expect(consoleSpy).toHaveBeenCalledWith(
"Invalid module export: invalid-export"
);
expect(consoleSpy).toHaveBeenCalledWith(
"Invalid module export: another-invalid"
);
consoleSpy.mockRestore();
});
});
describe("object format", () => {
it("should process simple string mappings", () => {
const config = {
exports: {
axios: "axios",
utils: "./src/utils/index.ts"
}
};
const result = parseModuleConfig(
testModuleName,
testRoot,
config
);
expect(result.exports.axios).toEqual({
name: "axios",
rewrite: true,
inputTarget: {
client: "axios",
server: "axios"
}
});
expect(result.exports.utils).toEqual({
name: "utils",
rewrite: true,
inputTarget: {
client: "./src/utils/index.ts",
server: "./src/utils/index.ts"
}
});
});
it("should process complete export objects", () => {
const config = {
exports: {
storage: {
inputTarget: {
client: "./src/storage/indexedDB.ts",
server: "./src/storage/filesystem.ts"
},
rewrite: true
},
"npm-package": {
input: "some-package",
rewrite: false
}
}
};
const result = parseModuleConfig(
testModuleName,
testRoot,
config
);
expect(result.exports.storage).toEqual({
name: "storage",
rewrite: true,
inputTarget: {
client: "./src/storage/indexedDB.ts",
server: "./src/storage/filesystem.ts"
}
});
expect(result.exports["npm-package"]).toEqual({
name: "npm-package",
rewrite: false,
inputTarget: {
client: "some-package",
server: "some-package"
}
});
});
it("should handle inputTarget with false values", () => {
const config = {
exports: {
"client-only": {
inputTarget: {
client: "./src/client-feature.ts",
server: false
}
},
"server-only": {
inputTarget: {
client: false,
server: "./src/server-feature.ts"
}
}
}
};
const result = parseModuleConfig(
testModuleName,
testRoot,
config
);
expect(result.exports["client-only"]).toEqual({
name: "client-only",
rewrite: true,
inputTarget: {
client: "./src/client-feature.ts",
server: false
}
});
expect(result.exports["server-only"]).toEqual({
name: "server-only",
rewrite: true,
inputTarget: {
client: false,
server: "./src/server-feature.ts"
}
});
});
});
describe("mixed configurations", () => {
it("should handle complex mixed export configuration", () => {
const config = {
exports: {
// Simple string mapping
simple: "./src/simple.ts",
// Complete object with inputTarget
complex: {
inputTarget: {
client: "./src/complex.client.ts",
server: "./src/complex.server.ts"
},
rewrite: false
},
// Object with just input
"with-input": {
input: "./src/with-input.ts"
},
// Object with just rewrite
"with-rewrite": {
rewrite: false
}
}
};
const result = parseModuleConfig(
testModuleName,
testRoot,
config
);
expect(result.exports.simple).toEqual({
name: "simple",
rewrite: true,
inputTarget: {
client: "./src/simple.ts",
server: "./src/simple.ts"
}
});
expect(result.exports.complex).toEqual({
name: "complex",
rewrite: false,
inputTarget: {
client: "./src/complex.client.ts",
server: "./src/complex.server.ts"
}
});
expect(result.exports["with-input"]).toEqual({
name: "with-input",
rewrite: true,
inputTarget: {
client: "./src/with-input.ts",
server: "./src/with-input.ts"
}
});
expect(result.exports["with-rewrite"]).toEqual({
name: "with-rewrite",
rewrite: false,
inputTarget: {
client: "with-rewrite",
server: "with-rewrite"
}
});
});
});
describe("default value handling", () => {
it("should use default rewrite value of true", () => {
const config = {
exports: {
"test-export": {
input: "./src/test.ts"
// rewrite not specified, should default to true
}
}
};
const result = parseModuleConfig(
testModuleName,
testRoot,
config
);
expect(result.exports["test-export"].rewrite).toBe(true);
});
it("should use export name as fallback for input paths", () => {
const config = {
exports: {
"fallback-test": {
// No input or inputTarget specified
rewrite: false
}
}
};
const result = parseModuleConfig(
testModuleName,
testRoot,
config
);
expect(result.exports["fallback-test"].inputTarget).toEqual({
client: "fallback-test",
server: "fallback-test"
});
});
});
});
describe("imports processing", () => {
it("should pass through imports configuration unchanged", () => {
const imports = {
axios: "shared-lib/axios",
lodash: "shared-lib/lodash",
"custom-lib": "api-utils/custom"
};
const config = { imports };
const result = parseModuleConfig(testModuleName, testRoot, config);
expect(result.imports).toEqual(imports);
});
it("should handle empty imports", () => {
const config = {};
const result = parseModuleConfig(testModuleName, testRoot, config);
expect(result.imports).toEqual({});
});
it("should handle undefined imports", () => {
const config = {
links: { test: "./test" },
exports: ["npm:axios"]
// imports intentionally omitted
};
const result = parseModuleConfig(testModuleName, testRoot, config);
expect(result.imports).toEqual({});
});
});
describe("edge cases", () => {
it("should handle empty exports array", () => {
const config = {
exports: []
};
const result = parseModuleConfig(testModuleName, testRoot, config);
expect(result.exports).toHaveProperty("src/entry.client");
expect(result.exports).toHaveProperty("src/entry.server");
expect(Object.keys(result.exports)).toHaveLength(2);
});
it("should handle empty exports object", () => {
const config = {
exports: {}
};
const result = parseModuleConfig(testModuleName, testRoot, config);
expect(result.exports).toHaveProperty("src/entry.client");
expect(result.exports).toHaveProperty("src/entry.server");
expect(Object.keys(result.exports)).toHaveLength(2);
});
it("should handle null and undefined values gracefully", () => {
const config = {
links: void 0,
imports: void 0,
exports: void 0
};
const result = parseModuleConfig(testModuleName, testRoot, config);
expect(result.links).toHaveProperty(testModuleName);
expect(result.imports).toEqual({});
expect(result.exports).toHaveProperty("src/entry.client");
expect(result.exports).toHaveProperty("src/entry.server");
});
it("should handle special characters in module names and paths", () => {
const specialModuleName = "test-module_with.special@chars";
const config = {
links: {
"special-lib@1.0.0": "../special-lib/dist"
},
exports: {
"special_export-name": "./src/special.ts"
}
};
const result = parseModuleConfig(
specialModuleName,
testRoot,
config
);
expect(result.name).toBe(specialModuleName);
expect(result.links).toHaveProperty("special-lib@1.0.0");
expect(result.exports).toHaveProperty("special_export-name");
});
});
describe("type safety", () => {
it("should maintain type safety for ParsedModuleConfig", () => {
const config = {
links: { test: "./test" },
imports: { axios: "test/axios" },
exports: ["npm:lodash"]
};
const result = parseModuleConfig(
testModuleName,
testRoot,
config
);
expect(typeof result.name).toBe("string");
expect(typeof result.root).toBe("string");
expect(typeof result.links).toBe("object");
expect(typeof result.imports).toBe("object");
expect(typeof result.exports).toBe("object");
});
it("should maintain type safety for ParsedModuleConfigExport", () => {
const config = {
exports: ["npm:axios"]
};
const result = parseModuleConfig(testModuleName, testRoot, config);
const exportConfig = result.exports.axios;
expect(typeof exportConfig.name).toBe("string");
expect(typeof exportConfig.rewrite).toBe("boolean");
expect(typeof exportConfig.inputTarget).toBe("object");
expect(typeof exportConfig.inputTarget.client).toBe("string");
expect(typeof exportConfig.inputTarget.server).toBe("string");
});
});
});