AspNetMVC React Webpack - egnomerator/misc GitHub Wiki
note on assumptions about the reader (future me):
- To benefit from this document and the referenced repository (linked to below at "See the code here"), I'm assuming a reader (my future self) has some experience/familiarity with React and Bootstrap and plenty experience with ASP.NET MVC
- The less familiar material might be NPM, NPM package, Webpack, babel, what all these things are, what they do, and what part they play in the referenced repository's web application
- So the focus of this document is mainly that less familiar material in preparation for looking at the referenced repository's web app, seeing it all in action, and understanding what you're seeing
An ASP.NET MVC Web App targeting .NET Framework 4.7.2 with Razor views using Bootstrap style sheets and utilizing React with Reactstrap JS styling written in ES6 and JSX, built with NPM running Webpack using babel for transpilation.
See the code here.
- I've seen
- a lot of examples of full stack JS web apps
- a lot of examples of ASP.NET MVC web apps with various JS libraries (like jQuery, Knockout, etc.)
- a few examples of ASP.NET MVC web apps (mostly .NET Core) that basically function as a web service exposing an API to a JS SPA utilizing React (or Angular, but in this document, my focus is React)
- But I've NOT seen any examples of an ASP.NET MVC web app that takes full advantage of what the .NET world has to offer while also taking advantage of React without shifting the burden (perhaps undesirably) of certain functionality away from the server and onto the client (browser)
- the closest would be ReactJS.NET tutorials which are good, but I've noticed ReactJS.NET limitations such as not supporting ES6 modules, and experimental SSR
- I think the reason I've had trouble finding this is that it can be a major barrier to entry to have to figure out NPM, Webpack, transpilers, etc. and to figure out which of those tools to choose and how to use them to really add value to an ASP.NET MVC app rather than simply bringing chunks of its functionality to the browser
- making a full switch to an SPA template that's been fully setup (like Facebook's create-react-app) let's you dive right in whether you know what you are getting into or not
- this template can be a direction to a great app, but it becomes an SPA at this point, and if you want to use these tools without creating an SPA, it's not so easy
The goal of this document is to show one approach to integrating the most helpful pieces of the JS world into an ASP.NET MVC web app in a way that let's an ASP.NET MVC Web App developer continue to utilize the power of the .NET world while utilizing the best parts of the JS world to enhance the interactive aspect of their web app
- this basically sounds similar to just using jQuery, and it might be quite the same, but depending on how you design your app, there is great potential for it to be very different
-
So again, why? (if this can be similar to using jQuery?)
- rapid development
- even if the end result were an app that could have worked just the same with jQuery, I think there is more potential for rapid development in JS by using React and some of these tools that maximize React's usefulness
- better code practices, cleaner code
- using React can naturally help keep the JS code base cleaner, easier to debug, easier to maintain
- (this doesn't just magically happen though, you still have to deliberately strive to achieve this)
- using React can naturally help keep the JS code base cleaner, easier to debug, easier to maintain
- rapid development
note:
- this is not an example of that vision of a full-fledged application utilizing the best of both worlds .NET and JS
- this is just intended to explain how to set set yourself up with the integration of these tools and provide some thoughts that can hopefully point in the right design direction
The .NET Parts
- This will look like a typical ASP.NET MVC app for the most part
- It will use models, controllers, Routing, Razor views (including shared _Layout page) in typical ASP.NET MVC fashion
The JS Parts
- NPM will run Webpack to build the JS bundle that the web app will use
- Webpack will be configured to use babel to transpile ES6 and JSX
- React components will be used to render specific parts of the page on the client
A Mix
- Some parts of the page will be rendered in the Razor views server-side while also being rendered by React on the client
- there are pros and cons to this "isomorphic"/"universal" rendering, and there are variations on how to do it
- note: the app linked to above does not implement "isomorphic"/"universal" rendering--I manually wrote the code in a Razor view causing it to be rendered on the server as any normal Razor view is
- this is a very simplistic (ahem bad?) approach used just for this demo app
- the ASP.NET Core library JavaScriptServices includes a NodeServices package which provides a way to call Node from .NET--this would allow actual server-side rendering of the React component--but I haven't looked into this yet
- after rendering server-side, you're still not done--you would want to learn about how, on the client, React "hydrates" an existing component that was rendered server-side
- note: the app linked to above does not implement "isomorphic"/"universal" rendering--I manually wrote the code in a Razor view causing it to be rendered on the server as any normal Razor view is
- there are some situations where this makes sense to do and some where it makes no sense to do--this documentation doesn't attempt to make those decisions, only to show an approach to doing it
- there are pros and cons to this "isomorphic"/"universal" rendering, and there are variations on how to do it
Note on Version Choices:
- the implementation below explicitly provides specific versions
- NPM version differences can sometimes result in painful issues
- my version choices are very simply the latest versions at the time i created this app
- these versions all worked well together, so this is simply a record of an arbitrary collection of versions that will hopefully minimize version compatibility issues
Environment
- IDE: Visual Studio 2017 Update 3
- Node: for the purposes of this app, Node.js runs NPM and and runs webpack builds
Create the app
- In Visual Studio, Select New Project/Web/ASP.NET Web Application (.NET Framework)
- target .NET 4.7.2
- I'll name it "PracticalAspNetMVCReactWebpack"
- choose MVC template
Nuget
- Update Bootstrap to v4.1.3
- The chosen template already has the Bootstrap Nuget installed but at an earlier version
- Remove all existing content under the scripts folder
- this is to get rid of any Bootstrap-related Javascript
- after the initial creation of the template, this is just Bootstrap and jQuery (and Popper after the Bootstrap Nuget update)
- Any direct manipulation of the DOM with JS can cause React's virtual DOM to be inaccurate
- React specifically recommends not using jQuery and since Bootstrap uses jQuery, there are other libraries that implement Bootstrap in a way that is compatible with React
- this app will use one of these libraries (Reactstrap)
- We do want to keep all the Bootstrap styles
- optional: delete all contents of the template Site.css file
NPM
- in VS, in the solution explorer, toggle on "Show All Files" to see files created in the solution during the NPM steps
- initialize
-
cd
to the project root andnpm init
- (this creates the package.json file)
- i just kept all defaults (including some blank values) through the
npm init
prompts
- in VS, include the package.json file in the project
- there is a "package-lock.json" file as well
- there is debate on whether this should be git committed
- it seems like most of the people saying not to use it have version problems
- my approach to handling the version problems it to make sure the package.json file lists dependencies as specific versions and not with the auto-update syntax (explained more below)
- so I commit the file since it seems to be the intention that this file be committed
- also, it looks like the issues may have been fixed with recent updates, but I don't know
-
- install packages
- note: don't include the node_modules folder and its contents created during the package installs
- note: when installing NPM packages, there can be A LOT of dependencies installed as a result of only installing a few packages, so expect the node_modules folder to have a lot in it by the end of these NPM steps
- note: the npm
--save-dev
flag specifies that the package is to be installed as a dev dependency so it will only be included in dev builds, and not production builds- there is also a
--save
flag which is the default flag which installs it as a regular dependency - these flags also cause the package.json file to be updated with these dependencies
- as of NPM v5 the
--save
flag is default behavior--earlier versions would not update the package.json file, unless this flag was used
- as of NPM v5 the
- there is also a
- note: optionally use an additional flag
--save-exact
on all below package install commands to avoid package version auto-updates- NPM's default behavior is to include the caret (NPM's default semver range operator) on each dependency version in the package.json file to implement automatic minor version updates
- webpack
npm install [email protected] [email protected] --save-dev
- babel
npm install @babel/[email protected] [email protected] @babel/[email protected] @babel/[email protected] --save-dev
- react and react dom
npm install [email protected] [email protected]
- reactstrap
npm install [email protected]
- this library provides special bootstrap-related react syntax which the library converts to bootstrap v4 classes
- note on react-bootstrap NPM package:
- react-bootstrap accomplishes the same thing as reactstrap
- I chose reactstrap over react-bootstrap due to the current beta state of react-bootstrap
- at this time, react-bootstrap is in beta with an explicit warning that APIs will change
- they are updating the library to support Bootstrap v4
- this library will be a great option later
- note on Bootstrap NPM package:
- we don't need this because we won't be rendering html pages via react--we will only use react for react components
- so styles references will be handled in Razor views which will apply the styles based on the classes on the React components thanks to Reactstrap's conversion of the React-style syntax to Bootstrap classes
Edits to package.json
-
remove all version update carets from package.json
- this disables automatic package updates just to avoid any version compatibility issues in the future
- only need to do this if you didn't include the
--save-exact
flag with the above package install commands
- additional script builds
- this determines how Webpack will build the JS bundle
- add a "dev", "watch", and "build" property in the "scripts" object
- "dev" will be for running webpack in development mode
- "watch" will be for running webpack in development mode with the watch flag (auto-rebuild on watched file changes)
- "build" will be for running webpack in production mode
- the "test" property was created automatically during the NPM initialization
- final package.json
- after NPM installs and final edits, the file should look like this:
{ "name": "practicalaspnetmvcreactwebpack", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "dev": "webpack --mode development", "watch": "webpack --mode development --watch", "build": "webpack --mode production", "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC", "dependencies": { "react": "16.5.2", "react-dom": "16.5.2", "reactstrap": "6.5.0" }, "devDependencies": { "@babel/core": "7.1.2", "@babel/preset-env": "7.1.0", "@babel/preset-react": "7.0.0", "babel-loader": "8.0.4", "webpack": "4.21.0", "webpack-cli": "3.1.2" } }
- after NPM installs and final edits, the file should look like this:
When it comes time to use NPM to run Webpack to build the JS bundle, go to command prompt and type one of the following variations of the run command depending on how you want Webpack to behave:
npm run dev
npm run watch
npm run build
Create the webpack.config.js file
- create a new JS file called "webpack.config.js" in the project root
- configuration notes
- devtool: "source-map"
- this helps during debugging by mapping the code in the webpack-build result file to the original JS files
- as we debug, we want to step through the code we wrote in individual files (and in ES6 and JSX) rather than stepping through the transpiled code in the bundled file--this property facilitates that desired debugging experience
- babel
- configure babel to transpile .js and .jsx files
- devtool: "source-map"
- note:
- this is a simple webpack configuration; webpack can do much more than this configuration handles
- this is a targeted configuration intended to handle as little as is necessary--only what ASP.NET MVC won't handle--specifically the ES6 and JSX transpilation and the source-map debugging
- final webpack.config.js file
- after all edits, the file should look like this:
const path = require("path"); var appPath = __dirname; module.exports = { context: appPath, devtool: "source-map", entry: { app: [ "./Scripts/renderComponents.js" ] }, output: { path: path.resolve(appPath, "Scripts/dist"), filename: "webpack_bundle_[name].js?[hash]", publicPath: "~/Scripts/dist/" }, module: { rules: [ { test: /\.(js|jsx)$/, exclude: /node_modules/, use: { loader: "babel-loader", options: { presets: [ ["@babel/preset-env"], ["@babel/preset-react"] ] } } } ] } };
- after all edits, the file should look like this:
See the code here.
The above link is to the repository that has
- a small example application with
- the above specifications
- a simple index Razor view and shared _Layout Razor view
- 3 React components
Now take a look at that app and see these tools and libraries in use. When running the app, it's helpful to observe how it loads and to read further explanations on the rendered index page.