vite-plugin-vue-layouts-next
Advanced tools
Comparing version
@@ -32,2 +32,7 @@ import { Plugin } from 'vite'; | ||
/** | ||
* If set, wrap the route's internal component with a layout instead of adding a wrapping route | ||
* @default false | ||
*/ | ||
wrapComponent?: boolean; | ||
/** | ||
* Mode for importing layouts | ||
@@ -37,6 +42,6 @@ */ | ||
} | ||
type FileContainer = { | ||
interface FileContainer { | ||
path: string; | ||
files: string[]; | ||
}; | ||
} | ||
type UserOptions = Partial<Options>; | ||
@@ -60,8 +65,12 @@ interface ResolvedOptions extends Options { | ||
importMode?: 'sync' | 'async'; | ||
/** | ||
* If set, wrap the route's internal component with a layout instead of adding a wrapping route | ||
* @default false | ||
*/ | ||
wrapComponent?: boolean; | ||
} | ||
declare function defaultImportMode(name: string): "sync" | "async"; | ||
declare function Layout(userOptions?: UserOptions): Plugin; | ||
declare function ClientSideLayout(options?: clientSideOptions): Plugin; | ||
export { ClientSideLayout, type FileContainer, type ResolvedOptions, type UserOptions, type clientSideOptions, Layout as default, defaultImportMode }; | ||
export { ClientSideLayout, type FileContainer, type ResolvedOptions, type UserOptions, type clientSideOptions, Layout as default }; |
@@ -34,13 +34,85 @@ "use strict"; | ||
ClientSideLayout: () => ClientSideLayout, | ||
default: () => Layout, | ||
defaultImportMode: () => defaultImportMode | ||
default: () => Layout | ||
}); | ||
module.exports = __toCommonJS(index_exports); | ||
var import_node_path3 = require("path"); | ||
var import_node_process2 = __toESM(require("process")); | ||
// src/clientSide.ts | ||
var import_node_path2 = require("path"); | ||
// src/utils.ts | ||
var import_node_path = require("path"); | ||
function normalizePath(path) { | ||
var import_debug = __toESM(require("debug")); | ||
var import_fast_glob = __toESM(require("fast-glob")); | ||
function extensionsToGlob(extensions) { | ||
return extensions.length > 1 ? `{${extensions.join(",")}}` : extensions[0] || ""; | ||
} | ||
function normalizePath(str) { | ||
return str.replace(/\\/g, "/"); | ||
} | ||
var debug = (0, import_debug.default)("vite-plugin-layouts"); | ||
function resolveDirs(dirs, root) { | ||
if (dirs === null) | ||
return []; | ||
const dirsArray = Array.isArray(dirs) ? dirs : [dirs]; | ||
const dirsResolved = []; | ||
for (const dir of dirsArray) { | ||
if (dir.includes("**")) { | ||
const matches = import_fast_glob.default.sync(dir, { onlyDirectories: true }); | ||
for (const match of matches) | ||
dirsResolved.push(normalizePath((0, import_node_path.resolve)(root, match))); | ||
} else { | ||
dirsResolved.push(normalizePath((0, import_node_path.resolve)(root, dir))); | ||
} | ||
} | ||
return dirsResolved; | ||
} | ||
function addIndentation(code, indent) { | ||
return code.replace(/\n[ \t]+/g, ` | ||
${" ".repeat(indent)}`); | ||
} | ||
// src/layoutReturn.ts | ||
var returnLayoutRoute = ( | ||
/* js */ | ||
` | ||
/** @type {import('vue-router').RouteRecordRaw} */ | ||
const layoutRoute = { | ||
path: route.path, | ||
component: layouts[layout], | ||
meta: { ...route.meta, isLayout: true }, | ||
// Handle root path specially to avoid infinite nesting | ||
children: top && route.path === '/' | ||
? [route] | ||
: [{ ...route, path: '', meta: { ...route.meta, isLayout: false } }] | ||
} | ||
return layoutRoute | ||
` | ||
); | ||
var returnLayoutComponent = ( | ||
/* js */ | ||
` | ||
if (!route.component) { | ||
return route | ||
} | ||
/** @type {import('vue-router').RouteRecordRaw} */ | ||
const wrappedRoute = { | ||
...route, | ||
component: h('div', [ | ||
h(layouts[layout].layout), | ||
h(layouts[layout].isSync ? defineComponent(() => route.component()) : defineAsyncComponent(() => route.component())), | ||
]), | ||
meta: { | ||
...route.meta, | ||
isLayout: true | ||
} | ||
} | ||
return wrappedRoute | ||
` | ||
); | ||
// src/clientSide.ts | ||
function normalizePath2(path) { | ||
path = path.startsWith("/") ? path : `/${path}`; | ||
return import_node_path.posix.normalize(path); | ||
return import_node_path2.posix.normalize(path); | ||
} | ||
@@ -53,5 +125,7 @@ async function createVirtualGlob(target, isSync) { | ||
const { layoutDir, defaultLayout, importMode } = options; | ||
const normalizedTarget = normalizePath(layoutDir); | ||
const normalizedTarget = normalizePath2(layoutDir); | ||
const isSync = importMode === "sync"; | ||
return ` | ||
return ( | ||
/* js */ | ||
` | ||
export const createGetRoutes = (router, withLayout = false) => { | ||
@@ -69,5 +143,5 @@ const routes = router.getRoutes() | ||
const modules = ${await createVirtualGlob( | ||
normalizedTarget, | ||
isSync | ||
)} | ||
normalizedTarget, | ||
isSync | ||
)} | ||
@@ -85,32 +159,15 @@ Object.entries(modules).forEach(([name, module]) => { | ||
if (top) { | ||
// unplugin-vue-router adds a top-level route to the routing group, which we should skip. | ||
const skipLayout = !route.component && route.children?.find(r => (r.path === '' || r.path === '/') && r.meta?.isLayout) | ||
const layout = route.meta?.layout ?? '${options.defaultLayout}' | ||
if (skipLayout) { | ||
return route | ||
} | ||
if (route.meta?.layout !== false) { | ||
return { | ||
path: route.path, | ||
component: layouts[route.meta?.layout || '${defaultLayout}'], | ||
children: route.path === '/' ? [route] : [{...route, path: ''}], | ||
meta: { | ||
isLayout: true | ||
} | ||
} | ||
} | ||
} | ||
const skipLayout = top | ||
&& !route.component | ||
&& route.children?.find(r => (r.path === '' || r.path === '/') && r.meta?.isLayout); | ||
if (route.meta?.layout) { | ||
return { | ||
path: route.path, | ||
component: layouts[route.meta?.layout], | ||
children: [ {...route, path: ''} ], | ||
meta: { | ||
isLayout: true | ||
} | ||
} | ||
if (skipLayout) { | ||
return route | ||
} | ||
if (layout && layouts[layout]) { | ||
${addIndentation(options.wrapComponent ? returnLayoutComponent : returnLayoutRoute, 8)} | ||
} | ||
@@ -122,37 +179,33 @@ return route | ||
return deepSetupLayout(routes) | ||
}`; | ||
}` | ||
); | ||
} | ||
// src/files.ts | ||
var import_fast_glob2 = __toESM(require("fast-glob")); | ||
// src/utils.ts | ||
var import_node_path2 = require("path"); | ||
var import_debug = __toESM(require("debug")); | ||
var import_fast_glob = __toESM(require("fast-glob")); | ||
function extensionsToGlob(extensions) { | ||
return extensions.length > 1 ? `{${extensions.join(",")}}` : extensions[0] || ""; | ||
// src/defaults.ts | ||
var import_node_process = __toESM(require("process")); | ||
function defaultImportMode(name) { | ||
if (import_node_process.default.env.VITE_SSG) | ||
return "sync"; | ||
return name === "default" ? "sync" : "async"; | ||
} | ||
function normalizePath2(str) { | ||
return str.replace(/\\/g, "/"); | ||
function resolveOptions(userOptions) { | ||
return Object.assign( | ||
{ | ||
defaultLayout: "default", | ||
layoutsDirs: "src/layouts", | ||
pagesDirs: "src/pages", | ||
extensions: ["vue"], | ||
exclude: [], | ||
wrapComponent: false, | ||
importMode: defaultImportMode | ||
}, | ||
userOptions | ||
); | ||
} | ||
var debug = (0, import_debug.default)("vite-plugin-layouts"); | ||
function resolveDirs(dirs, root) { | ||
if (dirs === null) | ||
return []; | ||
const dirsArray = Array.isArray(dirs) ? dirs : [dirs]; | ||
const dirsResolved = []; | ||
for (const dir of dirsArray) { | ||
if (dir.includes("**")) { | ||
const matches = import_fast_glob.default.sync(dir, { onlyDirectories: true }); | ||
for (const match of matches) | ||
dirsResolved.push(normalizePath2((0, import_node_path2.resolve)(root, match))); | ||
} else { | ||
dirsResolved.push(normalizePath2((0, import_node_path2.resolve)(root, dir))); | ||
} | ||
} | ||
return dirsResolved; | ||
} | ||
// src/generateLayouts.ts | ||
var import_node_path4 = require("path"); | ||
// src/files.ts | ||
var import_fast_glob2 = __toESM(require("fast-glob")); | ||
async function getFilesFromPath(path, options) { | ||
@@ -174,3 +227,3 @@ const { | ||
// src/importCode.ts | ||
var import_path = require("path"); | ||
var import_node_path3 = require("path"); | ||
function getImportCode(files, options) { | ||
@@ -183,15 +236,21 @@ const imports = []; | ||
const path = __.path.substr(0, 1) === "/" ? `${__.path}/${file}` : `/${__.path}/${file}`; | ||
const parsed = (0, import_path.parse)(file); | ||
const name = (0, import_path.join)(parsed.dir, parsed.name).replace(/\\/g, "/"); | ||
const parsed = (0, import_node_path3.parse)(file); | ||
const name = (0, import_node_path3.join)(parsed.dir, parsed.name).replace(/\\/g, "/"); | ||
if (options.importMode(name) === "sync") { | ||
const variable = `__layout_${id}`; | ||
head.push(`import ${variable} from '${path}'`); | ||
imports.push(`'${name}': ${variable},`); | ||
imports.push( | ||
/* js */ | ||
`'${name}': { layout: ${variable}, isSync: true },` | ||
); | ||
id += 1; | ||
} else { | ||
imports.push(`'${name}': () => import('${path}'),`); | ||
imports.push( | ||
/* js */ | ||
`'${name}': { layout: () => import('${path}'), isSync: false },` | ||
); | ||
} | ||
} | ||
} | ||
const importsCode = ` | ||
let importsCode = ` | ||
${head.join("\n")} | ||
@@ -201,2 +260,20 @@ export const layouts = { | ||
}`; | ||
if (options.wrapComponent) { | ||
const vueImports = []; | ||
const nullImports = []; | ||
vueImports.push("h"); | ||
if (id > 0) | ||
vueImports.push("defineAsyncComponent"); | ||
else | ||
nullImports.push("defineAsyncComponent"); | ||
if (imports.length - id > 0) | ||
vueImports.push("defineComponent"); | ||
else | ||
nullImports.push("defineComponent"); | ||
importsCode = ` | ||
import { ${vueImports.join(", ")} } from 'vue' | ||
${importsCode} | ||
${nullImports.map((v) => `const ${v} = null`).join("\n")} | ||
`; | ||
} | ||
return importsCode; | ||
@@ -207,3 +284,5 @@ } | ||
function getClientCode(importCode, options) { | ||
const code = ` | ||
const code = ( | ||
/* js */ | ||
` | ||
${importCode} | ||
@@ -225,31 +304,14 @@ export const createGetRoutes = (router, withLayout = false) => { | ||
if (top) { | ||
// unplugin-vue-router adds a top-level route to the routing group, which we should skip. | ||
const skipLayout = !route.component && route.children?.find(r => (r.path === '' || r.path === '/') && r.meta?.isLayout) | ||
const layout = route.meta?.layout ?? '${options.defaultLayout}' | ||
if (skipLayout) { | ||
return route | ||
} | ||
const skipLayout = top | ||
&& !route.component | ||
&& route.children?.find(r => (r.path === '' || r.path === '/') && r.meta?.isLayout); | ||
if (route.meta?.layout !== false) { | ||
return { | ||
path: route.path, | ||
component: layouts[route.meta?.layout || '${options.defaultLayout}'], | ||
children: route.path === '/' ? [route] : [{...route, path: ''}], | ||
meta: { | ||
isLayout: true | ||
} | ||
} | ||
} | ||
if (skipLayout) { | ||
return route | ||
} | ||
if (route.meta?.layout) { | ||
return { | ||
path: route.path, | ||
component: layouts[route.meta?.layout], | ||
children: [ {...route, path: ''} ], | ||
meta: { | ||
isLayout: true | ||
} | ||
} | ||
if (layout && layouts[layout]) { | ||
${addIndentation(options.wrapComponent ? returnLayoutComponent : returnLayoutRoute, 8)} | ||
} | ||
@@ -264,3 +326,4 @@ | ||
} | ||
`; | ||
` | ||
); | ||
return code; | ||
@@ -270,23 +333,19 @@ } | ||
// src/generateLayouts.ts | ||
async function generateLayouts(layoutDirs, options, config) { | ||
const container = []; | ||
for (const dir of layoutDirs) { | ||
const layoutsDirPath = dir.substr(0, 1) === "/" ? normalizePath(dir) : normalizePath((0, import_node_path4.resolve)(config.root, dir)); | ||
const _f = await getFilesFromPath(layoutsDirPath, options); | ||
container.push({ path: layoutsDirPath, files: _f }); | ||
} | ||
const importCode = getImportCode(container, options); | ||
const clientCode = RouteLayout_default(importCode, options); | ||
debug("Client code: %O", clientCode); | ||
return clientCode; | ||
} | ||
// src/index.ts | ||
var MODULE_IDS = ["layouts-generated", "virtual:generated-layouts"]; | ||
var MODULE_ID_VIRTUAL = "/@vite-plugin-vue-layouts-next/generated-layouts"; | ||
function defaultImportMode(name) { | ||
if (process.env.VITE_SSG) | ||
return "sync"; | ||
return name === "default" ? "sync" : "async"; | ||
} | ||
function resolveOptions(userOptions) { | ||
return Object.assign( | ||
{ | ||
defaultLayout: "default", | ||
layoutsDirs: "src/layouts", | ||
pagesDirs: "src/pages", | ||
extensions: ["vue"], | ||
exclude: [], | ||
importMode: defaultImportMode | ||
}, | ||
userOptions | ||
); | ||
} | ||
function Layout(userOptions = {}) { | ||
@@ -325,3 +384,3 @@ if (canEnableClientLayout(userOptions)) { | ||
const updateVirtualModule = (path) => { | ||
path = normalizePath2(path); | ||
path = normalizePath(path); | ||
if (pagesDirs.length === 0 || pagesDirs.some((dir) => path.startsWith(dir)) || layoutDirs.some((dir) => path.startsWith(dir))) { | ||
@@ -348,13 +407,3 @@ debug("reload", path); | ||
if (id === MODULE_ID_VIRTUAL) { | ||
const container = []; | ||
for (const dir of layoutDirs) { | ||
const layoutsDirPath = dir.substr(0, 1) === "/" ? normalizePath2(dir) : normalizePath2((0, import_node_path3.resolve)(config.root, dir)); | ||
debug("Loading Layout Dir: %O", layoutsDirPath); | ||
const _f = await getFilesFromPath(layoutsDirPath, options); | ||
container.push({ path: layoutsDirPath, files: _f }); | ||
} | ||
const importCode = getImportCode(container, options); | ||
const clientCode = RouteLayout_default(importCode, options); | ||
debug("Client code: %O", clientCode); | ||
return clientCode; | ||
return generateLayouts(layoutDirs, options, config); | ||
} | ||
@@ -368,3 +417,4 @@ } | ||
defaultLayout = "default", | ||
importMode = process.env.VITE_SSG ? "sync" : "async" | ||
importMode = import_node_process2.default.env.VITE_SSG ? "sync" : "async", | ||
wrapComponent = false | ||
} = options || {}; | ||
@@ -383,3 +433,4 @@ return { | ||
importMode, | ||
defaultLayout | ||
defaultLayout, | ||
wrapComponent | ||
}); | ||
@@ -400,4 +451,3 @@ } | ||
0 && (module.exports = { | ||
ClientSideLayout, | ||
defaultImportMode | ||
ClientSideLayout | ||
}); |
{ | ||
"name": "vite-plugin-vue-layouts-next", | ||
"version": "0.0.14", | ||
"version": "0.1.0", | ||
"description": "Router-based layout plugin for Vite and Vue, supports the latest versions.", | ||
@@ -57,3 +57,8 @@ "author": "loicduong <npm@relate.dev>", | ||
"example:build-vitesse": "npm -C examples/vitesse run build", | ||
"example:serve-vitesse": "npm -C examples/vitesse run preview" | ||
"example:serve-vitesse": "npm -C examples/vitesse run preview", | ||
"test": "vitest run", | ||
"test-watch": "vitest run --watch", | ||
"test-update": "vitest run --update", | ||
"test:unit": "vitest run --exclude test/e2e/**", | ||
"test:e2e": "vitest run --exclude test/unit/**" | ||
}, | ||
@@ -72,13 +77,14 @@ "peerDependencies": { | ||
"@types/debug": "^4.1.12", | ||
"@types/node": "^22.15.2", | ||
"@types/node": "^22.15.3", | ||
"cross-env": "^7.0.3", | ||
"eslint": "^9.25.1", | ||
"eslint": "^9.26.0", | ||
"eslint-plugin-format": "^1.0.1", | ||
"rollup": "^4.40.0", | ||
"rollup": "^4.40.1", | ||
"tsup": "^8.4.0", | ||
"typescript": "^5.8.3", | ||
"vite": "^6.3.3", | ||
"vue": "^3.5.13", | ||
"vue-router": "^4.5.1" | ||
"vite": "catalog:", | ||
"vitest": "catalog:", | ||
"vue": "catalog:", | ||
"vue-router": "catalog:" | ||
} | ||
} |
@@ -53,4 +53,4 @@ # vite-plugin-vue-layouts-next | ||
```js | ||
import { setupLayouts } from 'virtual:generated-layouts' | ||
import { createRouter } from 'vue-router' | ||
import { setupLayouts } from 'virtual:generated-layouts' | ||
import generatedRoutes from '~pages' | ||
@@ -69,4 +69,4 @@ | ||
```js | ||
import { setupLayouts } from 'virtual:generated-layouts' | ||
import { createRouter } from 'vue-router' | ||
import { setupLayouts } from 'virtual:generated-layouts' | ||
import { routes } from 'vue-router/auto-routes' | ||
@@ -101,2 +101,3 @@ | ||
defaultLayout?: string | ||
wrapComponent?: boolean | ||
importMode?: (name: string) => 'sync' | 'async' | ||
@@ -170,2 +171,8 @@ } | ||
### wrapComponent | ||
If set to `true`, wraps the route's internal component with a layout instead of adding a wrapping route. This can be useful for better performance and simpler route structure. Especially if you have extensive routes, you can use this to avoid the overhead of adding a wrapping route for each page and maintain an easily parsable route structure. | ||
**Default:** `false` | ||
## How it works | ||
@@ -172,0 +179,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
40942
8.03%860
13.46%316
2.27%1
-75%13
8.33%1
Infinity%