Skip to content
This repository has been archived by the owner on Aug 16, 2022. It is now read-only.

Commit

Permalink
feat: transform export declarations (#5)
Browse files Browse the repository at this point in the history
* refactor(typescript): `TrextNodePath`

add ExportAllDeclaration and ExportNamedDeclaration as TrextNodePath#node types

* feat(plugins): `Trextel#ExportAllDeclaration`

* feat(typescript): `TrextNodeType`, `TrextNode`

* feat: `TrextOptions#mandatory`

* feat(plugins): `Trextel#ExportNamedDeclaration`

* feat(plugins): `Trextel.getCode`

* docs: mandatory file extensions

* build: export `plugins` and `utils` from package entry point
  • Loading branch information
unicornware authored Oct 15, 2021
1 parent 4d1f246 commit f937db0
Show file tree
Hide file tree
Showing 20 changed files with 568 additions and 76 deletions.
1 change: 1 addition & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ module.exports = {
skipWords: [
...RULES_SPELLCHECKER[1].skipWords,
'callee',
'dirix',
'errno',
'filenames',
'trext',
Expand Down
148 changes: 116 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,21 @@ File extension transformer
## Getting Started

Interested in using `.cjs` and `.mjs` file extensions, but _not_ in setting up
another build workflow? Use `trext` to transform file extensions, import
statements, call expressions, and even source maps!
another build workflow? Use `trext` to transform your project's file extensions.
Heavily inspired by [`convert-extension`][6].

Heavily inspired by [`convert-extension`][5], `trext` intends to provide a more
configurable user experience. In addition to passing custom
[Babel transform](#babel-transform) options, maintainers can also specify custom
[file extension search patterns](#file-extension-search-patterns) and use
functions to [dynamically generate extensions](#dynamic-file-extensions).
Transform file extensions in:

- `export` (default and named) declarations
- `import` declarations
- `require` statements
- sourcemaps

In addition to file extension transformations:

- Set custom [file extension search patterns](#file-extension-search-patterns)
- Use functions to [dynamically generate extensions](#dynamic-file-extensions)
- Pass custom [Babel transform](#babel-transform) options

## Installation

Expand Down Expand Up @@ -64,9 +71,9 @@ trext('esm/', TREXT_OPTIONS)

#### Babel Transform

`trext` implements a [custom Babel plugin][6] to update `import` and `require`
statements. If enabled, source maps will also be updated. You can specify
additional transform options using the `babel` property:
`trext` implements a [custom Babel plugin][7] to update `export`, `import`, and
`require` statements. If enabled, source maps will also be updated. You can
specify additional transform options using the `babel` property:

```typescript
import type { TrextOptions } from '@flex-development/trext'
Expand All @@ -91,22 +98,22 @@ trext('cjs/', TREXT_OPTIONS)

#### Dynamic File Extensions

`trext` uses the [`String.prototype.replace`][7] method to replace file
`trext` uses the [`String.prototype.replace`][8] method to replace file
extensions. The `to` option can also be specifed as a function to take full
advantage of the method's capabilities. When using a function, however, note
that **the function will also be invoked within the context of [`Trextel`][6],
that **the function will also be invoked within the context of [`Trextel`][7],
thus drastically changing the arguments passed to your function**:

```typescript
import type { NodePath } from '@babel/traverse'
import type { CallExpression, ImportDeclaration } from '@babel/types'
import { isNode } from '@babel/types'
import type {
import {
FileExtension,
trext,
TrextMatch,
TrextNodePath,
TrextOptions
} from '@flex-development/trext'
import { trext } from '@flex-development/trext'
import Trextel from '@flex-development/trext/plugins/esm/trextel.plugin'
import { inspect } from 'util'

/**
Expand All @@ -120,16 +127,30 @@ const TREXT_OPTIONS: TrextOptions<'js', 'cjs' | 'mjs'> = {
to(match: TrextMatch, ...args: any[]): FileExtension<'cjs' | 'mjs'> {
// Check if match is NodePath, args === []
if (isNode((match as any).node)) {
const nodePath = match as NodePath<CallExpression | ImportDeclaration>

if (nodePath.type === 'CallExpression') {
//
const nodePath = match as TrextNodePath
const code: string | undefined = Trextel.getCode(nodePath)

switch (nodePath.type) {
case 'CallExpression':
//
break
case 'ExportAllDeclaration':
//
break
case 'ExportNamedDeclaration':
//
break
case 'ImportDeclaration':
//
break
default:
break
}

return '.mjs'
}

// Check is match is RegExp object
// Check if match is RegExp object
if (match.constructor.name === 'RegExp') {
const regex = match as RegExp

Expand Down Expand Up @@ -176,9 +197,10 @@ trext('esm/', TREXT_OPTIONS)

#### Ignoring Directory Indexes

Directory entry points are a common way of exposing a series of modules from a
single `index.*` file. Directory index syntax allows developers to `import` and
`require` those entries without including `/index.*` in the module specifier:
Directory entry points are a common way of exposing a group of modules from a
single `index.*` file. Directory index (dirix) syntax allows developers to use
partial [import specifiers][9] (or [call expression arguments][10]) to `export`,
`import`, or `require` those modules without including `/index.*`:

```typescript
/**
Expand All @@ -192,11 +214,10 @@ export { default as Trext, trext, trextFile } from './plugins/trext.plugin'
export * from './types'
```

Specifiers `'./interfaces'` and `'./types'` use directory index syntax and
**will be ignored** when encountered by [`Trextel`][6].

By default, index lookups are performed in the `process.cwd()/src` directory.
Set `src` to change the lookup location:
[`Trextel`][7] searches for indexes in the `process.cwd()/src` directory. **When
[mandatory extensions](#mandatory-file-extensions) are disabled, _partial_
specifiers and call expression arguments are ignored**. Set `src` to change the
directory index lookup location:

```typescript
import type { TrextOptions } from '@flex-development/trext'
Expand All @@ -220,18 +241,81 @@ trext('cjs/', TREXT_OPTIONS)
.catch(error => console.error(inspect(error, false, null)))
```

#### Mandatory File Extensions

`trext` forces all specifiers (and call expression arguments) to be [fully
specified][11]. Set `mandatory` to `false` to disable transformations for all
[`TrextNode`][12] types:

```typescript
import type { TrextOptions } from '@flex-development/trext'
import { trext } from '@flex-development/trext'
import { inspect } from 'util'

/**
* @file Examples - Disabling Mandatory File Extensions
* @module docs/examples/mandatory
*/

const TREXT_OPTIONS: TrextOptions<'js', 'mjs'> = {
from: 'js',
mandatory: false,
pattern: /.js$/,
to: 'mjs'
}

trext('esm/', TREXT_OPTIONS)
.then(results => console.info(inspect(results, false, null)))
.catch(error => console.error(inspect(error, false, null)))
```

You can also disable transformations by [`TrextNode`][12] type:

```typescript
import type { TrextOptions } from '@flex-development/trext'
import { trext } from '@flex-development/trext'
import { inspect } from 'util'

/**
* @file Examples - Disabling Mandatory File Extensions (By Node)
* @module docs/examples/mandatory-by-node
*/

const TREXT_OPTIONS: TrextOptions<'js', 'mjs'> = {
from: 'js',
mandatory: {
call: false,
exportAll: true,
exportNamed: false,
import: true
},
pattern: /.js$/,
to: 'mjs'
}

trext('mjs/', TREXT_OPTIONS)
.then(results => console.info(inspect(results, false, null)))
.catch(error => console.error(inspect(error, false, null)))
```

## Built With

- [@babel/core][1] - Babel compiler core
- [@babel/traverse][2] - Traverse and update nodes
- [glob][3] - Match file paths using glob patterns
- [mkdirp][4] - Node.js implementation of `mkdir -p`
- [path-type][5] - Check if a path is a file, directory, or symlink

[1]: https://github.com/babel/babel/tree/main/packages/babel-core
[2]: https://github.com/babel/babel/tree/main/packages/babel-traverse
[3]: https://github.com/isaacs/node-glob
[4]: https://github.com/isaacs/node-mkdirp
[5]: https://github.com/peterjwest/convert-extension
[6]: src/plugins/trextel.plugin.ts
[7]:
[5]: https://github.com/sindresorhus/path-type
[6]: https://github.com/peterjwest/convert-extension
[7]: src/plugins/trextel.plugin.ts
[8]:
https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/replace
[9]: https://nodejs.org/api/esm.html#esm_import_specifiers
[10]: https://babeljs.io/docs/en/babel-types#callexpression
[11]: https://nodejs.org/api/esm.html#esm_mandatory_file_extensions
[12]: src/types/trext-node.type.ts
8 changes: 8 additions & 0 deletions __tests__/__fixtures__/hub.fixture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Hub } from '@babel/traverse'

/**
* @file Test Fixture - Hub
* @module tests/fixtures/Hub
*/

export default new Hub()
8 changes: 8 additions & 0 deletions __tests__/__fixtures__/program.fixture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { program } from '@babel/types'

/**
* @file Test Fixture - program
* @module tests/fixtures/program
*/

export default program([])
32 changes: 23 additions & 9 deletions docs/examples/dynamic.example.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type { NodePath } from '@babel/traverse'
import type { CallExpression, ImportDeclaration } from '@babel/types'
import { isNode } from '@babel/types'
import type {
import {
FileExtension,
trext,
TrextMatch,
TrextNodePath,
TrextOptions
} from '@flex-development/trext'
import { trext } from '@flex-development/trext'
import Trextel from '@flex-development/trext/plugins/trextel.plugin'
import { inspect } from 'util'

/**
Expand All @@ -20,16 +20,30 @@ const TREXT_OPTIONS: TrextOptions<'js', 'cjs' | 'mjs'> = {
to(match: TrextMatch, ...args: any[]): FileExtension<'cjs' | 'mjs'> {
// Check if match is NodePath, args === []
if (isNode((match as any).node)) {
const nodePath = match as NodePath<CallExpression | ImportDeclaration>

if (nodePath.type === 'CallExpression') {
//
const nodePath = match as TrextNodePath
const code: string | undefined = Trextel.getCode(nodePath)

switch (nodePath.type) {
case 'CallExpression':
//
break
case 'ExportAllDeclaration':
//
break
case 'ExportNamedDeclaration':
//
break
case 'ImportDeclaration':
//
break
default:
break
}

return '.mjs'
}

// Check is match is RegExp object
// Check if match is RegExp object
if (match.constructor.name === 'RegExp') {
const regex = match as RegExp

Expand Down
24 changes: 24 additions & 0 deletions docs/examples/mandatory-by-node.example.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { TrextOptions } from '@flex-development/trext'
import { trext } from '@flex-development/trext'
import { inspect } from 'util'

/**
* @file Examples - Disabling Mandatory File Extensions (By Node)
* @module docs/examples/mandatory-by-node
*/

const TREXT_OPTIONS: TrextOptions<'js', 'mjs'> = {
from: 'js',
mandatory: {
call: false,
exportAll: true,
exportNamed: false,
import: true
},
pattern: /.js$/,
to: 'mjs'
}

trext('mjs/', TREXT_OPTIONS)
.then(results => console.info(inspect(results, false, null)))
.catch(error => console.error(inspect(error, false, null)))
19 changes: 19 additions & 0 deletions docs/examples/mandatory.example.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { TrextOptions } from '@flex-development/trext'
import { trext } from '@flex-development/trext'
import { inspect } from 'util'

/**
* @file Examples - Disabling Mandatory File Extensions
* @module docs/examples/mandatory
*/

const TREXT_OPTIONS: TrextOptions<'js', 'mjs'> = {
from: 'js',
mandatory: false,
pattern: /.js$/,
to: 'mjs'
}

trext('esm/', TREXT_OPTIONS)
.then(results => console.info(inspect(results, false, null)))
.catch(error => console.error(inspect(error, false, null)))
1 change: 1 addition & 0 deletions src/config/defaults.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { TrextDefaults } from '@trext/types'

const DEFAULTS: TrextDefaults = {
babel: {},
mandatory: true,
pattern: /\..+$/,
src: `${process.cwd()}/src`
}
Expand Down
4 changes: 3 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@

export { default as TREXT_DEFAULTS } from './config/defaults.config'
export * from './interfaces'
export { default as Trext, trext, trextFile } from './plugins/trext.plugin'
export * from './plugins'
export { trext, trextFile } from './plugins/trext.plugin'
export * from './types'
export * from './utils'
12 changes: 12 additions & 0 deletions src/interfaces/trext-options.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ interface TrextOptions<F extends string = string, T extends string = string> {
*/
from: F

/**
* Add file extensions to all call expression arguments and module specifiers.
*
* @see https://nodejs.org/api/esm.html#esm_import_specifiers
* @see https://nodejs.org/api/esm.html#esm_mandatory_file_extensions
*
* @default true
*/
mandatory?:
| boolean
| Partial<Record<'call' | 'exportAll' | 'exportNamed' | 'import', boolean>>

/**
* File extension search pattern.
*
Expand Down
Loading

0 comments on commit f937db0

Please sign in to comment.