background

Recently, the author was working on a visual editing platform for active pages. One day, he found that the editing effect of rich text on the page was inconsistent with the preview effect. After a simple check, he found that the reason was that the editing and preview of the platform differentiated routes and used react. lazy+ Import to load routes lazily. The edit route introduces the Antd UI library, but the preview route does not package THE ANTD-related code, and Antd’s global.css internally overwrites the default styles for multiple tags, causing problems

When exactly was Antd’s global.css introduced? What is the construction process of Antd UI library, and how is the solution of component loading on demand designed? With these doubts, the author started a debugging of Antd source code, from the perspective of source code analysis of Antd on demand loading and construction scheme

Use YARN Link to debug the local Antd package

The first step is to comb through the Antd framework’s overall build process. How to debug a local Antd package on a local project? The answer is Yarn Link

Perform the following steps

  1. Git Clone antD-design repository
  2. Execute in the antD-design rootyarn installyarn run buildStart by building the source code
  3. Antd-design in the root directoryyarn link
  4. Execute in the local project root (the project for which ANTD needs to be debugged)yarn link antd

After the preceding steps are complete, run yarn Run Dev to start the local project. Check whether the Antd component is displayed properly. If no accident occurs, the console of the browser displays the following error

This is because the react package of peerDependencies in Antd is not the same as the react package of peerDependencies in Antd. The solution is to rely on the React package of node_module in Antd

Link CD /workspace/antd/node_modules/react YARN link link reactCopy the code

After completing the above steps, try to modify the source code in es or lib under the Antd root directory. At this time, the webpack-dev-server of the local project can listen to the Antd source code changes and reload the page, indicating that the debugging process has gone through.

Antd is built by antD-Tools scaffolding, so modify the component code under components and run YARN Run compile to recompile

{"scripts": {compile: antd-tools run compile}}Copy the code

The product of Antd construction was analyzed

The author downloaded Antd 4.15.3 here. It can be seen from the construction product of its root directory that Antd builds three directories es, Lib and dist, which are used to support the on-demand and full introduction of component libraries

  • Es and lib

    Es and Lib correspond to esModule and CJS versions respectively

  • dist

    Dist is the full version of Antd, such as ANTD. js, antD. CSS, etc

Import file analysis

From the partial configuration of Antd’s package.json, you can see that the Antd entry file is configured with the main and Module fields. Represents the corresponding entry files of ANTD package when import {button} from ‘ANTd’ is performed in different environments

{
  "name": "antd"."main": "lib/index.js"."module": "es/index.js"."scripts": {
    "build": "npm run compile && npm run dist"."compile": "npm run clean && antd-tools run compile"."compile:less": "antd-tools run compile:less"."dist": "antd-tools run dist"."dist:esbuild": "ESBUILD=true npm run dist"."clean": "antd-tools run clean && rm -rf es lib",}}Copy the code
  • Main: Defines the entry file for the NPM package, which can be used by both Browser and Node environments
  • Module: Entry file that defines the ESM specification for NPM packages. Browser and Node environments can use this file
  • Browser: defines the entry file for NPM packages in browser environment

In the configuration of Webpack, resolve. MainFields can be used to specify which entry field of the NPM package is preferred

Antd’s load on Demand solution

As you can see from the packaged ES and lib directories, there are two options for loading Antd component libraries on demand: tree-shaking based on build tools, and importing component styles independently using babel-plugin-import. Because Antd already packages CSS and JS separately in lib and ES directories for each component, it can be imported directly from the outside on demand

The following is the directory structure of the button component in the lib directory after packaging. For example, if you use the Button component, you only need to import these two entry files to load the component on demand

├─ loadingIcon.d.Bass Exercises ─ LoadingIcon.js ├─ button-group.d.BASS Exercises ─ button-group.js ├ ─ ─ the index, which s ├ ─ ─ index. The js / / component entry documents └ ─ ─ style ├ ─ ─ index. The CSS ├ ─ ─ the index, which s ├ ─ ─ index. The js / / style entry documents ├ ─ ─ but less ├ ─ ─ Mixins. Less └ ─ ─ RTL. LessCopy the code
  • Tree Shaking

    Today, webPack, Rollup, and other build tools are Tree Shaking. It takes advantage of the import syntax of ESM modules, analyzes through AST syntax trees and removes unused code. In webpack, resolve. MainFields is used to specify the entry file priority of the NPM package. However, tree shaking only deals with the JS part, and the CSS loading of components needs to be introduced manually. There are usually two ways to load a component in full by adding import ‘ANTd /dist/ antD.css’ to the entry file, or manually importing component styles on demand

  • babel-plugin-import

    Babel-plugin-import is a plug-in of Babel, which can transform the import syntax in the source code during the compilation stage of Babel-Loader. After compilation, it can be directly introduced into the corresponding CSS and JS files of components to achieve on-demand loading

  / / the source code
  import {Button} from 'antd'
  // using babel-plugin-import will be changed during the babel-loader compilation phase
  var _button = require('antd/lib/button');
  require('antd/lib/button/style.js');
Copy the code

In the antd/lib/index.js file, you can also see that ANTd detects its full import as a component and throws a suggestion to use babel-plugin-import for on-demand loading.

Babel-plugin-import is an extension to babel-plugin-import.

  if(ENV ! = ='production'&& ENV ! = ='test' && typeof console! = ='undefined' && console.warn && 
      typeof window! = ='undefined') {
      // eslint-disable-next-line no-console
      console.warn('You are using a whole package of antd, ' + 'please use https://www.npmjs.com/package/babel-plugin-import to reduce app bundle size.');
  }

Copy the code

The global antd. The CSS

When was antD’s global.css introduced? The author took button component as an example to debug Antd source code locally, and found the answer to the problem when referring to the code in Button /style/index.tsx.

< style/core/global.less > < style/core/global.less > < button/style/index.tsx The result is a button/style/index.js file in lib and es. Import {button} from ‘antd’ : import {button} from ‘antd’ : import {button} from ‘antd’ : import {button} from ‘antd’ : import {button} from ‘antd’ : import {button} from ‘antd

In fact, every component of Antd has a style/core/global.less file imported into the style entry file, so is that why any component global style imported into Antd can be loaded in

Button source component directory

Button ├ ─ ─ LoadingIcon. TSX ├ ─ ─ the button - group. The TSX ├ ─ ─ button. The TSX ├ ─ ─ index. En - US. Md ├ ─ ─ index. The TSX / / module entry documents, introduces the button. The TSX, button - group ├ ─ ─ but useful - CN. Md └ ─ ─ style ├ ─ ─ but less / / component style source ├ ─ ─ index. The TSX / / style entry documents, Index. Less, And global style here be introduced ├ ─ ─ a mixin. Less └ ─ ─ RTL. Less / / components/style (antd put general style directory) style ├ ─ ─ color │ ├ ─ ─ colors. The less ├ ─ ─ Compact. The less ├ ─ ─ the core │ ├ ─ ─ base. The less │ ├ ─ ─ global. The less / / global style │ ├ ─ ─ but less │ ├ ─ ─ motion │ │ └ ─ ─ zoom. Less │ └ ─ ─ Motion. Less ├ ─ ─ dark. Less ├ ─ ─ but less ├ ─ ─ index. The TSX ├ ─ ─ mixins │ ├ ─ ─ box. The less └ ─ ─ themes └ ─ ─ index. The lessCopy the code

Button component structure in lib directory

├─ loadingIcon.d.Bass Exercises ─ LoadingIcon.js ├─ button-group.d.BASS Exercises ─ button-group.js ├ ─ ─ the index, which s ├ ─ ─ index. The js └ ─ ─ style ├ ─ ─ index. The CSS ├ ─ ─ the index, which s ├ ─ ─ index. The js ├ ─ ─ but less ├ ─ ─ a mixin. Less └ ─ ─ rtl.lessCopy the code

In fact, there are many people on the Internet who have tried various solutions to Antd’s global style problem, which will not be discussed here

How to elegantly and completely solve the ANTD global style problem

Antd construction scheme

Antd – the tools of scaffolding

The above part only analyzes how Antd supports on-demand loading from the build product. Next, the author analyzes the specific build implementation from a series of build instructions in package.json nPM-scripts

{
  "name": "antd"."main": "lib/index.js"."module": "es/index.js"."scripts": {
    "build": "npm run compile && npm run dist"."compile": "npm run clean && antd-tools run compile"."compile:less": "antd-tools run compile:less"."dist": "antd-tools run dist"."dist:esbuild": "ESBUILD=true npm run dist"."clean": "antd-tools run clean && rm -rf es lib",}}Copy the code

Npm-script declares build and coppile. Executing build will pack lib,es and dist. Executing compile will pack only lib,es

Antd-tools is the antD team’s independently packaged scaffolding project, specialized in building and publishing related work, such as handling build, Lint,publish, etc., packaging gulp tasks, starting webpack compilation, jest, ESLint, Babel, etc

Debug breakpoints on node scaffolding commands

Breakpoint debugging is a common technique used in analyzing source code. How to debug node scaffolding projects in vscode? The scaffolding command is used to execute the npm-script command defined by vscode. To debug the npm-script command in vscode, you can do the following steps

  1. Npm-scripts in Antd project adds a command to start scaffolding using node commands instead
  "debug": "node --inspect-brk=9999 ./node_modules/.bin/antd-tools-run compile"
Copy the code
  1. Configuration launch. Json

Follow the steps marked in the figure to configure, and finally click the green triangle to run, you can automatically break in the entry file. For the specific configuration field of launch.json, you can see VSCode configuration details

Antd-tools source code analysis

Instruction registration for scaffolding

When antD-tools is executed on the cli terminal, the corresponding path file in the bin field of package.json of tools will be found and executed. The following is the configuration of the bin field of package.json of ANTD-Tools

"name":"@ant-design/tools"."bin": {
   "antd-tools": "bin/antd-tools.js"."antd-tools-run": "bin/antd-tools-run.js",},Copy the code

Antd /toolsbin/antd-tools.js finally executes tools/lib/cli/run.js, and in run.js takes command line arguments from process.argv to execute the corresponding gulp task

Graph TD tools/bin/antd-tools.js --> tools/lib/cli/indx.js --> Run command --> tools/lib/cli/run.js --> gulp --> Execute the task defined in gulpfile.js

In antd/tools/lib/cli/run js, introduced a gulp, call runTask method gulp missions

#! /usr/bin/env node
require('colorful').colorful();
const gulp = require('gulp');
const program = require('commander');

program.parse(process.argv);

//toRun indicates the task names entered in the command line, such as compile, dist, etc
function runTask(toRun) {
  const metadata = { task: toRun };
  // Gulp >= 4.0.0 (doesn't support events)
  const taskInstance = gulp.task(toRun);
  if (taskInstance === undefined) {
    gulp.emit('task_not_found', metadata);
    return;
  }
  const start = process.hrtime();
  gulp.emit('task_start', metadata);
  try {
    taskInstance.apply(gulp);
    metadata.hrDuration = process.hrtime(start);
    gulp.emit('task_stop', metadata);
    gulp.emit('stop');
  } catch (err) {
    err.hrDuration = process.hrtime(start);
    err.task = metadata.task;
    gulp.emit('task_err', err); }}const task = program.args[0];

if(! task) { program.help(); }else {
  require('.. /gulpfile');
  // Accept command line arguments to execute the gulp task
  runTask(task); 
}

Copy the code

The postinstall hook overwrites npmScripts

The NPM postinstall hook is declared in the scripts of package.json of ANTD-tools. It is used to inject NPM Script specific packaging commands into ANTD package.json after yarn install is executed

"name":"@ant-design/tools"."bin": {
   "antd-tools": "bin/antd-tools.js"."antd-tools-run": "bin/antd-tools-run.js",},"scripts": {"postinstall": "node lib/init.js".// This script is executed when yarnn install is executed
 }
 
Copy the code

lib/init.js

    function addConfigHooks(cfg, projectDir) {
    if(! cfg.scripts) { cfg.scripts = {}; }if (cfg.scripts.pub) {
        return false;
    }
    cfg.scripts = Object.assign(cfg.scripts, {
        dist: 'antd-tools run dist'.compile: 'antd-tools run compile'.clean: 'antd-tools run clean'.start: 'antd-tools run start'.site: 'antd-tools run site'.deploy: 'antd-tools run update-self && antd-tools run deploy'.'just-deploy': 'antd-tools run just-deploy'.pub: 'antd-tools run update-self && antd-tools run pub'});if (cfg.scripts.prepublish) {
        cfg.scripts['pre-publish'] = cfg.scripts.prepublish;
    }

    cfg.scripts.prepublish = 'antd-tools run guard';

    writeFile(pathJoin(projectDir, 'package.json'), JSON.stringify(cfg, null.2));

    return true;
    }

Copy the code

The compile command

The compile directive is used to package all the component code to build the lib and es directories. The compile task is defined in gulpfile.js

  gulp.task(
     'compile',
      gulp.series(
      gulp.parallel('compile-with-es'.'compile-with-lib'),'compile-finalize')); gulp.task('compile-with-es'.done= > {
     console.log('[Parallel] Compile to es... ');
     compile(false).on('finish', done);
   });
   gulp.task('compile-with-lib'.done= > {
     console.log('[Parallel] Compile to js... ');
     compile().on('finish', done);
   });
   
Copy the code

As you can see in the code, the compile task executes three tasks. The compile-with-es and compile-with-lib tasks are called compile functions, and the compile- Finalize function is called Finalize function. Here we analyze the compile function and Finalize function one by one

  • The compile function
    
 // modules indicates whether to build the ESM version
function compile(modules) {
  const { compile: { transformTSFile, transformFile } = {} } = getConfig(); rimraf.sync(modules ! = =false ? libDir : esDir);

  // =============================== LESS ===============================
  const less = gulp
    .src(['components/**/*.less'])
    .pipe(
      through2.obj(function (file, encoding, next) {
        // Replace content
        const cloneFile = file.clone();
        const content = file.contents.toString().replace(/^\uFEFF/.' ');

        cloneFile.contents = Buffer.from(content);

        // Clone for css here since `this.push` will modify file.path
        const cloneCssFile = cloneFile.clone();

        this.push(cloneFile);

        // Transform less file
        if (
          file.path.match(/(\/|\\)style(\/|\\)index\.less$/) ||
          file.path.match(/(\/|\\)style(\/|\\)v2-compatible-reset\.less$/)
        ) {
          transformLess(cloneCssFile.contents.toString(), cloneCssFile.path)
            .then(css= > {
              cloneCssFile.contents = Buffer.from(css);
              cloneCssFile.path = cloneCssFile.path.replace(/\.less$/.'.css');
              this.push(cloneCssFile);
              next();
            })
            .catch(e= > {
              console.error(e);
            });
        } else {
          next();
        }
      })
    )
    .pipe(gulp.dest(modules === false ? esDir : libDir));
    
  const assets = gulp
    .src(['components/**/*.@(png|svg)'])
    .pipe(gulp.dest(modules === false ? esDir : libDir));
  let error = 0;

  // =============================== FILE ===============================
  let transformFileStream;

  if (transformFile) {
    transformFileStream = gulp
      .src(['components/**/*.tsx'])
      .pipe(
        through2.obj(function (file, encoding, next) {
          let nextFile = transformFile(file) || file;
          nextFile = Array.isArray(nextFile) ? nextFile : [nextFile];
          nextFile.forEach(f= > this.push(f));
          next();
        })
      )
      .pipe(gulp.dest(modules === false ? esDir : libDir));
  }

  // ================================ TS ================================
  const source = [
    'components/**/*.tsx'.'components/**/*.ts'.'typings/**/*.d.ts'.'! components/**/__tests__/**',];// allow jsx file in components/xxx/
  if (tsConfig.allowJs) {
    source.unshift('components/**/*.jsx');
  }

  // Strip content if needed
  let sourceStream = gulp.src(source);
  if (modules === false) {
    sourceStream = sourceStream.pipe(
      stripCode({
        start_comment: '@remove-on-es-build-begin'.end_comment: '@remove-on-es-build-end',})); }if (transformTSFile) {
    sourceStream = sourceStream.pipe(
      through2.obj(function (file, encoding, next) {
        let nextFile = transformTSFile(file) || file;
        nextFile = Array.isArray(nextFile) ? nextFile : [nextFile];
        nextFile.forEach(f= > this.push(f)); next(); })); }const tsResult = sourceStream.pipe(
    ts(tsConfig, {
      error(e) {
        tsDefaultReporter.error(e);
        error = 1;
      },
      finish: tsDefaultReporter.finish,
    })
  );

  function check() {
    if(error && ! argv['ignore-error']) {
      process.exit(1);
    }
  }

  tsResult.on('finish', check);
  tsResult.on('end', check);
  
  // Use gulp-babel to convert the code
  const tsFilesStream = babelify(tsResult.js, modules);
  const tsd = tsResult.dts.pipe(gulp.dest(modules === false ? esDir : libDir));
  
  return merge2([less,tsFilesStream, tsd, assets,transformFileStream].filter(s= > s));
  
}



function babelify(js, modules) {
  const babelConfig = getBabelCommonConfig(modules);
  delete babelConfig.cacheDirectory;
  if (modules === false) {
    babelConfig.plugins.push(replaceLib);
  }
  const stream = js.pipe(babel(babelConfig)).pipe(
    through2.obj(function z(file, encoding, next) {
      // Make a copy of the file to avoid subsequent changes to the original file
      this.push(file.clone());
      
      // Match file path is /style/index.js
      if (file.path.match(/(\/|\\)style(\/|\\)index\.js/)) {
        const content = file.contents.toString(encoding);
        if (content.indexOf("'react-native'")! = = -1) {
          // actually in [email protected], this case will never run,
          // since we both split style/index.mative.js style/index.js
          // but let us keep this check at here
          // in case some of our developer made a file name mistake ==
          next();
          return;
        }
        
    
        file.contents = Buffer.from(cssInjection(content));
        
        // Replace /style/index.js with css.js
        file.path = file.path.replace(/index\.js/.'css.js');
        this.push(file);
        next();
      } else{ next(); }}));return stream.pipe(gulp.dest(modules === false ? esDir : libDir));
}

// Replace some CSS code
function cssInjection(content) {
  return content
    .replace(/\/style\/? '/g."/style/css'")
    .replace(/\/style\/?" /g.'/style/css"')
    .replace(/\.less/g.'.css');
}

Copy the code
  • compile-finalize

The compile-Finalize task is very simple, it just implements finalize function, and Finalize only generates a component.less file in the style directory below lib. Antd-tools.config. js is the antD-Tools scaffold configuration file in the root directory of ANTd

gulp.task('compile-finalize', done => { // Additional process of compile finalize const { compile: { finalize } = {} } = getConfig(); if (finalize) { console.log('[Compile] Finalization... '); finalize(); } done(); }); Function getConfig() {const configPath = getProjectPath('.antd-tools.config.js'); // Antd-tools.config.js function getConfig() {const configPath = getProjectPath('.antd-tools.config.js'); if (fs.existsSync(configPath)) { return require(configPath); } return {}; } //antd-tools.config.js finalizeCompile function finalizeCompile() {if (fs.existssync (path.join(__dirname, './lib'))) { // Build a entry less file to dist/antd.less const componentsPath = path.join(process.cwd(), 'components');  let componentsLessContent = ''; // Build components in one file: lib/style/components.less fs.readdir(componentsPath, (err, files) => { files.forEach(file => { if (fs.existsSync(path.join(componentsPath, file, 'style', 'index.less'))) { componentsLessContent += `@import ".. /${path.join(file, 'style', 'index.less')}"; \n`; }}); fs.writeFileSync( path.join(process.cwd(), 'lib', 'style', 'components.less'), componentsLessContent, ); }); }}Copy the code

Dist command

Dist directive is used to package the full antD.js. This directive uses gulp to start webpack compilation

Graph TD gulp do dist task --> call getProjectPath to get webPackFconfig.js from Antd --> call getWebpackConfig to get general package configuration --> Export multiple copies of webpackConfig configuration --> Start Webpack with antD root directory index.js as the entry to build
  • Dist task

    The dist task itself does nothing but start webpack for compilation, and the webpack.config configuration is placed in the root directory of ANTD

onst webpack = require('webpack'); const babel = require('gulp-babel'); const rimraf = require('rimraf'); const { getProjectPath, injectRequire, getConfig } = require('./utils/projectHelper'); gulp.task( 'dist', gulp.series(done => { dist(done); })); function dist(done) { rimraf.sync(getProjectPath('dist')); process.env.RUN_ENV = 'PRODUCTION'; Const webpackConfig = require(getProjectPath('webpack.config.js')); webpack(webpackConfig, (err, stats) => { if (err) { console.error(err.stack || err); if (err.details) { console.error(err.details); } return; } const info = stats.toJson(); const { dist: { finalize } = {}, bail } = getConfig(); if (stats.hasErrors()) { (info.errors || []).forEach(error => { console.error(error); }); // https://github.com/ant-design/ant-design/pull/31662 if (bail) { process.exit(1); } } if (stats.hasWarnings()) { console.warn(info.warnings); } const buildInfo = stats.toString({ colors: true, children: true, chunks: false, modules: false, chunkModules: false, hash: false, version: false, }); console.log(buildInfo); If (Finalize) {console.log('[Dist] Finalization... '); finalize(); } done(0); }); }Copy the code
  • Under the antd webpack. Config. Js

    Call getWebpackConfig to get the basic configuration, export multiple WebPack configurations, and package different theme color CSS styles for Antd

/* eslint no-param-reassign: 0 */
// This config is for building dist files
const getWebpackConfig = require('@ant-design/tools/lib/getWebpackConfig');
const IgnoreEmitPlugin = require('ignore-emit-webpack-plugin');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const { ESBuildPlugin, ESBuildMinifyPlugin } = require('esbuild-loader');
const darkVars = require('./scripts/dark-vars');
const compactVars = require('./scripts/compact-vars');

const { webpack } = getWebpackConfig;

// noParse still leave `require('./locale' + name)` in dist files
// ignore is better: http://stackoverflow.com/q/25384360
function ignoreMomentLocale(webpackConfig) {
  delete webpackConfig.module.noParse;
  webpackConfig.plugins.push(new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/));
}

function addLocales(webpackConfig) {
  let packageName = 'antd-with-locales';
  if (webpackConfig.entry['antd.min']) {
    packageName += '.min';
  }
  webpackConfig.entry[packageName] = './index-with-locales.js';
  webpackConfig.output.filename = '[name].js';
}

function externalMoment(config) {
  config.externals.moment = {
    root: 'moment',
    commonjs2: 'moment',
    commonjs: 'moment',
    amd: 'moment',
  };
}

function injectWarningCondition(config) {
  config.module.rules.forEach(rule => {
    // Remove devWarning if needed
    if (rule.test.test('test.tsx')) {
      rule.use = [
        ...rule.use,
        {
          loader: 'string-replace-loader',
          options: {
            search: 'devWarning(',
            replace: "if (process.env.NODE_ENV !== 'production') devWarning(",
          },
        },
      ];
    }
  });
}

function processWebpackThemeConfig(themeConfig, theme, vars) {
  themeConfig.forEach(config => {
    ignoreMomentLocale(config);
    externalMoment(config);

    // rename default entry to ${theme} entry
    Object.keys(config.entry).forEach(entryName => {
      config.entry[entryName.replace('antd', `antd.${theme}`)] = config.entry[entryName];
      delete config.entry[entryName];
    });

    // apply ${theme} less variables
    config.module.rules.forEach(rule => {
      // filter less rule
      if (rule.test instanceof RegExp && rule.test.test('.less')) {
        const lessRule = rule.use[rule.use.length - 1];
        if (lessRule.options.lessOptions) {
          lessRule.options.lessOptions.modifyVars = vars;
        } else {
          lessRule.options.modifyVars = vars;
        }
      }
    });

    const themeReg = new RegExp(`${theme}(.min)?\\.js(\\.map)?$`);
    // ignore emit ${theme} entry js & js.map file
    config.plugins.push(new IgnoreEmitPlugin(themeReg));
  });
}

const webpackConfig = getWebpackConfig(false);
const webpackDarkConfig = getWebpackConfig(false);
const webpackCompactConfig = getWebpackConfig(false);

webpackConfig.forEach(config => {
  injectWarningCondition(config);
});

if (process.env.RUN_ENV === 'PRODUCTION') {
  webpackConfig.forEach(config => {
    ignoreMomentLocale(config);
    externalMoment(config);
    addLocales(config);
    // Reduce non-minified dist files size
    config.optimization.usedExports = true;
    // use esbuild
    if (process.env.ESBUILD || process.env.CSB_REPO) {
      config.plugins.push(new ESBuildPlugin());
      config.optimization.minimizer[0] = new ESBuildMinifyPlugin({
        target: 'es2015',
      });
    }

    config.plugins.push(
      new BundleAnalyzerPlugin({
        analyzerMode: 'static',
        openAnalyzer: false,
        reportFilename: '../report.html',
      }),
    );
  });

  processWebpackThemeConfig(webpackDarkConfig, 'dark', darkVars);
  processWebpackThemeConfig(webpackCompactConfig, 'compact', compactVars);
}

module.exports = [...webpackConfig, ...webpackDarkConfig, ...webpackCompactConfig];

Copy the code
  • getWebpackConfig.js

Basic configuration encapsulation of Webapck

const { getProjectPath, resolve, injectRequire } = require('./utils/projectHelper'); injectRequire(); // Show warning for webpack process.traceDeprecation = true; // Normal requirement const path = require('path'); const webpack = require('webpack'); const WebpackBar = require('webpackbar'); const webpackMerge = require('webpack-merge'); const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin'); const FilterWarningsPlugin = require('webpack-filter-warnings-plugin'); const CleanUpStatsPlugin = require('./utils/CleanUpStatsPlugin'); const svgRegex = /\.svg(\? v=\d+\.\d+\.\d+)? $/; const svgOptions = { limit: 10000, minetype: 'image/svg+xml', }; const imageOptions = { limit: 10000, }; Function getWebpackConfig(modules) {const PKG = require(getProjectPath('package.json')); const babelConfig = require('./getBabelCommonConfig')(modules || false); if (modules === false) { babelConfig.plugins.push(require.resolve('./replaceLib')); } const config = { devtool: 'source-map', output: { path: getProjectPath('./dist/'), filename: '[name].js', }, resolve: { modules: ['node_modules', path.join(__dirname, '../node_modules')], extensions: [ '.web.tsx', '.web.ts', '.web.jsx', '.web.js', '.ts', '.tsx', '.js', '.jsx', '.json', ], alias: { [pkg.name]: process.cwd(), }, }, node: [ 'child_process', 'cluster', 'dgram', 'dns', 'fs', 'module', 'net', 'readline', 'repl', 'tls', ].reduce( (acc, name) => ({ ... acc, [name]: 'empty', }), {} ), module: { noParse: [/moment.js/], rules: [ { test: /\.jsx?$/, exclude: /node_modules/, loader: resolve('babel-loader'), options: babelConfig, }, { test: /\.tsx?$/, use: [ { loader: resolve('babel-loader'), options: babelConfig, }, { loader: resolve('ts-loader'), options: { transpileOnly: true, }, }, ], }, { test: /\.css$/, use: [ MiniCssExtractPlugin.loader, { loader: resolve('css-loader'), options: { sourceMap: true, }, }, { loader: resolve('postcss-loader'), options: { postcssOptions: { plugins: ['autoprefixer'], }, sourceMap: true, }, }, ], }, { test: /\.less$/, use: [ MiniCssExtractPlugin.loader, { loader: resolve('css-loader'), options: { sourceMap: true, }, }, { loader: resolve('postcss-loader'), options: { postcssOptions: { plugins: ['autoprefixer'], }, sourceMap: true, }, }, { loader: resolve('less-loader'), options: { lessOptions: { javascriptEnabled: true, }, sourceMap: true, }, }, ], }, // Images { test: svgRegex, loader: resolve('url-loader'), options: svgOptions, }, { test: /\.(png|jpg|jpeg|gif)(\?v=\d+\.\d+\.\d+)?$/i, loader: resolve('url-loader'), options: imageOptions, }, ], }, plugins: [ new CaseSensitivePathsPlugin(), new webpack.BannerPlugin(` ${pkg.name} v${pkg.version} Copyright 2015-present, Alipay, Inc. All rights reserved. '), new WebpackBar({name: '🚚 Ant Design Tools', color: Alipay, Inc. All rights reserved. '), new WebpackBar({name: '🚚 Ant Design Tools', color: '#2f54eb', }), new CleanUpStatsPlugin(), new FilterWarningsPlugin({ // suppress conflicting order warnings from mini-css-extract-plugin. // ref: https://github.com/ant-design/ant-design/issues/14895 // see https://github.com/webpack-contrib/mini-css-extract-plugin/issues/250 exclude: /mini-css-extract-plugin[^]*Conflicting order between:/, }), ], performance: { hints: false, }, }; if (process.env.RUN_ENV === 'PRODUCTION') { const entry = ['./index']; // Common config config.externals = { react: { root: 'React', commonjs2: 'react', commonjs: 'react', amd: 'react', }, 'react-dom': { root: 'ReactDOM', commonjs2: 'react-dom', commonjs: 'react-dom', amd: 'react-dom', }, }; config.output.library = pkg.name; config.output.libraryTarget = 'umd'; config.optimization = { minimizer: [ new UglifyJsPlugin({ cache: true, parallel: true, sourceMap: true, uglifyOptions: { warnings: false, }, }), ], }; // Development const uncompressedConfig = webpackMerge({}, config, { entry: { [pkg.name]: entry, }, mode: 'development', plugins: [ new MiniCssExtractPlugin({ filename: '[name].css', }), ], }); // Production const prodConfig = webpackMerge({}, config, { entry: { [`${pkg.name}.min`]: entry, }, mode: 'production', plugins: [ new webpack.optimize.ModuleConcatenationPlugin(), new webpack.LoaderOptionsPlugin({ minimize: true, }), new MiniCssExtractPlugin({ filename: '[name].css', }), ], optimization: { minimize: true, minimizer: [new CssMinimizerPlugin({})], }, }); return [prodConfig, uncompressedConfig]; } return [config]; } getWebpackConfig.webpack = webpack; getWebpackConfig.svgRegex = svgRegex; getWebpackConfig.svgOptions = svgOptions; getWebpackConfig.imageOptions = imageOptions; module.exports = getWebpackConfig;Copy the code
  • Antd Webpack configuration entry file
/* eslint no-console:0 */ function pascalCase(name) { return name.charAt(0).toUpperCase() + name.slice(1).replace(/-(\w)/g, (m, n) => n.toUpperCase()); } // Just import style for https://github.com/ant-design/ant-design/issues/3745 const req = require.context('./components', true, /^\.\/[^_][\w-]+\/style\/index\.tsx?$/); req.keys().forEach(mod => { let v = req(mod); if (v && v.default) { v = v.default; } const match = mod.match(/^\.\/([^_][\w-]+)\/index\.tsx? $/); console.warn('match', match) if (match && match[1]) { if (match[1] === 'message' || match[1] === 'notification') { // message & notification should not be capitalized exports[match[1]] = v; } else { exports[pascalCase(match[1])] = v; }}}); module.exports = require('./components');Copy the code

conclusion

Antd-tools also has many other directives, such as pub, TS-Lint, TS-lint-fix, watch-tsc, etc. The author only analyzes the two key directives compile and dist, just because I am curious about how antD framework construction is implemented. The others don’t seem to be much of a learning experience