javascript - Webpack: Split dynamic imported modules into separate chunks while keeping the library in the main bundle - Stack O

I am working on a web app containing widgets that should be lazily loaded using dynamic imports.Each w

I am working on a web app containing widgets that should be lazily loaded using dynamic imports. Each widget should receive its separate bundle for it and their dependencies to only be fetched by the browser once it is being requested by the user. There's an exception to this: Popular widgets should be included in the main bundle as well as the library classes they use or extend. Below you see the folder structure I intend to achieve and the output I aim for:

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)

To lazily load widgets I am using following expression in a create method in WidgetFactory. This works fine, the widget modules get magically picked up by Webpack.

const Widget = await import(`../widgets/${name}/index`)

I was trying to solve the code splitting challenge by configuring optimization.splitChunks.cacheGroups in Webpack. Providing a test function I allocate modules into either the library or widgets cache group.

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
        }
      }
    }
  }
}

I am stuck!

  • How can I include widget dependencies in the widget bundles?
  • Using chunks: 'async' on the library cache group make some library classes go to the main chunk while others stay in the library chunk. Why?
  • When I use chunks: 'all' on the library cache group all modules properly get bined in it but I lose the entry point on the main chunk and get a blank page. How?
  • When I rename the library cache group to main I lose the entry point, as documented here. I don't quite understand why this is the case and how I manage to bine the main entry point with the library into a single bundle.

Hopefully you can shed some light onto my steep learning curve with Webpack.

I am working on a web app containing widgets that should be lazily loaded using dynamic imports. Each widget should receive its separate bundle for it and their dependencies to only be fetched by the browser once it is being requested by the user. There's an exception to this: Popular widgets should be included in the main bundle as well as the library classes they use or extend. Below you see the folder structure I intend to achieve and the output I aim for:

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)

To lazily load widgets I am using following expression in a create method in WidgetFactory. This works fine, the widget modules get magically picked up by Webpack.

const Widget = await import(`../widgets/${name}/index`)

I was trying to solve the code splitting challenge by configuring optimization.splitChunks.cacheGroups in Webpack. Providing a test function I allocate modules into either the library or widgets cache group.

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
        }
      }
    }
  }
}

I am stuck!

  • How can I include widget dependencies in the widget bundles?
  • Using chunks: 'async' on the library cache group make some library classes go to the main chunk while others stay in the library chunk. Why?
  • When I use chunks: 'all' on the library cache group all modules properly get bined in it but I lose the entry point on the main chunk and get a blank page. How?
  • When I rename the library cache group to main I lose the entry point, as documented here. I don't quite understand why this is the case and how I manage to bine the main entry point with the library into a single bundle.

Hopefully you can shed some light onto my steep learning curve with Webpack.

Share Improve this question edited Nov 18, 2019 at 15:54 ffraenz asked Nov 17, 2019 at 14:01 ffraenzffraenz 6602 gold badges10 silver badges35 bronze badges
Add a ment  | 

2 Answers 2

Reset to default 5 +50

created a simple github repo with configuration you need.

  1. SplitChunks configuration (change test and name functions to yours if you want):
    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
                }
            }
    }
  1. If you want PopularWidget to be in main, you should not import it dynamically. Use regular import statement. Because webpack will ALWAYS create a new chunk for any dynamic import. So please take a look at this file.
    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
Changed configuration to keep related node_modules with widgets.
I wrote two simple functions for new widgets chunk creation.
The trick here is module.issuer property which means who imported this file. So function isRelativeToWidget will return true for any dependencies (node_modules or not) imported at any depth of widget's file structure.
Code can be found here.
Resulting configuration:

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,
    }
}

According to what I usually use in my Webpack (v4) configuration, I'd configure it this way:

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
        },
    }
}

If widgets are only required lazily, do not add widgets to the cacheGroups.

Webpack should resolve the widget dependencies in library for each async require.

I didn't try forcing bundle names but I think it will behave the same.

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1742289118a4415864.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信