T
TanStack17mo ago
deep-jade

Code split sub-routes together

Wondering if it's possible to control how lazy file-based routes are split out instead of having each route be in its own chunk. Potential desired behavior would be that everything below a certain path (e.g. /users would include /users, /users/1, /users/new, etc) would all be included in the same chunk so that once you "entered" that section of the site once you'd already have the rest of the code loaded.
12 Replies
fascinating-indigo
fascinating-indigo17mo ago
this is currently not possible I think. @Tanner Linsley can we make this configurable with automatic code splitting?
sensitive-blue
sensitive-blue17mo ago
Anything is possible with enough effort This would mean some pretty complex additions to the vite plugin, so I'm weary to promise anything yet. I would be happy to see a PR or issue that explores the implications of this further, but not at the cost of focusing on other things for core maintainers.
fascinating-indigo
fascinating-indigo17mo ago
Rollup
The JavaScript module bundler
Vite
Next Generation Frontend Tooling
sensitive-blue
sensitive-blue17mo ago
Yep, 100% I would really love it if we could defer to vite for control on this Technically, we're not creating virtual modules, we're just importing the same file again with a search parameter that allows us to strip it down So I wonder if it would be possible for users to write their own manual chunking settings based on that ?tsr-split param
fascinating-indigo
fascinating-indigo17mo ago
I did a quick manual test in the basic-file-based-codesplitting example: baseline:
vite v5.0.13 building for production...
✓ 140 modules transformed.
dist/index.html 0.37 kB │ gzip: 0.25 kB
dist/assets/layout-b.test.lazy-sb8RgvUw.js 0.19 kB │ gzip: 0.17 kB
dist/assets/index.lazy-sDkA87w4.js 0.20 kB │ gzip: 0.18 kB
dist/assets/_layout-test.lazy-iJumyJJW.js 0.46 kB │ gzip: 0.26 kB
dist/assets/posts_._postId.deep.lazy-_nbinzzr.js 0.52 kB │ gzip: 0.35 kB
dist/assets/posts.lazy-0mkaJy-P.js 0.63 kB │ gzip: 0.40 kB
dist/assets/lazy-BxPdY6u5.js 0.72 kB │ gzip: 0.40 kB
dist/assets/index-vszCmgK_.js 274.79 kB │ gzip: 87.67 kB
vite v5.0.13 building for production...
✓ 140 modules transformed.
dist/index.html 0.37 kB │ gzip: 0.25 kB
dist/assets/layout-b.test.lazy-sb8RgvUw.js 0.19 kB │ gzip: 0.17 kB
dist/assets/index.lazy-sDkA87w4.js 0.20 kB │ gzip: 0.18 kB
dist/assets/_layout-test.lazy-iJumyJJW.js 0.46 kB │ gzip: 0.26 kB
dist/assets/posts_._postId.deep.lazy-_nbinzzr.js 0.52 kB │ gzip: 0.35 kB
dist/assets/posts.lazy-0mkaJy-P.js 0.63 kB │ gzip: 0.40 kB
dist/assets/lazy-BxPdY6u5.js 0.72 kB │ gzip: 0.40 kB
dist/assets/index-vszCmgK_.js 274.79 kB │ gzip: 87.67 kB
with the following vite config:
export default defineConfig({
plugins: [react(), TanStackRouterVite()],
build : {
rollupOptions: {
output: {
manualChunks: function(id) {
if (id.endsWith('lazy.tsx')) {
if(id.includes('posts')) {
return 'posts.grouped.lazy.js'
}
}
return null;
},
}
}
}
})
export default defineConfig({
plugins: [react(), TanStackRouterVite()],
build : {
rollupOptions: {
output: {
manualChunks: function(id) {
if (id.endsWith('lazy.tsx')) {
if(id.includes('posts')) {
return 'posts.grouped.lazy.js'
}
}
return null;
},
}
}
}
})
vite v5.0.13 building for production...
✓ 140 modules transformed.
dist/index.html 0.47 kB │ gzip: 0.30 kB
dist/assets/layout-b.test.lazy-cqNmIItC.js 0.20 kB │ gzip: 0.18 kB
dist/assets/index.lazy-GgvfSGxz.js 0.21 kB │ gzip: 0.19 kB
dist/assets/_layout-test.lazy-nXFkWJX3.js 0.48 kB │ gzip: 0.27 kB
dist/assets/index-nJPt7Mc0.js 87.12 kB │ gzip: 27.32 kB
dist/assets/posts.grouped.lazy.js-o6hp8VLJ.js 189.71 kB │ gzip: 61.33 kB
vite v5.0.13 building for production...
✓ 140 modules transformed.
dist/index.html 0.47 kB │ gzip: 0.30 kB
dist/assets/layout-b.test.lazy-cqNmIItC.js 0.20 kB │ gzip: 0.18 kB
dist/assets/index.lazy-GgvfSGxz.js 0.21 kB │ gzip: 0.19 kB
dist/assets/_layout-test.lazy-nXFkWJX3.js 0.48 kB │ gzip: 0.27 kB
dist/assets/index-nJPt7Mc0.js 87.12 kB │ gzip: 27.32 kB
dist/assets/posts.grouped.lazy.js-o6hp8VLJ.js 189.71 kB │ gzip: 61.33 kB
so you can already group routes into chunks
deep-jade
deep-jadeOP17mo ago
Interesting, I'll give that a try with my setup.
fascinating-indigo
fascinating-indigo17mo ago
probably there is some configuration mistake still since posts.grouped.lazy.js-o6hp8VLJ.js is way bigger than it should be this looks a bit better:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { TanStackRouterVite } from '@tanstack/router-vite-plugin'
import { splitVendorChunkPlugin } from 'vite'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), TanStackRouterVite(), splitVendorChunkPlugin()],
build : {
rollupOptions: {
output: {
manualChunks: function(id) {
if (id.endsWith('lazy.tsx')) {
if(id.includes('posts')) {
return 'posts.grouped.lazy.js'
}
}
return null;
},
}
}
}
})
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { TanStackRouterVite } from '@tanstack/router-vite-plugin'
import { splitVendorChunkPlugin } from 'vite'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), TanStackRouterVite(), splitVendorChunkPlugin()],
build : {
rollupOptions: {
output: {
manualChunks: function(id) {
if (id.endsWith('lazy.tsx')) {
if(id.includes('posts')) {
return 'posts.grouped.lazy.js'
}
}
return null;
},
}
}
}
})
vite v5.0.13 building for production...
✓ 140 modules transformed.
dist/index.html 0.54 kB │ gzip: 0.32 kB
dist/assets/layout-b.test.lazy-DLwev96k.js 0.24 kB │ gzip: 0.20 kB
dist/assets/index.lazy-Bm413NHW.js 0.25 kB │ gzip: 0.21 kB
dist/assets/_layout-test.lazy-PiVx8z74.js 0.50 kB │ gzip: 0.29 kB
dist/assets/posts.grouped.lazy.js--GwvVvEn.js 44.02 kB │ gzip: 15.04 kB
dist/assets/index-t1FJko9n.js 55.11 kB │ gzip: 14.28 kB
dist/assets/vendor-4Nh0gRnB.js 177.82 kB │ gzip: 59.51 kB
vite v5.0.13 building for production...
✓ 140 modules transformed.
dist/index.html 0.54 kB │ gzip: 0.32 kB
dist/assets/layout-b.test.lazy-DLwev96k.js 0.24 kB │ gzip: 0.20 kB
dist/assets/index.lazy-Bm413NHW.js 0.25 kB │ gzip: 0.21 kB
dist/assets/_layout-test.lazy-PiVx8z74.js 0.50 kB │ gzip: 0.29 kB
dist/assets/posts.grouped.lazy.js--GwvVvEn.js 44.02 kB │ gzip: 15.04 kB
dist/assets/index-t1FJko9n.js 55.11 kB │ gzip: 14.28 kB
dist/assets/vendor-4Nh0gRnB.js 177.82 kB │ gzip: 59.51 kB
deep-jade
deep-jadeOP17mo ago
I'm getting similar results on my end; however, when I load the actual site the split files are being loaded immediately. I'll continue messing with the configuration to see what needs to be included/excluded from the chunk to make it work. I'm not super familiar with how the chunking is handled, but it's looking like for some reason some common code that my components (the ones referenced by the lazy routes) are referencing is getting included in their chunk which is causing it to be loaded with the initial set of files. If I separately chunk the lazy routes on their own, that piece works fine (although that chunk is super tiny and isn't really where I'm looking to get the benefit - the components that are referenced is obviously the meatier part of the file size). Okay, I think I was able to get it going. I had to force my other code into a specific chunk instead of returning nothing. A bit later I'll make sure that it works with experimental chunking and I should be good to go. Thanks for the help!
fascinating-indigo
fascinating-indigo17mo ago
would be cool if you could post your final configuration for future reference
deep-jade
deep-jadeOP17mo ago
Yeah, absolutely. Experimental code splitting works as well, just needed to change my matcher. Here's what I ended up with (I'm also doing some splitting of larger vendor libraries). One remaining improvement on my end is to configure this at the top of the file instead of manually building if-checks.
rollupOptions: {
output: {
manualChunks(id: string) {
if (id.includes('@tanstack')) {
return '@tanstack';
}
if (id.includes('@mui') || id.includes('@emotion')) {
return '@mui';
}
// Split out generated lazy portions (with ?tsr-split) and all of the relevant components
if ((id.includes('/routes/samples/') && id.includes('?tsr-split')) || id.includes('libs/ui/sample')) {
return 'ui-sample';
}
// Split all remaining internal code into main chunk
if (id.includes('libs/ui/') || id.includes('apps/ui-')) {
return 'index';
}
// Split remaining code into vendors
return 'vendor';
},
},
},
rollupOptions: {
output: {
manualChunks(id: string) {
if (id.includes('@tanstack')) {
return '@tanstack';
}
if (id.includes('@mui') || id.includes('@emotion')) {
return '@mui';
}
// Split out generated lazy portions (with ?tsr-split) and all of the relevant components
if ((id.includes('/routes/samples/') && id.includes('?tsr-split')) || id.includes('libs/ui/sample')) {
return 'ui-sample';
}
// Split all remaining internal code into main chunk
if (id.includes('libs/ui/') || id.includes('apps/ui-')) {
return 'index';
}
// Split remaining code into vendors
return 'vendor';
},
},
},
deep-jade
deep-jade15mo ago
I just looked into this and I don't see id containing any ?tsr-split , should I look for it including .lazy. instead? I do see it being used when using experimental.enableCodeSplitting: true
TanStackRouterVite({
experimental: {
enableCodeSplitting: true,
},
}),
TanStackRouterVite({
experimental: {
enableCodeSplitting: true,
},
}),
But that's giving me different compile errors (TSR v1.35.6):
[vite-plugin-tanstack-router-code-splitter] /Users/melvin/project/app/routes/home/index.tsx?tsr-split: Unexpected splitNode type ☝️: ImportDefaultSpecifier
file: /Users/melvin/project/app/routes/home/index.tsx?tsr-split
[vite-plugin-tanstack-router-code-splitter] /Users/melvin/project/app/routes/home/index.tsx?tsr-split: Unexpected splitNode type ☝️: ImportDefaultSpecifier
file: /Users/melvin/project/app/routes/home/index.tsx?tsr-split
Edit: It seems it doesn't like me using a default import, but changing it to import { default as X } worked?
// Doesn't work: "Unexpected splitNode type ☝️: ImportDefaultSpecifier"
import HomeIndex from 'app/modules/home'
// Works
import { default as HomeIndex } from 'app/modules/home'
// Doesn't work: "Unexpected splitNode type ☝️: ImportDefaultSpecifier"
import HomeIndex from 'app/modules/home'
// Works
import { default as HomeIndex } from 'app/modules/home'
What's a good forum to report the vite router split bugs? I couldn't find any documentation on the experimental code splitting either 😊 @Tanner Linsley @somnolent2703 Did you end up using experimental code splitting? I read in another comment that when doing so one shouldn't use .lazy. in the file names. Did you confirm your chunks load correctly as needed?
fascinating-indigo
fascinating-indigo15mo ago
please report any router bugs as a GitHub issue yeah , either use lazy OR the automatic splitting

Did you find this page helpful?