Including a shared project in create-react-app - node.js

In my project i have the following folder/project structure.
Project
├── client (create-react-app)
│ ├── tsconfig.json
│ └── packages.json
├── server
│ ├── tsconfig.json
│ └── packages.json
└── shared
├── tsconfig.json
└── packages.json
The shared project is mostly interface/class type declarations that i use in both the front and backend so they use the same object type.
I was including the shared project in both client and server using the referenced projects from typescript.
tsconfig.json
...
},
"references": [
{
"path": "../shared"
}
],
Although upon trying to use one of the imported types in a useReducer hook:
Component in client/src
import React, { useReducer, useEffect } from 'react';
import { IRoadBook, GetNewRoadBook} from '../../../shared/src/types/IRoadBook';
import { Grid, TextField, Button } from '#material-ui/core';
import { useParams } from 'react-router-dom';
type State = {
RoadBook: IRoadBook | any;
}
export default function RoadBookEditor() {
const { objectId } = useParams();
const [state, dispatch] = useReducer(reducer, {RoadBook : GetNewRoadBook()});
....
The following error happens upon starting the project :
"Module not found: You attempted to import ../../../shared/src/types/IRoadBook which falls outside of the project src/ directory. Relative imports outside
of src/ are not supported."
Which from what i read is a limitation imposed by create-react-app configurations.
If i do
const [state, dispatch] = useReducer(reducer, {RoadBook :{}});
Code builds/runs successfuly. (causes visual issues down the road but that is not the point of this question).
Question : What is the proper way to include this shared project in my create-react-app client project?
shared/types/IRoadBook.tsx
export interface IRoadBook {
_id: string;
Name: string;
Description: string;
}
export function GetNewRoadBook(): IRoadBook {
return { _id: '', Name: '', Description: '' } as IRoadBook;
}

If I understood correctly what you're looking for is a monorepo setup.
Out of the box create-react-app does not support importing code from outside your source folder. To achieve it you need to modify the webpack config inside it, and there are a couple of alternatives for that:
1- Using something like customize-cra, craco, etc.
2- forking CRA and modifying react-scripts (https://create-react-app.dev/docs/alternatives-to-ejecting/)
3- Ejecting and modifying the webpackconfig
for 2 and 3, you'll need to remove ModuleScopePlugin from webpack config and add the paths to babel-loader.
A few other places you can get a direction:
Create React App + Typescript In monorepo code sharing
https://blog.usejournal.com/step-by-step-guide-to-create-a-typescript-monorepo-with-yarn-workspaces-and-lerna-a8ed530ecd6d

Related

paths in tsconfig cannot import the type

I'm working on server side project using TypeScript.
And I defined some types in ooo.d.ts. And I set paths in tsconfig.json.
But When I try to import the type I defined, It shows the error, Module '"../../#types/express-custom-types"' has no exported member 'CustomError'.
The project structure is like the below.
├── src
│   └── routes
│   └── errorHandlers.ts
├── tsconfig.json
└── #types
└── express-custom-types
└── index.d.ts
I define the types in index.d.ts like the below.
declare module "express-custom-types" {
export type ErrorBody = { type: string; status: number; message: string };
}
And I defined alias in tsconfig.json
"compilerOptions": {
...(omit)...
"baseUrl": "./",
"paths": {
"*": ["*"],
"#custom-types/*": ["#types/*"],
},
And import the type in errorHandlers.ts like this.
import { ErrorBody } from "#custom-types/express-custom-types";
But it shows error Module '"../../#types/express-custom-types"' has no exported member 'ErrorBody'.
I don't know what to do..
declare module ... can be used to add or augment declarations of some external module (usually installed through package.json or generated during the "build").
But this is not the case here, the file/module is part of the project and you can just remove declare module "express-custom-types" wrapper.
You want to make sure that the module you're declaring has the same full name as the one you're trying to import. This should work:
declare module "#custom-types/express-custom-types" {
export type ErrorBody = { type: string; status: number; message: string };
}

Versioning Nestjs routes?

I'm just getting started with Nestjs and am wondering how I can version my API using either a route prefix or through an Express Router instance?
Ideally, I would like to make endpoints accessible via:
/v1
/v2
etc, so I can gracefully degrade endpoints. I'm not seeing where I could add the version prefix. I know it's possible to set a global prefix on the application instance, but that's not for a particular set of endpoints.
Here's an open discussion about the RouterModule https://github.com/nestjs/nest/issues/255. I understand how important this functionality is, so it should appear in the near future. At this point it is necessary to put v1 / v2 directly into the #Controller() decorator.
Router Module comes to rescue, with Nest RouterModule it's now a painless organizing your routes.
See How it easy to setup.
const routes: Routes = [
{
path: '/ninja',
module: NinjaModule,
children: [
{
path: '/cats',
module: CatsModule,
},
{
path: '/dogs',
module: DogsModule,
},
],
},
];
#Module({
imports: [
RouterModule.forRoutes(routes), // setup the routes
CatsModule,
DogsModule,
NinjaModule
], // as usual, nothing new
})
export class ApplicationModule {}
this will produce something like this:
ninja
├── /
├── /katana
├── cats
│ ├── /
│ └── /ketty
├── dogs
├── /
└── /puppy
and sure, for Versioning the routes you could do similar to this
const routes: Routes = [
{
path: '/v1',
children: [CatsModule, DogsModule],
},
{
path: '/v2',
children: [CatsModule2, DogsModule2],
},
];
Nice !
check it out Nest Router
for version or any prefix, you can use "global prefix":
https://docs.nestjs.com/faq/global-prefix
according to latest docs inside main.ts after const app = await NestFactory.create(AppModule) use
// Versioning
app.enableVersioning({
type: VersioningType.URI,
defaultVersion: '1',
prefix: 'api/v',
});
This will give all your routes a default prefix of /api/v1 unless specified
To override version in controller use
#Controller({version:'2'}) decorator on controller class
For overriding version at route level use #Version('2') above controller method
Note: If you are using swagger make sure to call app.enableVersioning() before SwaggerModule.createDocument()
Link: https://docs.nestjs.com/techniques/versioning
Best and simple way to do this is using global prefix
An example is given below :
import { VersioningType } from "#nestjs/common";
app.enableVersioning({
type: VersioningType.URI,
});
app.setGlobalPrefix("api/v1"); //edit your prefix as per your requirements!
You can exclude routes from the global prefix using the following construction:
app.setGlobalPrefix('v1', {
exclude: [{ path: 'health', method: RequestMethod.GET }], // replace your endpoints in the place of health!
});
Alternatively, you can specify route as a string (it will apply to every request method):
app.setGlobalPrefix('v1', { exclude: ['cats'] }); // replace your endpoints in the place of cats!

Warning: error TS18002: The 'files' list in config file is empty

I'm using TypeScript 2.1.5.0. I've configured the grunt-typescript-using-tsconfig plugin as shown below but I get the error in the subject line when I execute the task.
The problem is the tsconfig.json property "files":[]. I didn't encounter this error when using gulp-typescript. Do you recommend that I configure something differently? Either my gruntfile.js for this plugin or tsconfig.json? Or can you recommend a different grunt plugin that will successfully hook into tsconfig.json and process the typescript task as expected?
typescriptUsingTsConfig: {
basic: {
options: {
rootDir: "./tsScripts"
}
}
}
Or can you recommend a different grunt plugin that will successfully hook into tsconfig.json and process the typescript task as expected?
gulp typescript supports tsconfig : https://github.com/ivogabe/gulp-typescript/#using-tsconfigjson
var tsProject = ts.createProject('tsconfig.json');
gulp.task('scripts', function() {
var tsResult = gulp.src(tsProject.src())
.pipe(tsProject());
return tsResult.js.pipe(gulp.dest('release'));
});
Try setting your Gruntfile.js configuration as shown in the following gist :
Gruntfile.js
module.exports = function(grunt) {
grunt.initConfig({
typescriptUsingTsConfig: {
basic: {
options: {
rootDir: './'
}
}
}
});
grunt.loadNpmTasks('grunt-typescript-using-tsconfig');
grunt.registerTask('default', [
'typescriptUsingTsConfig'
]);
};
Note the value for rootDir is set to ./ (i.e. The same folder as the Gruntfile.js).
tsconfig.json
Then ensure you have your tsconfig.json configured to include a list of all .ts files to be compiled to .js. For example:
{
"compilerOptions": {
"outDir": "./dist"
},
"files": [
"./tsScripts/a.ts",
"./tsScripts/b.ts",
"./tsScripts/c.ts"
]
}
There are of course other compiler options you can set in tsconfig.json
Directory structure
The configurations above assumes a directory structured as follows, however you can just adapt the code examples as required:
foo
├── Gruntfile.js
├── tsconfig.json
├── tsScripts
│ ├── a.ts
│ ├── b.ts
│ └── c.ts
└── node_modules
└── ...
Running grunt
cd to the project folder, (in these examples the one named foo), and run:
$ grunt
Output
Running grunt will create a folder named dist and output all .js files to it. For example:
foo
├── dist
│ ├── a.js
│ ├── b.js
│ └── c.js
└── ...
If you want the resultant .js files to be output to the same folder as the source .ts file, (i.e. not to the 'dist' folder), just exclude the "outDir": "./dist" part from your ts.config.json.

Aliasing modules using NodeJS

Some context here: It's not that I cannot use Webpack, it's that I do not want to use Webpack. I would like to keep everything as "vanilla" as possible.
Currently when creating modules in a project you have to require them using either a relative or absolute path, for example in the following directory..
project/
├── index.js
├── lib/
│ ├── network/
│ │ request.js
│ │ response.js
├── pages/
│ ├── foo.js
Considering we're in index.js we would import request via
var networkRequest = require('./lib/network/request.js')
and if we're in foo.js we would import request via
var networkRequest = require('../lib/network/request.js')
What I'm wondering is that if there's any way to perhaps, set a local alias in Package.json or anywhere else like so:
localPackages = [
{ name: 'network-request', path: './lib/network/request.js' }
];
In which you could just do
var networkRequest = require('network-request')
From any file and it will provide the correct path.
Yep, that's what npm link is for. Native and out of the box.
You can also set local paths in package.json
{
"name": "baz",
"dependencies": {
"bar": "file:../foo/bar"
}
}

Importing typescript from external node modules

I want to split my application into different node modules and have a main module which builds all other modules as well and I want to use typescript with es6 modules.
Here is my planned project structure:
main
node_modules
dep-a
dep-b
framework
interfaces
IComponent.ts
dep-a
components
test.ts
node_modules
framework
index.ts
dep-b
node_modules
framework
I want to be able to define interfaces in framework which can be consumed in dep-a, dep-b and main.
How do I set up this correctly? Can I compile everything from my main-module? Do I need to create different bundles for framework, dep-a, ... and another typing file? What is the best approach for this?
I already set up some test files and folders and used npm link to link the dependencies and webpack to bundle the files and I am always running into issues with files not being found:
error TS2307: Cannot find module 'framework/interfaces/IComponent'
and
Module not found: Error: Cannot resolve 'file' or 'directory' ./components/test
TL;DR generate declarations for the modules using declaration: true in tsconfig.json and specify the file for your generated typings in the typings entry of the package.json file
framework
Use a tsconfig file similar to this:
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"declaration": true,
"noImplicitAny": true,
"removeComments": true,
"outDir": "dist",
...
},
"files": [
...
]
}
The important bit is declaration: true which will generate internal declarations in the dist directory
Assuming there is an index.ts file which (re)exports all the interesting parts of framework, create a package.json file with a main and typings entry pointing to, respectively, the generated js and the generated declaration, i.e.
{
"name": "framework",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
...
}
Commit this module to a git repo, say bitbucket at : "https://myUser#bitbucket.org/myUser/framework.git"
dep-a
in package.json create a dependency to framework
{
"dependencies": {
"framework": "https://myUser#bitbucket.org/myUser/framework.git"
},
}
That is it.
import * from 'framework'
will pull the dependency with the typings, automatically
Obviously, it is possible to do with dep-a what was done with framework i.e. generate the declarations, update package.json and use dep-a as a module with embedded typings in main
note: a file URL will do in package.json/dependencies if you do not want go to via an external git repo
What arrived in TypeScript 1.6 is typings property in package.json module. You can check the relevant issue on GitHub.
So assuming you want to create separate modules ( dep-a, framework ). You can do the following :
main.ts // (1)
package.json // (2)
node_modules/
dep_a/
index.js // (3)
index.d.ts // (4)
package.json // (5)
node_modules/
framework/
index.js // (6)
index.d.ts // (7)
package.json // (8)
So let's see what you have in your files :
//(1) main.ts
import * as depA from "depA";
console.log(depA({ a : true, b : 2 }) === true) // true;
//(2) package.json
{
name: "main",
dependencies: {
"dep_a" : "0.0.1"
}
...
}
For depA
//(3) dep_a/index.js
module.exports = function a(options) { return true; };
//(4) dep_a/index.d.ts;
import * as framework from "framework";
export interface IDepA extends framework.IFramework {
a : boolean
}
export default function a(options: IDepA) : boolean;
//(5) dep_a/package.json
{
name: "dep_a",
dependencies: {
"framework" : "0.0.1"
},
...
typings : "index.d.ts" // < Magic happens here
}
For framework
//(6) dep_a/node_modules/framework/index.js
module.exports = true // we need index.js here, but we will only use definition file
//(7) dep_a/node_modules/framework/index.d.ts;
export interface IFramework {
b : number;
}
//(8) dep_a/node_modules/framework/package.json
{
name: "framework"
...
typings : "index.d.ts"
}
What I don't include in this answer ( for clarity ) is another compilation phase, so you could actually write the modules ( dep_a, framework ) with typescript and then compile them to index.js before you use them.
For a detailed explanation and some background also see: https://medium.com/#mweststrate/how-to-create-strongly-typed-npm-modules-1e1bda23a7f4

Resources