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

Merge development branch #4

Merged
merged 8 commits into from
Feb 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 122 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,36 @@

> A builder for Azure Function powered by esbuild.

[![Continuous Integration Workflow](https://github.com/beyerleinf/esbuild-azure-functions/actions/workflows/ci.yml/badge.svg)](https://github.com/beyerleinf/esbuild-azure-functions/actions/workflows/ci.yml) [![Codecov](https://img.shields.io/codecov/c/github/beyerleinf/esbuild-azure-functions)](https://app.codecov.io/gh/beyerleinf/esbuild-azure-functions) [![npm](https://img.shields.io/npm/v/esbuild-azue-functions)](https://www.npmjs.com/package/esbuild-azure-functions) [![npm](https://img.shields.io/npm/dm/esbuild-azure-functions)](https://www.npmjs.com/package/esbuild-azure-functions) [![GitHub](https://img.shields.io/github/license/beyerleinf/esbuild-azure-functions)](https://github.com/beyerleinf/esbuild-azure-functions/blob/main/LICENSE)
[![Continuous Integration Workflow](https://github.com/beyerleinf/esbuild-azure-functions/actions/workflows/ci.yml/badge.svg)](https://github.com/beyerleinf/esbuild-azure-functions/actions/workflows/ci.yml) [![Snyk Vulnerabilities for GitHub Repo](https://img.shields.io/snyk/vulnerabilities/github/beyerleinf/esbuild-azure-functions)](https://github.com/beyerleinf/esbuild-azure-functions) [![Codecov](https://img.shields.io/codecov/c/github/beyerleinf/esbuild-azure-functions)](https://app.codecov.io/gh/beyerleinf/esbuild-azure-functions) [![npm](https://img.shields.io/npm/v/esbuild-azure-functions)](https://www.npmjs.com/package/esbuild-azure-functions) [![npm](https://img.shields.io/npm/dm/esbuild-azure-functions)](https://www.npmjs.com/package/esbuild-azure-functions) [![GitHub](https://img.shields.io/github/license/beyerleinf/esbuild-azure-functions)](https://github.com/beyerleinf/esbuild-azure-functions/blob/main/LICENSE)

This tool is designed to work with Azure Functions written in TypeScript. It uses [esbuild](https://esbuild.github.io/) to create crazy small bundles. This is especially helpful with cold starts and deployment duration.

***Please read this readme to get started. It contains important information.***

# Table of Contents <!-- omit in toc -->
- [Usage](#usage)
- [Build](#build)
- [From the CLI](#from-the-cli)
- [Programmatically](#programmatically)
- [Programmatically](#programmatically)
- [Watch mode](#watch-mode)
- [From the CLI](#from-the-cli-1)
- [Programmatically](#programmatically-1)
- [Config](#config)
- [`project`](#project)
- [`entryPoints`](#entrypoints)
- [`exclude`](#exclude)
- [`clean`](#clean)
- [`logLevel`](#loglevel)
- [`esbuildOptions`](#esbuildoptions)
- [`advancedOptions`](#advancedoptions)
- [`enableDirnameShim`](#enabledirnameshim)
- [Common errors](#common-errors)
- [`ReferenceError: [__dirname|__filename] is not defined in ES module scope`](#referenceerror-__dirname__filename-is-not-defined-in-es-module-scope)
- [`Error: Dynamic require of "xyz" is not supported`](#error-dynamic-require-of-xyz-is-not-supported)
- [Benchmark](#benchmark)
- [Package size](#package-size)
- [Build time](#build-time)

## Usage
## Build

### From the CLI

Expand All @@ -31,7 +41,7 @@ By default, *esbuild-azure-functions* expects a config file called `esbuild-azur
npx esbuild-azure-functions [-c <config location>]
```

## Programmatically
### Programmatically

Install *esbuild-azure-functions* into your project

Expand All @@ -57,8 +67,46 @@ main();

```

## Watch mode

### From the CLI

By default, *esbuild-azure-functions* expects a config file called `esbuild-azure-functions.config.json` in the directory you are running it from. You can specify a different config location with the `-c | --config` flag. Refer to the [Config section](#config) for config options.

```
npx esbuild-azure-functions --watch [-c <config location>]
```

### Programmatically

Install *esbuild-azure-functions* into your project

```
npm i --save-dev esbuild-azure-functions
```

```ts
import { watch, BuilderConfigType } from 'esbuild-azure-functions';

const config: BuilderConfigType = {
project: process.cwd(),
esbuildOptions: {
outdir: 'MyCustomOutDir'
}
};

const main = async () => {
await watch(config);
}

main();

```

## Config

**Important: By default, the file extension of output files is set to `.mjs`. This is because the Azure Functions runtime requires this [see Microsoft Docs](https://docs.microsoft.com/en-us/azure/azure-functions/functions-reference-node?tabs=v2-v3-v4-export%2Cv2-v3-v4-done%2Cv2%2Cv2-log-custom-telemetry%2Cv2-accessing-request-and-response%2Cwindows-setting-the-node-version#ecmascript-modules). You need to change the `scriptFile` property of your *function.json* files accordingly.**

A simple starting config could look like this
```json
{
Expand All @@ -75,36 +123,36 @@ A simple starting config could look like this
**Type:** `string`
**Description:** The root folder of the Azure Functions project you want to build.
**Example:** `.`
**Default:**: `undefined`
**Default:** `undefined`

### `entryPoints`

**Required:** no
**Type:** `string[]`
**Description:** Specify custom entry points if you don't want *esbuild-azure-functions* to search for **index.ts** files in the `project` folder.
**Example:**: `[ "my-functions/entry.ts" ]`
**Default:**: `undefined`
**Example:** `[ "my-functions/entry.ts" ]`
**Default:** `undefined`

### `exclude`

**Required:** no
**Type:** `string[]`
**Description:** Specify directories as glob patterns to exclude when searching for **index.ts** files.
**Example:**: `[ "**/utils/**" ]`
**Default:**: `undefined`
**Example:** `[ "**/utils/**" ]`
**Default:** `undefined`

### `clean`

**Required:** no
**Type:** `boolean`
**Description:** Specify whether *esbuild-azure-functions* should the delete the output directory before building.
**Default:**: `false`
**Default:** `false`

### `logLevel`
**Required:** no
**Type:** `"off" | "error" | "warn" | "info" | "verbose"`
**Description:** Specify the verbosity of log messages.
**Default:**: `"error"`
**Default:** `"error"`

### `esbuildOptions`

Expand All @@ -121,10 +169,71 @@ A simple starting config could look like this
platform: 'node',
splitting: true,
format: 'esm',
outdir: 'build',
outdir: 'dist',
outExtension: { '.js': '.mjs' },
}
```

### `advancedOptions`
**Required:** no
**Type:** `object`
**Description:** Enable some advanced options depending on your environment

#### `enableDirnameShim`
**Required:** no
**Type:** `boolean`
**Description:** Enables a plugin that patches `__dirname` and `__filename` using `import.meta.url` ([see official Node.js docs](https://nodejs.org/docs/latest/api/esm.html#no-__filename-or-__dirname)) at the top of every output file because they are not available in ESM and esbuild doesn't shim them itself.

## Common errors

### `ReferenceError: [__dirname|__filename] is not defined in ES module scope`

This error stems from the fact that `__dirname` and `__filename` are not present in an ESM environment. To fix this, simply set `advancedOptions.enableDirnameShim` to `true` [see config](#enabledirnameshim)

### `Error: Dynamic require of "xyz" is not supported`

This error stems from esbuild not being able to convert CJS requires to ESM imports. This happens mostly (from what I've seen) with Node.js internals (like http, crypto and so on). To fix this issue you have two options:
1. Turn code splitting of and change the format to `cjs` **(not recommended because it increases the bundle size exponentially)**

```js
// build.mjs

await build({
project: '.',
clean: true,
esbuildOptions: {
splitting: false,
format: 'cjs',
outExtension: {}
},
});
```

2. Use `@esbuild-plugins/esm-externals` with the following setup:

```js
// build.mjs

import { EsmExternalsPlugin } from '@esbuild-plugins/esm-externals';
import { build } from 'esbuild-azure-functions';
import repl from 'repl';

await build({
project: '.',
exclude: ['**/utils/**'],
clean: true,
logLevel: 'verbose',
esbuildOptions: {
target: 'node12',
plugins: [
EsmExternalsPlugin({ externals: [...repl._builtinLibs] })
],
},
});
```

If there are other modules causing issues for you, just add them to the `externals` options of `EsmExternalsPlugin`.

## Benchmark

### Package size
Expand Down
48 changes: 41 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,13 @@
"@types/glob": "^7.2.0",
"@types/mocha": "^9.1.0",
"@types/mock-fs": "^4.13.1",
"@types/node": "^14.11.2",
"@types/node": "^14.18.12",
"@types/rimraf": "^3.0.2",
"@types/sinon": "^10.0.11",
"c8": "^7.11.0",
"chai": "^4.3.6",
"chai-as-promised": "^7.1.1",
"chai-exclude": "^2.1.0",
"gts": "^3.1.0",
"mocha": "^9.2.0",
"mocha-multi-reporters": "^1.5.1",
Expand Down
14 changes: 11 additions & 3 deletions src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { program } from 'commander';
import { loadConfig, parseConfig } from './lib/config-loader';
import { build } from './lib/builder';
import { build, watch } from './lib/builder';
import { promises as fs } from 'fs';

const main = async () => {
const { name, version, description } = JSON.parse(await fs.readFile(`${__dirname}/../../package.json`, 'utf8'));

program.name(name).description(description).version(version, '-v, --version');
program.requiredOption('-c, --config <file>', 'the config file to use', './esbuild-azure-functions.config.json');

program
.requiredOption('-c, --config <file>', 'the config file to use', './esbuild-azure-functions.config.json')
.option('-w, --watch', 'enable watch mode', false);

program.showSuggestionAfterError();
program.parse();

Expand All @@ -16,7 +20,11 @@ const main = async () => {
const file = await loadConfig(options.config);
const config = parseConfig(file);

await build(config);
if (!options.watch) {
await build(config);
} else {
await watch(config);
}
};

main();
2 changes: 0 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
/* c8 ignore start */
export * from './lib/builder';
export { BuilderConfigType } from './lib/models';
/* c8 ignore stop */
Loading