floydspace / serverless-esbuild

πŸ’¨ A Serverless framework plugin to bundle JavaScript and TypeScript with extremely fast esbuild
MIT License
452 stars 139 forks source link

Esbuild plugins do not work. Serverless exists with error: Plugin is missing a setup function #359

Open robblovell opened 2 years ago

robblovell commented 2 years ago

Describe the bug

When attempting to use any esbuild plugin, the following error occurs: node_modules/esbuild/lib/main.js:786:16: ERROR: [plugin: plugin:copy] Plugin is missing a setup function

To Reproduce

This error occurs with any plugin.

Here's a git repo that demonstrates the problem in both javascript and typescript: plugin-missing-setup-function

Example:

app.ts

export const api = () => {
  console.log('Hello from the custom Lambda!')
}

helloPlugin.ts

export const helloPlugin = {
  name: 'env',
  setup(build) {
    console.log('Hello World')
  },
}

serverless.ts

import type { Serverless } from 'serverless/aws';
import { helloPlugin } from './helloPlugin'

const serverlessConfiguration: Partial<Serverless> = <Serverless>{
  frameworkVersion: '3',
  service: 'thing',
  plugins: [
    'serverless-esbuild',
    'serverless-offline',
  ],
  package: {
    individually: true,
  },
  custom: {
    esbuild: {
      entryPoints: ['app.js'],
      bundle: true,
      outfile: 'out.js',
      plugins: [helloPlugin],
    },
    'serverless-offline': {
      host: "",
      httpPort: "",
    },
  },
  provider: {
    name: 'aws',
    runtime: 'nodejs16.x',
    stage: 'staging',
  },
  functions: {
    'hello-world': {
      handler: 'app.js',
    }
  }
}
module.exports = serverlessConfiguration;

package.json

{
  "name": "plugin-missing-setup-function",
  "version": "0.0.1",
  "description": "",
  "main": "app.js",
  "license": "ISC",
  "devDependencies": {
    "@types/aws-lambda": "^8.10.44",
    "@types/serverless": "3.12.7",
    "esbuild-plugin-tsc": "^0.3.1",
    "serverless": "^3.21.0",
    "serverless-esbuild": "^1.32.5",
    "serverless-offline": "^8.8.1"
  }
}

Independent esbuild.config.ts

import { build, BuildOptions } from 'esbuild'
import { helloPlugin } from './helloPlugin'

const options = {
  entryPoints: ['app.ts'],
  bundle: true,
  outfile: 'out.js',
  plugins: [helloPlugin],
} as BuildOptions

(async () => {
  const res = await build(options as BuildOptions)
})()
βœ— ts-node esbuild.config.ts         [22/08/14| 4:35PM]
Hello World
βœ— serverless offline start --verbose
Running "serverless" from node_modules
Compiling to node16 bundle with esbuild...
Compiling with concurrency: Infinity
✘ [ERROR] [plugin env] Plugin is missing a setup function

    .../app/node_modules/esbuild/lib/main.js:798:16:
      798 β”‚           throw new Error(`Plugin is missing a setup function`);
          β•΅                 ^

    at handlePlugins (.../node_modules/esbuild/lib/main.js:798:17)
    at Object.buildOrServe (.../node_modules/esbuild/lib/main.js:1149:7)
    at .../node_modules/esbuild/lib/main.js:2110:17
    at new Promise (<anonymous>)
    at Object.build (.../node_modules/esbuild/lib/main.js:2109:14)
    at build (.../node_modules/esbuild/lib/main.js:1956:51)
    at bundleMapper (.../node_modules/serverless-esbuild/dist/bundle.js:69:50)
    at .../node_modules/serverless-esbuild/node_modules/p-map/index.js:57:28

Environment: darwin, node 12.22.12, framework 3.21.0 (local) 3.21.0v (global), plugin 6.2.2, SDK 4.3.2
Docs:        docs.serverless.com
Support:     forum.serverless.com
Bugs:        github.com/serverless/serverless/issues

Error:
Error: Build failed with 1 error:
.../node_modules/esbuild/lib/main.js:798:16: ERROR: [plugin: env] Plugin is missing a setup function
    at failureErrorWithLog (.../node_modules/esbuild/lib/main.js:1624:15)
    at .../node_modules/esbuild/lib/main.js:1143:18
    at .../node_modules/esbuild/lib/main.js:1138:9
    at .../node_modules/esbuild/lib/main.js:678:9
    at handleIncomingPacket (...node_modules/esbuild/lib/main.js:775:9)
    at Socket.readFromStdout (.../node_modules/esbuild/lib/main.js:644:7)
    at Socket.emit (events.js:314:20)
    at Socket.EventEmitter.emit (domain.js:483:12)
    at addChunk (_stream_readable.js:297:12)
    at readableAddChunk (_stream_readable.js:272:9)
    at Socket.Readable.push (_stream_readable.js:213:10)
    at Pipe.onStreamRead (internal/stream_base_commons.js:188:23)

Expected behavior

Expect serverless to build and run using esbuild with plugins and not crash.

Versions (please complete the following information):

It looks like any function passed into the esbuild config fails.

For instance, if you pass a function to the watch option, it is removed.

Is there some sort of marshalling/un-marshalling happening where functions are inadvertently deleted?

samchungy commented 2 years ago

probably because of ESM/CJS issues. Think esbuild plugins may be expected in CJS

robblovell commented 2 years ago

Interesting idea... Experimenting around with ESM/CJS and changing the helloPlugin.ts CJS style also fails:

module.exports = {
  name: 'hello',
  setup(build: any) {
    console.log('Hello World')
  },
}

with serverless.ts:

const helloPlugin = require('./helloPlugin')
const serverlessConfiguration = {
...

This still results in: ERROR: [plugin: hello] Plugin is missing a setup function

robblovell commented 2 years ago

Not Working Serverless Javascript Configuration:

helloPlugin.js

const helloPlugin = {
  name: 'hello',
  setup(config) {
    console.log('Hello World')
  },
}
exports.helloPlugin = helloPlugin;

serverless.js

const { helloPlugin } = require('./helloPlugin')

const serverlessConfiguration = {
  frameworkVersion: '3',
  service: 'hello-world',
  plugins: [
    'serverless-esbuild',
    'serverless-offline',
  ],
  custom: {
    esbuild: {
      entryPoints: ['app.js'],
      platform: 'node',
      target: 'node16',
      bundle: true,
      outdir: '.esbuild/.build',
      plugins: [helloPlugin],
    },
    'serverless-offline': {
      host: "localhost",
      httpPort: 3005
    },
  },
  provider: {
    name: 'aws',
    runtime: 'nodejs16.x',
  },
  functions: {
    'hello-world': {
      handler: 'app.api',
      events: [{
        http: {
          path: 'hello',
          method: 'get',
        },
      }],
    }
  }
}
module.exports = serverlessConfiguration

serverless offline start

✘ [ERROR] [plugin hello] Plugin is missing a setup function

Working ESBuild configuration

esbuild.config.js

const { helloPlugin } = require('./helloPlugin.js')
const { build } = require('esbuild');

const options = {
  entryPoints: ['app.js'],
  platform: 'node',
  target: 'node16',
  bundle: true,
  outdir: '.esbuild/.build',
  plugins: [helloPlugin],
}

build(options)
  .then(result => {
    console.log('Esbuild result:', result);
  })
  .catch(error => {
    console.log('Esbuild error:', error);
  })

node esbuild.config.js

Hello World 
Esbuild result: { errors: [], warnings: [] }
robblovell commented 2 years ago

If the serverless.js is converted to a serverless.yml, a clue is given:

frameworkVersion: '3'
service: hello-world
plugins:
  - serverless-esbuild
  - serverless-offline
custom:
  esbuild:
    entryPoints:
      - app.js
    platform: node
    target: node16
    bundle: true
    outdir: .esbuild/.build
    plugins:
      - helloPlugin
  serverless-offline:
    host: localhost
    httpPort: 3005
provider:
  name: aws
  runtime: nodejs16.x
functions:
  hello-world:
    handler: app.api
    events:
      - http:
          path: hello
          method: get

The error now is:

✘ [ERROR] Plugin at index 0 must be an object

Which might mean the problem is related to how the configuration is handled by the serverless framework itself, not the serverless-esbuild plugin.

robblovell commented 2 years ago

I have raised this in the serverless framework main repo to see if there are any insights that can be found there: https://github.com/serverless/serverless/issues/11388

robblovell commented 2 years ago

Example repo: https://github.com/robblovell/plugin-missing-setup-function

samchungy commented 2 years ago

Hey have you seen how we're asking you to set it up in the documentation? That might solve your issue https://github.com/floydspace/serverless-esbuild#esbuild-plugins

kaito3desuyo commented 2 years ago

I tested it using serverless.ts.

As a prerequisite, importing the esbuild plugin directly into serverless.ts didn't work. So, referring to the documentation, I changed it as follows.

custom: {
    esbuild: {
        bundle: true,
        minify: false,
        sourcemap: true,
        exclude: '*',
        target: 'node16',
        define: { 'require.resolve': undefined },
        platform: 'node',
        concurrency: 10,
        plugins: 'esbuild-plugins.ts',
    },
},
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { nodeExternalsPlugin } = require('esbuild-node-externals');

module.exports = () => {
    return [nodeExternalsPlugin()];
};

Interestingly, even though the files we load are TypeScript, we can still deploy with this configuration. However, the esbuild-plugins.ts file must be written in CommonJS format. Otherwise you will get an error like:

Error:
Error: "plugins" must be an array
    at Object.buildOrServe (/home/<User>/Projects/<Sample>/node_modules/esbuild/lib/main.js:1125:17)
    at /home/<User>/Projects/<Sample>/node_modules/esbuild/lib/main.js:2110:17
    at new Promise (<anonymous>)
    at Object.build (/home/<User>/Projects/<Sample>/node_modules/esbuild/lib/main.js:2109:14)
    at build (/home/<User>/Projects/<Sample>/node_modules/esbuild/lib/main.js:1956:51)
    at bundleMapper (/home/<User>/Projects/<Sample>/node_modules/serverless-esbuild/dist/bundle.js:69:50)
    at /home/<User>/Projects/<Sample>/node_modules/p-map/index.js:57:28

However, this configuration method is not intuitive, so I think it would be better if we could use the writing style suggested by @robblovell.

kaykhan commented 2 years ago

Im also getting the same error when attempting to use "esbuild-plugin-copy";`

import { copy } from "esbuild-plugin-copy";
....
        esbuild: {
            bundle: true,
            minify: false,
            sourcemap: true,
            exclude: ["aws-sdk"],
            target: "node16",
            define: { "require.resolve": undefined },
            platform: "node",
            concurrency: 10,
            plugins: [
                copy({
                    resolveFrom: "cwd",
                    assets: {
                        from: ["src/templates"],
                        to: ["templates"],
                    },
                }),
            ],
        },

✘ [ERROR] [plugin plugin:copy] Plugin is missing a setup function

/home/kay/checkpoint/email-service/node_modules/esbuild/lib/main.js:798:16:
  798 β”‚           throw new Error(`Plugin is missing a setup function`);
Koleok commented 1 year ago

@kaito3desuyo Thanks so much for that tip πŸ™