Sunday, 18 March 2018

How to use Ant Design library with Isomorphic style loader in React Starter Kit Project

I wanted to use isomorphic-style-loader in combination with Ant Design in a project built using React Starter Kit so that I could use the less processor to customise the default Ant styles. If you try to do it the way suggested by the Ant website using babel-import-plugin:

module: {
    rules: [
        ...
        loader: 'babel-loader,
        options: {
            ...
            plugins: [
                ...
                ['import', {libraryName: 'antd', style: true}]
            ] 
        }
    ]
}

Then you'll quickly run into this type of issue:

[19:51:42] Launching server...
/home/frontend/node_modules/antd/lib/style/index.less:1
(function (exports, require, module, __filename, __dirname) { @import "./themes/default";
                                                              ^

SyntaxError: Invalid or unexpected token
    at createScript (vm.js:80:10)
    at Object.runInThisContext (vm.js:139:10)
    at Module._compile (module.js:607:28)
    at Module._extensions..js (module.js:654:10)
    at Object.require.extensions.(anonymous function) [as .js] (/home/frontend/node_modules/babel-register/lib/node.js:152:7)
    at Module.load (module.js:556:32)
    at tryModuleLoad (module.js:499:12)
    at Function.Module._load (module.js:491:3)
    at Module.require (module.js:587:17)
    at require (internal/module.js:11:18)
error An unexpected error occurred: "Command failed.
Exit code: 1

Why it doesn't work


This is because the babel-import-plugin transforms this:

import {Button} from 'antd'

Into:

require('antd/lib/button/style')
var _button = require('antd/lib/button')

In hindsight, I don't know why/how I ever thought this could work.

The whole point of using the isomorphic style loader is to be explicit about which style files are required by each component so your sever can package only the ones required for the initial page render. This is antithetical to what is happening with the babel-import-plugin output, which is just injecting require statements into files and not keeping track of any association with components. If you were using the regular style-loader this would be sufficient to ensure the styles were injected into the head of the html document. If you are using the isomorphic-style-loader then you are generating  the html dynamically in your server code and using it's API to insert just the styles in the current React provider context, so none of the Ant styles will get included.

Furthermore, if you are using a common module config in your webpack.config.js file for the server and client then by enabling babel-import-plugin you'll end up trying to require the Ant '.less' files from your node app which will always fail because less syntax is not valid javascript and library code (stuff in /node_modules/ ) is excluded from webpack bundles by default!


So what is the answer?

I found the simplest solution is to use the isomorphic-style-loader withStyles function to wrap the top-level application React component with *all* the styles needed by my app. The downside is that you'll ship more styles than are required for the initial render but probably not that much more, since if you are using Ant in your project, you're probably using it extensively throughout the app. The upside is that the code is explicit. I created a JS module called withAntStyles.js:

import React from 'react'
import withStyles from 'isomorphic-style-loader/lib/withStyles'


// Styles includes
// Include just the styles you are using in your project. 
// You'll have to remember to add new ones as you go along
import antdStyles from 'antd/lib/style/index.less'
import layoutStyles from 'antd/lib/layout/style/index.less'
import gridStyles from 'antd/lib/grid/style/index.less'
...
import badgeStyles from 'antd/lib/badge/style/index.less'
import uploadStyles from 'antd/lib/upload/style/index.less'


function withAntStyles(AntStyledComponent) {
  return withStyles(
    antdStyles,
    layoutStyles,
    gridStyles,
    ...
    badgeStyles,
    uploadStyles
  )(AntStyledComponent)
}

export default withAntStyles

So now all you have to do is wrap your top most application React Component, e.g.:


import React from 'react'
import withStyles from 'isomorphic-style-loader/lib/withStyles'
import {Layout} from 'antd'
import withAntStyles from '../withAntStyles'

const {Content, Sider} = Layout

class AppLayout extends React.Component {
  static propTypes = {
    children: PropTypes.node.isRequired,
  }
  render(){
    return (
      <layout>
        ...
      </layout>
    )
  }
}

export default withAntStyles(AppLayout)

Finally I added the less-loader to my Webpack config:

          // Compile Less to CSS
          // https://github.com/webpack-contrib/less-loader
          // Install dependencies before uncommenting: yarn add --dev less-loader less
          {
            test: /\.less$/,
            use:[
              {
                loader: 'less-loader',
                options: {
                  modifyVars: {}, // custom theme overrides go here...
                  javascriptEnabled: true
                }
              }
            ]
          }

2 comments:

Eric Chen said...

THANKS! Just got these styles working thanks to your help setting it up :) I was dreading getting all these ant styles setup properly, but thank god it works.

Also, helped me understand how the babel loader and styles works in the isomorphic stylesheets better.

cuo said...

hi cris, i'm using react-starter-kit too
and i'm stucked -__- do you have github? i wanted to see your work if you dont mind