TypeScript/ECMA
Tools
TypeScript projects use:
prettierfor formattingeslintfor checking code style and issuestscfor type checking and generatingd.tsfiles for librariesvitestfor testingvitefor bundling library or apptypedocfor generating documentation usingrustdoctheme
mono-dev packages itself as a node module. In TypeScript/ECMAScript
projects, the package needs to be declared in package.json to be
managed by the package manager.
Running pnpm up mono-dev will resolve the latest commit and update it.
Template: package.json
-
{ "devDependencies": { "mono-dev": "github:Pistonight/mono-dev#dist" }, "pistonight/mono-dev": { "nocheck": [ "/src/generated" ] } }- Paths in
nocheckwill not be processed byeslintorprettier. If the path is in the form of/fooorfoo, thefoodirectory will also not be type-checked. pistonight/mono-devis the options for mono-dev. See MonoDevOptions
- Paths in
Template: Taskfile.yml
-
version: '3' includes: ecma: taskfile: ./node_modules/mono-dev/task/ecma.yaml internal: true optional: true
Template: .gitignore
-
# mono-dev: ecma gitignores node_modules package-lock.json .prettierignore .eslintcache /eslint.config.js /tsconfig*.json /mono-dev /dist /docs
Type Checking & Linting
mono-dev automatically generates type checking configs based on directory structure:
- Each directory is type-checked separately
and allow for different env config (for example,
srcvsscripts) - no DOM and no types exist by default. They need to be manually included in
env.d.tsin each directory. Only directories withenv.d.tswill be checked. - If root directory contain any TypeScript stuff, it will be checked as well
- ESLint only checks the TypeScript projects. If you use ECMAScript, you opt-out of safety anyway
For LSP and compatibility with other tools, ESLint and tsconfig files
will be generated at the project root (like how they are for a regular project).
For eslint-lsp, you may need to add eslint dependency to downstream in order
for the server to find the eslint library.
Import Path remapping
We now use the NodeJS subpath imports to map internal imports to avoid the “relative parent import hell”. This has a few advantages over mapping it in TS:
- Generated
.d.tsfiles would have the correct imports - Bundler tools such as
viteorbundoesn’t need extra TS-specific configuration.
Since the bundler would remove these imports when bundling, they are only significant
in type declarations generated by TSC (since they keep the original imports).
This is why the field is mapped to the source typescript files instead of files in dist.
The mapping is always automatically generated for the src directory and
can be manuall disabled with mono-dev option importmap: false.
The following directory structure:
- src/
- app/
- util/
- image/
- index.ts
- data/
- index.ts
- lib/
- foo/
- index.ts
- index.ts
generates:
#lib -> ./src/lib/index.ts
#util/image -> ./src/util/image/index.ts
#util/data -> ./src/util/data/index.ts
Note that in the published package.json, the imports will be replaced
with the .d.ts files
Test
mono-dev re-exports vitest for testing. This ensures the version of vitest
is managed by the version of mono-dev. Import anything from mono-dev/vitest
instead of vitest.
Use ecma:test task to run test once and ecma:test-dev task to run in watch mode.
Library Exports
The tooling supports 3 kinds of exports:
-
Raw: As-configured in
exportsfield; anything that is not one of the below -
Auto-compiled: Any
exportskey that satisfies*.(c|m)?tsx?-
In the published package, the export is transformed to an object like below.
{ "exports": { "./foo": "./src/bar/foo.ts" } } // becomes: { "exports": { "./foo": { "import": "./dist/bar/foo.js", "types": "./dist/_dts_/src/bar/foo.d.ts" } } }In this configuration, only the published package contains ESM + Type Declaration. Internal consumers (like other packages in a monorepo) consume the TS source directly for more streamlined dev experience. For example running vite dev server will not require internal packages to be built into ESM first. Editing TS source also does not require rebuilding the packages
-
Use the mono-dev
nocompileoption to exclude auto-configured compilation, which means the published package will also export the TS file directly.
-
-
Manual-compiled: Object
exportskey of the form:{ "pistonight/mono-dev": { "compile": { "./foo": "./src/bar/foo.ts" } }, "exports": { "./foo": { "import": "./dist/bar/foo.js", "types": "./dist/_dts_/src/bar/foo.d.ts" } } }
Note that src must be where TS files are exported from, and dist must be where
output ESM is emitted. These 2 directory names are NOT configurable.
Build Library
Building library from TS source into consumable package without TS supports zero-config.
Under the hood, the library mode of vite is used. vite.config.ts will be generated
on-the-go when running the ecma:lib-build task.
Currently the library is only packaged as ESM.
The build uses dependencies, devDependencies and peerDependencies to automatically
externalize dependencies.
dependencies- Will be installed by package manager when consumer adds the library
- Externalized: The code of the dependency will not be in the library
peerDependencies:- Will NOT be installed by package manager when consumer adds the library; They have to also install it in the downstream.
- Externalized: The code of the dependency will not be in the library
devDependencies:- Will NOT be installed by package manager when consumer adds the library.
- NOT Externalized: The code of the dependency is bundled into the library.
Consideration: Unless the library is meant to be used as a framework, DO NOT add global states. It’s very easy to cause duplicated global states when resolving dependencies.
See the above section for out library exports are configured.
If needed, the lib-types export defines the types for library builds.
Put in env.d.ts
/// <reference types="mono-dev/lib-types" />
Vite
mono-dev ships a baseline vite config that adds common plugins
and configs to my projects.
vite.config.ts at the root:
import { configure } from "mono-dev/app-build-config";
// config options are referenced from pistonight/mono-dev options
// in package.json
// use configure just like defineConfig
export default configure({
/** normal vite config here */
});
Plugins are automatically added:
- YAML loader (always added)
- React and React Compiler (if react is installed)
- WASM (if configured)
Define vite types in src/env.d.ts:
/// <reference types="mono-dev/app-types" />
/// <reference types="dom" />
Use the ecma:app-dev and ecma:app-build tasks to execute vite
dev server and build.
import.meta.env
Mono-dev options can be used to specify auto import.meta.env values.
See MonoDevOptions
Publish
The tool need to zap the package.json before publishing.
Therefore, private must be true to prevent accidental
publishing of the original package.json.
Use the ecma:publish or pnpm exec mono publish to publish the package.
--access public is always specified.