多页

尽管 Webpack 经常用于打包单页面应用程序,但还可以将它与多个单独的页面一起使用,我们可以通过 HtmlWebpackPlugin 和一些配置来实现它。

可能的途径

使用 Webpack 生成多个页面时,您有以下几种可能的方法:

  • 通过 multi-compiler-mode(多编译器模式)并返回一组配置。只要页面是分开的,并且几乎不需要在它们之间共享代码,该方法就可以工作。这种方法的好处是您可以通过 parallel-webpack 处理它以提高构建性能。
  • 设置单个配置并提取通用代码。有多种实现方法,具体取决于您的操作。
  • 如果您要实现渐进式 Web 应用程序(PWA),您的应用要包含 App shellpage shell,并在使用应用时加载需要的部分。

在实践中,您可能还会碰到其他情况。例如,您必须为页面生成 i18n 变体。但处理方法大体上是一致的。

生成多个单独的页面

要生成多个单独的页面,应以某种方式初始化它们。您应该为每个页面返回一个配置,这样 Webpack 会针对性地通过多编译器模式处理它们。

摘要页面

要初始化页面,它至少应该包含页面标题,输出路径和可选模板。每个页面都应该接收可选的输出路径和用于自定义的模板。我们按照下面的配置来达到这个目的:

webpack.parts.js

...
// const HtmlWebpackPlugin = require("html-webpack-plugin");
...


const commonConfig = merge([

  // {
  //   plugins: [
  //     new HtmlWebpackPlugin({
  //       title: "Webpack demo",
  //     }),
  //   ],
  // },
  ...
]);

...

module.exports = mode => {

  // if (mode === "production") {
  //   return merge(commonConfig, productionConfig, { mode });
  // }

  // return merge(commonConfig, developmentConfig, { mode });

  const pages = [
    parts.page({ title: "Webpack demo" }),
    parts.page({ title: "Another demo", path: "another" }),
  ];
  const config =
    mode === "production" ? productionConfig : developmentConfig;

  return pages.map(page =>
    merge(commonConfig, config, page, { mode })
  );
};

在此更改之后,您应该在应用程序中有两个页面://another。我们可以导航进入这两个页面,但结果是相同的。

为每个页面注入不同的脚本

现在的问题是,如何为每个页面注入不同的脚本。在当前配置中,两者共享相同的 entry。要解决此问题,您应将 entry 配置移至较低抽象层级并按页面进行管理。现在我们来设置另一个入口点:

src/another.js

import "./main.css";
import component from "./component";

const demoComponent = component("Another");

document.body.appendChild(demoComponent);

这个文件可以存放到一个指定的目录中,这里重用现有代码以显示一些内容。Webpack 配置必须指向此文件:

webpack.config.js

...

const commonConfig = merge([

  {
    output: {
      // Needed for code splitting to work in nested paths
      publicPath: "/",
    },
  },

  ...
]);

...

module.exports = mode => {

  // const pages = [
  //   parts.page({ title: "Webpack demo" }),
  //   parts.page({ title: "Another demo", path: "another" }),
  // ];


  const pages = [
    parts.page({
      title: "Webpack demo",
      entry: {
        app: PATHS.app,
      },
    }),
    parts.page({
      title: "Another demo",
      path: "another",
      entry: {
        another: path.join(PATHS.app, "another.js"),
      },
    }),
  ];

  const config =
    mode === "production" ? productionConfig : developmentConfig;

    return pages.map(page =>
      merge(commonConfig, config, page, { mode })
    );
};

我们还要调整一下部分配置,以便将 entry 包含在配置中:

webpack.parts.js

exports.page = (
  {
    path = "",
    template = require.resolve(
      "html-webpack-plugin/default_index.ejs"
    ),
    title,

    entry,

  } = {}
) => ({

  entry,

  plugins: [
    new HtmlWebpackPlugin({
      filename: `${path && path + "/"}index.html`,
      title,
    }),
  ],
});

配置了这些以后,访问 /another 可以看到一些熟悉的东西:

multiple-page-in-webpack

优点和缺点

如果你构建应用程序(npm run build),你应该能找到 another/index.html。观察生成的代码,您会有以下发现:

  • 知道如何在配置中添加更多页面。
  • 生成的资源直接位于构建根目录下方。页面是一个例外,因为它们由 HtmlWebpackPlugin 处理,但它们仍然指向根目录下的资源。可以以_webpack.page.js_ 的形式添加一层抽象,并通过公开接受页面配置的函数来管理路径。
  • 模块的打包记录应该按照每个页面单独写在自己的文件中。目前,只有最后一个页面是正确写入的。上述解决方案能够解决这个问题。
  • 像 linting 和 cleaning 这样的过程现在运行了两次,这是不合理的。

通过删除多编译器模式,可以将这个方法推向另一个方向。尽管处理这种构建的速度较慢,但​​它可以实现代码共享和 shell。实现 shell 的第一步是对原来的配置进行再加工,以便它能够存放页面之间共享的代码。

生成多个页面并共享代码

由于当前页面的生成模式,当前配置其实已经巧合地共享代码了。只有一小部分代码不同——manifest 和指向它们入口的包不同。

在更复杂的应用程序中,您应该在页面中应用分割打包章节中介绍的技术。因此,删除多编译器模式是值得的。

调整配置

要在页面之间共享代码,我们需要调整一下配置。大多数代码可以保持不变。将配置暴露给 Webpack 的方式必须改变,以便它接收一个单独的配置对象。由于 HtmlWebpackPlugin 默认情况下会获取所有的块,你必须调整它以获取特定页面需要的块:

webpack.config.js

...

module.exports = mode => {
  const pages = [
    parts.page({
      title: "Webpack demo",
      entry: {
        app: PATHS.app,
      },

      chunks: ["app", "manifest", "vendors~app"],

    }),
    parts.page({
      title: "Another demo",
      path: "another",
      entry: {
        another: path.join(PATHS.app, "another.js"),
      },

      chunks: ["another", "manifest", "vendors~app"],

    }),
  ];
  const config =
    mode === "production" ? productionConfig : developmentConfig;


  // return pages.map(page =>
  //   merge(commonConfig, config, page, { mode })
  // );


  return merge([commonConfig, config, { mode }].concat(pages));
};

每个特定页面的配置也需要一些小的调整:

webpack.parts.js

exports.page = (
  {
    path = "",
    template = require.resolve(
      "html-webpack-plugin/default_index.ejs"
    ),
    title,
    entry,

    chunks,

  } = {}
) => ({
  entry,
  plugins: [
    new HtmlWebpackPlugin({

      chunks,

      ...
    }),
  ],
});

如果执行构建(npm run build),您应该注意到与第一个多页面构建相比会有些不同。这里只有一个 manifest 文件,而不是两个。由于新的设置,manifest 包含对生成的所有包的引用。反过来,每个入口文件指向 manifest 的不同部分,manifest 根据入口运行不同的代码。因此不需要多个单独的 manifest。

优点和缺点

与之前的方法相比,有所得也有所失:

  • 鉴于配置不再是多编译器形式,处理可能会更慢。
  • 插件如 CleanWebpackPlugin 不额外配置的话就无法工作。
  • 多个 manifest 文件变成了一个。但不是什么问题,不同的入口会根据其配置运行 manifest 的不同部分。

渐进式 Web 应用程序

如果我们将代码拆分与智能路由的概念相结合,所得到的就是渐进式 Web 应用程序(PWA)。webpack-pwa 案例说明了如何在 Webpack 中通过 app shell 或 page shell 实现 PWA。

App shell 会初始加载,并管理整个应用程序,包括路由部分。page shell 更精细,并且在使用应用程序时加载更多内容。在这种情况下,应用程序的总大小更大。但是,初始的 App shell 加载会很快。

PWA 可以与 offline pluginsw-precache-webpack-plugin 等插件完美结合。使用 Service Worker 可以改善离线体验。

TwitterTinder 案例演示了 PWA 应用的优势。

总结

Webpack 使您可以管理多个页面配置。PWA 使应用程序在使用时再加载,而通过 Webpack 可以实现 PWA。

回顾一下:

  • Webpack 可用于通过多编译器模式生成多个单独的页面,也可以将所有页面配置包含在一个页面中。
  • 我们可以通过一些外部方案并行运行多编译器配置,但是要应用分割打包等技术会更加困难。
  • 多页面配置可以生成渐进式 Web 应用程序。在这种情况下,您可以使用各种 Webpack 技术来快速加载应用并根据需要获取应用需要的部分。两种多页面配置都具备各自的优缺点。

您将在下一章中学习如何实现服务端渲染

用户头像
登录后发表评论