es

Flexible CSS cover images

I recently included the option to add a large cover image, like the one above, to my posts. The source image is cropped, and below specific maximum dimensions it’s displayed at a predetermined aspect ratio. This post describes the implementation.

Known support: Chrome, Firefox, Safari, Opera, IE 9+

Features

The way that the cover image scales, and changes aspect ratio, is illustrated in the following diagram.

The cover image component must:

  • render at a fixed aspect ratio, unless specific maximum dimensions are exceeded;
  • support different aspect ratios;
  • support max-height and max-width;
  • support different background images;
  • display the image to either fill, or be contained within the component;
  • center the image.

Aspect ratio

The aspect ratio of an empty, block-level element can be controlled by setting a percentage value for its padding-bottom or padding-top. Given a declaration of padding-bottom:50% (and no explicit height), the rendered height of the element will be 50% of its width.

.CoverImage {
  padding-bottom: 50%;
}

Changing that padding value will change the aspect ratio. For example, padding of 25% results in an aspect ratio of 4:1, padding of 33.333% results in an aspect ratio of 3:1, etc.

Maximum dimensions

The problem with using this aspect ratio hack is that if the element has a max-height declared, it will not be respected. To get around this, the hack can be applied to a pseudo-element instead.

.CoverImage:before {
  content: "";
  display: block;
  padding-bottom: 50%;
}

Now the main element can take a max-height. It should also clip the pseudo-element overflow.

.CoverImage {
  display: block;
  max-height: 300px;
  max-width: 1000px;
  overflow: hidden;
}

.CoverImage:before {
  content: "";
  display: block;
  padding-bottom: 50%;
}

This aspect ratio pattern is provided by the FlexEmbed component for SUITCSS. That component is primarily for responsive video embeds, but it’s flexible enough to be useful whenever you need an element rendered at a predetermined aspect ratio. It comes with modifiers for 2:1, 3:1, 16:9, and 4:3 aspect ratios. The cover image component can extend the FlexEmbed component.

<div class="CoverImage FlexEmbed FlexEmbed--2by1"></div>

Background image

The cover image is applied as a background image that is sized to cover the entire area of the element. This makes sure the image is clipped to fit the aspect ratio of the element.

.CoverImage {
  ...
  background-repeat: no-repeat;
  background-size: cover;
}

If you want different cover images for different instances of the component, they can be applied via the style attribute.

<div class="..." style="background-image: url(cover.jpg)"></div>

The image can be fully centered by using background positioning and block centering. This makes sure that the image is centered in the element, and that the element is centered within its parent (when it reaches the max-width value).

.CoverImage {
  ...
  background-position: 50%;
  background-repeat: no-repeat;
  background-size: cover;
  margin: 0 auto;
}

Final result

If you depend on the FlexEmbed module, the amount of additional code required is minimal. (See the demo for all the code, including the FlexEmbed dependency.)

/**
 * Requires: suitcss/flex-embed
 */

.CoverImage {
  background-position: 50%;
  background-repeat: no-repeat;
  background-size: cover;
  margin: 0 auto;
  max-height: 300px;
  max-width: 1000px;
}
<div class="CoverImage FlexEmbed FlexEmbed--3by1"
     style="background-image:url(cover.jpg)">
</div>

You can add further customizations, such as setting the accompanying background color, or providing a means to switch between the cover and contain keywords for background-size.




es

Custom CSS preprocessing

Did you know that you can build your own CSS preprocessor with Node.js libraries? They can be used alongside established preprocessors like Sass, and are useful for defining tasks beyond preprocessing.

Libraries like Rework and PostCSS let you create and assemble an arbitrary collection of plugins that can inspect or manipulate CSS.

At the time of writing, Twitter uses Rework to perform various tasks against our CSS source code for twitter.com.

Creating a CSS preprocessor with Rework

At its core, Rework is a module that accepts a string of CSS, produces a CSS abstract syntax tree (AST), and provides an API for manipulating that AST. Plugins are functions that have access to the AST and a Rework instance. Rework lets you chain together different plugins and generate a string of new CSS when you’re done.

The source string is passed into the rework function and each plugin is applied with .use(fn). The plugins transform the data in the AST, and .toString() generates the new string of CSS.

Below is an example of a custom preprocessor script using Rework and Autoprefixer. It’s a simplified version of the transformation step we use for twitter.com’s CSS.

var autoprefixer = require('autoprefixer');
var calc = require('rework-calc');
var rework = require('rework');
var vars = require('rework-vars')();

var css = fs.readFileSync('./css/main.css', 'utf-8');

css = rework(css)
  .use(vars)
  .use(calc)
  .toString();

css = autoprefixer().process(css);

fs.writeFileSync('./build/bundle.css', css)

The script runs rework-vars, rework-calc, and then passes the CSS to Autoprefixer (which uses PostCSS internally) to handle the addition of any necessary vendor prefixes.

rework-vars provides a limited subset of the features described in the W3C-style CSS custom property spec. It’s not a polyfill!

Variables can be declared as custom CSS properties on the :root element, prefixed with --. Variables are referenced with the var() function, taking the name of a variable as the first argument and an optional fallback as the second.

For example, this source:

:root {
  --width-button: 200px;
}

.button {
  width: var(--width-button);
}

yields:

.button {
  width: 200px;
}

There are many different Rework plugins that you can use to create a custom preprocessor. A more complete list is available on npm. In order to limit the chances of long-term divergence between our source code and native CSS, I’ve chosen to stick fairly closely to features that are aligned with future additions to native CSS.

Creating your own Rework plugin

Rework plugins are functions that inspect or mutate the AST they are provided. Below is a plugin that rewrites the value of any font-family property to sans-serif.

module.exports = function plugin(ast, reworkInstance) {
  ast.rules.forEach(function (rule) {
    if (rule.type != 'rule') return;

    rule.declarations.forEach(function (declaration, index) {
      if (declaration.property == 'font-family') {
        declaration.value = 'sans-serif';
      }
    });
  });
};

Rework uses css-parse to create the AST. Unfortunately, both projects are currently lacking comprehensive documentation of the AST, but it’s not difficult to piece it together yourself.

Beyond preprocessing

Since Rework and PostCSS expose an AST and provide a plugin API, they can be used for other CSS tasks, not just preprocessing.

At Twitter, our CSS build pipeline allows you to perform custom tasks at 2 stages of the process: on individual files and on generated bundles. We use Rework at both stages.

Individual files are tested with rework-suit-conformance to ensure that the SUIT-style CSS for a component is properly scoped.

/** @define MyComponent */

:root {
  --property-MyComponent: value;
}

.MyComponent {}

Bundles are preprocessed as previously described, and also tested with rework-ie-limits to ensure that the number of selectors doesn’t exceed IE 8/9’s limit of 4095 selectors per style sheet.

Other tasks you can perform include generating RTL style sheets (e.g., css-flip) and extracting detailed information about the perceived health of your CSS (e.g., the number of different colours used, duplicate selectors, etc.).

Hopefully this has given you a small glimpse into some of the benefits and flexibility of using these tools to work with CSS.




es

How to test React components using Karma and webpack

I’m working on a project at Twitter that uses React and webpack. After a few conversations with @sokra last year, this is the setup I put in place for testing React components (authored using JSX and ES6) using Karma.

Dependencies

You’ll need to install various packages. It looks like a lot of dependencies, but all the non-Karma packages will be necessary for general module bundling during development.

Full set of required packages:

webpack entry file

If you use webpack-specific features in your modules (e.g., loaders, plugins) you will need to use webpack to build a test bundle. The fastest and simplest approach is to create a single, test-specific entry file.

Create a file named tests.bundle.js. Within this file, you create a webpack context to match all the files that conform to a naming pattern – in this case *.spec.js(x).

var context = require.context('.', true, /.+.spec.jsx?$/);
context.keys().forEach(context);
module.exports = context;

Next, you point Karma to this file.

Karma config

Karma is configured using a karma.conf.js file. The browsers, plugins, and frameworks are specified in the standard way.

Point Karma at the tests.bundle.js file, and run it through the relevant preprocessor plugins (see example below).

The karma-webpack plugin relies on 2 custom properties of the Karma config: webpack and webpackMiddleware. The value of the former must be a webpack config object.

module.exports = function (config) {
  config.set({
    browsers: [ 'Chrome' ],
    // karma only needs to know about the test bundle
    files: [
      'tests.bundle.js'
    ],
    frameworks: [ 'chai', 'mocha' ],
    plugins: [
      'karma-chrome-launcher',
      'karma-chai',
      'karma-mocha',
      'karma-sourcemap-loader',
      'karma-webpack',
    ],
    // run the bundle through the webpack and sourcemap plugins
    preprocessors: {
      'tests.bundle.js': [ 'webpack', 'sourcemap' ]
    },
    reporters: [ 'dots' ],
    singleRun: true,
    // webpack config object
    webpack: {
      devtool: 'inline-source-map',
      module: {
        loaders: [
          {
            exclude: /node_modules/,
            loader: 'babel-loader,
            test: /.jsx?$/
          }
        ],
      }
    },
    webpackMiddleware: {
      noInfo: true,
    }
  });
};

Rather than duplicating your webpack config, you can require it in the Karma config file and override the devtool value to get sourcemaps working.

var webpackConfig = require('./webpack.config');
webpackConfig.devtool = 'inline-source-map';

module.exports = function (config) {
  config.set({
    ...
    webpack: webpackConfig
  });
};

That’s all you need to do to configure Karma to use webpack to load your JSX, ES6 React components.




es

Redux modules and code-splitting

Twitter Lite uses Redux for state management and relies on code-splitting. However, Redux’s default API is not designed for applications that are incrementally-loaded during a user session.

This post describes how I added support for incrementally loading the Redux modules in Twitter Lite. It’s relatively straight-forward and proven in production over several years.

Redux modules

Redux modules comprise of a reducer, actions, action creators, and selectors. Organizing redux code into self-contained modules makes it possible to create APIs that don’t involve directly referencing the internal state of a reducer – this makes refactoring and testing a lot easier. (More about the concept of redux modules.)

Here’s an example of a small “redux module”.

// data/notifications/index.js

const initialState = [];
let notificationId = 0;

const createActionName = name => `app/notifications/${name}`;

// reducer
export default function reducer(state = initialState, action = {}) {
  switch (action.type) {
    case ADD_NOTIFICATION:
      return [...state, { ...action.payload, id: notificationId += 1 }];
    case REMOVE_NOTIFICATION:
      return state.slice(1);
    default:
      return state;
  }
}

// selectors
export const selectAllNotifications = state => state.notifications;
export const selectNextNotification = state => state.notifications[0];

// actions
export const ADD_NOTIFICATION = createActionName(ADD_NOTIFICATION);
export const REMOVE_NOTIFICATION = createActionName(REMOVE_NOTIFICATION);

// action creators
export const addNotification = payload => ({ payload, type: ADD_NOTIFICATION });
export const removeNotification = () => ({ type: REMOVE_NOTIFICATION });

This module can be used to add and select notifications. Here’s an example of how it can be used to provide props to a React component.

// components/NotificationView/connect.js

import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import { removeNotification, selectNextNotification } from '../../data/notifications';

const mapStateToProps = createStructuredSelector({
  nextNotification: selectNextNotification
});
const mapDispatchToProps = { removeNotification };

export default connect(mapStateToProps, mapDispatchToProps);
// components/NotificationView/index.js

import connect from './connect';
export class NotificationView extends React.Component { /*...*/ }
export default connect(NotificationView);

This allows you to import specific modules that are responsible for modifying and querying specific parts of the overall state. This can be very useful when relying on code-splitting.

However, problems with this approach are evident once it comes to adding the reducer to a Redux store.

// data/createStore.js

import { combineReducers, createStore } from 'redux';
Import notifications from './notifications';

const initialState = /* from local storage or server */

const reducer = combineReducers({ notifications });
const store = createStore(reducer, initialState);

export default store;

You’ll notice that the notifications namespace is defined at the time the store is created, and not by the Redux module that defines the reducer. If the “notifications” reducer name is changed in createStore, all the selectors in the “notifications” Redux module no longer work. Worse, every Redux module needs to be imported in the createStore file before it can be added to the store’s reducer. This doesn’t scale and isn’t good for large apps that rely on code-splitting to incrementally load modules. A large app could have dozens of Redux modules, many of which are only used by a few components and unnecessary for initial render.

Both of these issues can be avoided by introducing a Redux reducer registry.

Redux reducer registry

The reducer registry enables Redux reducers to be added to the store’s reducer after the store has been created. This allows Redux modules to be loaded on-demand, without requiring all Redux modules to be bundled in the main chunk for the store to correctly initialize.

// data/reducerRegistry.js

export class ReducerRegistry {
  constructor() {
    this._emitChange = null;
    this._reducers = {};
  }

  getReducers() {
    return { ...this._reducers };
  }

  register(name, reducer) {
    this._reducers = { ...this._reducers, [name]: reducer };
    if (this._emitChange) {
      this._emitChange(this.getReducers());
    }
  }

  setChangeListener(listener) {
    this._emitChange = listener;
  }
}

const reducerRegistry = new ReducerRegistry();
export default reducerRegistry;

Each Redux module can now register itself and define its own reducer name.

// data/notifications/index.js

import reducerRegistry from '../reducerRegistry';

const initialState = [];
let notificationId = 0;

const reducerName = 'notifications';

const createActionName = name => `app/${reducerName}/${name}`;

// reducer
export default function reducer(state = initialState, action = {}) {
  switch (action.type) {
    case ADD_NOTIFICATION:
      return [...state, { ...action.payload, id: notificationId += 1 }];
    case REMOVE_NOTIFICATION:
      return state.slice(1);
    default:
      return state;
  }
}

reducerRegistry.register(reducerName, reducer);

// selectors
export const selectAllNotifications = state => state[reducerName];
export const selectNextNotification = state => state[reducerName][0];

// actions
export const ADD_NOTIFICATION = createActionName(ADD_NOTIFICATION);
export const REMOVE_NOTIFICATION = createActionName(REMOVE_NOTIFICATION);

// action creators
export const addNotification = payload => ({ payload, type: ADD_NOTIFICATION });
export const removeNotification = () => ({ type: REMOVE_NOTIFICATION });

Next, we need to replace the store’s combined reducer whenever a new reducer is registered (e.g., after loading an on-demand chunk). This is complicated slightly by the need to preserve initial state that may have been created by reducers that aren’t yet loaded on the client. By default, once an action is dispatched, Redux will throw away state that is not tied to a known reducer. To avoid that, reducer stubs are created to preserve the state.

// data/createStore.js

import { combineReducers, createStore } from 'redux';
import reducerRegistry from './reducerRegistry';

const initialState = /* from local storage or server */

// Preserve initial state for not-yet-loaded reducers
const combine = (reducers) => {
  const reducerNames = Object.keys(reducers);
  Object.keys(initialState).forEach(item => {
    if (reducerNames.indexOf(item) === -1) {
      reducers[item] = (state = null) => state;
    }
  });
  return combineReducers(reducers);
};

const reducer = combine(reducerRegistry.getReducers());
const store = createStore(reducer, initialState);

// Replace the store's reducer whenever a new reducer is registered.
reducerRegistry.setChangeListener(reducers => {
  store.replaceReducer(combine(reducers));
});

export default store;

Managing the Redux store’s reducer with a registry should help you better code-split your application and modularize your state management.




es

Making SVG icon libraries for React apps

Using SVG is currently the best way to create icon libraries for apps. Icons built with SVG are scalable and adjustable, but also discrete, which allows them to be incrementally loaded and updated. In contrast, icons built as fonts cannot be incrementally loaded or updated. This alone makes SVG icons the better choice for high-performance apps that rely on code-splitting and incremental deploys.

This post describes how to make a package of React components from a library of SVG icons. Although I’m focusing on React, making any other type of package is also possible. At Twitter I used the approach described here to publish the company’s SVG icon library in several different formats: optimized SVGs, plain JavaScript modules, React DOM components, and React Native components.

Using the icons

The end result is a JavaScript package that can be installed and used like any other JavaScript package.

yarnpkg add @acme/react-icons

Each icon is available as an individually exported React component.

import IconCamera from '@acme/react-icons/camera';

This allows your module bundler to package only the icons that are needed, and icons can be efficiently split across chunks when using code-splitting. This is a significant advantage over icon libraries that require fonts and bundle all icons into a single component.

// entire icon library is bundled with your app
import Icon from '@acme/react-icons';
const IconCamera = <Icon name='camera' />;

Each icon is straightforward to customize (e.g., color and dimensions) on a per-use basis.

import IconCamera from '@twitter/react-icons/camera';
const Icon = (
  <IconCamera
    style={{ color: 'white', height: '2em' }}
  />
);

Although the icons render to SVG, this is an implementation detail that isn’t exposed to users of the components.

Creating components

Each React component renders an inline SVG, using path and dimensions data extracted from the SVG source files. A helper function called createIconComponent means that only a few lines of boilerplate are needed to create a component from SVG data.

import createIconComponent from './utils/createIconComponent';
import React from 'react';
const IconCamera = createIconComponent({
  content: <g><path d='...'></g>,
  height: 24,
  width: 24
});
IconCamera.displayName = 'IconCamera';
export default IconCamera;

This is an example of what the createIconComponent function looks like when building components for a web app like Twitter Lite, which is built with React Native for Web.

// createIconComponent.js
import { createElement, StyleSheet } from 'react-native-web';
import React from 'react';

const createIconComponent = ({ content, height, width }) =>
  (initialProps) => {
    const props = {
      ...initialProps,
      style: StyleSheet.compose(styles.root, initialProps.style),
      viewBox: `0 0 ${width} ${height}`
    };

    return createElement('svg', props, content);
  };

const styles = StyleSheet.create({
  root: {
    display: 'inline-block',
    fill: 'currentcolor',
    height: '1.25em',
    maxWidth: '100%',
    position: 'relative',
    userSelect: 'none',
    textAlignVertical: 'text-bottom'
  }
});

Setting the fill style to currentcolor allows you to control the color of the SVG using the color style property instead.

All that’s left is to use scripts to process the SVGs and generate each React component.

Creating icon packages

A complete example of one way to do this can be found in the icon-builder-example repository on GitHub.

The project structure of the example tool looks like this.

.
├── README.md
├── package.json
├── scripts/
    ├── build.js
    ├── createReactPackage.js
    └── svgOptimize.js
└── src/
    ├── alerts.svg
    ├── camera.svg
    ├── circle.svg
    └── ...

The build script uses SVGO to optimize the SVGs, extract SVG path data, and extract metadata. The example packager for React then uses templates to create a package.json and the React icon components shown earlier.

import createIconComponent from './utils/createIconComponent';
import React from 'react';
const ${componentName} = createIconComponent({
  height: ${height},
  width: ${width},
  content: <g>${paths}</g>
});
${componentName}.displayName = '${componentName}';
export default ${componentName};

Additional packagers can be included to build other package types from the same SVG source. When the underlying icon library changes, it only takes a couple of commands to rebuild hundreds of icons and publish new versions of each package.




es

Coronavirus | Lockdown chokes Maharashtra’s economic lifeline

The industrial hub faces a massive shortfall in revenues amid growing cost of combating the pandemic




es

Coronavirus | Maharashtra adds 1,089 new cases; Mumbai’s death toll stands at 462

Of the 1,089 new cases, Mumbai accounted for 748, with a cumulative tally of 12,142. With 75 new cases, Pune district’s tally has risen to 2,537.




es

Coronavirus | Indore remains worst hit in Madhya Pradesh with 3 more deaths

Bhopal, by comparison, has so far reported 679 cases and 24 deaths, with 354 patients, or more than half of those infected, having recovered.




es

Coronavirus | 390 new cases, 24 deaths in Gujarat; clashes in Ahmedabad

Two prominent medical experts — AIIMS director Dr Randeep Guleriya and Dr. Manish Suneja — flew into Ahmedabad on Friday following instructions from the Home Minister to guide local doctors




es

Coronavirus | 87 fresh cases, 1 death in Punjab

Major chunk of cases reported from Gurdaspur, Tarn Taran districts




es

Coronavirus | Odisha records 52 cases, highest single-day spike

43 cases from Ganjam district; State’s total mounts to 271




es

Bihar government to do random testing of migrants on arrival

TrueNat machines will be used for the first time in the State for virus screening




es

Coronavirus lockdown | With no work or food, workers brave the long march home from Uttar Pradesh

"We don’t want anything from the government. We just want to be dropped home," says a migrant worker from Chhattisgarh.




es

AMU academic session from Aug.

1,300 students of the varsity leave for home by special train




es

Take back changes in labour laws: Priyanka

‘U.P. government crushing their rights’




es

PMC Bank fraud case: Rakesh Wadhawan denied interim bail

HDIL promoter is lodged in Arthur Road Central Jail




es

PNB scam: HC rejects bail plea of accused who tested positive for COVID-19

Court says Hemant Bhatt needs to be treated at a govt. hospital




es

SC stays Orissa HC order on testing migrants

The Centre said it feared that the order may have a “cascading effect” on migrants of other States as well




es

Air India employees move HC over pay cut

They say it violates Centre’s directive




es

Coronavirus | 30 more test positive in J&K, cases mount to 823

Bandipora tops the list with 134 cases, followed by Srinagar at 129




es

No fees for medical screening, Maharashtra tells HC

Court wants decision on levying costs for transport of migrant workers




es

Coronavirus | Nine deaths, 130 cases reported in Bengal

This has been the highest spike in the number of cases in the State in a single day, taking the number of cases to 1,678




es

First special train with migrant workers leaves from Mumbai’s LTT

All 1,111 passengers underwent thermal screening at the station before departing for Basti in U.P.




es

Army will not be called into Mumbai, assures Uddhav Thackeray

In his address, CM says lockdown cannot go on forever




es

Tablighi Jamaat: 10 Indonesian nationals granted bail

Mumbai civil and sessions court gives anticipatory bail to two others who are in quarantine




es

Ganjam sparred community spread as migrants stay put at quarantine centres

All returnees are taken to centres from buses and trains




es

Punjab police arrest gangster Baljinder Singh

He is wanted for murder, attempt to murder and smuggling of weapons and drugs




es

Coronavirus | Tripura State Rifles men risk infection from BSF soldiers

State health officials are planning extensive tests




es

Muslim villagers help Hindu woman's last rites




es

Populist measures in the time of lockdown




es

Participators in Nizamuddin event must undergo test




es

COVID19 positive goes up to 16




es

Nizamuddin attendees from Assam found in UP




es

COVID19 cases 20




es

Lockdown washes away watermelon prospect




es

Rumour charge: AIUDF MLA arrested




es

SC asks for govt response on detention camp inmates




es

MASS condemn arrest of Gautam Navalakha and Anand Teltumbe




es

CRPF officer dies of COVID19 infection




es

The stirring of soul in the workplace [electronic resource] / Alan Briskin

Briskin, Alan, 1954-




es

Stop complainers and energy drainers [electronic resource] : how to negotiate work drama to get more done / Linda Byars Swindling

Swindling, Linda Byars, 1965-




es

Stranded in the Nyiri Desert [electronic resource] : a group case study / Matthew J. Drake ; Aimee A. Kane and Mercy Shitemi

Drake, Matthew, author




es

Strategic excellence in the architecture, engineering, and construction industries [electronic resource] : how AEC firms can develop and execute strategy using lean Six Sigma / Gerhard Plenert and Joshua J. Plenert

Plenert, Gerhard Johannes, author




es

Strategic information management [electronic resource] : challenges and strategies in managing information systems / R.D. Galliers and D.E. Leidner

Galliers, Robert, 1947-




es

A strategic-oriented implementation of projects [electronic resource] / Mihály Görög, PhD, Professor of Project Management

Görög, Mihály, 1951-




es

Strategic risk management [electronic resource] : new tools for competitive advantage in an uncertain age / Paul C. Godfrey, [and three others]

Godfrey, Paul C., author




es

Strategic value management [electronic resource] : stock value creation and the management of the firm / Juan Pablo Stegmann

Stegmann, Juan Pablo




es

Strategien zur Vermeidung von Burnout [electronic resource] : der mögliche Einfluss von Coping-Stilen / Markus H. Kipfer

Kipfer, Markus H, author




es

Strategische personalentwicklung in der praxis [electronic resource] : instrumente, erfolgsmodelle, checklisten, praxisbeispiele. / Christine Wegerich

Wegerich, Christine, author




es

Strategisches IT-Management [electronic resource] / Josephine Hofmann, Matthias Knoll (Hrsg.)