TSC command taking 372s to complete - node.js

I have a very small TypeScript Firebase repo that I use for cloud functions (totals 6 files in my src directory). The tsc command was taking a really long time to complete so I ran it with --diagnotics and got this output:
functions % tsc --extendedDiagnostics
Files: 290
Lines of Library: 28331
Lines of Definitions: 110562
Lines of TypeScript: 123
Lines of JavaScript: 0
Lines of JSON: 0
Lines of Other: 0
Nodes of Library: 117266
Nodes of Definitions: 336850
Nodes of TypeScript: 659
Nodes of JavaScript: 0
Nodes of JSON: 0
Nodes of Other: 0
Identifiers: 167151
Symbols: 183758
Types: 53351
Instantiations: 68377
Memory used: 209026K
Assignability cache size: 12195
Identity cache size: 223
Subtype cache size: 34
Strict subtype cache size: 0
I/O Read time: 0.58s
Parse time: 93.62s
ResolveModule time: 9.01s
ResolveTypeReference time: 1.41s
Program time: 107.81s
Bind time: 49.14s
Check time: 212.69s
transformTime time: 0.42s
Source Map time: 0.26s
commentTime time: 0.21s
I/O Write time: 0.01s
printTime time: 2.52s
Emit time: 2.53s
Total time: 372.17s
From what I can see online this seems to be really high. I've Googled around and some have suggested including "typeRoots": [ "node_modules/#types" ] in my tsconfig.json or ensuring include has only my src directory, as well as updating to the latest TS (running 4.4.2) but none of this has helped. I'm not a Node eng so not sure where to start.
In case it helps, here's my tsconfig.json:
{
"compilerOptions": {
"module": "commonjs",
"noImplicitReturns": true,
"noUnusedLocals": true,
"outDir": "lib",
"sourceMap": true,
"strict": true,
"target": "es2017",
"typeRoots": [ "node_modules/#types" ],
},
"compileOnSave": true,
"include": [
"src"
]
}

After further experimenting, I tried installing the current (non-LTS) build of Node as it explicitly lists arm64 as an architecture (I'm running an M1 Mac), and everything ran blazingly fast.
Running the bleeding edge version of Node isn't possible for me because Firebase doesn't support it yet, but I found this post which recommends running Terminal in Open in Rosetta mode to circumvent issues like these. This fixed the issue.

Related

Custom Dashboard for AdminJS not working in production

I have a Koa nodejs server which I added AdminJS to and it's working beautifully locally. My goal is to override the Dashboard component. I did so successfully when not running in production. However when I run in production mode (NODE_ENV=production node ./dist/server.js) it fails silently.
const componentLoader = new ComponentLoader();
const Components = {
Dashboard: componentLoader.add("Dashboard", "./admin/dashboard"),
};
const admin = new AdminJS({
componentLoader,
dashboard: {
component: Components.Dashboard,
}
});
My dashboard.tsx file is in src/admin/ and admin is a folder on the same level as src/server.ts. Also, my componentLoader when I inspect it is showing the correct filePath that ends with dist/admin/dashboard
Also, when I check dist/admin/dashboard.js I see my React code. So my tsconfig seems to be correct and the dashboard.tsx has a default export.
What confuses me is when I run nodemon --watch src --exec node -r esbuild-register src/server.ts is works correctly so it seems in general I have things hooked up correctly.
Lastly, here's my tsconfig.json.
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"jsx": "react",
"lib": [
"es6"
],
"target": "es2017",
"module": "commonjs",
"esModuleInterop": true,
"resolveJsonModule": true,
"strict": true,
"allowSyntheticDefaultImports": true,
"noImplicitAny": true,
"allowJs": false,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"noImplicitReturns": true,
"strictNullChecks": true,
"moduleResolution": "node",
"inlineSources": true,
"sourceRoot": "/",
"sourceMap": true,
"isolatedModules": true,
"outDir": "./dist",
"rootDir": "./src",
"composite": true,
"baseUrl": ".",
"paths": {
"src/*": [
"src/*"
]
}
},
"exclude": [
"node_modules",
"./node_modules/*"
],
"files": [
"./src/server.ts"
],
"include": [
"./src/**/*",
"./src/*"
]
}
UPDATE:
I did notice that the components.bundle.js file was missing when navigating to my adminjs dashboard. Since I am using GCP App Engine, I know that that file will not able to be built and saved on the fly in the file system so I have integrated #adminjs/bundler which creates the missing files. However the piece I still haven't put together is how to integrate it into the build pipeline (in particular I'm not sure what the destination of the components.bundle.js should be).
Before I explain my solution here are a few pieces of context:
When using NODE_ENV=production, adminjs does a few things differently, in particular the components.bundle.js file gets served differently. In production, it looks for the file at ./.adminjs/bundle.js
That's when the bundler comes in (which is necessary anyway for certain cloud environments like GCP App Engine). You have to create your own components.bundler.js file which they have a tool for.
First, I created a file which bundles the frontend components. I have not tried doing that with the ComponentLoader so I wouldn't need duplicate code yet, but here's what I know for certain works:
import AdminJS, { OverridableComponent } from "adminjs";
const bundle = (path: string, componentName: string) =>
AdminJS.bundle(`./${path}`, componentName as OverridableComponent);
export const DashboardComponent = bundle("../src/dashboard", "Dashboard");
I believe if I were to create a file which creates the ComponentLoader and adds the components that it would be equivalent (it would export the Components and the componentLoader for use by the AdminJS configuration).
Note ../src/dashboard is simply the location of the dashboard.tsx file I chose. And Dashboard is the name of the component.
Then, I created a script which uses #adminjs/bundler to actually create the bundles. (I named it bundler.ts).
import { bundle } from "#adminjs/bundler";
/**
* yarn admin:bundle invokes this script.
* This file is used to bundle AdminJS files. It is used at compile time
* to generate the frontend component bundles that are used in AdminJS.
*/
void (async () => {
await bundle({
customComponentsInitializationFilePath: "./components.ts",
destinationDir: "./.adminjs",
});
})();
I added a script to my package.json which does the following:
ts-node ./bundler.ts && mv ./.adminjs/components.bundle.js ./.adminjs/bundle.js
Now, when I run this script (which I do when I run before doing node ./dist/server.js), the adminjs router is going to be able to find the previously missing file.
Note that when running your server you'll also want to make sure you set ADMIN_JS_SKIP_BUNDLE='true'.
I hope this helps the next person. I also do hope some documentation and better tooling is on its way. This is kind of messy but solved my issue for now.

How to use import-map with Deno

I have this import_map.json file:
{
"imports": {
"node_modules/" : "./node_modules"
}
}
at a high-level I am trying to create some compatibility for .ts files, for both Deno and Node.
My imports look like this:
import * as util from 'util';
import chalk from "node_modules/chalk";
When I run this:
deno run --import-map='import_map.json' ./src/linked-queue.ts
I get this loathsome error:
Import map diagnostics:
- Invalid target address "file:///.../linked-queue/node_modules" for package specifier "node_modules/". Package address targets must end with "/".
error: Blocked by null entry for ""node_modules/""
at file:///.../linked-queue/src/linked-queue.ts:4:19
Anyone know how to resolve this error?
"imports": {
"node_modules/" : "./node_modules/"
}
Add a trailing slash on the target specifier. See also the spec and the source.
The manual covers this scenario in the following three sections:
4.1 - Using npm packages with npm specifiers
4.3 - The std/node Library
4.4 - Using Import Maps
I'll show a reproducible example rather than copy + paste everything from the docs (because a few copied snippets aren't really enough; this is a multi-faceted issue) — however take note of the values in the import map, as they are derived by reading through all three linked sections of the documentation:
./import_map.json:
{
"imports": {
"chalk": "npm:chalk#5.2.0",
"node:util": "https://deno.land/std#0.170.0/node/util.ts"
}
}
./deno.jsonc:
{
"importMap": "./import_map.json",
"tasks": {
// I included these permissions (which are required by chalk) in advance to avoid needing to grant them one-by-one at runtime:
"dev": "deno run --allow-env=FORCE_COLOR,TF_BUILD,TERM,CI,TEAMCITY_VERSION,COLORTERM,TERM_PROGRAM,TERM_PROGRAM_VERSION src/linked-queue.ts"
}
}
./src/linked-queue.ts:
import * as util from "node:util";
import chalk from "chalk";
console.log('util:', typeof util); // util: object
console.log('chalk:', typeof chalk); // chalk: function
Running in the terminal using the defined task:
% deno --version
deno 1.29.1 (release, x86_64-apple-darwin)
v8 10.9.194.5
typescript 4.9.4
% deno task dev
Task dev deno run --allow-env=FORCE_COLOR,TF_BUILD,TERM,CI,TEAMCITY_VERSION,COLORTERM,TERM_PROGRAM,TERM_PROGRAM_VERSION src/linked-queue.ts
util: object
chalk: function
% echo $?
0
So far, everything is great in Deno.
Let's check to see that the same code works without modification in Node.js. The following files need to be added to compile and run using Node, since it doesn't include all of Deno's built-in tooling:
./package.json:
{
"name": "so-74905332",
"version": "0.1.0",
"type": "module",
"scripts": {
"compile": "tsc",
"dev": "tsc && node src/linked-queue.js"
},
"license": "MIT",
"dependencies": {
"chalk": "5.2.0"
},
"devDependencies": {
"#types/node": "^18.11.17",
"typescript": "^4.9.4"
}
}
./tsconfig.json:
Why these values? I'm just using a recommended base, linked to from the TS repo wiki:
// This file was autogenerated by a script
// Equivalent to a config of: strictest extends esm extends node18
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Node LTS + ESM + Strictest",
"_version": "18.12.1",
"compilerOptions": {
"lib": [
"es2022"
],
"module": "es2022",
"target": "es2022",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"allowUnusedLabels": false,
"allowUnreachableCode": false,
"exactOptionalPropertyTypes": true,
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noPropertyAccessFromIndexSignature": true,
"noUncheckedIndexedAccess": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"importsNotUsedAsValues": "error",
"checkJs": true
}
}
Running in the terminal using the defined npm script:
% node --version
v18.12.1
% npm install
added 3 packages, and audited 4 packages in 1s
1 package is looking for funding
run `npm fund` for details
found 0 vulnerabilities
% npm run dev
> so-74905332#0.1.0 dev
> tsc && node src/linked-queue.js
util: object
chalk: function
% echo $?
0
The same module source code also works in Node.js.

Prevent tsc type checking projects in node_modules folder?

I am running into an issue whereby tsc is insisting on type-checking files in the node_modules folder, resulting in errors such as:
> my-project#0.0.0 build:ts
> tsc --project tsconfig.json
node_modules/mongoose/types/query.d.ts:619:34 - error TS1144: '{' or ';' expected.
619 toConstructor(): typeof Query<ResultType, DocType, THelpers, RawDocType>;
~
node_modules/mongoose/types/query.d.ts:619:45 - error TS1005: '>' expected.
619 toConstructor(): typeof Query<ResultType, DocType, THelpers, RawDocType>;
~
node_modules/mongoose/types/query.d.ts:619:77 - error TS1109: Expression expected.
619 toConstructor(): typeof Query<ResultType, DocType, THelpers, RawDocType>;
~
node_modules/mongoose/types/query.d.ts:622:19 - error TS1109: Expression expected.
622 update(filter?: FilterQuery<DocType>, update?: UpdateQuery<DocType> | UpdateWithAggregationPipeline, options?: QueryOptions<DocType> | null, callback?: Callback<UpdateWriteOpResult>): QueryWithHelpers<UpdateWriteOpResult, DocType, THelpers, RawDocType>;
This has only just started happening and I am trying to work out the cause and the resolution. The skipLibCheck parameter doesn't seem to be having any impact.
My tsconfig.json is as follows:
{
"compilerOptions": {
"lib": [
"es2020"
],
"module": "commonjs",
"experimentalDecorators": true,
"moduleResolution": "node",
"sourceMap": true,
"strict": false,
"target": "es2020",
"noImplicitAny": false,
"suppressImplicitAnyIndexErrors": true,
"typeRoots": [
"node_modules/#types"
],
"outDir": "dist",
"esModuleInterop": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": [
"src/**/*.ts"
],
"paths": {
"*": [
"node_modules/*",
"src/types/*"
]
},
"ts-node": {
"files": true,
}
}
Version info:
tsc 4.5.5 (provided by typescript#4.5.5)
node v16.10.0
The issue turned out to be a Typescript version mismatch, where we were using an older version than one of our dependencies.
The project was using Typescript 4.5.5 and one of the dependencies, in this case mongoose, changed to using Typescript 4.8.x in their 6.7.0 release. I didn't catch this until I checked the node_modules/mongoose/package.json and noticed that package.json specified:
"mongoose": "^6.1.2"
This meant the minor version was free to change, since it was an equivalent version, resulting in a silent breaking change appearing in our project.
This means I have two solutions:
Upgrade the Typescript version, used by the project
Use the '~' for the mongoose version to stay in 6.1.x
The first approach is what makes sense medium to long term, but the second one will get us unblocked the fastest. This is partly because upgrading the TS version will present the risk of unknowns, which we aren't ready for.

Nodejs Typescript type only package is not exporting all types properly

The package I'm building (https://github.com/plastikfan/xiberia/tree/develop) is a type only package (I'm not using DefinitelyTyped and this question is not about DT).
The package essentially is just a single file (index.ts) which contains various exported types such as:
export interface IYargsFailHandler {
(msg: string, err: Error, inst: yargs.Argv, command: any): yargs.Argv;
}
The problem is, when I use this in a client app, most of the types are missing and the only type that appears by intellisense is:
export const CoercivePrimitiveStrArray = ['boolean', 'number', 'symbol'];
All the other types are missing.
When I look at the corresponding index.js file, all it contains is:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CoercivePrimitiveStrArray = ['boolean', 'number', 'symbol'];
// -----------------------------------------------------------------------------
//# sourceMappingURL=index.js.map
The generated index.d.ts looks correct and contains all the types, (well there is one very weird definition that I can't account for at the end of the file):
export {};
My typescript config file is:
{
"compilerOptions": {
"allowJs": true,
"alwaysStrict": true,
"esModuleInterop": true,
"module": "commonjs",
"moduleResolution": "Node",
"noImplicitAny": true,
"sourceMap": true,
"strictNullChecks": true,
"target": "es5",
"declaration": true,
"declarationDir": "./dist",
"outDir": "./dist",
"diagnostics": true,
"lib": [
"es5",
"es2015",
"es6",
"dom"
],
"types": [
"node", "yargs"
],
},
"include": [
"./index.ts"
],
"exclude": [
"node_modules",
"dist"
]
}
So why are most of the types missing and how do I correct it, thanks.
EDIT: OOPS, I just made a really silly mistake. The types should not be in the resultant .js file. The only valid js is indeed the CoercivePrimitiveStrArrayCoercivePrimitiveStrArray which is being exported.
But that doesnt explain why the types that are being exported are not shown by intellisense on the clienbt side.
So on the client, this is what I have:
in a client file:
import * as xiberia from 'xiberia';
When I type, "xiberia.", I would expect to see all the types being exported, but I don't see any.
I read up on the triple slash directive and it appears they are not appropriate for this situation.
So what other config setting do i need for te intellisense to work as expected?
I fixed this problem by simplifying the package as much as possible. Previously, I was building artefacts into the 'dist' folder, a pattern which is widely used. I have removed this (this is a simple single file package, so using a dist folder is overkill) and simply build out the resultant index.d.ts, index.js and the .map files into the root directory. This also required explicity specifiying these files in the 'files' property in package.json (this ensures that these files are included in the resultant package tarball built when performing npm pack via the publish mechanism).
I don't understand why now intellisense is working as a result of these actions; perhaps somebody who knows better can comment.

Error importing node modules in TypeScript

I had a problem this morning that was driving me crazy. I'll explain the issue and then I'll provide my answer below (so that others who come across this can get to a solution sooner).
It is very easy to duplicate the issue by just issuing these commands:
tsd query react --action install
mkdir src
echo "import React = require('react');" > src/foo.ts
I also included the following tsconfig.json file in src:
{
"version": "1.6.2",
"compilerOptions": {
"outDir": "./tsdir",
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"isolatedModules": false,
"jsx": "react",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"declaration": true,
"noImplicitAny": false,
"removeComments": true,
"noLib": false,
"preserveConstEnums": true,
"suppressImplicitAnyIndexErrors": true
},
"files": [
"foo.ts"
]
}
If I try to compile this by simply running the tsc (version 1.6.2) command inside src, I get:
foo.ts(1,24): error TS2307: Cannot find module 'react'.
What I find baffling here is that I've installed the react bindings with tsd but when I run tsc, I get this error. It looks like I've done everything right, so why the error?
So what I eventually figured out was that I need to explicitly include the typings file in my list of "files", i.e.,
{
"version": "1.6.2",
"compilerOptions": {
...
},
"files": [
"foo.ts",
"../typings/react/react.d.ts"
]
}
In other words, I had to include the typings files explicitly in the "files". I don't really know why. I thought tsc was smart enough to look for them itself.
If there is a better solution that doesn't involve having to list all the .d.ts files explicitly in "files", I'm all ears. But I just wanted to point out that this is at least a workaround.

Resources