Example code on Github.
This is the second part in my small series on ASP.NET Core and the coolest of the cool JavaScript libraries out there, except that they are probably already outdated by the time I finish writing this.
In part I we took a look on how to install npm and webpack into our ASP.NET Core project. In this part we will set up TypeScript. Visual Studio (VS) will compile TypeScript for you automatically but we will disable that feature and let webpack do the TypeScript build just for the fun of it.
Set up TypeScript
Start by installing TypeScript using npm (this is all described on in webpack’s documentation):
npm install --save-dev typescript ts-loader
Notice how package.json is updated with typescript and ts-loader. You may be wondering what ts-loader is. I know I was. It is a “TypeScript loader for webpack” which really does not say much but it is the thing that makes webpack take care of our TypeScript code.
While we are at it let’s install Knockout.js which we will use for building view models in TypeScript.
npm install --save-dev knockout @types/knockout
By using @types we tell npm to install the typings for Knockout as well. I tend to think of typings being to TypeScript what header-files are to C++. The typings go into the node_modules folder as everything else.
Next we need to create a configuration file for TypeScript. Right-click your project node in VS solution explorer and click “Add New Item”. Search for “json” in the templates dialog and choose “TypeScript JSON Configuration File”. The file must be name “tsconfig.json”. Change the contents so that it looks something like this:
{
"compilerOptions": {
"outDir": "./wwwroot/build/",
"noImplicitAny": false,
"noEmitOnError": true,
"removeComments": false,
"sourceMap": true,
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"compileOnSave": true
},
"exclude": [
"node_modules",
"wwwroot"
]
}
Notice that I have told the TypeScript loader to put the generated .js files in the folder “wwwroot/build”, and that I have told it to resolve any 3rd party modules by using “node” (i.e. it will look in the “node_modules” folder).
Test TypeScript build
Let us test that we can build TypeScript files. By default VS will build what ever .ts files you add to the project. Start by creating af Scripts folder in your project next to the wwwroot folder. Add a TypeScript a file named “myviewmodel.ts” to the folder. We will create a Knockout view model class so start by importing Knockout to the .ts file by adding the following line at the top.
import * as ko from "knockout"
Note how VS IntelliSense kicks in as you type. Very cool. Above we set “modeResolution” to “node” so that the TypeScript loader knows to look in node_modules to find Knockout. Now lets add a view model with two observable fields using the Knockout TypeScript definitions. The last line applies the Knockout bindings to the view.
import * as ko from "knockout"
class MyViewModel {
firstname: KnockoutObservable<string>;
lastname: KnockoutObservable<string>;
constructor(firstname: string, lastname: string) {
this.firstname = ko.observable(firstname);
this.lastname = ko.observable(lastname);
}
}
ko.applyBindings(new MyViewModel("Jakob", "Christensen"));
Now if you build your project in VS, you will see a new folder underneath “wwwroot/build” with the compiled .js file.
Set up the webpack TypeScript load
Instead of letting VS do the TypeScript build we want webpack to do it and we already installed ts-loader to do it for us. Why would we want to do that now that VS can do it for us? I like to do it because I prefer to keep everything front-end-ish together. So, webpack does the build, the bundling, the code splitting, etc.
Now, add a file called webpack.config.js to your project at the project root. Paste the following into the file.
var path = require('path');
module.exports = {
entry: {
site: [
'./wwwroot/js/site.js',
'./scripts/myviewmodel.ts']
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'wwwroot/dist/')
},
module: {
rules: [
{
test: /\.tsx?$/,
loader: 'ts-loader',
exclude: /node_modules/,
},
]
},
resolve: {
extensions: [".tsx", ".ts", ".js"]
}
};
This configures webpack to compile the .ts files. It also instructs webpack to take the compiled .js file and bundle it together with some other site.js file that we might have in our project and put it all into a file called bundle.js places in “wwwroot/dist”. This is the file you want to reference in your HTML files. By the way, the compiled .js files will no longer end up in the “wwwroot/build” folder so you can delete that.
Webpack build
To build and bundle, first edit your package.json so that the build block looks like this.
"scripts": {
"build": "webpack"
},
Then remove the line containing “compileOnSave” from tsconfig.json.
Finally, go to the cmd prompt and run the following npm command from your project folder.
npm run build
You should now see the file bundle.js in “wwwroot/dist”.
Of course you don’t want to go to the cmd prompt every time you have changed something in your .ts files, so we want VS to run the npm build. Fortunately, the ever-present Mads Kristensen has created a VS extension that does it for you. After installing the extension you can see the npm custom build task in Visual Studio’s Task Runner Explorer. Right-click “build” to tell VS to run the build task before or after your normal VS build.
This will add a line to the package.json file.
"-vs-binding":{"BeforeBuild":["build"]}
Cleaning up
As I said above, VS automatically picks up .ts files and builds. You don’t want that when using webpack. To disable the VS build, right-click your project in Solution Explorer and choose “Edit [your project name].csproj”. Add the following line under the <PropertyGroup>
element.
<PropertyGroup>
<!-- ... -->
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
</PropertyGroup>
Also, you might want to remove bower.json and bundleconfig.json if present, as package.json and webpack.config.js replace them. As far as I know bundleconfig.json works with another Mads Kristensen extension to bundle .js files.
That’s it. Now it is up to you to take fully advantage of webpack for code splitting and uglifying and what not.
hmm, xml tags got removed.
another attempt:
<TypeScriptCompileBlock>
should be
<TypeScriptCompileBlocked>
Hi,
Congratulations for this two parts article. Very enjoyable and useful. It is exactly what I was looking for. Thanks a lot
Hi,
Congratulations for your post. But I am afraid there is a typo at the Cleaning Up section that might be a headache. Shouldn’t it be “TypeScriptCompileBlocked” instead of TypeScriptCompileBlock?
Thanks and regards
Hi Damián,
Thank you for your comment. You are absolutely right. I fixed the typo.
Thanks.
Jakob
Great article, really helpful.
Most documentation out there assumes that VS2017 developers always use the project generator to create a new solution. However for those of us that want to understand how to set up a build environment manually, the documentation I have found to be woeful (or non-existent), except for your articles. Nice work.
Thanks, Ben. I appreciate it.
Hi, Jakob! Your blog is really-really beautiful! I also like that you’re into asp.net core and webpack like I am. That’s why I would suggest you to check out this extension to asp.net core 2.0, which helps to work with webpack assets: https://github.com/sergeysolovev/webpack-aspnetcore
It comes with a sample app to make things as clear as possible. I would like to know, what you think about it.
Wish you the best,
Sergee S.
Hi Sergee,
Thank you for your kind comment.
Your webpack-aspnetcore looks very interesting. You should write a blog entry on why you created it and what problems it solves. If you don’t run a blog, you can write on http://dev.to (where I post as well).
Kind regards,
Jakob
The webpack.config.js file refers to a folder named “scripts” but earlier in the text you say to create a folder named “Script”. It took me forever to figure out the error that resulted from that. To others that run into the problem either rename the folder Scripts or change webpack.config.js to reference the “script” folder.
Hi James,
Thanks for the heads-up. I changed the folder name accordingly.
Thanks,
Jakob
Thanks for the great tutorial.
Please not that the link to the “webpack and typescript guideline” is broken (in the paragraph ‘set up typescript’).
It should link to https://webpack.js.org/guides/typescript/
Hi Jo,
Thank you for the heads-up. I have updated the link.
Best regards,
Jakob
The npm run build
Kicked out an error… To fix it I followed the error message advice…
The CLI moved into a separate package: webpack-cli.
Please install ‘webpack-cli’ in addition to webpack itself to use the CLI.
-> When using npm: npm install webpack-cli -D
-> When using yarn: yarn add webpack-cli -D
Hi Warren,
Thank you for the tips.
(Make sure you deleted the bundle.js file to ensure your task runner is executing correctly)
If you configure the TASK RUNNER EXPLORER and you do NOT get your files in the DIST FOLDER…
Make sure that Visual Studio is using the global installed version of npm not it’s local.
In VS 2017 TOOLS | OPTIONS
In filter box type: External Web Tools
Move $(PATH) to the top of the list in the Locations of External tools
Just realized also that $(PATH) must come earlier, mine picked up the old NPM that came with the VS, and did not work. After moving the $(PATH) it’s all good.
Warren and Jacon, are you guys using Webpack 4.2.0 and webpack-cli 2.0.12 ? As soon as i run npm run build, i get this error, any suggestions?
C:\Users\Arun\Source\Repos\egg\UI\Egg.Admin\node_modules\webpack\bin\webpack.js:3
let webpackCliInstalled = false;
^^^
SyntaxError: Block-scoped declarations (let, const, function, class) not yet supported outside strict mode
at exports.runInThisContext (vm.js:53:16)
at Module._compile (module.js:373:25)
at Object.Module._extensions..js (module.js:416:10)
at Module.load (module.js:343:32)
at Function.Module._load (module.js:300:12)
at Function.Module.runMain (module.js:441:10)
at startup (node.js:139:18)
at node.js:968:3
okay turns our this has to do with my node version. i was on node 4.x , updated to node 8.X and it solved the problem
Thanks for this tutorial
Thanks for the feedback, Arun.