Vue2 进阶场景:Webpack 深度集成与优化实战

1. 自定义 Loader/Plugin 开发

1.1 自定义 Loader 实现 Markdown 解析

场景需求:在 Vue 项目中直接导入 .md 文件作为组件内容

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

module.exports = function(source) {
  // 将 Markdown 转换为 HTML
  const html = marked(source);
  
  // 返回 Vue 组件格式的字符串
  return `
    <template>
      <div class="markdown">${html}</div>
    </template>
    <script>
      export default { name: 'MarkdownContent' }
    </script>
    <style>
      .markdown { line-height: 1.6; }
    </style>
  `;
};

Webpack 配置

module.exports = {
  module: {
    rules: [
      {
        test: /\.md$/,
        use: [
          'vue-loader',
          './markdown-loader' // 自定义 loader 路径
        ]
      }
    ]
  }
}

实践建议

  1. Loader 应该保持单一职责原则,只做转换不做其他操作
  2. 充分利用 loader-utils 包提供的工具方法
  3. 开发时使用 loader-runner 进行单元测试

1.2 自定义 Plugin 开发

场景需求:构建完成后生成版本报告文件

class VersionReportPlugin {
  constructor(options) {
    this.filename = options.filename || 'version-report.json';
  }

  apply(compiler) {
    compiler.hooks.done.tap('VersionReportPlugin', (stats) => {
      const pkg = require('./package.json');
      const report = {
        version: pkg.version,
        buildTime: new Date().toISOString(),
        dependencies: pkg.dependencies
      };
      
      const outputPath = stats.compilation.outputOptions.path;
      fs.writeFileSync(
        path.join(outputPath, this.filename),
        JSON.stringify(report, null, 2)
      );
    });
  }
}

使用方式

plugins: [
  new VersionReportPlugin({ filename: 'build-info.json' })
]

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

2.1 基础配置方案

// 动态生成入口和HtmlWebpackPlugin配置
const pages = ['index', 'admin'];

const entry = {};
const htmlPlugins = [];

pages.forEach(page => {
  entry[page] = `./src/entries/${page}.js`;
  
  htmlPlugins.push(new HtmlWebpackPlugin({
    template: `./public/${page}.html`,
    filename: `${page}.html`,
    chunks: [page], // 只注入当前页面的chunk
    minify: true
  }));
});

module.exports = {
  entry,
  plugins: [...htmlPlugins]
};

2.2 高级优化方案

// 使用 glob 自动扫描页面
const glob = require('glob');

const getEntries = () => {
  const entries = {};
  glob.sync('./src/pages/**/index.js').forEach(path => {
    const name = path.split('/')[3]; // 假设路径是 ./src/pages/pageName/index.js
    entries[name] = path;
  });
  return entries;
};

const entries = getEntries();

目录结构建议

src/
  pages/
    home/
      index.js
      App.vue
    admin/
      index.js
      App.vue
public/
  home.html
  admin.html

实践建议

  1. 每个页面使用独立的 Vue 实例避免污染
  2. 公共依赖通过 SplitChunks 提取
  3. 开发环境使用 chunkFilename 保持清晰的构建输出

3. SSR 服务端渲染实现

3.1 基础服务端配置

// webpack.server.js
module.exports = {
  target: 'node',
  entry: './src/entry-server.js',
  output: {
    libraryTarget: 'commonjs2',
    filename: 'server-bundle.js'
  },
  // 关键配置:不打包 node_modules
  externals: nodeExternals({
    allowlist: /\.css$/
  })
};

3.2 客户端激活配置

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

3.3 服务端渲染核心逻辑

// server.js
const { createBundleRenderer } = require('vue-server-renderer');
const renderer = createBundleRenderer(serverBundle, {
  runInNewContext: false,
  template: fs.readFileSync('./src/index.template.html', 'utf-8'),
  clientManifest
});

server.get('*', (req, res) => {
  const context = { url: req.url };
  
  renderer.renderToString(context, (err, html) => {
    if (err) {
      if (err.code === 404) {
        res.status(404).end('Page not found');
      } else {
        res.status(500).end('Internal Server Error');
      }
    } else {
      res.end(html);
    }
  });
});

性能优化建议

  1. 使用 LRU 缓存组件渲染结果
  2. 实现流式渲染减少 TTFB 时间
  3. 对静态页面进行预渲染(prerender)

4. 性能分析与优化

4.1 使用 webpack-bundle-analyzer

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

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

4.2 优化策略分析

体积优化方案

  1. 按需加载第三方库(如 lodash 的 babel-plugin-lodash)
  2. 使用 webpack 的 externals 排除大型库(如通过 CDN 引入 Vue)
  3. 压缩图片等静态资源

构建速度优化

module.exports = {
  cache: {
    type: 'filesystem',
    buildDependencies: {
      config: [__filename] // 当webpack配置变化时自动失效缓存
    }
  },
  resolve: {
    symlinks: false // 禁用符号链接解析
  }
};

4.3 性能监控集成

// 使用 performance 配置设定预算
module.exports = {
  performance: {
    hints: 'warning',
    maxAssetSize: 250000,
    maxEntrypointSize: 250000,
    assetFilter: function(assetFilename) {
      return !/\.map$/.test(assetFilename);
    }
  }
};

实践建议

  1. 定期进行构建分析(建议纳入 CI 流程)
  2. 区分开发环境和生产环境的优化策略
  3. 使用 DLLPlugin 对不常变化的依赖进行预构建

总结流程图

图1

通过以上进阶配置,开发者可以构建出更强大、性能更优的 Vue2 应用。建议根据实际项目需求选择适当的优化方案,避免过度优化带来的维护成本增加。

评论已关闭