Skip to content

Commit

Permalink
feat: add support for typescript and esm generation (fastify#577)
Browse files Browse the repository at this point in the history
--esm --lang=ts support, and fixes code coverage of app-esm using c8
  • Loading branch information
S-Abhishek committed Mar 25, 2023
1 parent ec71989 commit e66b05b
Show file tree
Hide file tree
Showing 21 changed files with 550 additions and 3 deletions.
19 changes: 18 additions & 1 deletion generate.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,24 @@ function cli (args) {

let template
if (opts.lang === 'ts' || opts.lang === 'typescript') {
template = typescriptTemplate
template = { ...typescriptTemplate }

if (opts.esm) {
template.dir = 'app-ts-esm'
template.type = 'module'
template.tap = {
'node-arg': [
'--no-warnings',
'--experimental-loader',
'ts-node/esm'
],
coverage: false
}

// For coverage, NYC with Typescript ESM doesn't work https://github.com/tapjs/node-tap/issues/735
template.devDependencies.c8 = cliPkg.devDependencies.c8
template.scripts.test = 'npm run build:ts && tsc -p test/tsconfig.json && c8 tap --ts "test/**/*.test.ts"'
}
} else {
template = { ...javascriptTemplate }

Expand Down
11 changes: 11 additions & 0 deletions helper.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import fastify from 'fastify'

declare module 'fastify-cli/helper.js' {
type fastifyFunctionReturnType = ReturnType<fastify>;

module helper {
export function build(argv: Array<string>, config: Object): fastifyFunctionReturnType;
}

export = helper;
}
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@
"scripts": {
"lint": "standard",
"lint:fix": "standard --fix",
"unit": "tap \"test/**/*.test.{js,ts}\" \"templates/**/*.test.{js,ts}\" --timeout 300",
"unit:template-ts-esm": "TS_NODE_PROJECT=./templates/app-ts-esm/tsconfig.json tap templates/app-ts-esm/test/**/*.test.ts --no-coverage --node-arg=--loader=ts-node/esm --timeout 100",
"unit:cli": "tap \"test/**/*.test.{js,ts}\" --no-coverage --timeout 200",
"unit:templates-without-ts-esm": "tap \"templates/app/**/*.test.js\" \"templates/app-esm/**/*.test.js\" \"templates/app-ts/**/*.test.ts\" --no-coverage --timeout 200",
"pretest": "xcopy /e /k /i . \"..\\node_modules\\fastify-cli\" || rsync -r --exclude=node_modules ./ node_modules/fastify-cli || echo 'this is fine'",
"test": "npm run unit && npm run test:typescript",
"test-no-coverage": "npm run unit:cli && npm run unit:templates-without-ts-esm && npm run unit:template-ts-esm && npm run test:typescript",
"test": "c8 --clean npm run test-no-coverage",
"test:typescript": "tsd templates/plugin && tsc --project templates/app-ts/tsconfig.json && del-cli templates/app-ts/dist"
},
"keywords": [
Expand Down Expand Up @@ -67,6 +70,7 @@
"@istanbuljs/esm-loader-hook": "0.2.0",
"@types/node": "^18.0.0",
"@types/tap": "^15.0.5",
"c8": "^7.13.0",
"concurrently": "^7.0.0",
"del-cli": "^3.0.1",
"fastify-tsconfig": "^1.0.1",
Expand Down
23 changes: 23 additions & 0 deletions templates/app-ts-esm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Getting Started with [Fastify-CLI](https://www.npmjs.com/package/fastify-cli)
This project was bootstrapped with Fastify-CLI.

## Available Scripts

In the project directory, you can run:

### `npm run dev`

To start the app in dev mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.

### `npm start`

For production mode

### `npm run test`

Run the test cases.

## Learn More

To learn Fastify, check out the [Fastify documentation](https://www.fastify.io/docs/latest/).
65 changes: 65 additions & 0 deletions templates/app-ts-esm/__gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Logs
logs
*.log
npm-debug.log*

# Runtime data
pids
*.pid
*.seed

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# nyc test coverage
.nyc_output

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# node-waf configuration
.lock-wscript

# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules
jspm_packages

# Optional npm cache directory
.npm

# Optional REPL history
.node_repl_history

# 0x
profile-*

# mac files
.DS_Store

# vim swap files
*.swp

# webstorm
.idea

# vscode
.vscode
*code-workspace

# clinic
profile*
*clinic*
*flamegraph*

# generated code
examples/typescript-server.js
test/types/index.js

# compiled app
dist
4 changes: 4 additions & 0 deletions templates/app-ts-esm/__taprc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
test-env: [
TS_NODE_FILES=true,
TS_NODE_PROJECT=./test/tsconfig.json
]
3 changes: 3 additions & 0 deletions templates/app-ts-esm/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"type": "module"
}
46 changes: 46 additions & 0 deletions templates/app-ts-esm/src/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import * as path from 'path';
import AutoLoad, {AutoloadPluginOptions} from '@fastify/autoload';
import { FastifyPluginAsync } from 'fastify';
import { fileURLToPath } from 'url'

const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)

export type AppOptions = {
// Place your custom options for app below here.
} & Partial<AutoloadPluginOptions>;


// Pass --options via CLI arguments in command to enable these options.
const options: AppOptions = {
}

const app: FastifyPluginAsync<AppOptions> = async (
fastify,
opts
): Promise<void> => {
// Place here your custom code!

// Do not touch the following lines

// This loads all plugins defined in plugins
// those should be support plugins that are reused
// through your application
void fastify.register(AutoLoad, {
dir: path.join(__dirname, 'plugins'),
options: opts,
forceESM: true
})

// This loads all plugins defined in routes
// define your routes in one of these
void fastify.register(AutoLoad, {
dir: path.join(__dirname, 'routes'),
options: opts,
forceESM: true
})

};

export default app;
export { app, options }
16 changes: 16 additions & 0 deletions templates/app-ts-esm/src/plugins/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Plugins Folder

Plugins define behavior that is common to all the routes in your
application. Authentication, caching, templates, and all the other cross
cutting concerns should be handled by plugins placed in this folder.

Files in this folder are typically defined through the
[`fastify-plugin`](https://github.com/fastify/fastify-plugin) module,
making them non-encapsulated. They can define decorators and set hooks
that will then be used in the rest of your application.

Check out:

* [The hitchhiker's guide to plugins](https://www.fastify.io/docs/latest/Guides/Plugins-Guide/)
* [Fastify decorators](https://www.fastify.io/docs/latest/Reference/Decorators/).
* [Fastify lifecycle](https://www.fastify.io/docs/latest/Reference/Lifecycle/).
11 changes: 11 additions & 0 deletions templates/app-ts-esm/src/plugins/sensible.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import fp from 'fastify-plugin'
import sensible, { SensibleOptions } from '@fastify/sensible'

/**
* This plugins adds some utilities to handle http errors
*
* @see https://github.com/fastify/fastify-sensible
*/
export default fp<SensibleOptions>(async (fastify) => {
fastify.register(sensible)
})
20 changes: 20 additions & 0 deletions templates/app-ts-esm/src/plugins/support.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import fp from 'fastify-plugin'

export interface SupportPluginOptions {
// Specify Support plugin options here
}

// The use of fastify-plugin is required to be able
// to export the decorators to the outer scope
export default fp<SupportPluginOptions>(async (fastify, opts) => {
fastify.decorate('someSupport', function () {
return 'hugs'
})
})

// When using .decorate you have to specify added properties for Typescript
declare module 'fastify' {
export interface FastifyInstance {
someSupport(): string;
}
}
24 changes: 24 additions & 0 deletions templates/app-ts-esm/src/routes/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Routes Folder

Routes define endpoints within your application. Fastify provides an
easy path to a microservice architecture, in the future you might want
to independently deploy some of those.

In this folder you should define all the routes that define the endpoints
of your web application.
Each service is a [Fastify
plugin](https://www.fastify.io/docs/latest/Reference/Plugins/), it is
encapsulated (it can have its own independent plugins) and it is
typically stored in a file; be careful to group your routes logically,
e.g. all `/users` routes in a `users.js` file. We have added
a `root.js` file for you with a '/' root added.

If a single file become too large, create a folder and add a `index.js` file there:
this file must be a Fastify plugin, and it will be loaded automatically
by the application. You can now add as many files as you want inside that folder.
In this way you can create complex routes within a single monolith,
and eventually extract them.

If you need to share functionality between routes, place that
functionality into the `plugins` folder, and share it via
[decorators](https://www.fastify.io/docs/latest/Reference/Decorators/).
9 changes: 9 additions & 0 deletions templates/app-ts-esm/src/routes/example/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { FastifyPluginAsync } from "fastify"

const example: FastifyPluginAsync = async (fastify, opts): Promise<void> => {
fastify.get('/', async function (request, reply) {
return 'this is an example'
})
}

export default example;
9 changes: 9 additions & 0 deletions templates/app-ts-esm/src/routes/root.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { FastifyPluginAsync } from 'fastify'

const root: FastifyPluginAsync = async (fastify, opts): Promise<void> => {
fastify.get('/', async function (request, reply) {
return { root: true }
})
}

export default root;
39 changes: 39 additions & 0 deletions templates/app-ts-esm/test/helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// This file contains code that we reuse between our tests.
import helper from 'fastify-cli/helper.js'
import path from 'path'
import tap from 'tap';
import { fileURLToPath } from 'url'


export type Test = typeof tap['Test']['prototype'];

const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const AppPath = path.join(__dirname, '..', 'src', 'app.ts')

// Fill in this config with all the configurations
// needed for testing the application
async function config () {
return {}
}

// Automatically build and tear down our instance
async function build (t: Test) {
// you can set all the options supported by the fastify CLI command
const argv = [AppPath]

// fastify-plugin ensures that all decorators
// are exposed for testing purposes, this is
// different from the production setup
const app = await helper.build(argv, await config())

// Tear down our app after we are done
t.teardown(() => void app.close())

return app
}

export {
config,
build
}
11 changes: 11 additions & 0 deletions templates/app-ts-esm/test/plugins/support.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { test } from 'tap'
import Fastify from 'fastify'
import Support from '../../src/plugins/support.js'

test('support works standalone', async (t) => {
const fastify = Fastify()
void fastify.register(Support)
await fastify.ready()

t.equal(fastify.someSupport(), 'hugs')
})
12 changes: 12 additions & 0 deletions templates/app-ts-esm/test/routes/example.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { test } from 'tap'
import { build } from '../helper.js'

test('example is loaded', async (t) => {
const app = await build(t)

const res = await app.inject({
url: '/example'
})

t.equal(res.payload, 'this is an example')
})
11 changes: 11 additions & 0 deletions templates/app-ts-esm/test/routes/root.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { test } from 'tap'
import { build } from '../helper.js'

test('default root route', async (t) => {
const app = await build(t)

const res = await app.inject({
url: '/'
})
t.same(JSON.parse(res.payload), { root: true })
})
8 changes: 8 additions & 0 deletions templates/app-ts-esm/test/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"noEmit": false
},
"include": ["../src/**/*.ts", "**/*.ts"]
}
Loading

0 comments on commit e66b05b

Please sign in to comment.