From 317169c5e4c7bbf247dbd88ae7e64d4729497dd4 Mon Sep 17 00:00:00 2001 From: Valerio Ageno Date: Sun, 28 Apr 2024 10:46:45 +0200 Subject: [PATCH] feat: imported from @tanstack/router base vite-plugin logic --- .eslintrc | 2 +- .npmrc | 2 + .nvmrc | 1 + package.json | 1 + packages/vite-fs-router/package.json | 20 +- packages/vite-fs-router/src/compiler.ts | 281 ++++++++++++++++- packages/vite-fs-router/src/constants.ts | 1 + .../src/eliminateUnreferencedIdentifiers.ts | 298 ++++++++++++++++++ packages/vite-fs-router/src/index.ts | 15 +- packages/vite-fs-router/tests/index.test.ts | 34 ++ .../vite-fs-router/tests/shared/imported.tsx | 3 + .../snapshots/default-exported?split.tsx | 3 + .../tests/snapshots/function-export?split.tsx | 4 + .../tests/snapshots/imported?split.tsx | 2 + .../tests/test-files/default-exported.tsx | 5 + .../tests/test-files/function-export.tsx | 7 + .../tests/test-files/imported.tsx | 4 + packages/vite-fs-router/vite.config.ts | 7 +- tsconfig.json | 1 + 19 files changed, 678 insertions(+), 13 deletions(-) create mode 100644 .npmrc create mode 100644 .nvmrc create mode 100644 packages/vite-fs-router/src/constants.ts create mode 100644 packages/vite-fs-router/src/eliminateUnreferencedIdentifiers.ts create mode 100644 packages/vite-fs-router/tests/index.test.ts create mode 100644 packages/vite-fs-router/tests/shared/imported.tsx create mode 100644 packages/vite-fs-router/tests/snapshots/default-exported?split.tsx create mode 100644 packages/vite-fs-router/tests/snapshots/function-export?split.tsx create mode 100644 packages/vite-fs-router/tests/snapshots/imported?split.tsx create mode 100644 packages/vite-fs-router/tests/test-files/default-exported.tsx create mode 100644 packages/vite-fs-router/tests/test-files/function-export.tsx create mode 100644 packages/vite-fs-router/tests/test-files/imported.tsx diff --git a/.eslintrc b/.eslintrc index 867235dd..55ecadef 100644 --- a/.eslintrc +++ b/.eslintrc @@ -59,7 +59,7 @@ ], "@typescript-eslint/no-empty-function": "error", "@typescript-eslint/no-empty-interface": "error", - "@typescript-eslint/no-explicit-any": "error", + "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-non-null-assertion": "error", "@typescript-eslint/no-unnecessary-condition": "error", "@typescript-eslint/no-unnecessary-type-assertion": "error", diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000..270e1d17 --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +prefer-workspace-packages=true + diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..790e1105 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v20.10.0 diff --git a/package.json b/package.json index d98129d9..6529ea14 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "format": "turbo format" }, "keywords": [], + "type": "module", "author": "", "license": "MIT", "devDependencies": { diff --git a/packages/vite-fs-router/package.json b/packages/vite-fs-router/package.json index 67d714bf..cd1eba39 100644 --- a/packages/vite-fs-router/package.json +++ b/packages/vite-fs-router/package.json @@ -8,6 +8,7 @@ "build": "vite build", "lint": "eslint --ext .ts,.tsx ./src -c ../../.eslintrc", "format": "prettier --ignore-unknown '**/*'", + "test": "vitest --typecheck --watch=false", "types": "tsc --noEmit" }, "exports": { @@ -32,10 +33,25 @@ "type": "module", "author": "Valerio Ageno", "license": "MIT", - "devDependencies": { + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/generator": "^7.23.6", + "@babel/plugin-syntax-jsx": "^7.24.1", + "@babel/plugin-syntax-typescript": "^7.24.1", + "@babel/plugin-transform-react-jsx": "^7.23.4", + "@babel/plugin-transform-typescript": "^7.24.1", + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.1", + "@babel/types": "^7.24.0", + "@types/babel__core": "^7.20.5", + "@vitejs/plugin-react": "^4.2.1", "rollup-plugin-preserve-directives": "^0.4.0", "vite": "^5.2.10", "vite-plugin-dts": "^3.9.0", - "vite-plugin-externalize-deps": "^0.8.0" + "vite-plugin-externalize-deps": "^0.8.0", + "vitest": "^1.5.2" + }, + "devDependencies": { + "@types/babel-traverse": "^6.25.10" } } diff --git a/packages/vite-fs-router/src/compiler.ts b/packages/vite-fs-router/src/compiler.ts index 9fbdaf93..9aec80d6 100644 --- a/packages/vite-fs-router/src/compiler.ts +++ b/packages/vite-fs-router/src/compiler.ts @@ -1,11 +1,284 @@ -export const makeCompile = (): void => { - console.log('compiling [compile fn]') +import * as babel from '@babel/core' +import * as t from '@babel/types' +import { eliminateUnreferencedIdentifiers } from './eliminateUnreferencedIdentifiers' +import { SPLIT_PREFIX } from './constants' +import type { BabelFileResult } from '@babel/core' + +type SplitModulesById = Record< + string, + { id: string; node: t.FunctionExpression } +> + +interface State { + filename: string + opts: { + minify: boolean + root: string + } + imported: Record + refs: Set + serverIndex: number + splitIndex: number + splitModulesById: SplitModulesById +} + +interface MakeCompileFnArgs { + root: string +} + +interface MakeCompileFnReturnArgs { + code: string + filename: string + getBabelConfig: () => { plugins: any[] } +} + +interface CompileOutput { + code: string + map: BabelFileResult['map'] +} + +type SplitNodeType = (typeof splitNodeTypes)[number] + +export const makeCompile = ({ + root, +}: MakeCompileFnArgs): ((args: MakeCompileFnReturnArgs) => CompileOutput) => { + return ({ code, getBabelConfig, filename }) => { + const res = babel.transformSync(code, { + plugins: [ + ['@babel/plugin-syntax-jsx', {}], + ['@babel/plugin-syntax-typescript', { isTSX: true }], + ...getBabelConfig().plugins, + ], + root, + filename, + sourceMaps: true, + }) + + if (res?.code) { + return { + code: res.code, + map: res.map, + } + } + + return { + code, + map: null, + } + } } interface SplitFileFnArgs { code: string + compile: (args: MakeCompileFnReturnArgs) => CompileOutput + filename: string } -export const splitFile = async ({ code }: SplitFileFnArgs): Promise => { - console.log('Split file [splitFile fn]', code) +// NOTE: We don't need the loader +const splitNodeTypes = ['component', 'loader'] as const + +// Reusable function to get literal value or resolve variable to literal +function resolveIdentifier(path: any, node: any) { + if (t.isIdentifier(node)) { + const binding = path.scope.getBinding(node.name) + if (binding) { + const declarator = binding.path.node + if (t.isObjectExpression(declarator.init)) { + return declarator.init + } else if (t.isFunctionDeclaration(declarator.init)) { + return declarator.init + } + } + return undefined + } + + return node +} + +export const splitFile = async ({ + code, + compile, + filename, +}: SplitFileFnArgs): Promise => { + console.log('Split file [splitFile fn]', code) + return compile({ + code, + filename, + getBabelConfig: () => ({ + plugins: [ + [ + { + visitor: { + Program: { + enter( + programPath: babel.NodePath, + state: State, + ): void { + const splitNodesByType: Record< + SplitNodeType, + t.Node | undefined + > = { + component: undefined, + loader: undefined, + } + + // Find the node + // NOTE: I'd use a more NextJS approach. + // All the "default exported" fns are the page. + programPath.traverse( + { + CallExpression: (path) => { + if (path.node.callee.type === 'Identifier') { + // TODO: Update this to support just "default exported" fns + if (path.node.callee.name === 'createFileRoute') { + if ( + path.parentPath.node.type === 'CallExpression' + ) { + const options = resolveIdentifier( + path, + path.parentPath.node.arguments[0], + ) + + if (t.isObjectExpression(options)) { + options.properties.forEach((prop) => { + if (t.isObjectProperty(prop)) { + splitNodeTypes.forEach((type) => { + if (t.isIdentifier(prop.key)) { + if (prop.key.name === 'type') { + splitNodesByType[type] = prop.value + } + } + }) + } + }) + + // Remove all of the options + options.properties = [] + } + } + } + } + }, + }, + state, + ) + + // TODO: We don't need to iterate since we don't support the loader + splitNodeTypes.forEach((splitType) => { + let splitNode = splitNodesByType[splitType] + + if (!splitNode) { + return + } + + while (t.isIdentifier(splitNode)) { + const binding = programPath.scope.getBinding( + splitNode.name, + ) + splitNode = binding?.path.node + } + + // Add the node to the program + if (splitNode) { + if (t.isFunctionDeclaration(splitNode)) { + programPath.pushContainer( + 'body', + t.variableDeclaration('const', [ + t.variableDeclarator( + t.identifier(splitType), + t.functionExpression( + splitNode.id || null, + splitNode.params, + splitNode.body, + splitNode.generator, + splitNode.async, + ), + ), + ]), + ) + } else if ( + t.isFunctionExpression(splitNode) || + t.isArrowFunctionExpression(splitNode) + ) { + programPath.pushContainer( + 'body', + t.variableDeclaration('const', [ + t.variableDeclarator( + t.identifier(splitType), + splitType as any, + ), + ]), + ) + } else if (t.isImportSpecifier(splitNode)) { + programPath.pushContainer( + 'body', + t.variableDeclaration('const', [ + t.variableDeclarator( + t.identifier(splitType), + splitNode.local, + ), + ]), + ) + } else { + console.log(splitNode) + throw new Error( + `Unexpected splitNode type ${splitNode.type}`, + ) + } + } + + // If the splitNode exists at the top of the program + // then we need to remove that copy + programPath.node.body = programPath.node.body.filter( + (node) => { + return node !== splitNode + }, + ) + + // Export the node + programPath.pushContainer('body', [ + t.exportNamedDeclaration(null, [ + t.exportSpecifier( + t.identifier(splitType), + t.identifier(splitType), + ), + ]), + ]) + }) + + // Convert exports to imports from the original file + programPath.traverse({ + ExportNamedDeclaration(path) { + // e.g. export const x = 1 or export { x } + // becomes + // import { x } from '${opts.id}' + if (path.node.declaration) { + if (t.isVariableDeclaration(path.node.declaration)) { + path.replaceWith( + t.importDeclaration( + path.node.declaration.declarations.map((decl) => + t.importSpecifier( + t.identifier((decl.id as any).name), + t.identifier((decl.id as any).name), + ), + ), + t.stringLiteral( + filename.split(`?${SPLIT_PREFIX}`)[0] as string, + ), + ), + ) + } + } + }, + }) + + eliminateUnreferencedIdentifiers(programPath) + }, + }, + }, + }, + ], + ], + }), + }) } diff --git a/packages/vite-fs-router/src/constants.ts b/packages/vite-fs-router/src/constants.ts new file mode 100644 index 00000000..a617de7e --- /dev/null +++ b/packages/vite-fs-router/src/constants.ts @@ -0,0 +1 @@ +export const SPLIT_PREFIX = 'sp-' diff --git a/packages/vite-fs-router/src/eliminateUnreferencedIdentifiers.ts b/packages/vite-fs-router/src/eliminateUnreferencedIdentifiers.ts new file mode 100644 index 00000000..6d63cfe8 --- /dev/null +++ b/packages/vite-fs-router/src/eliminateUnreferencedIdentifiers.ts @@ -0,0 +1,298 @@ +// Copied from https://github.com/pcattori/vite-env-only/blob/main/src/dce.ts +// Adapted with some minor changes for the purpose of this project + +import * as t from '@babel/types' +import type { types as BabelTypes } from '@babel/core' +import type { NodePath } from '@babel/traverse' + +type IdentifierPath = NodePath + +// export function findReferencedIdentifiers( +// programPath: NodePath, +// ): Set { +// const refs = new Set() + +// function markFunction( +// path: NodePath< +// | BabelTypes.FunctionDeclaration +// | BabelTypes.FunctionExpression +// | BabelTypes.ArrowFunctionExpression +// >, +// ) { +// const ident = getIdentifier(path) +// if (ident?.node && isIdentifierReferenced(ident)) { +// refs.add(ident) +// } +// } + +// function markImport( +// path: NodePath< +// | BabelTypes.ImportSpecifier +// | BabelTypes.ImportDefaultSpecifier +// | BabelTypes.ImportNamespaceSpecifier +// >, +// ) { +// const local = path.get('local') +// if (isIdentifierReferenced(local)) { +// refs.add(local) +// } +// } + +// programPath.traverse({ +// VariableDeclarator(path) { +// if (path.node.id.type === 'Identifier') { +// const local = path.get('id') as NodePath +// if (isIdentifierReferenced(local)) { +// refs.add(local) +// } +// } else if (path.node.id.type === 'ObjectPattern') { +// const pattern = path.get('id') as NodePath + +// const properties = pattern.get('properties') +// properties.forEach((p) => { +// const local = p.get( +// p.node.type === 'ObjectProperty' +// ? 'value' +// : p.node.type === 'RestElement' +// ? 'argument' +// : (function () { +// throw new Error('invariant') +// })(), +// ) as NodePath +// if (isIdentifierReferenced(local)) { +// refs.add(local) +// } +// }) +// } else if (path.node.id.type === 'ArrayPattern') { +// const pattern = path.get('id') as NodePath + +// const elements = pattern.get('elements') +// elements.forEach((e) => { +// let local: NodePath +// if (e.node?.type === 'Identifier') { +// local = e as NodePath +// } else if (e.node?.type === 'RestElement') { +// local = e.get('argument') as NodePath +// } else { +// return +// } + +// if (isIdentifierReferenced(local)) { +// refs.add(local) +// } +// }) +// } +// }, + +// FunctionDeclaration: markFunction, +// FunctionExpression: markFunction, +// ArrowFunctionExpression: markFunction, +// ImportSpecifier: markImport, +// ImportDefaultSpecifier: markImport, +// ImportNamespaceSpecifier: markImport, +// }) +// return refs +// } + +/** + * @param refs - If provided, only these identifiers will be considered for removal. + */ +export const eliminateUnreferencedIdentifiers = ( + programPath: NodePath, + refs?: Set, +): void => { + let referencesRemovedInThisPass: number + + const shouldBeRemoved = (ident: IdentifierPath): boolean => { + if (isIdentifierReferenced(ident)) return false + if (!refs) return true + return refs.has(ident) + } + + const sweepFunction = ( + path: NodePath< + | BabelTypes.FunctionDeclaration + | BabelTypes.FunctionExpression + | BabelTypes.ArrowFunctionExpression + >, + ): void => { + const identifier = getIdentifier(path) + if (identifier?.node && shouldBeRemoved(identifier)) { + ++referencesRemovedInThisPass + + if ( + t.isAssignmentExpression(path.parentPath.node) || + t.isVariableDeclarator(path.parentPath.node) + ) { + path.parentPath.remove() + } else { + path.remove() + } + } + } + + const sweepImport = ( + path: NodePath< + | BabelTypes.ImportSpecifier + | BabelTypes.ImportDefaultSpecifier + | BabelTypes.ImportNamespaceSpecifier + >, + ): void => { + const local = path.get('local') + if (shouldBeRemoved(local)) { + ++referencesRemovedInThisPass + path.remove() + if ( + (path.parent as BabelTypes.ImportDeclaration).specifiers.length === 0 + ) { + path.parentPath.remove() + } + } + } + + // Traverse again to remove unused references. This happens at least once, + // then repeats until no more references are removed. + do { + referencesRemovedInThisPass = 0 + + programPath.scope.crawl() + + programPath.traverse({ + VariableDeclarator(path) { + if (path.node.id.type === 'Identifier') { + const local = path.get('id') as NodePath + if (shouldBeRemoved(local)) { + ++referencesRemovedInThisPass + path.remove() + } + } else if (path.node.id.type === 'ObjectPattern') { + const pattern = path.get('id') as NodePath + + const beforeCount = referencesRemovedInThisPass + const properties = pattern.get('properties') + properties.forEach((property) => { + const local = property.get( + property.node.type === 'ObjectProperty' + ? 'value' + : // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + property.node.type === 'RestElement' + ? 'argument' + : (function () { + throw new Error('invariant') + })(), + ) as NodePath + + if (shouldBeRemoved(local)) { + ++referencesRemovedInThisPass + property.remove() + } + }) + + if ( + beforeCount !== referencesRemovedInThisPass && + pattern.get('properties').length < 1 + ) { + path.remove() + } + } else if (path.node.id.type === 'ArrayPattern') { + const pattern = path.get('id') as NodePath + + let hasRemoved = false as boolean + + pattern.get('elements').forEach((element, index) => { + // if (!element) return // Skip holes in the pattern + + let identifierPath: NodePath + + if (t.isIdentifier(element.node)) { + identifierPath = element as NodePath + } else if (t.isRestElement(element.node)) { + identifierPath = element.get( + 'argument', + ) as NodePath + } else { + // For now, ignore other types like AssignmentPattern + return + } + + if (shouldBeRemoved(identifierPath)) { + hasRemoved = true + pattern.node.elements[index] = null // Remove the element by setting it to null + } + }) + + // If any elements were removed and no elements are left, remove the entire declaration + if ( + hasRemoved && + pattern.node.elements.every((element) => element === null) + ) { + path.remove() + ++referencesRemovedInThisPass + } + } + }, + FunctionDeclaration: sweepFunction, + FunctionExpression: sweepFunction, + ArrowFunctionExpression: sweepFunction, + ImportSpecifier: sweepImport, + ImportDefaultSpecifier: sweepImport, + ImportNamespaceSpecifier: sweepImport, + }) + } while (referencesRemovedInThisPass) +} + +function getIdentifier( + path: NodePath< + | BabelTypes.FunctionDeclaration + | BabelTypes.FunctionExpression + | BabelTypes.ArrowFunctionExpression + >, +): NodePath | null { + const parentPath = path.parentPath + if (parentPath.type === 'VariableDeclarator') { + const variablePath = parentPath as NodePath + const name = variablePath.get('id') + return name.node.type === 'Identifier' + ? (name as NodePath) + : null + } + + if (parentPath.type === 'AssignmentExpression') { + const variablePath = parentPath as NodePath + const name = variablePath.get('left') + return name.node.type === 'Identifier' + ? (name as NodePath) + : null + } + + if (path.node.type === 'ArrowFunctionExpression') { + return null + } + + if (path.node.type === 'FunctionExpression') { + return null + } + + return path.node.id && path.node.id.type === 'Identifier' + ? (path.get('id') as NodePath) + : null +} + +function isIdentifierReferenced( + ident: NodePath, +): boolean { + const binding = ident.scope.getBinding(ident.node.name) + if (binding?.referenced) { + // Functions can reference themselves, so we need to check if there's a + // binding outside the function scope or not. + if (binding.path.type === 'FunctionDeclaration') { + return !binding.constantViolations + .concat(binding.referencePaths) + // Check that every reference is contained within the function: + .every((ref) => ref.findParent((parent) => parent === binding.path)) + } + + return true + } + return false +} diff --git a/packages/vite-fs-router/src/index.ts b/packages/vite-fs-router/src/index.ts index b450ab79..c5ca60ab 100644 --- a/packages/vite-fs-router/src/index.ts +++ b/packages/vite-fs-router/src/index.ts @@ -3,21 +3,23 @@ import { fileURLToPath, pathToFileURL } from 'url' import { normalize } from 'path' // eslint-disable-next-line sort-imports import { makeCompile, splitFile } from './compiler' +import { SPLIT_PREFIX } from './constants' + import type { Plugin } from 'vite' -const SPLIT_PREFIX = 'sp-' const ROUTES_DIRECTORY_PATH = 'src/routes' const DEBUG = true let lock = false export function RouterGenerator(): Plugin { - const _generate = async (): Promise => { + const generate = async (): Promise => { if (lock) return lock = true try { // TODO: generator function + // This generator function is from the router-generator package console.log('Generating [generate fn]') } catch (err) { console.log(err) @@ -32,11 +34,15 @@ export function RouterGenerator(): Plugin { if (filePath.startsWith(ROUTES_DIRECTORY_PATH)) { // TODO: generator function console.log('Generating [handleFile fn]') + await generate() } } return { name: 'vite-plugin-fs-router-generator', + configResolved: async (): Promise => { + await generate() + }, watchChange: async ( file: string, context: { event: string }, @@ -49,6 +55,8 @@ export function RouterGenerator(): Plugin { } export function RouterCodeSplitter(): Plugin { + const ROOT: string = process.cwd() + return { name: 'vite-plugin-fs-router-code-splitter', enforce: 'pre', @@ -63,7 +71,8 @@ export function RouterCodeSplitter(): Plugin { url.searchParams.delete('v') id = fileURLToPath(url).replace(/\\/g, '/') - const compile = makeCompile() + const compile = makeCompile({ root: ROOT }) + if (DEBUG) console.info('Route: ', id) if (id.includes(SPLIT_PREFIX)) { diff --git a/packages/vite-fs-router/tests/index.test.ts b/packages/vite-fs-router/tests/index.test.ts new file mode 100644 index 00000000..97822b8a --- /dev/null +++ b/packages/vite-fs-router/tests/index.test.ts @@ -0,0 +1,34 @@ +import { readFile, readdir } from 'fs/promises' +import path from 'path' +import { expect, test } from 'vitest' +import { makeCompile, splitFile } from '../src/compiler' +import { SPLIT_PREFIX } from '../src/constants' + +async function splitTestFile(opts: { file: string }): void { + const code = ( + await readFile(path.resolve(__dirname, `./test-files/${opts.file}`)) + ).toString() + + const filename = opts.file.replace(__dirname, '') + + const result = await splitFile({ + code, + compile: makeCompile({ + root: './test-files', + }), + filename: `${filename}?${SPLIT_PREFIX}`, + }) + + await expect(result.code).toMatchFileSnapshot( + `./snapshots/${filename.replace('.tsx', '')}?split.tsx`, + ) +} + +test('it compiles and splits', async (): Promise => { + // get the list of files from the /test-files directory + const files = await readdir(path.resolve(__dirname, './test-files')) + for (const file of files) { + console.log('Testing:', file) + await splitTestFile({ file }) + } +}) diff --git a/packages/vite-fs-router/tests/shared/imported.tsx b/packages/vite-fs-router/tests/shared/imported.tsx new file mode 100644 index 00000000..09ebaaf6 --- /dev/null +++ b/packages/vite-fs-router/tests/shared/imported.tsx @@ -0,0 +1,3 @@ +export const ImportedRoute(): JSX.Element => { +return

Imported route

+} diff --git a/packages/vite-fs-router/tests/snapshots/default-exported?split.tsx b/packages/vite-fs-router/tests/snapshots/default-exported?split.tsx new file mode 100644 index 00000000..8fe2f060 --- /dev/null +++ b/packages/vite-fs-router/tests/snapshots/default-exported?split.tsx @@ -0,0 +1,3 @@ +export default function Route(): JSX.Element { + return

Test route

; +} \ No newline at end of file diff --git a/packages/vite-fs-router/tests/snapshots/function-export?split.tsx b/packages/vite-fs-router/tests/snapshots/function-export?split.tsx new file mode 100644 index 00000000..81c152e8 --- /dev/null +++ b/packages/vite-fs-router/tests/snapshots/function-export?split.tsx @@ -0,0 +1,4 @@ +function PostsRoute() { + return

Post route

; +} +export default PostsRoute; \ No newline at end of file diff --git a/packages/vite-fs-router/tests/snapshots/imported?split.tsx b/packages/vite-fs-router/tests/snapshots/imported?split.tsx new file mode 100644 index 00000000..ca2a8803 --- /dev/null +++ b/packages/vite-fs-router/tests/snapshots/imported?split.tsx @@ -0,0 +1,2 @@ +import { ImportedRoute } from '../shared/imported'; +export default ImportedRoute; \ No newline at end of file diff --git a/packages/vite-fs-router/tests/test-files/default-exported.tsx b/packages/vite-fs-router/tests/test-files/default-exported.tsx new file mode 100644 index 00000000..de00a4a8 --- /dev/null +++ b/packages/vite-fs-router/tests/test-files/default-exported.tsx @@ -0,0 +1,5 @@ +import * as React from 'react' + +export default function Route(): JSX.Element { + return

Test route

+} diff --git a/packages/vite-fs-router/tests/test-files/function-export.tsx b/packages/vite-fs-router/tests/test-files/function-export.tsx new file mode 100644 index 00000000..9f73fa2a --- /dev/null +++ b/packages/vite-fs-router/tests/test-files/function-export.tsx @@ -0,0 +1,7 @@ +import * as React from 'react' + +function PostsRoute() { + return

Post route

+} + +export default PostsRoute diff --git a/packages/vite-fs-router/tests/test-files/imported.tsx b/packages/vite-fs-router/tests/test-files/imported.tsx new file mode 100644 index 00000000..52062c65 --- /dev/null +++ b/packages/vite-fs-router/tests/test-files/imported.tsx @@ -0,0 +1,4 @@ +import * as React from 'react' +import { ImportedRoute } from '../shared/imported' + +export default ImportedRoute diff --git a/packages/vite-fs-router/vite.config.ts b/packages/vite-fs-router/vite.config.ts index 329e88ea..6b3b4750 100644 --- a/packages/vite-fs-router/vite.config.ts +++ b/packages/vite-fs-router/vite.config.ts @@ -1,9 +1,9 @@ import { defineConfig } from 'vite' -import { preserveDirectives } from 'rollup-plugin-preserve-directives' -import { externalizeDeps } from 'vite-plugin-externalize-deps' import dts from 'vite-plugin-dts' +import { externalizeDeps } from 'vite-plugin-externalize-deps' +import { preserveDirectives } from 'rollup-plugin-preserve-directives' -const config = ({ entryRoot, include, exclude }) => +const config = ({ entryRoot, include, exclude }): any => defineConfig({ plugins: [ externalizeDeps(), @@ -72,4 +72,5 @@ const config = ({ entryRoot, include, exclude }) => export default config({ entryRoot: './src/index.ts', include: './src', + exclude: ['./src/tests/'], }) diff --git a/tsconfig.json b/tsconfig.json index caebdc5e..55c2de78 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -24,4 +24,5 @@ "target": "ES2020", }, "include": [".eslintrc.cjs", "prettier.config.js"], + "exclude": ["node_modules"] }