├── .gitignore ├── .vscode └── settings.json ├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── src ├── index.ts ├── hello.ts └── tsconfig.json ├── .editorconfig ├── integration-tests ├── fixtures │ ├── server.key │ └── server.crt ├── hello.test.ts ├── nginx.conf └── hooks.ts ├── .mocharc.js ├── babel.config.js ├── LICENSE ├── .eslintrc.js ├── rollup.config.mjs ├── package.json ├── README.adoc └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | /dist/ 2 | /lib/ 3 | node_modules/ 4 | *.log 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib" 3 | } -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: jirutka 4 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { hello } from './hello' 2 | 3 | // NOTE: This module must contain only the default export, no named exports! 4 | 5 | export default { 6 | hello, 7 | } 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | ; http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 2 8 | indent_style = space 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | -------------------------------------------------------------------------------- /integration-tests/fixtures/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MHcCAQEEIHvVRgEolhWa24c3uDwkMVm5WuXYc5sVGEvASzorLM+zoAoGCCqGSM49 3 | AwEHoUQDQgAE3scviiZ1KL/K/TkjmLuXE+QqcVtH8MDombBUZCJicjFNKoxZu0US 4 | 6QlRj0YbQlZf+s2QCR2DIVt2NUHXN/5H8w== 5 | -----END EC PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /integration-tests/hello.test.ts: -------------------------------------------------------------------------------- 1 | import assert from 'assert/strict' 2 | import { test } from 'mocha' 3 | import './hooks' 4 | 5 | 6 | test('/hello?name=njs', async function () { 7 | const resp = await this.client.get('hello?name=njs') 8 | 9 | assert.equal(resp.statusCode, 200) 10 | assert.match(resp.body, /Meow, njs!/) 11 | }) 12 | -------------------------------------------------------------------------------- /.mocharc.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | process.env.BABEL_ENV = 'mocha' 4 | 5 | module.exports = { 6 | checkLeaks: true, 7 | extension: ['ts'], 8 | require: [ 9 | 'babel-register-ts', 10 | 'source-map-support/register', 11 | 'integration-tests/hooks.ts', 12 | ], 13 | spec: [ 14 | 'integration-tests/**/*.test.ts', 15 | ], 16 | } 17 | -------------------------------------------------------------------------------- /src/hello.ts: -------------------------------------------------------------------------------- 1 | import qs from 'querystring' 2 | 3 | 4 | export function hello (r: NginxHTTPRequest): void { 5 | const name = r.args.name ? qs.unescape(r.args.name) : 'world' 6 | 7 | return r.return(200, ` 8 | Meow, ${name}! 9 | 10 | ("\`-''-/").___..--''"\`-._ 11 | \`6_ 6 ) \`-. ( ).\`-.__.\`) 12 | (_Y_.)' ._ ) \`._ \`. \`\`-..-' 13 | _..\`--'_..-_/ /--'_.' ,' 14 | (il),-'' (li),' ((!.-' 15 | `) 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | - push 4 | - pull_request 5 | 6 | jobs: 7 | test: 8 | name: Test with nginx ${{ matrix.nginx-version }} 9 | strategy: 10 | matrix: 11 | nginx-version: 12 | - 1.22.x 13 | - 1.23.x 14 | - 1.24.x 15 | env: 16 | NGINX_VERSION: ${{ matrix.nginx-version }} 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v3 20 | - uses: actions/setup-node@v3 21 | - run: npm install 22 | - run: npm run build 23 | - run: npm run test 24 | - run: npm run lint 25 | -------------------------------------------------------------------------------- /integration-tests/nginx.conf: -------------------------------------------------------------------------------- 1 | events {} 2 | 3 | http { 4 | js_import main from ../dist/main.js; 5 | 6 | server { 7 | listen __ADDRESS__:__PORT__; 8 | 9 | # Using TLS in testing is useful if your JS script depends on it somehow 10 | # (e.g. secure cookies). If it's not your case, you can remove it and 11 | # change https:// to http:// in hooks.ts. 12 | ssl on; 13 | ssl_certificate "fixtures/server.crt"; 14 | ssl_certificate_key "fixtures/server.key"; 15 | 16 | location = /health { 17 | return 200; 18 | } 19 | 20 | location = /hello { 21 | js_content main.hello; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /integration-tests/fixtures/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIBuzCCAWGgAwIBAgIUTCvGL6ywCDzUf7xP8rZZXSOu5dwwCgYIKoZIzj0EAwIw 3 | FDESMBAGA1UEAwwJbG9jYWxob3N0MCAXDTIxMDMyODE3MzUxNloYDzIxMjEwMzA0 4 | MTczNTE2WjAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwWTATBgcqhkjOPQIBBggqhkjO 5 | PQMBBwNCAATexy+KJnUov8r9OSOYu5cT5CpxW0fwwOiZsFRkImJyMU0qjFm7RRLp 6 | CVGPRhtCVl/6zZAJHYMhW3Y1Qdc3/kfzo4GOMIGLMB0GA1UdDgQWBBQmKg0NL8RN 7 | lSWQQmCDxUMC3XrwZjAfBgNVHSMEGDAWgBQmKg0NL8RNlSWQQmCDxUMC3XrwZjAP 8 | BgNVHRMBAf8EBTADAQH/MDgGA1UdEQQxMC+CCWxvY2FsaG9zdIcEfwAAAYcEfwAA 9 | AocEfwAAA4cQAAAAAAAAAAAAAAAAAAAAATAKBggqhkjOPQQDAgNIADBFAiEAi9HE 10 | JjnBC9YmtW4heV9i7/4T2vjO1TsYMLIbRCDD3soCIBgRK2Zo5vE80AMlooj3BeCY 11 | XjHLfQjoKVWzp8uCejTa 12 | -----END CERTIFICATE----- 13 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // @ts-check 3 | 4 | /** @type {babel.ConfigFunction} */ 5 | module.exports = (api) => ({ 6 | presets: [ 7 | // Transpile modern JavaScript into code compatible with njs. 8 | // This is used only for building the dist bundle with Rollup. 9 | ...api.env('njs') ? [ 10 | 'babel-preset-njs', 11 | ] : [], 12 | // Parse TypeScript syntax and transform it to JavaScript (i.e. it strips 13 | // type annotations, but does not perform type checking). 14 | ['@babel/preset-typescript', { 15 | allowDeclareFields: true, 16 | }], 17 | ], 18 | 19 | plugins: [ 20 | ...!api.caller(c => c && c.supportsStaticESM) ? [ 21 | // Transform ES modules to CommonJS if needed needed for Mocha tests). 22 | // Mocha, babel-node, babel/register etc. don't understand ES module 23 | // syntax, so we have to transform it to CommonJS. 24 | // This is not used with Rollup. 25 | '@babel/plugin-transform-modules-commonjs' 26 | ] : [], 27 | ], 28 | }) 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright 2021 Jakub Jirutka . 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // @ts-check 3 | 4 | /** @type {import('eslint/lib/shared/types').ConfigData} */ 5 | module.exports = { 6 | root: true, 7 | ignorePatterns: [ 8 | '**/node_modules/', 9 | '/dist/', 10 | '/lib/', 11 | ], 12 | env: { 13 | es6: true, 14 | node: true, 15 | }, 16 | parserOptions: { 17 | sourceType: 'module', 18 | }, 19 | extends: [ 20 | 'eslint:recommended', 21 | ], 22 | // Common rules for all files. 23 | rules: { 24 | }, 25 | overrides: [ 26 | { 27 | files: '*.ts', 28 | parser: '@typescript-eslint/parser', 29 | parserOptions: { 30 | project: [ 31 | './tsconfig.json', 32 | './src/tsconfig.json', 33 | ], 34 | }, 35 | extends: [ 36 | 'eslint:recommended', 37 | 'plugin:@typescript-eslint/recommended', 38 | 'plugin:@typescript-eslint/recommended-requiring-type-checking', 39 | ], 40 | // Rules for TypeScript files. 41 | rules: { 42 | // Changed options 43 | '@typescript-eslint/ban-types': ['error', { 44 | // Allow to use {} and object - they are actually useful. 45 | types: { 46 | '{}': false, 47 | 'object': false, 48 | }, 49 | extendDefaults: true, 50 | }], 51 | 52 | // Changed from error to warn 53 | '@typescript-eslint/no-namespace': 'warn', 54 | '@typescript-eslint/no-unsafe-assignment': 'warn', 55 | '@typescript-eslint/no-unsafe-member-access': 'warn', 56 | 57 | // Disabled 58 | '@typescript-eslint/no-explicit-any': 'off', // `any` is sometimes needed 59 | '@typescript-eslint/restrict-template-expressions': 'off', // has false positives 60 | '@typescript-eslint/triple-slash-reference': 'off', // used for njs 61 | '@typescript-eslint/unbound-method': 'off', // has false positives 62 | 63 | // Added 64 | '@typescript-eslint/no-require-imports': 'error', 65 | }, 66 | }, 67 | ], 68 | } 69 | -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "target": "ES5", 5 | "module": "ES2015", 6 | "lib": [ 7 | "ES5", 8 | // "ES2015.Collection", 9 | "ES2015.Core", 10 | // "ES2015.Generator", 11 | "ES2015.Iterable", // since 0.5.0 12 | "ES2015.Promise", // since 0.6.2 13 | // "ES2015.Proxy", 14 | // "ES2015.Reflect", 15 | "ES2015.Symbol", // since 0.7.6 16 | "ES2015.Symbol.WellKnown", 17 | "ES2016.Array.Include", 18 | // "ES2017.Intl", 19 | "ES2017.Object", // since 0.2.7 20 | // "ES2017.SharedMemory", 21 | "ES2017.String", 22 | "ES2017.TypedArrays", // since 0.3.8 23 | // "ES2018.AsyncGenerator", 24 | // "ES2018.AsyncIterable", 25 | // "ES2018.Intl", 26 | "ES2018.Promise", // since 0.3.8 27 | "ES2018.Regexp", // since 0.3.2 28 | // "ES2019.Array", 29 | // "ES2019.Intl", 30 | // "ES2019.Object", 31 | "ES2019.String", // since 0.3.4 32 | "ES2019.Symbol", 33 | // "ES2020.BigInt", 34 | "ES2020.Date", 35 | // "ES2020.Intl", 36 | // "ES2020.Number", 37 | "ES2020.Promise", // since 0.6.2 38 | // "ES2020.SharedMemory", 39 | // "ES2020.String", 40 | // "ES2020.Symbol.WellKnown", 41 | // "ES2021.Intl", 42 | "ES2021.Promise", // since 0.6.2 43 | "ES2021.String", // since 0.7.10 44 | // "ES2021.WeakRef", 45 | ], 46 | "declaration": true, 47 | "outDir": "../lib", 48 | "composite": true, 49 | "tsBuildInfoFile": "../node_modules/.cache/tsc/src.tsbuildinfo", 50 | "noEmit": false, 51 | "emitDeclarationOnly": true, 52 | "isolatedModules": true, 53 | 54 | "typeRoots": [], 55 | "types": [ 56 | "njs-types", 57 | ], 58 | }, 59 | "include": [ 60 | ".", 61 | ], 62 | "files": [ 63 | // NOTE: Add/replace with ngx_stream_js_module.d.ts if you're developing 64 | // script for the stream module. 65 | "../node_modules/njs-types/ngx_http_js_module.d.ts", 66 | ], 67 | } 68 | -------------------------------------------------------------------------------- /rollup.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import * as FS from 'node:fs' 3 | 4 | import addGitMsg from 'rollup-plugin-add-git-msg' 5 | import { babel } from '@rollup/plugin-babel' 6 | import commonjs from '@rollup/plugin-commonjs' 7 | import { nodeResolve } from '@rollup/plugin-node-resolve' 8 | 9 | 10 | // List of njs built-in modules. 11 | // 12 | // - xml has added in njs 0.7.11 13 | // - zlib was added in njs 0.7.12 14 | const njsExternals = ['crypto', 'fs', 'querystring', 'xml', 'zlib'] 15 | const isEnvProd = process.env.NODE_ENV === 'production' 16 | 17 | /** 18 | * Plugin to fix syntax of the default export to be compatible with njs. 19 | * (https://github.com/rollup/rollup/pull/4182#issuecomment-1002241017) 20 | * 21 | * If you use njs >=0.7.12, you can remove this. 22 | * 23 | * @return {import('rollup').OutputPlugin} 24 | */ 25 | const fixExportDefault = () => ({ 26 | name: 'fix-export-default', 27 | renderChunk: (code) => ({ 28 | code: code.replace(/\bexport { (\S+) as default };/, 'export default $1;'), 29 | map: null, 30 | }), 31 | }) 32 | 33 | /** 34 | * @type {import('./package.json')} 35 | */ 36 | const pkg = JSON.parse(FS.readFileSync('./package.json', 'utf8')) 37 | 38 | /** 39 | * @type {import('rollup').RollupOptions} 40 | */ 41 | const options = { 42 | input: 'src/index.ts', 43 | external: njsExternals, 44 | plugins: [ 45 | // Transpile TypeScript sources to JS. 46 | babel({ 47 | babelHelpers: 'bundled', 48 | envName: 'njs', 49 | extensions: ['.ts', '.mjs', '.js'], 50 | }), 51 | // Resolve node modules. 52 | nodeResolve({ 53 | extensions: ['.mjs', '.js', '.json', '.ts'], 54 | }), 55 | // Convert CommonJS modules to ES6 modules. 56 | // @ts-ignore XXX: workaround for https://github.com/rollup/plugins/issues/1329 57 | commonjs(), 58 | // Fix syntax of the default export. 59 | fixExportDefault(), 60 | // Plugins to use in production mode only. 61 | ...isEnvProd ? [ 62 | // Add git tag, commit SHA, build date and copyright at top of the file. 63 | addGitMsg(), 64 | ] : [], 65 | ], 66 | output: { 67 | file: pkg.main, 68 | format: 'es', 69 | }, 70 | } 71 | export default options 72 | -------------------------------------------------------------------------------- /integration-tests/hooks.ts: -------------------------------------------------------------------------------- 1 | import * as FS from 'fs' 2 | import got, { Got } from 'got' 3 | import { Context, RootHookObject } from 'mocha' 4 | import { beforeEachSuite } from 'mocha-suite-hooks' 5 | import { startNginx, NginxServer } from 'nginx-testing' 6 | 7 | 8 | const certificate = FS.readFileSync(`${__dirname}/fixtures/server.crt`) 9 | const host = '127.0.0.1' 10 | const nginxConfig = `${__dirname}/nginx.conf` 11 | const nginxVersion = process.env.NGINX_VERSION || '1.24.x' 12 | 13 | declare module 'mocha' { 14 | export interface Context { 15 | client: Got 16 | nginx: NginxServer 17 | } 18 | } 19 | 20 | export const mochaHooks: RootHookObject = { 21 | async beforeAll (this: Context) { 22 | this.timeout(30_000) 23 | 24 | this.nginx = await startNginx({ version: nginxVersion, bindAddress: host, configPath: nginxConfig }) 25 | 26 | const errors = (await this.nginx.readErrorLog()) 27 | .split('\n') 28 | .filter(line => line.includes('[error]')) 29 | if (errors) { 30 | console.error(errors.join('\n')) 31 | } 32 | 33 | this.client = got.extend({ 34 | https: { 35 | certificateAuthority: certificate, 36 | }, 37 | prefixUrl: `https://${host}:${this.nginx.port}`, 38 | retry: 0, 39 | throwHttpErrors: false, 40 | }) 41 | 42 | beforeEachSuite (async function () { 43 | // Read the logs to consume (discard) them before running next test suite 44 | // (describe block). 45 | await this.nginx.readErrorLog() 46 | await this.nginx.readAccessLog() 47 | }) 48 | }, 49 | 50 | async afterAll (this: Context) { 51 | if (this.nginx) { 52 | await this.nginx.stop() 53 | } 54 | }, 55 | 56 | async afterEach (this: Context) { 57 | const { currentTest, nginx } = this 58 | 59 | if (currentTest?.state === 'failed' && currentTest.err) { 60 | const errorLog = await nginx.readErrorLog() 61 | const accessLog = await nginx.readAccessLog() 62 | 63 | const logs = [ 64 | errorLog && '----- Error Log -----\n' + errorLog, 65 | accessLog && '----- Access Log -----\n' + accessLog, 66 | ].filter(Boolean) 67 | 68 | if (logs.length > 0) { 69 | currentTest.err.stack += '\n\n' + logs.join('\n\n').replace(/^/gm, ' ') 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "njs-typescript-starter", 3 | "version": "0.0.0", 4 | "description": "A starting template for NGINX njs scripts written in TypeScript", 5 | "author": "Jakub Jirutka ", 6 | "license": "MIT", 7 | "homepage": "https://github.com/jirutka/njs-typescript-starter", 8 | "bugs": "https://github.com/jirutka/njs-typescript-starter/issues/", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/jirutka/njs-typescript-starter.git" 12 | }, 13 | "main": "dist/main.js", 14 | "files": [ 15 | "dist", 16 | "src" 17 | ], 18 | "scripts": { 19 | "build": "rollup -c --environment NODE_ENV:production", 20 | "clean": "rm -rf dist lib node_modules/.cache", 21 | "lint": "run-p lint:*", 22 | "lint:eslint": "eslint --cache --cache-location node_modules/.cache/eslint --ext .ts,.js,.mjs .", 23 | "lint:types": "tsc -b", 24 | "prepublishOnly": "run-s build && asciidoctor -b docbook -a npm-readme -a gh-branch=v$npm_package_version -o - README.adoc | pandoc -f docbook -t gfm --shift-heading-level-by 1 --wrap preserve -o README.md", 25 | "start": "run-p watch start-nginx", 26 | "start-nginx": "start-nginx --version 1.24.x --port 8090 --watch dist/ integration-tests/nginx.conf", 27 | "test": "rollup -c && mocha", 28 | "watch": "rollup -c --watch --no-watch.clearScreen" 29 | }, 30 | "engines": { 31 | "node": ">= 18.12" 32 | }, 33 | "devDependencies": { 34 | "@babel/core": "^7.21.8", 35 | "@babel/plugin-transform-modules-commonjs": "^7.21.5", 36 | "@babel/preset-typescript": "^7.21.5", 37 | "@babel/register": "^7.21.0", 38 | "@rollup/plugin-babel": "^6.0.3", 39 | "@rollup/plugin-commonjs": "^25.0.0", 40 | "@rollup/plugin-node-resolve": "^15.0.2", 41 | "@types/babel__core": "~7.20.0", 42 | "@types/mocha": "~10.0.1", 43 | "@types/rollup-plugin-add-git-msg": "~1.1.1", 44 | "@typescript-eslint/eslint-plugin": "^5.59.6", 45 | "@typescript-eslint/parser": "^5.59.6", 46 | "babel-preset-njs": "^0.7.0", 47 | "babel-register-ts": "^7.0.0", 48 | "eslint": "^8.40.0", 49 | "got": "^11.8.1", 50 | "mocha": "^10.2.0", 51 | "mocha-suite-hooks": "^0.1.0", 52 | "nginx-testing": "^0.4.0", 53 | "njs-types": "^0.7.12", 54 | "npm-run-all": "^4.1.5", 55 | "rollup": "^3.22.0", 56 | "rollup-plugin-add-git-msg": "^1.1.0", 57 | "typescript": "~5.0.4" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = Njs TypeScript Starter 2 | :toc: macro 3 | :toc-title: 4 | :gh-name: jirutka/njs-typescript-starter 5 | // non-breakable hyphen 6 | :nb-: ‑ 7 | // links 8 | :Babel: link:https://babeljs.io[Babel] 9 | :ESLint: link:https://eslint.org[ESLint] 10 | :Mocha: link:https://mochajs.org[Mocha] 11 | :nginx-testing: link:https://github.com/jirutka/nginx-testing[nginx-testing] 12 | :Rollup: link:https://babeljs.io[Rollup] 13 | :yarn: link:https://yarnpkg.com/[yarn] 14 | :npm: link:https://docs.npmjs.com/cli/commands/npm/[npm] 15 | 16 | A full-featured starting template for developing https://github.com/nginx/njs[njs] (NGINX JavaScript) scripts for https://nginx.org[NGINX] server in https://www.typescriptlang.org[TypeScript]. 17 | 18 | This template uses {Babel} and {Rollup} to compile TypeScript sources into a single JavaScript file for njs and {Mocha} with {nginx-testing} for running integration tests against NGINX sever. 19 | 20 | 21 | [discrete] 22 | == Table of Contents 23 | 24 | toc::[] 25 | 26 | 27 | == Pre-requisites 28 | 29 | To build and run this project locally you will need: 30 | 31 | * Linux system footnote:[It should work on any recent Linux distribution for x86_64, aarch64, or ppc64le architecture.], macOS footnote:[I’m not sure what is the lowest version you need. It should work both on Intel and M1.] or Windows Subsystem for Linux (WSL) footnote:[njs doesn’t support Windows, so integration tests won’t run on Windows out of the box. If you’re stuck with Windows, WSL is the easiest way to go.] 32 | * https://nodejs.org/en/download/package-manager/[Node.js] 18.12 or newer 33 | * {npm} (distributed with Node.js) or {yarn} 34 | 35 | *NOTE:* You do *not* need Docker or other containerization tool for developing and testing njs scripts on Linux and macOS! See {nginx-testing} for more information. 36 | 37 | 38 | == Getting Started 39 | 40 | . Clone this repository: 41 | + 42 | [source, sh, subs="+attributes"] 43 | git clone --depth=1 https://github.com/{gh-name}.git 44 | cd 45 | 46 | . Install dependencies: 47 | + 48 | [source, sh] 49 | npm install # or yarn install 50 | 51 | . Build the project and run tests: 52 | + 53 | [source, sh] 54 | npm test 55 | 56 | . Start nginx and project’s build in the watch mode: 57 | + 58 | [source, sh] 59 | npm run start 60 | 61 | . Open http://127.0.0.1:8090/hello in the browser and now you should see the output of link:src/hello.ts[]: 62 | + 63 | .... 64 | Meow, world! 65 | 66 | ("`-''-/").___..--''"`-._ 67 | `6_ 6 ) `-. ( ).`-.__.`) 68 | (_Y_.)' ._ ) `._ `. ``-..-' 69 | _..`--'_..-_/ /--'_.' ,' 70 | (il),-'' (li),' ((!.-' 71 | .... 72 | 73 | At least *before publication* of your project (e.g. on GitHub), do the following: 74 | 75 | * Change _name_, _version_, _description_, _author_, _license_ (if you prefer a license other than MIT), _homepage_, _bugs_, and _repository_ fields in link:package.json[] to reflect your project. 76 | * Add your name to link:LICENSE[] and/or change the license text if you prefer a license other than MIT. You don’t have to keep my name in the license. 77 | * Replace this README file. If you prefer Markdown to AsciiDoc, replace this file with `README.md` and remove `scripts.prepublishOnly` from link:package.json[]. 78 | 79 | 80 | == Project Structure 81 | 82 | Files and directory structure of the project. 83 | 84 | [%header] 85 | |=== 86 | | Path | Description 87 | 88 | | link:.github/workflows/[] 89 | | Contains https://github.com/features/actions/[GitHub Actions] workflows. 90 | 91 | | *link:dist/[]* 92 | | Contains the built JavaScript file for NGINX’s JS module. 93 | 94 | | *link:integration-tests/[integration{nb-}tests]* 95 | | Contains your integration tests. 96 | 97 | | lib/ 98 | | Contains type declarations generated from TypeScript sources in `src/` directory. 99 | 100 | | node_modules/ 101 | | Contains all your npm dependencies. 102 | 103 | | *link:src/[]* 104 | | Contains your source code that will be compiled to the `dist/` directory. 105 | 106 | | link:src/tsconfig.json[] 107 | | Config settings for type checking your TypeScript source code that will be compiled for njs. 108 | 109 | | link:.editorconfig[] 110 | | Defines very basic code style used in the project. 111 | See https://editorconfig.org[editorconfig.org] for more information. 112 | 113 | | link:.eslintrc.js[] 114 | | {ESLint} config for linting your TypeScript and JavaScript files. 115 | 116 | | link:.mocharc.js[] 117 | | {Mocha} config for integration tests. 118 | 119 | | link:babel.config.js[] 120 | | {Babel} config for compiling TypeScript sources into plain JavaScript compatible with njs and Node.js. 121 | 122 | | *link:package.json[]* 123 | | File that contains npm dependencies as well as <>. 124 | 125 | | package{nb-}lock.json 126 | | Describes the exact dependency tree that was generated, including exact versions and checksums of the installed packages. 127 | This file is automatically generated by {npm} when you run e.g. `npm install`. 128 | (https://docs.npmjs.com/cli/configuring-npm/package-lock-json[read more…]) 129 | 130 | | link:rollup.config.js[] 131 | | {Rollup} config for compiling and bundling your source code in `src/` together with the dependencies into a single JavaScript file for NGINX JS module. 132 | 133 | | link:tsconfig.json[] 134 | | Config settings for type checking code written in TypeScript or JavaScript that will be executed by Node.js (i.e. integration tests and JS configs). 135 | 136 | | yarn.lock 137 | | If you use {yarn} instead of {npm}; this file has the same purpose as _package-lock.json_ for npm. 138 | |=== 139 | 140 | *NOTE:* Some of the files and directories will be created after installing dependencies or building the project. 141 | 142 | 143 | == Build Scripts 144 | 145 | All the build steps are orchestrated via https://docs.npmjs.com/misc/scripts[npm scripts]. 146 | Npm scripts basically allow us to call (and chain) terminal commands via npm. 147 | If you open link:package.json[], you will see a scripts section with all the different scripts you can call. 148 | To call a script, simply run `npm run ` (or `yarn `) from the command line. 149 | 150 | Below is a list of all the scripts this template has available: 151 | 152 | [%header, cols="m,d"] 153 | |=== 154 | | Npm{nbsp}Script | Description 155 | 156 | | build | Compiles and bundles all source `.ts` files together with their dependencies into a single `.js` file in the `dist` directory. 157 | | clean | Cleans `dist`, `lib`, and `node_modules/.cache` directories. 158 | | lint | Runs both `lint:eslint` and `lint:types` in parallel. 159 | | lint:eslint | Runs {ESLint} linter on project files. 160 | | lint:types | Runs TypeScript type checker on project files. 161 | | start | Runs `start-nginx` and `watch` in parallel. 162 | | start{nb-}nginx | Starts nginx 1.24.x on port 8090 with config `integration-tests/nginx.conf` and reloads it on each change of the config file and files in `dist/`. 163 | | test | Runs all tests in the `integration-tests` directory using {Mocha} test runner. 164 | | watch | Same as `build` but continuously watches project’s `.ts` files and re-compiles when needed. 165 | |=== 166 | 167 | 168 | == Dependencies 169 | :npmjs-pkg: https://www.npmjs.com/package/ 170 | 171 | Project’s dependencies are specified in file link:package.json[], sections `dependencies` (runtime dependencies) and `devDependencies` (build-time dependencies). 172 | They are managed by {npm} or {yarn}. 173 | 174 | The following is a list of `devDependencies` with their description. 175 | 176 | * {npmjs-pkg}/typescript[typescript] -- TypeScript compiler and type checker. Used for type checking the TypeScript sources. 177 | ** {npmjs-pkg}/njs-types[njs-types] -- TypeScript type definitions for njs. 178 | ** @types/* -- Packages that provide TypeScript types. 179 | 180 | * {npmjs-pkg}/@babel/core[@babel/core] -- A {Babel} compiler core. Babel is used to compile TypeScript sources into plain JavaScript compatible with njs and Node.js. 181 | ** {npmjs-pkg}/babel-preset-njs[babel-preset-njs] -- A {Babel} preset for transforming JavaScript code with modern language features into code compatible with njs. 182 | ** {npmjs-pkg}/@babel/preset-typescript[@babel/preset-typescript] -- A {Babel} preset to transform TypeScript code into plain JavaScript. It basically just strips the type annotations. 183 | ** {npmjs-pkg}/@babel/plugin-transform-modules-commonjs[@babel/plugin-transform-modules-commonjs] -- A {Babel} plugin to transform ES2015 modules into CommonJS modules. Used for running integration tests with Node.js. 184 | ** {npmjs-pkg}/@babel/register[@babel/register] -- A {Babel} require hook. Used for {Mocha} integration with TypeScript. 185 | ** {npmjs-pkg}/babel-register-ts[babel-register-ts] -- A `@babel/register` wrapper with additional `.ts` and `.tsx` extensions. Used for {Mocha} integration with TypeScript. 186 | 187 | * {npmjs-pkg}/rollup[rollup] -- A module bundler for JavaScript which compiles small pieces of code into a single JavaScript file. 188 | ** {npmjs-pkg}/@rollup/plugin-babel[@rollup/plugin-babel] -- A {Rollup} plugin for seamless integration between Rollup and {Babel}. 189 | ** {npmjs-pkg}/@rollup/plugin-commonjs[@rollup/plugin-commonjs] -- A {Rollup} plugin to convert CommonJS modules to ES6, so they can be included in a Rollup bundle. 190 | ** {npmjs-pkg}/@rollup/plugin-node-resolve[@rollup/plugin-node-resolve] -- A {Rollup} plugin which locates modules using the Node resolution algorithm, for using third party modules in `node_modules`. 191 | ** {npmjs-pkg}/rollup-plugin-add-git-msg[rollup-plugin-add-git-msg] -- A {Rollup} plugin that inserts git tag, commit hash, build date and copyright at top of the generated JS bundle. 192 | 193 | * {npmjs-pkg}/mocha[mocha] -- A flexible JavaScript test framework for Node.js. 194 | ** {npmjs-pkg}/mocha-suite-hooks[mocha-suite-hooks] -- Suite-level hooks for {Mocha}; allows to run hook before/after describe block. 195 | 196 | * {npmjs-pkg}/nginx-testing[nginx-testing] -- Support for integration/acceptance testing of nginx configuration. 197 | 198 | * {npmjs-pkg}/got[got] -- A human-friendly and powerful HTTP request library for Node.js. Used in integration tests. Do _not_ update it to version 12.x (see https://github.com/{gh-name}/issues/3[#3])! 199 | 200 | * {npmjs-pkg}/eslint[eslint] -- {ESLint} is a tool for identifying and reporting on patterns found in JavaScript and TypeScript code. 201 | ** {npmjs-pkg}/@typescript-eslint/eslint-plugin[@typescript-eslint/eslint-plugin] -- A TypeScript plugin for {ESlint}. 202 | ** {npmjs-pkg}/@typescript-eslint/parser[@typescript-eslint/parser] -- An {ESLint} parser which leverages TypeScript ESTree to allow for ESLint to lint TypeScript source code. 203 | 204 | * {npmjs-pkg}/npm-run-all[npm-run-all] -- A CLI tool to run multiple npm-scripts in parallel or sequential. Used in npm scripts. 205 | 206 | 207 | == License 208 | 209 | This project is licensed under https://opensource.org/license/mit/[MIT License]. 210 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | "tsBuildInfoFile": "./node_modules/.cache/tsc/root.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "ES2021", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | "experimentalDecorators": false, /* Enable experimental support for legacy experimental decorators. */ 18 | "emitDecoratorMetadata": false, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | "useDefineForClassFields": false, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "commonjs", /* Specify what module code is generated. */ 29 | // "rootDir": "./", /* Specify the root folder within your source files. */ 30 | "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ 39 | // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ 40 | // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ 41 | // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ 42 | "resolveJsonModule": true, /* Enable importing .json files. */ 43 | // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ 44 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 45 | 46 | /* JavaScript Support */ 47 | "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 48 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 49 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 50 | 51 | /* Emit */ 52 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 53 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 54 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 55 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 56 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 57 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 58 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 59 | // "removeComments": true, /* Disable emitting comments. */ 60 | "noEmit": true, /* Disable emitting files from a compilation. */ 61 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 62 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 63 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 64 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 65 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 66 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 67 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 68 | "newLine": "lf", /* Set the newline character for emitting files. */ 69 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 70 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 71 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 72 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 73 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 74 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 75 | 76 | /* Interop Constraints */ 77 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 78 | // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ 79 | "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 80 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 81 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 82 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 83 | 84 | /* Type Checking */ 85 | "strict": true, /* Enable all strict type-checking options. */ 86 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 87 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 88 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 89 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 90 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 91 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 92 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 93 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 94 | "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 95 | "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 96 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 97 | "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 98 | "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 99 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 100 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 101 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 102 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 103 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 104 | 105 | /* Completeness */ 106 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 107 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 108 | }, 109 | "include": [ 110 | "./integration-tests", 111 | "./*.js", 112 | "./*.mjs", 113 | ], 114 | "references": [ 115 | { "path": "./src" }, 116 | ], 117 | } 118 | --------------------------------------------------------------------------------