Skip to content

IE11 Support

Leslie edited this page Apr 24, 2019 · 3 revisions

Supporting IE-11 with PatternFly 4

Let’s get right to it, you want to use the latest Patternfly 4 (PF4) but your application is required to support IE. Don’t worry, we’ve got you covered! The PF team has identified required polyfills and pre/post-processing steps to take for basic IE-11 support. Here we’ll show you how to achieve a full page layout for your app by using raw HTML for the page structure and using a simple node script to transform your CSS into a compatible format. For a complete example of what the end resulting script may look like, see this example ie11 stylesheet converter script.

The Problem… The Plan

CSS variables are not supported at all, and IE-11 uses an older version of the CSS grid layout spec, which isn’t compatible with the current grid layout spec. At a high-level what we’re going to do is create a simple node script that will collect all the stylesheets we need, in an ideal order, and then process them using some tools we’ve put together for you. The final output is an ie11-ready stylesheet that you can serve up with your application.

Adjust the JavaScript

Adding an es6-promise polyfill to your project is required as IE doesn’t support the Promise interface natively.

Adjust the HTML

Use the <section> element instead of <main> for your app’s primary content container.

Create a Script - Transform your CSS

Let’s create a node script you can use to convert any stylesheet into an IE11 format, considering the limitations of the processing library we’re using of course.

Step 1. Create the node script

Create a file to house the script. We’ll call it ie11-stylesheet-converter.js and add a simple log to it. Ensure you can invoke the script and get an expected result.

touch ie11-stylesheet-converter.js && echo "console.log('test')" >> ie11-stylesheet-converter.js && node ie11-stylesheet-converter.js

Step 2. Add IE11 compatible styling overrides

Apply IE11 compatible styles to explicitly set properties where the styles provided by core do not work properly. This fixes issues in the Gallery layout and the Page component. Similar styling overrides are needed for other components.

.pf-l-gallery {
  --pf-l-gallery--GridTemplateColumns: 1fr 1fr 1fr;
  --pf-l-gallery--GridTemplateRows: 1fr 1fr 1fr;
  --pf-l-gallery--m-gutter--GridGap: var(--pf-global--gutter);
}

.pf-l-gallery {
  grid-template-columns: var(--pf-l-gallery--GridTemplateColumns);
  grid-template-rows: var(--pf-l-gallery--GridTemplateRows);
  grid-gap: var(--pf-l-gallery--m-gutter--GridGap);
}

.pf-c-page__main-section {
  background-color: var(--pf-c-page__main-section--BackgroundColor);
}

.pf-c-page__main-section:last-of-type {
  background-color: rgb(237, 237, 237);
}

.pf-c-page__main-section.pf-m-light {
  background-color: var(--pf-c-page__main-section--m-light--BackgroundColor);
}

.pf-c-page__header .pf-c-dropdown__toggle.pf-m-plain,
.pf-c-page__header .pf-c-button, {
  color: var(--pf-global--Color--light-100);
}

Step 3. Add raw PF4 markup for the components you need

Use raw PF4 markup to create your app’s chrome (header/sidebar). Latest markup can be found in the PF4 Page component demo. Below is an example React component that shows how to render raw PF4 markup for the page component, not to be confused with the actual PF React "Page" component. PF4 React components are not currently supported in IE.

import React, { Component } from 'react';
// we'll turn this on after we build and run our transform script
// import './pf-ie11.css';
export default class App extends Component {
  render() {
    return (
      <React.Fragment>
        <div className="pf-c-background-image">
        </div>
        <div className="pf-c-page" id="page-layout-default-nav">
          <header role="banner" className="pf-c-page__header">
            ...
          </header>
          <aside className="pf-c-page__sidebar pf-m-expanded">
            ...
          </aside>
          <section role="main" className="pf-c-page__main">
            <section className="pf-c-page__main-section pf-m-light">
              ...
            </section>
            <section className="pf-c-page__main-section">
              ...
            </section>
          </section>
        </div>
      </React.Fragment>
    );
  }
}

Step 4. Install the required dependencies

npm install fs-extra glob concat postcss postcss-css-variables postcss-preset-env --save-dev

Step 5. Create some project specific variables

Open ie11-stylesheet-converter.js and create references to some of the node packages we’ll need. Then create a few more constants that point to various stylesheets, so the converter script knows where to find things. Adjust these paths to fit your project as necessary. The “toPath” variable is where you define where you want the final ie-11 ready stylesheet to live and what it should be named. The “filesThatNeedPathAdjustments” variable is an array that holds paths to individual component stylesheets which contain asset paths that differ from those found in patternfly-base.css. We’ll iterate over these stylesheets and fix those differences shortly.

const fs = require('fs');
const fse = require('fs-extra');
const path = require('path');
const concat = require('concat');
const { getStylesheetPaths, transform } = require('@patternfly/patternfly/scripts/ie-conversion-utils');
const pfStylesheetsGlob = path.resolve(__dirname, './node_modules/@patternfly/patternfly/{components,layouts,utilities}/**/*.css');
const patternflyBasePath = path.resolve(__dirname, './node_modules/@patternfly/patternfly/patternfly-base.css');
const myAppStylesheetPath = path.resolve(__dirname, './src/App/app.css');
const toPath = './src/App/pf-ie11.css';
const filesThatNeedPathAdjustments = [
  path.resolve(__dirname, './node_modules/@patternfly/patternfly/components/BackgroundImage/background-image.css'),
path.resolve(__dirname, './node_modules/@patternfly/patternfly/components/AboutModalBox/about-modal-box.css')
];

Step 6. Tweak the asset path references so they are all the same

Align the asset paths before concatenating your stylesheets for the transformation. The reason this is needed is that some of the component stylesheets (background image for example) use a relative path which is different than those found within patternfly-base.css for fonts. This path can be adjusted to fit your needs, the only thing that matters is that once the final stylesheet is produced, the asset paths in it all point to a single directory where assets live.

function fixAssetPaths(files) {
  // fix path discrepancy between .pf-c-background-image and font definitions
  files.map(filePath => {
    const startingCss = fs.readFileSync(filePath, 'utf8').match(/[^\r\n]+/g);
    const cssWithFixedPaths = startingCss.map(
      line => {
        const re = new RegExp('../../assets', 'g');
        return (line.includes('../../assets')) ? line.replace(re, './assets') : line;
      }).join('\n');

    // update these files in place
    fs.writeFileSync(
      filePath,
      cssWithFixedPaths
    );
  });
}

fixAssetPaths(filesThatNeedPathAdjustments);

Step 7. Specify any stylesheets to exclude

Create a constant for any stylesheets you’d like to exclude from the final distribution CSS.

const stylesheetsToExclude = ['Table', 'Login'];

Step 8. Build an array of stylesheet paths to process

Now, let’s start writing the heart of the script. getStylesheetPaths takes three params and returns a Promise to be fulfilled with an array of absolute paths to stylesheets.

  1. First param: pfStylesheetsGlob represents the stylesheets from patternfly you want. In this example, I’ve selected all of the components, layouts, and utility stylesheets that conveniently start with “ie11-”, pretty much everything.

  2. Second param: stylesheetsToExclude is a basic array of any specific components, utilities, or layouts from PF that you may not want. Your options are the directory names of the direct descendants of the components, utilities, and layouts folders.

  3. Third param: [myAppSylesheetPath] is another basic array that contains paths to your app’s custom stylesheets. This stylesheet will be loaded at the end of the cascade when processed.

Ok, we’re almost there. We’ll take the output from getStylesheetPaths and pipe that through a couple utility functions and we’ll have our CSS in no time.

// try logging this to the console so you can see the order of the cascade
// of course modify it as necessary to suite your needs, default order is recommended
const gatherStylesheetsToTransform = getStylesheetPaths(pfStylesheetsGlob, stylesheetsToExclude, [myAppStylesheetPath]);

Step 9. Concat the stylesheets and transform the CSS

Pass the results from getStylesheetPaths on through to concat, which takes that array and stitches the stylesheets together into one long string, in memory, and returns a Promise in standard fashion for the next step. We then take that concatenated CSS string and pass it over to transform from patternfly/scripts/ie-conversion-utils, which takes care of the heavy lifting. transform takes two required params and one optional. It returns a string of CSS that’s using older formats for the modern styling properties.

  1. First param: concatCss this is the string of css returned by concat

  2. Second param: patternflyBasePath this is a simple string that points to patternfly-base.css. If you don’t want this, use an empty array.

  3. Third param: excludeBase optional, boolean, if you want to exclude patternfly-base.css. Unless you’re building a component library, you probably don’t need to set this.

getStylesheetPaths(pfStylesheetsGlob, stylesheetsToExclude, [myAppStylesheetPath])
  .then(files => concat(files))
  .then(concatCss => transform(concatCss, patternflyBasePath))
  .then(ie11ReadyStylesheet => {
    // ie11ReadyStylesheet is a really long string of CSS...
    // console.log(ie11ReadyStylesheet);
    // now we just need to write it to disk and copy our assets
    // into a location where the stylesheet can find them
  })
  .catch(error => {
    throw new Error(error);
  });

Step 10. Write the transformed CSS (string) to disk

Now that we’ve gotten back our ie11-ready stylesheet, we just need to write it to disk and copy the assets directory over into our project. Add the following to the final .then() block.

fs.writeFileSync(
  path.resolve(__dirname, toPath),
  ie11ReadyStylesheet
);

// copy assets into local directory where our stylesheets can find them
const sourceAssetsDir = path.resolve(__dirname, './node_modules/@patternfly/patternfly/assets');
const newAssetDir = path.resolve(__dirname, './src/App/assets');

fse.copy(sourceAssetsDir, newAssetDir, function (error) {
  if (error) {
    throw new Error(error);
  }
});

With this is in place, you need to run the script and uncomment the stylesheet import (from step 3) so that we’re actually importing the stylesheet this script generates. That’s it, you now have the tools to have your cake and eat it too, enjoy!