Quantcast
Channel: MVC – Software Engineering
Viewing all articles
Browse latest Browse all 96

An ASP.NET Core Razor Pages Bootstrap 4 Application using Webpack, Typescript, and npm

$
0
0

This article shows how an ASP.NET Core Razor Pages application could be setup to use webpack, Typescript and npm to build, and bundle the client js, CSS for development and production. The application uses Bootstrap 4.

Code: https://github.com/damienbod/AspNetCorePagesWebpack

The example is setup so that the vendor ( 3rd Party packages ) javascript files are used as part of the application in development, but CDN links are used for the production deployment. The vendor CSS files, (bootstrap 4) are loaded in the same way, locally for development, and CDNs for production. SASS is used to build the application CSS and this is built into the application bundle.

Getting the client packages using npm

A package.json file is added to the root of the project. This file is used to install the required npm packages and to define the scripts used for the client builds. All the packages for the project and the client build packages are added to this file.

{
 "scripts": {
  "build": "webpack --env=development",
  "build-watch": "webpack --env=development --watch",
  "release": "webpack --env=production",
  "publish": "npm run release && dotnet publish -c Release"
 },
 "dependencies": {
  "bootstrap": "4.1.1",
  "jquery": "3.3.1",
  "jquery-validation": "1.17.0",
  "jquery-validation-unobtrusive": "3.2.10",
  "core-js": "2.5.7",
  "zone.js": "0.8.26",
  "es6-promise": "^4.2.4",
  "ie-shim": "0.1.0",
  "isomorphic-fetch": "^2.2.1",
  "rxjs": "6.2.1"
 },
 "devDependencies": {
  "@types/node": "^10.3.4",
  "awesome-typescript-loader": "^5.2.0",
  "clean-webpack-plugin": "~0.1.19",
  "codelyzer": "^4.3.0",
  "concurrently": "^3.6.0",
  "copy-webpack-plugin": "^4.5.1",
  "css-loader": "~0.28.11",
  "file-loader": "^1.1.11",
  "html-webpack-plugin": "~3.2.0",
  "jquery": "^3.3.1",
  "json-loader": "^0.5.7",
  "mini-css-extract-plugin": "~0.4.0",
  "node-sass": "^4.9.0",
  "raw-loader": "^0.5.1",
  "rimraf": "^2.6.2",
  "sass-loader": "^7.0.3",
  "source-map-loader": "^0.2.3",
  "style-loader": "^0.21.0",
  "ts-loader": "~4.4.1",
  "tslint": "^5.10.0",
  "tslint-loader": "^3.6.0",
  "typescript": "~2.9.2",
  "uglifyjs-webpack-plugin": "^1.2.6",
  "url-loader": "^1.0.1",
  "webpack": "~4.12.0",
  "webpack-bundle-analyzer": "^2.13.1",
  "webpack-cli": "~3.0.6"
 }
}

Install nodeJS if not already installed and update npm (npm install -g npm) after installing nodeJS. Then install the packages.

> 
> npm install

A webpack config file is added to the root of the project. The development build or the production build can be started using this file.

/// <binding ProjectOpened='Run - Development' />

module.exports = function(env) {
  return require(`./Client/webpack.${env}.js`)
}

The webpack development build creates 3 files, a polyfills file, the vendor bundle file using the vendor.development.ts file and the app bundle using the main.ts as an entry point. The built files are created in the wwwroot/dist. The assets are copied 1 to 1 to the wwwroot. The CSS for bootstrap 4 is loaded directly to the wwwroot, and not bundled with the application CSS. This is then added in the header of the _Layout.cshtml. The sass files are built into the application directly into the app bundle.

const path = require('path');
const rxPaths = require('rxjs/_esm5/path-mapping');

const webpack = require('webpack');

const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

const helpers = require('./webpack.helpers');

const ROOT = path.resolve(__dirname, '..');

console.log('@@@@@@@@@ USING DEVELOPMENT @@@@@@@@@@@@@@@');

module.exports = {
  mode: 'development',
  devtool: 'source-map',
  performance: {
    hints: false
  },
  entry: {
    polyfills: './Client/polyfills.ts',
      vendor: './Client/vendor.development.ts',
      app: './Client/main.ts'
  },

  output: {
    path: ROOT + '/wwwroot/',
    filename: 'dist/[name].bundle.js',
    chunkFilename: 'dist/[id].chunk.js',
    publicPath: '/'
  },

  resolve: {
    extensions: ['.ts', '.js', '.json'],
    alias: rxPaths()
  },

  devServer: {
    historyApiFallback: true,
    contentBase: path.join(ROOT, '/wwwroot/'),
    watchOptions: {
      aggregateTimeout: 300,
      poll: 1000
    }
  },

  module: {
    rules: [
      {
        test: /\.ts$/,
        use: [
          'awesome-typescript-loader',
          'source-map-loader'
        ]
      },
      {
        test: /\.(png|jpg|gif|woff|woff2|ttf|svg|eot)$/,
        use: 'file-loader?name=assets/[name]-[hash:6].[ext]'
      },
      {
        test: /favicon.ico$/,
        use: 'file-loader?name=/[name].[ext]'
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      },
      {
        test: /\.scss$/,
          include: path.join(ROOT, 'Client/styles'),
        use: ['style-loader', 'css-loader', 'sass-loader']
      },
      {
        test: /\.scss$/,
          exclude: path.join(ROOT, 'Client/styles'),
        use: ['raw-loader', 'sass-loader']
      },
      {
        test: /\.html$/,
        use: 'raw-loader'
      }
    ],
    exprContextCritical: false
  },
  plugins: [
    function() {
      this.plugin('watch-run', function(watching, callback) {
        console.log(
          '\x1b[33m%s\x1b[0m',
          `Begin compile at ${new Date().toTimeString()}`
        );
        callback();
      });
    },

    new webpack.optimize.ModuleConcatenationPlugin(),

    new webpack.ProvidePlugin({
      $: 'jquery',
      jQuery: 'jquery',
      'window.jQuery': 'jquery'
    }),

    // new webpack.optimize.CommonsChunkPlugin({ name: ['vendor', 'polyfills'] }),

    new CleanWebpackPlugin(['./wwwroot/dist', './wwwroot/assets'], {
      root: ROOT
    }),

    new HtmlWebpackPlugin({
        filename: '../Pages/Shared/_Layout.cshtml',
        inject: 'body',
        template: 'Client/_Layout.cshtml'
    }),

    new CopyWebpackPlugin([
        { from: './Client/assets/*.*', to: 'assets/', flatten: true }
    ]),

    new CopyWebpackPlugin([
        { from: './node_modules/bootstrap/dist/css/*.*', to: 'css/', flatten: true }
    ])
  ]
};

The ASP.NET Core _Layout.cshtml src file is added to the Client folder. When webpack builds, the required bundles are added to the file, and copied to the Shared/Pages required by the Razor Pages.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <environment exclude="Development">
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" integrity="sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB" crossorigin="anonymous">
    </environment>

    <environment include="Development">
        <link href="~/css/bootstrap.min.css" rel="stylesheet" />
    </environment>

    <title>@ViewData["Title"] - ASP.NET Core Pages Webpack</title>
</head>
<body>
    <div class="container">
        <nav class="bg-dark mb-4 navbar navbar-dark navbar-expand-md">
            <a asp-page="/Index" class="navbar-brand">
                <em>ASP.NET Core Pages Webpack</em>
            </a>
            <button aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation" class="navbar-toggler" data-target="#topNavbarCollapse" data-toggle="collapse" type="button">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="topNavbarCollapse">
                <ul class="mr-auto navbar-nav">
                    <li class="nav-item">
                        <a asp-page="/Index" class="nav-link">Home</a>
                    </li>
                    <li class="nav-item">
                        <a asp-page="/About" class="nav-link">About</a>
                    </li>
                    <li class="nav-item">
                        <a asp-page="/Contact" class="nav-link">Contact</a>
                    </li>
                </ul>
                <ul class="navbar-nav">
                    <li class="nav-item">
                        <a class="nav-link" href="https://twitter.com/damien_bod">
                            <img height="30" src="assets/damienbod.jpg" />
                        </a>
                    </li>
                </ul>
            </div>
        </nav>

    </div>
    
    <partial name="_CookieConsentPartial" />

    <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>
            <p>&copy; 2018 - ASP.NET Core Pages Webpack Bootstrap 4</p>
        </footer>
    </div>

    <environment exclude="Development">
        <!-- Optional JavaScript -->
        <!-- jQuery first, then Popper.js, then Bootstrap JS -->
        <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
        <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js" integrity="sha384-smHYKdLADwkXOn1EmN1qk/HfnUcbVRZyYmZ4qpPea6sjB/pTJ0euyQp0Mk8ck+5T" crossorigin="anonymous"></script>
    </environment>

    @RenderSection("Scripts", required: false)
</body>
</html>

Client Production build

The webpack production is is very similar except that most, if not all of the vendor client libraries are removed and loaded using CDNs. You can choose where the production scripts should be read from, depending on the project. The vendor.production.ts in this project is empty.

The main.ts is the entry point for the application scripts. All typescript code can be added here.

import './styles/app.scss';

// Write your ts code here
console.log("My site scripts if needed");

In Visual Studio, the npm Task Runner can be installed and used to do the client builds.

Or from the cmd

>
> npm run build
>

When the application is started, the client bundles are used in the Pages application.

Links:

https://docs.microsoft.com/en-us/aspnet/core/razor-pages/?view=aspnetcore-2.1&tabs=visual-studio

https://getbootstrap.com/

https://webpack.js.org/

https://www.typescriptlang.org/

https://nodejs.org/en/

https://www.npmjs.com/


Viewing all articles
Browse latest Browse all 96

Trending Articles