New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow more programmatic control over manual chunks #2688
Comments
I believe there are several options of achieving what you are after, let me explain. The simplest option is to just designate
Example: import resolve from 'rollup-plugin-node-resolve';
export default ({
// to even better control the generated names and folders, use an object, e.g.
// {main: 'main.js', 'vendor/pkg1': 'npm-pkg-1', 'vendor/pkg2': 'npm-pkg-2'}
input: ['main.js', 'npm-pkg-1', 'npm-pkg-2'],
plugins: [resolve()],
output: [{
dir: 'dist',
format: 'esm',
// fine-grained control over the generated names; note that the "entryFileNames" option
// is now the more relevant one
entryFileNames: '[name]-[hash].js',
chunkFileNames: '[name]-[hash].js'
}]
}); Example where two chunks are created Example where more chunks are created If you want to make sure that no additional chunks are created, manualChunks are the way to go. It would be interesting to know why they did not work for you, this is what worked for me: import resolve from 'rollup-plugin-node-resolve';
export default ({
input: 'main.js',
plugins: [resolve()],
manualChunks: {'pkg-1': ['npm-pkg-1'], 'pkg-2': ['npm-pkg-2']}
output: [{
dir: 'dist',
format: 'esm'
}]
}); Note that you definitely need the |
Thanks for looking into this:
In my case this either doesn't work or isn't ideal for a few reasons:
This is a nice example (I wish the docs had examples!). I wasn't actually aware that you could list raw package names in the module list. But even then this unfortunately doesn't quite work for me. The problems here are effectively the same as the problems above: first, It requires me to know and enumerate all of my
While it's possible for me to manually work around this particular problem, it'd be a lot nicer if I could declare my manual chunks programmatically. And the second problem, again, is not all my modules have entry points, so if they don't then I have to individually list the modules I want to belong to a particular chunk. All this requires me to know a lot about my dependencies (and their dependencies). Would it be possible to add the feature proposal I outlined above? Where the |
It's it's helpful, I was able to programmatically get Rollup to do what I want, but in order to do it I had to run Rollup twice: a first time to generate a module graph I could use to create Here's an example I though together (not super optimized, but maybe it'll help clarify what I'm trying to do): const {rollup} = require('rollup');
const resolve = require('rollup-plugin-node-resolve');
const tSort = (nodes) => {
const sorted = [];
const seen = new Set();
const visit = (node) => {
if (!seen.has(node)) {
for (const dep of node.deps) {
visit(dep);
}
seen.add(node);
sorted.unshift(node);
}
}
for (const node of nodes.values()) {
visit(node);
}
return sorted;
}
const tSortRollupModules = (rollupModules) => {
// Reverse the dependency graph generated by Rollup.
// I.e. rather that a list of modules and their dependencies, make a list
// of modules and the other modules who depend on them.
const modules = new Map();
const getOrCreateModule = (id) => {
if (!modules.has(id)) {
modules.set(id, {id, deps: new Set()})
}
return modules.get(id);
};
for (const {id, dependencies} of rollupModules) {
for (const dep of dependencies) {
getOrCreateModule(dep).deps.add(getOrCreateModule(id));
}
};
return tSort(modules);
}
(async () => {
// Do the prebundle to generate the dependency graph.
const prebundle = await rollup({
input: 'assets/javascript/main.js',
plugins: [resolve()],
});
const modules = tSortRollupModules(prebundle.cache.modules);
const manualChunks = {};
const NODE_MODULE = /node_modules\/([^/]+)/;
for (const mod of modules) {
if (mod.id.match(NODE_MODULE)) {
const chunk = RegExp.$1;
let chunkMods = manualChunks[chunk] || [];
// Delete the key, so when it's re-added it goes at the end.
// A bit of a hack, but keys in `manualChunks` need to be ordered.
if (chunk in manualChunks) {
delete manualChunks[chunk];
}
manualChunks[chunk] = chunkMods
chunkMods.push(mod.id);
}
}
const bundle = await rollup({
input: 'assets/javascript/main.js',
plugins: [
resolve(),
],
manualChunks,
});
await bundle.write({
dir: 'dist',
format: 'esm',
chunkFileNames: '[name]-[hash].mjs',
entryFileNames: '[name]-[hash].mjs',
});
})(); It's a fairly involved process to generate And ideally, it'd be possible to do this in a single invocation of In case you're curious, here's what that object looked like when run for my blog:{ 'workbox-core': [ '/Users/philipwalton/Projects/philipwalton/blog/node_modules/workbox-core/_version.mjs', '/Users/philipwalton/Projects/philipwalton/blog/node_modules/workbox-core/_private/logger.mjs', '/Users/philipwalton/Projects/philipwalton/blog/node_modules/workbox-core/_private/Deferred.mjs' ], idlize: [ '/Users/philipwalton/Projects/philipwalton/blog/node_modules/idlize/lib/queueMicrotask.mjs', '/Users/philipwalton/Projects/philipwalton/blog/node_modules/idlize/lib/now.mjs', '/Users/philipwalton/Projects/philipwalton/blog/node_modules/idlize/idle-callback-polyfills.mjs', '/Users/philipwalton/Projects/philipwalton/blog/node_modules/idlize/IdleValue.mjs', '/Users/philipwalton/Projects/philipwalton/blog/node_modules/idlize/IdleQueue.mjs' ], 'dom-utils': [ '/Users/philipwalton/Projects/philipwalton/blog/node_modules/dom-utils/lib/parse-url.js', '/Users/philipwalton/Projects/philipwalton/blog/node_modules/dom-utils/lib/parents.js', '/Users/philipwalton/Projects/philipwalton/blog/node_modules/dom-utils/lib/matches.js', '/Users/philipwalton/Projects/philipwalton/blog/node_modules/dom-utils/lib/get-attributes.js', '/Users/philipwalton/Projects/philipwalton/blog/node_modules/dom-utils/lib/dispatch.js', '/Users/philipwalton/Projects/philipwalton/blog/node_modules/dom-utils/lib/closest.js', '/Users/philipwalton/Projects/philipwalton/blog/node_modules/dom-utils/lib/delegate.js', '/Users/philipwalton/Projects/philipwalton/blog/node_modules/dom-utils/index.js' ], autotrack: [ '/Users/philipwalton/Projects/philipwalton/blog/node_modules/autotrack/lib/event-emitter.js', '/Users/philipwalton/Projects/philipwalton/blog/node_modules/autotrack/lib/method-chain.js', '/Users/philipwalton/Projects/philipwalton/blog/node_modules/autotrack/lib/constants.js', '/Users/philipwalton/Projects/philipwalton/blog/node_modules/autotrack/lib/usage.js', '/Users/philipwalton/Projects/philipwalton/blog/node_modules/autotrack/lib/tracker-queue.js', '/Users/philipwalton/Projects/philipwalton/blog/node_modules/autotrack/lib/utilities.js', '/Users/philipwalton/Projects/philipwalton/blog/node_modules/autotrack/lib/store.js', '/Users/philipwalton/Projects/philipwalton/blog/node_modules/autotrack/lib/session.js', '/Users/philipwalton/Projects/philipwalton/blog/node_modules/autotrack/lib/provide.js', '/Users/philipwalton/Projects/philipwalton/blog/node_modules/autotrack/lib/plugins/url-change-tracker.js', '/Users/philipwalton/Projects/philipwalton/blog/node_modules/autotrack/lib/plugins/page-visibility-tracker.js', '/Users/philipwalton/Projects/philipwalton/blog/node_modules/autotrack/lib/plugins/media-query-tracker.js', '/Users/philipwalton/Projects/philipwalton/blog/node_modules/autotrack/lib/plugins/impression-tracker.js', '/Users/philipwalton/Projects/philipwalton/blog/node_modules/autotrack/lib/plugins/outbound-link-tracker.js', '/Users/philipwalton/Projects/philipwalton/blog/node_modules/autotrack/lib/plugins/max-scroll-tracker.js', '/Users/philipwalton/Projects/philipwalton/blog/node_modules/autotrack/lib/plugins/clean-url-tracker.js', '/Users/philipwalton/Projects/philipwalton/blog/node_modules/autotrack/lib/plugins/event-tracker.js' ], 'workbox-window': [ '/Users/philipwalton/Projects/philipwalton/blog/node_modules/workbox-window/_version.mjs', '/Users/philipwalton/Projects/philipwalton/blog/node_modules/workbox-window/utils/WorkboxEvent.mjs', '/Users/philipwalton/Projects/philipwalton/blog/node_modules/workbox-window/utils/urlsMatch.mjs', '/Users/philipwalton/Projects/philipwalton/blog/node_modules/workbox-window/utils/EventTargetShim.mjs', '/Users/philipwalton/Projects/philipwalton/blog/node_modules/workbox-window/messageSW.mjs', '/Users/philipwalton/Projects/philipwalton/blog/node_modules/workbox-window/Workbox.mjs' ] } |
So i recently ran into the same issue when trying to factor-out my vendor chunk in react application, like so:
Error was
My app seems to build and work just file with this tweak, but i'm worried that next two lines maybe did something important:
But on a first glance, if we already marked |
@Xazzzi that does sound like a bug, please post a PR if you can. Ideally we should make sure we are doing a unique comparison though, not based on |
@philipwalton I created #2831 which allows manual chunks to be defined as a function. This should hopefully allow you to solve your use case. |
EDIT: I solved my question. I was getting duplicate entries. The solution to assign some modules to the entry where they are used, is to explicitly return the name of the entry as a string, for those root level modules. If I used just
edit: correct code below in case that is useful to anybody, it creates a VENDOR, and a COMMONS bundle. COMMONS includes all shared code, instead of having multiple smaller files. So I end up for any given page in my old php app, I have a VENDOR, COMMONS, an input: [
"./src/entry-account.ts",
"./src/entry-landing.ts",
// etc
],
manualChunks: (id, { getModuleInfo }) => {
if (/\/node_modules\//.test(id)) {
console.log('%d : "%s" goes into VENDOR', nr, id);
return "VENDOR";
}
const entryPoints = [];
// We use a Set here so we handle each module at most once. This
// prevents infinite loops in case of circular dependencies
const idsToHandle = new Set(getModuleInfo(id).importers);
for (const moduleId of idsToHandle) {
const { isEntry, importers } = getModuleInfo(
moduleId
);
if (isEntry) {
entryPoints.push(moduleId);
}
// The Set iterator is intelligent enough to iterate over elements that
// are added during iteration
for (const importerId of importers) idsToHandle.add(importerId);
}
// This is an entry (root level)
if (entryPoints.length === 0) {
let entryName = `${id.split('/').slice(-1)[0].split('.')[0]}`;
console.log('%d : "%s" is the ENTRY %s', nr, id, entryName);
return entryName;
}
// If there is a unique entry, we bundle the code with that entry
if (entryPoints.length === 1) {
let entryName = `${entryPoints[0].split('/').slice(-1)[0].split('.')[0]}`;
console.log('"%s" goes into UNIQUE ENTRY %s', id, entryName);
return entryName;
}
// For multiple entries, we put it into a "shared code" bundle
if (entryPoints.length > 1) {
console.log('"%s" goes into COMMONS (non-vendor) chunk', id);
return 'common';
}
}, |
You need |
@lukastaegert thanks, this worked for me! import resolve from 'rollup-plugin-node-resolve';
export default ({
input: 'main.js',
plugins: [resolve()],
output: [{
dir: 'dist',
format: 'esm',
// note that manualChunks should now be used inside the output options!
manualChunks: {'pkg-1': ['npm-pkg-1'], 'pkg-2': ['npm-pkg-2']},
chunkFileNames: "[name].js"
}]
}); |
Feature Use Case
Note: I'm trying to do something with Rollup that I don't think is possible, so consider this a feature request. If it is possible, please let me know how it can be done.
What I want to do is use Rollup to generate multiple output files (chunks) from a single input file. The reason I want to do this, is I want to deploy and version my application code separately from my third-party dependency code.
As an example, imagine my app has entry point
./main.mjs
, and this file loads other modules both local to my application as well as some innode_modules
. Here's some super simplified code:After running this through Rollup, I want my output files to look like this:
And I want to load them using a single script tag like this:
And inside
./main-XXXXXXXX.mjs
it would look like this:The point here is that all modules from
npm-pkg-1
get collapsed into a single module (with a version string), and all modules fromnpm-pkg-2
get collapsed into a different module (with its own version string as well).Or, if you wanted, you should be able to configure rollup to collapse all third-party code into a single
vendor-XXXXXXXX.mjs
module.Note, webpack already supports a feature similar to this using its
optimization.splitChunks
andoptimization.splitChunks.cacheGroups
options. For example, on my blog I have the following config that allows me to independently version all my npm dependencies:The only difference between what I'm suggesting here and what I can do with webpack, is with webpack I have to load the scripts individually in my HTML (and also include the webpack runtime):
While listing all modules in the HTML isn't that much of a burden, my preference would be to just leverege the native module loader and not have to include the runtime.
Another side benefit of a feature like this, is you can dramatically reduce the number of total
import
statements your production code makes, which should help a lot with performance when using native modules in production.Does something like this seem feasible?
Feature Proposal
I expected the
manualChunks
option to work this this already, but it doesn't seem like it does. If that option could be extended (or a new option creating) to take a function that gets invoked with the module ID and returns a chunk name, that seems like the simplest API to me:The text was updated successfully, but these errors were encountered: