vite-plugin-vue-layouts-next
Advanced tools
Comparing version
@@ -32,7 +32,2 @@ 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 | ||
@@ -42,6 +37,6 @@ */ | ||
} | ||
interface FileContainer { | ||
type FileContainer = { | ||
path: string; | ||
files: string[]; | ||
} | ||
}; | ||
type UserOptions = Partial<Options>; | ||
@@ -65,12 +60,8 @@ 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 }; | ||
export { ClientSideLayout, type FileContainer, type ResolvedOptions, type UserOptions, type clientSideOptions, Layout as default, defaultImportMode }; |
@@ -34,85 +34,13 @@ "use strict"; | ||
ClientSideLayout: () => ClientSideLayout, | ||
default: () => Layout | ||
default: () => Layout, | ||
defaultImportMode: () => defaultImportMode | ||
}); | ||
module.exports = __toCommonJS(index_exports); | ||
var import_node_process2 = __toESM(require("process")); | ||
var import_node_path3 = require("path"); | ||
// src/clientSide.ts | ||
var import_node_path2 = require("path"); | ||
// src/utils.ts | ||
var import_node_path = 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] || ""; | ||
} | ||
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) { | ||
function normalizePath(path) { | ||
path = path.startsWith("/") ? path : `/${path}`; | ||
return import_node_path2.posix.normalize(path); | ||
return import_node_path.posix.normalize(path); | ||
} | ||
@@ -125,7 +53,5 @@ async function createVirtualGlob(target, isSync) { | ||
const { layoutDir, defaultLayout, importMode } = options; | ||
const normalizedTarget = normalizePath2(layoutDir); | ||
const normalizedTarget = normalizePath(layoutDir); | ||
const isSync = importMode === "sync"; | ||
return ( | ||
/* js */ | ||
` | ||
return ` | ||
export const createGetRoutes = (router, withLayout = false) => { | ||
@@ -143,5 +69,5 @@ const routes = router.getRoutes() | ||
const modules = ${await createVirtualGlob( | ||
normalizedTarget, | ||
isSync | ||
)} | ||
normalizedTarget, | ||
isSync | ||
)} | ||
@@ -159,15 +85,32 @@ Object.entries(modules).forEach(([name, module]) => { | ||
const layout = route.meta?.layout ?? '${options.defaultLayout}' | ||
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 skipLayout = top | ||
&& !route.component | ||
&& route.children?.find(r => (r.path === '' || r.path === '/') && r.meta?.isLayout); | ||
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 | ||
} | ||
} | ||
} | ||
} | ||
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)} | ||
} | ||
@@ -179,33 +122,37 @@ return route | ||
return deepSetupLayout(routes) | ||
}` | ||
); | ||
}`; | ||
} | ||
// 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"; | ||
// 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] || ""; | ||
} | ||
function resolveOptions(userOptions) { | ||
return Object.assign( | ||
{ | ||
defaultLayout: "default", | ||
layoutsDirs: "src/layouts", | ||
pagesDirs: "src/pages", | ||
extensions: ["vue"], | ||
exclude: [], | ||
wrapComponent: false, | ||
importMode: defaultImportMode | ||
}, | ||
userOptions | ||
); | ||
function normalizePath2(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(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) { | ||
@@ -227,3 +174,3 @@ const { | ||
// src/importCode.ts | ||
var import_node_path3 = require("path"); | ||
var import_path = require("path"); | ||
function getImportCode(files, options) { | ||
@@ -236,21 +183,15 @@ const imports = []; | ||
const path = __.path.substr(0, 1) === "/" ? `${__.path}/${file}` : `/${__.path}/${file}`; | ||
const parsed = (0, import_node_path3.parse)(file); | ||
const name = (0, import_node_path3.join)(parsed.dir, parsed.name).replace(/\\/g, "/"); | ||
const parsed = (0, import_path.parse)(file); | ||
const name = (0, import_path.join)(parsed.dir, parsed.name).replace(/\\/g, "/"); | ||
if (options.importMode(name) === "sync") { | ||
const variable = `__layout_${id}`; | ||
head.push(`import ${variable} from '${path}'`); | ||
imports.push( | ||
/* js */ | ||
`'${name}': { layout: ${variable}, isSync: true },` | ||
); | ||
imports.push(`'${name}': ${variable},`); | ||
id += 1; | ||
} else { | ||
imports.push( | ||
/* js */ | ||
`'${name}': { layout: () => import('${path}'), isSync: false },` | ||
); | ||
imports.push(`'${name}': () => import('${path}'),`); | ||
} | ||
} | ||
} | ||
let importsCode = ` | ||
const importsCode = ` | ||
${head.join("\n")} | ||
@@ -260,20 +201,2 @@ 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; | ||
@@ -284,5 +207,3 @@ } | ||
function getClientCode(importCode, options) { | ||
const code = ( | ||
/* js */ | ||
` | ||
const code = ` | ||
${importCode} | ||
@@ -304,14 +225,31 @@ export const createGetRoutes = (router, withLayout = false) => { | ||
const layout = route.meta?.layout ?? '${options.defaultLayout}' | ||
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 skipLayout = top | ||
&& !route.component | ||
&& route.children?.find(r => (r.path === '' || r.path === '/') && r.meta?.isLayout); | ||
if (skipLayout) { | ||
return route | ||
} | ||
if (skipLayout) { | ||
return route | ||
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 (layout && layouts[layout]) { | ||
${addIndentation(options.wrapComponent ? returnLayoutComponent : returnLayoutRoute, 8)} | ||
if (route.meta?.layout) { | ||
return { | ||
path: route.path, | ||
component: layouts[route.meta?.layout], | ||
children: [ {...route, path: ''} ], | ||
meta: { | ||
isLayout: true | ||
} | ||
} | ||
} | ||
@@ -326,4 +264,3 @@ | ||
} | ||
` | ||
); | ||
`; | ||
return code; | ||
@@ -333,19 +270,23 @@ } | ||
// 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 = {}) { | ||
@@ -384,3 +325,3 @@ if (canEnableClientLayout(userOptions)) { | ||
const updateVirtualModule = (path) => { | ||
path = normalizePath(path); | ||
path = normalizePath2(path); | ||
if (pagesDirs.length === 0 || pagesDirs.some((dir) => path.startsWith(dir)) || layoutDirs.some((dir) => path.startsWith(dir))) { | ||
@@ -407,3 +348,13 @@ debug("reload", path); | ||
if (id === MODULE_ID_VIRTUAL) { | ||
return generateLayouts(layoutDirs, options, config); | ||
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; | ||
} | ||
@@ -417,4 +368,3 @@ } | ||
defaultLayout = "default", | ||
importMode = import_node_process2.default.env.VITE_SSG ? "sync" : "async", | ||
wrapComponent = false | ||
importMode = process.env.VITE_SSG ? "sync" : "async" | ||
} = options || {}; | ||
@@ -433,4 +383,3 @@ return { | ||
importMode, | ||
defaultLayout, | ||
wrapComponent | ||
defaultLayout | ||
}); | ||
@@ -451,3 +400,4 @@ } | ||
0 && (module.exports = { | ||
ClientSideLayout | ||
ClientSideLayout, | ||
defaultImportMode | ||
}); |
{ | ||
"name": "vite-plugin-vue-layouts-next", | ||
"version": "0.1.0", | ||
"version": "0.1.1", | ||
"description": "Router-based layout plugin for Vite and Vue, supports the latest versions.", | ||
@@ -57,8 +57,3 @@ "author": "loicduong <npm@relate.dev>", | ||
"example:build-vitesse": "npm -C examples/vitesse run build", | ||
"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/**" | ||
"example:serve-vitesse": "npm -C examples/vitesse run preview" | ||
}, | ||
@@ -84,7 +79,6 @@ "peerDependencies": { | ||
"typescript": "^5.8.3", | ||
"vite": "catalog:", | ||
"vitest": "catalog:", | ||
"vue": "catalog:", | ||
"vue-router": "catalog:" | ||
"vite": "^6.3.3", | ||
"vue": "^3.5.13", | ||
"vue-router": "^4.5.1" | ||
} | ||
} |
@@ -53,4 +53,4 @@ # vite-plugin-vue-layouts-next | ||
```js | ||
import { createRouter } from 'vue-router' | ||
import { setupLayouts } from 'virtual:generated-layouts' | ||
import { createRouter } from 'vue-router' | ||
import generatedRoutes from '~pages' | ||
@@ -69,4 +69,4 @@ | ||
```js | ||
import { createRouter } from 'vue-router' | ||
import { setupLayouts } from 'virtual:generated-layouts' | ||
import { createRouter } from 'vue-router' | ||
import { routes } from 'vue-router/auto-routes' | ||
@@ -101,3 +101,2 @@ | ||
defaultLayout?: string | ||
wrapComponent?: boolean | ||
importMode?: (name: string) => 'sync' | 'async' | ||
@@ -171,8 +170,2 @@ } | ||
### 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 | ||
@@ -179,0 +172,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
12
-7.69%0
-100%37896
-7.44%758
-11.86%309
-2.22%4
300%