工程化

基础 npm
  1. npm create vite 为什么能够创建项目

​ npm create xxx 实际上是npm exec create-xxx 的简写

  1. 怎么让一个脚本是全局可执行而不通过文件路径来执行

    1. 添加 shebang #!/usr/bin/env node 来告诉操作系统用node来执行这个文件
    2. 在pkg.json中添加 bin 字段告诉npm这个项目包含一个可执行的命令以及文件
    3. 在这个项目根目录下运行 npm link ,它会在全局的依赖中创建一个指向这个项目的符号连接,并且在全局的bin下创建一个指向我们项目的脚本
  2. 有哪些包管理器,它们有什么区别

    npm 和 yarn,采用的是扁平化安装,所有的子依赖都会被提升到顶层避免重复安装,带来幽灵依赖

    pnpm,采用内容寻址的方式,它会在公共区域把所有依赖都存起来,采用硬链接或者软链接的方式指向这些文件,它极大地节省了磁盘空间,因为不同的项目可以共享同一个版本的依赖。同时,它也完美解决了幽灵依赖问题,因为 node_modules 中只有你真正声明的依赖。

    当使用pnpm安装一个包的时候,它会先检查全局存储区是否有这个依赖,如果有就硬链接过来,如果没有才下载

  3. 模块化

    1. IIFE
    2. Commonjs
    3. AMD
    4. CMD
    5. ES6 Module: 具名导出值引用,默认导出值拷贝
打包工具
  1. webpack打包流程

    1. webpack启动构建流程后会读取我们的 config 文件和 shell参数并合并,得到最终的运行参数

    2. 用上一步得到的参数

      1. 初始化 compiler对象
      2. 加载所有配置的 plugin 插件,也就是插件内部通过apply方法来订阅webpack构建中的事件
      3. 然后执行对象的run方法开始编译
    3. 根据配置里的entry,找到所有的入口文件

    4. 然后从入口文件出发,调用配置的loader对模块进行翻译,比如babel。然后在AST树上找出引入语句,识别该模块依赖的模块递归处理。递归过程中会生成依赖图来确定模块的引用顺序

    5. 根据入口和模块之间的依赖关系,组成一个包含多个模块的chunk,再将chunk转换成单独文件加入到输出列表

    6. 根据输出内容和配置里的输出配置,输出文件夹

  2. loader 和 plugin的区别

​ loader将不同类型的文件转换成js模块,使其能被webpack处理

​ plugin是扩展工具,webpack会提供一些api和生命周期钩子来做额外的操作,例如最常见的HTMLPlugin在打包完成之后生成html文件

  1. babel 原理 / AST 树

    1. 第一步解析,babel通过词法分析将代码字符串打散成一个个的token,然后通过语法分析来构建一个 AST
    2. 第二部转换,接收到AST树后遍历,通过对树上的节点增删改实现兼容性转换,比如我们可能会将所有的const节点换成 var
    3. 第三步生成,将新转换的AST生成新的js代码字符串
  2. soucemap 原理

    在配置sourcemap之后,编译过程中会生成一个.map文件,用于代码调试或者监控,比如生产环境我们可能会写一个插件将所有的map上传到监控平台。这个map包含了源代码,编译后的代码以及映射关系,然后我们编译后的文件中会加一个注释指向对应的map文件,当我们使用调试工具比如f12会读取这行注释加载对应的sourcemap

  3. webpack hmr原理

    1. webpack在devserver启动ws服务器并和浏览器建立连接

    2. 监听变化:webpack用watch模式监听文件变化,一旦文件改变webpack就会重新编译变化的模块并声称更新清单

    3. 通过ws发送更新清单给客户端

    4. 浏览器收到更新清单之后获取新的模块,替换旧的模块。如果中间出现失败则会触发页面整体刷新

    5. 收到更新后调用模块的 hot.accept函数进行替换

      1. 对于css会生成新的style标签并注入
      2. 对于js会进行依赖检查,通常会配合框架的loader来将新的部分注入到正在运行的组件中然后渲染更新
  4. webpack 性能优化(减少打包体积、多线程并行打包、利用缓存提效)

    1. 压缩资源,利用各种plugin或者loader来减小代码体积
    2. 引入外部cdn,比如lodash,框架等不会经常变化的代码,我们在externals配置中标记外部引入,不打包到bundle中
    3. 最常用的代码分割:利用SplitChunks提取公共模块和第三方库,减少重复编译,我们也可以手动提取公共模块。另外我们可以配置size来将大模块分割成更小的块,但是要注意不是越小越好,因为浏览器并行数量是有上限的
    4. thread-loader多进程打包
    5. 缓存提效
  5. 按需加载

    1. 使用import动态导入,这个import函数会返回一个promise
    2. webpack监测到语句存在时会分割这部份代码,将import的模块打包到新的bundle中
    3. 当import被执行后,浏览器会发起一个网络请求来加载对应的文件
  6. 为什么vite速度比webpack快

  7. vite和webpack区别

    1. 底层:webpack使用node,vite使用go编写的esbuild
    2. 开发模式:webpack先编译,vite直接启动,加载对应模块的时候再编译
    3. 热更新:webpack某个模块更新后,它或者依赖它的模块需要重新编译;vite做法是让浏览器重新请求该模块
  8. 为什么vite生产模式用rollup

    1. rollup静态分析能力更强,treeshaking能力更强,打包体积更小
    2. rollup兼容性很强
    3. esbuild兼容性不好,有时候会出现开发和生产表现不一致的bug /计划用rust构建的rolldown统一
  9. rollup原理

    1. 解析构建依赖图
    2. treeshaking
  10. tree shaking原理

    1. 标记:ESM的import 和 export语法是静态的,代码可以在不执行的情况下通过解析ast确定导入和导出关系,打包工具从入口文件开始构建一个完整的依赖图。它会沿着所有的import递归解析和追踪被引用的模块,在这个过程中所有被引用的、实际被调用的函数和变量和类会被标记为活代码
    2. 移除:依赖图构建完成之后,没有被标记为活代码的部分会被视为死代码,打包工具在最终生成bundle时只会将活代码打包进去
  11. 什么情况不会被 tree shaking

    1. 模块存在副作用:例如文件中有个console,被引用的时候就会执行。可以在sideEffects中配置
    2. 使用commonjs
    3. 动态导入
    4. babel设置不当

感谢您的支持 | Thank you for supporting

工程化
http://example.com/2025/10/17/2Engineering/
作者
Eli Bi
发布于
2025年10月17日
更新于
2025年10月20日
许可协议