Build FAQ

Here only some common problems and bad cases are recorded.

If the build products do not meet expectations, especially when buildPreset is configured, please first understand what configuration items buildPreset represents, and then check each configuration item based on all configuration items.

Product FAQ

Initialization of Class Fields

TypeSript provides the useDefineForClassFields configuration to control the conversion handling for public class fields.

If we have a piece of code:

class C {
  foo = 100;
  bar: string;
}

When useDefineForClassFields is false, then the compiled code will look like:

class C {
  constructor() {
    this.foo = 100;
  }
}

When useDefineForClassFields is true, then the compiled code will look like:

class C {
  constructor() {
    Object.defineProperty(this, 'foo', {
      enumerable: true,
      configurable: true,
      writable: true,
      value: 100,
    });
    Object.defineProperty(this, 'bar', {
      enumerable: true,
      configurable: true,
      writable: true,
      value: void 0,
    });
  }
}

Also the default value of this configuration will change depending on the target configuration of tsconfig.json: When target is ES2022 or higher, then useDefineForClassFields is configured to true by default, otherwise it defaults to false.

For more information on this configuration of TypeScript, you can refer to the following link:

The Modern.js Module will currently process according to the following logic:

  1. The first decision to enable this feature inside Modern.js Module is based on the useDefineForClassFields configuration in tsconfig.json of the current project. Currently, only the contents of the tsconfig.json file under the current project path will be read, and the final tsconfig configuration based on the extends configuration is not supported at this time.
  2. If the useDefineForClassFields configuration of tsconfig.json is not detected, the default value is determined based on the target configuration of tsconfig.json. If target is greater than ES2022 (including EsNext), then useDefineForClassFields defaults to true, otherwise it is false.
  3. If the target of tsconfig.json is not detected, it is processed according to the value of useDefineForClassFields as true.

babel-plugin-lodash treats the introduced lodash as undefined

This problem occurs when using something like the following:

import * as Lodash from 'lodash';

export const libs = {
  _: Lodash,
};

Current related issues on the babel-plugin-lodash Github:

The solution to this problem is to remove babel-plugin-lodash, since the plugin is not needed for on-demand referencing at this point and using it would have side effects.

A similar situation occurs with babel-plugin-import. If there is code like the following:

import * as Comps from 'components';

export const libs = {
  comps: Comps,
};

In this case babel-plugin-import may also cause Comps to become undefined. So you need to remove the corresponding babel-plugin-import as well.

Cannot find module 'http'

If the output reports an error like Cannot find module 'http' at browser runtime, it means that your output has bundled node modules. This may occur if some of your dependencies support both browser and node, such as axios, in which case you only need to set the platform to browser. If some third-party packages don't support the browser, you may need to manually inject node polyfill.

Exceptions FAQ

Dynamic require of "react" is not supported

Problem Description

When the product format in the product configuration of the build is ES modules.

export default defineConfig({
  buildConfig: {
    format: 'esm',
  },
});

If you import a cjs product from a third-party npm package, the resulting product may not work properly under webpack.

Solution

  1. First you need to find which third-party package is causing the problem. For example, if the error message points to the react package, then look for a third-party package that has code like require('react') in it. For example react-draggable, which only contains cjs artifacts, then the problem is localized to the react-draggable package.
  2. Then we need to exclude this package with the following configuration, i.e. not package problematic third-party packages.
export default defineConfig({
  buildConfig: {
    externals: ['react-draggable'],
  },
});

During compilation, an error was reported in the less file of a component library:'Operation on an invalid type'

This is probably because the component library depends on Less version v3, while Modern.js Module defaults to v4. v3 and v4 have a Break Change in the math configuration, check this link for details.

Therefore, if a component library like this is used in the source code:

buildPreset is used in the build configuration, make the following changes:

module.exports = {
  buildPreset({ extendPreset }) {
    return extendPreset('your-build-preset', {
      style: {
        less: {
          lessOptions: {
            math: 'always',
          },
        },
      },
    });
  },
};

Or, if a custom buildConfig is used, modify it as follows:

module.exports = {
  buildConfig: {
    style: {
      less: {
        lessOptions: {
          math: 'always',
        },
      },
    },
  },
};

If you are using a component like this in Storybook, you will need to modify the debugging configuration of Storybook:

.storybook/main.ts
module.exports = {
  framework: {
    options: {
      builderConfig: {
        tools: {
          webpackChain(chain, { CHAIN_ID }) {
            chain.module
              .rule(CHAIN_ID.RULE.LESS)
              .use(CHAIN_ID.USE.LESS)
              .tap(options => {
                options.lessOptions = {
                  ...options.lessOptions,
                  math: 'always',
                };
                return options;
              });
          },
        },
      },
    },
  },
};

Bundleless DTS failed

In the bundleless scenario, it's tsc that generates the type declaration file directly. You can find the problem file by printing the error message in the terminal. For source code files, it is recommended to fix the type problem, which can better enable reuse of your package. However. if you encounter a type checking problem with a thrid-party package:

  1. enable skipLibCheck to skip the d.ts check of the thrid-party package.
  2. If the package exports ts files directly, skipLibCheck will not work because it can only skip the d.ts check, so you can only turn off dts.abortOnError to ignore these errors.

Bundle DTS failed

The Modern.js Module directly uses rollup-plugin-dts to package your type description files. It may not be able to handle the type files of some third-party dependencies

If you encounter an error message titled "Bundle DTS failed" during the build process of the Modern.js Module, you can observe that the error message is from a third-party dependency. Try setting dts.respectExternal to false to turn off the behavior of packaging type files of third-party dependencies.

Error reported for defineConfig function type: If there is no reference to "..." then the inferred type of "default" cannot be named

Check if the include configuration exists in the project's tsconfig.json file, and if not, try adding the following:

{
  "include": ["src"]
}

Other FAQ

How to skip the pre-processing of less/scss files with bundleless

Bundleless is a single-file compilation method. You just need to remove your less/scss files from the entry and copy them to the output. Note that we will also rewrite the moduleId of js referencing less/scss, turn it off through the redirect plugin.

Below is an example of skipping less file processing. You will find that all less files in src are copied to dist and the relative path is preserved.

modern.config.ts
export default defineConfig({
  buildConfig: {
    buildType: 'bundleless',
    input: ['src', '!src/**/*.less'],
    redirect: {
      style: false,
    },
    copy: {
      patterns: [
        {
          from: './**/*.less',
          to: './',
        },
      ],
      options: {
        enableCopySync: true,
      },
    },
  },
});

Add additional compilation feature

The Modern.js Module is based on the esbuild implementation, so if you have special needs or want to add additional compilation capabilities, you can do so by implementing the esbuild plugin.

The Modern.js Module provides esbuildOptions configuration to allow modification of Modern.js's internal esbuild configuration, so that custom esbuild plugins can be added via this configuration:

modern.config.ts
import { myEsbuildPlugin } from './myEsbuildPlugin';

export default defineConfig({
  buildConfig: {
    esbuildOptions: options => {
      options.plugins = [myEsbuildPlugin, ...options.plugins];
      return options;
    },
  },
});

When adding an esbuild plugin, please note that you need to add the plugin at the beginning of the plugins array. This is because the Modern.js Module is also integrated into the entire build process through an esbuild plugin. Therefore, custom plugins need to be registered with higher priority.

Support for generating TypeScript declaration files for CSS Modules

modern.config.ts
import { defineConfig } from '@modern-js/module-tools';

export default defineConfig(async () => {
  const { dts } = await import('@guanghechen/postcss-modules-dts');
  return {
    buildConfig: {
      style: {
        modules: { ...dts },
      },
    },
    // custom config
  };
});