Webpack:将动态导入的模块拆分为单独的块,同时将库保留在主包中
Webpack: Split dynamic imported modules into separate chunks while keeping the library in the main bundle
我正在开发一个网络应用程序,其中包含应使用 dynamic imports 延迟加载的小部件。
每个小部件都应该为其接收单独的包,并且它们的依赖项仅在用户请求时才由浏览器获取。有一个例外:流行的小部件应该包含在主包中以及它们使用或扩展的库 类 中。下面是我打算实现的文件夹结构和我的目标输出:
File Structure Desired chunk
src/
├── widgets/
│ ├── popular-widget/index.ts main
│ ├── edge-case-widget/index.ts edge-case-widget
│ └── interesting-widget/index.ts interesting-widget
├── library/
│ ├── Widget.ts main
│ └── WidgetFactory.ts main
└── index.ts main (incl. entry point)
dist/
├── edge-case-widget.app.js edge-case-widget
├── interesting-widget.app.js interesting-widget
└── main.app.js main (incl. entry point)
为了延迟加载小部件,我在 WidgetFactory
的 create
方法中使用了以下表达式。这工作正常,小部件模块被 Webpack 神奇地拾取。
const Widget = await import(`../widgets/${name}/index`)
我试图通过在 Webpack 中配置 optimization.splitChunks.cacheGroups
来解决代码拆分挑战。提供 test
函数,我将模块分配到 library
或 widgets
缓存组中。
module.exports = {
entry: './src/index.ts',
[...]
optimization: {
splitChunks: {
cacheGroups: {
library: {
test: module => isInLibrary(module.identifier()),
name: 'library',
chunks: 'all',
minSize: 0
},
widgets: {
test: module => isWidgetBundle(module.identifier()),
name: module => widgetNameFromModuleId(module.identifier()),
chunks: 'async',
minSize: 0
}
}
}
}
}
我卡住了!
- 如何在小部件包中包含小部件依赖项?
- 在
library
缓存组上使用 chunks: 'async'
使一些库 类 转到 main
块,而其他库留在 library
块中。为什么?
- 当我在
library
缓存组上使用 chunks: 'all'
时,所有模块都正确地组合在其中,但我丢失了 main
块上的入口点并得到一个空白页。怎么样?
- 当我将
library
缓存组重命名为 main
时,我丢失了入口点 as documented here。我不太明白为什么会这样,以及我如何设法将 main
入口点与 library
组合成一个包。
希望您能阐明我使用 Webpack 的陡峭学习曲线。
根据我平时使用的Webpack(v4)配置,我会这样配置:
splitChunks: {
chunks: 'async',
minSize: 30000,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
name: true,
cacheGroups: {
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
library: {
test: module => module => isInLibrary(module.identifier()),
chunks: 'all',
enforce: true
},
}
}
如果只是懒惰地需要小部件,请不要将 widgets
添加到 cacheGroups。
Webpack 应该为每个异步需求解析库中的小部件依赖关系。
我没有尝试强制使用包名称,但我认为它的行为是一样的。
使用您需要的配置创建了一个简单的 github repo。
- SplitChunks 配置(如果需要,将
test
和 name
函数更改为您的):
splitChunks: {
cacheGroups: {
widgets: {
test: module => {
return module.identifier().includes('src/widgets');
},
name: module => {
const list = module.identifier().split('/');
list.pop();
return list.pop();
},
chunks: 'async',
enforce: true
}
}
}
- 如果您希望 PopularWidget 在
main
中,则不应动态导入它。使用常规 import
语句。因为 webpack 总是会为任何动态导入创建一个新块。所以请看一下 this 文件。
import { Widget } from '../widgets/popular-widget';
export class WidgetFactory {
async create(name) {
if (name === 'popular-widget') return await Promise.resolve(Widget);
return await import(`../widgets/${name}/index`)
}
}
UPD
更改配置以保持 node_modules 与小部件相关。
我为创建新的小部件块编写了两个简单的函数。
这里的技巧是 module.issuer
属性 这意味着谁导入了这个文件。因此函数 isRelativeToWidget
将 return 对于在小部件文件结构的任何深度导入的任何依赖项(node_modules 或不)为真。
可以找到代码here.
结果配置:
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: false,
default: false,
edgeCaseWidget: getSplitChunksRuleForWidget('edge-case-widget'),
interestingWidget: getSplitChunksRuleForWidget('interesting-widget')
}
}
function isRelativeToWidget(module, widgetName) {
if (module.identifier().includes(widgetName)) return true;
if (module.issuer) return isRelativeToWidget(module.issuer, widgetName)
}
function getSplitChunksRuleForWidget(widgetName) {
return {
test: module => isRelativeToWidget(module, widgetName),
name: widgetName,
chunks: 'async',
enforce: true,
}
}
我正在开发一个网络应用程序,其中包含应使用 dynamic imports 延迟加载的小部件。 每个小部件都应该为其接收单独的包,并且它们的依赖项仅在用户请求时才由浏览器获取。有一个例外:流行的小部件应该包含在主包中以及它们使用或扩展的库 类 中。下面是我打算实现的文件夹结构和我的目标输出:
File Structure Desired chunk
src/
├── widgets/
│ ├── popular-widget/index.ts main
│ ├── edge-case-widget/index.ts edge-case-widget
│ └── interesting-widget/index.ts interesting-widget
├── library/
│ ├── Widget.ts main
│ └── WidgetFactory.ts main
└── index.ts main (incl. entry point)
dist/
├── edge-case-widget.app.js edge-case-widget
├── interesting-widget.app.js interesting-widget
└── main.app.js main (incl. entry point)
为了延迟加载小部件,我在 WidgetFactory
的 create
方法中使用了以下表达式。这工作正常,小部件模块被 Webpack 神奇地拾取。
const Widget = await import(`../widgets/${name}/index`)
我试图通过在 Webpack 中配置 optimization.splitChunks.cacheGroups
来解决代码拆分挑战。提供 test
函数,我将模块分配到 library
或 widgets
缓存组中。
module.exports = {
entry: './src/index.ts',
[...]
optimization: {
splitChunks: {
cacheGroups: {
library: {
test: module => isInLibrary(module.identifier()),
name: 'library',
chunks: 'all',
minSize: 0
},
widgets: {
test: module => isWidgetBundle(module.identifier()),
name: module => widgetNameFromModuleId(module.identifier()),
chunks: 'async',
minSize: 0
}
}
}
}
}
我卡住了!
- 如何在小部件包中包含小部件依赖项?
- 在
library
缓存组上使用chunks: 'async'
使一些库 类 转到main
块,而其他库留在library
块中。为什么? - 当我在
library
缓存组上使用chunks: 'all'
时,所有模块都正确地组合在其中,但我丢失了main
块上的入口点并得到一个空白页。怎么样? - 当我将
library
缓存组重命名为main
时,我丢失了入口点 as documented here。我不太明白为什么会这样,以及我如何设法将main
入口点与library
组合成一个包。
希望您能阐明我使用 Webpack 的陡峭学习曲线。
根据我平时使用的Webpack(v4)配置,我会这样配置:
splitChunks: {
chunks: 'async',
minSize: 30000,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
name: true,
cacheGroups: {
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
library: {
test: module => module => isInLibrary(module.identifier()),
chunks: 'all',
enforce: true
},
}
}
如果只是懒惰地需要小部件,请不要将 widgets
添加到 cacheGroups。
Webpack 应该为每个异步需求解析库中的小部件依赖关系。
我没有尝试强制使用包名称,但我认为它的行为是一样的。
使用您需要的配置创建了一个简单的 github repo。
- SplitChunks 配置(如果需要,将
test
和name
函数更改为您的):
splitChunks: {
cacheGroups: {
widgets: {
test: module => {
return module.identifier().includes('src/widgets');
},
name: module => {
const list = module.identifier().split('/');
list.pop();
return list.pop();
},
chunks: 'async',
enforce: true
}
}
}
- 如果您希望 PopularWidget 在
main
中,则不应动态导入它。使用常规import
语句。因为 webpack 总是会为任何动态导入创建一个新块。所以请看一下 this 文件。
import { Widget } from '../widgets/popular-widget';
export class WidgetFactory {
async create(name) {
if (name === 'popular-widget') return await Promise.resolve(Widget);
return await import(`../widgets/${name}/index`)
}
}
UPD
更改配置以保持 node_modules 与小部件相关。
我为创建新的小部件块编写了两个简单的函数。
这里的技巧是 module.issuer
属性 这意味着谁导入了这个文件。因此函数 isRelativeToWidget
将 return 对于在小部件文件结构的任何深度导入的任何依赖项(node_modules 或不)为真。
可以找到代码here.
结果配置:
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: false,
default: false,
edgeCaseWidget: getSplitChunksRuleForWidget('edge-case-widget'),
interestingWidget: getSplitChunksRuleForWidget('interesting-widget')
}
}
function isRelativeToWidget(module, widgetName) {
if (module.identifier().includes(widgetName)) return true;
if (module.issuer) return isRelativeToWidget(module.issuer, widgetName)
}
function getSplitChunksRuleForWidget(widgetName) {
return {
test: module => isRelativeToWidget(module, widgetName),
name: widgetName,
chunks: 'async',
enforce: true,
}
}