Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion packages/router-generator/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { z } from 'zod'
import { virtualRootRouteSchema } from './filesystem/virtual/config'

export const configSchema = z.object({
target: z.enum(['react']).optional().default('react'),
target: z.enum(['react', 'solid']).optional().default('react'),
virtualRouteConfig: virtualRootRouteSchema.or(z.string()).optional(),
routeFilePrefix: z.string().optional(),
routeFileIgnorePrefix: z.string().optional().default('-'),
Expand Down
46 changes: 29 additions & 17 deletions packages/router-plugin/src/core/code-splitter/compilers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import { deadCodeElimination } from 'babel-dead-code-elimination'
import { generateFromAst, parseAst } from '@tanstack/router-utils'
import { tsrSplit } from '../constants'
import { createIdentifier } from './path-ids'
import { getFrameworkOptions } from './framework-options'
import type { GeneratorResult, ParseAstOptions } from '@tanstack/router-utils'
import type { CodeSplitGroupings, SplitRouteIdentNodes } from '../constants'
import type { Config } from '../config'

// eslint-disable-next-line unused-imports/no-unused-vars
const debug = process.env.TSR_VITE_DEBUG
Expand All @@ -31,7 +33,7 @@ interface State {

type SplitNodeMeta = {
routeIdent: SplitRouteIdentNodes
splitStrategy: 'normal' | 'react-component'
splitStrategy: 'lazyFn' | 'lazyRouteComponent'
localImporterIdent: string
exporterIdent: string
localExporterIdent: string
Expand All @@ -42,7 +44,7 @@ const SPLIT_NODES_CONFIG = new Map<SplitRouteIdentNodes, SplitNodeMeta>([
{
routeIdent: 'loader',
localImporterIdent: '$$splitLoaderImporter', // const $$splitLoaderImporter = () => import('...')
splitStrategy: 'normal',
splitStrategy: 'lazyFn',
localExporterIdent: 'SplitLoader', // const SplitLoader = ...
exporterIdent: 'loader', // export { SplitLoader as loader }
},
Expand All @@ -52,7 +54,7 @@ const SPLIT_NODES_CONFIG = new Map<SplitRouteIdentNodes, SplitNodeMeta>([
{
routeIdent: 'component',
localImporterIdent: '$$splitComponentImporter', // const $$splitComponentImporter = () => import('...')
splitStrategy: 'react-component',
splitStrategy: 'lazyRouteComponent',
localExporterIdent: 'SplitComponent', // const SplitComponent = ...
exporterIdent: 'component', // export { SplitComponent as component }
},
Expand All @@ -62,7 +64,7 @@ const SPLIT_NODES_CONFIG = new Map<SplitRouteIdentNodes, SplitNodeMeta>([
{
routeIdent: 'pendingComponent',
localImporterIdent: '$$splitPendingComponentImporter', // const $$splitPendingComponentImporter = () => import('...')
splitStrategy: 'react-component',
splitStrategy: 'lazyRouteComponent',
localExporterIdent: 'SplitPendingComponent', // const SplitPendingComponent = ...
exporterIdent: 'pendingComponent', // export { SplitPendingComponent as pendingComponent }
},
Expand All @@ -72,7 +74,7 @@ const SPLIT_NODES_CONFIG = new Map<SplitRouteIdentNodes, SplitNodeMeta>([
{
routeIdent: 'errorComponent',
localImporterIdent: '$$splitErrorComponentImporter', // const $$splitErrorComponentImporter = () => import('...')
splitStrategy: 'react-component',
splitStrategy: 'lazyRouteComponent',
localExporterIdent: 'SplitErrorComponent', // const SplitErrorComponent = ...
exporterIdent: 'errorComponent', // export { SplitErrorComponent as errorComponent }
},
Expand All @@ -82,7 +84,7 @@ const SPLIT_NODES_CONFIG = new Map<SplitRouteIdentNodes, SplitNodeMeta>([
{
routeIdent: 'notFoundComponent',
localImporterIdent: '$$splitNotFoundComponentImporter', // const $$splitNotFoundComponentImporter = () => import('...')
splitStrategy: 'react-component',
splitStrategy: 'lazyRouteComponent',
localExporterIdent: 'SplitNotFoundComponent', // const SplitNotFoundComponent = ...
exporterIdent: 'notFoundComponent', // export { SplitNotFoundComponent as notFoundComponent }
},
Expand Down Expand Up @@ -111,6 +113,7 @@ export function compileCodeSplitReferenceRoute(
opts: ParseAstOptions & {
runtimeEnv: 'dev' | 'prod'
codeSplitGroupings: CodeSplitGroupings
targetFramework: Config['target']
},
): GeneratorResult {
const ast = parseAst(opts)
Expand All @@ -121,6 +124,11 @@ export function compileCodeSplitReferenceRoute(
)
}

const frameworkOptions = getFrameworkOptions(opts.targetFramework)
const PACKAGE = frameworkOptions.package
const LAZY_ROUTE_COMPONENT_IDENT = frameworkOptions.idents.lazyRouteComponent
const LAZY_FN_IDENT = frameworkOptions.idents.lazyFn

babel.traverse(ast, {
Program: {
enter(programPath, programState) {
Expand Down Expand Up @@ -202,7 +210,9 @@ export function compileCodeSplitReferenceRoute(
codeSplitGroup,
)

if (splitNodeMeta.splitStrategy === 'react-component') {
if (
splitNodeMeta.splitStrategy === 'lazyRouteComponent'
) {
const value = prop.value

let shouldSplit = true
Expand Down Expand Up @@ -238,12 +248,12 @@ export function compileCodeSplitReferenceRoute(

if (
!hasImportedOrDefinedIdentifier(
'lazyRouteComponent',
LAZY_ROUTE_COMPONENT_IDENT,
)
) {
programPath.unshiftContainer('body', [
template.statement(
`import { lazyRouteComponent } from '@tanstack/react-router'`,
`import { ${LAZY_ROUTE_COMPONENT_IDENT} } from '${PACKAGE}'`,
)(),
])
}
Expand All @@ -265,28 +275,30 @@ export function compileCodeSplitReferenceRoute(
// If it's a component, we need to pass the function to check the Route.ssr value
if (key === 'component') {
prop.value = template.expression(
`lazyRouteComponent(${splitNodeMeta.localImporterIdent}, '${splitNodeMeta.exporterIdent}', () => Route.ssr)`,
`${LAZY_ROUTE_COMPONENT_IDENT}(${splitNodeMeta.localImporterIdent}, '${splitNodeMeta.exporterIdent}', () => Route.ssr)`,
)()
} else {
prop.value = template.expression(
`lazyRouteComponent(${splitNodeMeta.localImporterIdent}, '${splitNodeMeta.exporterIdent}')`,
`${LAZY_ROUTE_COMPONENT_IDENT}(${splitNodeMeta.localImporterIdent}, '${splitNodeMeta.exporterIdent}')`,
)()
}

// If the TSRDummyComponent is not defined, define it
if (
opts.runtimeEnv !== 'prod' && // only in development
!hasImportedOrDefinedIdentifier('TSRDummyComponent')
!hasImportedOrDefinedIdentifier(
frameworkOptions.idents.dummyHMRComponent,
)
) {
programPath.pushContainer('body', [
template.statement(
`export function TSRDummyComponent() { return null }`,
frameworkOptions.dummyHMRComponent,
)(),
])
}
}

if (splitNodeMeta.splitStrategy === 'normal') {
if (splitNodeMeta.splitStrategy === 'lazyFn') {
const value = prop.value

let shouldSplit = true
Expand Down Expand Up @@ -317,11 +329,11 @@ export function compileCodeSplitReferenceRoute(
}

// Prepend the import statement to the program along with the importer function
if (!hasImportedOrDefinedIdentifier('lazyFn')) {
if (!hasImportedOrDefinedIdentifier(LAZY_FN_IDENT)) {
programPath.unshiftContainer(
'body',
template.smart(
`import { lazyFn } from '@tanstack/react-router'`,
`import { ${LAZY_FN_IDENT} } from '${PACKAGE}'`,
)(),
)
}
Expand All @@ -342,7 +354,7 @@ export function compileCodeSplitReferenceRoute(

// Add the lazyFn call with the dynamic import to the prop value
prop.value = template.expression(
`lazyFn(${splitNodeMeta.localImporterIdent}, '${splitNodeMeta.exporterIdent}')`,
`${LAZY_FN_IDENT}(${splitNodeMeta.localImporterIdent}, '${splitNodeMeta.exporterIdent}')`,
)()
}
}
Expand Down
47 changes: 47 additions & 0 deletions packages/router-plugin/src/core/code-splitter/framework-options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
type FrameworkOptions = {
package: string
idents: {
createFileRoute: string
lazyFn: string
lazyRouteComponent: string
dummyHMRComponent: string
}
dummyHMRComponent: string
}

export function getFrameworkOptions(framework: string): FrameworkOptions {
let frameworkOptions: FrameworkOptions

switch (framework) {
case 'react':
frameworkOptions = {
package: '@tanstack/react-router',
idents: {
createFileRoute: 'createFileRoute',
lazyFn: 'lazyFn',
lazyRouteComponent: 'lazyRouteComponent',
dummyHMRComponent: 'TSRDummyComponent',
},
dummyHMRComponent: `export function TSRDummyComponent() { return null }`,
}
break
case 'solid':
frameworkOptions = {
package: '@tanstack/solid-router',
idents: {
createFileRoute: 'createFileRoute',
lazyFn: 'lazyFn',
lazyRouteComponent: 'lazyRouteComponent',
dummyHMRComponent: 'TSRDummyComponent',
},
dummyHMRComponent: `export function TSRDummyComponent() { return null }`,
}
break
default:
throw new Error(
`[getFrameworkOptions] - Unsupported framework: ${framework}`,
)
}

return frameworkOptions
}
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ export const unpluginRouterCodeSplitterFactory: UnpluginFactory<
filename: id,
runtimeEnv: isProduction ? 'prod' : 'dev',
codeSplitGroupings: splitGroupings,
targetFramework: userConfig.target,
})

if (debug) {
Expand Down
134 changes: 71 additions & 63 deletions packages/router-plugin/tests/code-splitter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,18 @@ import {
import { createIdentifier } from '../src/core/code-splitter/path-ids'
import { defaultCodeSplitGroupings } from '../src/core/constants'
import type { CodeSplitGroupings } from '../src/core/constants'
import type { Config } from '../src/core/config'

async function getFilenames() {
return await readdir(path.resolve(__dirname, './code-splitter/test-files'))
function getFrameworkDir(framework: string) {
const files = path.resolve(
__dirname,
`./code-splitter/test-files/${framework}`,
)
const snapshots = path.resolve(
__dirname,
`./code-splitter/snapshots/${framework}`,
)
return { files, snapshots }
}

const testGroups: Array<{ name: string; groupings: CodeSplitGroupings }> = [
Expand All @@ -35,74 +44,73 @@ const testGroups: Array<{ name: string; groupings: CodeSplitGroupings }> = [
},
]

const frameworks: Array<Config['target']> = ['react', 'solid']

describe('code-splitter works', () => {
describe.each(testGroups)(
'SPLIT_GROUP=$name',
async ({ groupings: grouping, name: groupName }) => {
const filenames = await getFilenames()
describe.each(frameworks)('FRAMEWORK=%s', (framework) => {
describe.each(testGroups)(
'SPLIT_GROUP=$name',
async ({ groupings: grouping, name: groupName }) => {
const dirs = getFrameworkDir(framework)
const filenames = await readdir(dirs.files)

describe.each(['development', 'production'])(
'NODE_ENV=%s ',
(NODE_ENV) => {
process.env.NODE_ENV = NODE_ENV
describe.each(['development', 'production'])(
'NODE_ENV=%s ',
(NODE_ENV) => {
process.env.NODE_ENV = NODE_ENV

it.each(filenames)(
`should compile "reference" for "%s"`,
async (filename) => {
const file = await readFile(
path.resolve(
__dirname,
`./code-splitter/test-files/${filename}`,
),
)
const code = file.toString()
it.each(filenames)(
`should compile "reference" for "%s"`,
async (filename) => {
const file = await readFile(path.join(dirs.files, filename))
const code = file.toString()

const compileResult = compileCodeSplitReferenceRoute({
code,
root: './code-splitter/test-files',
filename,
runtimeEnv: NODE_ENV === 'production' ? 'prod' : 'dev',
codeSplitGroupings: grouping,
})
const compileResult = compileCodeSplitReferenceRoute({
code,
root: dirs.files,
filename,
runtimeEnv: NODE_ENV === 'production' ? 'prod' : 'dev',
codeSplitGroupings: grouping,
targetFramework: framework,
})

await expect(compileResult.code).toMatchFileSnapshot(
`./code-splitter/snapshots/${groupName}/${NODE_ENV}/${filename}`,
)
},
)
await expect(compileResult.code).toMatchFileSnapshot(
path.join(dirs.snapshots, groupName, NODE_ENV, filename),
)
},
)

it.each(filenames)(
`should compile "virtual" for "%s"`,
async (filename) => {
const file = await readFile(
path.resolve(
__dirname,
`./code-splitter/test-files/${filename}`,
),
)
const code = file.toString()
it.each(filenames)(
`should compile "virtual" for "%s"`,
async (filename) => {
const file = await readFile(path.join(dirs.files, filename))
const code = file.toString()

for (const targets of grouping) {
const ident = createIdentifier(targets)
for (const targets of grouping) {
const ident = createIdentifier(targets)

const splitResult = compileCodeSplitVirtualRoute({
code,
root: './code-splitter/test-files',
filename: `${filename}?${ident}`,
splitTargets: targets,
})
const splitResult = compileCodeSplitVirtualRoute({
code,
root: dirs.files,
filename: `${filename}?${ident}`,
splitTargets: targets,
})

await expect(splitResult.code).toMatchFileSnapshot(
`./code-splitter/snapshots/${groupName}/${NODE_ENV}/${filename.replace(
'.tsx',
'',
)}@${ident}.tsx`,
)
}
},
)
},
)
},
)
const snapshotFilename = path.join(
dirs.snapshots,
groupName,
NODE_ENV,
`${filename.replace('.tsx', '')}@${ident}.tsx`,
)
await expect(splitResult.code).toMatchFileSnapshot(
snapshotFilename,
)
}
},
)
},
)
},
)
})
})

This file was deleted.

Loading