Skip to content

Commit

Permalink
Update examples and README.
Browse files Browse the repository at this point in the history
- Add `examples/embedder`.
- Move docs example to `examples/docs`.
- Fix README.md typos.
- Add new FAQ section.
- Add sections from denoland/deno#19176: "Use cases", "Conventions", "Examples".
  • Loading branch information
EthanThatOneKid committed Jun 3, 2023
1 parent dffc7a0 commit d36cdc6
Show file tree
Hide file tree
Showing 12 changed files with 343 additions and 46 deletions.
306 changes: 282 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
Automate code generation by running procedures defined in comment annotations.

This repository is a proof-of-concept for the `deno generate` subcommand as
proposed in <https://github.com/denoland/deno/issues/19176>.
proposed in [Deno issue #19176](https://github.com/denoland/deno/issues/19176).

## Usage

Expand All @@ -18,29 +18,26 @@ with the command you want to run. For example:

### Running the CLI tool

To generate your code using the CLI tool, you can run the following command:

```sh
deno-generate <entrypoint file>
```

If you are not interested in installing the script, you can still run it
directly from `deno.land` or `github.com`:
To generate your code using the CLI tool, you can run the tool from
`deno.land/x` with the command:

```sh
deno run -Ar https://deno.land/x/generate/main.ts <entrypoint file>
```

If you are interested in installing the script, refer to the
[Installation](#installation) section.

```sh
deno run -Ar https://github.com/ethanthatonekid/deno_generate/raw/main/main.ts <entrypoint file>
deno-generate <entrypoint file>
```

You can also define a task in your `deno.jsonc` file to run the CLI tool in your
project:

```jsonc
{
"scripts": {
"tasks": {
"generate": "deno run -Ar https://deno.land/x/generate/main.ts <entrypoint file>"
}
}
Expand All @@ -57,8 +54,7 @@ deno-generate <entrypoint file>

<details>
<summary>
Install from script source
(Expand for more information)
Install from script source (Expand for more information)
</summary>

```sh
Expand All @@ -85,10 +81,233 @@ deno run -Ar https://deno.land/x/generate/main.ts <entrypoint file> --allow-run=
You can find more information about the `--allow-run` flag in the
[Deno permissions documentation](https://deno.land/manual/basics/permissions#permissions-list).

### Use cases

Code generation is an important programming technique because generated files
can automate repetitive or complex tasks, improve code consistency and
maintainability, and save developers time and effort.

As for use cases, your imagination is the limit. Here are a few:

1. Generating code from templates: Developers can define templates for commonly
used code patterns and use the CLI tool to automatically generate code that
follows those patterns.
2. Generating code from schemas: If a project uses a schema to define data
models or API specifications, developers can create generators that generate
code based on that schema.
3. Generating tests: Developers can define test templates that cover common
testing scenarios and use the CLI tool to automatically generate tests for
their code.
4. Generating code from annotations: Developers can add annotations to their
code that define which generators to run and how to run them.

### Conventions

To ensure a consistent developer experience, we recommend following these
conventions when using the CLI tool:

#### Pre-commit

To enhance your development workflow, we recommend implementing a pre-commit
hook in your project's Git repository. Follow these steps to set it up:

1. Create a file named "pre-commit" (without any file extension) within your
project's ".git/hooks" directory.
2. Ensure that the file is executable by running the following command in your
terminal:

```bash
chmod +x .git/hooks/pre-commit
```

3. Open the "pre-commit" file in a text editor and add the following code:

```bash
#!/bin/bash

# Run generators before committing.
deno task generate

# Check if any files have changed.
git diff --exit-code
```

#### Linguist generated

When dealing with code generation, there are situations where generated files
should not be visible to developers during a pull request. To address this, a
setting can be used to differentiate their changes, ensuring a cleaner and more
focused code review. On GitHub, you can achieve this by marking specific files
with the "linguist-generated" attribute in a ".gitattributes" file[^1]. This
attribute allows you to hide these files by default in diffs and exclude them
from contributing to the repository language statistics.

To implement this, follow these steps:

1. Create a ".gitattributes" file in your project's root directory if it doesn't
already exist.
2. Open the ".gitattributes" file in a text editor and include the relevant file
patterns along with the "linguist-generated" attribute. For example:

```bash
# Marking generated files
*.generated.extension linguist-generated
```

3. Save the file and commit it to your repository.

Now, when viewing pull requests or generating diffs on GitHub, the marked files
will be hidden by default, providing a more streamlined code review process and
excluding them from language statistics calculations.

## Examples

The `deno generate` subcommand is capable of facilitating the generation of code
in the Deno ecosystem. Developers use code generation to generate code for all
kinds of use cases.

You can find more examples in the [`examples` directory](examples).

### Run a script

Deno scripts should be able to invoke another Deno in its `//deno:generate`
statement:

#### `generator.ts`

The provided code generates basic TypeScript code using a for loop to export
constants.

```ts
//deno:generate deno run -A generate.ts
for (let i = 0; i < 10; i++) {
console.log(`export const example${i} = ${i};`);
}
```

> **Note** While this example works for simple cases, it is advisable to utilize
> a widely-used library such as [ts-morph](https://github.com/dsherret/ts-morph)
> for more comprehensive and complete examples. `ts-morph` is a TypeScript
> Compiler API wrapper for static analysis and programmatic code changes. This
> module provides an extensive set of features and utilities, simplifying the
> handling of complex code generation scenarios. By leveraging ts-morph,
> developers can ensure a more robust, maintainable code generation process.
##### `generate.ts`

```ts
// Create a child process using Deno.Command, running the "generate.ts" script.
const generatorChild = new Deno.Command(Deno.execPath(), {
args: ["run", "generator.ts"],
stdin: "piped",
stdout: "piped",
}).spawn();

// Create another child process, running deno fmt.
const fmtChild = new Deno.Command(Deno.execPath(), {
args: ["fmt", "-"],
stdin: "piped",
stdout: "piped",
}).spawn();

// Pipe the current process stdin to the child process stdin.
generatorChild.stdout.pipeTo(fmtChild.stdin);

// Close the child process stdin.
generatorChild.stdin.close();

// Pipe the child process stdout to a writable file named "generated.ts".
fmtChild.stdout.pipeTo(
Deno.openSync("generated.ts", { write: true, create: true }).writable,
);
```

### Generate OpenAPI types

OpenAPI is a JSON-based specification that represents comprehensive API details,
providing a formal and professional representation of the API specifications.
One of the most common use cases of OpenAPI is to generate API clients,
simplifying the development process by automatically generating code based on
the defined API specifications. OpenAPI schemas offer versatile applications and
integrations, enabling a wide range of possibilities for API design and
development.

```ts
//deno:generate deno run -A npm:openapi-typescript@6.2.4 ./examples/github_api.json -o ./examples/github_api.ts
```

### Generate static website with Lume

Lume is a website framework for the Deno ecosystem. Entire static websites are
generated by Lume with a single command.
[See more](https://github.com/lumeland/lume).

```ts
//deno:generate deno run -A https://deno.land/x/lume@v1.17.3/cli.ts --src ./examples/lume --dest ./examples/lume/_site
```

### Generate `deno_bindgen` bindings

This tool aims to simplify glue code generation for Deno FFI libraries written
in Rust. [See more](https://github.com/denoland/deno_bindgen).

```ts
//deno:generate deno run -A https://deno.land/x/deno_bindgen@0.8.0/cli.ts
```

### Generate deno-embedder file

deno-embedder is a tool that simplifies the development and distribution of Deno
applications, particularly when access to static files (.txt, .png, etc.) is
required at runtime. It allows you to create an embedder.ts file that
encompasses both configuration and the main function call, providing benefits
such as IDE-based type-checking.
[See more](https://github.com/NfNitLoop/deno-embedder).

#### [`examples/embedder/with_embedder.ts`](examples/embedder/with_embedder.ts)

```ts
//deno:generate deno run -A embedder.ts
import examplesDir from "./dir.ts";

const indexHTML = await examplesDir.load("with_embedder.ts");
console.log("You are currently reading:", await indexHTML.text());
```

Bundlee is a deno-embedder alternative.

```ts
import { Bundlee } from "https://deno.land/x/bundlee/mod.ts";

//deno:generate deno run -A https://deno.land/x/bundlee@0.9.4/bundlee.ts --bundle static/ bundle.json
const staticFiles = await Bundlee.load("bundle.json");
```

### Generate `deno doc` JSON

#### [`examples/docs/with_generate_docs.ts`](examples/docs/with_generate_docs.ts)

```ts
//deno:generate deno run -A generate_docs.ts
import doc from "./doc.json" assert { type: "json" };
```

#### [`examples/docs/generate_docs.ts`](examples/docs/generate_docs.ts)

```ts
// Create a child process running `deno doc --json`.
const child = new Deno.Command(Deno.execPath(), {
args: ["doc", "--json"],
stdin: "piped",
stdout: "piped",
}).spawn();

// Pipe the child process stdout to a writable file named "doc.json".
child.stdout.pipeTo(
Deno.openSync("doc.json", { write: true, create: true }).writable,
);
```

## Development

To run the tool from source, use the following command:
Expand All @@ -115,27 +334,66 @@ This process cleans your code and identifies common errors.
deno task all
```

The `all` task is defined in [`deno.jsonc`](deno.jsonc) and combines all of the
development tasks into one command.
The `all` task is defined in [`deno.jsonc`](deno.jsonc) and executes the
following tasks:

- `fmt`: Formats the code using
[Deno fmt](https://deno.land/manual/tools/formatter)
- `lint`: Runs [Deno lint](https://deno.land/manual/tools/linter) to identify
linting errors

You can run each task individually using the following commands:

```bash
deno task fmt
```

```bash
deno task lint
```

### Contributing

If you would like to contribute to this project, please read the
[contributing guidelines](CONTRIBUTING.md).
Contributions are welcome! Read the [contributing guide](CONTRIBUTING.md) for
more information.

### FAQ

## Inspiration
#### Why do I want to put `//deno:generate <command>` in source code instead of just in `deno task`?

- [Go Generate Proposal](https://go.googlesource.com/proposal/+/refs/heads/master/design/go-generate.md)
Using `//deno:generate <command>` in a TypeScript file instead of solely relying
on `deno task` offers several advantages. Firstly, incorporating
`//deno:generate` directly into the source code allows for better integration
between the generators and the codebase. This approach enables developers to
easily understand and manage the generators as an integral part of the project.

## License
Additionally, by utilizing `//deno:generate`, we can conveniently run multiple
generators that are closely tied to the specific TypeScript files. While it's
technically feasible to write a `deno task` that mimics the functionality of a
series of `//deno:generate` commands, this approach may not scale efficiently
when the project relies on numerous generators. By placing the `//deno:generate`
directives within the relevant TypeScript files, we achieve a more scalable and
flexible solution for managing generator-related tasks.

This project is licensed under the [MIT License](LICENSE).
#### If I have two files that depend on some generated code, which file do does `//deno:generate` belong in?

## Acknowledgements
In situations where multiple files depend on the same generated code, it's
highly recommended to create a shared module. This shared module serves as a
central location from which both files can import the generated code. By doing
so, we promote better code organization and encourage code reuse.

Special thanks to the [Deno](https://deno.land) team for creating this excellent
tool.
The shared module approach also simplifies future modifications and updates. If
the generated code needs to be modified or enhanced, we can make the changes in
a single place—the shared module—and have the updates reflect in both files that
depend on it. This not only reduces code duplication but also improves
maintainability by ensuring consistency throughout the project.

### License

[MIT](LICENSE)

---

Programmed with 🦕 by [**@EthanThatOneKid**](https://etok.codes/)

[^1]: https://docs.github.com/en/repositories/working-with-files/managing-files/customizing-how-changed-files-appear-on-github
Loading

0 comments on commit d36cdc6

Please sign in to comment.