In part 1 of this series we created a shiny new ASP.NET Core MVC project, and I introduced some fundamentals.
Let's now go ahead with our first task, which is to bring our dependencies under control with JSPM as our javascript package manager.
Controlling Dependencies?
Take a peek under the wwwroot\lib
folder. You should see a few packages like JQuery
and Bootstrap
:
How did these get there? How do you upgrade them to new versions if you need to? What if you want your build server to restore them rather than commit them to your repository?
Well in the modern world, developers use package managers to manage dependencies and solve issues such as these.
Note: In previous versions of the Visual Studio Project templates (pre ASP.NET Core 2.1.0), these dependencies would have been under the management of Bower
by default and your project would have a bower.json
file in. For the purposes if this tutorial we are going to be using JSPM
not Bower, so if you are following along with an old project template and you have a Bower.json
file - you can delete it.
So the first problem we face is to bring these dependencies under control. I am going to show you how to do this using NPM and JSPM.
NPM
Let's add NPM.
- Right click on the Project in Solution Explorer and "Add --> New Item"
- Search for "Npm" and add a new "npm Configuration File"
Great.
Begin Wreckless Abandon
Unmanaged dependencies in the /wwwroot/lib
should be dead to us.
Delete this /wwwroot/lib
folder which will delete all of these packages. Awesome.
With those javascript / css packages deleted, what happens if we run the application now? Let's run it and find out..
As you can see, there are now errors displayed in the browser, and our site looks awful. This makes sense - our application is referencing javascript and css files that used to live in the lib folder, and now they are no longer found because we deleted them.
To fix this situation we'll need to add these packages back to our application, using JSPM
, and then fix up the way our application is loading
these dependencies (javascript, css files) at runtime.
Install JSPM
JSPM can be installed as a local NPM
package.
- Open
Package.json
- Add
JSPM
and whatever the latest version is:
- Save the file.
The NPM
package for JSPM
should now be downloaded and installed into your project. You will see that the package is installed into the "node_modules" folder within your project.
Configure JSPM
Now that the JSPM
package has been installed, we need to configure JSPM
.
The way to do this, is a little bit fiddely, as you have to drop to the command line - there is no fancy support for JSPM
in Visual Studio at the moment.
- Open a
command prompt
window, andCD
to your project directory - Type
"node_modules/.bin/jspm" init
and hit enter.
Note: I recomment that you install JSPM
as a global npm package using npm install jspm -g
then it will be added to your PATH so that you can run jspm
from the command line without having to run it from "node_modules/.bin/jspm".
You will now be asked a series of questions. At the end of answering these questions, the relevent config
will be produced within the project.
Here are the answers. Some of them you can just hit enter without typing anything, and the default value will be used.
I'll quickly run through each option briefly.. But you should defer to the JSPM
documentation site for further clarifications.
"Would you like jspm to prefix the jspm package.json properties under jspm?" We answer yes to this (the default) and this just means that JSPM will store its project configuration within a "jspm" section in our existing
package.json
file."Enter server baseURL (public folder path)" The word URL is a bit confusing here. This is the relative path to your "public" folder within the project. By public folder, we mean the folder that will serve up static files and is therefore accessible to a browser. We need to set this to the path to our
wwwroot
directory. So the value we set for this question is./wwwroot
as the value is relative to the current (project) directory."Enter jspm packages folder [wwwroot\jspm_packages]" We accept the default value for this question. Previously, our Bower packages were installed under
wwwroot\lib
folder, so if you want to keep this consistent you could change this value towwwroot\lib
. I however am happy to keep the default."Enter config file path [wwwroot\config.js]" This is the path to where you would like the config javascript file to be placed. Remember,
JSPM
is not just a package manager in a the sense of allowing you to adopt packages atdesign time
. It also has features that are used your application when it runs. This means it has aconfig
file (a javascript file) that your application will actually need to reference at runtime. This config file must therefore be placed in a directory that can be served up. We accept the default value (wwwroot\config.js)"Configuration file wwwroot\config.js doesn't exist, create it? [yes]" We accept the default which is
yes
as we want it to create this config file for us."Enter client baseURL (public folder URL) [/]" This is the URL or path that the browser uses to browse to the public folder (wwwroot). We accept the default value, because our public folder (wwwroot) is served up as the root path ("/").
"Do you wish to use a transpiler? [yes]" We accept the default answer of "yes" because transpilers are awesome. They allow us to write javascript using the latest language specifications, and then they "transpile" that javascript so that it can run in browsers that don't support the latest language specifications yet.
"Which ES6 transpiler would you like to use, Babel, Typescript, or Traceur? [babel]" For the purposes of this blog, I am accepting the default of "Babel".
The transpiler will just allow us to write javascript using ES6 language features, and this will be transpiled to run in browsers that don't support ES6 yet.
Installing JSPM Packages
Now that we have JSPM
configured, it's time to install those packages that we previously had in our wwwroot/lib
folder.
Back in the command prompt
run the following jspm commands:
jspm install jquery
jspm install jquery-validation
jspm install github:aspnet/jquery-validation-unobtrusive
jspm install bootstrap@3.3.7
Once that is done, those packages will now be installed under your wwwroot\jspm_packages
folder:
The next step is to fix up our MVC application so that it loads our javascript and css using the module loader
.
Transitioning to Modules.
The changes we have been making up until now, have been about managing our packages in our project at design time. This next step is about making changes to our application so that rather than including javascript and css files directly into particular pages, we instead, write "modular" javascript, that declares any dependencies it has, and then allow a module loader
(SystemJS
) to satisfy those dependencies for us at runtime by loading any needed javascript / css dependencies that our module requires.
If this sounds overwhelming, don't worry, it's simple once you get your head around the basic concept. Hopefully things will become more clear as we continue.
First, we need to include the module loader itself, and it's configuration file, into our application.
If you open _Layout.cshtml
you will see a section like this:
<environment names="Development">
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
</environment>
<environment names="Staging,Production">
<script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.1.4.min.js"
asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
asp-fallback-test="window.jQuery">
</script>
<script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.5/bootstrap.min.js"
asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.min.js"
asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal">
</script>
<script src="~/js/site.min.js" asp-append-version="true"></script>
</environment>
Let's comment out that whole section and replace it with this:
<script src="~/jspm_packages/system.js"></script>
<script src="~/config.js"></script>
<script>System.import("js/site");</script>
At this point, let's run the application!
You should now be able to see that we no longer get any errors about failing to load javascript files jquery.js
and bootstrap.js
. In fact, those javascript files are not being loaded anymore.
Why aren't we loading JQuery and Bootstrap anymore?
We are no longer directly including the bootstrap
and jquery
scripts into our _Layout.cshtml
file, so they aren't being loaded! So we aren't seeing any 404's anymore within the browser console window - which is good, but don't we need those files for our site to function?
This is the nature of modular
javascript. What we are in the process of transitioning to now, is a Modular concept, where bootstrap
and jquery
are modules that will only be loaded, if some other module that we are loading via the module loader, requires them as dependencies.
With that in mind, let's look at the module we are currently loading via the module loader. It's one called js/site
<script>System.import("js/site");</script>
This resolves (thanks to the config.js
file) to the js/site.js
file in our wwwroot
directory. This file is currently empty, meaning it also has no dependencies declared in it for any other modules. This is why the module loader no longer bothers to load JQuery
or Bootstrap
anymore.
This is good, because we are not including any javascript or css by default anymore, until its actually required by something (with the exception of the module loader, and config.js file iteself).
Therefore, as our js/site
module is being loaded in our _Layout.cshtml
file - which means it's going to be loaded on every page, we can "force" JQuery and Bootstrap to be loaded on every page, by decalring them as depencies for our module. This could be viewed as a bit of a cheat as really we don't want to load dependencies just for the sake of it, we only want to load them if they are actually used.
So, let's now assume that we are willing to load JQuery
, and Bootstrap
as a dependency for every page:
- Open
site.js
and insert the following code, then save it an re-run the application:
import $ from 'jquery';
import bootstrap from 'bootstrap';
This is ES6
syntax for declaring a module dependency.
You should now see that JQuery
and Bootstrap
are loaded on every page:
What about CSS
Now that we have got our javascript files loading again, we are still left with 404's for the bootstrap.css file.
Well we can use JSPM for CSS too, but we need to install the CSS
plugin.
Installing the CSS plugin for JSPM
Back in the command prompt
in your project directory, run the following
jspm install css
Now go back to your site.js
file, and add an import for the bootstrap.css. It should now look like this:
import $ from 'jquery';
import bootstrap from 'bootstrap';
import 'bootstrap/css/bootstrap.css!';
Lastly, in _Layout.cshtml
, comment out the link to the old - non existent, bootstrap.css file:
<environment names="Development">
@*<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />*@
<link rel="stylesheet" href="~/css/site.css" />
</environment>
<environment names="Staging,Production">
@*<link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.5/css/bootstrap.min.css"
asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"*@
asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" />
<link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
</environment>
Now run your application!
Wahoo! We now have no errors in the console window, our javascript and css is being loaded - and our application looks ok again.
Flash of unstyled content
You may notice that using the CSS plugin, your page is displayed in an unstyled form for a brief moment, whilst the CSS file is loaded asynchronosuly. This is known as a Flash of Unstyled Content and is a problem with using the CSS plugin at present - see here. Hopefully this will be addressed in the future, but in the meantime, feel free not to use the CSS Plugin if this is an issue, you can instead just directly reference the Bootstrap.css
file in the _Layout.cshtml
file as before, but from its new location under the jspm_packages
directory.
Finishing Touches - _ValidationScripsPartial.cshtml
Our application is running again, but you may notice a few of the pages have errors.
If you click on "Register" link for example you will see these errors in the browser console window:
This is because many of the views within our MVC application are rendering a partial called _ValidationScriptsPartial.cshtml
For example, if you look at the bottom of Register.cshtml
, you will see the following:
@section Scripts {
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}
If you look at the contents of _ValidationScriptsPartial
we can see that it is actually including additional scripts onto the page:
<environment names="Development">
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
</environment>
<environment names="Staging,Production">
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.14.0/jquery.validate.min.js"
asp-fallback-src="~/lib/jquery-validation/dist/jquery.validate.min.js"
asp-fallback-test="window.jQuery && window.jQuery.validator">
</script>
<script src="https://ajax.aspnetcdn.com/ajax/mvc/5.2.3/jquery.validate.unobtrusive.min.js"
asp-fallback-src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"
asp-fallback-test="window.jQuery && window.jQuery.validator && window.jQuery.validator.unobtrusive">
</script>
</environment>
As you can see, depending upon the environment that ASP.NET determines your application is running on, this renders some script includes to particular js files, used for forms validation. These were previoulsy located within Bower
packages, that we have deleted.
To correct this, we'll just need to instruct the module loader to load the jquery.validate.unobtrusive
module instead. Notice that you don't need to also instruct the module loader to load the jquery.validate
module, because jquery.validate
is a dependency of jquery.validate.unobtrusive
so the module loader will resolve it automatically.
So change the contents of _ValidationScriptsPartial.cshtml
to this:
<script>System.import("aspnet/jquery-validation-unobtrusive");</script>
And now - everything is working!
Recap
In this blog post, we took an out of the box ASP.NET Core MVC application that we created in part 1, and brought our dependencies under control with JSPM
. We changed the way our application resolves it's javascript and css files, to use a module loader
instead.
We also saw that using the module loader to load CSS currently results in a "flash of unstyled content" issue, and so if that's an issue for your application then it's probably best to stick to directly linking to your css files as before, for the time being. That's a decision for you to make!
Keep your eyes peeled for the next blog post/s in this series, I will be covering:
- Creating a basic Aurelia application on the Home page.
- Introducing Linting, Bundling, and Minification into the build process, using Gulp.
- Implementing "Automatic Browser Refresh" so our page refreshes as we make changes to javascript and css files.
- Addressing the CSS "Flash of Unstyled Content" when using the jspm css plugin.