mirror of
https://github.com/tuono-labs/tuono
synced 2026-07-25 12:52:47 -07:00
feat: imported from @tanstack/router base vite-plugin logic
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"format": "turbo format"
|
||||
},
|
||||
"keywords": [],
|
||||
"type": "module",
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<string, boolean>
|
||||
refs: Set<any>
|
||||
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<void> => {
|
||||
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<void> => {
|
||||
console.log('Split file [splitFile fn]', code)
|
||||
return compile({
|
||||
code,
|
||||
filename,
|
||||
getBabelConfig: () => ({
|
||||
plugins: [
|
||||
[
|
||||
{
|
||||
visitor: {
|
||||
Program: {
|
||||
enter(
|
||||
programPath: babel.NodePath<t.Program>,
|
||||
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)
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export const SPLIT_PREFIX = 'sp-'
|
||||
@@ -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<BabelTypes.Identifier>
|
||||
|
||||
// export function findReferencedIdentifiers(
|
||||
// programPath: NodePath<BabelTypes.Program>,
|
||||
// ): Set<IdentifierPath> {
|
||||
// const refs = new Set<IdentifierPath>()
|
||||
|
||||
// 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<BabelTypes.Identifier>
|
||||
// if (isIdentifierReferenced(local)) {
|
||||
// refs.add(local)
|
||||
// }
|
||||
// } else if (path.node.id.type === 'ObjectPattern') {
|
||||
// const pattern = path.get('id') as NodePath<BabelTypes.ObjectPattern>
|
||||
|
||||
// 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<BabelTypes.Identifier>
|
||||
// if (isIdentifierReferenced(local)) {
|
||||
// refs.add(local)
|
||||
// }
|
||||
// })
|
||||
// } else if (path.node.id.type === 'ArrayPattern') {
|
||||
// const pattern = path.get('id') as NodePath<BabelTypes.ArrayPattern>
|
||||
|
||||
// const elements = pattern.get('elements')
|
||||
// elements.forEach((e) => {
|
||||
// let local: NodePath<BabelTypes.Identifier>
|
||||
// if (e.node?.type === 'Identifier') {
|
||||
// local = e as NodePath<BabelTypes.Identifier>
|
||||
// } else if (e.node?.type === 'RestElement') {
|
||||
// local = e.get('argument') as NodePath<BabelTypes.Identifier>
|
||||
// } 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<BabelTypes.Program>,
|
||||
refs?: Set<IdentifierPath>,
|
||||
): 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<BabelTypes.Identifier>
|
||||
if (shouldBeRemoved(local)) {
|
||||
++referencesRemovedInThisPass
|
||||
path.remove()
|
||||
}
|
||||
} else if (path.node.id.type === 'ObjectPattern') {
|
||||
const pattern = path.get('id') as NodePath<BabelTypes.ObjectPattern>
|
||||
|
||||
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<BabelTypes.Identifier>
|
||||
|
||||
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<BabelTypes.ArrayPattern>
|
||||
|
||||
let hasRemoved = false as boolean
|
||||
|
||||
pattern.get('elements').forEach((element, index) => {
|
||||
// if (!element) return // Skip holes in the pattern
|
||||
|
||||
let identifierPath: NodePath<BabelTypes.Identifier>
|
||||
|
||||
if (t.isIdentifier(element.node)) {
|
||||
identifierPath = element as NodePath<BabelTypes.Identifier>
|
||||
} else if (t.isRestElement(element.node)) {
|
||||
identifierPath = element.get(
|
||||
'argument',
|
||||
) as NodePath<BabelTypes.Identifier>
|
||||
} 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<BabelTypes.Identifier> | null {
|
||||
const parentPath = path.parentPath
|
||||
if (parentPath.type === 'VariableDeclarator') {
|
||||
const variablePath = parentPath as NodePath<BabelTypes.VariableDeclarator>
|
||||
const name = variablePath.get('id')
|
||||
return name.node.type === 'Identifier'
|
||||
? (name as NodePath<BabelTypes.Identifier>)
|
||||
: null
|
||||
}
|
||||
|
||||
if (parentPath.type === 'AssignmentExpression') {
|
||||
const variablePath = parentPath as NodePath<BabelTypes.AssignmentExpression>
|
||||
const name = variablePath.get('left')
|
||||
return name.node.type === 'Identifier'
|
||||
? (name as NodePath<BabelTypes.Identifier>)
|
||||
: 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<BabelTypes.Identifier>)
|
||||
: null
|
||||
}
|
||||
|
||||
function isIdentifierReferenced(
|
||||
ident: NodePath<BabelTypes.Identifier>,
|
||||
): 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
|
||||
}
|
||||
@@ -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<void> => {
|
||||
const generate = async (): Promise<void> => {
|
||||
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<void> => {
|
||||
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)) {
|
||||
|
||||
@@ -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<void> => {
|
||||
// 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 })
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,3 @@
|
||||
export const ImportedRoute(): JSX.Element => {
|
||||
return <h1>Imported route</h1>
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export default function Route(): JSX.Element {
|
||||
return <h1>Test route</h1>;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
function PostsRoute() {
|
||||
return <h1>Post route</h1>;
|
||||
}
|
||||
export default PostsRoute;
|
||||
@@ -0,0 +1,2 @@
|
||||
import { ImportedRoute } from '../shared/imported';
|
||||
export default ImportedRoute;
|
||||
@@ -0,0 +1,5 @@
|
||||
import * as React from 'react'
|
||||
|
||||
export default function Route(): JSX.Element {
|
||||
return <h1>Test route</h1>
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import * as React from 'react'
|
||||
|
||||
function PostsRoute() {
|
||||
return <h1>Post route</h1>
|
||||
}
|
||||
|
||||
export default PostsRoute
|
||||
@@ -0,0 +1,4 @@
|
||||
import * as React from 'react'
|
||||
import { ImportedRoute } from '../shared/imported'
|
||||
|
||||
export default ImportedRoute
|
||||
@@ -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/'],
|
||||
})
|
||||
|
||||
@@ -24,4 +24,5 @@
|
||||
"target": "ES2020",
|
||||
},
|
||||
"include": [".eslintrc.cjs", "prettier.config.js"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user