- Notifications
You must be signed in to change notification settings - Fork0
Single dependency no-config monorepo linting, testing and bundling for TypeScript projects
License
zaripych/repka
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
- single dependency linting, bundling, testing and packaging for TypeScriptprojects
- supports both monorepo with multiple packages and single package repos
- minimum configuration required, driven by
package.json
- advanced configuration via TypeScript scripts with auto-completion
- ESM by default
Have a look at example packages in theplayground.
Repka allows you to quickly setup a TypeScript project with linting, bundling,testing and packaging. All you need is apackage.json
file and a singledependency@repka-kit/ts
.
If you are setting up a project from scratch:
npx --package @repka-kit/ts@1.0.0-beta.10 repka init
If you are addingrepka
to an existing project, you can use the same command,or just add it as a dependency and follow your instincts:
npm add -D @repka-kit/ts@1.0.0-beta.10
After that following dependencies become available:
tsc
tsx
eslint
jest
dts-bundle-generator
prettier
repka
repka
encapsulates configurations for all the tools and automaticallyinitializes them based on thepackage.json
file in the project root and yourworkspaces configuration.
Main benefit is that we can then update essential dev dependencies in thisrepository without having to change much in the packages that use thisrepository.
Another benefit is consistency across repositories.
Repka supports both monorepo and single package repositories. It is designed ina way that you can start with a single package repository and then easilymigrate to a monorepo, if needed.
When building a repository, you can put all source code for every package in thesrc
directory and have apackage.json
,tsconfig.json
and possibly otherconfig files (if we want to override default settings or eslint rules) next toit:
src/ index.tstsconfig.json.eslintrc.jspackage.json
The contents of thetsconfig.json
will be generated by theinit
command anddo not need to change if all of the source files are within thesrc
directory.
{"extends":"@repka-kit/ts/configs/tsconfig.base.json","compilerOptions": {"outDir":".tsc-out","tsBuildInfoFile":".tsc-out/.tsbuildinfo" },"include": ["src"]}
The contents of thepackage.json
for an app package can also be quite typical:
{"name":"@playground/todo-list-store","version":"0.0.0-development","private":true,"type":"module","exports":"./src/index.ts","types":"./src/index.ts","scripts": {"build":"repka build:node","lint":"repka lint","test":"repka test" },"dependencies": {},"devDependencies": {"@repka-kit/ts":"workspace:*" }}
There are a few things to note here:
@repka-kit/ts
is a dev dependency, it is recommended to put it as aworkspace dependency in a monorepo - this way all packages will use the sameversion ofrepka
and it will be easier to update it.The
exports
field only points to TypeScript files. There is literally noneed for any JavaScript files in the repository. This includes any filesdeclared in thebin
field (more on that below).repka
will usetsx
whererequired to run TypeScript files.The
type
ismodule
, while we could supportcjs
output, it is really nota good idea.repka lint
will runeslint
andtsc
and run them in parallelrepka test
will runjest
In case, when we have multiple packages can be like so:
packages/ package1/ src/ index.ts tsconfig.json package.json package2/ src/ index.ts tsconfig.json package.jsonpackage.jsontsconfig.json.eslintrc.js
Depending on the package manager the way workspaces are configured will bedifferent. Fornpm
it will be:
{"workspaces": ["packages/*"]}
Forpnpm
:
# ./pnpm-workspace.yamlpackages: -'packages/*'
Refer to the documentation of your package manager for more details.
repka
encourages you to reference TypeScript files directly, so there is noneed to build your packages. You can reference one package in another packageviapackage.json
dependencies
field,install
the repo to symlink packagesusing your package manager of choice (pnpm
recommended) and finally, startusing it. It just works.
The fact that you don't need to build your packages while developing them isquite awesome. It allows you to iterate faster and not worry about building theright dependencies before testing them.
How is this possible?
Well, we actually have twopackage.json
files. One is the original file thatyou use for development, another is generated byrepka
before you publish thepackage. This allows you to use TypeScript directly in your packages duringdevelopment and not worry about what happens to those entries before youpublish.
Then,tsc
kind of just works with TypeScript files. It is able to loadTypeScript files on the fly while type-checking. Becauserepka
uses"moduleResolution": "bundler"
there is no need to specify any extensions whenimporting files.
For cases when you actually need to run code,repka
encourages you to usetsx
, for example if you have a"bin"
field in yourpackage.json
:
{"bin": {"cli":"./src/bin/cli.ts" }}
You will be asked to add a shebang to the file:
#!/usr/bin/env tsx
And then you can run it directly:
pnpm run cli
Another benefit of having separatepackage.json
for publishing is that we canoptimize list of dependencies and exclude dependencies which we already bundledinto the package.
Now imagine that we have a monorepo with a lot of packages or a repository witha single package. We can run exactly same command to lint every package at theroot of the repo:
pnpm eslint
Which will runeslint
on entire repository.
To also check for TypeScript issues we can use:
pnpm repka lint
Which will runtsc
andeslint
in parallel.
Or we can parallelize it viapnpm -r
:
pnpm -r lint
While the above still requireslint
script to be present in every package, wedon't have to worry about creating aeslint
config for every package ormaintaining a list of all the plugins inpackage.json
dependencies.
Inrepka
we only enforceeslint
rules that are absolutely necessary and leadto bugs, when violated. Rules which can lead to false positives are not used.This is to ensure that the code is not riddled witheslint-disable
comments.The more you disable - the more it becomes useless.
eslint
is paired withprettier
and it is expected that developers use formaton save along witheslint --fix
on save. This way we can ensure that the codeis consistent, formatted and linted at all times.
eslint
rules still can be overridden standardeslint
way.
Another case is running tests:
pnpm jest
Use the above command to run all tests in the monorepo.
Alternatively, pass a glob pattern to run tests for a given set of files:
pnpm jest packages/playground/todo-list-store
There is no need to create ajest.config.js
file for every package. It's allencapsulated byrepka
and automatically discovered when needed.
What if we want to segregate integration tests from unit tests? It's done via"convention" inrepka
:
src __integration__ test-helpers.ts example.test.ts file.ts file.test.ts
Just put your integration tests into__integration__
directory and they can beexecuted via:
pnpm jest --integration
Bundling is done viarepka build:node
command. This is the command thatgenerates the./dist/package.json
for publishing and bundles the entry points.
side note: As monorepo can contain applications of different types there is aplan to add a command to build web apps as well. But for now it's just node.The plan was to just use rollup, vite or webpack, but it's not done yet.
The bundling is based offpackage.json
exports
field. If you want to havemultiple entry points, just add them to theexports
field:
{"exports": {".":"./src/index.ts","./helpers":"./src/helpers.ts" }}
You can userepka build:node --watch
to watch for changes and rebuild the codeas you code.
In addition to that, naturally, globs are also supported:
{"exports": {".":"./src/index.ts","./features/*":"./src/features/*" }}
Every TypeScript file infeatures/
directory is going to become its own entrypoint.
After bundling, if you mean to publish a package which can be consumed as alibrary it is recommended to generate declarations for it. This is done viarepka declarations
command. It will generate.d.ts
files for all the entrypoints.
It just works. No need to configure anything.
However, thedts-bundle-generator
which is used under the hood has a fewlimitations:https://github.com/timocov/dts-bundle-generator#known-limitations
repka
is designed to be as simple as possible, but it also allows you toconfigure bundling via code. Create abuild.ts
file at the root of the packagethat needs configuring.
Here is an example snippet:
import{buildForNode,pipeline}from'./src';import{addCjsBundle}from'./src/build/cjsBuildHelpers';awaitpipeline(buildForNode({extraRollupConfigs:(opts)=>[/** * Add custom rollup config to support cjs bundles * or whatever else we want */addCjsBundle(opts),],copy:[{/** * Copy files we don't want bundled but as is */include:['configs/**/*'],destination:'./dist/',},],}),async()=>{// do something else during the build});
repka
usesesbuild
forjest
andrepka build:node
.
Forkedversion of the DTS Bundle Generator is used to generate .d.ts files
Turnipicons created by Ridho Imam Prayogi - Flaticon
Now, I don't expect that a lot of people will use this as the project is quiteopinionated. In addition to that - I'm just a single person and might not havethe resources to help people out in case they have issues with it.
If you find the solution useful and want to help support the project -contributions are welcome.
About
Single dependency no-config monorepo linting, testing and bundling for TypeScript projects