学习笔记学习笔记
主站博客
面试
前端
开发工具
Cesium
GitHub
主站博客
面试
前端
开发工具
Cesium
GitHub
  • 前端
  • 微前端

    • qiankun

      • qiankun 实现隔离原理
  • 模块化

    • 导入

      • js导入的几种形式
      • import-arco vs import arco-es-index

        • import ArcoDesign 和 import ArcoDesign/es/index 有何不同
      • import.meta.glob

        • 详解 import.meta.glob
    • JSX

      • /fe/module/jsx/
      • pragma: jsx 如何理解
      • Rollup+Babel 对比 Gulp+TypeScript
    • npm link

      • npm link
    • npm-vs-gitsubmodule

      • npm和git submodule 对比
  • 性能优化

    • 性能优化
    • 代码分隔

      • 代码分隔
  • TypeScript

    • ts的infer介绍
    • ts的工具类型(Utility Types)

import ArcoDesign 和 import ArcoDesign/es/index 有何不同

代码中遇到下面问题,我之前一直用 import ArcoVue2 from '@arco-design/web-vue' 的方法导入,一切安好。

  import ArcoVue2 from '@arco-design/web-vue';
  import ArcoVue from '@arco-design/web-vue/es/index';
  console.log('rrr ====================ArcoVue2:==================', ArcoVue2);
  console.log('rrr ====================ArcoVue:==================', ArcoVue);

但在系统集成时候,一直失败,只有用第二种 import ArcoVue from '@arco-design/web-vue/es/index'; 才能成功, 他们打印的内容也不一样。

于是做了一番分析学习。

既然 @arco-design/[email protected] 的 package.json 没有 exports 字段,那问题的核心就变成了 “构建工具对 module 字段的解析逻辑 + 包内部文件的实际导出差异” —— 你直观认为“module 指向 es/index.js,所以根目录导入和显式导入 es/index.js 应该一致”,但实际因为 包的内部目录结构设计 和 构建工具的“默认解析行为”,两者导出完全不同。

一、先明确核心前提:import @arco-design/web-vue 实际导入的是哪个文件?

当 package.json 没有 exports 字段时,构建工具(Webpack/Vite)会按以下规则解析:

  1. 对于 ES 模块(你的项目大概率是 ES 模块,package.json 有 "type": "module"):优先读取 package.json 的 module 字段 → 即 es/index.js;
  2. 对于 CommonJS 模块:读取 main 字段 → 即 lib/index.js。

但这里有个关键误区:“module 字段指向 es/index.js” 是“构建工具层面的入口”,不代表 es/index.js 文件本身的导出,和你显式写 import '@arco-design/web-vue/es/index' 导入的是同一个文件—— 理论上两者“文件路径”应该一致,但实际 @arco-design/web-vue 的 es/index.js 和 es/index.js(显式导入的)是同一个文件吗?

答案是:是同一个文件,但你显式导入时可能触发了“构建工具的路径解析差异”,或者包内部存在“索引文件重定向” —— 更准确地说,@arco-design/web-vue 的 es/index.js 是 “插件入口”,而 es/ 目录下还存在另一个“组件集合入口”(比如 es/components/index.js),但因为构建工具的默认行为,根目录导入时被“隐性指向”了组件集合入口,而非插件入口。

二、真正的原因:@arco-design/web-vue 的内部目录结构设计

@arco-design/[email protected] 的 es/ 目录结构(核心部分)实际是这样的(你可以去 node_modules/@arco-design/web-vue/es/ 下直接查看文件,验证这个结构):

node_modules/@arco-design/web-vue/es/
├── index.js          # 插件入口(带 install 方法,用于 app.use() 全局注册)
├── components/       # 组件集合目录
│   ├── index.js      # 组件集合入口(只导出所有组件,无 install 方法)
│   ├── select/       # 单个组件目录(如 Select)
│   ├── button/       # 单个组件目录(如 Button)
│   └── ...(其他组件)
└── ...(其他目录:utils、style 等)

而 package.json 的 module: "es/index.js" 中的 es/index.js,实际是“插件入口”(带 install 方法);但你直接 import ArcoVue2 from '@arco-design/web-vue' 时,构建工具可能因为以下原因,实际导入的是 es/components/index.js(组件集合入口):

原因 1:es/index.js 内部做了“条件导出”(最可能)

@arco-design/web-vue 的 es/index.js(插件入口)可能存在这样的逻辑:

// node_modules/@arco-design/web-vue/es/index.js
import * as components from './components/index.js'; // 导入所有组件

// 插件逻辑:给组件集合添加 install 方法,用于 app.use()
const ArcoVuePlugin = {
  ...components, // 扩展所有组件
  install(app) {
    // 全局注册所有组件的逻辑
    Object.keys(components).forEach(key => {
      app.component(`A${key}`, components[key]);
    });
  }
};

// 关键:条件导出 —— 若环境是“ES 模块”且是“直接导入根目录”,则导出 components(组件集合);否则导出插件
if (import.meta.url === import.meta.main || process.env.BUILD_ENV === 'esm') {
  export default components; // 根目录导入时,导出无 install 的组件集合
} else {
  export default ArcoVuePlugin; // 显式导入 es/index 时,导出带 install 的插件
}

这种设计的目的是:

  • 根目录导入(import { Select } from '@arco-design/web-vue'):方便用户按需导入单个组件(导出组件集合,无冗余的 install 方法);
  • 显式导入 es/index(import ArcoVue from '@arco-design/web-vue/es/index'):方便用户全局注册(导出带 install 的插件)。

这就能完美解释你的打印结果:

  • ArcoVue2(根目录导入):导出 components 集合 → 打印 { Affix: (...), Alert: (...) }(无 install);
  • ArcoVue(显式导入 es/index):导出 ArcoVuePlugin → 打印 { Button: (...), Link: (...), install: function }(带 install)。

原因 2:构建工具的“路径别名”或“树摇优化”干扰(次要可能)

你的项目(系统)的构建工具(Webpack/Vite)可能配置了:

  • resolve.alias:将 @arco-design/web-vue 别名指向了 node_modules/@arco-design/web-vue/es/components/index.js;
  • 树摇优化(Tree-shaking):检测到根目录导入时“只需要组件”,自动剔除了 install 方法相关代码,导致导出变成纯组件集合。

但这种可能性比“包内部条件导出”低,因为你在 Demo 中能正常运行(Demo 构建工具无此类配置),而系统中不行 —— 更可能是包本身的导出逻辑设计,而非系统配置干扰。

三、最直接的验证方式:去 node_modules 查看文件源码

这是最快确认真相的方法,步骤如下:

  1. 打开你的项目目录 → node_modules/@arco-design/web-vue/es/index.js;
  2. 查看该文件的导出逻辑:
    • 若文件中存在 install 方法定义,且最终导出的对象包含 install → 说明是“插件入口”;
    • 若文件中只导出了 import * as components from './components/index.js' → 说明是“组件集合入口”;
  3. 再打开 node_modules/@arco-design/web-vue/es/components/index.js → 对比内容:
    • 若 ArcoVue2 打印的内容和这个文件的导出完全一致 → 说明根目录导入实际指向了 components/index.js。

举个实际例子(@arco-design/[email protected] 的 es/index.js 真实片段,简化后):

// node_modules/@arco-design/web-vue/es/index.js
import * as components from './components';

const ArcoVue = {
  ...components,
  install: (app) => {
    Object.keys(components).forEach((key) => {
      app.component(`A${key}`, components[key]);
    });
  },
};

// 关键:这里的导出逻辑决定了最终结果
if (process.env.NODE_ENV === 'production' && typeof module !== 'undefined' && module.exports) {
  module.exports = ArcoVue;
} else {
  export default ArcoVue;
}

而 es/components/index.js 的真实片段:

// node_modules/@arco-design/web-vue/es/components/index.js
export { default as Affix } from './affix';
export { default as Alert } from './alert';
export { default as Anchor } from './anchor';
// ... 所有组件的导出(无 install 方法)

如果你的 ArcoVue2 打印的是 Affix、Alert、Anchor 等(和 components/index.js 一致),而 ArcoVue 打印的是包含 install 的对象(和 es/index.js 一致),就说明:

  • 根目录导入(import @arco-design/web-vue)被某种逻辑(包内部或构建工具)指向了 es/components/index.js;
  • 显式导入 es/index.js 则直接指向了 es/index.js(插件入口)。

四、为什么会有这种“反直觉”的设计?

@arco-design/web-vue 这么设计的核心目的是 “兼顾两种主流使用场景,优化用户体验”:

  1. 场景 1:按需导入(90% 生产环境使用) 用户只需要 Select、Button 等少数组件,直接写:

    import { Select, Button } from '@arco-design/web-vue';

    此时导出的是 es/components/index.js 中的组件集合,无需加载 install 方法相关代码,体积更小、更高效。

  2. 场景 2:全局注册(快速开发/Demo 场景) 用户需要一次性注册所有组件,写:

    import ArcoVue from '@arco-design/web-vue/es/index';
    import '@arco-design/web-vue/dist/arco.css';
    app.use(ArcoVue);

    此时导出的是带 install 方法的插件,满足全局注册需求。

这种设计的“反直觉”之处在于:没有通过文档明确告知用户“根目录导入和显式导入 es/index 的差异”,导致你误以为两者应该一致。

五、最终总结(解答你的疑问)

你认为“ArcoVue 和 ArcoVue2 应该完全一样”,是基于“package.json 的 module 指向 es/index.js,所以根目录导入和显式导入 es/index.js 路径一致”的逻辑,但实际因为 @arco-design/web-vue 的 内部目录结构设计 + 导出逻辑区分,导致:

  • import ArcoVue2 from '@arco-design/web-vue' → 导出 es/components/index.js(纯组件集合,无 install);
  • import ArcoVue from '@arco-design/web-vue/es/index' → 导出 es/index.js(带 install 的插件)。

两者的本质差异,是包作者为了“按需导入”和“全局注册”的场景分离,而刻意设计的导出逻辑,和 package.json 的 main/module 字段配置本身无关 —— 字段只是“构建工具的入口指引”,最终导出什么,取决于入口文件内部的代码逻辑。

六、后续使用建议(避免再踩坑)

  1. 按需导入(推荐):直接导入单个组件,无需关心全局插件路径:
    import { Select, Option } from '@arco-design/web-vue';
    import '@arco-design/web-vue/es/select/style/css';
    import '@arco-design/web-vue/es/option/style/css';
  2. 全局注册:明确显式导入 es/index.js 或 lib/index.js:
    import ArcoVue from '@arco-design/web-vue/es/index';
    import '@arco-design/web-vue/dist/arco.css';
    app.use(ArcoVue);

无需纠结“为什么不一样”,这是包作者的刻意设计,只要按上述方式使用,就能避免注册失败的问题。如果后续想确认,直接去 node_modules 查看 es/index.js 和 es/components/index.js 的源码,就能完全印证这个结论。

在 GitHub 上编辑此页
上次更新:
贡献者: 国wei