Skip to content

Commit

Permalink
Merge pull request #4 from beyerleinf/dev
Browse files Browse the repository at this point in the history
Merge development branch
  • Loading branch information
beyerleinf authored Feb 18, 2022
2 parents 66bf515 + 7158cf1 commit 166fc92
Show file tree
Hide file tree
Showing 21 changed files with 995 additions and 188 deletions.
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

0 comments on commit 166fc92

Please sign in to comment.