NPM, Webpack and SASS Setup for DotNetNuke Skin Development

I recently had to do development work for a freelance client on the DotNetNuke CMS (all judgement on said client’s life/CMS choices are withheld in this article), and I wanted to use best practices for the front-end development requirements. I browsed around looking for a guide for setting up Webpack, and the latest NPM packages for SASS, Bootstrap etc., but couldn’t find one, so I decided to put one together to share the steps I took to get up and running.

There are several articles on the web that talk about how to install NodeJS and setup NPM for web development, using weback, sass and the other technologies mentioned here so I won’t go into detail on how to do that. This article’s intention is to inform a DNN skin developer about where to put things.

To begin, let’s establish a final folder structure (omitting most of the core DNN folders) and then work backwards to figure out what goes where. I opted for the following:

root/                                #DotNetNuke root CMS folder
|
|– Portals/                                  
| |– _default
| | |– Skins
| | | |_ CustomSkin/                 #Your Skin's root folder for "npm init"
| | | | |_ assets/
| | | | | |_ css/
| | | | | | |_ dist/
| | | | | | |_ scss/
| | | | | |   ...                    #7-1 SASS folder structure
| | | | | |_ images/
| | | | | | |_ sprite-images/
| | | | | |_ js/
| | | | | | |_ dist/
| | | | | | |_ src/
| | | | |_ partials/                 #For DNN partials

The SASS folders will use the 7-1 folder structure which Hugo Giraudel describes in detail in this article.

With a folder structure established, we take the following steps:

  1. Install NodeJS if you haven’t already
  2. Open a command line and run “npm init” in your skin’s root folder
  3. Using npm install in the command line, install the following node packages (I won’t detail what they do or what conflicts arise during their installation as that’s easily inferred from their websites):
    • Sass
    • Bootstrap
    • webpack
    • mini-css-extract-plugin
    • terser-webpack-plugin
    • webpack-spritesmith
    • webpack-livereload-plugin

The next thing you’ll need to do is setup a webpack.config.js file in the root folder to handle bundling. My complete config file looked like the following:

const path = require('path');
const buildOutputPath = path.resolve(__dirname, 'assets/js/dist');
const cssOutputPath = path.resolve(__dirname, 'assets/css/dist');
const scssPath = path.resolve(__dirname, 'node_modules');

const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const LiveReloadPlugin = require('webpack-livereload-plugin');
const SpritesmithPlugin = require('webpack-spritesmith');

module.exports = {
  devtool: 'source-map',
  entry: {
    index: './assets/js/src/webpack/index.js'
  },
  output: {
    filename: 'main.js',
    path: buildOutputPath
  },
  optimization: {
    minimize: true
  },
  module: {
     rules: [
       {        
         test: /\.(sa|sc|c)ss$/,
         use: [
            'style-loader',
            {
              loader: MiniCssExtractPlugin.loader,
              options: {
                publicPath: cssOutputPath
              }
            },                       
            {
              loader: 'css-loader',
              options: {url:false}
            },
            {
              loader: 'resolve-url-loader',
              options: {}
            },
            {
              loader: "sass-loader",
              options: {
                  includePaths: [scssPath]
              }
            }
         ]
       },
       {
            test: /\.(png|jp(e*)g|svg)$/,  
            use: [{
                loader: 'url-loader',
                options: { 
                    limit: 1000, // Convert images < 1MB to base64 strings
                    name: 'images/[hash]-[name].[ext]'
                } 
            }]
      },
       { 
         test: /\.js$/, 
         exclude: /node_modules/, 
         loader: "babel-loader" 
       },
        {test: /\.png$/, use: [
            'file-loader?name=i/[hash].[ext]'
        ]}
     ]
   },
   plugins: [    
    new MiniCssExtractPlugin({
      filename: "../../css/dist/skin.css",
      chunkFilename: "../../css/dist/[id].css"
    }),
    new LiveReloadPlugin(),
    new SpritesmithPlugin({
        src: {
            cwd: path.resolve(__dirname, 'assets/images/sprite-images'),
            glob: '*.png'            
        },
        target: {
            image: path.resolve(__dirname, 'assets/images/sprite.png'),
            css: path.resolve(__dirname, 'assets/css/scss/vendors/_sprite.scss'),
        },
        apiOptions: {
            cssImageRef: "../../images/sprite.png"
        },
        spritesmithOptions:{
          algorithm: 'diagonal'
        }        
    })   
  ]
};

In a nutshell, an index.js entry file is created in the js/src/webpack path that imports javascript and scss assets to be processed, combined, minified and outputted to the path js/dist/main.js file. Naturally there are several other ways that this could be configured.

I use a minifier, sprite generator and one of my favorite utilities — the live reload plugin which refreshes the browser in realtime after you make and save changes in your bundled files.

To import the assets generated by Webpack into my theme, I added an “_includes.ascx” file in the partials folder containing the following lines of code:

<dnn:DnnCssInclude runat="server" FilePath="assets/css/dist/skin.css"  PathNameAlias="SkinPath"  />
<dnn:DnnJsInclude runat="server" FilePath="assets/js/dist/main.js"  PathNameAlias="SkinPath"  />

The above references the compiled SASS css and the the main.js files. Which can then be imported into your theme pages (eg. home.ascx) as follows

<!--#include file="partials/_includes.ascx" -->

I used a seperate partial called _header.ascx to setup live reload. It requires the following entry in the partial:

<dnn:DnnJsInclude runat="server" FilePath="http://localhost:35729/livereload.js"/>

And that’s about all there is to it. The main pain point for me was not having a precedent in the context of DNN for setting up Webpack for skin development, and perhaps a few issues resolving dependencies during package installations. The latter has fairly good coverage as far as articles online explaining how to resolve such issues.

Leave a Reply

Your email address will not be published. Required fields are marked *