UNPKG

vite-plugin-pages2

Version:

File system base vue-router plugin for Vite

1 lines 85.2 kB
{"version":3,"sources":["../src/index.ts","../src/constants.ts","../src/context.ts","../node_modules/.pnpm/@antfu+utils@9.2.0/node_modules/@antfu/utils/dist/index.mjs","../src/files.ts","../src/utils.ts","../src/options.ts","../src/stringify.ts","../src/resolvers/react.ts","../src/resolvers/solid.ts","../src/resolvers/vue.ts","../src/customBlock.ts"],"sourcesContent":["import type { Plugin } from 'vite'\r\nimport type { UserOptions } from './types'\r\nimport { MODULE_ID_VIRTUAL, ROUTE_BLOCK_ID_VIRTUAL, routeBlockQueryRE } from './constants'\r\n\r\nimport { PageContext } from './context'\r\nimport { parsePageRequest } from './utils'\r\n\r\nfunction pagesPlugin(userOptions: UserOptions = {}): Plugin {\r\n let ctx: PageContext\r\n\r\n return {\r\n name: 'vite-plugin-pages',\r\n enforce: 'pre',\r\n async configResolved(config) {\r\n // auto set resolver for react project\r\n if (\r\n !userOptions.resolver\r\n && config.plugins.find(i => i.name.includes('vite:react'))\r\n ) {\r\n userOptions.resolver = 'react'\r\n }\r\n\r\n // auto set resolver for solid project\r\n if (\r\n !userOptions.resolver\r\n && config.plugins.find(i => i.name.includes('solid'))\r\n ) {\r\n userOptions.resolver = 'solid'\r\n }\r\n\r\n ctx = new PageContext(userOptions, config.root)\r\n ctx.setLogger(config.logger)\r\n await ctx.searchGlob()\r\n },\r\n api: {\r\n getResolvedRoutes() {\r\n return ctx.options.resolver.getComputedRoutes(ctx)\r\n },\r\n },\r\n configureServer(server) {\r\n ctx.setupViteServer(server)\r\n },\r\n resolveId(id) {\r\n if (ctx.options.moduleIds.includes(id))\r\n return `${MODULE_ID_VIRTUAL}?id=${id}`\r\n\r\n if (routeBlockQueryRE.test(id))\r\n return ROUTE_BLOCK_ID_VIRTUAL\r\n\r\n return null\r\n },\r\n async load(id) {\r\n const {\r\n moduleId,\r\n pageId,\r\n } = parsePageRequest(id)\r\n\r\n if (moduleId === MODULE_ID_VIRTUAL && pageId && ctx.options.moduleIds.includes(pageId))\r\n return ctx.resolveRoutes()\r\n\r\n if (id === ROUTE_BLOCK_ID_VIRTUAL) {\r\n return {\r\n code: 'export default {};',\r\n map: null,\r\n }\r\n }\r\n\r\n return null\r\n },\r\n }\r\n}\r\n\r\nexport { syncIndexResolver } from './options'\r\nexport type {\r\n ReactRoute,\r\n SolidRoute,\r\n VueRoute,\r\n} from './resolvers'\r\n\r\nexport {\r\n reactResolver,\r\n solidResolver,\r\n vueResolver,\r\n} from './resolvers'\r\nexport * from './types'\r\nexport { PageContext }\r\nexport default pagesPlugin\r\n","export const MODULE_IDS = [\r\n '~pages',\r\n '~react-pages',\r\n '~solid-pages',\r\n 'pages-generated',\r\n 'virtual:generated-pages',\r\n 'virtual:generated-pages-react',\r\n]\r\n\r\nexport const MODULE_ID_VIRTUAL = 'virtual:vite-plugin-pages/generated-pages'\r\nexport const ROUTE_BLOCK_ID_VIRTUAL = 'virtual:vite-plugin-pages/route-block'\r\nexport const ROUTE_IMPORT_NAME = '__pages_import_$1__'\r\n\r\nexport const routeBlockQueryRE = /\\?vue&type=route/\r\n\r\nexport const dynamicRouteRE = /^\\[(.+)\\]$/\r\nexport const cacheAllRouteRE = /^\\[\\.{3}(.*)\\]$/\r\nexport const replaceDynamicRouteRE = /^\\[(?:\\.{3})?(.*)\\]$/\r\n\r\nexport const nuxtDynamicRouteRE = /^_(.*)$/\r\nexport const nuxtCacheAllRouteRE = /^_$/\r\n\r\nexport const countSlashRE = /\\//g\r\n\r\nexport const replaceIndexRE = /\\/?index$/\r\n","import type { Logger, ViteDevServer } from 'vite'\r\nimport type { PageOptions, ResolvedOptions, UserOptions } from './types'\r\nimport { join, resolve } from 'node:path'\r\nimport process from 'node:process'\r\nimport { slash, toArray } from '@antfu/utils'\r\nimport { getPageFiles } from './files'\r\nimport { resolveOptions } from './options'\r\n\r\nimport { debug, invalidatePagesModule, isTarget } from './utils'\r\n\r\nexport interface PageRoute {\r\n path: string\r\n route: string\r\n}\r\n\r\nexport class PageContext {\r\n private _server: ViteDevServer | undefined\r\n private _pageRouteMap = new Map<string, PageRoute>()\r\n\r\n rawOptions: UserOptions\r\n root: string\r\n options: ResolvedOptions\r\n logger?: Logger\r\n\r\n constructor(userOptions: UserOptions, viteRoot: string = process.cwd()) {\r\n this.rawOptions = userOptions\r\n this.root = slash(viteRoot)\r\n debug.env('root', this.root)\r\n this.options = resolveOptions(userOptions, this.root)\r\n debug.options(this.options)\r\n }\r\n\r\n setLogger(logger: Logger) {\r\n this.logger = logger\r\n }\r\n\r\n setupViteServer(server: ViteDevServer) {\r\n if (this._server === server)\r\n return\r\n\r\n this._server = server\r\n this.setupWatcher(server.watcher)\r\n }\r\n\r\n setupWatcher(watcher: ViteDevServer['watcher']) {\r\n watcher\r\n .on('unlink', async (path) => {\r\n path = slash(path)\r\n if (!isTarget(path, this.options))\r\n return\r\n await this.removePage(path)\r\n this.onUpdate()\r\n })\r\n watcher\r\n .on('add', async (path) => {\r\n path = slash(path)\r\n if (!isTarget(path, this.options))\r\n return\r\n const page = this.options.dirs.find(i => path.startsWith(slash(resolve(this.root, i.dir))))!\r\n await this.addPage(path, page)\r\n this.onUpdate()\r\n })\r\n\r\n watcher\r\n .on('change', async (path) => {\r\n path = slash(path)\r\n if (!isTarget(path, this.options))\r\n return\r\n const page = this._pageRouteMap.get(path)\r\n if (page)\r\n await this.options.resolver.hmr?.changed?.(this, path)\r\n })\r\n }\r\n\r\n async addPage(path: string | string[], pageDir: PageOptions) {\r\n debug.pages('add', path)\r\n for (const p of toArray(path)) {\r\n const pageDirPath = slash(resolve(this.root, pageDir.dir))\r\n const extension = this.options.extensions.find(ext => p.endsWith(`.${ext}`))\r\n if (!extension)\r\n continue\r\n\r\n const route = slash(join(pageDir.baseRoute, p.replace(`${pageDirPath}/`, '').replace(`.${extension}`, '')))\r\n this._pageRouteMap.set(p, {\r\n path: p,\r\n route,\r\n })\r\n await this.options.resolver.hmr?.added?.(this, p)\r\n }\r\n }\r\n\r\n async removePage(path: string) {\r\n debug.pages('remove', path)\r\n this._pageRouteMap.delete(path)\r\n await this.options.resolver.hmr?.removed?.(this, path)\r\n }\r\n\r\n onUpdate() {\r\n if (!this._server)\r\n return\r\n\r\n invalidatePagesModule(this._server)\r\n debug.hmr('Reload generated pages.')\r\n this._server.ws.send({\r\n type: 'full-reload',\r\n })\r\n }\r\n\r\n async resolveRoutes() {\r\n return this.options.resolver.resolveRoutes(this)\r\n }\r\n\r\n async searchGlob() {\r\n const pageDirFiles = this.options.dirs.map((page) => {\r\n const pagesDirPath = slash(resolve(this.options.root, page.dir))\r\n const files = getPageFiles(pagesDirPath, this.options, page)\r\n debug.search(page.dir, files)\r\n return {\r\n ...page,\r\n files: files.map(file => slash(file)),\r\n }\r\n })\r\n\r\n for (const page of pageDirFiles)\r\n await this.addPage(page.files, page)\r\n\r\n debug.cache(this.pageRouteMap)\r\n }\r\n\r\n get debug() {\r\n return debug\r\n }\r\n\r\n get pageRouteMap() {\r\n return this._pageRouteMap\r\n }\r\n}\r\n","function clamp(n, min, max) {\n return Math.min(max, Math.max(min, n));\n}\nfunction sum(...args) {\n return flattenArrayable(args).reduce((a, b) => a + b, 0);\n}\nfunction lerp(min, max, t) {\n const interpolation = clamp(t, 0, 1);\n return min + (max - min) * interpolation;\n}\nfunction remap(n, inMin, inMax, outMin, outMax) {\n const interpolation = (n - inMin) / (inMax - inMin);\n return lerp(outMin, outMax, interpolation);\n}\n\nfunction toArray(array) {\n array = array ?? [];\n return Array.isArray(array) ? array : [array];\n}\nfunction flattenArrayable(array) {\n return toArray(array).flat(1);\n}\nfunction mergeArrayable(...args) {\n return args.flatMap((i) => toArray(i));\n}\nfunction partition(array, ...filters) {\n const result = Array.from({ length: filters.length + 1 }).fill(null).map(() => []);\n array.forEach((e, idx, arr) => {\n let i = 0;\n for (const filter of filters) {\n if (filter(e, idx, arr)) {\n result[i].push(e);\n return;\n }\n i += 1;\n }\n result[i].push(e);\n });\n return result;\n}\nfunction uniq(array) {\n return Array.from(new Set(array));\n}\nfunction uniqueBy(array, equalFn) {\n return array.reduce((acc, cur) => {\n const index = acc.findIndex((item) => equalFn(cur, item));\n if (index === -1)\n acc.push(cur);\n return acc;\n }, []);\n}\nfunction last(array) {\n return at(array, -1);\n}\nfunction remove(array, value) {\n if (!array)\n return false;\n const index = array.indexOf(value);\n if (index >= 0) {\n array.splice(index, 1);\n return true;\n }\n return false;\n}\nfunction at(array, index) {\n const len = array.length;\n if (!len)\n return void 0;\n if (index < 0)\n index += len;\n return array[index];\n}\nfunction range(...args) {\n let start, stop, step;\n if (args.length === 1) {\n start = 0;\n step = 1;\n [stop] = args;\n } else {\n [start, stop, step = 1] = args;\n }\n const arr = [];\n let current = start;\n while (current < stop) {\n arr.push(current);\n current += step || 1;\n }\n return arr;\n}\nfunction move(arr, from, to) {\n arr.splice(to, 0, arr.splice(from, 1)[0]);\n return arr;\n}\nfunction clampArrayRange(n, arr) {\n return clamp(n, 0, arr.length - 1);\n}\nfunction sample(arr, quantity) {\n return Array.from({ length: quantity }, (_) => arr[Math.round(Math.random() * (arr.length - 1))]);\n}\nfunction shuffle(array) {\n for (let i = array.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [array[i], array[j]] = [array[j], array[i]];\n }\n return array;\n}\nfunction filterInPlace(array, predicate) {\n for (let i = array.length; i--; i >= 0) {\n if (!predicate(array[i], i, array))\n array.splice(i, 1);\n }\n return array;\n}\n\nfunction assert(condition, message) {\n if (!condition)\n throw new Error(message);\n}\nconst toString = (v) => Object.prototype.toString.call(v);\nfunction getTypeName(v) {\n if (v === null)\n return \"null\";\n const type = toString(v).slice(8, -1).toLowerCase();\n return typeof v === \"object\" || typeof v === \"function\" ? type : typeof v;\n}\nfunction noop() {\n}\n\nfunction isDeepEqual(value1, value2) {\n const type1 = getTypeName(value1);\n const type2 = getTypeName(value2);\n if (type1 !== type2)\n return false;\n if (type1 === \"array\") {\n if (value1.length !== value2.length)\n return false;\n return value1.every((item, i) => {\n return isDeepEqual(item, value2[i]);\n });\n }\n if (type1 === \"object\") {\n const keyArr = Object.keys(value1);\n if (keyArr.length !== Object.keys(value2).length)\n return false;\n return keyArr.every((key) => {\n return isDeepEqual(value1[key], value2[key]);\n });\n }\n return Object.is(value1, value2);\n}\n\nfunction batchInvoke(functions) {\n functions.forEach((fn) => fn && fn());\n}\nfunction invoke(fn) {\n return fn();\n}\nfunction tap(value, callback) {\n callback(value);\n return value;\n}\n\nfunction notNullish(v) {\n return v != null;\n}\nfunction noNull(v) {\n return v !== null;\n}\nfunction notUndefined(v) {\n return v !== void 0;\n}\nfunction isTruthy(v) {\n return Boolean(v);\n}\n\nconst isDef = (val) => typeof val !== \"undefined\";\nconst isBoolean = (val) => typeof val === \"boolean\";\nconst isFunction = (val) => typeof val === \"function\";\nconst isNumber = (val) => typeof val === \"number\";\nconst isString = (val) => typeof val === \"string\";\nconst isObject = (val) => toString(val) === \"[object Object]\";\nconst isUndefined = (val) => toString(val) === \"[object Undefined]\";\nconst isNull = (val) => toString(val) === \"[object Null]\";\nconst isRegExp = (val) => toString(val) === \"[object RegExp]\";\nconst isDate = (val) => toString(val) === \"[object Date]\";\nfunction isPrimitive(val) {\n return !val || Object(val) !== val;\n}\nconst isWindow = (val) => typeof window !== \"undefined\" && toString(val) === \"[object Window]\";\nconst isBrowser = typeof window !== \"undefined\";\n\nfunction slash(str) {\n return str.replace(/\\\\/g, \"/\");\n}\nfunction ensurePrefix(prefix, str) {\n if (!str.startsWith(prefix))\n return prefix + str;\n return str;\n}\nfunction ensureSuffix(suffix, str) {\n if (!str.endsWith(suffix))\n return str + suffix;\n return str;\n}\nfunction template(str, ...args) {\n const [firstArg, fallback] = args;\n if (isObject(firstArg)) {\n const vars = firstArg;\n return str.replace(/\\{(\\w+)\\}/g, (_, key) => vars[key] || ((typeof fallback === \"function\" ? fallback(key) : fallback) ?? key));\n } else {\n return str.replace(/\\{(\\d+)\\}/g, (_, key) => {\n const index = Number(key);\n if (Number.isNaN(index))\n return key;\n return args[index];\n });\n }\n}\nconst urlAlphabet = \"useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict\";\nfunction randomStr(size = 16, dict = urlAlphabet) {\n let id = \"\";\n let i = size;\n const len = dict.length;\n while (i--)\n id += dict[Math.random() * len | 0];\n return id;\n}\nfunction capitalize(str) {\n return str[0].toUpperCase() + str.slice(1).toLowerCase();\n}\nconst _reFullWs = /^\\s*$/;\nfunction unindent(str) {\n const lines = (typeof str === \"string\" ? str : str[0]).split(\"\\n\");\n const whitespaceLines = lines.map((line) => _reFullWs.test(line));\n const commonIndent = lines.reduce((min, line, idx) => {\n if (whitespaceLines[idx])\n return min;\n const indent = line.match(/^\\s*/)?.[0].length;\n return indent === void 0 ? min : Math.min(min, indent);\n }, Number.POSITIVE_INFINITY);\n let emptyLinesHead = 0;\n while (emptyLinesHead < lines.length && whitespaceLines[emptyLinesHead])\n emptyLinesHead++;\n let emptyLinesTail = 0;\n while (emptyLinesTail < lines.length && whitespaceLines[lines.length - emptyLinesTail - 1])\n emptyLinesTail++;\n return lines.slice(emptyLinesHead, lines.length - emptyLinesTail).map((line) => line.slice(commonIndent)).join(\"\\n\");\n}\n\nfunction objectMap(obj, fn) {\n return Object.fromEntries(\n Object.entries(obj).map(([k, v]) => fn(k, v)).filter(notNullish)\n );\n}\nfunction isKeyOf(obj, k) {\n return k in obj;\n}\nfunction objectKeys(obj) {\n return Object.keys(obj);\n}\nfunction objectEntries(obj) {\n return Object.entries(obj);\n}\nfunction deepMerge(target, ...sources) {\n if (!sources.length)\n return target;\n const source = sources.shift();\n if (source === void 0)\n return target;\n if (isMergableObject(target) && isMergableObject(source)) {\n objectKeys(source).forEach((key) => {\n if (key === \"__proto__\" || key === \"constructor\" || key === \"prototype\")\n return;\n if (isMergableObject(source[key])) {\n if (!target[key])\n target[key] = {};\n if (isMergableObject(target[key])) {\n deepMerge(target[key], source[key]);\n } else {\n target[key] = source[key];\n }\n } else {\n target[key] = source[key];\n }\n });\n }\n return deepMerge(target, ...sources);\n}\nfunction deepMergeWithArray(target, ...sources) {\n if (!sources.length)\n return target;\n const source = sources.shift();\n if (source === void 0)\n return target;\n if (Array.isArray(target) && Array.isArray(source))\n target.push(...source);\n if (isMergableObject(target) && isMergableObject(source)) {\n objectKeys(source).forEach((key) => {\n if (key === \"__proto__\" || key === \"constructor\" || key === \"prototype\")\n return;\n if (Array.isArray(source[key])) {\n if (!target[key])\n target[key] = [];\n deepMergeWithArray(target[key], source[key]);\n } else if (isMergableObject(source[key])) {\n if (!target[key])\n target[key] = {};\n deepMergeWithArray(target[key], source[key]);\n } else {\n target[key] = source[key];\n }\n });\n }\n return deepMergeWithArray(target, ...sources);\n}\nfunction isMergableObject(item) {\n return isObject(item) && !Array.isArray(item);\n}\nfunction objectPick(obj, keys, omitUndefined = false) {\n return keys.reduce((n, k) => {\n if (k in obj) {\n if (!omitUndefined || obj[k] !== void 0)\n n[k] = obj[k];\n }\n return n;\n }, {});\n}\nfunction clearUndefined(obj) {\n Object.keys(obj).forEach((key) => obj[key] === void 0 ? delete obj[key] : {});\n return obj;\n}\nfunction hasOwnProperty(obj, v) {\n if (obj == null)\n return false;\n return Object.prototype.hasOwnProperty.call(obj, v);\n}\nconst _objectIdMap = /* @__PURE__ */ new WeakMap();\nfunction objectId(obj) {\n if (isPrimitive(obj))\n return obj;\n if (!_objectIdMap.has(obj)) {\n _objectIdMap.set(obj, randomStr());\n }\n return _objectIdMap.get(obj);\n}\n\n/*\nHow it works:\n`this.#head` is an instance of `Node` which keeps track of its current value and nests another instance of `Node` that keeps the value that comes after it. When a value is provided to `.enqueue()`, the code needs to iterate through `this.#head`, going deeper and deeper to find the last value. However, iterating through every single item is slow. This problem is solved by saving a reference to the last value as `this.#tail` so that it can reference it to add a new value.\n*/\n\nclass Node {\n\tvalue;\n\tnext;\n\n\tconstructor(value) {\n\t\tthis.value = value;\n\t}\n}\n\nclass Queue {\n\t#head;\n\t#tail;\n\t#size;\n\n\tconstructor() {\n\t\tthis.clear();\n\t}\n\n\tenqueue(value) {\n\t\tconst node = new Node(value);\n\n\t\tif (this.#head) {\n\t\t\tthis.#tail.next = node;\n\t\t\tthis.#tail = node;\n\t\t} else {\n\t\t\tthis.#head = node;\n\t\t\tthis.#tail = node;\n\t\t}\n\n\t\tthis.#size++;\n\t}\n\n\tdequeue() {\n\t\tconst current = this.#head;\n\t\tif (!current) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#head = this.#head.next;\n\t\tthis.#size--;\n\t\treturn current.value;\n\t}\n\n\tpeek() {\n\t\tif (!this.#head) {\n\t\t\treturn;\n\t\t}\n\n\t\treturn this.#head.value;\n\n\t\t// TODO: Node.js 18.\n\t\t// return this.#head?.value;\n\t}\n\n\tclear() {\n\t\tthis.#head = undefined;\n\t\tthis.#tail = undefined;\n\t\tthis.#size = 0;\n\t}\n\n\tget size() {\n\t\treturn this.#size;\n\t}\n\n\t* [Symbol.iterator]() {\n\t\tlet current = this.#head;\n\n\t\twhile (current) {\n\t\t\tyield current.value;\n\t\t\tcurrent = current.next;\n\t\t}\n\t}\n}\n\nfunction pLimit(concurrency) {\n\tvalidateConcurrency(concurrency);\n\n\tconst queue = new Queue();\n\tlet activeCount = 0;\n\n\tconst resumeNext = () => {\n\t\tif (activeCount < concurrency && queue.size > 0) {\n\t\t\tqueue.dequeue()();\n\t\t\t// Since `pendingCount` has been decreased by one, increase `activeCount` by one.\n\t\t\tactiveCount++;\n\t\t}\n\t};\n\n\tconst next = () => {\n\t\tactiveCount--;\n\n\t\tresumeNext();\n\t};\n\n\tconst run = async (function_, resolve, arguments_) => {\n\t\tconst result = (async () => function_(...arguments_))();\n\n\t\tresolve(result);\n\n\t\ttry {\n\t\t\tawait result;\n\t\t} catch {}\n\n\t\tnext();\n\t};\n\n\tconst enqueue = (function_, resolve, arguments_) => {\n\t\t// Queue `internalResolve` instead of the `run` function\n\t\t// to preserve asynchronous context.\n\t\tnew Promise(internalResolve => {\n\t\t\tqueue.enqueue(internalResolve);\n\t\t}).then(\n\t\t\trun.bind(undefined, function_, resolve, arguments_),\n\t\t);\n\n\t\t(async () => {\n\t\t\t// This function needs to wait until the next microtask before comparing\n\t\t\t// `activeCount` to `concurrency`, because `activeCount` is updated asynchronously\n\t\t\t// after the `internalResolve` function is dequeued and called. The comparison in the if-statement\n\t\t\t// needs to happen asynchronously as well to get an up-to-date value for `activeCount`.\n\t\t\tawait Promise.resolve();\n\n\t\t\tif (activeCount < concurrency) {\n\t\t\t\tresumeNext();\n\t\t\t}\n\t\t})();\n\t};\n\n\tconst generator = (function_, ...arguments_) => new Promise(resolve => {\n\t\tenqueue(function_, resolve, arguments_);\n\t});\n\n\tObject.defineProperties(generator, {\n\t\tactiveCount: {\n\t\t\tget: () => activeCount,\n\t\t},\n\t\tpendingCount: {\n\t\t\tget: () => queue.size,\n\t\t},\n\t\tclearQueue: {\n\t\t\tvalue() {\n\t\t\t\tqueue.clear();\n\t\t\t},\n\t\t},\n\t\tconcurrency: {\n\t\t\tget: () => concurrency,\n\n\t\t\tset(newConcurrency) {\n\t\t\t\tvalidateConcurrency(newConcurrency);\n\t\t\t\tconcurrency = newConcurrency;\n\n\t\t\t\tqueueMicrotask(() => {\n\t\t\t\t\t// eslint-disable-next-line no-unmodified-loop-condition\n\t\t\t\t\twhile (activeCount < concurrency && queue.size > 0) {\n\t\t\t\t\t\tresumeNext();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t},\n\t\t},\n\t});\n\n\treturn generator;\n}\n\nfunction validateConcurrency(concurrency) {\n\tif (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) {\n\t\tthrow new TypeError('Expected `concurrency` to be a number from 1 and up');\n\t}\n}\n\nconst VOID = Symbol(\"p-void\");\nclass PInstance extends Promise {\n constructor(items = [], options) {\n super(() => {\n });\n this.items = items;\n this.options = options;\n }\n promises = /* @__PURE__ */ new Set();\n get promise() {\n let batch;\n const items = [...Array.from(this.items), ...Array.from(this.promises)];\n if (this.options?.concurrency) {\n const limit = pLimit(this.options.concurrency);\n batch = Promise.all(items.map((p2) => limit(() => p2)));\n } else {\n batch = Promise.all(items);\n }\n return batch.then((l) => l.filter((i) => i !== VOID));\n }\n add(...args) {\n args.forEach((i) => {\n this.promises.add(i);\n });\n }\n map(fn) {\n return new PInstance(\n Array.from(this.items).map(async (i, idx) => {\n const v = await i;\n if (v === VOID)\n return VOID;\n return fn(v, idx);\n }),\n this.options\n );\n }\n filter(fn) {\n return new PInstance(\n Array.from(this.items).map(async (i, idx) => {\n const v = await i;\n const r = await fn(v, idx);\n if (!r)\n return VOID;\n return v;\n }),\n this.options\n );\n }\n forEach(fn) {\n return this.map(fn).then();\n }\n reduce(fn, initialValue) {\n return this.promise.then((array) => array.reduce(fn, initialValue));\n }\n clear() {\n this.promises.clear();\n }\n then(onfulfilled, onrejected) {\n return this.promise.then(onfulfilled, onrejected);\n }\n catch(fn) {\n return this.promise.catch(fn);\n }\n finally(fn) {\n return this.promise.finally(fn);\n }\n}\nfunction p(items, options) {\n return new PInstance(items, options);\n}\n\nfunction createSingletonPromise(fn) {\n let _promise;\n function wrapper() {\n if (!_promise)\n _promise = fn();\n return _promise;\n }\n wrapper.reset = async () => {\n const _prev = _promise;\n _promise = void 0;\n if (_prev)\n await _prev;\n };\n return wrapper;\n}\nfunction sleep(ms, callback) {\n return new Promise(\n (resolve) => setTimeout(async () => {\n await callback?.();\n resolve();\n }, ms)\n );\n}\nfunction createPromiseLock() {\n const locks = [];\n return {\n async run(fn) {\n const p = fn();\n locks.push(p);\n try {\n return await p;\n } finally {\n remove(locks, p);\n }\n },\n async wait() {\n await Promise.allSettled(locks);\n },\n isWaiting() {\n return Boolean(locks.length);\n },\n clear() {\n locks.length = 0;\n }\n };\n}\nfunction createControlledPromise() {\n let resolve, reject;\n const promise = new Promise((_resolve, _reject) => {\n resolve = _resolve;\n reject = _reject;\n });\n promise.resolve = resolve;\n promise.reject = reject;\n return promise;\n}\n\nconst timestamp = () => +Date.now();\n\n/* eslint-disable no-undefined,no-param-reassign,no-shadow */\n\n/**\n * Throttle execution of a function. Especially useful for rate limiting\n * execution of handlers on events like resize and scroll.\n *\n * @param {number} delay - A zero-or-greater delay in milliseconds. For event callbacks, values around 100 or 250 (or even higher)\n * are most useful.\n * @param {Function} callback - A function to be executed after delay milliseconds. The `this` context and all arguments are passed through,\n * as-is, to `callback` when the throttled-function is executed.\n * @param {object} [options] - An object to configure options.\n * @param {boolean} [options.noTrailing] - Optional, defaults to false. If noTrailing is true, callback will only execute every `delay` milliseconds\n * while the throttled-function is being called. If noTrailing is false or unspecified, callback will be executed\n * one final time after the last throttled-function call. (After the throttled-function has not been called for\n * `delay` milliseconds, the internal counter is reset).\n * @param {boolean} [options.noLeading] - Optional, defaults to false. If noLeading is false, the first throttled-function call will execute callback\n * immediately. If noLeading is true, the first the callback execution will be skipped. It should be noted that\n * callback will never executed if both noLeading = true and noTrailing = true.\n * @param {boolean} [options.debounceMode] - If `debounceMode` is true (at begin), schedule `clear` to execute after `delay` ms. If `debounceMode` is\n * false (at end), schedule `callback` to execute after `delay` ms.\n *\n * @returns {Function} A new, throttled, function.\n */\nfunction throttle$1 (delay, callback, options) {\n var _ref = options || {},\n _ref$noTrailing = _ref.noTrailing,\n noTrailing = _ref$noTrailing === void 0 ? false : _ref$noTrailing,\n _ref$noLeading = _ref.noLeading,\n noLeading = _ref$noLeading === void 0 ? false : _ref$noLeading,\n _ref$debounceMode = _ref.debounceMode,\n debounceMode = _ref$debounceMode === void 0 ? undefined : _ref$debounceMode;\n /*\n * After wrapper has stopped being called, this timeout ensures that\n * `callback` is executed at the proper times in `throttle` and `end`\n * debounce modes.\n */\n\n\n var timeoutID;\n var cancelled = false; // Keep track of the last time `callback` was executed.\n\n var lastExec = 0; // Function to clear existing timeout\n\n function clearExistingTimeout() {\n if (timeoutID) {\n clearTimeout(timeoutID);\n }\n } // Function to cancel next exec\n\n\n function cancel(options) {\n var _ref2 = options || {},\n _ref2$upcomingOnly = _ref2.upcomingOnly,\n upcomingOnly = _ref2$upcomingOnly === void 0 ? false : _ref2$upcomingOnly;\n\n clearExistingTimeout();\n cancelled = !upcomingOnly;\n }\n /*\n * The `wrapper` function encapsulates all of the throttling / debouncing\n * functionality and when executed will limit the rate at which `callback`\n * is executed.\n */\n\n\n function wrapper() {\n for (var _len = arguments.length, arguments_ = new Array(_len), _key = 0; _key < _len; _key++) {\n arguments_[_key] = arguments[_key];\n }\n\n var self = this;\n var elapsed = Date.now() - lastExec;\n\n if (cancelled) {\n return;\n } // Execute `callback` and update the `lastExec` timestamp.\n\n\n function exec() {\n lastExec = Date.now();\n callback.apply(self, arguments_);\n }\n /*\n * If `debounceMode` is true (at begin) this is used to clear the flag\n * to allow future `callback` executions.\n */\n\n\n function clear() {\n timeoutID = undefined;\n }\n\n if (!noLeading && debounceMode && !timeoutID) {\n /*\n * Since `wrapper` is being called for the first time and\n * `debounceMode` is true (at begin), execute `callback`\n * and noLeading != true.\n */\n exec();\n }\n\n clearExistingTimeout();\n\n if (debounceMode === undefined && elapsed > delay) {\n if (noLeading) {\n /*\n * In throttle mode with noLeading, if `delay` time has\n * been exceeded, update `lastExec` and schedule `callback`\n * to execute after `delay` ms.\n */\n lastExec = Date.now();\n\n if (!noTrailing) {\n timeoutID = setTimeout(debounceMode ? clear : exec, delay);\n }\n } else {\n /*\n * In throttle mode without noLeading, if `delay` time has been exceeded, execute\n * `callback`.\n */\n exec();\n }\n } else if (noTrailing !== true) {\n /*\n * In trailing throttle mode, since `delay` time has not been\n * exceeded, schedule `callback` to execute `delay` ms after most\n * recent execution.\n *\n * If `debounceMode` is true (at begin), schedule `clear` to execute\n * after `delay` ms.\n *\n * If `debounceMode` is false (at end), schedule `callback` to\n * execute after `delay` ms.\n */\n timeoutID = setTimeout(debounceMode ? clear : exec, debounceMode === undefined ? delay - elapsed : delay);\n }\n }\n\n wrapper.cancel = cancel; // Return the wrapper function.\n\n return wrapper;\n}\n\n/* eslint-disable no-undefined */\n/**\n * Debounce execution of a function. Debouncing, unlike throttling,\n * guarantees that a function is only executed a single time, either at the\n * very beginning of a series of calls, or at the very end.\n *\n * @param {number} delay - A zero-or-greater delay in milliseconds. For event callbacks, values around 100 or 250 (or even higher) are most useful.\n * @param {Function} callback - A function to be executed after delay milliseconds. The `this` context and all arguments are passed through, as-is,\n * to `callback` when the debounced-function is executed.\n * @param {object} [options] - An object to configure options.\n * @param {boolean} [options.atBegin] - Optional, defaults to false. If atBegin is false or unspecified, callback will only be executed `delay` milliseconds\n * after the last debounced-function call. If atBegin is true, callback will be executed only at the first debounced-function call.\n * (After the throttled-function has not been called for `delay` milliseconds, the internal counter is reset).\n *\n * @returns {Function} A new, debounced function.\n */\n\nfunction debounce$1 (delay, callback, options) {\n var _ref = options || {},\n _ref$atBegin = _ref.atBegin,\n atBegin = _ref$atBegin === void 0 ? false : _ref$atBegin;\n\n return throttle$1(delay, callback, {\n debounceMode: atBegin !== false\n });\n}\n\nfunction throttle(...args) {\n return throttle$1(...args);\n}\nfunction debounce(...args) {\n return debounce$1(...args);\n}\n\nexport { assert, at, batchInvoke, capitalize, clamp, clampArrayRange, clearUndefined, createControlledPromise, createPromiseLock, createSingletonPromise, debounce, deepMerge, deepMergeWithArray, ensurePrefix, ensureSuffix, filterInPlace, flattenArrayable, getTypeName, hasOwnProperty, invoke, isBoolean, isBrowser, isDate, isDeepEqual, isDef, isFunction, isKeyOf, isNull, isNumber, isObject, isPrimitive, isRegExp, isString, isTruthy, isUndefined, isWindow, last, lerp, mergeArrayable, move, noNull, noop, notNullish, notUndefined, objectEntries, objectId, objectKeys, objectMap, objectPick, p, partition, randomStr, range, remap, remove, sample, shuffle, slash, sleep, sum, tap, template, throttle, timestamp, toArray, toString, unindent, uniq, uniqueBy };\n","import type { PageOptions, ResolvedOptions } from './types'\r\nimport { join } from 'node:path'\r\nimport { slash } from '@antfu/utils'\r\nimport { globSync } from 'tinyglobby'\r\n\r\nimport { extsToGlob } from './utils'\r\n\r\n/**\r\n * Resolves the page dirs for its for its given globs\r\n */\r\nexport function getPageDirs(PageOptions: PageOptions, root: string, exclude: string[]): PageOptions[] {\r\n const dirs = globSync(slash(PageOptions.dir), {\r\n ignore: exclude,\r\n onlyDirectories: true,\r\n dot: true,\r\n expandDirectories: false,\r\n cwd: root,\r\n })\r\n\r\n const pageDirs = dirs.map(dir => ({\r\n ...PageOptions,\r\n dir: dir.replace(/\\/$/, ''),\r\n }))\r\n\r\n return pageDirs\r\n}\r\n\r\n/**\r\n * Resolves the files that are valid pages for the given context.\r\n */\r\nexport function getPageFiles(path: string, options: ResolvedOptions, pageOptions?: PageOptions): string[] {\r\n const {\r\n exclude,\r\n extensions,\r\n } = options\r\n\r\n const ext = extsToGlob(extensions)\r\n const pattern = pageOptions?.filePattern ?? `**/*.${ext}`\r\n\r\n const files = globSync(pattern, {\r\n ignore: exclude,\r\n onlyFiles: true,\r\n cwd: path,\r\n }).map(p => slash(join(path, p)))\r\n\r\n return files\r\n}\r\n","import type { ModuleNode, ViteDevServer } from 'vite'\r\nimport type { ResolvedOptions } from './types'\r\nimport { resolve, win32 } from 'node:path'\r\nimport { URLSearchParams } from 'node:url'\r\nimport { slash } from '@antfu/utils'\r\nimport Debug from 'debug'\r\nimport micromatch from 'micromatch'\r\n\r\nimport { cacheAllRouteRE, countSlashRE, dynamicRouteRE, MODULE_ID_VIRTUAL, nuxtCacheAllRouteRE, nuxtDynamicRouteRE, replaceDynamicRouteRE, replaceIndexRE } from './constants'\r\n\r\nexport const debug = {\r\n hmr: Debug('vite-plugin-pages:hmr'),\r\n routeBlock: Debug('vite-plugin-pages:routeBlock'),\r\n options: Debug('vite-plugin-pages:options'),\r\n pages: Debug('vite-plugin-pages:pages'),\r\n search: Debug('vite-plugin-pages:search'),\r\n env: Debug('vite-plugin-pages:env'),\r\n cache: Debug('vite-plugin-pages:cache'),\r\n resolver: Debug('vite-plugin-pages:resolver'),\r\n}\r\n\r\nexport function extsToGlob(extensions: string[]) {\r\n return extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0] || ''\r\n}\r\n\r\nexport function countSlash(value: string) {\r\n return (value.match(countSlashRE) || []).length\r\n}\r\n\r\nfunction isPagesDir(path: string, options: ResolvedOptions) {\r\n for (const page of options.dirs) {\r\n const dirPath = slash(resolve(options.root, page.dir))\r\n if (path.startsWith(dirPath))\r\n return true\r\n }\r\n return false\r\n}\r\n\r\nexport function isTarget(path: string, options: ResolvedOptions) {\r\n return isPagesDir(path, options) && !micromatch.isMatch(path, options.exclude) && options.extensionsRE.test(path)\r\n}\r\n\r\nexport function isDynamicRoute(routePath: string, nuxtStyle = false) {\r\n return nuxtStyle\r\n ? nuxtDynamicRouteRE.test(routePath)\r\n : dynamicRouteRE.test(routePath)\r\n}\r\n\r\nexport function isCatchAllRoute(routePath: string, nuxtStyle = false) {\r\n return nuxtStyle\r\n ? nuxtCacheAllRouteRE.test(routePath)\r\n : cacheAllRouteRE.test(routePath)\r\n}\r\n\r\nexport function resolveImportMode(\r\n filepath: string,\r\n options: ResolvedOptions,\r\n) {\r\n const mode = options.importMode\r\n if (typeof mode === 'function')\r\n return mode(filepath, options)\r\n return mode\r\n}\r\n\r\nexport function invalidatePagesModule(server: ViteDevServer) {\r\n const { moduleGraph } = server\r\n const mods = moduleGraph.getModulesByFile(MODULE_ID_VIRTUAL)\r\n if (mods) {\r\n const seen = new Set<ModuleNode>()\r\n mods.forEach((mod) => {\r\n moduleGraph.invalidateModule(mod, seen)\r\n })\r\n }\r\n}\r\n\r\nexport function normalizeCase(str: string, caseSensitive: boolean) {\r\n if (!caseSensitive)\r\n return str.toLocaleLowerCase()\r\n return str\r\n}\r\n\r\nexport function normalizeName(name: string, isDynamic: boolean, nuxtStyle = false) {\r\n if (!isDynamic)\r\n return name\r\n\r\n return nuxtStyle\r\n ? name.replace(nuxtDynamicRouteRE, '$1') || 'all'\r\n : name.replace(replaceDynamicRouteRE, '$1')\r\n}\r\n\r\nexport function buildReactRoutePath(node: string, nuxtStyle = false): string | undefined {\r\n const isDynamic = isDynamicRoute(node, nuxtStyle)\r\n const isCatchAll = isCatchAllRoute(node, nuxtStyle)\r\n const normalizedName = normalizeName(node, isDynamic, nuxtStyle)\r\n\r\n if (isDynamic) {\r\n if (isCatchAll)\r\n return '*'\r\n\r\n return `:${normalizedName}`\r\n }\r\n\r\n return `${normalizedName}`\r\n}\r\n\r\n// https://github.dev/remix-run/remix/blob/264e3f8884c5cafd8d06acc3e01153b376745b7c/packages/remix-dev/config/routesConvention.ts#L105\r\nexport function buildReactRemixRoutePath(node: string): string | undefined {\r\n const escapeStart = '['\r\n const escapeEnd = ']'\r\n let result = ''\r\n let rawSegmentBuffer = ''\r\n\r\n let inEscapeSequence = 0\r\n let skipSegment = false\r\n for (let i = 0; i < node.length; i++) {\r\n const char = node.charAt(i)\r\n const lastChar = i > 0 ? node.charAt(i - 1) : undefined\r\n const nextChar = i < node.length - 1 ? node.charAt(i + 1) : undefined\r\n\r\n function isNewEscapeSequence() {\r\n return (\r\n !inEscapeSequence && char === escapeStart && lastChar !== escapeStart\r\n )\r\n }\r\n\r\n function isCloseEscapeSequence() {\r\n return inEscapeSequence && char === escapeEnd && nextChar !== escapeEnd\r\n }\r\n\r\n function isStartOfLayoutSegment() {\r\n return char === '_' && nextChar === '_' && !rawSegmentBuffer\r\n }\r\n\r\n if (skipSegment) {\r\n if (char === '/' || char === '.' || char === win32.sep)\r\n skipSegment = false\r\n\r\n continue\r\n }\r\n\r\n if (isNewEscapeSequence()) {\r\n inEscapeSequence++\r\n continue\r\n }\r\n\r\n if (isCloseEscapeSequence()) {\r\n inEscapeSequence--\r\n continue\r\n }\r\n\r\n if (inEscapeSequence) {\r\n result += char\r\n continue\r\n }\r\n\r\n if (char === '/' || char === win32.sep || char === '.') {\r\n if (rawSegmentBuffer === 'index' && result.endsWith('index'))\r\n result = result.replace(replaceIndexRE, '')\r\n else result += '/'\r\n\r\n rawSegmentBuffer = ''\r\n continue\r\n }\r\n\r\n if (isStartOfLayoutSegment()) {\r\n skipSegment = true\r\n continue\r\n }\r\n\r\n rawSegmentBuffer += char\r\n\r\n if (char === '$') {\r\n result += typeof nextChar === 'undefined' ? '*' : ':'\r\n continue\r\n }\r\n\r\n result += char\r\n }\r\n\r\n if (rawSegmentBuffer === 'index' && result.endsWith('index'))\r\n result = result.replace(replaceIndexRE, '')\r\n\r\n return result || undefined\r\n}\r\n\r\nexport function parsePageRequest(id: string) {\r\n const [moduleId, rawQuery] = id.split('?', 2)\r\n const query = new URLSearchParams(rawQuery)\r\n const pageId = query.get('id')\r\n return {\r\n moduleId,\r\n query,\r\n pageId,\r\n }\r\n}\r\n","import type { ImportModeResolver, ResolvedOptions, UserOptions } from './types'\r\nimport { resolve } from 'node:path'\r\nimport process from 'node:process'\r\nimport { slash, toArray } from '@antfu/utils'\r\n\r\nimport { MODULE_IDS } from './constants'\r\nimport { getPageDirs } from './files'\r\nimport { reactResolver, solidResolver, vueResolver } from './resolvers'\r\n\r\nfunction resolvePageDirs(dirs: UserOptions['dirs'], root: string, exclude: string[]) {\r\n dirs = toArray(dirs)\r\n return dirs.flatMap((dir) => {\r\n const option = typeof dir === 'string'\r\n ? { dir, baseRoute: '' }\r\n : dir\r\n\r\n option.dir = slash(resolve(root, option.dir)).replace(`${root}/`, '')\r\n option.baseRoute = option.baseRoute.replace(/^\\//, '').replace(/\\/$/, '')\r\n\r\n return getPageDirs(option, root, exclude)\r\n })\r\n}\r\n\r\nexport const syncIndexResolver: ImportModeResolver = (filepath, options) => {\r\n for (const page of options.dirs) {\r\n if (page.baseRoute === '' && filepath.startsWith(`/${page.dir}/index`))\r\n return 'sync'\r\n }\r\n return 'async'\r\n}\r\n\r\nfunction getResolver(originalResolver: UserOptions['resolver']) {\r\n let resolver = originalResolver || 'vue'\r\n\r\n if (typeof resolver !== 'string')\r\n return resolver\r\n\r\n switch (resolver) {\r\n case 'vue':\r\n resolver = vueResolver()\r\n break\r\n case 'react':\r\n resolver = reactResolver()\r\n break\r\n case 'solid':\r\n resolver = solidResolver()\r\n break\r\n default:\r\n throw new Error(`Unsupported resolver: ${resolver}`)\r\n }\r\n return resolver\r\n}\r\n\r\nexport function resolveOptions(userOptions: UserOptions, viteRoot?: string): ResolvedOptions {\r\n const {\r\n dirs = ['src/pages'],\r\n routeBlockLang = 'json5',\r\n exclude = ['node_modules', '.git', '**/__*__/**'],\r\n caseSensitive = false,\r\n routeNameSeparator = '-',\r\n extendRoute,\r\n onRoutesGenerated,\r\n onClientGenerated,\r\n } = userOptions\r\n\r\n const root = viteRoot || slash(process.cwd())\r\n\r\n const importMode = userOptions.importMode || syncIndexResolver\r\n\r\n const importPath = userOptions.importPath || 'relative'\r\n\r\n const resolver = getResolver(userOptions.resolver)\r\n\r\n const extensions = userOptions.extensions || resolver.resolveExtensions()\r\n\r\n const extensionsRE = new RegExp(`\\\\.(${extensions.join('|')})$`)\r\n\r\n const resolvedDirs = resolvePageDirs(dirs, root, exclude)\r\n\r\n const routeStyle = userOptions.routeStyle || 'next'\r\n\r\n const moduleIds = userOptions.moduleId\r\n ? [userOptions.moduleId]\r\n : resolver.resolveModuleIds?.() || MODULE_IDS\r\n\r\n const resolvedOptions: ResolvedOptions = {\r\n dirs: resolvedDirs,\r\n routeStyle,\r\n routeBlockLang,\r\n moduleIds,\r\n root,\r\n extensions,\r\n importMode,\r\n importPath,\r\n exclude,\r\n caseSensitive,\r\n resolver,\r\n extensionsRE,\r\n extendRoute,\r\n onRoutesGenerated,\r\n onClientGenerated,\r\n routeNameSeparator,\r\n }\r\n\r\n return resolvedOptions\r\n}\r\n","import type { ResolvedOptions } from './types'\r\nimport { ROUTE_IMPORT_NAME } from './constants'\r\n\r\nimport { resolveImportMode } from './utils'\r\n\r\nconst componentRE = /\"(?:component|element)\":(\"(.*?)\")/g\r\nconst hasFunctionRE = /\"(?:props|beforeEnter)\":(\"(.*?)\")/g\r\n\r\nconst multilineCommentsRE = /\\/\\*(.|[\\r\\n])*?\\*\\//g\r\nconst singlelineCommentsRE = /\\/\\/.*/g\r\n\r\nfunction replaceFunction(_: any, value: any) {\r\n if (typeof value === 'function' || typeof value === 'function') {\r\n const fnBody = value.toString()\r\n .replace(multilineCommentsRE, '')\r\n .replace(singlelineCommentsRE, '')\r\n .replace(/(\\s)/g, '')\r\n\r\n // ES6 Arrow Function\r\n if (fnBody.length < 8 || fnBody.substring(0, 8) !== 'function')\r\n return `_NuFrRa_${fnBody}`\r\n\r\n return fnBody\r\n }\r\n\r\n return value\r\n}\r\n\r\n/**\r\n * Creates a stringified Vue Router route definition.\r\n */\r\nexport function stringifyRoutes(\r\n preparedRoutes: any[],\r\n options: ResolvedOptions,\r\n) {\r\n const importsMap: Map<string, string> = new Map()\r\n\r\n function getImportString(path: string, importName: string) {\r\n const mode = resolveImportMode(path, options)\r\n return mode === 'sync'\r\n ? `import ${importName} from \"${path}\"`\r\n : `const ${importName} = ${\r\n options.resolver.stringify?.dynamicImport?.(path) || `() => import(\"${path}\")`\r\n }`\r\n }\r\n\r\n function componentReplacer(str: string, replaceStr: string, path: string) {\r\n let importName = importsMap.get(path)\r\n\r\n if (!importName)\r\n importName = ROUTE_IMPORT_NAME.replace('$1', `${importsMap.size}`)\r\n\r\n importsMap.set(path, importName)\r\n\r\n importName = options.resolver.stringify?.component?.(importName) || importName\r\n\r\n return str.replace(replaceStr, importName)\r\n }\r\n\r\n function functionReplacer(str: string, replaceStr: string, content: string) {\r\n if (content.startsWith('function'))\r\n return str.replace(replaceStr, content)\r\n\r\n if (content.startsWith('_NuFrRa_'))\r\n return str.replace(replaceStr, content.slice(8))\r\n\r\n return str\r\n }\r\n\r\n const stringRoutes = JSON\r\n .stringify(preparedRoutes, replaceFunction)\r\n .replace(componentRE, componentReplacer)\r\n .replace(hasFunctionRE, functionReplacer)\r\n\r\n const imports = Array.from(importsMap).map(args => getImportString(...args))\r\n\r\n return {\r\n imports,\r\n stringRoutes,\r\n }\r\n}\r\n\r\nexport function generateClientCode(routes: any[], options: ResolvedOptions) {\r\n const { imports, stringRoutes } = stringifyRoutes(routes, options)\r\n const code = `${imports.join(';\\n')};\\n\\nconst routes = ${stringRoutes};\\n\\nexport default routes;`\r\n return options.resolver.stringify?.final?.(code) || code\r\n}\r\n","import type { PageContext } from '../context'\r\nimport type { Optional, PageResolver, ResolvedOptions } from '../types'\r\n\r\nimport { generateClientCode } from '../stringify'\r\nimport {\r\n buildReactRemixRoutePath,\r\n buildReactRoutePath,\r\n countSlash,\r\n normalizeCase,\r\n} from '../utils'\r\n\r\nexport interface ReactRouteBase {\r\n caseSensitive?: boolean\r\n children?: ReactRouteBase[]\r\n element?: string\r\n index?: boolean\r\n path?: string\r\n rawRoute: string\r\n}\r\n\r\nexport interface ReactRoute extends Omit<Optional<ReactRouteBase, 'rawRoute' | 'path'>, 'children'> {\r\n children?: ReactRoute[]\r\n}\r\n\r\nfunction prepareRoutes(\r\n routes: ReactRoute[],\r\n options: ResolvedOptions,\r\n parent?: ReactRoute,\r\n) {\r\n for (const route of routes) {\r\n if (parent)\r\n route.path = route.path?.replace(/^\\//, '')\r\n\r\n if (route.children)\r\n route.children = prepareRoutes(route.children, options, route)\r\n\r\n delete route.rawRoute\r\n\r\n Object.assign(route, options.extendRoute?.(route, parent) || {})\r\n }\r\n\r\n return routes\r\n}\r\n\r\nasync function computeReactRoutes(ctx: PageContext): Promise<ReactRoute[]> {\r\n const { routeStyle, caseSensitive, importPath } = ctx.options\r\n const nuxtStyle = routeStyle === 'nuxt'\r\n\r\n const pageRoutes = [...ctx.pageRouteMap.values()]\r\n // sort routes for HMR\r\n .sort((a, b) => countSlash(a.route) - countSlash(b.route))\r\n\r\n const routes: ReactRouteBase[] = []\r\n\r\n pageRoutes.forEach((page) => {\r\n const pathNodes = page.route.split('/')\r\n const element = importPath === 'relative' ? page.path.replace(ctx.root, '') : page.path\r\n let parentRoutes = routes\r\n\r\n for (let i = 0; i < pathNodes.length; i++) {\r\n const node = pathNodes[i]\r\n\r\n const route: ReactRouteBase = {\r\n caseSensitive,\r\n path: '',\r\n rawRoute: pathNodes.slice(0, i + 1).join('/'),\r\n }\r\n\r\n if (i === pathNodes.length - 1)\r\n route.element = element\r\n\r\n const isIndexRoute = normalizeCase(node, caseSensitive).endsWith('index')\r\n\r\n if (!route.path && isIndexRoute) {\r\n route.path = '/'\r\n }\r\n else if (!isIndexRoute) {\r\n if (routeStyle === 'remix')\r\n route.path = buildReactRemixRoutePath(node)\r\n else\r\n route.path = buildReactRoutePath(node, nuxtStyle)\r\n }\r\n\r\n // Check parent exits\r\n const parent = parentRoutes.find((parent) => {\r\n return pathNodes.slice(0, i).join('/') === parent.rawRoute\r\n })\r\n\r\n if (parent) {\r\n // Make sure children exits in parent\r\n parent.children = parent.children || []\r\n // Append to parent's children\r\n parentRoutes = parent.children\r\n }\r\n\r\n const exits = parentRoutes.some((parent) => {\r\n return pathNodes.slice(0, i + 1).join('/') === parent.rawRoute\r\n })\r\n if (!exits)\r\n parentRoutes.push(route)\r\n }\r\n })\r\n\r\n // sort by dynamic routes\r\n let finalRoutes = prepareRoutes(routes, ctx.options)\r\n\r\n finalRoutes = (await ctx.options.onRoutesGenerated?.(finalRoutes)) || finalRoutes\r\n\r\n return finalRoutes\r\n}\r\n\r\nasync function resolveReactRoutes(ctx: PageContext) {\r\n const finalRoutes = await computeReactRoutes(ctx)\r\n let client = generateClientCode(finalRoutes, ctx.options)\r\n client = (await ctx.options.onClientGenerated?.(client)) || client\r\n return client\r\n}\r\n\r\nexport function reactResolver(): PageResolver {\r\n return {\r\n resolveModuleIds() {\r\n return ['~react-pages', 'virtual:generated-pages-react']\r\n },\r\n resolveExtensions() {\r\n return ['tsx', 'jsx', 'ts', 'js']\r\n },\r\n async resolveRoutes(ctx) {\r\n return resolveReactRoutes(ctx)\r\n },\r\n async getComputedRoutes(ctx) {\r\n return computeReactRoutes(ctx)\r\n },\r\n stringify: {\r\n component: path => `React.createElement(${path})`,\r\n dynamicImport: path => `React.lazy(() => import(\"${path}\"))`,\r\n final: code => `import React from \"react\";\\n${code}`,\r\n },\r\n }\r\n}\r\n","i