npx
allows to run a single executable file without installing a full npm package locally.
I recently implemented a static website generator that runs a single npx
command to generate a full html website from markdown:
In this post we learn how to:
- Create an
npx
executable script using Typescript - How to configure
package.json
to publish annpx
command - How to bundle Typescript code to a single javascript executable file
- How to publish the
npx
code to npm
Implement code to execute
The implementation is up to you ππΌββοΈ
The only requirement is adding a shebang in the first line of the executable code:
shebang: number sign and exclamation mark (
#!
) at the beginning of a script.It tells the operating system (Unix) that this file is a node script.
#!/usr/bin/env node
import { Effect, Layer, LogLevel, Logger, ReadonlyArray, pipe } from "effect";
import * as Converter from "./Converter.js";
import * as Css from "./Css.js";
import * as FileSystem from "./FileSystem.js";
import * as LinkCheck from "./LinkCheck.js";
import { ChalkLogger } from "./Logger.js";
import * as SiteConfig from "./SiteConfig.js";
import * as Template from "./Template.js";
When running npx
the code in this entry file will be executed (bin.ts/js
in this example π).
In the code below the final line executes the full program as a Promise
and logs unexpected errors (using Effect):
#!/usr/bin/env node
const program = Effect.gen(function* (_) {
/// ...
});
const MainLive = Layer.mergeAll(
/// ...
);
const runnable = program.pipe(
Effect.provide(
/// ...
)
);
const main: Effect.Effect<never, never, void> = runnable.pipe(
Effect.catchTags({
/// ...
})
);
Effect.runPromise(main).catch(console.error);
package.json
configuration (bin
)
Inside package.json
we define the npx
command to run using bin
.
bin
can be either:
- A single string with the name of the file to execute
- An object where the key is the name of the command and the value is the file to execute
In the example below we define a menimal
command that executes the code inside ./dist/bin.js
.
This will create a npx menimal
command on npm:
{
"name": "menimal",
"version": "0.0.11",
"author": "Sandro Maglione",
"description": "Generate a static html-only website from markdown and css",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/SandroMaglione/menimal.git"
},
"keywords": [
"effect"
],
"main": "./dist/bin.js",
"bin": {
"menimal": "./dist/bin.js"
},
"files": [
"dist"
],
"scripts": {
"tsc": "tsc -p tsconfig.json",
"dev": "tsx src/bin.ts",
"bundle": "tsup && tsx scripts/copy-templates.ts",
"upload": "pnpm bundle && npm publish"
},
"dependencies": {
"@effect/platform": "^0.43.7",
"@effect/platform-node": "^0.42.7",
"@effect/schema": "^0.61.5",
"chalk": "^4.1.2",
"effect": "^2.2.3",
"gray-matter": "^4.0.3",
"html-minifier": "^4.0.0",
"lightningcss": "^1.23.0",
"mustache": "^4.2.0",
"node-html-parser": "^6.1.12",
"showdown": "^2.1.0"
},
"devDependencies": {
"@types/html-minifier": "^4.0.5",
"@types/mustache": "^4.2.5",
"@types/node": "^20.11.16",
"@types/showdown": "^2.0.6",
"tsup": "^8.0.1",
"tsx": "^4.7.0",
"typescript": "^5.3.3"
}
}
There is more π€©
Timeless coding principles, practices, and tools that make a difference, regardless of your language or framework, delivered in your inbox every week.
tsup
: Bundle Typescript library
Since our script is written using Typescript, we need to bundle it to a single javascript file.
In this example I used tsup
:
"devDependencies": {
"@types/html-minifier": "^4.0.5",
"@types/mustache": "^4.2.5",
"@types/node": "^20.11.16",
"@types/showdown": "^2.0.6",
"tsup": "^8.0.1",
"tsx": "^4.7.0",
"typescript": "^5.3.3"
}
Inside tsup.config.ts
we define the bundling configuration:
- Specify entry file (
src/bin.ts
) - Specify the output format (
cjs
for Node)
import { defineConfig } from "tsup";
export default defineConfig({
entry: ["src/bin.ts"],
publicDir: false,
clean: true,
minify: true,
format: ["cjs"], // π Node
});
Now we can simply run the tsup
command:
"scripts": {
"tsc": "tsc -p tsconfig.json",
"dev": "tsx src/bin.ts",
"bundle": "tsup && tsx scripts/copy-templates.ts",
"upload": "pnpm bundle && npm publish"
},
I added also a custom script
copy-templates.ts
that is run usingtsx
.
tsup will read the configuration from tsup.config.ts and tsconfig.json and bundle the full typescript code to a single bin.js file
This will generate a dist
folder containing the bundled code (a single bin.js
file):
All the code is bundled inside a single bin.js. Make sure to ignore this file using .gitignore
Publish library on npm
The final step is publishing the library on npm.
You need to have a valid account on npm to be allowed to publish a package βοΈ
We define an upload
command that bundles the code and executes npm publish
:
You will be prompted to login to your npm account. You can also run the
npm login
command yourself.
"scripts": {
"tsc": "tsc -p tsconfig.json",
"dev": "tsx src/bin.ts",
"bundle": "tsup && tsx scripts/copy-templates.ts",
"upload": "pnpm bundle && npm publish"
},
This is it!
You can now publish your own npx
commands on npm. This comes handy when you want to execute some code without installing the package locally.
Check out the
menimal
package on npm.
If you are interested to learn more, every week I publish a new open source project and share notes and lessons learned in my newsletter. You can subscribe here below π
Thanks for reading.