UNPKG

unplugin-vue-router

Version:
1,491 lines (1,433 loc) 60.6 kB
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3; var _class4; var _class5; var _class6; var _chunk6INN7JETcjs = require('./chunk-6INN7JET.cjs'); // src/index.ts var _unplugin = require('unplugin'); // src/core/treeNodeValue.ts var EDITS_OVERRIDE_NAME = "@@edits"; var _TreeNodeValueBase = (_class = class { /** * flag based on the type of the segment */ /** * segment as defined by the file structure e.g. keeps the `index` name, `(group-name)` */ /** * transformed version of the segment into a vue-router path. e.g. `'index'` becomes `''` and `[param]` becomes * `:param`, `prefix-[param]-end` becomes `prefix-:param-end`. */ /** * Array of sub segments. This is usually one single elements but can have more for paths like `prefix-[param]-end.vue` */ /** * Overrides defined by each file. The map is necessary to handle named views. */ __init() {this._overrides = /* @__PURE__ */ new Map()} // TODO: cache the overrides generation /** * View name (Vue Router feature) mapped to their corresponding file. By default, the view name is `default` unless * specified with a `@` e.g. `index@aux.vue` will have a view name of `aux`. */ __init2() {this.components = /* @__PURE__ */ new Map()} constructor(rawSegment, parent, pathSegment = rawSegment, subSegments = [pathSegment]) {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this); this._type = 0; this.rawSegment = rawSegment; this.pathSegment = pathSegment; this.subSegments = subSegments; this.parent = parent; } /** * fullPath of the node based on parent nodes */ get path() { const parentPath = _optionalChain([this, 'access', _ => _.parent, 'optionalAccess', _2 => _2.path]); const pathSegment = _nullishCoalesce(this.overrides.path, () => ( this.pathSegment)); return (!parentPath || parentPath === "/") && pathSegment === "" ? "/" : _chunk6INN7JETcjs.joinPath.call(void 0, parentPath || "", pathSegment); } toString() { return this.pathSegment || "<index>"; } isParam() { return !!(this._type & 2 /* param */); } isStatic() { return this._type === 0 /* static */; } isGroup() { return this._type === 1 /* group */; } get overrides() { return [...this._overrides.entries()].sort( ([nameA], [nameB]) => nameA === nameB ? 0 : ( // EDITS_OVERRIDE_NAME should always be last nameA !== EDITS_OVERRIDE_NAME && (nameA < nameB || nameB === EDITS_OVERRIDE_NAME) ? -1 : 1 ) ).reduce((acc, [_path, routeBlock]) => { return _chunk6INN7JETcjs.mergeRouteRecordOverride.call(void 0, acc, routeBlock); }, {}); } setOverride(filePath, routeBlock) { this._overrides.set(filePath, routeBlock || {}); } /** * Remove all overrides for a given key. * * @param key - key to remove from the override, e.g. path, name, etc */ removeOverride(key) { for (const [_filePath, routeBlock] of this._overrides) { delete routeBlock[key]; } } /** * Add an override to the current node by merging with the existing values. * * @param filePath - The file path to add to the override * @param routeBlock - The route block to add to the override */ mergeOverride(filePath, routeBlock) { const existing = this._overrides.get(filePath) || {}; this._overrides.set( filePath, _chunk6INN7JETcjs.mergeRouteRecordOverride.call(void 0, existing, routeBlock) ); } /** * Add an override to the current node using the special file path `@@edits` that makes this added at build time. * * @param routeBlock - The route block to add to the override */ addEditOverride(routeBlock) { return this.mergeOverride(EDITS_OVERRIDE_NAME, routeBlock); } /** * Set a specific value in the _edits_ override. * * @param key - key to set in the override, e.g. path, name, etc * @param value - value to set in the override */ setEditOverride(key, value) { if (!this._overrides.has(EDITS_OVERRIDE_NAME)) { this._overrides.set(EDITS_OVERRIDE_NAME, {}); } const existing = this._overrides.get(EDITS_OVERRIDE_NAME); existing[key] = value; } }, _class); var TreeNodeValueStatic = (_class2 = class extends _TreeNodeValueBase { __init3() {this._type = 0} /* static */ constructor(rawSegment, parent, pathSegment = rawSegment) { super(rawSegment, parent, pathSegment);_class2.prototype.__init3.call(this);; } }, _class2); var TreeNodeValueGroup = (_class3 = class extends _TreeNodeValueBase { __init4() {this._type = 1} /* group */ constructor(rawSegment, parent, pathSegment, groupName) { super(rawSegment, parent, pathSegment);_class3.prototype.__init4.call(this);; this.groupName = groupName; } }, _class3); var TreeNodeValueParam = (_class4 = class extends _TreeNodeValueBase { __init5() {this._type = 2} /* param */ constructor(rawSegment, parent, params, pathSegment, subSegments) { super(rawSegment, parent, pathSegment, subSegments);_class4.prototype.__init5.call(this);; this.params = params; } }, _class4); function resolveTreeNodeValueOptions(options) { return { format: "file", dotNesting: true, ...options }; } function createTreeNodeValue(segment, parent, opts = {}) { if (!segment || segment === "index") { return new TreeNodeValueStatic(segment, parent, ""); } const options = resolveTreeNodeValueOptions(opts); const openingPar = segment.indexOf("("); if (options.format === "file" && openingPar >= 0) { let groupName; const closingPar = segment.lastIndexOf(")"); if (closingPar < 0 || closingPar < openingPar) { _chunk6INN7JETcjs.warn.call(void 0, `Segment "${segment}" is missing the closing ")". It will be treated as a static segment.` ); return new TreeNodeValueStatic(segment, parent, segment); } groupName = segment.slice(openingPar + 1, closingPar); const before = segment.slice(0, openingPar); const after = segment.slice(closingPar + 1); if (!before && !after) { return new TreeNodeValueGroup(segment, parent, "", groupName); } } const [pathSegment, params, subSegments] = options.format === "path" ? parseRawPathSegment(segment) : ( // by default, we use the file format parseFileSegment(segment, options) ); if (params.length) { return new TreeNodeValueParam( segment, parent, params, pathSegment, subSegments ); } return new TreeNodeValueStatic(segment, parent, pathSegment); } var IS_VARIABLE_CHAR_RE = /[0-9a-zA-Z_]/; function parseFileSegment(segment, { dotNesting = true } = {}) { let buffer = ""; let state = 0 /* static */; const params = []; let pathSegment = ""; const subSegments = []; let currentTreeRouteParam = createEmptyRouteParam(); let pos = 0; let c; function consumeBuffer() { if (state === 0 /* static */) { pathSegment += buffer; subSegments.push(buffer); } else if (state === 3 /* modifier */) { currentTreeRouteParam.paramName = buffer; currentTreeRouteParam.modifier = currentTreeRouteParam.optional ? currentTreeRouteParam.repeatable ? "*" : "?" : currentTreeRouteParam.repeatable ? "+" : ""; buffer = ""; pathSegment += `:${currentTreeRouteParam.paramName}${currentTreeRouteParam.isSplat ? "(.*)" : ( // Only append () if necessary pos < segment.length - 1 && IS_VARIABLE_CHAR_RE.test(segment[pos + 1]) ? "()" : ( // allow routes like /[id]_suffix to make suffix static and not part of the param "" ) )}${currentTreeRouteParam.modifier}`; params.push(currentTreeRouteParam); subSegments.push(currentTreeRouteParam); currentTreeRouteParam = createEmptyRouteParam(); } buffer = ""; } for (pos = 0; pos < segment.length; pos++) { c = segment[pos]; if (state === 0 /* static */) { if (c === "[") { consumeBuffer(); state = 1 /* paramOptional */; } else { buffer += dotNesting && c === "." ? "/" : c; } } else if (state === 1 /* paramOptional */) { if (c === "[") { currentTreeRouteParam.optional = true; } else if (c === ".") { currentTreeRouteParam.isSplat = true; pos += 2; } else { buffer += c; } state = 2 /* param */; } else if (state === 2 /* param */) { if (c === "]") { if (currentTreeRouteParam.optional) { pos++; } state = 3 /* modifier */; } else if (c === ".") { currentTreeRouteParam.isSplat = true; pos += 2; } else { buffer += c; } } else if (state === 3 /* modifier */) { if (c === "+") { currentTreeRouteParam.repeatable = true; } else { pos--; } consumeBuffer(); state = 0 /* static */; } } if (state === 2 /* param */ || state === 1 /* paramOptional */) { throw new Error(`Invalid segment: "${segment}"`); } if (buffer) { consumeBuffer(); } return [pathSegment, params, subSegments]; } var IS_MODIFIER_RE = /[+*?]/; function parseRawPathSegment(segment) { let buffer = ""; let state = 0 /* static */; const params = []; const subSegments = []; let currentTreeRouteParam = createEmptyRouteParam(); let pos = 0; let c; function consumeBuffer() { if (state === 0 /* static */) { subSegments.push(buffer); } else if (state === 1 /* param */ || state === 2 /* regexp */ || state === 3 /* modifier */) { subSegments.push(currentTreeRouteParam); params.push(currentTreeRouteParam); currentTreeRouteParam = createEmptyRouteParam(); } buffer = ""; } for (pos = 0; pos < segment.length; pos++) { c = segment[pos]; if (c === "\\") { pos++; buffer += segment[pos]; continue; } if (state === 0 /* static */) { if (c === ":") { consumeBuffer(); state = 1 /* param */; } else { buffer += c; } } else if (state === 1 /* param */) { if (c === "(") { currentTreeRouteParam.paramName = buffer; buffer = ""; state = 2 /* regexp */; } else if (IS_MODIFIER_RE.test(c)) { currentTreeRouteParam.modifier = c; currentTreeRouteParam.optional = c === "?" || c === "*"; currentTreeRouteParam.repeatable = c === "+" || c === "*"; consumeBuffer(); state = 0 /* static */; } else if (IS_VARIABLE_CHAR_RE.test(c)) { buffer += c; currentTreeRouteParam.paramName = buffer; } else { currentTreeRouteParam.paramName = buffer; consumeBuffer(); pos--; state = 0 /* static */; } } else if (state === 2 /* regexp */) { if (c === ")") { if (buffer === ".*") { currentTreeRouteParam.isSplat = true; } state = 3 /* modifier */; } else { buffer += c; } } else if (state === 3 /* modifier */) { if (IS_MODIFIER_RE.test(c)) { currentTreeRouteParam.modifier = c; currentTreeRouteParam.optional = c === "?" || c === "*"; currentTreeRouteParam.repeatable = c === "+" || c === "*"; } else { pos--; } consumeBuffer(); state = 0 /* static */; } } if (state === 2 /* regexp */) { throw new Error(`Invalid segment: "${segment}"`); } if (buffer || // an empty finished regexp enters this state but must also be consumed state === 3 /* modifier */) { consumeBuffer(); } return [ // here the segment is already a valid path segment segment, params, subSegments ]; } function createEmptyRouteParam() { return { paramName: "", modifier: "", optional: false, repeatable: false, isSplat: false }; } // src/core/tree.ts var TreeNode = (_class5 = class _TreeNode { /** * value of the node */ /** * children of the node */ __init6() {this.children = /* @__PURE__ */ new Map()} /** * Parent node. */ /** * Plugin options taken into account by the tree. */ // FIXME: refactor this code. It currently helps to keep track if a page has at least one component with `definePage()` but it doesn't tell which. It should keep track of which one while still caching the result per file. /** * Should this page import the page info */ __init7() {this.hasDefinePage = false} /** * Creates a new tree node. * * @param options - TreeNodeOptions shared by all nodes * @param pathSegment - path segment of this node e.g. `users` or `:id` * @param parent */ constructor(options, pathSegment, parent) {;_class5.prototype.__init6.call(this);_class5.prototype.__init7.call(this); this.options = options; this.parent = parent; this.value = createTreeNodeValue( pathSegment, _optionalChain([parent, 'optionalAccess', _3 => _3.value]), options.treeNodeOptions || options.pathParser ); } /** * Adds a path to the tree. `path` cannot start with a `/`. * * @param path - path segment to insert. **It shouldn't contain the file extension** * @param filePath - file path, must be a file (not a folder) */ insert(path, filePath) { const { tail, segment, viewName } = splitFilePath(path); if (!this.children.has(segment)) { this.children.set(segment, new _TreeNode(this.options, segment, this)); } const child = this.children.get(segment); if (!tail) { child.value.components.set(viewName, filePath); } else { return child.insert(tail, filePath); } return child; } /** * Adds a path that has already been parsed to the tree. `path` cannot start with a `/`. This method is similar to * `insert` but the path argument should be already parsed. e.g. `users/:id` for a file named `users/[id].vue`. * * @param path - path segment to insert, already parsed (e.g. users/:id) * @param filePath - file path, defaults to path for convenience and testing */ insertParsedPath(path, filePath = path) { const isComponent = true; const node = new _TreeNode( { ...this.options, // force the format to raw treeNodeOptions: { ...this.options.pathParser, format: "path" } }, path, this ); this.children.set(path, node); if (isComponent) { node.value.components.set("default", filePath); } return node; } /** * Saves a custom route block for a specific file path. The file path is used as a key. Some special file paths will * have a lower or higher priority. * * @param filePath - file path where the custom block is located * @param routeBlock - custom block to set */ setCustomRouteBlock(filePath, routeBlock) { this.value.setOverride(filePath, routeBlock); } getSortedChildren() { return Array.from(this.children.values()).sort( (a, b) => a.path.localeCompare(b.path) ); } /** * Delete and detach itself from the tree. */ delete() { if (!this.parent) { throw new Error("Cannot delete the root node."); } this.parent.children.delete(this.value.rawSegment); this.parent = void 0; } /** * Remove a route from the tree. The path shouldn't start with a `/` but it can be a nested one. e.g. `foo/bar`. * The `path` should be relative to the page folder. * * @param path - path segment of the file */ remove(path) { const { tail, segment, viewName } = splitFilePath(path); const child = this.children.get(segment); if (!child) { throw new Error( `Cannot Delete "${path}". "${segment}" not found at "${this.path}".` ); } if (tail) { child.remove(tail); if (child.children.size === 0 && child.value.components.size === 0) { this.children.delete(segment); } } else { child.value.components.delete(viewName); if (child.children.size === 0 && child.value.components.size === 0) { this.children.delete(segment); } } } /** * Returns the route path of the node without parent paths. If the path was overridden, it returns the override. */ get path() { return _nullishCoalesce(this.value.overrides.path, () => ( (_optionalChain([this, 'access', _4 => _4.parent, 'optionalAccess', _5 => _5.isRoot, 'call', _6 => _6()]) ? "/" : "") + this.value.pathSegment)); } /** * Returns the route path of the node including parent paths. */ get fullPath() { return _nullishCoalesce(this.value.overrides.path, () => ( this.value.path)); } /** * Returns the route name of the node. If the name was overridden, it returns the override. */ get name() { return this.value.overrides.name || this.options.getRouteName(this); } /** * Returns the meta property as an object. */ get metaAsObject() { return { ...this.value.overrides.meta }; } /** * Returns the JSON string of the meta object of the node. If the meta was overridden, it returns the override. If * there is no override, it returns an empty string. */ get meta() { const overrideMeta = this.metaAsObject; return Object.keys(overrideMeta).length > 0 ? JSON.stringify(overrideMeta, null, 2) : ""; } get params() { const params = this.value.isParam() ? [...this.value.params] : []; let node = this.parent; while (node) { if (node.value.isParam()) { params.unshift(...node.value.params); } node = node.parent; } return params; } /** * Returns wether this tree node is the root node of the tree. * * @returns true if the node is the root node */ isRoot() { return !this.parent && this.value.path === "/" && !this.value.components.size; } toString() { return `${this.value}${// either we have multiple names this.value.components.size > 1 || // or we have one name and it's not default this.value.components.size === 1 && !this.value.components.get("default") ? ` \u2388(${Array.from(this.value.components.keys()).join(", ")})` : ""}${this.hasDefinePage ? " \u2691 definePage()" : ""}`; } }, _class5); var PrefixTree = (_class6 = class extends TreeNode { __init8() {this.map = /* @__PURE__ */ new Map()} constructor(options) { super(options, "");_class6.prototype.__init8.call(this);; } insert(path, filePath) { const node = super.insert(path, filePath); this.map.set(filePath, node); return node; } /** * Returns the tree node of the given file path. * * @param filePath - file path of the tree node to get */ getChild(filePath) { return this.map.get(filePath); } /** * Removes the tree node of the given file path. * * @param filePath - file path of the tree node to remove */ removeChild(filePath) { if (this.map.has(filePath)) { this.map.get(filePath).delete(); this.map.delete(filePath); } } }, _class6); function splitFilePath(filePath) { const slashPos = filePath.indexOf("/"); let head = slashPos < 0 ? filePath : filePath.slice(0, slashPos); const tail = slashPos < 0 ? "" : filePath.slice(slashPos + 1); let segment = head; let viewName = "default"; const namedSeparatorPos = segment.indexOf("@"); if (namedSeparatorPos > 0) { viewName = segment.slice(namedSeparatorPos + 1); segment = segment.slice(0, namedSeparatorPos); } return { segment, tail, viewName }; } // src/core/context.ts var _fs = require('fs'); // src/codegen/generateRouteParams.ts function generateRouteParams(node, isRaw) { const nodeParams = node.params; return nodeParams.length > 0 ? `{ ${nodeParams.map( (param) => `${param.paramName}${param.optional ? "?" : ""}: ` + (param.modifier === "+" ? `ParamValueOneOrMore<${isRaw}>` : param.modifier === "*" ? `ParamValueZeroOrMore<${isRaw}>` : param.modifier === "?" ? `ParamValueZeroOrOne<${isRaw}>` : `ParamValue<${isRaw}>`) ).join(", ")} }` : ( // no params allowed "Record<never, never>" ); } // src/codegen/generateRouteMap.ts function generateRouteNamedMap(node) { if (node.isRoot()) { return `export interface RouteNamedMap { ${node.getSortedChildren().map(generateRouteNamedMap).join("")}}`; } return ( // if the node has a filePath, it's a component, it has a routeName and it should be referenced in the RouteNamedMap // otherwise it should be skipped to avoid navigating to a route that doesn't render anything (node.value.components.size ? ` '${node.name}': ${generateRouteRecordInfo(node)}, ` : "") + (node.children.size > 0 ? node.getSortedChildren().map(generateRouteNamedMap).join("\n") : "") ); } function generateRouteRecordInfo(node) { return `RouteRecordInfo<'${node.name}', '${node.fullPath}', ${generateRouteParams(node, true)}, ${generateRouteParams(node, false)}>`; } // src/core/moduleConstants.ts var MODULE_VUE_ROUTER_AUTO = "vue-router/auto"; var MODULE_ROUTES_PATH = `${MODULE_VUE_ROUTER_AUTO}-routes`; var time = Date.now(); var ROUTES_LAST_LOAD_TIME = { get value() { return time; }, update(when = Date.now()) { time = when; } }; var VIRTUAL_PREFIX = "/__"; var ROUTE_BLOCK_ID = `${VIRTUAL_PREFIX}/vue-router/auto/route-block`; function getVirtualId(id) { return id.startsWith(VIRTUAL_PREFIX) ? id.slice(VIRTUAL_PREFIX.length) : null; } var routeBlockQueryRE = /\?vue&type=route/; function asVirtualId(id) { return VIRTUAL_PREFIX + id; } // src/codegen/generateRouteRecords.ts function generateRouteRecord(node, options, importsMap, indent = 0) { if (node.isRoot()) { return `[ ${node.getSortedChildren().map((child) => generateRouteRecord(child, options, importsMap, indent + 1)).join(",\n")} ]`; } const definePageDataList = []; if (node.hasDefinePage) { for (const [name, filePath] of node.value.components) { const pageDataImport = `_definePage_${name}_${importsMap.size}`; definePageDataList.push(pageDataImport); importsMap.addDefault( // TODO: apply the language used in the sfc `${filePath}?definePage&vue&lang.tsx`, pageDataImport ); } if (definePageDataList.length > 0) { indent++; } } const startIndent = " ".repeat(indent * 2); const indentStr = " ".repeat((indent + 1) * 2); const overrides = node.value.overrides; const routeRecord = `${startIndent}{ ${indentStr}path: '${node.path}', ${indentStr}${node.value.components.size ? `name: '${node.name}',` : `/* internal name: '${node.name}' */`} ${// component indentStr}${node.value.components.size ? generateRouteRecordComponent( node, indentStr, options.importMode, importsMap ) : "/* no component */"} ${overrides.props != null ? indentStr + `props: ${overrides.props}, ` : ""}${overrides.alias != null ? indentStr + `alias: ${JSON.stringify(overrides.alias)}, ` : ""}${// children indentStr}${node.children.size > 0 ? `children: [ ${node.getSortedChildren().map((child) => generateRouteRecord(child, options, importsMap, indent + 2)).join(",\n")} ${indentStr}],` : "/* no children */"}${formatMeta(node, indentStr)} ${startIndent}}`; if (definePageDataList.length > 0) { const mergeCallIndent = startIndent.slice(2); importsMap.add("unplugin-vue-router/runtime", "_mergeRouteRecord"); return `${mergeCallIndent}_mergeRouteRecord( ${routeRecord}, ${definePageDataList.map((s) => startIndent + s).join(",\n")} ${mergeCallIndent})`; } return routeRecord; } function generateRouteRecordComponent(node, indentStr, importMode, importsMap) { const files = Array.from(node.value.components); const isDefaultExport = files.length === 1 && files[0][0] === "default"; return isDefaultExport ? `component: ${generatePageImport(files[0][1], importMode, importsMap)},` : ( // files has at least one entry `components: { ${files.map( ([key, path]) => `${indentStr + " "}'${key}': ${generatePageImport( path, importMode, importsMap )}` ).join(",\n")} ${indentStr}},` ); } function generatePageImport(filepath, importMode, importsMap) { const mode = typeof importMode === "function" ? importMode(filepath) : importMode; if (mode === "async") { return `() => import('${filepath}')`; } const existingEntry = importsMap.getImportList(filepath).find((entry) => entry.name === "default"); if (existingEntry) { return existingEntry.as; } const importName = `_page_${importsMap.size}`; importsMap.addDefault(filepath, importName); return importName; } function formatMeta(node, indent) { const meta = node.meta; const formatted = meta && meta.split("\n").map((line) => indent + line).join("\n"); return formatted ? "\n" + indent + "meta: " + formatted.trimStart() : ""; } // src/core/context.ts var _fastglob = require('fast-glob'); var _fastglob2 = _interopRequireDefault(_fastglob); var _pathe = require('pathe'); // src/core/customBlock.ts var _compilersfc = require('@vue/compiler-sfc'); var _json5 = require('json5'); var _json52 = _interopRequireDefault(_json5); var _yaml = require('yaml'); function getRouteBlock(path, content, options) { const parsedSFC = _compilersfc.parse.call(void 0, content, { pad: "space" }).descriptor; const blockStr = _optionalChain([parsedSFC, 'optionalAccess', _7 => _7.customBlocks, 'access', _8 => _8.find, 'call', _9 => _9((b) => b.type === "route")]); if (!blockStr) return; let result = parseCustomBlock(blockStr, path, options); if (result) { if (result.path != null && !result.path.startsWith("/")) { _chunk6INN7JETcjs.warn.call(void 0, `Overridden path must start with "/". Found in "${path}".`); } } return result; } function parseCustomBlock(block, filePath, options) { const lang = _nullishCoalesce(block.lang, () => ( options.routeBlockLang)); if (lang === "json5") { try { return _json52.default.parse(block.content); } catch (err) { _chunk6INN7JETcjs.warn.call(void 0, `Invalid JSON5 format of <${block.type}> content in ${filePath} ${err.message}` ); } } else if (lang === "json") { try { return JSON.parse(block.content); } catch (err) { _chunk6INN7JETcjs.warn.call(void 0, `Invalid JSON format of <${block.type}> content in ${filePath} ${err.message}` ); } } else if (lang === "yaml" || lang === "yml") { try { return _yaml.parse.call(void 0, block.content); } catch (err) { _chunk6INN7JETcjs.warn.call(void 0, `Invalid YAML format of <${block.type}> content in ${filePath} ${err.message}` ); } } else { _chunk6INN7JETcjs.warn.call(void 0, `Language "${lang}" for <${block.type}> is not supported. Supported languages are: json5, json, yaml, yml. Found in in ${filePath}.` ); } } // src/core/RoutesFolderWatcher.ts var _chokidar = require('chokidar'); var _micromatch = require('micromatch'); var _micromatch2 = _interopRequireDefault(_micromatch); var RoutesFolderWatcher = class { constructor(folderOptions) { this.src = folderOptions.src; this.path = folderOptions.path; this.exclude = folderOptions.exclude; this.extensions = folderOptions.extensions; this.filePatterns = folderOptions.filePatterns; this.watcher = _chokidar.watch.call(void 0, ".", { cwd: this.src, ignoreInitial: true, ignorePermissionErrors: true, ignored: (filepath, stats) => { if (_optionalChain([stats, 'optionalAccess', _10 => _10.isDirectory, 'call', _11 => _11()])) { return false; } const isMatch = _micromatch2.default.isMatch(filepath, this.filePatterns, { cwd: this.src, ignore: this.exclude }); return !isMatch; } // TODO: allow user options }); } on(event, handler) { this.watcher.on(event, (filePath) => { if (this.extensions.every((extension) => !filePath.endsWith(extension))) { return; } filePath = _pathe.resolve.call(void 0, this.src, filePath); handler({ filePath, routePath: _chunk6INN7JETcjs.asRoutePath.call(void 0, { src: this.src, path: this.path, extensions: this.extensions }, filePath ) }); }); return this; } close() { this.watcher.close(); } }; function resolveFolderOptions(globalOptions, folderOptions) { const extensions = overrideOption( globalOptions.extensions, folderOptions.extensions ); const filePatterns = overrideOption( globalOptions.filePatterns, folderOptions.filePatterns ); return { src: folderOptions.src, pattern: _chunk6INN7JETcjs.appendExtensionListToPattern.call(void 0, filePatterns, // also override the extensions if the folder has a custom extensions extensions ), path: folderOptions.path || "", extensions, filePatterns, exclude: overrideOption(globalOptions.exclude, folderOptions.exclude).map( (p) => p.startsWith("**") ? p : _pathe.resolve.call(void 0, p) ) }; } function overrideOption(existing, newValue) { const asArray = typeof existing === "string" ? [existing] : existing; if (typeof newValue === "function") { return newValue(asArray); } if (typeof newValue !== "undefined") { return typeof newValue === "string" ? [newValue] : newValue; } return asArray; } // src/utils/index.ts var ts = String.raw; // src/codegen/generateDTS.ts function generateDTS({ routesModule, routeNamedMap }) { return ts` /* eslint-disable */ /* prettier-ignore */ // @ts-nocheck // Generated by unplugin-vue-router. ‼️ DO NOT MODIFY THIS FILE ‼️ // It's recommended to commit this file. // Make sure to add this file to your tsconfig.json file as an "includes" or "files" entry. declare module '${routesModule}' { import type { RouteRecordInfo, ParamValue, ParamValueOneOrMore, ParamValueZeroOrMore, ParamValueZeroOrOne, } from 'vue-router' /** * Route name map generated by unplugin-vue-router */ ${routeNamedMap.split("\n").filter((line) => line.length !== 0).map((line) => " " + line).join("\n")} } `.trimStart(); } // src/codegen/vueRouterModule.ts function generateVueRouterProxy(_routesModule, _options, { addPiniaColada }) { return ts` import { createRouter as _createRouter } from 'vue-router' export * from 'vue-router' export { definePage } from 'unplugin-vue-router/runtime' export { DataLoaderPlugin, NavigationResult, } from 'unplugin-vue-router/data-loaders' export * from 'unplugin-vue-router/data-loaders/basic' ${addPiniaColada ? "export * from 'unplugin-vue-router/data-loaders/pinia-colada'" : ""} export function createRouter(options) { const { extendRoutes, routes } = options if (extendRoutes) { console.warn('"extendRoutes()" is deprecated, please modify the routes directly. See https://uvr.esm.is/guide/extending-routes.html#extending-routes-at-runtime for an alternative.') } // use Object.assign for better browser support const router = _createRouter(Object.assign( options, { routes: typeof extendRoutes === 'function' ? (extendRoutes(routes) || routes) : routes }, )) return router } `.trimStart(); } // src/core/definePage.ts var _common = require('@vue-macros/common'); var _astwalkerscope = require('ast-walker-scope'); var _mlly = require('mlly'); var MACRO_DEFINE_PAGE = "definePage"; var MACRO_DEFINE_PAGE_QUERY = /[?&]definePage\b/; function definePageTransform({ code, id }) { const isExtractingDefinePage = MACRO_DEFINE_PAGE_QUERY.test(id); if (!code.includes(MACRO_DEFINE_PAGE)) { return isExtractingDefinePage ? "export default {}" : void 0; } const sfc = _common.parseSFC.call(void 0, code, id); if (!sfc.scriptSetup) return; const { scriptSetup, getSetupAst } = sfc; const setupAst = getSetupAst(); const definePageNodes = (_optionalChain([setupAst, 'optionalAccess', _12 => _12.body]) || []).map((node) => { if (node.type === "ExpressionStatement") node = node.expression; return _common.isCallOf.call(void 0, node, MACRO_DEFINE_PAGE) ? node : null; }).filter((node) => !!node); if (!definePageNodes.length) { return isExtractingDefinePage ? ( // e.g. index.vue?definePage that contains a commented `definePage() "export default {}" ) : ( // e.g. index.vue that contains a commented `definePage() null ); } else if (definePageNodes.length > 1) { throw new SyntaxError(`duplicate definePage() call`); } const definePageNode = definePageNodes[0]; const setupOffset = scriptSetup.loc.start.offset; if (isExtractingDefinePage) { const s = new (0, _common.MagicString)(code); const routeRecord = definePageNode.arguments[0]; if (!routeRecord) { throw new SyntaxError( `[${id}]: definePage() expects an object expression as its only argument` ); } const scriptBindings = _optionalChain([setupAst, 'optionalAccess', _13 => _13.body]) ? getIdentifiers(setupAst.body) : []; _common.checkInvalidScopeReference.call(void 0, routeRecord, MACRO_DEFINE_PAGE, scriptBindings); s.remove(setupOffset + routeRecord.end, code.length); s.remove(0, setupOffset + routeRecord.start); s.prepend(`export default `); const staticImports = _mlly.findStaticImports.call(void 0, code); const usedIds = /* @__PURE__ */ new Set(); const localIds = /* @__PURE__ */ new Set(); _astwalkerscope.walkAST.call(void 0, routeRecord, { enter(node) { if (_optionalChain([this, 'access', _14 => _14.parent, 'optionalAccess', _15 => _15.type]) === "ObjectProperty" && this.parent.key === node && // still track computed keys [a + b]: 1 !this.parent.computed && node.type === "Identifier") { this.skip(); } else if ( // filter out things like 'log' in console.log _optionalChain([this, 'access', _16 => _16.parent, 'optionalAccess', _17 => _17.type]) === "MemberExpression" && this.parent.property === node && !this.parent.computed && node.type === "Identifier" ) { this.skip(); } else if (node.type === "TSTypeAnnotation") { this.skip(); } else if (node.type === "Identifier" && !localIds.has(node.name)) { usedIds.add(node.name); } else if ("scopeIds" in node && node.scopeIds instanceof Set) { for (const id2 of node.scopeIds) { localIds.add(id2); } } }, leave(node) { if ("scopeIds" in node && node.scopeIds instanceof Set) { for (const id2 of node.scopeIds) { localIds.delete(id2); } } } }); for (const imp of staticImports) { const importCode = generateFilteredImportStatement( _mlly.parseStaticImport.call(void 0, imp), usedIds ); if (importCode) { s.prepend(importCode + "\n"); } } return _common.generateTransform.call(void 0, s, id); } else { const s = new (0, _common.MagicString)(code); s.remove( setupOffset + definePageNode.start, setupOffset + definePageNode.end ); return _common.generateTransform.call(void 0, s, id); } } function extractDefinePageNameAndPath(sfcCode, id) { if (!sfcCode.includes(MACRO_DEFINE_PAGE)) return; const sfc = _common.parseSFC.call(void 0, sfcCode, id); if (!sfc.scriptSetup) return; const { getSetupAst } = sfc; const setupAst = getSetupAst(); const definePageNodes = (_nullishCoalesce(_optionalChain([setupAst, 'optionalAccess', _18 => _18.body]), () => ( []))).map((node) => { if (node.type === "ExpressionStatement") node = node.expression; return _common.isCallOf.call(void 0, node, MACRO_DEFINE_PAGE) ? node : null; }).filter((node) => !!node); if (!definePageNodes.length) { return; } else if (definePageNodes.length > 1) { throw new SyntaxError(`duplicate definePage() call`); } const definePageNode = definePageNodes[0]; const routeRecord = definePageNode.arguments[0]; if (!routeRecord) { throw new SyntaxError( `[${id}]: definePage() expects an object expression as its only argument` ); } if (routeRecord.type !== "ObjectExpression") { throw new SyntaxError( `[${id}]: definePage() expects an object expression as its only argument` ); } const routeInfo = {}; for (const prop of routeRecord.properties) { if (prop.type === "ObjectProperty" && prop.key.type === "Identifier") { if (prop.key.name === "name") { if (prop.value.type !== "StringLiteral") { _chunk6INN7JETcjs.warn.call(void 0, `route name must be a string literal. Found in "${id}".`); } else { routeInfo.name = prop.value.value; } } else if (prop.key.name === "path") { if (prop.value.type !== "StringLiteral") { _chunk6INN7JETcjs.warn.call(void 0, `route path must be a string literal. Found in "${id}".`); } else { routeInfo.path = prop.value.value; } } } } return routeInfo; } var getIdentifiers = (stmts) => { let ids = []; _astwalkerscope.walkAST.call(void 0, { type: "Program", body: stmts, directives: [], sourceType: "module" }, { enter(node) { if (node.type === "BlockStatement") { this.skip(); } }, leave(node) { if (node.type !== "Program") return; ids = Object.keys(this.scope); } } ); return ids; }; function generateFilteredImportStatement(parsedImports, usedIds) { if (!parsedImports || usedIds.size < 1) return null; const { namedImports, defaultImport, namespacedImport } = parsedImports; if (namespacedImport && usedIds.has(namespacedImport)) { return `import * as ${namespacedImport} from '${parsedImports.specifier}'`; } let importListCode = ""; if (defaultImport && usedIds.has(defaultImport)) { importListCode += defaultImport; } let namedImportListCode = ""; for (const importName in namedImports) { if (usedIds.has(importName)) { namedImportListCode += namedImportListCode ? `, ` : ""; namedImportListCode += importName === namedImports[importName] ? importName : `${importName} as ${namedImports[importName]}`; } } importListCode += importListCode && namedImportListCode ? ", " : ""; importListCode += namedImportListCode ? `{${namedImportListCode}}` : ""; if (!importListCode) return null; return `import ${importListCode} from '${parsedImports.specifier}'`; } // src/core/extendRoutes.ts var EditableTreeNode = class _EditableTreeNode { // private _parent?: EditableTreeNode constructor(node) { this.node = node; } /** * Remove and detach the current route node from the tree. Subsequently, its children will be removed as well. */ delete() { return this.node.delete(); } /** * Inserts a new route as a child of this route. This route cannot use `definePage()`. If it was meant to be included, * add it to the `routesFolder` option. * * @param path - path segment to insert. Note this is relative to the current route. **It shouldn't start with `/`**. If it does, it will be added to the root of the tree. * @param filePath - file path * @returns the new editable route node */ insert(path, filePath) { let addBackLeadingSlash = false; if (path.startsWith("/")) { path = path.slice(1); addBackLeadingSlash = !this.node.isRoot(); } const node = this.node.insertParsedPath(path, filePath); const editable = new _EditableTreeNode(node); if (addBackLeadingSlash) { editable.path = "/" + node.path; } return editable; } /** * Get an editable version of the parent node if it exists. */ get parent() { return this.node.parent && new _EditableTreeNode(this.node.parent); } /** * Return a Map of the files associated to the current route. The key of the map represents the name of the view (Vue * Router feature) while the value is the **resolved** file path. * By default, the name of the view is `default`. */ get components() { return this.node.value.components; } /** * Alias for `route.components.get('default')`. */ get component() { return this.node.value.components.get("default"); } /** * Name of the route. Note that **all routes are named** but when the final `routes` array is generated, routes * without a `component` will not include their `name` property to avoid accidentally navigating to them and display * nothing. * @see {@link isPassThrough} */ get name() { return this.node.name; } /** * Override the name of the route. */ set name(name) { this.node.value.addEditOverride({ name }); } /** * Whether the route is a pass-through route. A pass-through route is a route that does not have a component and is * used to group other routes under the same prefix `path` and/or `meta` properties. */ get isPassThrough() { return this.node.value.components.size === 0; } /** * Meta property of the route as an object. Note this property is readonly and will be serialized as JSON. It won't contain the meta properties defined with `definePage()` as it could contain expressions **but it does contain the meta properties defined with `<route>` blocks**. */ get meta() { return this.node.metaAsObject; } /** * Override the meta property of the route. This will discard any other meta property defined with `<route>` blocks or * through other means. If you want to keep the existing meta properties, use `addToMeta`. * @see {@link addToMeta} */ set meta(meta) { this.node.value.removeOverride("meta"); this.node.value.setEditOverride("meta", meta); } /** * Add meta properties to the route keeping the existing ones. The passed object will be deeply merged with the * existing meta object if any. Note that the meta property is later on serialized as JSON so you can't pass functions * or any other non-serializable value. */ addToMeta(meta) { this.node.value.addEditOverride({ meta }); } /** * Path of the route without parent paths. */ get path() { return this.node.path; } /** * Override the path of the route. You must ensure `params` match with the existing path. */ set path(path) { if (!path.startsWith("/")) { _chunk6INN7JETcjs.warn.call(void 0, `Only absolute paths are supported. Make sure that "${path}" starts with a slash "/".` ); return; } this.node.value.addEditOverride({ path }); } /** * Alias of the route. */ get alias() { return this.node.value.overrides.alias; } /** * Add an alias to the route. * * @param alias - Alias to add to the route */ addAlias(alias) { this.node.value.addEditOverride({ alias }); } /** * Array of the route params and all of its parent's params. Note that changing the params will not update the path, * you need to update both. */ get params() { return this.node.params; } /** * Path of the route including parent paths. */ get fullPath() { return this.node.fullPath; } /** * Computes an array of EditableTreeNode from the current node. Differently from iterating over the tree, this method * **only returns direct children**. */ get children() { return [...this.node.children.values()].map( (node) => new _EditableTreeNode(node) ); } /** * DFS traversal of the tree. * @example * ```ts * for (const node of tree) { * // ... * } * ``` */ *traverseDFS() { if (!this.node.isRoot()) { yield this; } for (const [_name, child] of this.node.children) { yield* new _EditableTreeNode(child).traverseDFS(); } } *[Symbol.iterator]() { yield* this.traverseBFS(); } /** * BFS traversal of the tree as a generator. * * @example * ```ts * for (const node of tree) { * // ... * } * ``` */ *traverseBFS() { for (const [_name, child] of this.node.children) { yield new _EditableTreeNode(child); } for (const [_name, child] of this.node.children) { yield* new _EditableTreeNode(child).traverseBFS(); } } }; // src/core/context.ts var _localpkg = require('local-pkg'); function createRoutesContext(options) { const { dts: preferDTS, root, routesFolder } = options; const dts = preferDTS === false ? false : preferDTS === true ? _pathe.resolve.call(void 0, root, "typed-router.d.ts") : _pathe.resolve.call(void 0, root, preferDTS); const routeTree = new PrefixTree(options); const editableRoutes = new EditableTreeNode(routeTree); const logger = new Proxy(console, { get(target, prop) { const res = Reflect.get(target, prop); if (typeof res === "function") { return options.logs ? res : () => { }; } return res; } }); const watchers = []; async function scanPages(startWatchers = true) { if (options.extensions.length < 1) { throw new Error( '"extensions" cannot be empty. Please specify at least one extension.' ); } if (watchers.length > 0) { return; } await Promise.all( routesFolder.map((folder) => resolveFolderOptions(options, folder)).map((folder) => { if (startWatchers) { watchers.push(setupWatcher(new RoutesFolderWatcher(folder))); } const ignorePattern = folder.exclude.map( (f) => ( // if it starts with ** then it will work as expected f.startsWith("**") ? f : _pathe.relative.call(void 0, folder.src, f) ) ); return _fastglob2.default.call(void 0, folder.pattern, { cwd: folder.src, // TODO: do they return the symbolic link path or the original file? // followSymbolicLinks: false, ignore: ignorePattern }).then( (files) => Promise.all( files.map((file) => _pathe.resolve.call(void 0, folder.src, file)).map( (file) => addPage({ routePath: _chunk6INN7JETcjs.asRoutePath.call(void 0, folder, file), filePath: file }) ) ) ); }) ); for (const route of editableRoutes) { await _optionalChain([options, 'access', _19 => _19.extendRoute, 'optionalCall', _20 => _20(route)]); } await _writeConfigFiles(); } async function writeRouteInfoToNode(node, filePath) { const content = await _fs.promises.readFile(filePath, "utf8"); node.hasDefinePage ||= content.includes("definePage"); const definedPageNameAndPath = extractDefinePageNameAndPath( content, filePath ); const routeBlock = getRouteBlock(filePath, content, options); node.setCustomRouteBlock(filePath, { ...routeBlock, ...definedPageNameAndPath }); } async function addPage({ filePath, routePath }, triggerExtendRoute = false) { logger.log(`added "${routePath}" for "${filePath}"`); const node = routeTree.insert(routePath, filePath); await writeRouteInfoToNode(node, filePath); if (triggerExtendRoute) { await _optionalChain([options, 'access', _21 => _21.extendRoute, 'optionalCall', _22 => _22(new EditableTreeNode(node))]); } _optionalChain([server, 'optionalAccess', _23 => _23.updateRoutes, 'call', _24 => _24()]); } async function updatePage({ filePath, routePath }) { logger.log(`updated "${routePath}" for "${filePath}"`); const node = routeTree.getChild(filePath); if (!node) { logger.warn(`Cannot update "${filePath}": Not found.`); return; } await writeRouteInfoToNode(node, filePath); await _optionalChain([options, 'access', _25 => _25.extendRoute, 'optionalCall', _26 => _26(new EditableTreeNode(node))]); } function removePage({ filePath, routePath }) { logger.log(`remove "${routePath}" for "${filePath}"`); routeTree.removeChild(filePath); _optionalChain([server, 'optionalAccess', _27 => _27.updateRoutes, 'call', _28 => _28()]); } function setupWatcher(watcher) { logger.log(`\u{1F916} Scanning files in ${watcher.src}`); return watcher.on("change", async (ctx) => { await updatePage(ctx); writeConfigFiles(); }).on("add", async (ctx) => { await addPage(ctx, true); writeConfigFiles(); }).on("unlink", (ctx) => { removePage(ctx); writeConfigFiles(); }); } function generateRoutes() { const importsMap = new (0, _chunk6INN7JETcjs.ImportsMap)(); const routeList = `export const routes = ${generateRouteRecord( routeTree, options, importsMap )} `; let hmr = ts` export function handleHotUpdate(_router, _hotUpdateCallback) { if (import.meta.hot) { import.meta.hot.data.router = _router import.meta.hot.data.router_hotUpdateCallback = _hotUpdateCallback } } if (import.meta.hot) { import.meta.hot.accept((mod) => { const router = import.meta.hot.data.rou