Repository: netlify/zip-it-and-ship-it (js)

Issue:
Allow `.mjs` entrypoints for functions
**- Do you want to request a _feature_ or report a _bug_?**

Feature

**- What is the current behavior?**

While zisi can bundle ESM files with`.mjs` extensions, it does not recognize them as entrypoints.

**- If the current behavior is a bug, please provide the steps to reproduce.**

1. Create a function in `netlify/functions/hello/hello.mjs`
2. Run `zip-it-and-ship-it netlify/functions out_functions`

**- What is the expected behavior?**
The function `hello` is bundled, defaulting to esbuild 



Pull request description:
feat: allow JS functions with `.mjs` extension
**- Summary**

Adds support for JavaScript functions using a `.mjs` extension. These functions will use the esbuild bundler.

Closes #588.

**- Test plan**

New test added.

**- A picture of a cute animal (not mandatory but encouraged)**

![maxresdefault](https://user-images.githubusercontent.com/4162329/130224756-132939e7-70d6-4398-901e-91c35a0243fd.jpg)


Patch:
diff --git a/src/runtimes/node/finder.js b/src/runtimes/node/finder.js
index 08d0af4..2aefbd4 100644
--- a/src/runtimes/node/finder.js
+++ b/src/runtimes/node/finder.js
@@ -74,6 +74,8 @@ const getMainFile = function (srcPath, filename, stat) {
       [
         join(srcPath, `${filename}.js`),
         join(srcPath, 'index.js'),
+        join(srcPath, `${filename}.mjs`),
+        join(srcPath, 'index.mjs'),
         join(srcPath, `${filename}.ts`),
         join(srcPath, 'index.ts'),
       ],
diff --git a/src/runtimes/node/index.js b/src/runtimes/node/index.js
index 547f04e..ba08759 100644
--- a/src/runtimes/node/index.js
+++ b/src/runtimes/node/index.js
@@ -9,9 +9,10 @@ const { getSrcFiles } = require('./src_files')
 const { zipEsbuild } = require('./zip_esbuild')
 const { zipZisi } = require('./zip_zisi')
 
-// We use ZISI as the default bundler until the next major release, with the
-// exception of TypeScript files, for which the only option is esbuild.
-const getDefaultBundler = ({ extension }) => (extension === '.ts' ? JS_BUNDLER_ESBUILD : JS_BUNDLER_ZISI)
+// We use ZISI as the default bundler, except for certain extensions, for which
+// esbuild is the only option.
+const getDefaultBundler = ({ extension }) =>
+  ['.mjs', '.ts'].includes(extension) ? JS_BUNDLER_ESBUILD : JS_BUNDLER_ZISI
 
 // A proxy for the `getSrcFiles` function which adds a default `bundler` using
 // the `getDefaultBundler` function.


Test patch:
diff --git a/tests/fixtures/node-mjs/func1.mjs b/tests/fixtures/node-mjs/func1.mjs
new file mode 100644
index 0000000..767df32
--- /dev/null
+++ b/tests/fixtures/node-mjs/func1.mjs
@@ -0,0 +1,1 @@
+export const handler = () => true
diff --git a/tests/fixtures/node-mjs/func2/func2.mjs b/tests/fixtures/node-mjs/func2/func2.mjs
new file mode 100644
index 0000000..767df32
--- /dev/null
+++ b/tests/fixtures/node-mjs/func2/func2.mjs
@@ -0,0 +1,1 @@
+export const handler = () => true
diff --git a/tests/fixtures/node-mjs/func3/index.mjs b/tests/fixtures/node-mjs/func3/index.mjs
new file mode 100644
index 0000000..767df32
--- /dev/null
+++ b/tests/fixtures/node-mjs/func3/index.mjs
@@ -0,0 +1,1 @@
+export const handler = () => true
diff --git a/tests/main.js b/tests/main.js
index 57dec83..c259b01 100644
--- a/tests/main.js
+++ b/tests/main.js
@@ -952,6 +952,30 @@ testBundlers('Handles a TypeScript function with imports', [ESBUILD, ESBUILD_ZIS
   t.true(typeof require(`${tmpDir}/function.js`).type === 'string')
 })
 
+testBundlers(
+  'Handles a JavaScript function ({name}.mjs, {name}/{name}.mjs, {name}/index.mjs)',
+  [ESBUILD, ESBUILD_ZISI, DEFAULT],
+  async (bundler, t) => {
+    const { files, tmpDir } = await zipFixture(t, 'node-mjs', {
+      length: 3,
+      opts: { config: { '*': { nodeBundler: bundler } } },
+    })
+
+    await unzipFiles(files)
+
+    t.is(files.length, 3)
+    files.forEach((file) => {
+      t.is(file.bundler, 'esbuild')
+    })
+
+    /* eslint-disable import/no-dynamic-require, node/global-require */
+    t.true(require(`${tmpDir}/func1.js`).handler())
+    t.true(require(`${tmpDir}/func2.js`).handler())
+    t.true(require(`${tmpDir}/func3.js`).handler())
+    /* eslint-enable import/no-dynamic-require, node/global-require */
+  },
+)
+
 testBundlers(
   'Loads a tsconfig.json placed in the same directory as the function',
   [ESBUILD, ESBUILD_ZISI, DEFAULT],