I have a node application that compiles typescript files to a dist folder and then serves these files as lambda resolvers via aws cdk. Here is an example of my setup:
The code
register.ts
import ValidateUserFields from '../utils/forms';
exports.main = async function (event: any, context: any) {
return {
statusCode: 200,
};
}
register-lambda-config.ts
import { Construct } from 'constructs';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as s3 from 'aws-cdk-lib/aws-s3';
export class FrontendService extends Construct {
constructor(scope: Construct, id: string) {
super(scope, id);
const api = new apigateway.RestApi(this, 'frontend-api', {
restApiName: 'Frontend Service',
description: 'This service serves the frontend.',
});
const functionName = 'register';
const handler = new lambda.Function(this, functionName, {
functionName,
runtime: lambda.Runtime.NODEJS_14_X,
code: lambda.Code.fromAsset('dist/src/lambda'),
handler: 'register.main',
});
const registerIntegration = new apigateway.LambdaIntegration(handler, {
requestTemplates: { 'application/json': '{ "statusCode": "200" }' },
});
const registerResource = api.root.addResource('register');
registerResource.addMethod('POST', registerIntegration);
}
}
tsconfig.json
{
"compilerOptions": {
"target": "ES2018",
"module": "commonjs",
"moduleResolution": "node",
"lib": ["es2018"],
"declaration": true,
"strict": true,
"noImplicitAny": false,
"strictNullChecks": true,
"noImplicitThis": true,
"alwaysStrict": true,
"esModuleInterop": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": false,
"inlineSourceMap": true,
"inlineSources": true,
"experimentalDecorators": true,
"strictPropertyInitialization": false,
"outDir": "dist",
"typeRoots": ["./node_modules/#types"]
},
"exclude": ["node_modules", "cdk.out", "./dist/**/*"]
}
And finally here is the script part of my package.json file:
"scripts": {
"build": "tsc",
"watch": "tsc -w",
"cdk": "cdk",
"bootstrap": "cdk bootstrap",
"deploy": "cdk deploy && rimraf cdk.out",
"destroy": "cdk destroy",
"run-same-local-fe-api": "sam local start-api -p 4000 -t ./template.yaml",
"dev": "npm run build && npm run synth && concurrently --kill-others \"npm run watch\" \"npm run run-same-local-fe-api\"",
"synth": "cdk synth --no-staging > template.yaml"
},
The problem
When I run npm run dev it compiles my typescript files to the dist folder in the same structure as what I have in my src folder (where all my typescript files live). I however run into the following error if I have any imports in my register.ts file:
{"errorType":"Runtime.ImportModuleError","errorMessage":"Error: Cannot
find module '../utils/forms'\nRequire stack:\n-
/var/task/register.js\n- /var/runtime/UserFunction.js\n-
/var/runtime/index.js","stack":["Runtime.ImportModuleError: Error:
Cannot find module '../utils/forms'","Require stack:","-
/var/task/register.js","- /var/runtime/UserFunction.js","-
/var/runtime/index.js"," at _loadUserApp
(/var/runtime/UserFunction.js:202:13)"," at
Object.module.exports.load (/var/runtime/UserFunction.js:242:17)","
at Object. (/var/runtime/index.js:43:30)"," at
Module._compile (internal/modules/cjs/loader.js:1085:14)"," at
Object.Module._extensions..js
(internal/modules/cjs/loader.js:1114:10)"," at Module.load
(internal/modules/cjs/loader.js:950:32)"," at Function.Module._load
(internal/modules/cjs/loader.js:790:12)"," at
Function.executeUserEntryPoint [as runMain]
(internal/modules/run_main.js:75:12)"," at
internal/main/run_main_module.js:17:47"]}
This happens for imports from relative local files (like '../utils/forms' as shown in the code above) but also for imports from node_modules. When I look into the compiled register.js file in the dist folder I see that it has made an attempt to parse the import:
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const forms_1 = __importDefault(require("../utils/forms"));
const bucketName = process.env.BUCKET;
exports.main = async function (event, context) { ...
however it shows the error message above. I have tried using require instead of import but it was the same result...
Any help would be greatly appreciate! Thanks
Stated that this is really hard to answer without a minimal reproducible example; I would at least suggest to avoid any require and exports, and to use only import / export statements and following in tsconfig.json.
{
"compilerOptions": {
"module": "esnext"
}
}
Well.. I do understand that you want your main function to look something like this:
// final result written in javascript
exports.main = async function (event, context) {
return {
statusCode: 200,
};
}
But... using module.exports in Typescript is not the way to achieve that. Instead, Typescript using export directive (no s at the end of it) to define which parts of your code should be export. It's then up to your tsconfig.json file to determine which syntax will be used in order to represent this export (this is actually a part of Typescript engine)
So... a script written like this in Typescript
export async function main(event: any, context: any) {
return {
statusCode: 200,
};
}
Will be parse in Typescript as follow (I've used module: commonjs to achieve below result)
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.main = void 0;
async function main(event, context) {
return {
statusCode: 200,
};
}
exports.main = main;
//# sourceMappingURL=test.js.map
Please note how the resulted js file correctly use modile.exports and main as you intended
In short: when using Typescript, please use the language directives and let the engine to do the rest for you. This way - a single source of code can be deployed for different environment without requireing changing your app logic. Neat!
Related
I use CDK to develop my serverless application in AWS. When I try to deploy a lambda function post after TS file compilation, its not importing TS modules in lambda function as JS modules.
Due to that, I am facing an Module not found error when I invoke my lambda.
The steps that I follow before I deploy the stack:
tsc -> to complile TS files
cdk synth
cdk deploy
Ts config:
{
"compilerOptions": {
"target": "ES2018",
"module": "commonjs",
"lib": [
"es2018"
],
"noEmit": false,
"declaration": false,
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": false,
"inlineSourceMap": true,
"inlineSources": true,
"experimentalDecorators": true,
"strictPropertyInitialization": false,
"typeRoots": [
"./node_modules/#types"
]
},
"exclude": [
"node_modules",
"cdk.out"
]
}
Code:
import { APIGatewayProxyEventV2, APIGatewayProxyResultV2 } from 'aws-lambda';
import { DynamoDBClient, GetItemCommand, GetItemCommandInput } from "#aws-sdk/client-dynamodb";
import { marshall, unmarshall } from "#aws-sdk/util-dynamodb";
export async function handler(event: APIGatewayProxyEventV2,): Promise<APIGatewayProxyResultV2> {
console.log('event', event);
let userLat = event.pathParameters?.lat;
let userLng = event.pathParameters?.lng;
let tableName = process.env.TABLE_NAME;
let results;
const dbClient: DynamoDBClient = new DynamoDBClient({ region: "ap-south-1" });
let params: GetItemCommandInput;
if (tableName) {
params = {
TableName: tableName,
Key: marshall({
"lat": userLat,
"lng": userLng
}),
};
}
const run = async function () {
try {
const resp = await dbClient.send(new GetItemCommand(params));
results = unmarshall(resp.Item || {});
} catch(err) {
results = err;
}
};
run();
return {
body: JSON.stringify(
results
),
statusCode: 200
}; }
Details:
Node: v14.20.0
NPM: 6.14.17
CDK: 2.40.0 (build 56ba2ab)
Ts: Version 4.8.2
Error :
{
"errorType": "Runtime.ImportModuleError",
"errorMessage": "Error: Cannot find module '#aws-sdk/client-dynamodb'\nRequire stack:\n- /var/task/fetchpartner.js\n- /var/runtime/UserFunction.js\n- /var/runtime/Runtime.js\n- /var/runtime/index.js",
"trace": [
"Runtime.ImportModuleError: Error: Cannot find module '#aws-sdk/client-dynamodb'",
"Require stack:",
"- /var/task/fetchpartner.js",
"- /var/runtime/UserFunction.js",
"- /var/runtime/Runtime.js",
"- /var/runtime/index.js",
" at _loadUserApp (/var/runtime/UserFunction.js:221:13)",
" at Object.module.exports.load (/var/runtime/UserFunction.js:279:17)",
" at Object. (/var/runtime/index.js:43:34)",
" at Module._compile (internal/modules/cjs/loader.js:1085:14)",
" at Object.Module._extensions..js (internal/modules/cjs/loader.js:1114:10)",
" at Module.load (internal/modules/cjs/loader.js:950:32)",
" at Function.Module._load (internal/modules/cjs/loader.js:790:12)",
" at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:75:12)",
" at internal/main/run_main_module.js:17:47"
]
}
You can run npm init in the directory that contains your lambda code and install the modules you want there. Another way is to use something like webpack to compile and combine your code and modules into a single file.
You can use the NodeJsFunction instead of the Lambda function. That will do an esbuild in the directory before deploying.
https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_lambda_nodejs.NodejsFunction.html
https://github.com/schuettc/single-stack-full-stack-example/blob/9c5c3f51381a4c3d813ed097a298fccc16e81509/src/infrastructure.ts#L38-L51
I have a node application that compiles typescript files to a dist folder and then serves these files as lambda resolvers via aws cdk. Here is an example of my setup:
The code
register.ts
import ValidateUserFields from '../utils/forms';
export async function main(event: any, context: any) {
return {
statusCode: 200,
};
}
register-lambda-config.ts
import { Construct } from 'constructs';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as s3 from 'aws-cdk-lib/aws-s3';
export class FrontendService extends Construct {
constructor(scope: Construct, id: string) {
super(scope, id);
const api = new apigateway.RestApi(this, 'frontend-api', {
restApiName: 'Frontend Service',
description: 'This service serves the frontend.',
});
const functionName = 'register';
const handler = new lambda.Function(this, functionName, {
functionName,
runtime: lambda.Runtime.NODEJS_14_X,
code: lambda.Code.fromAsset('dist/src/lambda'),
handler: 'register.main',
});
const registerIntegration = new apigateway.LambdaIntegration(handler, {
requestTemplates: { 'application/json': '{ "statusCode": "200" }' },
});
const registerResource = api.root.addResource('register');
registerResource.addMethod('POST', registerIntegration);
}
}
tsconfig.json
{
"compilerOptions": {
"target": "ES2018",
"module": "commonjs",
"moduleResolution": "node",
"lib": ["es2018"],
"declaration": true,
"strict": true,
"noImplicitAny": false,
"strictNullChecks": true,
"noImplicitThis": true,
"alwaysStrict": true,
"esModuleInterop": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": false,
"inlineSourceMap": true,
"inlineSources": true,
"experimentalDecorators": true,
"strictPropertyInitialization": false,
"outDir": "dist",
"typeRoots": ["./node_modules/#types"]
},
"exclude": ["node_modules", "cdk.out", "./dist/**/*"]
}
The problem
When I run npm run dev it compiles my typescript files to the dist folder in the same structure as what I have in my src folder (where all my typescript files live). I however run into the following error if I have any imports in my register.ts file:
{"errorType":"Runtime.ImportModuleError","errorMessage":"Error: Cannot
find module '../utils/forms'\nRequire stack:\n-
/var/task/register.js\n- /var/runtime/UserFunction.js\n-
/var/runtime/index.js","stack":["Runtime.ImportModuleError: Error:
Cannot find module '../utils/forms'","Require stack:","-
/var/task/register.js","- /var/runtime/UserFunction.js","-
/var/runtime/index.js"," at _loadUserApp
(/var/runtime/UserFunction.js:202:13)"," at Object.module.exports.load
(/var/runtime/UserFunction.js:242:17)"," at Object.
(/var/runtime/index.js:43:30)"," at Module._compile
(internal/modules/cjs/loader.js:1085:14)"," at
Object.Module._extensions..js
(internal/modules/cjs/loader.js:1114:10)"," at Module.load
(internal/modules/cjs/loader.js:950:32)"," at Function.Module._load
(internal/modules/cjs/loader.js:790:12)"," at
Function.executeUserEntryPoint [as runMain]
(internal/modules/run_main.js:75:12)"," at
internal/main/run_main_module.js:17:47"]}
This happens for imports from relative local files (like '../utils/forms' as shown in the code above) but also for imports from node_modules. When I look into the compiled register.js file in the dist folder I see that it has made an attempt to parse the import:
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const forms_1 = __importDefault(require("../utils/forms"));
const bucketName = process.env.BUCKET;
exports.main = async function (event, context) { ...
however it shows the error message above. I have tried changing the compiler options in tsconfig to "module": "esnext" but this introduces other errors...
Here is a link to the repo: https://easyupload.io/ulnmo4
just do npm i then npm run dev. Then import the following template into Postman to reproduce error: https://pastebin.com/vr3MhR5Q
Any help would be greatly appreciated.
TodoAppUI.js:15 Uncaught ReferenceError: exports is not defined
I have the export keyword in every class.
For example:
export class mysclass {
public constructor(){}
}
I'm using this syntax to import but still not working
import TodoAppUI = require("./TodoAppUI");
This is my config file:
{
"compilerOptions": {
"module": "commonjs",
"noImplicitAny": true,
"removeComments": true,
"preserveConstEnums": true,
"sourceMap": true,
"target": "es5"
},
"files": [
"TodoApp.ts",
"BaseService.ts",
"TodoAppUI.ts"
]
}
Here is the code that throws the error:
Object.defineProperty(exports, "__esModule", { value: true });
module: 'commonjs' in your config file tells compiler to compile the codes to an commonjs module in which you get a module like const TodoAppUI = require("./TodoAppUI") . If you want to use import, then fix the configFile with module: 'ES6'
If I have a TypeScript module saved as my-function.ts as follows :
export function myFunction (param: number): number { return param }
This will be compiled to JavaScript in whichever way and loose its type definitions. I am then able to create a index.d.ts file which declare this module's definitions, but this seems a bit tedious to redefine/redeclare the definitions.
Are there ways to generate the type definitions automatically from the my-function.ts file to a index.d.ts file?
If you compile with the --declaration flag, TypeScript will automatically generate .d.ts files for you.
This mode will require that you certain types are visible so that they can be described in your .d.ts files.
Here's how I managed to solve it:
Creating the infra
Create an new Node package with typescript for the infra.
Inside the new package, make sure to configure a tsconfig.json with declaration:true Doing so will cause typescript to generate definition files which can be consumed by the users of this infra.
My tsconfig.json:
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"declaration": true,
"outDir": "./tsOutputs"
},
"include": [
"lib/**/*.ts",
"index.ts"
],
"exclude": [
"test/**/*.ts"
]
}
Create a an "index.ts" file which will export the public API of the infra.
Note: In order to be able to cast & create instances of objects, you need to have two different exports per each entity. Once as type and another as const.
Here's my index.ts:
import {HttpClient as HC} from "./lib/http/http-client";
import {HttpRequest as HReq, HttpResponse as HRes} from "./lib/http/contracts";
export namespace MyJsInfra {
export type HttpClient = HC;
export namespace Entities {
export type HttpRequest = HReq;
export const HttpRequest = HReq;
export type HttpResponse = HRes;
export const HttpResponse = HRes;
}
}
You can read more info on the reasoning behind this dual declaration in here:
https://github.com/Microsoft/TypeScript/issues/10058#issuecomment-236458961
After all of the following, when we'll run build we should have the corresponding "*.d.ts" files per each type. Now we have to handle the package.json of the infra, in order to pack all the items.
Inside the package.json make sure to set the types, main to point to the generated index.d.ts & index.js files.
In addition, you have to make sure that the "*.d.ts" files are being packaged as part of infra. In my case, I've specified the following pattern in the files property: "tsOutputs/**/*.d.ts"
Here's my package.json:
{
"name": "my-js-infra",
"version": "1.0.0",
"description": "Infrastructure code.",
"scripts": {
"build":"./node_modules/.bin/tsc -p .",
"prepublish":"npm run build",
},
"homepage": "https://github.com/Nadav/My.JS.Infra#readme",
"devDependencies": {
...
"typescript": "^2.4.2",
...
},
"dependencies": {
...
"needle": "^1.4.2",
...
},
"files": [
"tsOutputs/**/*.js",
"tsOutputs/**/*.d.ts",
"tsOutputs/index.d.ts"
],
"types":"tsOutputs/index.d.ts",
"main":"tsOutputs/index.js"
}
All done. Now you can publish your common code.
Consuming the code
Install the infra. In our case the user have to use: npm install my-js-infra --save
Modify the tsconfig.json of the consuming application to load the modules using the Node module resolution. You do so by setting moduleResolution:true inside the file.
Here's my tsconfig.json:
{
"compilerOptions": {
"target": "es5",
"lib": ["es5", "es6"],
"module": "umd",
"sourceMap": true,
"watch": false,
"outDir": "./tsOutputs",
"moduleResolution":"node" /* This must be specified in order for typescript to find the my-js-infra. Another option is to use "paths" and "baseUrl". Something like:
...
"baseUrl": ".", // This must be specified if "paths" is used.
"paths":{
"my-js-infra":["node_modules/my-js-infra/tsOutputs/index.d.ts"]
}
...
*/
}
}
You can read more on module resolution in Typescript in here:
https://www.typescriptlang.org/docs/handbook/module-resolution.html
Start using the code. For example:
import {MyJsInfra } from "my-js-infra";
public doMagic(cmd, callback) {
try {
var request: MyJsInfra.Entities.HttpRequest = {
verb: "GET",
url: "http://www.google.com",
};
var client = new MyJsInfra.HttpClient();
client.doRequest(request, (err, data) => {
if (err)
return callback(err, null)
return callback(null, data);
})
} catch (err) {
callback(err);
}
}
Problem
I can't seem to get the MSAL library to import properly into my typescript code. I'm using the MSAL for JS library (which is supposed to have typings) in a simple typescript/react project scaffolded using the create-react-app with react-typescript scripts. I'm new to typescript and not sure if I'm missing something obvious or if there is a problem with the MSAL package when using it with typescript projects.
Details:
I added the MSAL package from NPM using npm install --save msal.
I attempted to import the MSAL into my .ts using different forms of import {Msal} from 'msal';
This results in a typescript error Could not find a declaration file for module 'msal'. '<path>/node_modules/msal/out/msal.js' implicitly has an 'any' type.
Thinking that was odd, I looked at the the node_module/msal/out folder and saw a 'msal.d.ts' file, which is what I would expect.
When I look at the contents of the msal.d.ts file, I don't see any exports, which I would normally expect to see.
I tried install the declaration from #types using npm install --save-dev #types/msal, but it doesn't exist.
I also tried importing it into my file using let Msal = require('Msal');, but get an error that the Msal.UserAgentApplication isn't a constructor.
I didn't have much luck trying to use the /// reference directive and adding a script tag to the main index.html. This also doesn't feel like the right way to solve the problem.
ExampleMsal.ts
import { observable, action, computed } from 'mobx';
import * as Msal from 'msal'; // <-- This line gives the error
class ExampleMsal{
#observable
private _isLoggedIn: boolean;
constructor() {
this._isLoggedIn = false;
}
#computed
get isLoggedIn(): boolean {
return this._isLoggedIn;
}
#action
signIn() {
let userAgentApplication = new Msal.UserAgentApplication('<client-id>', null,
function (errorDes: string, token: string, error: string, tokenType: string) {
// this callback is called after loginRedirect OR acquireTokenRedirect
// (not used for loginPopup/aquireTokenPopup)
}
);
userAgentApplication.loginPopup(['user.read']).then(function(token: string) {
let user = userAgentApplication.getUser();
if (user) {
// signin successful
alert('success');
} else {
// signin failure
alert('fail');
}
}, function (error: string) {
// handle error
alert('Error' + error);
});
this._isLoggedIn = true;
}
#action
signOut() {
this._isLoggedIn = false;
}
}
export default ExampleMsal;
tsconfig.json
{
"compilerOptions": {
"outDir": "build/dist",
"module": "commonjs",
"target": "es5",
"lib": ["es6", "dom"],
"sourceMap": true,
"allowJs": true,
"jsx": "react",
"moduleResolution": "node",
"rootDir": "src",
"forceConsistentCasingInFileNames": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": true,
"strictNullChecks": true,
"suppressImplicitAnyIndexErrors": true,
"noUnusedLocals": true,
"experimentalDecorators": true
},
"exclude": [
"node_modules",
"build",
"scripts",
"acceptance-tests",
"webpack",
"jest",
"src/setupTests.ts"
],
"types": [
"typePatches"
]
}
It looks like the latest version of MSAL.js does have a CommonJS export. You can now just do the following in TypeScript (tested with version 2.3.3 of TypeScript and 0.1.3 of MSAL.js):
import * as Msal from 'msal';
Now in your .ts (or in my case .tsx file) you can, for instance, setup a click event handler and create a UserAgentApplication object:
// In you class somewhere
private userAgentApplication: any = undefined;
// The login button click handler
handleLoginClick = (event: any): void => {
if (!this.userAgentApplication) {
this.userAgentApplication = new Msal.UserAgentApplication(
'clientID string', 'authority string or empty', this.authCallback, { cacheLocation: 'localStorage'});
}
// Other login stuff...
}
// In React render()
public render() {
return (
<Button
bsStyle="warning"
type="button"
onClick={(e) => this.handleLoginClick(e)}
>
Log in
</Button>
);
}
As you have correctly mentioned - in the msal.d.ts there are no exports - its not a module, and therefore you should not try importing.
Instead you can use it like this:
/// <reference path="./node_modules/msal/out/msal.d.ts" />
const userAgentApplication = new Msal.UserAgentApplication("your_client_id", null, (errorDes, token, error, tokenType) =>
{
});
Note that even in readme they specify only one way of using their library - by including script tag, not by importing module. And further looking into their source code shows they are not using modules as well.
I had the same issue and couldn't wait for the author to fix it, so forked and modified the original code. Just as a temporary fix you can use my version msalx instead of msal.
npm install msalx
You can find the source code and an example usage in react at: https://github.com/malekpour/microsoft-authentication-library-for-js#example
If you install the exports-loader (npm install exports-loader --save-dev) you can avoid the script tag and add the following to your directives:
var Msal = require("exports-loader?Msal!../../../node_modules/msal/out/msal.js");