Customize theme on runtime

chenshuai2144afc163
Last updated on 2020-01-16 10:26:38

In the v4 version of pro we have added an online theme change feature. The intent of this feature is to allow designers to modify the main colors of Ant Design more quickly and preview them directly to save developers time. However, due to the immaturity of the program, many students encountered problems during the development, but could not find the problem. It was a waste of time, so we wrote this little article to explain our implementation and why it caused this problem.

background

In order to simplify the use of Pro, css-module is enabled, and the css-module is awesome in the project. Features, he can solve the problem that bothers each programmer, how to make a meaningful but not repeated css class name.

But for the component css-module will increase the cost, if you want to keep less source code when publishing components, it is a more troublesome thing. First of all, even if it is not kept less. The class name of each component will change. This makes it impossible to modify the style of a component with css.

Some components that don't want to be modified can use css-module, and each version is a new className, effectively preventing tampering.

The online theme plugin is mainly implemented by compiling less in the browser. First, he finds that the project is all less and extracts the selector with fewer variables. Combine a new less, that is, the color .less, and compile it in the browser with less.js, then overwrite the original properties. It is done in the following three steps:

Merge less

This function is mainly implemented by a plugin, antd-pro-merge-less, which scans all the less in src and merges them into a ./ temp / ant-design-pro.ess. Plugins are also the most problematic plugins, causing some of the fewer references to fail.

Convert css-module

However, as a plug-in for the theme, it is necessary to ensure that the class name is fixed, but it cannot be repeated. We did two things. First customize the class name. This can be done easily with the apigetLocalIdent of css-module. This is how the pro is handled.

const getLocalIdent = (context, localIdentName, localName) => {
  if (
    context.resourcePath.includes('node_modules') ||
    context.resourcePath.includes('ant.design.pro.less') ||
    //umi's global.less convention does not use css-module
    context.resourcePath.includes('global.less')
  ) {
    return localName;
  }

  // convert the uuid class name to the style of the ant-pro-file path.
  // like `.antd-pro-components-global-footer-index-links`
  const match = context.resourcePath.match(/src(.*)/);
  if (match && match[1]) {
    const antdProPath = match[1].replace('.less', '');
    const arr = slash(antdProPath)
      .split('/')
      .map(a => a.replace(/([A-Z])/g, '-$1'))
      .map(a => a.toLowerCase());
    return `antd-pro${arr.join('-')}-${localName}`.replace(/--/g, '-');
  }
  return localName;
};

This way, as long as the extraction is small, the class name is generated in the same way to ensure that the two are the same.

Using postcss-less-engine here, you can generate less syntax trees and modify it.

Extracting fewer variables

This step is done by antd-theme-webpack-plugin. It extracts all selectors with fewer variables in the configuration by traversing fewer syntax trees and combining them into a single color.less file. antd-theme-generator can see the implementation.

After extracting color.less, add the introduction of less in the html generated by the build through the ability of webpack. The default is

<script
  type="text/javascript"
  src="https://gw.alipayobjects.com/os/lib/less.js/3.8.1/less.min.js"
></script>
<link rel="stylesheet/less" type="text/css" href="/color.less" />

When you need to change the theme, you can compile the reference by calling window.less.modifyVars.

//Because the compilation time is too long and the rendering will be blocked, be sure to add a hint.
Window.less
  .modifyVars({
    '@ primary-color': primaryColor,
  })
  .then(() => {
    //Compile successfully
  })
  .catch(() => {
    //Compile failed
  });

Existing question

Using the above scheme can realize the function of online theme change, but at the same time introduce some new problems. So we don't recommend using it in a production environment. Pro v4 will refine it.

First of all, antd-pro-merge-less will cause some of the introduction of less to fail. And it is difficult to check. Compiling, merging and extracting will cause the hot update in development to slow down or become stuck. These will affect the development experience. Secondly, compiling less in the browser is not a good solution. The compilation of less will cause the main process of the browser to be stuck, and the whole page is stuck. The experience is very poor.

The antd-theme-webpack-plugin method of extracting variables also has some problems. The compiled css does not perform well in small details. Far from being perfect in the webpack compilation.

Future improvements

The current online theme is a demo, with some issues and is not suitable for adaptation in a formal environment. In v4 we will improve his performance. Start with a more elegant solution for less merging and extraction. Support for the full feature of less, which will not cause pits in development, but will reduce the impact of the development experience.

The extracted variable algorithm in antd-theme-webpack-plugin is also optimized to speed up and is extracted after it is no longer compiled. In the formal environment, multiple css will be compiled, and the dynamic import css will be used to change the theme and be more smooth. The experience is better. At the same time, the plugins are merged, and now the three plugins are heavily coupled and do not support hot swap and downgrade scenarios.