mirror of
https://github.com/serverless/serverless.git
synced 2026-01-18 14:58:43 +00:00
feat: adds support for esbuild plugins (SC-2514) (#12662)
* feat: adds support for esbuild plugins * chore: update docs * chore: memoize build function
This commit is contained in:
parent
d2f09094d9
commit
2cb089e5c5
@ -46,6 +46,49 @@ build:
|
||||
setNodeOptions: true
|
||||
```
|
||||
|
||||
You may also configure esbuild with a JavaScript file, which is useful if you want to use esbuild plugins. Here's an example:
|
||||
|
||||
```yml
|
||||
build:
|
||||
esbuild:
|
||||
# Path to the esbuild config file relative to the `serverless.yml` file
|
||||
configFile: ./esbuild.config.js
|
||||
```
|
||||
|
||||
The JavaScript file must export a function that returns an esbuild configuration object. For your convenience, the **serverless** instance is passed to that function.
|
||||
|
||||
Here's an example of the `esbuild.config.js` file that uses the `esbuild-plugin-env` plugin:
|
||||
|
||||
**ESM:**
|
||||
|
||||
```js
|
||||
/**
|
||||
* don't forget to set the `"type": "module"` property in `package.json`
|
||||
* and install the `esbuild-plugin-env` package
|
||||
*/
|
||||
import env from 'esbuild-plugin-env'
|
||||
|
||||
export default (serverless) => {
|
||||
return {
|
||||
external: ['@aws-sdk/client-s3'],
|
||||
plugins: [env()],
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**CommonJS:**
|
||||
|
||||
```js
|
||||
const env = require('esbuild-plugin-env')
|
||||
|
||||
module.exports = (serverless) => {
|
||||
return {
|
||||
external: ['@aws-sdk/client-s3'],
|
||||
plugins: [env()],
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Plugin Conflicts
|
||||
|
||||
Please note, plugins that build your code will not work unless you opt out of the default build experience. Some of the plugins affected are:
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import path from 'path'
|
||||
import { pathToFileURL } from 'url'
|
||||
import { readFile, rm, writeFile, stat } from 'fs/promises'
|
||||
import { createWriteStream, existsSync } from 'fs'
|
||||
import * as esbuild from 'esbuild'
|
||||
@ -19,6 +20,8 @@ class Esbuild {
|
||||
this.options = options || {}
|
||||
this._functions = undefined
|
||||
|
||||
this._buildProperties = _.memoize(this._buildProperties.bind(this))
|
||||
|
||||
this.hooks = {
|
||||
'before:dev-build:build': async () => {
|
||||
if (await this._shouldRun('originalHandler')) {
|
||||
@ -273,17 +276,50 @@ class Esbuild {
|
||||
return undefined
|
||||
}
|
||||
|
||||
_buildProperties() {
|
||||
async _buildProperties() {
|
||||
const defaultConfig = { bundle: true, minify: false, sourcemap: true }
|
||||
if (
|
||||
this.serverless.service.build &&
|
||||
this.serverless.service.build !== 'esbuild' &&
|
||||
this.serverless.service.build.esbuild
|
||||
) {
|
||||
// For advanced use cases, users can provide a js file that exports a function that returns esbuild configuration options
|
||||
// This is useful for when users want to use esbuild plugins (which require calling a function) or other advanced configurations
|
||||
// That you can't really do in serverless.yml
|
||||
let jsConfig = {}
|
||||
if (this.serverless.service.build.esbuild.configFile) {
|
||||
// Resolve the absolute path to the config file
|
||||
const configFilePath = path.resolve(
|
||||
this.serverless.config.serviceDir,
|
||||
this.serverless.service.build.esbuild.configFile,
|
||||
)
|
||||
|
||||
// This is a dynamic import because we want to support both CommonJS and ESM
|
||||
const configFile = await import(pathToFileURL(configFilePath).href)
|
||||
|
||||
const configFunction = configFile.default || configFile
|
||||
|
||||
// Print a nice error message if the export is not a function
|
||||
if (typeof configFunction !== 'function') {
|
||||
throw new ServerlessError(
|
||||
`Your build config "${path.basename(configFilePath)}" file must export a function that returns esbuild configuration options. For more details, please refer to the documentation: https://www.serverless.com/framework/docs/providers/aws/guide/building`,
|
||||
'ESBUILD_CONFIG_ERROR',
|
||||
)
|
||||
}
|
||||
|
||||
// Passing the serverless instance can be useful
|
||||
// Ref: https://github.com/floydspace/serverless-esbuild/issues/168
|
||||
jsConfig = await configFunction(this.serverless)
|
||||
}
|
||||
|
||||
// Users can use both serverless.yml and js file to configure esbuild
|
||||
// The yml config will take precedence over js config
|
||||
const mergedOptions = _.merge(
|
||||
defaultConfig,
|
||||
jsConfig,
|
||||
this.serverless.service.build.esbuild,
|
||||
)
|
||||
|
||||
if (this.serverless.service.build.esbuild.sourcemap === true) {
|
||||
mergedOptions.sourcemap = true
|
||||
} else if (this.serverless.service.build.esbuild.sourcemap === false) {
|
||||
@ -307,8 +343,8 @@ class Esbuild {
|
||||
* @param {string} runtime - The provider.runtime or functionObject.runtime value used to determine which version of the AWS SDK to exclude
|
||||
* @returns
|
||||
*/
|
||||
_getExternal(runtime) {
|
||||
const buildProperties = this._buildProperties()
|
||||
async _getExternal(runtime) {
|
||||
const buildProperties = await this._buildProperties()
|
||||
let external = new Set(buildProperties.external || [])
|
||||
let exclude = new Set(buildProperties.exclude || [])
|
||||
if (buildProperties.excludes) {
|
||||
@ -356,7 +392,7 @@ class Esbuild {
|
||||
|
||||
const updatedFunctionsToBuild = {}
|
||||
|
||||
const buildProperties = this._buildProperties()
|
||||
const buildProperties = await this._buildProperties()
|
||||
|
||||
for (const [alias, functionObject] of Object.entries(functionsToBuild)) {
|
||||
const functionName = path
|
||||
@ -369,7 +405,7 @@ class Esbuild {
|
||||
const runtime =
|
||||
functionObject.runtime || this.serverless.service.provider.runtime
|
||||
|
||||
const external = Array.from(this._getExternal(runtime).external)
|
||||
const external = Array.from((await this._getExternal(runtime)).external)
|
||||
|
||||
const extension = await this._extensionForFunction(
|
||||
functionObject[handlerPropertyName],
|
||||
@ -426,6 +462,7 @@ class Esbuild {
|
||||
// Remove the following properties from the esbuildProps as they are not valid esbuild properties
|
||||
delete esbuildProps.exclude
|
||||
delete esbuildProps.buildConcurrency
|
||||
delete esbuildProps.configFile
|
||||
|
||||
await esbuild.build(esbuildProps)
|
||||
if (!this.serverless.builtFunctions) {
|
||||
@ -474,7 +511,7 @@ class Esbuild {
|
||||
*/
|
||||
async _package(handlerPropertyName = 'handler') {
|
||||
const functions = await this.functions(handlerPropertyName)
|
||||
const buildProperties = this._buildProperties()
|
||||
const buildProperties = await this._buildProperties()
|
||||
|
||||
if (Object.keys(functions).length === 0) {
|
||||
log.debug('No functions to package')
|
||||
@ -672,7 +709,7 @@ class Esbuild {
|
||||
async _preparePackageJson() {
|
||||
const runtime = this.serverless.service.provider.runtime || 'nodejs18.x'
|
||||
|
||||
const { external, exclude } = this._getExternal(runtime)
|
||||
const { external, exclude } = await this._getExternal(runtime)
|
||||
|
||||
const packageJsonPath = path.join(
|
||||
this.serverless.config.serviceDir,
|
||||
@ -686,7 +723,7 @@ class Esbuild {
|
||||
}
|
||||
delete packageJsonNoDevDeps.devDependencies
|
||||
|
||||
const buildProperties = this._buildProperties()
|
||||
const buildProperties = await this._buildProperties()
|
||||
|
||||
if (packageJson.dependencies) {
|
||||
if (buildProperties.packages !== 'external') {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user