PnP – Office 365 Starter Intranet Solution (Part 2: Frameworks and libraries used)

The PnP Office 365 starter intranet solution uses a lot of frameworks and libraries. In this article, I will explain all the choices I’ve made for the implementation of this solution.

This article is not a tutorial about how to use these tools or frameworks.

Development tools

The solution was implemented using Visual Studio Code, a very lightweight IDE perfect for JavaScript development. For the PowerShell scripts part, I simply used the PowerShell ISE interface coming with Windows 10. I also used Node JS especially for the npm package manager to manage application dependencies. Don’t forget to read the setup section in the README.md file to set up your environment correctly before deploying this solution.

 TypeScript

typescript logo

Honestly, at the beginning of this project, I didn’t plan to use TypeScript. Why? Well…

Another language/tool to learn in the oversaturated and ephemeral world of JavaScript…! Hmm…I will try to use what I already know before invest my time into something I’m not sure it will be sustainable.

I’m not a dedicated web developer so learning each brand new « whatever JS » library or language released every month is definitely not something I have time to do. But while the code was becoming more and more complicated, I gave a try to TypeScript in order to structure the code correctly and make it more reliable:

Ok, Microsoft chose TypeScript for the new SharePoint Framework so this is probably the best time to learn it because I will probably have to work with it in the early the next months.

And I have to say…combined with Webpack, it is pretty simple and efficient! It took me less than one week-end to refactor the entire application using TypeScript. Coming from a C# developer background, it is very intuitive and much more cleaner than the traditional « spaghetti style » JavaScript code.

However, when you use TypeScript, you have to keep in mind some important points:

  • Not all third party libraries support TypeScript natively. To benefit of the intellisense feature you will have to install « typings ». Typings are just definition files for the original library or script for the TypeScript compiler. Typings are made by the community so, be careful: some of them (the most popular) can be very well maintained and some others absolutely not. Notice that it is also possible to work without typings.
  • It is recommended to use a « linter » like « TSLint« , available with Visual Studio Code as an extension to make sure your syntax follows the best practices.
  • You have a special configuration to make to get it work with Webpack (see below).

Webpack

Before starting this project, I didn’t know much about Webpack and I was pretty confused about the purpose of this tool compared to Gulp and other trending tools within the web development stack. The Webpack documentation is not so simple to understand for beginners and not very clear. To make it simple, Webpack is basically a bundler. It packages the files in your application into one or multiple aggregated files (called « chunks ») by tracking module dependencies automatically (i.e when you make a require() , or import call in a file). Packaging your application is important mainly because of performance considerations. Loading one file (or few files when you have multiples chunks) is much more efficient than loading multiples JavaScript files in the browser. When you work with client-side technologies, the performance matter.

Webpack is not designed for non-bundling tasks like upload files into a CDN (or SharePoint), load external scripts dynamically, etc. Webpack is a tool to be used before your application is running, not during.

But what is « bundling » exactly mean? Well, it can be:

  • Minify CSS style sheets
  • Compile TypeScript files to JavaScript files
  • Compile SASS style sheets to CSS
  • Replace strings in your files
  • Etc.

Bundling operations with Webpack are done through « loaders ». Actually, Webpack without loaders is limited to JavaScript files only. For example, to compile TypeScript files, you will need a loader to process *.ts files, then call the TypeScript compiler and finally output the generated *.js in a specified location. Loaders are made by the community and there are loaders for almost everything so don’t worry about this. They are all installed via npm like any other dependency (for example: https://www.npmjs.com/package/ts-loader) and can be chained to each others (like the css loader and the source map loader).

So, does Webpack replace Gulp? Short answer: NO. I don’t use Gulp in this sample example because I don’t need it. Webpack and Gulp are complementary tools (for example, bundling you application with Webpack and upload it to a CDN with a Gulp task). In my opinion, if you use Gulp only for bundling tasks, you should try Webpack instead.

A webpack.config.js configuration file is here to tell Webpack how to process files. Here is an example for the TypeScript loader definition:

// Wrap the whole Webpack config with a validator to avoid typos mistakes
module.exports = validate({
    ...  
 
    resolve: {    
              
        extensions: ["", ".webpack.js", ".web.js", ".js",".ts"],        
    },
    
    module: {
 
        loaders: [
 
            ...
            {  
                // Loader for TypeScript files (.ts)
                test: /\.tsx?$/,
                exclude: /node_modules/,
                loader: 'ts-loader'
            },  
            ...
        ],
    },
        
   ...
});

 

There are few things to notice in this sample for the Webpack configuration:
  • Webpack files are outputted in a « dist » folder and then uploaded to the Style Library of the SharePoint site via PowerShell.
  • I’ve split my application into two chunks, one for third party libraries (like jQuery, Knockout JS, etc.) and one for the application itself. The reason behind this is first to reduce the size of the final package, and then to keep a clear distinction between the application logic and third party libraries. However, because the application code has a direct dependency with the vendor chunk, the last one has to be loaded before (see the master page markup in the <head> section to see the order).
    module.exports = validate({
        
        entry: {
            app: "./main.ts", // The main entry point for the application.
     
            vendor: [
                    "jquery",
                    "knockout",
                    "knockout-mapping",
                    "office-ui-fabric/dist/css/fabric.css",
                    "office-ui-fabric/dist/css/fabric.components.css",
                    "bootstrap/dist/js/bootstrap.js",
                    "es6-promise",
                    "whatwg-fetch"]
        },
        
        output: {
            filename: "js/app.js",
            ...
        },
     
        plugins: [
     
            ...
     
            // Split the application into chunks
            new webpack.optimize.CommonsChunkPlugin(/* chunkName= */"vendor", /* filename= */"js/vendor.js")
        ]
    });
    var ExtractTextPlugin = require("extract-text-webpack-plugin");
     
    var layoutsCssExtractTextPlugin = new ExtractTextPlugin("layouts.css");
    ...
     
    module.exports = validate({
     
        module: {
     
            loaders: [
                ...
     
                {
                    // Page layouts styles (display and edit) are bundled separately. The loading is controlled by the page layout itself. 
                    test: /layouts\.scss$/,
                    loader: layoutsCssExtractTextPlugin.extract('style', 'css!sass?sourceMap')
                },
     
                ...
            ],
        },
            
        plugins: [
     
            ...                                
            layoutsCssExtractTextPlugin,
            ...
        ]
    });
    plugins: [
     
        ...                                
        // Every time webpack encounters $, jQuery, window.jQuery or ko, it will replace it by the correct library dependency
        // Especially useful for the default bootstrap.min.js file
        new webpack.ProvidePlugin({
            $: 'jquery',
            jQuery: 'jquery',
            "window.jQuery" : 'jquery',
            ko : 'knockout'
        }),
        ...
    ]

    Because of the import syntax of TypeScript is not supported for non-TypeScript files like images or CSS files, you have to manually define therequire() function to get it work with Webpack (https://github.com/TypeStrong/ts-loader).

    • declare var require: {
          <T>(path: string): T;
          (paths: string[], callback: (...modules: any[]) => void): void;
          ensure: (paths: string[], callback: (require: <T>(path: string) => T) => void) => void;
      };

       

      Before a file can be bundled, it has to be required in an other file. For static resources like images, CSS files, JSON files, etc., I make all require() calls in themain.ts file. It avoids me to make the « require » function declaration as global for the application.

      // Main style sheet for the application
      require("./styles/css/global.scss");
      require("./styles/css/layouts.scss");
      require("./styles/css/layouts-edit.scss");
      require("./styles/css/displaytemplates.scss");
       
      // Reusable contents CSS
      require("./styles/css/suggestionsbox.scss");
       
      // Images
      require("./styles/css/images/logo_intranet.png");
      require("./styles/css/images/spinner.gif");
      require("./styles/css/images/default_image.png");
      require("./styles/css/images/favicon_intranet.ico");
       
      // Bootstrap CSS isolation
      require("./styles/css/bootstrap/bootstrap-prefix.less");
       
      // Resources
      let enUSResources = require("./resources/en-US.json");
      let frFRResources = require("./resources/fr-FR.json");
      

       

      With Webpack, it is possible to output « Source Maps » for debugging purposes.

      module.exports = validate({
          
          ...
       
          devtool: "cheap-module-source-map",
       
          ...

       

      Office 365 Starter Intranet Webpack Source Map

      Office 365 Starter Intranet Webpack Source Map

      Webpack Source Map

      Be careful, source maps are not always well recognized by your browser depending the setting you chose in the configuration. I had a lot of troubles with Google Chrome with this. I advise you to use Google Chrome Canary instead for your debugging sessions. To choose the most appropriate setting, you can refer to this article.

    Knockout JS

    Knockout Logo

    Knockout Logo

    Knockout JS is used in this sample to handle the application UI behavior (navigation menus, search box ,etc.). Even though this is an arbitrary choice here, it is totally possible to rewrite this part using your favorite library* like React JS, Angular JS, Ember JS, etc. I chose Knockout because, first, I already knew it and then I wanted to leverage the component binding feature of the library. This feature gives you the ability to have a very flexible and modular solution in terms of UI.

    Generally speaking, thinking about modularity is not just only about technical considerations but also about the project management approach, especially if you work with agile methodologies. For example, you can see placeholders on the front page of the intranet (the grey boxes). These placeholders can be typically future components that would be implemented during the next development sprints. They already have a dedicated space in the interface and will be integrated with no additional refactoring costs because all components follow the same pattern anyway.

    Modular architectures and mechanisms allow you to have a « modular » project management approach like SCRUM so don’t neglect it. Just fill the grey boxes along your project ;).

    *Switching from Knockout JS to Angular JS is, in my opinion, not really relevant if you already know Knockout JS because it is basically the same approach. The switch to React JS makes more sense since it appears Microsoft chose it as its preferred library for the SharePoint Framework and seems to be more performant than the two others. This sample could be a good basis to start learning React JS by rewriting some components ;).

    Create a new component in the solution is actually very simple:

    1. Create a viewmodel (i.e searbox.viewmodel.ts). Plug it or not with a model class (typically where SharePoint client-side communications occur like taxonomy or search).
    2. Create a HTML template (i.e searchbox.template.html)
    3. Create the dedicated SASS file for styles (i.e searchbox.scss)
    4. Register it in the main.ts file and define the binding name:
    5. import { SearchBoxMobileViewModel } from "./viewmodels/searchboxmobile.viewmodel";
       
      public registerComponents() {
       
          // ===============================
          // Register Knockout components  
          // ===============================
       
          ...
       
          // Component: "Searchbox"
          let searchboxTemplate = require("./templates/searchbox.template.html");
          require("./styles/css/searchbox.scss");
          let searchboxComponent = new KnockoutComponent("component-searchbox", SearchBoxViewModel, searchboxTemplate);
       
          ...
      

      Use it either directly in an other HTML template or in your master page (or even in a script editor web part ).

      <!-- Header component -->
      <div id="header" class="hidden-xs">
          <div id="utilsZone">
              <div style="float:right">
                  ...
                  <component-searchbox></component-searchbox>
              </div>      
              ...
          </div>
      </div>

       

    Combined with TypeScript, you can even make generic components. For example, all navigation components (mainmenu.viewmodel.ts, contextualmenu.viewmodel.ts, breadcrumb.viewmodel.ts) are based on the same parent view model (navigation.viewmodel.ts). Without TypeScript I had to callko.utils.extend() in each component.

     // ========================================
    // Contextual Menu Component View Model
    // ========================================
    
    import { NavigationViewModel } from "../shared/navigation.viewmodel";
    ...
    
    export class ContextualMenuViewModel extends NavigationViewModel {
    
    ...

    PnP Remote Provisioning Engine

    Few months ago, I did a little calculation about some previous SharePoint On-Premise intranet project implementations. It appears that for a standard intranet project, SharePoint configuration automation tasks represent more than 50% of the global workload (you know, create columns, content types, lists, etc.). It doesn’t have to be like this…and this is all the paradox of SharePoint: end users can build their applications in minutes, but developers have to spend much more time to do the exact same thing by automation tasks.

    On one hand, you have developers who want to automate things and produce repeatable and professional solutions and in the other hand you have your customer who know he can do the same thing cheaper just by doing « click programming ». So where is the sweet spot?

    The PnP Remote Provisioning Engine is here to solve this problem: minimize the SharePoint configuration automation overhead for tasks that have no direct value for a customer allowing you to focus on more valuable features. Combined with the PnP PowerShell cmdlets, you can build a robust automated solution very quickly.

    SharePoint Online intranet project deployments in the real world

    Ok let’s face this reality: your customer doesn’t necessarily have an Office 365 tenant for every common environments (QA, PROD, DEV), either because he just doesn’t need it or he doesn’t want to pay for this (and it is totally understandable). To handle this situation, SharePoint Online (and On-Premise by the way) allows you to embed your configurations within the scope of a site collection. The most critical configurations are obviously the taxonomy and the search ones. In this sample, you have an example of a site collection self-contained solution. You can do your QA in a isolated site collection within an Office 365 production tenant without overlapping customizations.

    PnP JS-Core

    The PnP JavaScript Core library is relatively new in the PnP stack but I’m pretty sure it will become one its most important part in the next few months with the upcoming SharePoint Framework. Basically, the library is a wrapper of the SharePoint REST API interface and allows you to make your requests more easily through a clever syntax. I use it a lot in this sample especially for the translation control or to get the current page infos:

    import * as pnp from "sp-pnp-js";
     
    ...
     
    pnp.sp.web.lists.getByTitle("Pages").items.getById(_spPageContextInfo.pageItemId).select(this.selectedFields).expand(this.expandedFields).get().then((item) => {
        
        ... // Do something with your item
     
    }).catch((errorMesssage) => {
     
        pnp.log.write(errorMesssage, pnp.log.LogLevel.Error);
    });

     

    Unfortunately, for some scenarios like taxonomy, you still have to use the classic SharePoint CSOM. Please Microsoft, can you provide us a REST endpoint for taxonomy operations with SharePoint?

    Bootstrap/Office UI Fabric

    Bootstrap logo

    When talking about mobile web design, Bootstrap isn’t generally far away. Why? Simply because this is the most popular responsive framework on the internet. I’m not going to talk about this subject here voluntarily because there will have a detailled article about design considerations in SharePoint Online. I also use Office UI Fabric for colors and icons (personally, I don’t like the Bootstrap glyphicons).

    The mobile compatibility is currently the most controversial topic when talking about intranet portals in SharePoint Online and Office 365. Some developers say this is too complicated and prefer to implement their own ad-hoc application (like an MVC application with a SharePoint backend). Personally think this is not. Just stop focusing on the container, focus on the content instead and leverage the tool for what it is. This is what I’ve tried to demonstrate with this example.

    Custom master page or not?

    To be short on this:

    • If you use SharePoint as an « operational » day-to-day tool with team sites, etc., I totally agree to not modify the master page, because it makes no sense.
    • If you use SharePoint as a publishing portal, you probably might want your portal looks like something different from the ugly default SharePoint theme. Let’s be clear here: if you want a beautiful and a professional design, you have to modify the master page, even for few HTML markup modifications (and this is not a shame). This is your image, your graphic identity so what’s the problem?

    Other libraries and utilities

    Last but not least, I use some useful JavaScript libraries to resolve common situations encountered in a intranet project:

    • trunk8: used for text truncation and summarization (add « … » at the end of the description text in a search result for example). Maybe some you already know jQuery.dotdotdot, this is basically the same.
    • i18next: used for the multilingualism management. This library will be explained in details in another article.
    • PubSubJS: a library for the publish/subscribe JavaScript pattern implementation. Used in navigation menus. This part will be explained in details in a dedicated article.
    • moment.js: used for date formatting and manipulations.

     

    Contact the Author: Franck Cornu
    – LinkedIn: HTTPS://CA.LINKEDIN.COM/IN/FRANCKCORNU
    – Twitter: @FRANCKCORNU
    – Blog: HTTP://THECOLLABORATIONCORNER.COM/

     

    Reference:
    Cornu, F. (2016). Part 2: Frameworks and libraries used (How it is implemented?) | The collaboration corner. [online] Available at: http://thecollaborationcorner.com/2016/08/25/part-2-frameworks-and-libraries-used-how-it-is-implemented/ [Accessed 10 Feb. 2017].

Share this on...

Rate this Post:

Share: