…or how to make great npm packages for the mere mortal.
…or how to make great npm packages for the mere mortal.
However…
Then
a.k.a. lihbr
A little~
Packages
Ecosystem of packages
A package ecosystem is all the packages under the responsibility of an entity, whether it’s an individual, a company, etc.
Prismic
CMS
Users
SDKs (packages)
But let’s specify a few things…
Making an exception
-^
You’re growing an ecosystem that will support you in your journey
Internal auth service
Auth package
Maintain SDKs
Built with Vite
Packages are quite handy to abstract code you use often:
Abstraction never comes for free, neither does creating packages.
Small packages also make a difference.
But it’s always cool to know how to.
Few disclaimers before we embark:
package.json
, but no worriesIt’s two things!
tl;dr; they all claim to do something better than npm (that's pretty much true)
npm is also an online database.
You can configure npm to resolve your dependencies across multiple registries.
# .npmrc
# Fetch `@lihbr` packages from GitHub registry
@lihbr:registry=https://npm.pkg.github.com
# Fetch `@my-company` packages from My Company registry
@my-company:registry=https://npm.pkg.my-company.com
…our package definition
{
"name": "elk.zone",
"version": "0.5.0",
"scripts": {
"build": "nuxi build",
"dev": "nuxi dev --port 5314",
"generate": "nuxi generate"
},
"dependencies": {
"@vueuse/core": "^9.10.0",
"blurhash": "^2.0.4",
"masto": "^5.5.0"
},
"devDependencies": {
"eslint": "^8.31.0",
"nuxt": "^3.0.0",
"prettier": "^2.8.2",
"typescript": "^4.9.4"
}
}
Mandatory name
field.
@prismicio/client
Represents a user or an organization
You can only publish to scopes you have access to
Hint, at a glance, who's the package publisher
Mandatory version
field.
Mandatory version
field.
A package is about delivering code.
-^
Entry points, what’s accessible on our package.
{
"name": "my-package",
"version": "0.0.1",
"main": "dist/index.js",
"scripts": { ... },
"dependencies": { ... },
"devDependencies": { ... }
}
-^
const myPackage = require("my-package");
{
"name": "my-package",
"version": "0.0.1",
"main": "dist/index.js",
"module": "dist/index.mjs",
"scripts": { ... },
"dependencies": { ... },
"devDependencies": { ... }
}
-^
import myPackage from "my-package";
{
"name": "my-package",
"version": "0.0.1",
"main": "dist/index.js",
"module": "dist/index.mjs",
"scripts": { ... },
"dependencies": { ... },
"devDependencies": { ... }
}
-^
import myPackage from "my-package";
{
"name": "my-package",
"version": "0.0.1",
"main": "dist/index.js",
"module": "dist/index.mjs",
"exports": {
".": {
"require": "./dist/index.js",
"import": "./dist/index.mjs"
}
},
"scripts": { ... },
"dependencies": { ... },
"devDependencies": { ... }
}
Not straightforward to answer, but "basically":
exports
only if you want to be future-facing, and only future-facingmain
, module
, and exports
if you want (or need to) be compatible with most setupPackage code entry points:
main
, default entry point (CJS or ESM)module
, ESM-specific entry pointexports
, modern entry points, more flexible{
"name": "my-package",
"version": "0.0.1",
"main": "dist/index.js",
"module": "dist/index.mjs",
"exports": {
".": {
"require": "./dist/index.js",
"import": "./dist/index.mjs"
}
},
"types": "dist/index.d.ts",
"scripts": { ... },
"dependencies": { ... },
"devDependencies": { ... }
}
{
"name": "my-package",
"version": "0.0.1",
"main": "dist/index.js",
"module": "dist/index.mjs",
"exports": {
".": {
"require": "./dist/index.js",
"import": "./dist/index.mjs"
}
},
"types": "dist/index.d.ts",
"bin": {
"my-package": "bin/cli.js"
},
"scripts": { ... },
"dependencies": { ... },
"devDependencies": { ... }
}
{
"name": "my-package",
"version": "0.0.1",
"main": "dist/index.js",
"module": "dist/index.mjs",
"exports": {
".": {
"require": "./dist/index.js",
"import": "./dist/index.mjs"
}
},
"types": "dist/index.d.ts",
"bin": {
"my-package": "bin/cli.js"
},
"files": ["src"],
"scripts": { ... },
"dependencies": { ... },
"devDependencies": { ... }
}
{
"name": "@prismicio/client",
"version": "7.0.0",
"exports": {
".": {
"require": "./dist/index.cjs",
"import": "./dist/index.js"
},
"./package.json": "./package.json"
},
"main": "dist/index.cjs",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist",
"src"
],
"scripts": { ... },
"dependencies": { ... },
"devDependencies": { ... }
}
Types of dependencies:
dependencies
devDependencies
Only package’s dependencies
are installed when it’s installed, devDependencies
are ignored
{
"name": "my-package",
"version": "0.0.1",
"main": "dist/index.js",
"module": "dist/index.mjs",
"scripts": {
"build": "vite build"
},
"dependencies": { ... },
"devDependencies": {
"vite": "^4.0.4"
}
}
Types of dependencies:
dependencies
, required to run the packagedevDependencies
, required to build the packagepeerDependencies
foo
{
"name": "vue-router",
"version": "4.1.6",
"main": "index.js",
"module": "dist/vue-router.mjs",
"scripts": { ... },
"dependencies": { ... },
"devDependencies": { ... },
"peerDependencies": {
"vue": "^3.2.0"
}
}
Main things to consider:
name
and version
fieldsThings I omitted:
engine
type
(no s
!)repository
, sideEffects
, publishConfig
, etc.More on the documentation: https://docs.npmjs.com/cli/v9/configuring-npm/package-json
People expect a lot from open-source packages.
If the package works fine enough for you and others, it’s already great!
Things that work great for others, might work great for you!
You can learn a lot this way.
.github
folderRegular maintenance:
Helps ensuring your package keeps working and is easy to work with.
At Prismic, once a week if there are issues, otherwise, once a month.
Evolving?
<NuxtLink />
RFC exampleA package can die.
Be a great citizen and use the npm deprecate
command.
…or how to make great npm packages for the mere mortal.
Everything from this talk & more:
Twitter:
Mastodon: