尽管 Webpack 经常用于打包单页面应用程序,但还可以将它与多个单独的页面一起使用,我们可以通过 HtmlWebpackPlugin
和一些配置来实现它。
可能的途径
使用 Webpack 生成多个页面时,您有以下几种可能的方法:
- 通过 multi-compiler-mode(多编译器模式)并返回一组配置。只要页面是分开的,并且几乎不需要在它们之间共享代码,该方法就可以工作。这种方法的好处是您可以通过 parallel-webpack 处理它以提高构建性能。
- 设置单个配置并提取通用代码。有多种实现方法,具体取决于您的操作。
- 如果您要实现渐进式 Web 应用程序(PWA),您的应用要包含 App shell 或 page 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
可以看到一些熟悉的东西:
优点和缺点
如果你构建应用程序(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 plugin、sw-precache-webpack-plugin 等插件完美结合。使用 Service Worker 可以改善离线体验。
总结
Webpack 使您可以管理多个页面配置。PWA 使应用程序在使用时再加载,而通过 Webpack 可以实现 PWA。
回顾一下:
- Webpack 可用于通过多编译器模式生成多个单独的页面,也可以将所有页面配置包含在一个页面中。
- 我们可以通过一些外部方案并行运行多编译器配置,但是要应用分割打包等技术会更加困难。
- 多页面配置可以生成渐进式 Web 应用程序。在这种情况下,您可以使用各种 Webpack 技术来快速加载应用并根据需要获取应用需要的部分。两种多页面配置都具备各自的优缺点。
您将在下一章中学习如何实现服务端渲染。