JavaScript依赖管理核心是代码加载时机与方式,浏览器原生ESM限制多(需type="module"、带后缀路径、不支持node_modules直接引入),故真实项目必须用Webpack或Vite等打包工具处理模块解析、tree-shaking等。
JavaScript 依赖管理的核心不是“装多少包”,而是“谁在什么时候、以什么方式加载了什么代码”。现代项目几乎必须用模块打包工具,否则 import 会直接报错,node_modules 中的包也无法被浏览器执行。
import 在浏览器里直接写会报错?原生浏览器只支持 ES 模块(ESM),但有硬性限制:必须显式声明 type="module",且所有 import 路径必须带后缀(如 ./utils.js),不能省略;同时不支持 import 从 node_modules 直接引入(例如 import _ from 'lodash' 会 404)。
package.json 的 exports 或 main
node_modules 查找逻辑require())或 JSON 导入等 Node 特性所以,光靠浏览器跑 import 只适用于极简静态模块,真实项目必须走打包流程。
npm install 装的是什么?devDependencies 和 dependencies 怎么分?npm install 把包下载到 node_modules,但只是“存着”,不会自动接入代码。是否真正参与构建/运行,取决于你是否在源码中 import 或 require 它,以及打包工具是否将其纳入输出。
dependencies:运行时必需,比如 react、axios —— 打包后仍需存在于最终 JS 中devDependencies:仅开发期用,比如 webpack、eslint、@vitejs/plugin-react —— 不会进入生产产物typescript 是 devDependency,但 @types/react 也是,因为类型只用于编译检查,不产出 JSVite 利用浏览器原生 ESM,在开发时按需编译单个文件(冷启动快);Webpack 则是先全量构建依赖图,再启动服务(启动慢,但兼容性更可控)。两者都解决同一个问题:把 import 'lodash' 这种语句,转成浏览器能加载的真实路径,并处理循环依赖、tree-shaking、代码分割等。
import { debounce } from 'lodash-es';
export function setupSearch() {
const input = document.getElementById('search');
input.addEventListener('input', debounce(handleSearch, 300));
}
上面这段代码在 Vite 中可直接运行;在 Webpack 中也行,但若你用的是 lodash(非 lodash-es),Webpack 需额外配置 babel-plugin-lodash 或 alias 才能避免打包整个库 —— 这就是模块格式(CJS vs ESM)对打包结果的直接影响。
import?可以,但代价是放弃灵活性用 deno run 或 node --experimental-modules 确实能直接执行 ESM,但它们不处理:
@/components → src/components)import './style.css')import.meta.env.VITE_API_URL)import() 的代码分割与预加载提示也就是说,跳过打包工具 = 主动放弃工程化能力。小脚本可以试,中大型项目会卡在第 3 天的样式加载失败或环境变量读不到上。