diff --git a/lib/library/SystemLibraryPlugin.js b/lib/library/SystemLibraryPlugin.js index 380962091..36526fa53 100644 --- a/lib/library/SystemLibraryPlugin.js +++ b/lib/library/SystemLibraryPlugin.js @@ -133,7 +133,20 @@ class SystemLibraryPlugin extends AbstractLibraryPlugin { chunk.runtime ); if (used) { - if (otherUnused || used !== exportInfo.name) { + if (used === "default" && exportInfo.name === "default") { + instructions.push( + Template.asString([ + "if (typeof module.default !== 'undefined') {", + Template.indent([ + `${external}.default = module.default;` + ]), + "} else {", + Template.indent([`${external} = module;`]), + "}" + ]) + ); + handledNames.push(exportInfo.name); + } else if (otherUnused || used !== exportInfo.name) { instructions.push( `${external}${propertyAccess([ used @@ -172,9 +185,24 @@ class SystemLibraryPlugin extends AbstractLibraryPlugin { } else { instructions.push( Template.asString([ - "Object.keys(module).forEach(function(key) {", - Template.indent([`${external}[key] = module[key];`]), - "});" + "if (typeof module.default !== 'undefined') {", + Template.indent([ + "Object.keys(module).forEach(function(key) {", + Template.indent([ + `${external}[key] = module[key];` + ]), + "});" + ]), + "} else {", + Template.indent([ + `${external} = module;`, + "Object.keys(module).forEach(function(key) {", + Template.indent([ + `${external}[key] = module[key];` + ]), + "});" + ]), + "}" ]) ); } diff --git a/test/configCases/target/system-default-import-fix/index.js b/test/configCases/target/system-default-import-fix/index.js new file mode 100644 index 000000000..83e9d9f92 --- /dev/null +++ b/test/configCases/target/system-default-import-fix/index.js @@ -0,0 +1,19 @@ +import React, { useEffect } from "react"; + +/* This test verifies that default imports work correctly with SystemJS externals + * when the external module doesn't have a default property (e.g., React) + */ + +it("should correctly handle default import from SystemJS external", function() { + // React should be the entire module object, not undefined + expect(React).toBeDefined(); + expect(typeof React).toBe("object"); + expect(React.Component).toBeDefined(); + expect(React.Fragment).toBeDefined(); + + // Named imports should still work + expect(typeof useEffect).toBe("function"); + + // The default import should not be undefined + expect(React).not.toBeUndefined(); +}); diff --git a/test/configCases/target/system-default-import-fix/react-with-default.js b/test/configCases/target/system-default-import-fix/react-with-default.js new file mode 100644 index 000000000..f8e36b12e --- /dev/null +++ b/test/configCases/target/system-default-import-fix/react-with-default.js @@ -0,0 +1,26 @@ +// Mock React module that HAS a default property +// This simulates ES6 modules with default exports +const React = { + Component: function Component() {}, + Fragment: Symbol("react.fragment"), + Profiler: Symbol("react.profiler"), + useEffect: function useEffect(callback, deps) { + return callback(); + }, + useState: function useState(initial) { + return [initial, function() {}]; + }, + createElement: function createElement(type, props, ...children) { + return { type, props, children }; + } +}; + +// Export with default property (ES6 module format) +module.exports = React; +module.exports.default = React; +module.exports.useEffect = React.useEffect; +module.exports.Component = React.Component; +module.exports.Fragment = React.Fragment; +module.exports.Profiler = React.Profiler; +module.exports.useState = React.useState; +module.exports.createElement = React.createElement; diff --git a/test/configCases/target/system-default-import-fix/react.js b/test/configCases/target/system-default-import-fix/react.js new file mode 100644 index 000000000..2b59fb098 --- /dev/null +++ b/test/configCases/target/system-default-import-fix/react.js @@ -0,0 +1,25 @@ +// Mock React module that doesn't have a default property +// This simulates how React is typically exported in SystemJS environments +const React = { + Component: function Component() {}, + Fragment: Symbol("react.fragment"), + Profiler: Symbol("react.profiler"), + useEffect: function useEffect(callback, deps) { + return callback(); + }, + useState: function useState(initial) { + return [initial, function() {}]; + }, + createElement: function createElement(type, props, ...children) { + return { type, props, children }; + } +}; + +// Export named exports (SystemJS style - no default property) +module.exports = React; +module.exports.useEffect = React.useEffect; +module.exports.Component = React.Component; +module.exports.Fragment = React.Fragment; +module.exports.Profiler = React.Profiler; +module.exports.useState = React.useState; +module.exports.createElement = React.createElement; diff --git a/test/configCases/target/system-default-import-fix/test.config.js b/test/configCases/target/system-default-import-fix/test.config.js new file mode 100644 index 000000000..7d86e2c73 --- /dev/null +++ b/test/configCases/target/system-default-import-fix/test.config.js @@ -0,0 +1,27 @@ +"use strict"; + +const path = require("path"); + +/** @type {import("../../../../").Configuration} */ +module.exports = { + entry: "./test.js", + output: { + path: path.resolve(__dirname, "dist"), + filename: "bundle.js", + libraryTarget: "system" + }, + externals: { + react: "react", + "react-with-default": "react-with-default" + }, + resolve: { + alias: { + react: path.resolve(__dirname, "react.js"), + "react-with-default": path.resolve(__dirname, "react-with-default.js") + } + }, + node: { + __dirname: false, + __filename: false + } +}; diff --git a/test/configCases/target/system-default-import-fix/test.js b/test/configCases/target/system-default-import-fix/test.js new file mode 100644 index 000000000..d01378be1 --- /dev/null +++ b/test/configCases/target/system-default-import-fix/test.js @@ -0,0 +1,36 @@ +import React, { useEffect } from "react"; +import ReactWithDefault, { useEffect as useEffectWithDefault } from "react-with-default"; + +/* This test verifies that default imports work correctly with SystemJS externals + * in both scenarios: + * 1. External module without default property (traditional SystemJS) + * 2. External module with default property (ES6 module format) + */ + +it("should correctly handle default import from SystemJS external without default property", function() { + // React should be the entire module object, not undefined + expect(React).toBeDefined(); + expect(typeof React).toBe("object"); + expect(React.Component).toBeDefined(); + expect(React.Fragment).toBeDefined(); + + // Named imports should still work + expect(typeof useEffect).toBe("function"); + + // The default import should not be undefined + expect(React).not.toBeUndefined(); +}); + +it("should correctly handle default import from SystemJS external with default property", function() { + // ReactWithDefault should be the default property value + expect(ReactWithDefault).toBeDefined(); + expect(typeof ReactWithDefault).toBe("object"); + expect(ReactWithDefault.Component).toBeDefined(); + expect(ReactWithDefault.Fragment).toBeDefined(); + + // Named imports should still work + expect(typeof useEffectWithDefault).toBe("function"); + + // The default import should not be undefined + expect(ReactWithDefault).not.toBeUndefined(); +}); diff --git a/test/configCases/target/system-default-import-fix/webpack.config.js b/test/configCases/target/system-default-import-fix/webpack.config.js new file mode 100644 index 000000000..2e66a663c --- /dev/null +++ b/test/configCases/target/system-default-import-fix/webpack.config.js @@ -0,0 +1,15 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + output: { + libraryTarget: "system" + }, + externals: { + react: "react" + }, + node: { + __dirname: false, + __filename: false + } +};