Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

'use client' directive is not preserved at top level when module type is 'umd' #8728

Open
sohnjunior opened this issue Mar 11, 2024 · 3 comments
Assignees
Labels

Comments

@sohnjunior
Copy link

sohnjunior commented Mar 11, 2024

Describe the bug

// input.ts

'use client';

export function ClientComponent() {
  return 'Hello world';
}
> swc input.ts -o output.js -C module.type=umd
// output.js

(function(global, factory) {
    if (typeof module === "object" && typeof module.exports === "object") factory(exports);
    else if (typeof define === "function" && define.amd) define([
        "exports"
    ], factory);
    else if (global = typeof globalThis !== "undefined" ? globalThis : global || self) factory(global.input = {});
})(this, function(exports) {
    "use client";
    "use strict";
    Object.defineProperty(exports, "__esModule", {
        value: true
    });
    Object.defineProperty(exports, "ClientComponent", {
        enumerable: true,
        get: function() {
            return ClientComponent;
        }
    });
    function ClientComponent() {
        return "Hello world";
    }
});

The 'use client' directive is defined inside the factory function.

Perhaps because of this, an error saying 'use client directive does not exist' is occurring in the Next.js environment that uses the App directory.

This issue is related with #7315

Input code

No response

Config

No response

Playground link (or link to the minimal reproduction)

https://play.swc.rs/?version=1.3.100&code=H4sIAAAAAAAAAx3LMQqAMAxG4T2n%2BLfqYg%2Fg2MV71AiFmpQ2RUG8u8XtwcfzHklKt8Uaud4YMScWcysR30Wr4egSLakg%2FBL0LCojphkPAZWtV4HbOGfFpTXvY37pA1C%2FhblZAAAA&config=H4sIAAAAAAAAA1WPSw7DIAwF95wCed1tN71DD4GIE1GFj2wjFUW5eyGFtNnh98YMbEpreLGFh97qsQ7JECOdc024BDHvmoCUhGzJJYHbaIVbNZuV8Yj2bwNiaEFpW8j3jsMaI%2BPAe%2BZdcHP5F9roEyHzFWyoCcuKV53qSvBxykfZP9Ie2%2FTZT%2FCDhuy8GBw%2Fx6ZQRrV%2FAL%2FpEOoUAQAA

SWC Info output

No response

Expected behavior

// output.js

+ "use client";
+ "use strict";

(function(global, factory) {
    if (typeof module === "object" && typeof module.exports === "object") factory(exports);
    else if (typeof define === "function" && define.amd) define([
        "exports"
    ], factory);
    else if (global = typeof globalThis !== "undefined" ? globalThis : global || self) factory(global.input = {});
})(this, function(exports) {
-    "use client";
-    "use strict";
    Object.defineProperty(exports, "__esModule", {
        value: true
    });
    Object.defineProperty(exports, "ClientComponent", {
        enumerable: true,
        get: function() {
            return ClientComponent;
        }
    });
    function ClientComponent() {
        return "Hello world";
    }
});

The 'use client' directive is kept at the top of the file.

Actual behavior

No response

Version

1.3.101

Additional context

No response

@sohnjunior sohnjunior changed the title 'use client' directive is not working when module type is 'umd' 'use client' directive is not preserved when module type is 'umd' Mar 11, 2024
@sohnjunior sohnjunior changed the title 'use client' directive is not preserved when module type is 'umd' 'use client' directive is not preserved at top level when module type is 'umd' Mar 11, 2024
@magic-akari magic-akari self-assigned this Mar 13, 2024
@magic-akari
Copy link
Member

Investigation:

Babel

(function (global, factory) {
  if (typeof define === "function" && define.amd) {
    define(["exports"], factory);
  } else if (typeof exports !== "undefined") {
    factory(exports);
  } else {
    var mod = {
      exports: {}
    };
    factory(mod.exports);
    global.repl = mod.exports;
  }
})(typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : this, function (_exports) {
  "use strict";
  'use client';

  Object.defineProperty(_exports, "__esModule", {
    value: true
  });
  _exports.ClientComponent = ClientComponent;
  function ClientComponent() {
    return 'Hello world';
  }
});

TypeScript

(function (factory) {
    if (typeof module === "object" && typeof module.exports === "object") {
        var v = factory(require, exports);
        if (v !== undefined) module.exports = v;
    }
    else if (typeof define === "function" && define.amd) {
        define(["require", "exports"], factory);
    }
})(function (require, exports) {
    "use strict";
    // @module: umd
    'use client';
    Object.defineProperty(exports, "__esModule", { value: true });
    exports.ClientComponent = void 0;
    function ClientComponent() {
        return 'Hello world';
    }
    exports.ClientComponent = ClientComponent;
});

@magic-akari
Copy link
Member

magic-akari commented Mar 14, 2024

The use strict directive must be inside the UMD function and not outside, because UMD is safe to be used with simple file concatenation and thus directives at the top of the files would risk getting lost (and just transformed into normal string expression statements)

from @nicolo-ribaudo (https://x.com/NicoloRibaudo/status/1767871497400373272)

I fully agree with this statement. For UMD (as well as similar AMD and SystemJS), the actual module code resides within its function body segment. Given that the toolchain needs to accommodate more generic logic, we don't want to mess with how things currently work.

In the case of UMD, there is a common expectation that it can be easily concatenated together. In such scenarios, we want directives to only affect the code inside the module, rather than the global code.

The root of this issue lies in the differences between toolchain's expectations of directives and how React perceives directives. Within React, directives need to be at the file level and must be at the top of the file to take effect. Conversely, in the toolchain's perspective, directives function at the block level and can be safely relocated to their new scope during the transformation process.

This misalignment of expectations has led to several challenges. We've got a few ways to tackle it:

  1. The first involves starting from React, enabling React to recognize UMD/AMD/SystemJS module code. However, due to the flexibility of module wrappers, this approach proves challenging in practice.
  2. Acknowledge that UMD and React don't mix well for client components that you can't just stick together. It might be better to use CJS or stick with ESM instead.
  3. Create a custom SWC plugin to move directives where they need to go within the plugin.

@sohnjunior
Copy link
Author

sohnjunior commented Apr 8, 2024

Thank you for your feedback. Fortunately, I identified that I no longer needed to build modules with UMD in my environment, so I decided to adopt the CJS modular approach.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

No branches or pull requests

2 participants