2024-12-16 08:03:37 +01:00
|
|
|
import { transformSync } from '@babel/core'
|
|
|
|
|
import type { PluginItem as BabelPluginItem } from '@babel/core'
|
|
|
|
|
import * as BabelTypes from '@babel/types'
|
2024-12-18 09:11:30 +01:00
|
|
|
import { createFilter } from 'vite'
|
|
|
|
|
import type { Plugin as VitePlugin, Rollup, FilterPattern } from 'vite'
|
2024-06-20 19:40:14 +02:00
|
|
|
|
2024-12-01 10:07:35 +01:00
|
|
|
import {
|
|
|
|
|
TUONO_MAIN_PACKAGE,
|
|
|
|
|
TUONO_DYNAMIC_FN_ID,
|
|
|
|
|
TUONO_LAZY_FN_ID,
|
|
|
|
|
} from './constants'
|
|
|
|
|
import { isTuonoDynamicFnImported } from './utils'
|
|
|
|
|
|
2024-06-20 19:40:14 +02:00
|
|
|
/**
|
2024-08-14 20:50:47 +02:00
|
|
|
* [SERVER build]
|
2024-06-21 18:52:58 +02:00
|
|
|
* This plugin just removes the `dynamic` imported function from any tuono import
|
2024-06-20 19:40:14 +02:00
|
|
|
*/
|
2024-12-16 08:03:37 +01:00
|
|
|
const RemoveTuonoLazyImport: BabelPluginItem = {
|
2024-06-20 19:40:14 +02:00
|
|
|
name: 'remove-tuono-lazy-import-plugin',
|
|
|
|
|
visitor: {
|
2024-12-16 08:03:37 +01:00
|
|
|
ImportDeclaration: (path) => {
|
|
|
|
|
const importNode = path.node
|
|
|
|
|
if (importNode.source.value !== TUONO_MAIN_PACKAGE) return
|
|
|
|
|
|
|
|
|
|
path.traverse({
|
|
|
|
|
ImportSpecifier: (importSpecifierPath) => {
|
|
|
|
|
if (isTuonoDynamicFnImported(importSpecifierPath)) {
|
|
|
|
|
importSpecifierPath.remove()
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// If there are no specifiers left after traverse
|
|
|
|
|
// remove the import to avoid unwanted side effects
|
|
|
|
|
if (importNode.specifiers.length === 0) {
|
2024-10-16 20:41:33 +02:00
|
|
|
path.remove()
|
2024-06-20 19:40:14 +02:00
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2024-08-14 20:50:47 +02:00
|
|
|
* [CLIENT build]
|
2024-12-15 09:22:19 +01:00
|
|
|
* This plugin replace the `dynamic` function with the `__tuono__internal__lazyLoadComponent` one
|
2024-06-20 19:40:14 +02:00
|
|
|
*/
|
2024-12-16 08:03:37 +01:00
|
|
|
const ReplaceTuonoLazyImport: BabelPluginItem = {
|
|
|
|
|
name: 'replace-tuono-lazy-import-plugin',
|
2024-06-20 19:40:14 +02:00
|
|
|
visitor: {
|
2024-08-14 20:50:47 +02:00
|
|
|
ImportSpecifier: (path) => {
|
2024-12-16 08:03:37 +01:00
|
|
|
if (
|
|
|
|
|
BabelTypes.isIdentifier(path.node.imported) &&
|
|
|
|
|
isTuonoDynamicFnImported(path)
|
|
|
|
|
) {
|
|
|
|
|
path.node.imported.name = TUONO_LAZY_FN_ID
|
2024-06-21 18:52:58 +02:00
|
|
|
}
|
|
|
|
|
},
|
2024-06-20 19:40:14 +02:00
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-16 20:41:33 +02:00
|
|
|
const turnLazyIntoStatic = {
|
2024-12-16 08:03:37 +01:00
|
|
|
VariableDeclaration: (
|
|
|
|
|
path: babel.NodePath<BabelTypes.VariableDeclaration>,
|
|
|
|
|
): void => {
|
|
|
|
|
path.node.declarations.forEach((variableDeclarationNode) => {
|
|
|
|
|
const init = variableDeclarationNode.init
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
BabelTypes.isCallExpression(init) &&
|
|
|
|
|
// ensures that the method call is `TUONO_DYNAMIC_FN_ID`
|
|
|
|
|
BabelTypes.isIdentifier(init.callee, { name: TUONO_DYNAMIC_FN_ID }) &&
|
|
|
|
|
// import name must be an identifier
|
|
|
|
|
BabelTypes.isIdentifier(variableDeclarationNode.id) &&
|
|
|
|
|
// check that the first function parameter is an arrow function
|
|
|
|
|
BabelTypes.isArrowFunctionExpression(init.arguments[0])
|
|
|
|
|
) {
|
|
|
|
|
const cmpImportFn = init.arguments[0]
|
|
|
|
|
|
|
|
|
|
// ensures that the first parameter is a call expression (may be a block statement)
|
|
|
|
|
if (!BabelTypes.isCallExpression(cmpImportFn.body)) return
|
|
|
|
|
// ensures that the first parameter is a string literal (the import path)
|
|
|
|
|
if (!BabelTypes.isStringLiteral(cmpImportFn.body.arguments[0])) return
|
|
|
|
|
|
|
|
|
|
const importName = variableDeclarationNode.id.name
|
|
|
|
|
const importPath = cmpImportFn.body.arguments[0].value
|
2024-10-16 20:41:33 +02:00
|
|
|
|
|
|
|
|
if (importName && importPath) {
|
2024-12-16 08:03:37 +01:00
|
|
|
const importDeclaration = BabelTypes.importDeclaration(
|
|
|
|
|
[
|
|
|
|
|
BabelTypes.importDefaultSpecifier(
|
|
|
|
|
BabelTypes.identifier(importName),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
BabelTypes.stringLiteral(importPath),
|
2024-10-16 20:41:33 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
path.replaceWith(importDeclaration)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-20 19:40:14 +02:00
|
|
|
/**
|
2024-08-14 20:50:47 +02:00
|
|
|
* [SERVER build]
|
|
|
|
|
* This plugin statically imports the lazy loaded components
|
2024-06-20 19:40:14 +02:00
|
|
|
*/
|
2024-12-16 08:03:37 +01:00
|
|
|
const TurnLazyIntoStaticImport: BabelPluginItem = {
|
2024-06-21 18:52:58 +02:00
|
|
|
name: 'turn-lazy-into-static-import-plugin',
|
2024-06-20 19:40:14 +02:00
|
|
|
visitor: {
|
2024-10-16 20:41:33 +02:00
|
|
|
Program: (path) => {
|
|
|
|
|
path.traverse({
|
|
|
|
|
ImportSpecifier: (subPath) => {
|
|
|
|
|
if (isTuonoDynamicFnImported(subPath)) {
|
|
|
|
|
path.traverse(turnLazyIntoStatic)
|
2024-06-20 19:40:14 +02:00
|
|
|
}
|
2024-10-16 20:41:33 +02:00
|
|
|
},
|
2024-06-20 19:40:14 +02:00
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-24 11:41:58 +01:00
|
|
|
interface TuonoLazyFnPluginOptions {
|
2024-12-18 09:11:30 +01:00
|
|
|
include: FilterPattern
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-24 11:41:58 +01:00
|
|
|
export function TuonoLazyFnPlugin(
|
|
|
|
|
options: TuonoLazyFnPluginOptions,
|
2024-12-18 09:11:30 +01:00
|
|
|
): VitePlugin {
|
|
|
|
|
const { include } = options
|
|
|
|
|
|
|
|
|
|
const filter = createFilter(include)
|
|
|
|
|
|
2024-06-20 19:40:14 +02:00
|
|
|
return {
|
|
|
|
|
name: 'vite-plugin-tuono-lazy-loading',
|
|
|
|
|
enforce: 'pre',
|
2024-12-18 09:11:30 +01:00
|
|
|
transform(code, id, opts): Rollup.TransformResult {
|
|
|
|
|
if (!filter(id)) return
|
|
|
|
|
|
2024-12-16 08:03:37 +01:00
|
|
|
/**
|
|
|
|
|
* @todo we should exclude non tsx files from this transformation
|
|
|
|
|
* this might benefit build time avoiding running `includes` on non-tsx files.
|
|
|
|
|
* This can be executed using `_id` parameter
|
|
|
|
|
* which is the filepath that is being processed
|
|
|
|
|
*/
|
|
|
|
|
|
2024-06-21 18:52:58 +02:00
|
|
|
if (
|
|
|
|
|
code.includes(TUONO_DYNAMIC_FN_ID) &&
|
|
|
|
|
code.includes(TUONO_MAIN_PACKAGE)
|
|
|
|
|
) {
|
2024-12-16 08:03:37 +01:00
|
|
|
const plugins: Array<BabelPluginItem> = [
|
|
|
|
|
['@babel/plugin-syntax-jsx', {}],
|
|
|
|
|
['@babel/plugin-syntax-typescript', { isTSX: true }],
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
if (opts?.ssr) {
|
|
|
|
|
plugins.push(RemoveTuonoLazyImport, TurnLazyIntoStaticImport)
|
|
|
|
|
} else {
|
|
|
|
|
plugins.push(ReplaceTuonoLazyImport)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const res = transformSync(code, { plugins })
|
2024-06-20 19:40:14 +02:00
|
|
|
|
|
|
|
|
return res?.code
|
|
|
|
|
}
|
2024-12-16 08:03:37 +01:00
|
|
|
|
2024-06-20 19:40:14 +02:00
|
|
|
return code
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|