vercel / micro

Asynchronous HTTP microservices
MIT License
10.58k stars 459 forks source link

Support native esm modules. #453

Closed dbo closed 2 years ago

dbo commented 2 years ago

Hi! ๐Ÿ‘‹

Firstly, thanks for your work on this project! ๐Ÿ™‚

Today I used patch-package to patch micro@9.3.4 for the project I'm working on.

Here is the diff that solved my problem:

diff --git a/node_modules/micro/lib/handler.js b/node_modules/micro/lib/handler.js
index 4b8fd1c..6e6439a 100644
--- a/node_modules/micro/lib/handler.js
+++ b/node_modules/micro/lib/handler.js
@@ -1,12 +1,36 @@
 // Utilities
+const path = require('path');
+const fs = require('fs');
 const logError = require('./error');

 module.exports = async file => {
    let mod;

    try {
-       mod = await require(file); // Await to support exporting Promises
+       let esm = /\.mjs$/.test(file);
+       if (!esm) { // look up package.json what kind it is
+           let dir = file;
+           while (true) {
+               const pkgPath = path.join(dir, 'package.json');
+               if (fs.existsSync(pkgPath)) {
+                   const pkg = JSON.parse(String(fs.readFileSync(pkgPath)));
+                   esm = /module/i.test(pkg.type);
+                   if (esm && dir === file) { // directory given, resolve main module for dynamic import
+                       const main = pkg.main || 'index.js';
+                       file = path.join(dir, ...main.split('/'));
+                   }
+                   break;
+               }
+               const parsed = path.parse(dir);
+               if (parsed.root === dir) {
+                   break;
+               }
+               dir = parsed.dir;
+           }
+       }
+       mod = esm ? await import(file) : require(file);

+       mod = await mod; // Await to support exporting Promises
        if (mod && typeof mod === 'object') {
            mod = await mod.default; // Await to support es6 module's default export
        }

This issue body was partially generated by patch-package.

leerob commented 2 years ago

https://github.com/vercel/micro/pull/448

dbo commented 2 years ago

@leerob The patch I submitted supports esm in a much more complete way, looking up the package's type like node.js does instead of urging to use the .mjs extension. Please consider it.

leerob commented 2 years ago

Would you be open to adding a PR with a test? Happy to review and merge it.

pmbanugo commented 2 years ago

@leerob What do you think about reopening this and solving it after we merge https://github.com/vercel/micro/pull/458 ?

leerob commented 2 years ago

Yeah I'm onboard.

TooTallNate commented 2 years ago

Why not just always use await import()?

leerob commented 2 years ago

I'm good with that as well, especially since we're doing a major version bump.

pmbanugo commented 2 years ago

Since we're using TS now, that gets transpiled to using require statements. We already use await import https://github.com/vercel/micro/blob/8d2ca07cea5f225752bf3e911e95cbf73caa3986/packages/micro/src/lib/handler.ts#L8 It was not a straightforward process to get that working the last time I tried. I plan to try a different solution when I get some time in the near future.

electerious commented 2 years ago

Is it correct that I currently can't use micro together with an ES Module / "type": "module"? Is there a recommended workaround or is it better to wait as this will be resolved soon?

pmbanugo commented 2 years ago

@electerious If you're not using micro programmatically then the cli will fail when processing ESM files. If you want to use ESM, then you can use it this way:

//programmatic usage

const micro = require('micro')

const server = micro(async (req, res) => {
  return 'Hello world'
})

server.listen(3000)

There would be a fix to load ESM files through the CLI in the near future.