From 8a3191b65cdb6fb7dc7c761640e3fac9114d98b1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Apr 2026 14:17:19 +0000 Subject: [PATCH 1/3] feat: add rolldown hook filters to static plugin hooks Agent-Logs-Url: https://github.com/ElMassimo/iles/sessions/2f29b5ee-5bf6-4265-aed8-abd9fd19681f Co-authored-by: ElMassimo <1158253+ElMassimo@users.noreply.github.com> --- packages/iles/src/node/build/islands.ts | 33 ++- packages/iles/src/node/plugin/documents.ts | 136 ++++++----- packages/iles/src/node/plugin/plugin.ts | 256 ++++++++++++--------- packages/images/src/images.ts | 13 +- packages/mdx/src/mdx-vite-plugins.ts | 33 ++- packages/pages/src/pages.ts | 29 ++- 6 files changed, 294 insertions(+), 206 deletions(-) diff --git a/packages/iles/src/node/build/islands.ts b/packages/iles/src/node/build/islands.ts index d29f2ce6..690f526b 100644 --- a/packages/iles/src/node/build/islands.ts +++ b/packages/iles/src/node/build/islands.ts @@ -56,21 +56,34 @@ export async function bundleIslands (config: AppConfig, islandsByPath: IslandsBy } function virtualEntrypointsPlugin (root: string, entrypoints: Record): Plugin { + const escapeRegex = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + const entryIds = Object.keys(entrypoints) + const entrypointIdFilter = entryIds.length > 0 + ? new RegExp(`^(?:${entryIds.map(escapeRegex).join('|')})$`) + : /^$/ + const turboIdFilter = /(?:^|[\\/])iles[\\/]turbo(?:\?|$)/ + return { name: 'iles:entrypoints', - resolveId (id, importer) { - if (id in entrypoints) - return VIRTUAL_PREFIX + id + resolveId: { + filter: { id: [entrypointIdFilter, turboIdFilter] }, + handler (id, importer) { + if (id in entrypoints) + return VIRTUAL_PREFIX + id - if (relative(root, id.split('?', 2)[0]) === VIRTUAL_TURBO_ID) - return VIRTUAL_TURBO_ID + if (relative(root, id.split('?', 2)[0]) === VIRTUAL_TURBO_ID) + return VIRTUAL_TURBO_ID + }, }, - async load (id) { - if (id.startsWith(VIRTUAL_PREFIX)) - return entrypoints[id.slice(VIRTUAL_PREFIX.length)] + load: { + filter: { id: { include: [/^virtual_ile_/, VIRTUAL_TURBO_ID] } }, + async handler (id) { + if (id.startsWith(VIRTUAL_PREFIX)) + return entrypoints[id.slice(VIRTUAL_PREFIX.length)] - if (id === VIRTUAL_TURBO_ID) - return await fs.readFile(TURBO_SCRIPT_PATH, 'utf-8') + if (id === VIRTUAL_TURBO_ID) + return await fs.readFile(TURBO_SCRIPT_PATH, 'utf-8') + }, }, } } diff --git a/packages/iles/src/node/plugin/documents.ts b/packages/iles/src/node/plugin/documents.ts index dca7aee2..e7c39f88 100644 --- a/packages/iles/src/node/plugin/documents.ts +++ b/packages/iles/src/node/plugin/documents.ts @@ -10,9 +10,10 @@ import { debug, serialize } from './utils' const definitionRegex = /(function|const|let|var)[\s\n]+\buseDocuments\b/ const usageRegex = /\buseDocuments[\s\n]*\(([^)]+)\)/g -const fileCanUseDocuments = /(\.vue|\.[tj]sx?)$/ +const fileCanUseDocuments = /(\.vue|\.[tj]sx?)(?:$|\?)/ const DOCS_VIRTUAL_ID = '/@islands/documents' +const docsVirtualIdFilter = new RegExp(`^${DOCS_VIRTUAL_ID.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?:\\?|$)`) interface DocumentModule { pattern: string @@ -31,55 +32,60 @@ export default function documentsPlugin (config: AppConfig): Plugin { configureServer (devServer) { server = devServer }, - resolveId (id) { - if (id.startsWith(DOCS_VIRTUAL_ID)) - return id + resolveId: { + filter: { id: docsVirtualIdFilter }, + handler (id) { + if (id.startsWith(DOCS_VIRTUAL_ID)) + return id + }, }, // Extract frontmatter for each file in the matching pattern, and create a // module where the default export is an array with each matching document. - async load (id, options) { - if (!id.startsWith(DOCS_VIRTUAL_ID)) return - - const { query: { pattern: rawPath } } = parseId(id) - - // Extract pattern from the virtual module path, and resolve any alias. - const path = relative(root, await config.resolvePath(rawPath) || rawPath) - const pattern = path.includes('*') ? path : `${path}/**/*.{md,mdx}` - - // Allow Vite to automatically detect added or removed files. - if (server) - modulesById[id] = { pattern, hasDocument: path => micromatch.isMatch(path, pattern) } - - // Obtain files matching the specified pattern and extract frontmatter. - const files = await glob(pattern, { cwd: root }) - debug.documents('%s %O', rawPath, { path, pattern, files }) - - let data = await Promise.all(files.map(async (file) => { - const frontmatter = await pages.api.frontmatterForPageOrFile(file) - frontmatter.meta.filename ||= file - return frontmatter - })) - - // Filter drafts from documents if needed. - if (!drafts) - data = data.filter(page => !page.draft) - debug.documents(`${files.length} files, ${data.length} documents, drafts: ${drafts}`) - - // Create the structure of each document in the default export. - const documents = data.map(({ route: _, meta, layout, ...frontmatter }, index) => { - return { ...meta, ...frontmatter, meta, frontmatter, component: `${index}_component` } - }) - - // Serialize all the documents, adding a `component` factory function. - const serialized = serialize(documents).replace(/component:"(\w+)"/g, (_, id) => { - const index = id.split('_component')[0] - return `component: unwrapDefault(() => import('/${documents[index].filename}'))` - }) - - // Use defineAsyncComponent to support using . - // HMR works by updating the value of the computed ref, while preserving - // any previously resolved component promises to avoid refetching. - return ` + load: { + filter: { id: docsVirtualIdFilter }, + async handler (id, options) { + if (!id.startsWith(DOCS_VIRTUAL_ID)) return + + const { query: { pattern: rawPath } } = parseId(id) + + // Extract pattern from the virtual module path, and resolve any alias. + const path = relative(root, await config.resolvePath(rawPath) || rawPath) + const pattern = path.includes('*') ? path : `${path}/**/*.{md,mdx}` + + // Allow Vite to automatically detect added or removed files. + if (server) + modulesById[id] = { pattern, hasDocument: path => micromatch.isMatch(path, pattern) } + + // Obtain files matching the specified pattern and extract frontmatter. + const files = await glob(pattern, { cwd: root }) + debug.documents('%s %O', rawPath, { path, pattern, files }) + + let data = await Promise.all(files.map(async (file) => { + const frontmatter = await pages.api.frontmatterForPageOrFile(file) + frontmatter.meta.filename ||= file + return frontmatter + })) + + // Filter drafts from documents if needed. + if (!drafts) + data = data.filter(page => !page.draft) + debug.documents(`${files.length} files, ${data.length} documents, drafts: ${drafts}`) + + // Create the structure of each document in the default export. + const documents = data.map(({ route: _, meta, layout, ...frontmatter }, index) => { + return { ...meta, ...frontmatter, meta, frontmatter, component: `${index}_component` } + }) + + // Serialize all the documents, adding a `component` factory function. + const serialized = serialize(documents).replace(/component:"(\w+)"/g, (_, id) => { + const index = id.split('_component')[0] + return `component: unwrapDefault(() => import('/${documents[index].filename}'))` + }) + + // Use defineAsyncComponent to support using . + // HMR works by updating the value of the computed ref, while preserving + // any previously resolved component promises to avoid refetching. + return ` import { shallowRef, defineAsyncComponent } from 'vue' export const documents = ${serialized} @@ -108,25 +114,29 @@ export default function documentsPlugin (config: AppConfig): Plugin { mod.documents.ref = documents.ref }) } - ` + ` + }, }, - async transform (code, id) { - // Replace each usage of useDocuments with an import of a virtual module. - if (fileCanUseDocuments.test(id) && !definitionRegex.test(code)) { - const paths: [string, string][] = [] - code = code.replace(usageRegex, (_, path) => { - path = path.trim().slice(1, -1) - const id = `_documents_${paths.length}` - paths.push([id, path]) - return id - }) - if (paths.length) { - const imports = paths.map(([id, path]) => - `import ${id} from '${DOCS_VIRTUAL_ID}?pattern=${path}'`) + transform: { + filter: { id: fileCanUseDocuments }, + async handler (code, id) { + // Replace each usage of useDocuments with an import of a virtual module. + if (fileCanUseDocuments.test(id) && !definitionRegex.test(code)) { + const paths: [string, string][] = [] + code = code.replace(usageRegex, (_, path) => { + path = path.trim().slice(1, -1) + const id = `_documents_${paths.length}` + paths.push([id, path]) + return id + }) + if (paths.length) { + const imports = paths.map(([id, path]) => + `import ${id} from '${DOCS_VIRTUAL_ID}?pattern=${path}'`) - return `${code};${imports.join(';')}` + return `${code};${imports.join(';')}` + } } - } + }, }, hotUpdate ({ file, modules }) { const relFile = relative(root, file) diff --git a/packages/iles/src/node/plugin/plugin.ts b/packages/iles/src/node/plugin/plugin.ts index e3749217..c5c1a44a 100644 --- a/packages/iles/src/node/plugin/plugin.ts +++ b/packages/iles/src/node/plugin/plugin.ts @@ -36,6 +36,7 @@ async function transformUserFile (path: string) { } const templateLayoutRegex = // +const escapeRegex = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // Public: Configures MDX, Vue, Components, and Islands plugins. export default function IslandsPlugins (appConfig: AppConfig): PluginOption[] { @@ -57,6 +58,12 @@ export default function IslandsPlugins (appConfig: AppConfig): PluginOption[] { return path.includes(appConfig.layoutsDir) } + const resolveIdFilter = new RegExp(`^(?:${ + [ILES_APP_ENTRY, APP_CONFIG_REQUEST_PATH, USER_APP_REQUEST_PATH, USER_SITE_REQUEST_PATH, NOT_FOUND_REQUEST_PATH, defaultLayoutPath] + .map(escapeRegex) + .join('|') + })$`) + return [ { name: 'iles', @@ -74,47 +81,65 @@ export default function IslandsPlugins (appConfig: AppConfig): PluginOption[] { const result = await transformUserFile(appPath) detectMDXComponents(result.code, appConfig, undefined) }, - async resolveId (id) { - if (id === ILES_APP_ENTRY) - return APP_PATH + resolveId: { + filter: { id: resolveIdFilter }, + async handler (id) { + if (id === ILES_APP_ENTRY) + return APP_PATH - if (id === APP_CONFIG_REQUEST_PATH || id === USER_APP_REQUEST_PATH || id === USER_SITE_REQUEST_PATH) - return id + if (id === APP_CONFIG_REQUEST_PATH || id === USER_APP_REQUEST_PATH || id === USER_SITE_REQUEST_PATH) + return id - if (id === NOT_FOUND_REQUEST_PATH) - return NOT_FOUND_COMPONENT_PATH + if (id === NOT_FOUND_REQUEST_PATH) + return NOT_FOUND_COMPONENT_PATH - // Prevent import analysis failure if the default layout doesn't exist. - if (id === defaultLayoutPath) return resolve(root, id.slice(1)) + // Prevent import analysis failure if the default layout doesn't exist. + if (id === defaultLayoutPath) return resolve(root, id.slice(1)) + }, }, - async load (id) { - if (id === APP_CONFIG_REQUEST_PATH) { - const { base, debug, jsx, ssg: { sitemap }, siteUrl, markdown: { overrideElements = [] } } = appConfig - const clientConfig: AppClientConfig = { base, debug, root, jsx, sitemap, siteUrl, overrideElements } - return `export default ${serialize(clientConfig)}` - } - - const userFilename = (id === USER_APP_REQUEST_PATH && appPath) - || (id === USER_SITE_REQUEST_PATH && sitePath) - if (userFilename) { - this.addWatchFile(userFilename) - const result = await transformUserFile(userFilename) - - if (id === USER_APP_REQUEST_PATH) - detectMDXComponents(result.code, appConfig, server) - - if (id === USER_SITE_REQUEST_PATH) - return extendSite(result.code, appConfig) - - return result - } - - if ((isBuild || process.env.VITEST) && id.includes(defaultLayoutPath) && !await exists(resolve(root, defaultLayoutPath.slice(1)))) - return '' + load: { + filter: { + id: { + include: [ + APP_CONFIG_REQUEST_PATH, + USER_APP_REQUEST_PATH, + USER_SITE_REQUEST_PATH, + new RegExp(escapeRegex(defaultLayoutPath)), + ], + }, + }, + async handler (id) { + if (id === APP_CONFIG_REQUEST_PATH) { + const { base, debug, jsx, ssg: { sitemap }, siteUrl, markdown: { overrideElements = [] } } = appConfig + const clientConfig: AppClientConfig = { base, debug, root, jsx, sitemap, siteUrl, overrideElements } + return `export default ${serialize(clientConfig)}` + } + + const userFilename = (id === USER_APP_REQUEST_PATH && appPath) + || (id === USER_SITE_REQUEST_PATH && sitePath) + if (userFilename) { + this.addWatchFile(userFilename) + const result = await transformUserFile(userFilename) + + if (id === USER_APP_REQUEST_PATH) + detectMDXComponents(result.code, appConfig, server) + + if (id === USER_SITE_REQUEST_PATH) + return extendSite(result.code, appConfig) + + return result + } + + if ((isBuild || process.env.VITEST) && id.includes(defaultLayoutPath) && !await exists(resolve(root, defaultLayoutPath.slice(1)))) + return '' + }, }, - transform (code, id) { - if (id === APP_COMPONENT_PATH && !isBuild && appConfig.debug) - return code.replace('const DebugPanel = () => null', () => `import DebugPanel from '${DEBUG_COMPONENT_PATH}'`) + transform: { + filter: { id: APP_COMPONENT_PATH }, + handler (code, id) { + if (id === APP_COMPONENT_PATH && !isBuild && appConfig.debug) + return code.replace('const DebugPanel = () => null', () => `import DebugPanel from '${DEBUG_COMPONENT_PATH}'`) + }, }, hotUpdate ({ file }) { if (file === appPath) return [this.environment.moduleGraph.getModuleById(USER_APP_REQUEST_PATH)!] @@ -128,25 +153,31 @@ export default function IslandsPlugins (appConfig: AppConfig): PluginOption[] { { name: 'iles:detect-islands-in-vue', enforce: 'pre', - async transform (code, id) { - const { path, query } = parseId(id) + transform: { + filter: { id: /\.vue(?:$|\?)/ }, + async handler (code, id) { + const { path, query } = parseId(id) - if (query.vue !== undefined && query.type === 'script-client') - return 'export default {}; if (import.meta.hot) import.meta.hot.accept()' + if (query.vue !== undefined && query.type === 'script-client') + return 'export default {}; if (import.meta.hot) import.meta.hot.accept()' - if (isSFCMain(path, query) && code.includes('client:') && code.includes(' - s.appendRight(sfcIndex, value ? `${key}:${value},` : `${key},`) + transform: { + filter: { id: /\.(?:mdx?|vue)(?:$|\?)/ }, + async handler (code, id, options) { + const { path, query } = parseId(id) + const isMdx = isMarkdown(path) + if (!isMdx && !isVueScript(path, query)) return + + const isLayoutFile = isLayout(path) + const isPage = plugins.pages.api.isPage(path) + if (!isMdx && !isLayoutFile && !isPage) return + + const sfcIndex = indexOfVueComponentDefinition(code) + if (!sfcIndex || sfcIndex === -1) + return + + const s = new MagicString(code) + const appendToSfc = (key: string, value?: string) => + s.appendRight(sfcIndex, value ? `${key}:${value},` : `${key},`) + + if (isLayoutFile) { + appendToSfc('name', `'${pascalCase(basename(path).replace('.vue', 'Layout'))}'`) + return s.toString() + } + + appendToSfc('inheritAttrs', serialize(false)) + + const { meta, layout = 'default', route: _r, ...frontmatter } + = await plugins.pages.api.frontmatterForPageOrFile(path, code) + + if (isMdx) { + // NOTE: Expose each frontmatter property to the MDX file. + const keys = Object.keys(frontmatter) + const bindings = Object.entries(frontmatter) + .map(([key, value]) => `${key} = ${serialize(value)}`) + + bindings.push(`meta = ${serialize(meta)}`) + bindings.push(`frontmatter = { ${keys.length > 0 ? keys.join(', ') : ''} }`) + + s.prepend(`const ${bindings.join(', ')};`) + appendToSfc('...meta, ...frontmatter, meta, frontmatter') + } + else { + s.prepend(`const _meta = ${serialize(meta)}, _frontmatter = ${serialize(frontmatter)};`) + appendToSfc('..._meta, ..._frontmatter, meta: _meta, frontmatter: _frontmatter') + } + + if (isPage) { + appendToSfc('layoutName', serialize(layout)) + appendToSfc('layoutFn', String(layout) === 'false' + ? 'false' + : `() => import('${layoutsRoot}/${layout}.vue').then(m => m.default)`) + } - if (isLayoutFile) { - appendToSfc('name', `'${pascalCase(basename(path).replace('.vue', 'Layout'))}'`) return s.toString() - } - - appendToSfc('inheritAttrs', serialize(false)) - - const { meta, layout = 'default', route: _r, ...frontmatter } - = await plugins.pages.api.frontmatterForPageOrFile(path, code) - - if (isMdx) { - // NOTE: Expose each frontmatter property to the MDX file. - const keys = Object.keys(frontmatter) - const bindings = Object.entries(frontmatter) - .map(([key, value]) => `${key} = ${serialize(value)}`) - - bindings.push(`meta = ${serialize(meta)}`) - bindings.push(`frontmatter = { ${keys.length > 0 ? keys.join(', ') : ''} }`) - - s.prepend(`const ${bindings.join(', ')};`) - appendToSfc('...meta, ...frontmatter, meta, frontmatter') - } - else { - s.prepend(`const _meta = ${serialize(meta)}, _frontmatter = ${serialize(frontmatter)};`) - appendToSfc('..._meta, ..._frontmatter, meta: _meta, frontmatter: _frontmatter') - } - - if (isPage) { - appendToSfc('layoutName', serialize(layout)) - appendToSfc('layoutFn', String(layout) === 'false' - ? 'false' - : `() => import('${layoutsRoot}/${layout}.vue').then(m => m.default)`) - } - - return s.toString() + }, }, }, @@ -231,12 +268,15 @@ export default function IslandsPlugins (appConfig: AppConfig): PluginOption[] { apply: 'serve', enforce: 'post', // Force a refresh for all page computed properties. - async transform (code, id) { - const { path } = parseId(id) - if (isLayout(path) || plugins.pages.api.isPage(path)) { - return `${code} + transform: { + filter: { id: /\.(?:mdx?|vue)(?:$|\?)/ }, + async handler (code, id) { + const { path } = parseId(id) + if (isLayout(path) || plugins.pages.api.isPage(path)) { + return `${code} import.meta.hot?.accept('/${relative(root, path)}', (...args) => __ILES_PAGE_UPDATE__(args)) ` + } } }, }, diff --git a/packages/images/src/images.ts b/packages/images/src/images.ts index ad7e6464..160b46c2 100644 --- a/packages/images/src/images.ts +++ b/packages/images/src/images.ts @@ -46,11 +46,14 @@ export default function IlesImagePresets (presets: ImagePresets, options?: Optio plugin, { name: '@islands/images:inject-mdx-component', - transform (code, id) { - if (id.includes('/composables/mdxComponents.js')) { - code = code.replace('inject(mdxComponentsKey)', '{ img: _Picture, ...inject(mdxComponentsKey) }') - return `import _Picture from '${PICTURE_COMPONENT_PATH}'\n${code}` - } + transform: { + filter: { id: /\/composables\/mdxComponents\.js/ }, + handler (code, id) { + if (id.includes('/composables/mdxComponents.js')) { + code = code.replace('inject(mdxComponentsKey)', '{ img: _Picture, ...inject(mdxComponentsKey) }') + return `import _Picture from '${PICTURE_COMPONENT_PATH}'\n${code}` + } + }, }, }, ], diff --git a/packages/mdx/src/mdx-vite-plugins.ts b/packages/mdx/src/mdx-vite-plugins.ts index eec699a3..ce018800 100644 --- a/packages/mdx/src/mdx-vite-plugins.ts +++ b/packages/mdx/src/mdx-vite-plugins.ts @@ -28,6 +28,8 @@ export default function IlesMdx (options: MarkdownOptions = {}): Plugin[] { }) } + const markdownIdFilter = /\.(?:md|mdx)(?:$|\?)/ + return [ { name: 'iles:mdx:compile', @@ -37,20 +39,25 @@ export default function IlesMdx (options: MarkdownOptions = {}): Plugin[] { await createMdxProcessor(isDevelopment || config.build.sourcemap) }, - async transform (value, path) { - if (!shouldTransform(path)) return + transform: { + filter: { id: markdownIdFilter }, + async handler (value, path) { + if (!shouldTransform(path)) return - const compiled = await markdownProcessor.process({ value, path }) - return { code: String(compiled.value), map: compiled.map } as TransformResult + const compiled = await markdownProcessor.process({ value, path }) + return { code: String(compiled.value), map: compiled.map } as TransformResult + }, }, }, { name: 'iles:mdx:sfc', - async transform (code, path) { - if (!shouldTransform(path)) return + transform: { + filter: { id: markdownIdFilter }, + async handler (code, path) { + if (!shouldTransform(path)) return - return code.replace('export default function MDXContent', () => ` + return code.replace('export default function MDXContent', () => ` import { defineComponent as $defineComponent } from 'iles/jsx-runtime' const _sfc_main = /* @__PURE__ */ $defineComponent(MDXContent, {${ @@ -59,17 +66,20 @@ const _sfc_main = /* @__PURE__ */ $defineComponent(MDXContent, {${ export default _sfc_main function MDXContent`) + }, }, }, { name: 'iles:mdx:hmr', apply: 'serve', - transform (code: string, path: string) { - if (!shouldTransform(path)) return + transform: { + filter: { id: markdownIdFilter }, + handler (code: string, path: string) { + if (!shouldTransform(path)) return - const hmrId = hash(`${path.split('?', 2)[0]}default`) + const hmrId = hash(`${path.split('?', 2)[0]}default`) - return `${code} + return `${code} _sfc_main.__hmrId = "${hmrId}" __VUE_HMR_RUNTIME__.createRecord("${hmrId}", _sfc_main) import.meta.hot?.accept(({ default: component } = {}) => { @@ -77,6 +87,7 @@ function MDXContent`) __VUE_HMR_RUNTIME__.reload(component.__hmrId, component) }) ` + }, }, }, ] diff --git a/packages/pages/src/pages.ts b/packages/pages/src/pages.ts index 684cc551..52a1553e 100644 --- a/packages/pages/src/pages.ts +++ b/packages/pages/src/pages.ts @@ -8,6 +8,8 @@ import { MODULE_ID } from './types' export * from './types' +const moduleIdRegex = new RegExp(`^${MODULE_ID.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`) + /** * An iles module that injects remark plugins to parse pages and expose it * to the MDX JS expressions as `meta` and `pages`. @@ -63,17 +65,26 @@ export default function IlesPages (): any { async buildStart () { await api.addAllPages() }, - async resolveId (id) { - if (id === MODULE_ID) - return MODULE_ID + resolveId: { + filter: { id: moduleIdRegex }, + async handler (id) { + if (id === MODULE_ID) + return MODULE_ID + }, }, - async load (id) { - if (id === MODULE_ID) - return generatedRoutes ||= await api.generateRoutesModule() + load: { + filter: { id: MODULE_ID }, + async handler (id) { + if (id === MODULE_ID) + return generatedRoutes ||= await api.generateRoutesModule() + }, }, - async transform (_code, id) { - if (id.includes('vue&type=page')) - return 'export default {};' + transform: { + filter: { id: /vue&type=page/ }, + async handler (_code, id) { + if (id.includes('vue&type=page')) + return 'export default {};' + }, }, } From 5c07b02bbbe90fcb7ddc97591fb482cc337cb6ce Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Apr 2026 14:25:16 +0000 Subject: [PATCH 2/3] fix: handle prefixed virtual ids in pages filter hooks Agent-Logs-Url: https://github.com/ElMassimo/iles/sessions/2f29b5ee-5bf6-4265-aed8-abd9fd19681f Co-authored-by: ElMassimo <1158253+ElMassimo@users.noreply.github.com> --- packages/iles/src/node/build/islands.ts | 33 ++++++++----------------- packages/pages/src/pages.ts | 9 ++++--- 2 files changed, 15 insertions(+), 27 deletions(-) diff --git a/packages/iles/src/node/build/islands.ts b/packages/iles/src/node/build/islands.ts index 690f526b..d29f2ce6 100644 --- a/packages/iles/src/node/build/islands.ts +++ b/packages/iles/src/node/build/islands.ts @@ -56,34 +56,21 @@ export async function bundleIslands (config: AppConfig, islandsByPath: IslandsBy } function virtualEntrypointsPlugin (root: string, entrypoints: Record): Plugin { - const escapeRegex = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') - const entryIds = Object.keys(entrypoints) - const entrypointIdFilter = entryIds.length > 0 - ? new RegExp(`^(?:${entryIds.map(escapeRegex).join('|')})$`) - : /^$/ - const turboIdFilter = /(?:^|[\\/])iles[\\/]turbo(?:\?|$)/ - return { name: 'iles:entrypoints', - resolveId: { - filter: { id: [entrypointIdFilter, turboIdFilter] }, - handler (id, importer) { - if (id in entrypoints) - return VIRTUAL_PREFIX + id + resolveId (id, importer) { + if (id in entrypoints) + return VIRTUAL_PREFIX + id - if (relative(root, id.split('?', 2)[0]) === VIRTUAL_TURBO_ID) - return VIRTUAL_TURBO_ID - }, + if (relative(root, id.split('?', 2)[0]) === VIRTUAL_TURBO_ID) + return VIRTUAL_TURBO_ID }, - load: { - filter: { id: { include: [/^virtual_ile_/, VIRTUAL_TURBO_ID] } }, - async handler (id) { - if (id.startsWith(VIRTUAL_PREFIX)) - return entrypoints[id.slice(VIRTUAL_PREFIX.length)] + async load (id) { + if (id.startsWith(VIRTUAL_PREFIX)) + return entrypoints[id.slice(VIRTUAL_PREFIX.length)] - if (id === VIRTUAL_TURBO_ID) - return await fs.readFile(TURBO_SCRIPT_PATH, 'utf-8') - }, + if (id === VIRTUAL_TURBO_ID) + return await fs.readFile(TURBO_SCRIPT_PATH, 'utf-8') }, } } diff --git a/packages/pages/src/pages.ts b/packages/pages/src/pages.ts index 52a1553e..85b3db69 100644 --- a/packages/pages/src/pages.ts +++ b/packages/pages/src/pages.ts @@ -8,7 +8,8 @@ import { MODULE_ID } from './types' export * from './types' -const moduleIdRegex = new RegExp(`^${MODULE_ID.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`) +const escapedModuleId = MODULE_ID.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') +const moduleIdRegex = new RegExp(`^(?:\\0|/@id/)?${escapedModuleId}(?:\\?.*)?$`) /** * An iles module that injects remark plugins to parse pages and expose it @@ -68,14 +69,14 @@ export default function IlesPages (): any { resolveId: { filter: { id: moduleIdRegex }, async handler (id) { - if (id === MODULE_ID) + if (moduleIdRegex.test(id)) return MODULE_ID }, }, load: { - filter: { id: MODULE_ID }, + filter: { id: moduleIdRegex }, async handler (id) { - if (id === MODULE_ID) + if (moduleIdRegex.test(id)) return generatedRoutes ||= await api.generateRoutesModule() }, }, From 50c6feab90dbfb0362af9012ddbdbf934e38effa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Apr 2026 14:33:50 +0000 Subject: [PATCH 3/3] refactor: simplify filtered hook handlers Agent-Logs-Url: https://github.com/ElMassimo/iles/sessions/2f29b5ee-5bf6-4265-aed8-abd9fd19681f Co-authored-by: ElMassimo <1158253+ElMassimo@users.noreply.github.com> --- packages/iles/src/node/plugin/documents.ts | 9 +++------ packages/iles/src/node/plugin/plugin.ts | 4 ++-- packages/images/src/images.ts | 8 +++----- packages/mdx/src/mdx-vite-plugins.ts | 12 ------------ packages/pages/src/pages.ts | 15 ++++++--------- 5 files changed, 14 insertions(+), 34 deletions(-) diff --git a/packages/iles/src/node/plugin/documents.ts b/packages/iles/src/node/plugin/documents.ts index e7c39f88..560bda49 100644 --- a/packages/iles/src/node/plugin/documents.ts +++ b/packages/iles/src/node/plugin/documents.ts @@ -35,17 +35,14 @@ export default function documentsPlugin (config: AppConfig): Plugin { resolveId: { filter: { id: docsVirtualIdFilter }, handler (id) { - if (id.startsWith(DOCS_VIRTUAL_ID)) - return id + return id }, }, // Extract frontmatter for each file in the matching pattern, and create a // module where the default export is an array with each matching document. load: { filter: { id: docsVirtualIdFilter }, - async handler (id, options) { - if (!id.startsWith(DOCS_VIRTUAL_ID)) return - + async handler (id, _options) { const { query: { pattern: rawPath } } = parseId(id) // Extract pattern from the virtual module path, and resolve any alias. @@ -121,7 +118,7 @@ export default function documentsPlugin (config: AppConfig): Plugin { filter: { id: fileCanUseDocuments }, async handler (code, id) { // Replace each usage of useDocuments with an import of a virtual module. - if (fileCanUseDocuments.test(id) && !definitionRegex.test(code)) { + if (!definitionRegex.test(code)) { const paths: [string, string][] = [] code = code.replace(usageRegex, (_, path) => { path = path.trim().slice(1, -1) diff --git a/packages/iles/src/node/plugin/plugin.ts b/packages/iles/src/node/plugin/plugin.ts index c5c1a44a..5d8c6812 100644 --- a/packages/iles/src/node/plugin/plugin.ts +++ b/packages/iles/src/node/plugin/plugin.ts @@ -136,8 +136,8 @@ export default function IslandsPlugins (appConfig: AppConfig): PluginOption[] { }, transform: { filter: { id: APP_COMPONENT_PATH }, - handler (code, id) { - if (id === APP_COMPONENT_PATH && !isBuild && appConfig.debug) + handler (code, _id) { + if (!isBuild && appConfig.debug) return code.replace('const DebugPanel = () => null', () => `import DebugPanel from '${DEBUG_COMPONENT_PATH}'`) }, }, diff --git a/packages/images/src/images.ts b/packages/images/src/images.ts index 160b46c2..eb5f68a9 100644 --- a/packages/images/src/images.ts +++ b/packages/images/src/images.ts @@ -48,11 +48,9 @@ export default function IlesImagePresets (presets: ImagePresets, options?: Optio name: '@islands/images:inject-mdx-component', transform: { filter: { id: /\/composables\/mdxComponents\.js/ }, - handler (code, id) { - if (id.includes('/composables/mdxComponents.js')) { - code = code.replace('inject(mdxComponentsKey)', '{ img: _Picture, ...inject(mdxComponentsKey) }') - return `import _Picture from '${PICTURE_COMPONENT_PATH}'\n${code}` - } + handler (code, _id) { + code = code.replace('inject(mdxComponentsKey)', '{ img: _Picture, ...inject(mdxComponentsKey) }') + return `import _Picture from '${PICTURE_COMPONENT_PATH}'\n${code}` }, }, }, diff --git a/packages/mdx/src/mdx-vite-plugins.ts b/packages/mdx/src/mdx-vite-plugins.ts index ce018800..d08998b4 100644 --- a/packages/mdx/src/mdx-vite-plugins.ts +++ b/packages/mdx/src/mdx-vite-plugins.ts @@ -1,5 +1,3 @@ -import { extname } from 'path' - import type { Plugin, TransformResult } from 'vite' import type { createFormatAwareProcessors } from '@mdx-js/mdx/internal-create-format-aware-processors' import hash from 'hash-sum' @@ -13,10 +11,6 @@ export default function IlesMdx (options: MarkdownOptions = {}): Plugin[] { let markdownProcessor: ReturnType let isDevelopment: boolean - function shouldTransform (path: string) { - return markdownProcessor.extnames.includes(extname(path)) - } - async function createMdxProcessor (sourcemap: string | boolean) { const { createFormatAwareProcessors } = await import('@mdx-js/mdx/internal-create-format-aware-processors') markdownProcessor = createFormatAwareProcessors({ @@ -42,8 +36,6 @@ export default function IlesMdx (options: MarkdownOptions = {}): Plugin[] { transform: { filter: { id: markdownIdFilter }, async handler (value, path) { - if (!shouldTransform(path)) return - const compiled = await markdownProcessor.process({ value, path }) return { code: String(compiled.value), map: compiled.map } as TransformResult }, @@ -55,8 +47,6 @@ export default function IlesMdx (options: MarkdownOptions = {}): Plugin[] { transform: { filter: { id: markdownIdFilter }, async handler (code, path) { - if (!shouldTransform(path)) return - return code.replace('export default function MDXContent', () => ` import { defineComponent as $defineComponent } from 'iles/jsx-runtime' @@ -75,8 +65,6 @@ function MDXContent`) transform: { filter: { id: markdownIdFilter }, handler (code: string, path: string) { - if (!shouldTransform(path)) return - const hmrId = hash(`${path.split('?', 2)[0]}default`) return `${code} diff --git a/packages/pages/src/pages.ts b/packages/pages/src/pages.ts index 85b3db69..8a2d937a 100644 --- a/packages/pages/src/pages.ts +++ b/packages/pages/src/pages.ts @@ -68,23 +68,20 @@ export default function IlesPages (): any { }, resolveId: { filter: { id: moduleIdRegex }, - async handler (id) { - if (moduleIdRegex.test(id)) - return MODULE_ID + async handler (_id) { + return MODULE_ID }, }, load: { filter: { id: moduleIdRegex }, - async handler (id) { - if (moduleIdRegex.test(id)) - return generatedRoutes ||= await api.generateRoutesModule() + async handler (_id) { + return generatedRoutes ||= await api.generateRoutesModule() }, }, transform: { filter: { id: /vue&type=page/ }, - async handler (_code, id) { - if (id.includes('vue&type=page')) - return 'export default {};' + async handler (_code, _id) { + return 'export default {};' }, }, }