Kodokon kodokon.com

ES 模块:import/export,组织项目结构

借助 ES 模块的静态分析来组织你的项目,并避开有害的桶文件和循环依赖。

8 分钟 · 3 题

在 Kodokon 中打开本课

ES 模块是静态的importexport 语句在文件被解析时就得到解析,早于任何代码运行。正是这一点使得tree-shaking(移除从未被导入的代码)成为可能,并能在构建时就捕获命名错误。是用具名导出还是默认导出?具名导出几乎在每一方面都更胜一筹:便于重构时重命名、自动补全可靠,而且没法把同一个东西用十个不同的名字导入。默认导出留给模块导出一个显而易见的东西的情况。

JAVASCRIPT
export const TAX_RATE = 0.2;

export function applyTax(price) {
  return price * (1 + TAX_RATE);
}

export default class Cart {
  items = [];
  add(item) {
    this.items.push(item);
    return this;
  }
}
cart.js:工具用具名导出,中心类用默认导出。

在消费方,有四种形式:具名导入(可通过 as 选择性地起别名)、默认导入、命名空间导入(* as),以及动态导入 import(),它返回一个关于命名空间的 promise。后者是代码分割的基本构件:只在用户真正需要的那一刻,才去加载一个笨重的模块(编辑器、图表、PDF)。

JAVASCRIPT
import Cart, { applyTax, TAX_RATE } from "./cart.js";
import * as cartModule from "./cart.js";

console.log(applyTax(100), TAX_RATE); // 120 0.2
console.log(cartModule.TAX_RATE); // 0.2

const lazy = await import("./cart.js");
const cart = new lazy.default();
console.log(cart instanceof Cart); // true
同一个模块的静态形式与动态导入。

在组织结构上,优先按功能拆分(features/cart/features/auth/),而不是按技术类型拆分(controllers/helpers/):一起变动的代码放在一起,删除一个功能就意味着删掉一个文件夹。通过一个狭窄的入口点来暴露每个功能,并把任何绕过该入口点的导入都视为一次边界违规。

知识检测

确认你已牢记本课的重点内容。

  1. 是什么让 ES 模块的 tree-shaking 成为可能?
    • 打包器会执行代码来看哪些东西被用到了
    • 导入和导出是静态的,无需运行代码即可分析
    • ES 模块会自动压缩死代码
  2. 表达式 import("./module.js") 返回什么?
    • 同步返回模块命名空间
    • 一个以模块命名空间解决的 promise
    • 一个被调用时才加载模块的函数
    • 只有该模块的默认导出
  3. 桶文件(一个把整个文件夹重新导出的 index)的主要风险是什么?
    • 它们让具名导出变得不可能
    • 它们可能为了一次导入就加载整个文件夹,并妨碍 tree-shaking
    • 它们会阻止对该文件夹的动态导入