I'm trying to set up a webpack-based flow for an Angular2 app with a node backend server. After many hours banging my head against it, I've managed to get the client to build happily, but I can not figure out how to now integrate my server build. My server uses generators, so must target ES6, and it needs to point to a different typings file (main.d.ts instead of browser.d.ts)..
My source tree looks like;
/
-- client/
-- -- <all my angular2 bits> (*.ts)
-- server/
-- -- <all my node/express bits> (*.ts)
-- webpack.config.js
-- typings/
-- -- browser.d.ts
-- -- main.d.ts
I thought perhaps just a tsconfig.json in the client and server folders would work, but no luck there. I also can't find a way to get html-webpack-plugin to ignore my server bundle and not inject it into index.html. My current tsconfig and webpack are below, but has anyone succeeded in getting webpack to bundle a setup like this? Any pointers would be much appreciated.
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"declaration": false,
"removeComments": true,
"noEmitHelpers": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true
},
"files": [
"typings/browser.d.ts",
"client/app.ts",
"client/bootstrap.ts",
"client/polyfills.ts"
]
}
and my webpack.config.js;
var Webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var Path = require('path');
module.exports = {
entry: {
'polyfills': Path.join(__dirname, 'client', 'polyfills.ts'),
'client': Path.join(__dirname, 'client', 'bootstrap.ts')
},
output: {
path: Path.join(__dirname, 'dist'),
filename: '[name].bundle.js'
},
resolve: {
extensions: ['', '.js', '.json', '.ts']
},
module: {
loaders: [
{
test: /\.ts$/,
loader: 'ts-loader',
query: {
ignoreDiagnostics: [
2403, // 2403 -> Subsequent variable declarations
2300, // 2300 -> Duplicate identifier
2374, // 2374 -> Duplicate number index signature
2375, // 2375 -> Duplicate string index signature
]
}
},
{ test: /\.json$/, loader: 'raw' },
{ test: /\.html$/, loader: 'raw' },
{ test: /\.css$/, loader: 'raw!postcss' },
{ test: /\.less$/, loSWE: 'raw!postcss!less' }
]
},
plugins: [
new HtmlWebpackPlugin({ template: 'client/index.html', filename: 'index.html' }),
new Webpack.optimize.CommonsChunkPlugin('common', 'common.bundle.js')
]
};
Personally, I tend to write my server side code in plain JS (with most of ES2015 available now in Node) and my Angular 2 app in Typescript, so this issue doesn't come up. However, you can get this to work with Webpack.
First, you should have two separate Webpack configs: one for your client-side code and one for the server side. It might be possible to do it with one config, but even if it were, it would likely be more trouble than it's worth. Make sure to set target: 'node' in your server-side config (target: 'web' is set automatically for the client side). And make sure you set an entry point for your server-side files (I don't see one above, but you will ultimately have this in a separate config anyway).
Second, you need to have multiple tsconfig files. By default, ts-loader will look for tsconfig.json in your root directory. However, you can tell specify another file by setting configFileName: 'path/to/tsconfig' in the options object or query string/object.
This may lead to another problem however. Your IDE will also look for your tsconfig.json file in your root directory. If you have two separate files, you will need some way to tell your IDE which one to use for any given file. The solution to this will depend on your IDE. Personally, I use Atom with atom-typescript, which is fantastic, but it looks like the multiple tsconfig files thing is still being worked on. Thankfully I have never had to worry about this problem.
As for the html-webpack-plugin issue, you won't have to worry about it since you won't include the plugin in your server-side config. However, just for reference, you can pass excludeChunks: ['someChunkName'] to omit certain chunks from being included in the script tags.
Related
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/*"]
I have a Node.js project that uses Typescript and Three.js. To import modules, I use the commonjs syntax, which I configured via
{
"compilerOptions": {
"module": "commonjs"
}
}
in my tsconfig.json. I downloaded Three.js via NPM have a typescript file like this:
const THREE = require('three');
const scene = new THREE.Scene();
which compiles fine, but I do not get any autocomplete. I don't think this specific to the editor used, as both Visual Studio Code as well as Neovim with YouCompleteMe don't work. Both work if I use the ES6 module syntax:
import * as THREE from 'node_modules/three/src/Three';
const scene = new THREE.Scene();
Here however I cannot get it to work without giving the actual path to the library (which is a problem later on when using webpack). What did I forget to configure to get autocomplete (or the ES6 syntax without explicitly defining the path, at this point I am fine with both solutions)?
EDIT
As mentioned in the comments to accepted answer, I was not able to find my mistake, but found a working solution while trying to create a minimal working project. So I will post this here, in case it might help someone else. If you have the same problem, please still read the answer, as it is correct.
My source file (in src/main.ts):
import * as THREE from 'three';
const scene = new THREE.Scene();
package.json (with webpack to test if the library can be resolved there):
{
"devDependencies": {
"#types/node": "^12.0.4",
"three": "^0.105.2",
"ts-loader": "^6.0.2",
"typescript": "^3.5.1",
"webpack": "^4.32.2",
"webpack-cli": "^3.3.2"
}
}
tsconfig.json:
{
"compilerOptions": {
"baseUrl": ".",
"paths": { "*": ["types/*"] },
"target": "es6",
"module": "es6",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"moduleResolution": "node",
"esModuleInterop": true,
"sourceMap": true
},
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules/"
]
}
webpack.config.js:
const path = require('path');
module.exports = {
entry: './src/main.ts',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
resolve: {
extensions: [ '.ts', '.js' ]
},
module: {
rules : [
{ test: /\.ts$/, use: [ 'ts-loader' ], exclude: /node_modules/ }
]
}
};
What version of three is installed in your package.json file? Make sure it's 0.101 or later, since that's when TypeScript support began. I recommend you use the latest (105 as of this writing), since it gets updated definition files on each release.
Then, in your .ts files, you can import it with:
import * as THREE from "three";
// After importing, THREE is available for auto-complete.
var lala = new THREE.WebGLRenderer();
Edit:
You might need to perform path-mapping in your .tsconfig file to force the compiler to find the correct module address. I've never had to do this, but the Typescript documentation suggests something like this:
{
"compilerOptions": {
"baseUrl": ".", // This must be specified if "paths" is.
"paths": {
"three": ["node_modules/three/src/Three"] // relative to "baseUrl"
}
}
}
Update r126
As of revision r126, Three.js has moved Typescript declaration files to a separate repository. If you're using r126 or later, you'll have to run npm install #types/three as a second step. Make sure you install the version of #types/three that targets your version of Three.js. For example:
"three": "0.129.0",
"#types/three": "^0.129.1",
I have the following hierarchy:
dist/
|- BuildTasks/
|- CustomTask/
- CustomTask.js
node_modules/
source/
|- BuildTasks/
|- CustomTask/
- CustomTask.ts
- tsconfig.json
Additionally, I am trying to create a VSTS Task extension for internal (private) usage. Originally, I had my tsconfig.json at my root directory, and everything worked just fine on my local machine. The problem is that a VSTS Extension requires all the files to be included in the same directory as the task folder itself. See https://github.com/Microsoft/vsts-task-lib/issues/274 for more information:
you need to publish a self contained task folder. the agent doesnt run
npm install to restore your dependencies.
Originally, I had a this problem solved by include a step to copy the entire node_modules directory into each Task folder, in this case my CustomTask folder which contains my JS file. But, this seems a bit much considering that not every task I am writing has the same module requirements.
My idea was to create a tsconfig.json in each of the Task folders which would specify to create a single output file containing all of the dependent modules, but unfortunately it is not working:
{
"compilerOptions": {
"baseUrl": ".",
"target": "ES6",
"module": "system",
"strict": true,
"rootDir": ".",
"outFile": "../../../dist/BuildTasks/CustomTask/CustomTask.js",
"paths": {
"*" : ["../../../node_modules/*"]
}
}
}
Prior to adding the "paths", I was getting the following errors:
error TS2307: Cannot find module 'vsts-task-lib/task'.
error TS2307: Cannot find module 'moment'.
After adding the paths, I still get the error that it cannot find the module 'moment', which is in my node_modules directory. Also, when I look at the output JS it seems that it didn't include the 'vsts-tasks-lib' code necessary, maybe because it still had an error in regards to the 'moment' module? Not sure what I missed?
Using webpack to compile JavaScript modules, simple sample:
webpack.config.js:
const path = require('path');
module.exports = {
entry: './testtask.ts',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
},
resolve: {
extensions: ['.tsx', '.ts', '.js']
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
node: {
fs: 'empty'
},
target: 'node'
};
After that, there are just bundle.js and task.json in task folder.
Update: sample code in testtask.ts:
import tl = require('vsts-task-lib/task');
import fs = require('fs');
console.log('Set variable================');
tl.setVariable('varCode1', 'code1');
tl.setTaskVariable('varTaskCode1', 'taskCode1');
var taskVariables = tl.getVariables();
console.log("variables are:");
for (var taskVariable of taskVariables) {
console.log(taskVariable.name);
console.log(taskVariable.value);
}
console.log('##vso[task.setvariable variable=LogCode1;]LogCode1');
console.log('end========================');
console.log('current path is:' + __dirname);
fs.appendFile('TextFile1.txt', 'data to append', function (err) {
if (err) throw err;
console.log('Saved!');
});
console.log('configure file path:' + process.env.myconfig);
console.log('configure file path2:' + process.env.myconfig2);
I'm trying to set up a Node.js + TypeScript project using Intern for testing. Everything works fine when I compile the project using "commonjs" (which I do for the normal build); and TypeScript is equally happy when compiling for "amd", which is required by Intern. However, when passing the tests with intern-client, it complains about a couple of things:
First, imports from "index.ts" files (so-called "barrel" modules) won't work. My setup is something like this (everything in the same directory):
// index.ts
export { x } from './x'
// x.ts
export function x() {}
// x.test.ts
import { x } from '.' // "Error: Failed to load module ..."
In fact, the generated JavaScript code (for x.test.ts) looks something like this:
define(["require", "exports", "."], function (...) { ... })
And I'm not sure that AMD knows how to handle the ".".
The second issue happens under the same circumstances (TypeScript compiles happily, but intern-client complains). In summary, I get an error when doing:
import jsdom = require('jsdom')
Which I need to transform to:
const jsdom = require('jsdom')
For Intern to be able to deal with it.
Here is the tsconfig.json file I use to compile the tests:
{
"compilerOptions": {
"target": "es6",
"module": "amd",
"moduleResolution": "node",
"sourceMap": true,
"rootDir": "src",
"outDir": "build/tests",
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true
}
}
And here is my intern.js configuration file, in case it helps:
define({
suites: ['build/tests/**/*.test.js'],
excludeInstrumentation: true,
filterErrorStack: true
})
Edit (2017-05-03)
To help understand the issue, here is an excerpt of the directory tree of the project:
build
tests // The compiled tests will end up here
src
core
utils
x.ts
x.test.ts
// Other files, each containing a function that I would like to unit-test...
intern.js
package.json
tsconfig.json
...
Regarding the first issue, AMD's handling of an import like '.' is different than Node's. While both of them will map '.' to a package, Node uses a default module name of index.js, while AMD uses main.js. To get things working in an AMD loader, you'll need to first define a package for '.', and then tell the AMD loader what default module to use for that package. Given your project layout, you could configure Intern like this:
loaderOptions: {
map: {
// When a module in src/ references 'src/utils', redirect
// it to 'utils'
'src': {
'src/utils': 'utils'
}
},
packages: [
// Define a package 'utils' with files in 'src/utils' that defaults
// to the module index.js
{ name: 'utils', location: 'src/utils', main: 'index.js' }
]
}
Regarding the second issue, its not clear what the problem actually is. Import statements will be transpiled into define dependencies by TypeScript, so Intern should never be seeing them.
I created a sample react starter kit project in webstorm using webstorm's pre-defined project template and am trying to set breakpoints in debug mode.
I first built the project using npm run build then set the debug configuration to run build/server.js.
However it won't recognize any of the breakpoints in the original source files and seems to be ignoring the sourcemaps. How can I get it to recognize the sourcemaps and allow me to both set breakpoints in the source files as well as step into the source files.
There is this issue in the react starter kit repo: https://github.com/kriasoft/react-starter-kit/issues/121 but I couldn't see what the resolution was, and unlike the commenter, I couldn't even get it to step into the source files... it just stayed on the generated js files instead.
Well...
WebStorm 10 has no support for sourcemaps generated by Webpack. They are partially supported in WebStorm 11 for client-side applications (see http://blog.jetbrains.com/webstorm/2015/09/debugging-webpack-applications-in-webstorm/), but not supported for Node.js.
so, you can't debug server.js in WebStorm 11, but you can debug client side. To do this, try the following:
change appConfig in src/config.js as follows:
const appConfig = merge({}, config, {
entry: [
...(WATCH ? ['webpack-hot-middleware/client'] : []),
'./src/app.js',
],
output: {
path: path.join(__dirname, '../build/public'),
filename: 'app.js',
},
devtool: 'source-map',
module: {
loaders: [
WATCH ? {
...JS_LOADER,
query: {
// Wraps all React components into arbitrary transforms
// https://github.com/gaearon/babel-plugin-react-transform
plugins: ['react-transform'],
extra: {
'react-transform': {
transforms: [
{
transform: 'react-transform-hmr',
imports: ['react'],
locals: ['module'],
}, {
transform: 'react-transform-catch-errors',
imports: ['react', 'redbox-react'],
},
],
},
},
},
} : JS_LOADER,
...config.module.loaders,
{
test: /\.css$/,
loader: 'style-loader/useable!css-loader!postcss-loader',
},
],
},
});
set up the javascript debug run configuration:
URL: http://localhost:5000
Remote URLs: map project root folder to 'webpack:///path/to/react-starter-kit', like 'webpack:///C:/WebstormProjects/react-starter-kit'
map build/public to http://localhost:5000
This doesn't work perfectly, but works in general - breakpoints in src/routes.js, src/app.js are hit