构建相关问题

这里只记录了一些常见问题和 bad case。

如果是构建产物不符合预期的场景,尤其是配置了 buildPreset 的情况下, 请先了解 buildPreset 代表了哪些配置项,再根据所有的配置项逐个检查

产物问题

Class Fields 的初始化处理

TypeSript 提供了 useDefineForClassFields 配置用于控制对于 public class fields 的转换处理。

如果有一段代码:

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

useDefineForClassFieldsfalse 的时候,则编译后的代码会变为:

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

useDefineForClassFieldstrue 的时候,则编译后的代码会变为:

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,
    });
  }
}

同时该配置的默认值会根据 tsconfig.json 的 target 配置而变化:target 是 ES2022 或者更高的时候,则 useDefineForClassFields 默认配置为 true,否则就是默认为 false

关于 TypeScript 这个配置的更多信息,可以参考下面的链接:

Modern.js Module 目前会根据下面的逻辑进行处理:

  1. 首先根据当前项目的 tsconfig.json 的 useDefineForClassFields 配置确定在 Modern.js Module 内部如何处理。目前只会读取当前项目路径下的 tsconfig.json 文件的内容,暂时不支持根据 extends 配置来获取最终的 tsconfig 配置。
  2. 如果没有检测 tsconfig.json 的 useDefineForClassFields 配置,则会根据 tsconfig.json 的 target 配置来确定默认值。如果 target 大于 ES2022(包含 EsNext),则useDefineForClassFields默认为 true,否则为 false
  3. 如果没有检测到 tsconfig.json 的 target,则按照 useDefineForClassFields的值 为 true 进行处理。

babel-plugin-lodash 将引入的 lodash 处理成 undefined

当使用类似下面的方式的时候,会出现这个问题:

import * as Lodash from 'lodash';

export const libs = {
  _: Lodash,
};

目前在 babel-plugin-lodash Github 上的相关 Issue:

这个问题的解决办法是移除 babel-plugin-lodash,因为此时不需要该插件进行按需引用,使用该插件会产生副作用。

类似的情况在 babel-plugin-import 上也可能会出现。比如有类似如下的代码:

import * as Comps from 'components';

export const libs = {
  comps: Comps,
};

此时 babel-plugin-import 也可能会导致 Comps 变为 undefined。因此也需要移除对应的 babel-plugin-import

Cannot find module http

如果产物在浏览器运行时报了类似 Cannot find module 'http' 的错误,说明你的产物打包进了 node 模块。 这可能会发生于你的依赖里有一些同时支持 browser 和 node 的三方包,例如 axios,此时只需要将 platform 设置为 browser 即可。 如果一些三方包不支持 browser, 你可能需要手动注入 node polyfill

异常类问题

Dynamic require of "react" is not supported

问题描述

当构建的产物配置中产物格式为 ES modules 的时候:

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

如果导入了的第三方 npm 包的 cjs 产物,那么生成的产物可能会在 webpack 下有可能无法正常运行。

解决办法

  1. 首先需要找到是哪个第三方包引起的问题。例如报错信息中指向了 react 这个包,那么就要寻找在哪个第三方包里存在 require('react') 这样的代码。比如 react-draggable ,这个包仅包含 cjs 产物,那么问题定位到 react-draggable 这个包。
  2. 然后我们需要将这个包通过下面的配置进行排除,即不打包存在问题的第三方包
export default defineConfig({
  buildConfig: {
    externals: ['react-draggable'],
  },
});

参考链接

编译过程中,因为某个组件库的 less 文件报错:'Operation on an invalid type'

可能是因为该组件库依赖的 Less 版本是 v3,而 Modern.js Module 默认是 v4。v3 与 v4 在 math 配置上存在有 Break Change,可以查看这个链接 了解详情。

因此,如果是在源码中使用了类似这样的组件库:

在构建配置中使用了 buildPreset 的情况下,按照下面进行修改:

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

或者使用了自定义的 buildConfig 的情况下,按照下面进行修改:

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

如果是在 Storybook 中使用了类似这样的组件,则需要修改 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

在不打包的场景下,是直接 tsc 生成类型声明文件。通过终端打印的错误信息,你可以找到问题文件。对于源码文件,推荐将类型问题进行修复,这能够更好地使你的包得到复用。但如果遇到三方包的类型检查问题:

  1. 开启 skipLibCheck 来跳过三方包的 d.ts 检查。
  2. 如果三方包直接导出 ts 文件, skipLibCheck 将会失效,因为其只能跳过 d.ts 检查,因此你只能关闭 dts.abortOnError 来忽略这些错误。

Bundle DTS failed

Modern.js Module 直接使用 rollup-plugin-dts 来打包你的类型描述文件。 它可能无法处理某些第三方依赖的类型文件

如果遇到 Modern.js Module 构建过程中出现 Bundle DTS failed 的错误信息标题的时候,可以观察报错信息是来自某个第三方依赖。尝试设置 dts.respectExternalfalse 来关闭打包第三方依赖的类型文件的行为。

defineConfig 函数类型报错:如果没有引用 "...",则无法命名 "default" 的推断类型

检查项目的 tsconfig.json 文件中是否存在 include 配置,如果没有,则尝试增加下面的内容:

{
  "include": ["src"]
}

其他

bundleless 如何跳过对 less / scss 文件的预处理

bundleless 是单文件编译的方式,只需要将你的 less / scss 文件从入口里移除并且将其拷贝到产物里即可。 注意我们还会把 js 引用 less / scss 的 moduleId 进行改写,你可以通过 redirect 配置关闭它。

下面是一个跳过 less 文件处理的例子,你会发现 src 里面所有的 less 文件都被拷贝到 dist 里并且保留了一致的相对路径。

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

增加额外的编译能力

Modern.js Module 基于 esbuild 实现,因此如果有特殊需求或者想要增加额外的编译能力,可以通过实现 esbuild 插件来解决。

Modern.js Module 提供了 esbuildOptions 配置允许修改 Modern.js Module 内部的 esbuild 配置,因此可以通过该配置来增加自定义的 esbuild 插件:

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

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

在增加 esbuild 插件时,请注意你需要将插件加在 plugins 数组的头部,因为 Modern.js Module 内部也是通过一个 esbuild 插件介入到整个构建流程中去的,因此需要将自定义插件优先注册。

支持生成 CSS Modules 的 TypeScript 声明文件

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 },
      },
    },
    // 你的自定义配置
  };
});