Webpack迁到Vite第七节(二):全家桶移除 — Babel 全家桶深度分析

Webpack迁到Vite第七节(二):全家桶移除 --- Babel 全家桶深度分析

本篇定位:Babel 在 Webpack 时期扛着语法转译、API polyfill、helper 去重三座大山。迁移到 Vite 后 esbuild 接手了语法转译,但 polyfill 和 helper 的问题在新架构中怎么解决?本篇逐包拆解四个 Babel 依赖移除的原因,以及 Vite 对应的替代链路。

思维导图

复制代码
Babel 全家桶移除
├── @babel/core --- 核心编译器
│   └── esbuild 替代:目标浏览器原生 ES6+,无需语法降级
├── @babel/preset-env --- 按 browserslist 转译
│   └── 替代:esbuild target: modules(不降级)+ plugin-legacy(ES5 兜底)
├── @babel/plugin-transform-runtime --- helper 去重
│   └── 替代:chunk 级编译天然共享;regeneratorRuntime 残留但不修
└── babel-plugin-import --- antd 按需加载
    └── 替代:统一入口导入 + Rollup tree-shaking(生产)+ Vite 自动依赖预构建(开发)

1.2 Babel 相关(4 个)------ 逐包深度分析

本册分析 Babel 全家桶 4 个包;整个第七节系列合计移除 16 个包(七(一) 构建核心 4 + 本册 4 + 七(三) Loader/Plugin 6 + 七(三) 其他 3 个中移除 2 个(core-js 保留升级),另有后续 lodash 替换)。

Babel 相关(4 个)

@babel/core --- Babel 核心

原用途:Babel 的核心编译器,负责将 ES6+ 代码解析为 AST(抽象语法树),然后应用插件转换,最后生成目标代码。

Babel 的处理流程:

复制代码
输入源码(ES6+)
    │
    ▼
  ① 解析(Parser): 源码 → AST
    │  @babel/parser 将字符串解析为 AST 树
    │
    ▼
  ② 转换(Transform): AST → AST
    │  遍历 AST 节点,应用 preset/plugin 的转换规则
    │  @babel/preset-env → 转换箭头函数、const/let 等
    │  @babel/plugin-transform-runtime → 复用辅助函数
    │
    ▼
  ③ 生成(Generator): AST → 输出代码
    │  @babel/generator 将 AST 转回字符串
    │
    ▼
输出代码(ES5)

为什么不再需要 :Babel 在这里做的核心工作是 ES6+ 语法 → ES5 语法转译 (箭头函数→function、const/let→var、class→构造函数等),以兼容 browserslist 中可能出现的旧浏览器(> 1% 覆盖率可能包含 IE11 等不支持 ES6 的浏览器)。

Vite 时期,vite.config.js 未显式设置 build.target。Vite 7 的默认值为 'baseline-widely-available'(约 Chrome 107+/Firefox 104+/Safari 16+);且本项目因使用 @vitejs/plugin-legacy(未传 modernTargets),target 被覆盖为插件的默认现代浏览器集合 ['es2020', 'edge79', 'firefox67', 'chrome64', 'safari12']。无论哪一层,这些目标浏览器原生支持 ES6+ 语法 ------const、箭头函数、class、async/await 等都不需要转译。因此,esbuild 不需要做"同样的工作",它根本就不需要做 ES6→ES5 的语法降级。

javascript 复制代码
// vite.config.js --- 没有显式设置 build.target
// Vite 7 默认 'baseline-widely-available',本项目经 plugin-legacy 覆盖为现代浏览器集合
// esbuild 不转译任何 ES6+ 语法,因为目标浏览器原生支持

这不是"esbuild 比 Babel 更快地完成了转译",而是"转译这个工作量本身被消除了"------目标浏览器升级了,不再需要照顾 ES5 兼容。ES5 兼容场景(如签署人使用旧 Android 手机)由 @vitejs/plugin-legacy 处理------让 Babel 重回构建链路生成传统产物(已实施)(详见 第七节(五):ES5 降级方案专题)。

@babel/preset-env --- 按目标环境转译

原用途 :根据目标浏览器列表(browserslist),自动确定需要转译哪些 ES6+ 语法。例如,如果目标浏览器支持箭头函数,@babel/preset-env 就不会转换箭头函数;如果不支持,就转换为普通函数。

@babel/preset-env 的工作逻辑:

javascript 复制代码
// babel.config.js
presets: [
  ['@babel/preset-env', {
    useBuiltIns: 'usage',           // 按需引入 polyfill
    corejs: 3,                      // 使用 core-js 3
  }],
  // 注意:没有 targets 选项,目标浏览器由 package.json 中的 browserslist 统一管理
]
json 复制代码
// package.json
{
  "browserslist": [
    "> 1%",            // 全球使用率超过 1% 的浏览器
    "last 2 versions", // 每个浏览器最近 2 个版本
    "not ie <= 10"     // 排除 IE10 及以下
  ]
}

为什么 targets 不在 babel.config.js 中?

browserslist 是前端工具链的共享配置 ------不只 Babel 在用,autoprefixer(CSS 前缀)、postcss-preset-env(CSS 特性降级)、eslint-plugin-compat(兼容性检查)等工具都会读取同一份 browserslist。把它放在 package.json 的顶层,所有工具自动共享,无需在每个工具配置中重复声明。

@babel/preset-env 在不传 targets 时,默认从以下位置按优先级查找 browserslist:

  1. package.json 的 browserslist 字段
  2. 项目根目录的 .browserslistrc 文件
  3. BROWSERSLIST 环境变量

browserslist 各条目含义:

条目 含义
> 1% 全球市场份额超过 1% 的浏览器版本
last 2 versions 每个浏览器的最新 2 个主版本(如 Chrome 120、Chrome 119)
not ie <= 10 排除 IE10 及以下

它做了三件事:

  1. 查 browserslist 确定目标浏览器范围
  2. 查 @babel/compat-data 确定哪些语法需要转换
  3. 只转换目标浏览器不支持的语法

为什么不再需要 :@babel/preset-env 根据 browserslist 确定需要降级哪些语法(如箭头函数→function、const→var)。esbuild 也有 target 选项可以控制语法级别,但在本项目配置中,esbuild 的 target 为现代浏览器集合(chrome64+ 等),不做任何 ES6→ES5 转译 ------因为目标浏览器已原生支持 ES6+ 语法。@babel/preset-env 的语法降级工作不是被 esbuild 替代了,而是因为目标浏览器升级而不再需要了 。ES5 降级的场景交由 @vitejs/plugin-legacy(内部使用 Babel)处理,而非 esbuild 的 target(已实施,见第七节(五))。

@babel/plugin-transform-runtime --- 辅助函数复用

原用途 :Babel 在转换代码时,会生成一些辅助函数(helper),比如 _classCallCheck、_extends、_defineProperty 等。默认情况下,每个文件都会内联这些函数,导致大量重复代码。

没有 @babel/plugin-transform-runtime 时:

javascript 复制代码
// 文件A.js
function _classCallCheck(instance, Constructor) { ... }  // 内联 helper
var A = function A() { _classCallCheck(this, A); }

// 文件B.js(同样的 helper 再内联一次)
function _classCallCheck(instance, Constructor) { ... }  // 重复!
var B = function B() { _classCallCheck(this, B); }

有 @babel/plugin-transform-runtime 时:

javascript 复制代码
// 从 @babel/runtime 统一引用,不重复内联
var _classCallCheck = require('@babel/runtime/helpers/classCallCheck');
var A = function A() { _classCallCheck(this, A); }

为什么不再需要 :esbuild 没有辅助函数的概念------它直接生成目标代码,不产生中间 helper。新增的 plugin-legacy 走"先打包后转译"路径(chunk 级编译),helper 在 chunk 内天然共享,不需要 @babel/runtime 外部包。

transform-runtime 实际处理了哪些 helper(Webpack 时期 dist 产物实测):

复制代码
项目源码中的 Babel helper:
  _extends:         43 次(class 继承/Object.assign 兜底)
  _typeof:           0 次(源码无直接 typeof Symbol)
  _slicedToArray:    0 次(源码解构均可在目标环境原生运行)
  _asyncToGenerator: 0 次(async/await 在旧 browserslist 目标中需降级,但已由 regenerator 处理)

外部文件(pdf.worker.min.js)中的 helper: 2,624 次(预编译文件,未经项目 Babel 处理)

项目源码中只有 _extends 产生了 43 次 helper,全部由 transform-runtime 转为 @babel/runtime 引用。其余 helper 类型(_typeof、_slicedToArray 等)在项目源码中零出现------Vue 2 Options API 以对象字面量为主,不使用 class、极少解构深层嵌套。

在 plugin-legacy 流程中,Babel 处理的是 Rollup 输出的 chunk(189 个),每个 chunk 内 helper 定义一次后共享------不需要 @babel/runtime 外部运行时。效果等价于 transform-runtime,但不引入额外依赖。

⚠️ 深入分析:plugin-legacy 下的 helper 与 regeneratorRuntime

上文提到"helper 在 chunk 内天然共享"------这对 _classCallCheck、_extends 等短 helper 成立。但 async/await 的转译走的是另一条路径:@babel/preset-env 使用 @babel/plugin-transform-regenerator 将 async/await 转为 generator + regeneratorRuntime,后者是一段约 1.9KB 的 generator 运行时(minified),会被内联到每一个需要它的 chunk 中。

实测数据 (npm run build 产物全文搜索):

Helper 类型 出现文件数 来源
_classCallCheck 1 pdf-legacy(PDF.js 第三方)
_createClass 1 pdf-legacy
_asyncToGenerator 1 pdf-legacy
regeneratorRuntime 105 项目业务代码

关键发现:

  • 项目自身代码(Vue 2 Options API)不产生 _classCallCheck/_asyncToGenerator 等短 helper------这些 helper 全部来自第三方 pdfjs-dist
  • 唯一大量出现的是 regeneratorRuntime------189 个 legacy chunk 中 105 个 使用了 async/await

重复开销计算:

复制代码
单文件 regeneratorRuntime 大小:   1.9 KB (minified)
受影响文件数:                     105 / 189 个 legacy 文件
原始总重复:                       105 × 1.9 KB = 198 KB
gzip 后重复 (30% 压缩率):         ~59 KB
项目总构建产物 (dist/):           约 22 MB
重复占比 (gzip):                  < 0.3%

以核心 mixin envelopeMixin-legacy.js(约 73KB)为例,regeneratorRuntime 占 2.7%。

那为什么不加 @babel/plugin-transform-runtime 来解决?

@vitejs/plugin-legacy 的 Babel 配置在源码中硬编码:

javascript 复制代码
// node_modules/@vitejs/plugin-legacy/dist/index.js
babelrc: false,       // 不读 .babelrc
configFile: false,    // 不读 babel.config.js

且插件公开的 Options 接口中没有预留 Babel plugin 注入点 ------无法通过 vite.config.js 添加 @babel/plugin-transform-runtime。要绕过这个限制,有以下三种方案,但各有硬伤:

方案 是什么 怎么做 硬伤
patch-package npm 生态工具:修改 node_modules/ 源码后生成 .patch 文件(Git diff),提交到仓库。团队 npm install 后通过 "postinstall": "patch-package" 自动应用补丁 ① 改 @vitejs/plugin-legacy/dist/index.js ② npx patch-package @vitejs/plugin-legacy ③ 提交 patches/ 目录 依赖升级时 patch 大概率冲突,需手动重做;团队须理解 patch 的存在和原理
wrapper plugin 自定义 Vite 插件,包裹在 legacy() 外面,通过 transform 钩子拦截 legacy chunk,在官方插件处理完后再跑一次 Babel ,二次注入 @babel/plugin-transform-runtime ① 在 vite.config.js 中写一个 { name, transform(code,id){...} } 插件 ② 放在 legacy() 之后 注册 ③ 对含 -legacy 标识的 chunk 调 Babel 与 plugin-legacy 内部实现强耦合(chunk 命名、转换时机);二次 Babel 增加构建时间
fork 插件 把 @vitejs/plugin-legacy 源码复制到项目中,修改后作为本地插件使用,脱离 npm 官方版本 ① 拷贝源码到本地 ② 修改 Babel 配置 ③ import 本地路径 完全失去官方更新(安全补丁、bug 修复);需手动追踪上游变更并合并

无论哪种方案,都是在和插件设计对抗。

决策 :❌ 不加。优化收益 < 0.3%(且仅影响旧浏览器),实现成本高(需对抗插件架构),还需引入 @babel/runtime 生产依赖。保持 @vitejs/plugin-legacy 开箱配置即可。

babel-plugin-import --- antd 按需加载

原用途 :babel-plugin-import 自动把 import { Button } from 'ant-design-vue' 转换为按需导入路径:

javascript 复制代码
// 开发者写的代码:
import { Button } from 'ant-design-vue'

// babel-plugin-import 转换后的代码:
import Button from 'ant-design-vue/lib/button'  // 只导入 Button
import 'ant-design-vue/lib/button/style'         // 只导入 Button 样式

但实际上,本项目从未启用它 。babel.config.js 中 babel-plugin-import 的配置被注释掉了:

javascript 复制代码
// babel.config.js(实际代码)
// lazy load ant-design-vue
// if your use import on Demand, Use this code
// plugins.push(['import', {
//   'libraryName': 'ant-design-vue',
//   'libraryDirectory': 'es',
//   'style': true
// }])

module.exports = {
  presets: [...],
  plugins: ['@babel/plugin-transform-runtime'],  // 注意:只有 transform-runtime,没有 import
}

项目采用的是手动子路径导入 ------开发者在 src/components/pc/antd.js 中手工书写了 111 个子路径(57 个组件 + 53 个样式 + 1 个 version 导入):

javascript 复制代码
// src/components/pc/antd.js(实际代码)
import { default as Button } from 'ant-design-vue/es/button'
import 'ant-design-vue/es/button/style'
import { default as Modal } from 'ant-design-vue/es/modal'
import 'ant-design-vue/es/modal/style'
// ... 40+ 个组件

为什么用子路径导入?通过实验验证:

在 Webpack 生产构建环境下,用同一个 ant-design-vue 1.7.8 做了对照实验:

import { Button } from 'ant-design-vue' (主入口导入) import Button from 'ant-design-vue/es/button' (子路径导入)
构建时遍历的模块数 466 34
最终 bundle 大小 566,067 bytes 566,066 bytes

结论:Webpack 的 tree-shaking 能正确处理 antd 的 JS 代码------两个 bundle 大小几乎相同(差 1 byte)。但主入口导入时 Webpack 需要遍历解析全部 466 个模块(包括未使用的 Affix、Anchor、Carousel 等),构建速度明显慢于子路径的 34 个模块。

子路径导入的真正价值不是减小产物体积(tree-shaking 已经做到了),而是:

  1. 按需加载样式 :每个组件的样式(.less 文件)标记在 sideEffects 中("es/**/style/*"),无法被 tree-shaking。使用主入口 import { Button } from 'ant-design-vue' 不会自动引入 Button 的样式------需要额外 import 'ant-design-vue/es/button/style'。子路径导入 + 手动样式行让开发者显式控制每个组件的样式加载

  2. 减少构建时解析的模块数:34 vs 466,Webpack 只需解析实际使用的模块树,构建更快

  3. babel-plugin-import 的作用 :自动化上述手动过程------开发者写 import { Button } from 'ant-design-vue',插件自动拆分为组件子路径 + 样式子路径,省去手动维护百余行导入的繁琐

为什么不再需要 :Vite 使用 Rollup 做生产构建,Rollup 的 tree-shaking 同样能正确处理 antd。更重要的是,迁移到 Vite 后采用了 import 'ant-design-vue/dist/antd.less' 全量样式导入(替代 53 个独立样式导入),配合 Vite 开发模式的自动依赖预构建,两端都得到了更好的方案:

复制代码
Webpack 时期:babel-plugin-import(未启用)→ 手动子路径导入 → 组件按需 + 样式按需
Vite 时期:  统一入口导入 → Rollup tree-shaking + 全量样式文件
             开发模式:Vite 自动扫描并预构建依赖 → 减少请求数
             生产构建:Rollup tree-shaking 自动处理

遗留代码清理 :src/components/pc/antd.js 和 src/components/h5/vant/index.js 中的大量子路径导入(antd 111 条 + vant 54 条)是项目手写的,与 babel-plugin-import 无关。在 Vite 开发模式每个子路径是一次独立 HTTP 请求,改为统一入口导入后(import { Button } from 'ant-design-vue'),首屏请求数从 110+ 降到 ~4。详见第九节。

optimizeDeps 的角色澄清 :optimizeDeps.include 是 Vite 自动依赖扫描的兜底机制 ------本项目所有依赖(antd、vant、moment、lodash-es)均在 main.js 中直接 import,自动扫描已全部覆盖,该字段已从 vite.config.js 中删除(完整演进见第六节 §1.9)。开发与生产的模块处理分工:

复制代码
开发模式:Vite 自动扫描入口 import → esbuild 预构建 → 合并碎片模块 → 减少 HTTP 请求
生产构建:Rollup tree-shaking → 静态分析 ESM import/export → 移除未使用代码 → 减小 bundle 体积

Loader/Plugin(6 个)

(内容见第七节(三))


附录:Babel vs esbuild 深入对比

为什么 esbuild 比 Babel 快

维度 Babel esbuild
实现语言 JavaScript Go
解析器 @babel/parser(JS 实现的 AST 解析器) 自研 Go 解析器
转换方式 AST → 插件遍历 → 修改 AST → 生成代码 AST → 直接生成目标代码
并行能力 单线程(需 worker 池手动并行) 原生多线程(Go 的 goroutine)
启动时间 ~500ms(加载 JS 模块) ~10ms(原生二进制进程)
缓存策略 需手动配置 cacheDirectory 内置缓存
插件生态 大量可定制插件 有限(设计上故意保持简单)

速度对比数据(来自 esbuild 官方基准测试):

  • Babel 转换 100KB 代码:~500ms
  • esbuild 转换 100KB 代码:~50ms
  • 速度差距:10x ~ 20x

为什么 Go 比 JS 快? 两个核心原因:

  1. 编译为机器码:Go 编译为原生二进制,直接在 CPU 上运行。Babel 是 JavaScript,需要在 Node.js 的 V8 引擎中解释执行(即使有 JIT 编译,仍有解释器开销)
  2. 多线程:Go 的 goroutine 允许在多个 CPU 核心上并行处理文件。JavaScript 的单线程模型需要手动创建 worker 线程池才能利用多核

esbuild 能覆盖 Babel 的哪些功能

功能 Babel esbuild(本项目) 覆盖情况
ES6+ 语法转译(箭头函数/const/class 等) @babel/preset-env target 为现代浏览器集合 → 不做转译 不需要(目标浏览器原生支持)
按需 polyfill useBuiltIns: 'usage' 不支持 不需要(目标浏览器原生支持 API)
JSX/TSX 转译 需要插件 内置支持 ---(本项目无 JSX/TSX 源码)
自定义插件(如 babel-plugin-import) 丰富插件生态 不支持 ❌ 用统一入口导入 + Rollup tree-shaking 替代
辅助函数复用 @babel/plugin-transform-runtime 无 helper 概念 不适用(不做转译,不产生 helper)

esbuild 的 target 选项如何工作

javascript 复制代码
// vite.config.js --- 本项目没有显式设置 build.target
// Vite 7 默认 'baseline-widely-available';本项目经 plugin-legacy 覆盖为 ['es2020','edge79','firefox67','chrome64','safari12']
// 含义:目标浏览器支持原生 ESM → 不转译任何 ES6+ 语法

实际 target 下的行为 :esbuild 不做任何语法降级 。const 保持 const,箭头函数保持箭头函数,class 保持 class,async/await 保持原样。因为目标浏览器(chrome64+/firefox67+/safari12+ 等)全部原生支持这些语法。

如果显式设置 target: 'es2015'(本项目未设置,仅作参考),esbuild 会进行以下转换:

target 的含义是"输出代码需兼容的最低浏览器版本"。target: 'es2015' 意味着目标浏览器支持 ES2015------因此 ES2015 引入的语法(const/let、class、箭头函数等)无需转换 ,但 ES2015 之后 引入的语法(async/await、?? 等)需要转换:

复制代码
【转换】ES2015 之后引入的语法(目标浏览器不支持)
  async/await(ES2017)  → generator + regenerator 或 Promise 链
  ??(ES2020 空值合并)  → 显式 null/undefined 检查
  ?.(ES2020 可选链)    → 显式属性存在检查

【保留】ES2015 及之前已存在的语法(目标浏览器原生支持)
  const/let(ES2015)     → 保留(就是 ES6)
  箭头函数(ES2015)       → 保留
  class(ES2015)         → 保留
  template literal(ES2015) → 保留
  destructuring(ES2015)  → 保留

【始终保留】ES5 及更早的语法不涉及"转换"
  var、function、for、if 等

注意 :esbuild 只转换语法 ,不转换API 。Object.entries() 是 API,不是语法。如果目标浏览器不支持 Object.entries,esbuild 不会自动 polyfill。需要自己通过 core-js 引入。

ES3 / ES5 / ES2015 的区别

这三个级别经常被混淆,但它们之间有明确的语法边界:

复制代码
ES3 (1999)                   ES5 (2009)                ES6 / ES2015
  │                              │                          │
  ▼                              ▼                          ▼
已有: var                    新增: strict mode          新增: const / let
    function                    JSON.parse/stringify        箭头函数 =>
    for / while                 Array.forEach/map           class
    原始对象操作                Object.create              模板字符串 `${}`
                                getter / setter            Promise
                                "use strict"               Symbol / Map / Set
                                                           for...of / 解构
──────────────────────────────────────────────────────────────────────────────
典型浏览器:                典型浏览器:                 典型浏览器:
  IE6 ~ IE8                  IE9 ~ IE11                  Chrome 49+
                              Safari 9                    Firefox 52+

ES5 ≠ ES2015 :ES2015(即 ES6)在 ES5 基础上新增了 const/let、箭头、class、模板字符串等语法,两者相差 6 年标准演进。IE 11 是 ES5 级别浏览器------它不支持任何 ES2015 语法。

对本项目的意义:Webpack 时期的 browserslist 包含 IE 11,Babel 必须以 ES5 为目标(const→var、箭头→function、class→构造函数)。Vite plugin-legacy 使用相同的 browserslist,降级目标同样是 ES5。两者都不是 ES2015、也不是 ES3------ES3 早已被 ES5 全面覆盖,不需要单独考虑。

esbuild 的 target 支持范围

实测验证:

复制代码
es3     ❌ 不存在此选项
es5     ❌ ERROR: Transforming const to the configured target environment ("es5") is not supported yet
es2015  ✅
es2016  ✅
...
es2022  ✅
esnext  ✅

esbuild 的 target 最低只到 es2015 ,不支持 es5 或更低。这意味着:

  • 如果目标浏览器不支持 ES2015(如 IE11、旧 Android WebView),esbuild 无法 将 const/let/class/箭头函数等语法转为 ES5
  • ES5 降级必须走 @vitejs/plugin-legacy(内部用 Babel),esbuild 在架构层面就放弃了 ES5
  • @vitejs/plugin-legacy 不是"可选的优化",是 ES5 兼容的唯一可行路径

本项目不需要 polyfill 的推理

vite.config.js 中 build.target 未显式设置------Vite 7 默认为 'baseline-widely-available',本项目经 @vitejs/plugin-legacy 覆盖为现代浏览器集合(chrome64+/firefox67+/safari12+ 等)。这个范围包括:

  • Chrome 64+(2018)
  • Firefox 67+(2019)
  • Safari 12+(2018)
  • Edge 79+(2020)

这些浏览器已经实现了 ES2020 及以上的大部分 API。项目中使用的 ES6+ API(Promise、Object.assign、Array.includes、Map、Set 等)在这些浏览器中都原生支持,不需要 polyfill。


本节小结 :Babel 四个包全部移除,不是因为 esbuild"更快地做了同样的事",而是因为 转译工作量本身被消除了 ------目标浏览器(chrome64+ 等现代集合)已原生支持 ES6+。唯一遗留的是 plugin-legacy 下的 regeneratorRuntime 跨 chunk 重复(105/189 个 chunk),但优化收益不足 0.3% 且实现路径被锁死,决策不加。

相关推荐
console.log('npc')1 小时前
第 1 章 项目概述与环境搭建
前端·人工智能·大模型
IT_陈寒1 小时前
React组件莫名重渲染?你可能忘了这个小细节
前端·人工智能·后端
鶴哥只手遮天1 小时前
React 19.3.1 源码深度剖析-第 12 章(下)RSC 与 Flight 协议
前端·react.js·开源
deli0071 小时前
TCP 三次握手到底发几个包?丢包重传动画实测一遍
前端
盟接之桥2 小时前
盟接之桥谈线束数字化-按单入按单出:线束企业库存管理的破局之道
大数据·前端·网络·数据库·人工智能·制造
巴勒个啦2 小时前
Prompt Engineering 实战:5 个让 AI 生成生产级业务代码的工程模式
java·前端
去伪存真2 小时前
AI时代,古法编程的码农路在何方?
前端
计算机魔术师2 小时前
从7月绕过沙盒到9月再曝违规,OpenAI的AI安全防线在漏什么
前端
我的div丢了肿么办2 小时前
element-plus表格使用:filter-method过滤导致出现无数据
前端·javascript·vue.js