primers

What drove me to develop a UI Component Library using the Create React App[1]? Because the team chose Vue as the basic technology stack, it was very convenient to configure the production environment required to build the component library using the official VUE-CLI. For example, this set of woouI-Pro that we used internally produced components that met the team’s standards quickly after configuration based on CLI conventions. Can we quickly build a standardized component library using the React CRA? And began his journey of discovery with questions.

The target

We have summarized an environment configuration guide using the Vue technology stack. If you are interested, you can click here at πŸ‘‰

Our core goal is to configure a React UI Component Library based on CRA that provides a vuE-CLI experience.

demand

Now that we have set a goal, we should define what we need to accomplish that goal (yes, everyone is a product manager, 🐢).

  • CRA acts as the base scaffolding and does not eject
  • Use CSS Modules to manage CSS class names
  • Postcss precompiled plug-in can be configured
  • Configure code validation tools to ensure code standardization
  • Quickly generate component samples and documentation
  • You can Build a Library package for distribution

Based on these requirements, we will solve the problems encountered in fulfilling these requirements on a case-by-case basis.

start

CRA project initialization

The first thing to do is to create the project using CRA, which initializes the project in a single line of code

npx create-react-app my-app
Copy the code

The project file structure is as follows, which is quite succinct, and even suspicious of entering the wrong directory…

My-app β”œβ”€β”€ β”œ me.md β”œβ”€β”€ Package. json β”œβ”€β”€ Public β”‚ β”œβ”€ FavIcon. Ico β”‚ β”œβ”€ index.html β”‚ β”œβ”€β”€ β”œβ”€ β”œβ”€ β”œβ”€ β”œβ”€ β”œβ”€ β”œβ”€ β”œβ”€ β”œβ”€ β”œβ”€ β”œβ”€ β”œβ”€ β”œβ”€ β”œβ”€ β”œβ”€ β”œβ”€ β”œβ”€ β”œβ”€ β”œβ”€β”€ β”œ.js β”œβ”€ β”œ.js β”œβ”€ index.css β”œβ”€ index.js β”œβ”€ logo.svg β”œβ”€ serviceworker.jsCopy the code

Create React App Creates a React App as the name suggests, completely standardized scaffolding.

So, try introducing CSS Modules, following the documentation

Button.module.css

.error {
  background-color: red;
}
Copy the code

Button.js

import React, { Component } from 'react';
import styles from './Button.module.css'; // Import css modules
class Button extends Component {
  render() {
    // reference as a js object
    return <button className={styles.error}>Error Button</button>; }}Copy the code

The results of

<button class="Button_error_ax7yz">Error Button</button>
Copy the code

Button_error_ax7yz Black question.jpg! Can’t stand a component library CSS class name with MD5. I looked through the documentation and found no place to change the naming rules for CSS Modules. So what if you want to change this rule? CSS Modules are supported by CSS-Loader. Now you need to not eject CRA and change the configuration items of CSS-Loader.

React App Rewired configures Webpack

The React App is Rewired, and the instructions in Chinese are as follows:

This tool can modify the built-in webpack configuration of create-React-app without ‘eject’ or creating additional react-scripts, and then you’ll have all the features of create-React-app, And you can configure webpack plugins, loaders, and so on according to your needs.

These are exactly what we need to rely on to modify the CSS-Loader configuration.

Install the react – app – rewired

yarn add react-app-rewired --dev
Copy the code

Create a config-overrides. Js file in the project root directory

/* config-overrides.js */
module.exports = {
    webpack: function(config, env) {
        // Change the config
        // react-app-rewired intercepts and modifies the configuration, then scripts build according to the configuration
        returnconfig; }}Copy the code

Modify script directives in package.json

/* package.json */

  "scripts": {
-   "start": "react-scripts start",
+   "start": "react-app-rewired start",
  }
Copy the code

Modify the CSS-Loader configuration

The react-app-rewired module has a loader for modifying CSS Modules:

  • react-app-rewire-css-modules by @lnhrdt
  • react-app-rewire-css-modules-extensionless by @moxystudio

However, it seems that both loader extensions are not suitable for the current version of CRA (the current version of CRA already supports CSS Modules, my request is to modify the configuration).

But we can draw lessons from the code, reference code at the same time, we can also see our hijacked the react – how scripts webpack configuration, the file is in node_modules/react – scripts/config/webpack config. Js.

  1. Create a script to modify CSS Modules cssmoduleconfig. js in the root directory of the project.
/* scripts/cssModuleConfig.js */
const path = require('path');
const ruleChildren = loader= >
  loader.use || loader.oneOf || (Array.isArray(loader.loader) && loader.loader) || [];
const findIndexAndRules = (rulesSource, ruleMatcher) = > {
  let result = undefined;
  const rules = Array.isArray(rulesSource) ? rulesSource : ruleChildren(rulesSource);
  rules.some(
    (rule, index) = >
      (result = ruleMatcher(rule)
        ? { index, rules }
        : findIndexAndRules(ruleChildren(rule), ruleMatcher))
  );
  return result;
};
const findRule = (rulesSource, ruleMatcher) = > {
  const { index, rules } = findIndexAndRules(rulesSource, ruleMatcher);
  return rules[index];
};
const cssRuleMatcher = rule= >
  rule.test && String(rule.test) === String(/\.module\.css$/);
const sassRuleMatcher = rule= >
  rule.test && String(rule.test) === String(/\.module\.(scss|sass)$/);

const createLoaderMatcher = loader= > rule =>
  rule.loader && rule.loader.indexOf(`${path.sep}${loader}${path.sep}`)! = =- 1;
const cssLoaderMatcher = createLoaderMatcher('css-loader');
const sassLoaderMatcher = createLoaderMatcher('sass-loader');

module.exports = function(config, env, options) {
  const cssRule = findRule(config.module.rules, cssRuleMatcher);
  let cssModulesRuleCssLoader = findRule(cssRule, cssLoaderMatcher);
  const sassRule = findRule(config.module.rules, sassRuleMatcher);
  letsassModulesRuleCssLoader = findRule(sassRule, sassLoaderMatcher); cssModulesRuleCssLoader.options = { ... cssModulesRuleCssLoader.options, ... options }; sassModulesRuleCssLoader.options = { ... sassModulesRuleCssLoader.options, ... options };return config;
};
Copy the code

Find the corresponding loader and modify the options property.

  1. Modify the configuration of CSS Modules in config-overrides.
/* config-overrides.js */
const cssModuleConfig = require('./scripts/cssModuleConfig');
const loaderUtils = require('loader-utils');

module.exports = {
  webpack: function(config, env) {
    // Configure className in the namespace-foldername-localname format
    config = cssModuleConfig(config, env, {
      modules: {
        getLocalIdent: (context, localIdentName, localName, options) = > {
          const folderName = loaderUtils.interpolateName(context, '[folder]', options);
          const className =
            process.env.LIB_NAMESPACE + The '-' + folderName + The '-' + localName;
          returnclassName.toLowerCase(); }}});returnconfig; }};Copy the code

Results the acceptance

Button.module.css
.main {
    border: 1px solid;
}
Copy the code
Button.js
import styles from './Button.module.css'; // Import css modules
<button className={styles.main}>Button</button>
Copy the code
The results of
<button class="woo-button-main">Button</button>
Copy the code

The first step is to express! The next step should be the construction of the various components of the road, many components, both to show one by one and list the description, if completed step by step, it will consume a lot of energy. Is there a way to simplify the process? Then another artifact is to be offered:

React Styleguidist Generates example components

πŸ™React Styleguidist provides easy solutions to automatic property generation, component state presentation, documentation, and more, allowing you to focus on component development.

Install the react – styleguidist

yarn add react-styleguidist --dev
Copy the code

SRC directory creates the components directory

. β”” ─ ─ the SRC β”œ ─ ─ components β”œ ─ ─ Button β”œ ─ ─ Button. The module. The CSS / / CSS β”œ ─ ─ index. The js / / Button component entry β”œ ─ ─ the Readme. Md / / example...Copy the code

Modify the instructions in package.json

/* package.json */

  "scripts": {
-   "start": "react-app-rewired start",
+   "start": "styleguidist server",
  }
Copy the code

πŸš€ launch

Run the yarn start command to wait for a miracle to occur…

(Part of the code has been written based on the Button component)

No, wait, when I check the element I just configured the class name rule changed back? On second thought, the WebPack configuration loaded by Styleguidist is provided by CRA. We have to find a way to get Styleguidist to call Rewired to work so that what happens with the React-app-Rewired start happens on Styleguidist Server. Is that ok? Of course!

Configuration Styleguidist

Call the react-app-rewired configuration by creating a styleguide.config.js file

/* styleguide.config.js */
const { paths } = require('react-app-rewired');
const overrides = require('react-app-rewired/config-overrides');
const config = require(paths.scriptVersion + '/config/webpack.config');

module.exports = {
  webpackConfig: overrides.webpack(config(process.env.NODE_ENV), process.env.NODE_ENV)
};
Copy the code

πŸš€ Launch again

After the YARN start command is run, the CSS Modules configuration takes effect.

Configuration postcss

I have been using postCSS, a CSS precompilation tool, for two years. On the one hand, PostCSS is the standard for future CSS, and on the other hand, the plugin is installed on demand, which is faster than installing Node-Sass at a time. The postCSS file can be configured in any of the following ways: Normally, create a postcss.config.js file in the project root directory. The postCSs-loader reads the configuration and completes the compilation process according to the plug-in sequence. So postCSs-pxtorem.

postcss.config.js

/* postcss.config.js */
module.exports = {
  plugins: {
    'postcss-pxtorem': {
      rootValue: 16.propWhiteList: [
        The '*'.'! border'.'! border-top'.'! border-right'.'! border-bottom'.'! border-left'.'! border-width'].selectorBlackList: ['html'].mediaQuery: false}}};Copy the code

Button.module.css

.main {
    font-size: 16px;
}
Copy the code

The results of

.woo-button-main {
    font-size: 16px;
}
Copy the code

The expected result didn’t happen. CRA doesn’t have postCSS-loader, so Rewired is still needed

Rewired Postcss

Install the react – app – rewire – postcss

React-app-rewire-postcss has been tested and works properly. We can configure config-override.js according to the document

/* config-override.js */. const rewirePostcss =require('react-app-rewire-postcss');

module.exports = {
  webpack: function(config, env) {... config = rewirePostcss(config,true);
    returnconfig; }};Copy the code

Button.module.css

.main {
    font-size: 16px;
}
Copy the code

The results of

.woo-button-main {
    font-size: 1rem;
}
Copy the code

Done! In order to standardize Coding standards, we need to use tools to constrain it.

Standard code

Code Inspection With Prettier and an extension to ESLint, eslint-config-Prettier shuts off all rules that aren’t necessary or might conflict with Prettier. Eslint-plugin-prettier is the plug-in that adds the formatting rule for prettier.

The installation

yarn add prettier eslint-config-prettier eslint-plugin-prettier --dev
Copy the code

ESLint configuration

Create a.eslintrc file

{
  "extends": ["react-app"."plugin:prettier/recommended"]}Copy the code

Prettier configuration

Create the. Prettierrc file

{
  "printWidth": 90."singleQuote": true."semi": true
}
Copy the code

Configure Git commit verification

Next, configure Husky and Lint passively to ensure that your code is correct each time you commit it

yarn add husky lint-staged --dev
Copy the code

Modify the package. The json

{
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"}},"lint-staged": {
    "src/**/*.{js,jsx,json,css,md}": [
      "prettier --write"."git add"]}}Copy the code

Looking back at the goals we set at the beginning, only the most critical step remains, building UI Components as a Library.

Construction of the library

CRA provides only the ability to develop and build applications, but not the ability to build libraries. The react-app-rewire-create-react-library doesn’t work as well as React App Rewired. The react-app-rewire-create-react-library doesn’t work as well as React App Rewired.

Configuring environment Variables

Create a custom Library environment variable

  1. Start by installing env-cmd
 yarn add env-cmd --dev
Copy the code
  1. Create an environment variable file. Env.library
REACT_APP_NODE_ENV = "library"
Copy the code
  1. Modify the package. The json
{
    "scripts": {
        "build:library": "rm -rf build && env-cmd -f .env.library react-app-rewired build"}}Copy the code
  1. Configuration entry file
/* src/index.js */
import Button from './components/Button';;
export { Button };
Copy the code
  1. Package. json specifies the ES Module entry and main entry
{
    "module": "./src/index.js"."main": "./build/wooui-react.js"
}
Copy the code

The build script

The core idea of build library configuration is to omit all the steps such as code splitting, MD5 file name and template HTML modification in production environment construction, and then configure the output attribute parameter.

  1. Save the new packaging script reactLibraryconfig.js in the scripts directory:
/* scripts/reactLibraryConfig.js */
module.exports = function(config, env, options) {
  // Modify the configuration when the value is library
  if (env === 'library') {
    const srcFile = process.env.npm_package_module || options.module;
    const libName = process.env.npm_package_name || options.name;
    config.entry = srcFile;
    // Component library information
    config.output = {
      path: path.resolve('/'.'build'),
      filename: libName + '.js'.library: libName,
      libraryTarget: 'umd'
    };
    // Modify the Webpack Optimization property to remove the code splitting logic
    delete config.optimization.splitChunks;
    delete config.optimization.runtimeChunk;
    // Empty plugin only for building CSS names
    config.plugins = [];
    config.plugins.push(
      new MiniCssExtractPlugin({
        filename: libName + '.css'}));// The code comes from react-app-rewire-create-react-library
    // Generate externals to exclude external extensions such as React
    let externals = {};
    Object.keys(process.env).forEach(key= > {
      if (key.includes('npm_package_dependencies_')) {
        let pkgName = key.replace('npm_package_dependencies_'.' ');
        pkgName = pkgName.replace(/_/g.The '-');
        // below if condition addresses scoped packages : eg: @storybook/react
        if (pkgName.startsWith(The '-')) {
          const scopeName = pkgName.substr(1, pkgName.indexOf(The '-'.1) - 1);
          const remainingPackageName = pkgName.substr(
            pkgName.indexOf(The '-'.1) + 1,
            pkgName.length
          );
          pkgName = ` @${scopeName}/${remainingPackageName}`;
        }
        externals[pkgName] = `${pkgName}`; }}); config.externals = externals; }return config;
};
Copy the code

Calling the build script

React App Rewired: reactLibraryConfig: reactLibraryConfig: reactLibraryConfig: reactLibraryConfig: reactLibraryConfig Finally, the complete code is as follows

/* config-overrides.js */

const cssModuleConfig = require('./scripts/cssModuleConfig');
const loaderUtils = require('loader-utils');
const reactLibraryConfig = require('./scripts/reactLibraryConfig');
const rewirePostcss = require('react-app-rewire-postcss');

module.exports = {
  webpack: function(config, env) {
    // Configure CSS Modules
    config = cssModuleConfig(config, env, {
      modules: {
        getLocalIdent: (context, localIdentName, localName, options) = > {
          const folderName = loaderUtils.interpolateName(context, '[folder]', options);
          const className =
            process.env.LIB_NAMESPACE + The '-' + folderName + The '-' + localName;
          returnclassName.toLowerCase(); }}});/ / configuration Postcss
    config = rewirePostcss(config, true);
    // Configure the build information
    // When yarn Build :library is executed, process.env.REACT_APP_NODE_ENV is library
    config = reactLibraryConfig(config, process.env.REACT_APP_NODE_ENV);
    // Pass the react-app-rewired final configuration list
    returnconfig; }};Copy the code

Clearing the public directory

CRA copies all the contents of the public directory to the Build directory during production builds, so just keep index.html in that folder.

πŸ›°οΈπŸ‘¨πŸš€ A smooth landing

yarn build:library
Copy the code
Creating an optimized production build... Compiled successfully. File sizes after Gzip: 2.83 KB Build /wooui-react.js 684 B build/wooui-react.cssCopy the code

Build file directory, see two friends are waving to us ~

conclusion

Finally, in accordance with the established goals, to achieve all the requirements put forward before the start. The first is whether it is possible to quickly build a React based UI component library according to vue-CLI construction process. According to the initial requirements, I mined the solution step by step, encountered problems and difficulties, identified the core problems to be dealt with, clarified the solution ideas, found the solution, and then further enriched the requirements, so as to finally achieve the goal of building UI Component without eject CRA.

On second thought, is there a lot more that could be optimized? For example, the creation of a single component file, the generation of an entire entry file, the construction of a single component, and so on

This question is so, life, work, study and many other, is not the same?

Well, thanks for watching, and we’ll see you next time.

Oh, and the project’s source code is here:

wooui-react


  1. CRA β†©οΈŽ