Vue2 进阶场景:Webpack 深度定制与优化实践

1. 自定义 Loader/Plugin 扩展 Webpack 功能

1.1 自定义 Loader 实现 Markdown 解析

Webpack Loader 本质是一个函数,接收源文件内容并返回处理后的结果。下面实现一个将 Markdown 转换为 Vue 组件的 Loader:

// markdown-loader.js
const marked = require('marked');
const hljs = require('highlight.js');

module.exports = function(source) {
  // 配置marked
  marked.setOptions({
    highlight: (code, lang) => {
      return hljs.highlight(lang, code).value;
    }
  });
  
  // 将markdown转为HTML
  const html = marked(source);
  
  // 返回Vue组件格式
  return `
    <template>
      <div class="markdown">${html}</div>
    </template>
    <script>
      export default {
        name: 'MarkdownViewer'
      }
    </script>
    <style>
      .markdown {
        line-height: 1.6;
      }
      /* 添加代码高亮样式 */
      pre code.hljs {
        border-radius: 4px;
        padding: 1em;
      }
    </style>
  `;
};

配置使用:

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.md$/,
        use: [
          'vue-loader',
          path.resolve(__dirname, './markdown-loader.js')
        ]
      }
    ]
  }
}

实践建议:

  • 优先使用社区成熟 Loader(如 markdown-it-loader),自定义 Loader 适用于特殊需求
  • 保持 Loader 功能单一,遵循单一职责原则
  • 处理大文件时考虑使用缓存(this.cacheable()

1.2 自定义 Plugin 实现构建通知

Plugin 通过钩子机制扩展 Webpack 功能。下面实现一个构建完成通知的 Plugin:

class BuildNotifyPlugin {
  apply(compiler) {
    compiler.hooks.done.tap('BuildNotifyPlugin', stats => {
      const time = stats.endTime - stats.startTime;
      const notifier = require('node-notifier');
      
      notifier.notify({
        title: 'Webpack 构建完成',
        message: `耗时 ${time}ms`,
        sound: true
      });
      
      // 同时输出到控制台
      console.log(`构建完成,耗时 ${time}ms`);
    });
  }
}

配置使用:

// webpack.config.js
module.exports = {
  plugins: [
    new BuildNotifyPlugin()
  ]
}

2. 多页面应用(MPA)配置

2.1 基础多页面配置

Vue 默认是 SPA(单页应用),但通过 Webpack 可配置为 MPA:

// webpack.config.js
const glob = require('glob');
const HtmlWebpackPlugin = require('html-webpack-plugin');

// 动态获取入口文件
function getEntries(pattern) {
  const entries = {};
  glob.sync(pattern).forEach(file => {
    const name = path.basename(file, path.extname(file));
    entries[name] = file;
  });
  return entries;
}

const entries = getEntries('./src/pages/*/main.js');

module.exports = {
  entry: entries,
  output: {
    filename: 'js/[name].[contenthash:8].js',
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [
    ...Object.keys(entries).map(name => 
      new HtmlWebpackPlugin({
        template: `./src/pages/${name}/index.html`,
        filename: `${name}.html`,
        chunks: [name],
        minify: true
      })
    )
  ]
};

目录结构示例:

src/
  pages/
    home/
      main.js
      index.html
      App.vue
    about/
      main.js
      index.html
      App.vue

2.2 共享公共代码

// webpack.config.js
module.exports = {
  optimization: {
    splitChunks: {
      cacheGroups: {
        commons: {
          name: 'commons',
          chunks: 'initial',
          minChunks: 2,
          minSize: 0
        },
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all'
        }
      }
    }
  }
}

实践建议:

  • 每个页面应有独立的入口文件和根组件
  • 使用 chunks 配置精确控制每个页面加载的资源
  • 长期缓存考虑使用 contenthash 命名文件

3. SSR 支持配置

3.1 基础 SSR 配置

// webpack.ssr.config.js
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin');

module.exports = {
  target: 'node',
  entry: './src/entry-server.js',
  output: {
    filename: 'server-bundle.js',
    libraryTarget: 'commonjs2'
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          compilerOptions: {
            preserveWhitespace: false
          }
        }
      }
    ]
  },
  plugins: [
    new VueSSRServerPlugin()
  ]
};

客户端配置:

// webpack.client.config.js
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin');

module.exports = {
  entry: './src/entry-client.js',
  plugins: [
    new VueSSRClientPlugin()
  ]
};

3.2 服务端渲染流程

图1

实践建议:

  • 区分客户端和服务端特有的代码(如 DOM 操作)
  • 使用 bundleRenderer 实现流式渲染提升性能
  • 注意组件生命周期钩子的差异(服务端只有 beforeCreatecreated

4. 性能分析与优化

4.1 使用 webpack-bundle-analyzer

// webpack.config.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin({
      analyzerMode: 'static',
      reportFilename: 'report.html',
      openAnalyzer: false
    })
  ]
}

4.2 优化策略

  1. 代码分割:

    // 动态导入路由组件
    const UserDetails = () => import('./views/UserDetails.vue');
  2. 图片优化:

    {
      test: /\.(png|jpe?g|gif|webp)(\?.*)?$/,
      use: [
     {
       loader: 'url-loader',
       options: {
         limit: 4096, // 4KB以下转base64
         name: 'img/[name].[hash:8].[ext]',
         quality: 85 // 图片质量
       }
     }
      ]
    }
  3. Gzip 压缩:

    const CompressionPlugin = require('compression-webpack-plugin');
    
    module.exports = {
      plugins: [
     new CompressionPlugin({
       test: /\.(js|css|html|svg)$/,
       threshold: 10240 // 大于10KB的文件才压缩
     })
      ]
    }

实践建议:

  • 定期运行分析工具监控包体积变化
  • 优先优化最大的依赖项
  • 考虑使用 externals 排除不常更新的库(如 Vue 本身)

总结

通过自定义 Loader/Plugin、MPA 配置、SSR 支持和性能分析,可以充分发挥 Vue2 在 Webpack 中的潜力。关键点在于:

  1. 定制化:根据项目需求扩展 Webpack 功能
  2. 优化:持续监控和优化构建结果
  3. 平衡:在开发体验和构建性能间找到平衡点

实际项目中,建议将这些技术点逐步引入,避免一次性过度优化带来的复杂性。

评论已关闭