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 的 rootDirs
,aliases
等同于 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-mixins
(Demo)。
我已经给 Less.js 提了 Issue 和 PR,尚未被解决和采纳。
所以,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