bit 业务级实践:(1)模块别名的处理

472 阅读3分钟

bit 与别名

假设生产组件的源项目结构如下:

|--src/
   |--components/
      |--A/
      |--B/
      |--C/
   |--styles/

消费组件的项目结构如下:

|--src/
   |--components/
      |--X/
      |--Y/
      |--Z/
   |--styles/
|--components/

其中 src/components 目录下是消费者项目自有的组件, components 目录下则是通过 bit 引入的消费组件。

组件生产

在生产组件的阶段,bit 会解析依赖(bit tag)。如同配置 Webpack Alias 一样,我们也需要在 Bit Config 里配置解析规则,告知各种 Resolver 如何查找到别名对应的各个模块。

示例:

{
  "resolveModules": {
    "modulesDirectories": ["src"],
    "aliases": {
      "@components": "src/components/"
    }
  }
}

modulesDirectories 等同于 TypeScript Config 的 rootDirsaliases 等同于 TypeScript Config 的 paths 和 Webpack 的 alias;同一个项目里 bit 的相关配置应该与 TypeScript Config、Wepack 的配置一致。比如示例所对应的是:

tsconfig.json

{
  "compilerOptions": {
    "rootDirs": ["src"],
    "paths": {
      "@components/*": ["src/components/*"]
    }
  }
}

Webpack Config

{
  resolve: {
    alias: {
      "@components": `${process.cwd()}/src/components`
    }
  }
}

当然,还得有 Less、Scss 模块的别名,我们也需要确保与 Bit Config 一致。

理论上,Less 配置 less-loader(透传给 Less.js)

{
  ...
  {
    loader: 'less-loader',
    options: {
      lessOptions: loaderContext => {
        const { context, rootContext } = loaderContext;
        return {
          paths: [
            path.join(process.cwd(), 'src')
          ]
        };
      }
    }
}

Scss 则设置 SASS_PATH=node_modules:src 即可。

组件消费

在任一应用里消费组件(bit import)的阶段,如果源码里未使用别名,以消费组件 A 为例,A 通过相对路径 ../B 依赖 B,bit 会构造如下的目录结构(真实的模块 B 可能会在 .dependencies 目录下):

 |--components/
    |--A/
       |--A/index.ts
       |--B/index.ts
    |--B/

如果 A 是通过别名依赖了 B,则 bit 会构造另外一个迥异的目录结构——通过伪装 npm 的方式满足 npm 形式上的依赖解析:

 |--components/
    |--A/
       |--index.ts
       |--node_modules/
          |--@components/
             |--B/
    |--B/

消费者项目的别名配置

以上捋清了 bit 与别名的关系,可并不意味着消费组件的应用就能顺利的构建,因为消费者项目自身也会有各种别名设置,这会影响构建工具的行为;所以别名带来便利与灵活的同时,也让 bit 源码复用工作流的构建过程变得复杂。

以组件 A 通过别名 @components/B 依赖 B 为例:

|--src/
   |--components/
      |--X/
      |--Y/
      |--Z/
      |--B/ <-- 这里也有一个 B
   |--styles/
      |-- less-mixins.less
      |-- scss-mixins.scss
|--components/
    |--A/
       |--index.ts
       |--less.less  -> @import 'styles/less-mixins'
       |--scss.scss  -> @import 'styles/scss-mixins'
       |--node_modules/
          |--@components/
             |--B/
          |--styles
             |--less-mixins
             |--scss-mixins
    |--B/
    |--less-mixins
    |--scss-mixins
|--node_modules/
   |--@bit/
      |--scope-name.namespace.A <-- 软链到 components/A
      |--scope-name.namespace.B <-- 软链到 components/B
      |--scope-name.namespace.less-mixins <-- 软链到 components/less-mixins
      |--scope-name.namespace.scss-mixins <-- 软链到 components/scss-mixins

TypeScript

消费者项目可能使用与源项目相同的别名设置、组件命名,仅凭 Webpack Alias 配置,是不能将 A 的依赖 B 解析到 components/B,而是会错误的解析到 src/components/B

一个解决思路是把 ./components 目录排除在 Webpack Alias 别名作用外。

使用 TypeScript 的项目可以直接引入 @ekit/tsconfig-paths-webpack-plugin,这个 Webpack Resolve Plugin 插件实现了将 TypeScript Config paths 自动转换成模块解析规则,并可以取代配置 Alias,示例:

const TsconfigPathsPlugin = require('@ekit/tsconfig-paths-webpack-plugin');

module.exports = {
  ...,
  resolve: {
    plugins: [
      new TsconfigPathsPlugin({
        exclude: path.join(process.cwd(), 'components')
      })
    ]
  }
}

Scss

Scss 模块解析比较正常,实际上仅需要配置 SASS_PATH=node_modules:src 环境变量就可以,会将 A 通过别名 styles/scss-mixins 引入的依赖,正确的解析到 A/node_modules/styles/scss-mixins

Scss 的解析顺序:

-> context path + node_modules
-> context path + src
-> project path + node_modules
-> project path + src

Less

Less 模块解析其实也比较正常,但是实现的细节上会有点问题。

即便配置了 Less paths:

{
  lessOptions: loaderContext => {
    const { context, rootContext } = loaderContext;
    return {
      paths: [
        path.join(context, 'node_modules'),
        path.join(process.cwd(), 'node_modules'),
        path.join(process.cwd(), 'src')
      ]
    };
  }
}

Less 依旧会把 A 的别名依赖 styles/less-mixins 给错误的解析到 src/styles/less-mixinsDemo)。

我已经给 Less.js 提了 IssuePR,尚未被解决和采纳。

所以,Less 在 bit 源码复用工作流里是不能使用别名,只能使用相对路径的。

实践总结

通过以下实践,可以解决或者绕过 bit 与模块别名绝大多数的陷阱:

  • 同一应用里 Less、Scss、TypeScript 配置相同的别名根目录
  • Less
    • @import '~bootstrap/less' ——引用 node_modules 内样式资源
    • @import '../../mixins' ——使用相对路径引用项目内资源
  • Scss
    • 如配置 SASS_PATH=node_modules:src
    • @import '~{node_modules}' - 格式引用 node_modules 目录下 scss 资源
    • @import 'styles/mixins - 绝对路径格式引用 src 目录下其他 scss 资源
  • TypeScript