How do I compile my TypeScript code for Node.js to one file? - node.js

I want to compile TypeScript to one file when using with Node.js.
I have tried configuring "tsconfig.json" like this:
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"noImplicitAny": false,
"sourceMap": false,
"outFile": "./app/index.js",
"pretty": true,
"types": [
"node"
]
}",
...but when I try with module set to "commonjs", I get the error:
error TS6082: Only 'amd' and 'system' modules are supported alongside --outFile.
If I change it to "module": "system", I get this error when running the file in node:
ReferenceError: System is not defined
If I change module to "amd", I get this error when running the file in node:
ReferenceError: define is not defined

It is not possible to bundle Node.js modules into a single file with the TypeScript compiler alone: each file in the CommonJS module system (used by Node.js) is treated as a single module, and cannot be joined together without proper module bundling techniques found in the many JavaScript code bundlers out there (such as Browserify, Webpack, Rollup, Parceljs, ...).
Other module systems such as SystemJS do not have this limitation, and module definitions can be concatenated into a single file using just the TypeScript compiler: tsc ... --outfile bundle.js -module system. However, they are not supported by Node.js on the fly.
For the actual solution, there are two reasonable choices: either configure your project to bundle the solution using a separate tool (Browserify, Webpack, ...), or include an implementation of SystemJS into your Node.js application (instructions here).
I will also end with a side note: consider evaluating whether you really need to bundle your server-sided code. Bundling is typically performed in frontend projects to reduce the size of client-sided resources, although it can be done as well for server applications in resource-efficient scenarios.

#E_net4 is right. Currently, it is not possible to build modules into a single file with the TypeScript compiler alone. This article describes how to do so with webpack where the file structure is:
├── index.js
├── package.json
├── src
│ ├── add-message
│ │ └── index.ts
│ ├── index.ts
│ └── upcase-messages
│ └── index.ts
├── tsconfig.json
└── webpack.config.js
webpack.config.js
const nodeExternals = require('webpack-node-externals');
module.exports = {
entry: './src/index.ts',
output: {
filename: 'index.js', // <-- Important
libraryTarget: 'this' // <-- Important
},
target: 'node', // <-- Important
module: {
rules: [
{
test: /\.tsx?$/,
loader: 'ts-loader',
options: {
transpileOnly: true
}
}
]
},
resolve: {
extensions: [ '.ts', '.tsx', '.js' ]
},
externals: [nodeExternals()] // <-- Important
};
tsconfig.json
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"outDir": "./",
"noImplicitAny": true,
"strictNullChecks": true
},
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules"
]
}

Related

Mocking es6 with mocha in Typescript

I am struggling to properly stub/mock unit tests when using es6 modules along with a project with mixed .js and .ts files.
According to this post, testdouble should be able to provide the ESM mocking I need. However, it requires using --loader=testdouble to work, and I am currently using --loader=ts-node/esm. If I attempt to replace ts-node/esm, it is unable to find Typescript files:
Error [ERR_MODULE_NOT_FOUND]: Cannot find module
'/Users/repos/my-repo/src/models/connectionModel.js'
imported from
/Users/repos/my-repo/test/constants.tjs
(connectionModel is ts and imported as .js per esm convention)
Due to project requirements, I would need the project to be compiled in es6+, so removing type: module or setting module: cjs are not viable options for me.
Is there a viable way to use both loaders, or some other viable way to mock with es6?
package.json:
{
"type": "module",
"scripts": {
"test": mocha test/*.js test/*.spec.ts -r dotenv/config
}
}
tsconfig.json:
{
"compilerOptions": {
"target": "es2016",
"module": "es6,
"moduleResolution": "node16"
"allowJs": true,
"esModuleInterop": true
},
"ts-node": {
"esm": true
}
"include": [
"./src/**/*",
"test/**/*/.ts",
"test/**/*.js"
}
}
.mocharc.json: (grabbing from this answer)
{
"node-option": [
"experimental-specifier-resolution=node",
"loader=ts-node/esm"
]
}

Exporting Global Styles with Font Assets from a TypeScript->CommonJS module

I have a TypeScript React project organized as follows:
tsconfig.json
package.json
yarn.lock
lerna.json
node_modules/
packages/
ui-library/
package.json
tsconfig.json
typings.d.ts
src/
fonts/
myfont.ttf
components/
GlobalStyle.tsx
lib/ <-- `tsc` builds to here
web-app/
package.json
src/
components/
The web-app imports the ui-library as a commonjs module in its package.json. This is managed through Yarn Workspaces, hence node_modules being at the root instead of each folder. At one point, the web-app imports GlobalStyle from the ui-lib, which is where the issue occurs.
Error: Cannot find module '../fonts/myfont.ttf'
Require stack:
- /Users/me/sources/myproject/packages/ui-library/lib/styles/GlobalStyle.js...
Here is the original import statement from ui-library/src/GlobalStyle.tsx that causes the issue:
import myFont from "../fonts/myfont.ttf";
const GlobalStyle = () => (
<style global jsx>{`
#font-face {
font-family: "My font family";
src: url(${myFont}) format("truetype");
}
...</style>);
The issue is the require statement in the emitted GlobalStyle.js code, built when I run tsc in the ui-lib folder:
const MYFONT_ttf_1 = __importDefault(require("./myfont.ttf"));
I googled this issue online, and I found you had to add typings for compiling .ttf and other abnormal imports. So I created a typings.d.ts file with this declaration:
declare module "*.ttf" {
const value: string;
export default value;
}
And I included it on ui-library/tsconfig.json as follows:
{
"extends": "../../tsconfig.json",
"exclude": ["lib"],
"include": ["src"],
"files": ["./typings.d.ts"],
"compilerOptions": {
"baseUrl": "src",
"outDir": "lib",
"declaration": true,
"declarationMap": true,
"noEmit": false
}
}
The tsconfig above extends the root tsconfig:
{
"files": ["./typings.d.ts"],
"compilerOptions": {
"module": "commonjs",
"target": "es2019",
"lib": ["dom", "es2019"],
"strict": true,
"jsx": "react",
"moduleResolution": "node",
"isolatedModules": true,
"esModuleInterop": true,
"noUnusedLocals": false,
"forceConsistentCasingInFileNames": true,
"strictNullChecks": true,
"sourceMap": true,
"noEmit": true,
"declaration": false
},
"exclude": ["node_modules"]
}
We are using TypeScript 3.8. Let me know if there is any additional info to provide. My hunch is that I am either using the wrong module/target, or fundamentally misunderstanding some aspect of this.
More Details
I should note the reason we are using CommonJS modules is because ts-node can only work with that. We pull in ts-node when we are doing our gatsby build, following the gist here in gatsby-config.js:
require("ts-node").register();
// Use a TypeScript version of gatsby-config.js.
module.exports = require("./gatsby-config.ts");
Maybe it's an impossible problem to solve, to get fonts imported through ts-node which is a server side environment? Confused on what the right approach is to export a module with fonts. Willing to get rid of ts-node, and leave the Gatsby config files as js.. but mostly just want to be able to import my fonts.
I solved this by just copying the fonts as part of build steps. Basically, fonts have their own pipeline. There may be better ways, but this works well enough.

jest cannot resolve module aliases

I am using a npm module called module-alias. I map some modules in tsconfig.json and package.json
tsconfig.json
"compilerOptions": {
"baseUrl": "./src",
"paths": {
"#config/*": ["config/*"],
"#interfaces/*": ["interfaces/*"],
"#services/*": ["services/*"]
},
"module": "commonjs",
"target": "es2015", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
"sourceMap": true,
"outDir": "./dist", /* Redirect output structure to the directory. */
"rootDir": "./src",
"allowSyntheticDefaultImports": true /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
}
package.json
...
"_moduleAliases": {
"#config": "src/config",
"#interface": "src/interface",
"#services": "src/services"
},
"jest": {
"moduleNameMapper": {
"#config/(.*)": "src/config/$1",
"#interface/(.*)": "src/interface/$1",
"#services/(.*)": "src/services/$1"
},
"moduleFileExtensions": ['js', 'json', 'jsx', 'ts', 'tsx', 'node']
},
...
server.ts
import { logger } from '#config/logger';
everything works fine when I run npm start, but it gives me an error when I run jest
FAIL src/test/article.spec.ts
● Test suite failed to run
Cannot find module '#config/logger' from 'server.ts'
However, Jest was able to find:
'rest/server.ts'
You might want to include a file extension in your import, or update your 'moduleFileExtensions', which is currently ['js', 'json', 'jsx', 'ts', 'tsx', 'node'].
Anyone know what the problem is? thanks
Solution works for me (Update 18/10/2019) :
Create a jest.config.js with code below:
module.exports = {
"roots": [
"<rootDir>/src/"
],
"transform": {
"^.+\\.tsx?$": "ts-jest"
}
}
and update moduleNameMapper in package.json:
...
"_moduleAliases": {
"#config": "./src/config",
"#interfaces": "./src/interfaces",
"#services": "./src/services"
},
"jest": {
"moduleNameMapper": {
"#config/(.*)": "<rootDir>/src/config/$1",
"#interfaces/(.*)": "<rootDir>/src/interfaces/$1",
"#services/(.*)": "<rootDir>/src/services/$1"
}
}
...
After a few hours I've managed to make this work. I'll try my best to simplify it to save others time and make it smooth.
My app is of the following stack:
Typescript (tsc and ts-node/register, no Babel) for the API build (.ts)
React (.tsx using Webpack)
jest for API testing (no Babel)
testcafe for UI/E2E
VSCode as IDE
Key notes:
The solution that works for me is must have "#" character in front of the alias. It's not required in theory by module-alias, however Jest is getting lost when we apply the module name mapper.
There needs to be consistency for naming between 'webpack' aliases and 'tsconfig'. It is necessary for VSCode not to red underline module names in TSX files.
This should work regardless of your document structure but remember to adapt baseUrl and jest config if encounter issues.
When applying changes in VSCode for .tsx files do not be worried that some of the paths are underlined. It's temporary as VSCode seems to grasp it only when all files are correctly connected to each other. It demotivated me at the start.
First, install module-alias from https://www.npmjs.com/package/module-alias with
npm i --save module-alias
then add to your initial startup file (for .ts files only, i.e. your application server):
require('module-alias/register')
as the module-alias docs indicate. Then setup tsconfig.ts. Keep it mind that baseUrl is relevant here as well:
{
"compilerOptions": {
"baseUrl": ".",
...
"paths": {
"#helpers/*": ["src/helpers/*"],
"#root/*": ["src/*"]
...
}
}
then setup your webpack.js:
const path = require('path')
...
{
resolve: {
...
alias: {
'#helpers': path.resolve(__dirname, 'src', 'helpers'),
'#root': path.resolve(__dirname, 'src')
}
}
}
then setup your package.json:
{
...
"_moduleAliases": {
"#helpers": "src/helpers",
"#root": "src"
}
}
then setup your jest config (I attach only things that were relevant when applying my change):
{
rootDir: ".",
roots: ["./src"],
transform: {
"^.+\\.ts?$": "ts-jest"
},
moduleNameMapper: {
"#helpers/(.*)": "<rootDir>/src/helpers/$1",
"#root/(.*)": "<rootDir>/src/$1"
},
...
}
Now, we need to take care of the build process because tsc is not capable of transpiling aliases to their relative siblings.
To do that we will use tscpaths package: https://github.com/joonhocho/tscpaths . This one is simple.
So considering your build command was just:
tsc
Now it becomes
tsc && tscpaths -p tsconfig.json -s ./src -o ./dist/server
You need to adjust your -s and -o to your structure, but when you inspect your .js file after build you should see if the relative path is correctly linked (and debug accordingly).
That's it. It should work as ace. It's a lot but it's worth it.
Example of a call in the controller (.ts) and in React component (.tsx) file:
import { IApiResponse } from '#intf/IApi'
In my case to support #Greg Wozniak's answer, I only needed to fix my jest config file.
My app uses jest.config.ts
In my src/index.ts I have import "module-alias/register"
In package.json:
{
...
"_moduleAliases": {
"#helpers": "dist/helpers",
"#root": "dist"
}
}
In tsconfig.json:
{
"compilerOptions": {
"baseUrl": "./src",
...
"paths": {
"#helpers/*": ["helpers/*"],
"#root/*": ["*"]
...
}
}
In jest.config.ts:
added roots, preset, the (.*) and $1 signs in moduleNameMapper*
{
roots: [
"<rootDir>/src/"
],
preset: "ts-jest",
transform: {
"^.+\\.(ts|tsx)$": "ts-jest",
},
moduleNameMapper: {
"#helpers/(.*)": "<rootDir>/src/helpers/$1",
"#root/(.*)": "<rootDir>/src/$1"
},
...
}
I think you don't have the routes properly configured in your tsconfig, since the paths lack the src folder (while they appear in your package.json module alias config):
Your tsconfig code:
"#config/*": ["config/*"],
"#interfaces/*": ["interfaces/*"],
"#services/*": ["services/*"]
How I think it should be:
"#config/*": ["src/config/*"],
"#interfaces/*": ["src/interfaces/*"],
"#services/*": ["src/services/*"]

Error: Cannot find module 'build/addon.node'

I am trying to import a module using a non-relative import but it's not working:
tsconfig.json
{
"compilerOptions": {
"target": "es5",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": false,
"noImplicitAny": false,
"suppressImplicitAnyIndexErrors": true,
"rootDir": ".",
"outDir": "build/js",
"baseUrl": "."
// "paths": {
// "#build/*": ["./build/*"]
// }
},
"exclude": ["node_modules", "build-releases", "addons"]
}
Code:
// const addon = require('../../../build/addon.node');
const addon = require('build/addon.node');
const debug = true;
export { addon, debug };
The commented code works but the non-commented require does not.
This error outputs:
App threw an error during load
Error: Cannot find module 'build/addon.node'
Require stack:
- /Users/name/Desktop/workspace/proj/build/js/vapp/globals.js
Dir structure:
tree build/
build/
├── addon.node
└── js
└── vapp
├── client.js
├── client.js.map
├── globals.js
├── globals.js.map
├── index.js
├── index.js.map
├── utils.js
└── utils.js.map
(build is in CWD)
TypeScript docs say:
A relative import is one that starts with /, ./ or ../.
Any other import is considered non-relative.
Setting baseUrl informs the compiler where to find modules. All module imports with non-relative names are assumed to be relative to the baseUrl.
Question: Is this because TypeScript is not responsible for module resolution with a require? As in, it's a node related things vs. a TypeScript thing?
Edit
I tried running tsc --traceResolution and found no information about my require trying to import addon.node. That made me realize: TypeScript is likely not responsible for module resolution here, NodeJS is. It doesn't look like you can easily change the baseURL dir for NodeJS and I'd have to use something like Webpack if I wanted to transpile with different, aliased paths.

Webpack 2 won't compile a single typescript file

I'm on a project where I have multiple SPA on Angular JS, all written in typescript. I have the following directory structure:
src/
├── app
│ ├── frontend
│ │ ├── ...
│ │ └── frontend.module.ts
│ ├── backend
│ │ ├── ...
│ │ └── backend.module.ts
├── app.frontend.module.ts
├── app.backend.module.ts
Files at the root of src/ are the entry point for each app, they contain the following code (here for the frontend):
import * as angular from "angular";
import { FrontendModuleName } from './app/frontend/frontend.module';
angular.module('app', [FrontendModuleName]);
angular.bootstrap(document, ['app'], {
strictDi: true
});
So I should be able to tell Webpack to compile "src/app.frontend.module.ts", the compiler then follows the imports and everything gets merged nicely.
It works fine, but even if I explicitly tell Webpack to compile a single file, all of them are always compiled.
To give you an example, let's take this very basic configuration:
const path = require('path');
module.exports = {
context: path.resolve(__dirname, 'src'),
entry: {
frontend: './app.frontend.module.ts',
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].bundle.js',
},
module: {
loaders: [
{
test: /\.ts(x?)$/,
loaders: ['ts-loader']
}
]
}
};
Executing this will compile app.frontend.module.ts as requested BUT also app.backend.module.ts !
After some time I found out that it's not Webpack but the Typescript compiler who seems at fault, so I changed my .tsconfig to try to exclude the root files, like so :
{
"compilerOptions": {
...
},
"exclude": [
"node_modules",
"dist",
"dev",
"reports",
"./src/app.*.module.ts"
]
}
In a sense it works because it now ignores app.backend.module.ts, but it still tries to compile every .ts file in the src/app/backend/ directory!
That's where I am right now.. I've searched in the compiler options reference but didn't found any option to prevent the compiler to recursively compile everything.
In fact..
..I've found a very very ugly workaround that I don't find satisfying at all. There is the files options that can be used to specify what files should be compiled, but I cannot give it an empty array or an exception is thrown.
So I created an empty dummy.ts file in the src/ folder and it seems to work as expected:
{
"compilerOptions": {
...
},
"files": ["src/dummy.ts"],
"exclude": [
"node_modules",
"dist",
"dev",
"reports",
"./src/app.*.module.ts"
]
}
But I can't imagine there is no better way to handle this.
Any idea would be much appreciated.
Thanks for your help.

Resources