Typescript project setup with shared package - node.js

I have a few premises before starting:
I want a shared package which will be included in three other packages,
typescript,
just one node modules,
multiplatform usage (Windows / Linux),
use dependencies from the shared package,
(live reload will be nice),
not to lose the ability to later publish shared package (the other three packages are web servers, but now is under heavy development),
common scripts build:web build:api build:admin from global package.
My dreamed folder structure is:
/
/node_modules/***
/packages/shared/package.json // Shared dependencies like typeorm, lodash etc..
/packages/shared/src/index.ts
/packages/shared/src/models/User.ts
/packages/web/package.json
/packages/web/tsconfig.json
/packages/web/src/index.ts // first express server (using shared code)
/packages/api/package.json
/packages/api/tsconfig.json
/packages/api/src/index.ts
...
/package.json
/tsconfig.common.json
There are a lot of solutions across the internet - Lerna, typescript project reference, npm / yarn link... but I am not able to find a suitable solution...
For example, this https://github.com/Quramy/lerna-yarn-workspaces-example is cool, but I was not able to setup to run something like this:
// package.json
{
"scripts": {
"debug:web": "concurrently \"(cd packages/shared && tsc -w)\" \"(cd packages/web && tsc -w)\" \"(cd packages/web && nodemon --inspect dist/index.js --watch .. /)\""
...
I want to continuously build the shared package (generate dist/index.js and .ts files) and in nodemon restart server after something changed in ../ (packages folder).
After a few hours I gave up to chance to have code reloading and try that via "easiest-way" I could find - no package in shared but include code directly via:
// shared/index.ts
export const createConnection = () => ....
// web/index.ts
import { createConnection } from 'shared'
it looks like it does not work either:
// tsconfig.json
{
"extends": "../../tsconfig.settings.json",
"compilerOptions": {
"rootDir": "../",
"outDir": "dist",
"baseUrl": "./",
"paths": {
"shared": [ "../shared/src/" ]
}
}
}
the problem is now:
File /shared/src/index.ts is not listed within the file list of the project in tsconfig.json. Projects must list all files or use an 'include' pattern.
Next step is (obviously):
{
"extends": "../../tsconfig.settings.json",
"include": [
"./src/**/*.ts",
"../shared/src/**/*.ts"
],
"compilerOptions": {
"rootDir": "../",
"outDir": "dist",
"baseUrl": "./",
"paths": {
"shared": [ "../shared/src/" ]
}
}
}
This works! No errors, but no files in "dist" also... LOL
I am pretty sure, there is some nice solution, example or it won't work either, but I was not able to find it or build it.

Correct me if I'm wrong, but you need an example where the project is separated to packages in a monorepository, written in Typescript and some of the packages depend on each-other.
If that's the case, I created project like this. I used one of Typescript new features: project references. You can specify other tsconfigs in your config file and you can define a dependency for them. For example: package B depends on package A. Typescript is going to compile B and use its declarations before start compile A.
Here is a small example for the tsconfig
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib"
},
"include": ["src"],
"exclude": ["lib"],
"references": [{ "path": "../util" }, { "path": "../crud-request" }]
}
You can get more informations here: https://www.typescriptlang.org/docs/handbook/projectreferences.html
I used this project as an example to build my own, maybe it'll be useful for you too:
https://github.com/nestjsx/crud

Related

How to stop typescript compiling files *above* `../` the current src dir?

I am developing a package within a larger project, but compiling the package also tries to run all tests from further up the hierarchy. Is there any way to stop this?
app
node_modules
packages/
mypackage/
src/
when I run tsc inside mypackage it will look for files above, all the way to my home directory.
Now, I can compile and build outside my app, and all is good, but for reasons of distribution I wanted to compile things directly inside.
I tried with rootDir and exclude but to no avail.
{
"compilerOptions": {
"target": "ES2022",
"module": "commonjs",
"declaration": true,
"outDir": "./dist",
"strict": true,
"rootDir": "./src",
},
"include": [
"./src"
],
"exclude": [
"../../../node_modules",
"node_modules",
"**/node_modules/**",
]
}
the errors look like this:
../../../node_modules/#types/jest/index.d.ts:43:13
43 declare var xit: jest.It;
~~~
'xit' was also declared here.
Found 7 errors in the same file, starting at: ../../../node_modules/#types/mocha/index.d.ts:2642
update found this issue, but not sure if there is a solution. Letting tsc pull in random files from throughout your directory tree, outside the current .src, seems really dangerous behavior to allow TS to do.
https://github.com/microsoft/TypeScript/issues/9858
similar

Should I use commonjs or es module for yarn workspace sub packages for next.js proejct?

I'm building Next.js monorepo project with TS, yarn workspace.
For example, I have two packages in yarn workspace, /web and /api. /web is a next.js project and /api is a shared subpackage that is used by /web.
/my-project <-- project root
package.json
/web
src/
package.json
tsconfig.json
next.config.js
/api
src/ <-- rootDir
dist/ <-- outDir
package.json
tsconfig.json
...
// /my-project/package.json
{
"private": true,
"workspaces": [
"web",
"api"
],
}
// /web/packcage.json
{
"dependencies": {
"#api": "workspace:*"
}
}
// /api/packcage.json
{
"name": "#api"
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
}
// /api/tsconfing.json
{
"compilerOptions": {
"outDir": "dist",
"rootDir": "src",
"module": "ES6",
"moduleResolution": "node",
"target": "ES6",
},
}
From /api's tsconfig, TS creates transpiled result which has es6 module system.("module": "ES6")
As nextjs does not support external packages built with ES module, I expected that /api package does not work in /web project. However, it works well.
Why this can be possible?
When I tried to use some packages which use ES module(built only for browser), I met some errors something like unexpected token: export. At that time, I have to transpile them
manually by using next-transpile-modules and then it resolved the issue. But, in this case, nextjs work with es module package without any issue. Did I misunderstand something about this?
NextJs 11.1+ has an experimental option to support esm. The feedback thread is available here.
/**
* #type {import('next').NextConfig}
*/
const nextConfig = {
experimental: {
// Prefer loading of ES Modules over CommonJS
// #link {https://nextjs.org/blog/next-11-1#es-modules-support|Blog 11.1.0}
// #link {https://github.com/vercel/next.js/discussions/27876|Discussion}
esmExternals: true,
// Experimental monorepo support
// #link {https://github.com/vercel/next.js/pull/22867|Original PR}
// #link {https://github.com/vercel/next.js/discussions/26420|Discussion}
externalDir: true,
}
}
export default nextConfig;
ESM generally requires more than just "modules": "es6" in config... But as you noticed, I realized that some esm packages like '#sindrehorsus' ones where working without the experimental flag.
That said setting the experimental.esmExternals to true will solve most edge cases and should makes next-transpile-modules obsolete.
Note that generally I tend to set "modules" to "esnext" rather than "es6" to be inline to what nextjs recommends.
PS: If it helps, you can have a look this comment: https://stackoverflow.com/a/69554480/5490184 or the https://github.com/belgattitude/nextjs-monorepo-example.

Absolute path in the tsconfig doesn't work

I saw some questions about this problem, none of them does not work
I have a nodejs project along with Typescript. I do not like to use a relative path.I get the following error, when I set path in tsconfig :
Cannot find module '#app/controllers/main'
// main.ts
export const fullName = "xxxx";
...
// app.ts
import { fullName } from '#app/controllers/main'
...
This is the structure of my project :
-node_modules
-src
----controllers
---------------main.ts
----app.ts
-package.json
-tsconfig.json
tsconfig:
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"strict": true,
"baseUrl": ".",
"paths": {
"#app/*": ["src/*"]
},
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
Where is my problem?
Thanks in advance.
Update 2023
Install development dependencies
npm install --save-dev \
ts-patch \
typescript-transform-paths \
tsconfig-paths
ts-patch
Directly patch typescript installation to allow custom transformers (plugins).
The main difference why I prefer ts-patch over ttypescript is that there is no need to change the compiler (ttsc) because (hence the name) tsc is patched.
typescript-transform-paths
Transforms absolute imports to relative from paths in your tsconfig.json.
tsconfig-paths
Load modules whose location is specified in the paths section of tsconfig.json. Both loading at run-time and via API are supported.
Update tsconfig.json
Note: See paths and plugins
{
"compilerOptions":{
/* A series of entries which re-map imports to lookup locations relative to the baseUrl */
"paths":{
"~/*":[
"./src/*"
]
},
/* List of language service plugins */
"plugins":[
/* Transform paths in output .js files */
{
"transform":"typescript-transform-paths"
},
/* Transform paths in output .d.ts files */
{
"transform":"typescript-transform-paths",
"afterDeclarations": true
}
]
}
}
Patch Typescript
Note: This is NOT persistent
npx ts-patch install
Edit/Add prepare script in package.json to patch Typescript persistently
Note: This IS persistent
{
// ...
"scripts": {
"prepare": "npx ts-patch install -s"
}
}
Usage in import
import { hello } from '~/world';
Compile as always
npx tsc
Old Answer
Unfortunately (and I don't know why) the Typescript compiler currently does not support the paths transformation very well.
Here is my solution:
I used the solution with this project.
Install devDependencies
ttypescript -> npm install ttypescript --save-dev -> TTypescript (Transformer TypeScript) solves the problem by patching on the fly the compile module to use transformers from tsconfig.json.
typescript-transform-paths -> npm install typescript-transform-paths --save-dev -> Transforms absolute imports to relative from paths in your tsconfig.json.
tsconfig-paths -> npm install tsconfig-paths --save-dev -> Use this to load modules whose location is specified in the paths section of tsconfig.json. Both loading at run-time and via API are supported.
ts-node-dev -> npm install ts-node-dev --save-dev -> It restarts target node process when any of required files changes (as standard node-dev) but shares Typescript compilation process between restarts
tsconfig.json
Update the tsconfig.json file with the following options:
{
"compilerOptions": {
/* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
"paths": {
"#app/*": [
"./src/*"
]
},
/* Advanced Options */
"plugins": [
{
"transform": "typescript-transform-paths"
}
],
}
}
Build
For the compilation phase use ttsc instead of tsc with the configuration file. See the snippet below:
npx ttsc --p ./tsconfig.json
Development mode with autoreload
When you are in dev mode use the following script (put it in the scripts options in package.json) to automatically reload the project with the correct paths. The src/app.ts is the "entry point" of your application located under the src folder.
npx ts-node-dev --prefer-ts true --no-notify -r tsconfig-paths/register --watch src --transpileOnly src/app.ts
PS: Using ts-node-dev increase the speed significantly.
I tweak #Carlo Corradini answer a little bit,
here is my approach to fix this issue.
install required plugins:
npm i -D ttypescript typescript-transform-paths ts-node tsconfig-paths. these are packages that will help us to transform the paths.
ttypescript-> https://www.npmjs.com/package/ttypescript
then, on tsconfig.json, I put:
{
"ts-node": {
"transpileOnly": true,
"require": [ // set this so you dont need to use ts-node -r
"typescript-transform-paths/register",
"tsconfig-paths/register"
]
},
"compilerOptions": {
"composite": true,
"rootDir": ".", // must define
"baseUrl": "src", // must define, the paths will relative to this
"outDir": "lib",
"skipLibCheck": false,
"paths": {
"#app/*": ["./*"],
"#controllers/*": ["./controllers/*"],
},
"plugins": [
{ "transform": "typescript-transform-paths" }
]
}
}
then on your codes you can use:
import myControllers from "#controllers/main.ts"
now you can compile, npx ttsc -p tsconfig.json
and to run on ts-node use npx ts-node -p tsconfig.json src/app.ts
Your syntax is correct.
It appears that, at the time of writing, ts-node-dev does not support the paths entry of tsconfig.json.
This github issue discusses the problem and presents workaround options.

Use non-relative imports using TypeScript's baseurl property without WebPack

I am writing a node web application using TypeScript and Express.
I managed to get everything working, however the issue I have run into is that my imports don't seem to respect the baseUrl option of my tsconfig.json.
Here is how my tsconfig.json looks like:
{
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "node",
"pretty": true,
"sourceMap": true,
"target": "es6",
"outDir": "./dist",
"baseUrl": "./src"
},
"exclude": [
"node_modules"
]
}
As an example, let's say I have the following files:
- dist/
- a/
- car.js
- b/
- hat.js
In car.js I can easily require hat.js by doing:
import hat from '../../b/hat'; // relative version
This works as expected.
However, I also want to be able to do the following:
import hat from 'b/hat'; // absolute version
This does not generate any issues during compilation or shows any IDE errors, as the tsconfig.json specifies the baseUrl as ./src. Thus the above is perfectly valid TypeScript code.
However, my expectation was that the code will compile down to the relative version:
const hat = require('../../b/hat');
Unfortunatly it compiled down to:
const hat = require('b/hat');
and thus predictably does not work.
Other users have solved this issue by using 3rd party tools such as: https://github.com/s-panferov/awesome-typescript-loader
https://decembersoft.com/posts/say-goodbye-to-relative-paths-in-typescript-imports/
But majority of these tools is designed to work with WebPack, which isn't really suitable for an node back-end application. This is because we are running a long-running server, and thus won't benefit from being bundled into a single file versus several different files (unlike front-end web development).
My question is, how can I compile my TypeScript files, without WebPack, so that absolute imports works correctly.
So the way to do it is to transform back the non-relative imports to relative imports after build so that commonjs will recognize them. And the way to easily do it is the following:
Step 1: Install the transform modules:
npm i -D typescript-transform-paths
npm i -D ttypescript
Step 2: Modify tsconfig.json
"compilerOptions: {
...
"plugins": [
{ "transform": "typescript-transform-paths" },
{ "transform": "typescript-transform-paths", "afterDeclarations": true }
]
}
Step 3: Modify package.json
We will use ttypescript to build the project instead of tsc inorder to use transformers.
"build": "ttsc"
Step 4: Build and Start the project
npm run build
npm start
OP I haven't gotten this to work yet, but this article may provide some guidance: https://levelup.gitconnected.com/get-rid-of-relative-import-path-hell-in-your-typescript-project-9952adec2e84
TL;DR
yarn add link-module-alias
package.json
...
"_moduleAliases": {
"~": "dist"
}
...
My question is, how can I compile my TypeScript files, without WebPack, so that absolute imports works correctly.
My opinion
Don't. In fact don't do it even do it with webpack.
Reason
The node resolution algorithm is complicated enough : https://nodejs.org/api/modules.html#modules_all_together and doesn't need to be complicated further with "and oh, now we have baseurl".
If you still want to do it, here are your options.

Publish Typescript classes as npm package

I have written a set of Typescript classes that I am publishing as an NPM module to our internal package server. My other projects are able to retrieve this dependency using NPM.
I'd like to publish the classes in such a way that I can import them by referencing the package itself, similar to some other packages that I am using like Angular.
import { SomeClass } from 'mylibrary'
This is my current setup:
I've written a ./mylibrary.d.ts file:
export * from './dist/foldera/folderb'
//etc
./package.json:
"name": "mylibrary",
"main": "mylibrary.d.ts",
"files": [
"mylibrary.d.ts",
"dist/"
],
"types": "mylibrary.d.ts",
//ommitted other settings
src/tsconfig.json:
{
"compileOnSave": false,
"compilerOptions": {
"outDir": "../dist/",
"baseUrl": "src",
"sourceMap": true,
"declaration": true,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es5",
"typeRoots": [
"node_modules/#types"
],
"lib": [
"es2016",
"dom"
]
}
}
When I try to import the package using the desired syntax, MS VS Code says everything is ok, but running my project results in a rather cryptic error message:
Module build failed: Error: Debug Failure. False expression: Output generation failed
I'm feeling lost in the jargon of modules, namespaces and packages. Any help is greatly appreciated.
If you place an index.ts file in the root folder of your package (same level as package.json), so that it re-exports all the functionality from your src/ folder, than you can publish it without building to js, keeping in mind that it will only be used in TS projects.
The negative side of this approach is that, your package will be compiled with your project every time, and possible changes in TS environment may lead to inability to compile the whole project (I have problems compiling one of my packages on versions < 2.4.2, for example).
// index.ts
export { Class1 } from 'src/Class1'
export { Class2 } from 'src/Class2'

Resources