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

fix: allow both cjs and esm formats, by user configuration #8

Merged
merged 1 commit into from
Jun 7, 2024
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
4 changes: 4 additions & 0 deletions docs/.vitepress/sidebar.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ export const SIDEBAR: DefaultTheme.Sidebar = [
text: 'Missing Config File',
link: 'missing-config-file',
},
{
text: 'ESM issues',
link: 'esm-issues',
},
{
text: 'Empty Glob',
link: 'empty-glob',
Expand Down
4 changes: 2 additions & 2 deletions docs/app/quick-start.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ module.exports = {}

1. Create a Workflow via Alfred UI, or use an existing one
1. Open the Workflow directory, copy relevant files (icons, `info.plist`, `prefs.plist`, etc) to your project
1. Set `"type": "module"` in your `package.json` file - this is required for
using [`fast-alfred` runtime](./setup/runtime-explain)
1. In case your package declares `"type": "module"` in the `package.json` file, you'll
need to set both [`esmHelpers`](./setup/bundler-options#esmhelpers) and [`outputFormat`](./setup/bundler-options#outputformat) in order to use [`fast-alfred` runtime](./setup/runtime-explain)
1. Create a source directory for your Workflow scripts

```bash
Expand Down
53 changes: 32 additions & 21 deletions docs/app/setup/bundler-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,16 @@ module.exports = {
}
```

## `includeBanners`
## `esmHelpers`

By default, `fast-alfred` provides you with some compatibilities between CommonJS and ES Modules.
You can disable this feature by setting the `includeBanners` property to `false`.
By default, `fast-alfred` aims to be written in the `cjs` Module format.
If your package is using the `esm` format, AKA, having `"type": "module"` in the `package.json`, you should enable the `esmHelpers` property.

::: warning Warning :warning:
In case your package is written in `cjs`, enabling this option would break your build.

**Note that you should also set the [`outputFormat` property](#outputformat) to `esm` to output the files in the ES Module format.**
:::

##### Example

Expand All @@ -113,15 +119,20 @@ You can disable this feature by setting the `includeBanners` property to `false`
*/
module.exports = {
bundlerOptions: {
includeBanners: false, // Disable the compatibility banners
esmHelpers: true, // Enable compatibility for CommonJS packages
},
}
```

## `minify`
## `outputFormat`

By default, `fast-alfred` would minify the output files.
It might be useful to disable this feature for debugging purposes.
By default, `fast-alfred` would output the files in the `cjs` format.
You can change that by setting the `outputFormat` property.

::: tip TIP :zap:
If your `package.json` has `"type": "module"`, you should output the files in the `esm` format.
Otherwise, you should keep it in the `cjs` format.
:::

##### Example

Expand All @@ -131,20 +142,15 @@ It might be useful to disable this feature for debugging purposes.
*/
module.exports = {
bundlerOptions: {
minify: false, // Disable minification
outputFormat: 'esm', // Output files in the ES Module format
},
}
```

## `treeShaking`

By default, `fast-alfred` would tree-shake the output files. That way, only the necessary code will be included in the final bundle.
You can disable this feature by setting the `treeShaking` property to `false`.
## `minify`

::: warning Warning :warning:
Disabling tree shaking might increase the bundle size.
You should only disable tree shaking if you're facing issues with the output bundle.
:::
By default, `fast-alfred` would minify the output files.
It might be useful to disable this feature for debugging purposes.

##### Example

Expand All @@ -154,15 +160,20 @@ You should only disable tree shaking if you're facing issues with the output bun
*/
module.exports = {
bundlerOptions: {
treeShaking: false, // Disable tree shaking
minify: false, // Disable minification
},
}
```

## `outputFormat`
## `treeShaking`

By default, `fast-alfred` would output the files in the `cjs` format.
You can change that by setting the `outputFormat` property.
By default, `fast-alfred` would tree-shake the output files. That way, only the necessary code will be included in the final bundle.
You can disable this feature by setting the `treeShaking` property to `false`.

::: warning Warning :warning:
Disabling tree shaking might increase the bundle size.
You should only disable tree shaking if you're facing issues with the output bundle.
:::

##### Example

Expand All @@ -172,7 +183,7 @@ You can change that by setting the `outputFormat` property.
*/
module.exports = {
bundlerOptions: {
outputFormat: 'esm', // Output files in the ES Module format
treeShaking: false, // Disable tree shaking
},
}
```
Expand Down
37 changes: 33 additions & 4 deletions docs/app/setup/runtime-explain.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ next: true
The main advantage of this approach is that your Node.js script would be executed in an environment that is aware of the Alfred workflow, and it will be able to interact with it, as well as having caching and other features.

::: warning NOTE :rotating_light:
In order to use `fast-alfred` runtime, you have to configure your package as ESM module.
Add `"type": "module"` to your `package.json` file.
In order to use `fast-alfred` runtime in ESM format, you need to configure these two options [`esmHelpers`](./bundler-options#esmhelpers) and [`outputFormat`](./bundler-options#outputformat).
:::

## How It Works
Expand All @@ -37,7 +36,37 @@ The code below is an example of how to trigger your Node.js script in an Alfred

Sometimes, we just want to run scripts locally, and put some debugger breakpoints to understand the flow.

In case you're using [`typescript` paths](https://www.typescriptlang.org/tsconfig/#paths), you might need to convert them after being built (without `fast-alfred` bundler).
### `CommonJS` Format

In case you're using the [`CommonJS` format](./bundler-options#outputformat), you can run the following command:

```bash
ts-node ./src/main/your-script-under-main.ts
```

::: tip TIP :zap:
In case you're using [`typescript` paths](https://www.typescriptlang.org/tsconfig/#paths), you might need to convert them, without the help of `fast-alfred` bundler.
Add the following to your `tsconfig.json` file:,

```json
"ts-node": {
"require": ["tsconfig-paths/register"]
},
```

And install the `tsconfig-paths` package:

```bash
npm i -D tsconfig-paths
```

:::

### `ESM` Format

For the `ESM` format, `ts-node` is known to have some issues with it.

In case you're using [`typescript` paths](https://www.typescriptlang.org/tsconfig/#paths), you'll have to convert them is a separate step.

::: warning NOTE :rotating_light:
You need to install the `tsc-alias` package to convert the paths
Expand Down Expand Up @@ -68,5 +97,5 @@ node ./dist/your-script-under-main.js
The code above is an example of how to trigger your Node.js script in a local environment,
right from your IDE.

**You can place your breakpoints and debug your script from your .ts file.**
**You can place your breakpoints and debug your script from your .ts file (require `sourceMap`)**
:::
4 changes: 2 additions & 2 deletions docs/app/troubleshooting/empty-glob.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
prev:
text: 'Missing Configuration File'
link: '/app/troubleshooting/missing-config-file'
text: 'ESM issues'
link: '/app/troubleshooting/esm-issues'
next:
text: 'No Target Version'
link: '/app/troubleshooting/no-target-version'
Expand Down
66 changes: 66 additions & 0 deletions docs/app/troubleshooting/esm-issues.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
---
prev:
text: 'Missing Configuration File'
link: '/app/troubleshooting/missing-config-file'
next:
text: 'Empty Glob'
link: '/app/troubleshooting/empty-glob'
---

# ESM issues

`fast-alfred` by default bundles your scripts in the `cjs` format.
In case your script uses ESM syntax, you might encounter the following error:

```log
SyntaxError: Cannot use import statement outside a module
```

## Solution

### Make sure no ESM syntax is used in your build

`fast-alfred` provides nice [helpers to EMS syntax](../setup/bundler-options#esmhelpers), in order to have compatibility with CommonJS packages.
Make sure this option disabled in case you're using the `cjs` format.
Also, verify that the `outputFormat` is set to `cjs`.

::: code-group

```javascript [.fast-alfred.config.cjs]
/**
* @type {import('fast-alfred').FastAlfredConfig}
*/
module.exports = {
bundlerOptions: {
// Both should be the default
esmHelpers: false, // Disable compatibility for CommonJS packages
outputFormat: 'cjs', // Output files in the CommonJS format
},
}
```

:::

### Migrate to ESM

You can change the output format to `esm` by adding the following configuration to your `.fast-alfred.config.cjs` file.

::: warning NOTE :warning:
Your `package.json` should have `"type": "module"` in order to use the ESM format.
:::

::: code-group

```javascript [.fast-alfred.config.cjs]
/**
* @type {import('fast-alfred').FastAlfredConfig}
*/
module.exports = {
bundlerOptions: {
outputFormat: 'esm', // Change the output format to ESM
esmHelpers: true, // Enable compatibility for CommonJS packages - not mandatory, but recommended
},
}
```

:::
4 changes: 2 additions & 2 deletions docs/app/troubleshooting/missing-config-file.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---
prev: false
next:
text: 'Empty Glob'
link: '/app/troubleshooting/empty-glob'
text: 'ESM issues'
link: '/app/troubleshooting/esm-issues'
---

# Missing Configuration File
Expand Down
11 changes: 5 additions & 6 deletions src/bundler/constants/bundler-options-defaults.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const BUNDLER_DEFAULTS: Required<BundlerOptions> = {
assetsDir: 'assets',
targetDir: 'esbuild',
productionScripts: ['src/main/*.ts'],
includeBanners: true,
esmHelpers: false,
minify: true,
treeShaking: true,
outputFormat: 'cjs',
Expand All @@ -21,10 +21,10 @@ export const ALL_FRAMEWORK_ASSETS = [
]

/**
* Fix common issues when compiling both `esm` and `cjs` modules.
* @description
* This code would be included in the bundle, in order to resolve common issues when compiling both `esm` and `cjs` modules.
*/
export const DEFAULT_BANNERS = {
js: `
export const ESM_HELPERS = `
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
import _private_path from 'node:path';
Expand All @@ -33,8 +33,7 @@ import _private_url from 'node:url';
globalThis.require = createRequire(import.meta.url);
globalThis.__filename = _private_url.fileURLToPath(import.meta.url);
globalThis.__dirname = _private_path.dirname(__filename);
`,
} as const
` as const

export const PACK_ENTITIES = (targetDir: string) =>
['*.png ', '*.plist', 'README.md', `${targetDir}/**`, 'package.json'] as const
8 changes: 4 additions & 4 deletions src/bundler/models/bundler-options.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@ export interface BundlerOptions {

/**
* @description
* Whether to include JS banners in the bundle.
* Headers should resolve common issues when compiling both `esm` and `cjs` modules.
* Whether to include JS code in the bundle, that resolve common issues when compiling both `esm` and `cjs` modules.
* Most issues would be resolving `require`, `__dirname` and `__filename` variables.
*
* @default true
* @default false
*/
includeBanners?: boolean
esmHelpers?: boolean

/**
* @description
Expand Down
10 changes: 7 additions & 3 deletions src/bundler/services/build-workflow.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ import * as esbuild from 'esbuild'
import { basename } from 'node:path'
import { execPromise } from '@common/utils'
import { readWorkflowPackageJson } from '@common/workflow-package-json.service'
import { ALL_FRAMEWORK_ASSETS, DEFAULT_BANNERS, PACK_ENTITIES } from '../constants/bundler-options-defaults.config'
import { ALL_FRAMEWORK_ASSETS, ESM_HELPERS, PACK_ENTITIES } from '../constants/bundler-options-defaults.config'
import { buildOptions, cleanTarget, copyAssets } from '../utils/bundler.utils'

export async function buildWorkflow() {
const options = await buildOptions()
const {
assets,
assetsDir,
includeBanners,
esmHelpers,
minify,
outputFormat,
overrideEsbuildOptions,
Expand All @@ -23,6 +23,10 @@ export async function buildWorkflow() {

await cleanTarget(targetDir)

const banners = {
js: esmHelpers ? ESM_HELPERS : '',
}

await esbuild.build({
platform: 'node',
entryPoints: productionScripts,
Expand All @@ -31,7 +35,7 @@ export async function buildWorkflow() {
treeShaking,
minify,
format: outputFormat,
banner: includeBanners ? DEFAULT_BANNERS : undefined,
banner: banners,
...overrideEsbuildOptions,
})

Expand Down