Resolving relative paths in a serverless Nodejs app in Typescript - node.js

We have a NodeJS app written in typescript. We use modules using relative paths
e.g.
import foo from '#/bar';
We have following paths entry in tsconfig.json
"paths": {
"#/*": [
"./*"
]
}
As typescript does not have path transformation support, we are using ttypescript to compile and override tsconfig.json with tsconfig.build.json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"plugins": [
{
"transform": "#zerollup/ts-transform-paths"
}
]
},
"exclude": ["node_modules/**", "tests/**"]
}
and use yarn to build the code
yarn clean && ttsc --project ./tsconfig.build.json
Now this build the code and converts the relative #module paths to absolute.
Next we are using Serverless framework to build and deploy this code as lambda. Serverless uses tsconfig.json to build the code and effectively ignore the custom path transformation.
Question:
is it possible to solve the above problem without using ttypescript and plugin?
Is it possible to configure serverless to use ttypescript and custom tsconfig
Any other way to solve the problem?
Thanks

Related

Nestjs e2e testing of an application within a monorepo fails to resolve #app import from library with jest despite config in package.json

I'm trying to run e2e tests for a monorepo application that also utilises several libraries from within the monorepo, all imports throughout the application are resolved using "#app" imports, for example import { ConfigService } from "#app/config"
However, when trying to run e2e tests via the command:
"test:public": "jest --config ./apps/public-microservice/test/jest-e2e.json",
Jest throws:
Cannot find module '#app/config' from '../src/public-microservice.module.ts'
I've looked at this demo-repo from #jmcdo29 and can't find anything that is different with my configuration.
I've noticed there was an issue about wrong configurations being generated via jest here in 2019, but it has long been resolved, and my configuration for jest in package.json does indeed mention:
"moduleNameMapper": {
"#app/config/(.*)": "<rootDir>/libs/config/src/$1",
"#app/config": "<rootDir>/libs/config/src",
whilst the local targeted file by the package.json script command only contains:
{
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".",
"testEnvironment": "node",
"testRegex": ".e2e-spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
}
}
Is there something that's missing from my command or my configuration?
Is there anything I need to specify to tell jest to extend the configuration for jest available in package.json?
Any help investingating this is appreciated, thanks.
I added the "moduleNameMapper" key to my jest e2e suite config and updated the "../" relative paths to match where my libs are, in my specific scenario, it looks like this:
"moduleNameMapper": {
"#app/config/(.*)": "<rootDir>../../../libs/config/src/$1",
"#app/config": "<rootDir>../../../libs/config/src",
},

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.

Typescript project setup with shared package

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

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.

External imports in Babel 7 do not get transpiled

I'm currently migrating a codebase from Babel 6 to 7. The code is made up of multiple individual projects with their own configs.
The main project imports files from external however the scripts being imported from external by main aren't being transpiled and fails on "Unexpected token import". Scripts located directly in main do transpile correctly.
I'm using the following command within the main project to transpile the scripts:
babel-node ./index.js
Another project uses Webpack to do the same thing and handles everything correctly.
This setup also worked fine with Babel 6.
.babelrc for main
{
"ignore": [
"node_modules"
],
"presets": [
["#babel/preset-env", {
"targets": {
"node": "current"
},
"useBuiltIns": "entry"
}]
],
"plugins": [
[
"module-resolver", {
"alias": {
"External": "../external"
}
}
],
"#babel/plugin-proposal-decorators",
"#babel/plugin-proposal-object-rest-spread",
"#babel/plugin-proposal-export-default-from",
"#babel/plugin-proposal-export-namespace-from",
"#babel/plugin-proposal-class-properties"
]}
.babelrc for external
{
"presets": [
"#babel/preset-react"
],
"plugins": [
"#babel/plugin-proposal-class-properties",
"#babel/plugin-proposal-object-rest-spread",
"#babel/plugin-transform-runtime"
]}
I've created an example to detail my problem at:
https://gitlab.com/nerdyman/babel-7-external-import-broken
TL;DR I'm trying to import scripts from outside of a project's root directory but they don't get transpiled by Babel, the scripts from within the project do transpile.
I've managed to fix this by following this comment.
The solution is:
Move .babelrc in the main project to babel.config.js and make it a CommonJS module
Add --ignore=node_modules when running babel-node from the main project
This still seems pretty hacky and Babel doesn't seem to acknowledge the ignore property within babel.config.js it must be specified as a flag.
Babel 7 appears to only allow imports within the directory the babel config is in, however explicitly setting the --ignore flag overrides this.
You can view my working demo and the diff of what I changed to get it working.
The issue is still open on GitHub so there may be a better solution in the future.
current directory's .babelrc won't be loaded while import files in external directory, you may place a .babelrc in that directory and set its presets by relative path(instead of short name):
{ "presets": ["..\pad\node_modules\babel-preset-env"],
"retainLines": true }

Resources