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

Undefined behaviour when using TypeScript and export #1267

Closed
uhthomas opened this issue May 10, 2021 · 5 comments
Closed

Undefined behaviour when using TypeScript and export #1267

uhthomas opened this issue May 10, 2021 · 5 comments

Comments

@uhthomas
Copy link

Hi!

I'm trying to build an ESM for use with Cloudflare Workers.

Given a basic ESM

export default {
  async fetch(req) {
    return new Response();
  }
}

export class SomeDurableObject {
  async fetch(req) {
    return new Response();
  }
}

The (simplified) output ends up compiling to

export default {
    default: {
        fetch: ...
    }
    SomeDurableObject: {
      fetch: ...
    }
}

This breaks things a lot as they are not being exported correctly. There is a fix for the initial export default default such

export async function fetch(req) {
  ...
}

However, the class SomeDurableObject is still exported under default and so the Cloudflare Workers runtime cannot see it. As a workaround I'm having to write a custom shim to correctly export them.

config

esbuild(
    name = "bundle",
    args = ["--out-extension:.js=.mjs"],
    entry_point = "main.ts",
    format = "esm",
    output_dir = True,
    tool = "@com_github_evanw_esbuild//cmd/esbuild",
    visibility = ["//visibility:public"],
    deps = [":lib"],
)

version

v0.8.57
@uhthomas
Copy link
Author

This is the shim I'm using to get it to work properly.

import main from "./bundle/main.mjs"

const fetch = main.fetch;
const Results = main.Results;

export default { fetch };

export { Results };

@evanw
Copy link
Owner

evanw commented May 10, 2021

The (simplified) output ends up compiling to

export default {
    default: {
        fetch: ...
    }
    SomeDurableObject: {
      fetch: ...
    }
}

I get this output with esbuild main.ts --format=esm instead:

var main_default = {
  async fetch(req) {
    return new Response();
  }
};
var SomeDurableObject = class {
  async fetch(req) {
    return new Response();
  }
};
export {
  SomeDurableObject,
  main_default as default
};

This output looks correct. It does not export an object as a default export.

esbuild(
    name = "bundle",
    args = ["--out-extension:.js=.mjs"],
    entry_point = "main.ts",
    format = "esm",
    output_dir = True,
    tool = "@com_github_evanw_esbuild//cmd/esbuild",
    visibility = ["//visibility:public"],
    deps = [":lib"],
)

Is this Python? It looks like you are using some kind of esbuild wrapper made by someone else because esbuild doesn't have a Python API. I'm guessing that esbuild is working correctly but whatever extra code you are running is causing the problem. Either that or you are experiencing #706 but that was already fixed a while ago.

@uhthomas
Copy link
Author

I'm using Bazel, the configuration snippet is Starlark. See https://bazelbuild.github.io/rules_nodejs/esbuild.html

It's a wrapper around the standard esbuild binary.

For a full reproduction.

input

export default {
    async fetch(req: Request, env: any): Promise<Response> {
        return new Response();
    }
}

export class Results implements DurableObject {
    async fetch(req: Request): Promise<Response> {
        return new Response();
    }
}

output

var __commonJS = (callback, module) => () => {
  if (!module) {
    module = {exports: {}};
    callback(module.exports, module);
  }
  return module.exports;
};
var __async = (__this, __arguments, generator) => {
  return new Promise((resolve, reject) => {
    var fulfilled = (value) => {
      try {
        step(generator.next(value));
      } catch (e) {
        reject(e);
      }
    };
    var rejected = (value) => {
      try {
        step(generator.throw(value));
      } catch (e) {
        reject(e);
      }
    };
    var step = (result) => {
      return result.done ? resolve(result.value) : Promise.resolve(result.value).then(fulfilled, rejected);
    };
    step((generator = generator.apply(__this, __arguments)).next());
  });
};

// bazel-out/darwin-fastbuild/bin/cfapi-pytest/worker/src/index.js
var require_src = __commonJS((exports) => {
  "use strict";
  Object.defineProperty(exports, "__esModule", {value: true});
  exports.Results = void 0;
  exports.default = {
    fetch(req, env) {
      return __async(this, null, function* () {
        return new Response();
      });
    }
  };
  var Results = class {
    fetch(req) {
      return __async(this, null, function* () {
        return new Response();
      });
    }
  };
  exports.Results = Results;
});
export default require_src();

Using v0.11.20 has exactly the same behaviour.

It looks like this is the full command being run

esbuild --bundle bazel-out/darwin-fastbuild/bin/worker/src/index.js --sourcemap --preserve-symlinks '--platform=browser' '--target=es2015' '--log-level=info' '--metafile=bazel-out/darwin-fastbuild/bin/worker/src/bundle_metadata.json' '--error-limit=0' '--tree-shaking=ignore-annotations' '--sources-content=false' --splitting '--format=esm' '--outdir=bazel-out/darwin-fastbuild/bin/worker/src/bundle' '--tsconfig=bazel-out/darwin-fastbuild/bin/worker/src/bundle.config.json' '--out-extension:.js=.mjs' 

@uhthomas
Copy link
Author

Hm, maybe this isn't esbuild's fault.

This is the content of index.js which seems to be generated elsewhere.

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Results = void 0;
exports.default = {
    async fetch(req, env) {
        return new Response();
    }
};
class Results {
    async fetch(req) {
        return new Response();
    }
}
exports.Results = Results;

@uhthomas
Copy link
Author

Aha.

I had to update my tsconfig.

{
  "compilerOptions": {
-   "module": "commonjs",
+   "module": "esnext",
    "target": "esnext",
    ...
  }
}

Thanks so much for entertaining this issue. I'm glad it was a simple fix, even if I did spend a few hours on it. 😄

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

No branches or pull requests

2 participants