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.
- 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.
- 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
- Start by installing env-cmd
yarn add env-cmd --dev
Copy the code
- Create an environment variable file. Env.library
REACT_APP_NODE_ENV = "library"
Copy the code
- Modify the package. The json
{
"scripts": {
"build:library": "rm -rf build && env-cmd -f .env.library react-app-rewired build"}}Copy the code
- Configuration entry file
/* src/index.js */
import Button from './components/Button';;
export { Button };
Copy the code
- 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.
- 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
-
CRA β©οΈ