From 8057c2754ef2b12e4ade01ae5477c008f73f70b5 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 7 Apr 2026 14:06:14 +0000 Subject: [PATCH 1/2] Use Rolldown native MagicString for faster sourcemap generation Replace the JS `magic-string` dependency with Rolldown's native `RolldownMagicString` in all transform handlers. Native magic string performs string mutations and sourcemap generation in Rust, avoiding the overhead of JS-based sourcemap encoding. Transform hooks now return the `RolldownMagicString` instance directly as `code`, letting Rolldown handle sourcemap generation internally without serialization. https://claude.ai/code/session_01Gk9SG3CqsszLBotJRTf45S --- packages/iles/package.json | 1 - packages/iles/src/node/build/rebaseImports.ts | 4 ++-- packages/iles/src/node/plugin/plugin.ts | 6 +++--- packages/iles/src/node/plugin/wrap.ts | 10 +++++----- pnpm-lock.yaml | 3 --- 5 files changed, 10 insertions(+), 14 deletions(-) diff --git a/packages/iles/package.json b/packages/iles/package.json index f491d366..310f1eb4 100644 --- a/packages/iles/package.json +++ b/packages/iles/package.json @@ -69,7 +69,6 @@ "es-module-lexer": "^1.5.4", "fast-glob": "^3.3.2", "local-pkg": "^0.5.0", - "magic-string": "^0.29", "mico-spinner": "^1.4.0", "micromatch": "^4.0.7", "minimist": "^1.2.8", diff --git a/packages/iles/src/node/build/rebaseImports.ts b/packages/iles/src/node/build/rebaseImports.ts index b28b8214..cf317f01 100644 --- a/packages/iles/src/node/build/rebaseImports.ts +++ b/packages/iles/src/node/build/rebaseImports.ts @@ -1,6 +1,6 @@ import { posix } from 'path' import { init as initESLexer, parse as parseESModules } from 'es-module-lexer' -import MagicString from 'magic-string' +import { RolldownMagicString as MagicString } from 'rolldown' import type { AppConfig } from '../shared' export default async function rebaseImports ({ base, assetsDir }: AppConfig, codeStr: string) { @@ -15,7 +15,7 @@ export default async function rebaseImports ({ base, assetsDir }: AppConfig, cod s += 1 e -= 1 } - code.overwrite(s, e, posix.join(assetsBase, code.slice(s, e)), { contentOnly: true }) + code.overwrite(s, e, posix.join(assetsBase, code.slice(s, e))) }) return code.toString() } diff --git a/packages/iles/src/node/plugin/plugin.ts b/packages/iles/src/node/plugin/plugin.ts index e3749217..0c3a1e15 100644 --- a/packages/iles/src/node/plugin/plugin.ts +++ b/packages/iles/src/node/plugin/plugin.ts @@ -3,7 +3,7 @@ import { basename, resolve, relative } from 'pathe' import type { PluginOption, ResolvedConfig, ViteDevServer } from 'vite' import { transformWithOxc } from 'vite' -import MagicString from 'magic-string' +import { RolldownMagicString as MagicString } from 'rolldown' import type { AppConfig, AppClientConfig } from '../shared' import { ILES_APP_ENTRY } from '../constants' @@ -190,7 +190,7 @@ export default function IslandsPlugins (appConfig: AppConfig): PluginOption[] { if (isLayoutFile) { appendToSfc('name', `'${pascalCase(basename(path).replace('.vue', 'Layout'))}'`) - return s.toString() + return { code: s } } appendToSfc('inheritAttrs', serialize(false)) @@ -222,7 +222,7 @@ export default function IslandsPlugins (appConfig: AppConfig): PluginOption[] { : `() => import('${layoutsRoot}/${layout}.vue').then(m => m.default)`) } - return s.toString() + return { code: s } }, }, diff --git a/packages/iles/src/node/plugin/wrap.ts b/packages/iles/src/node/plugin/wrap.ts index 48e59d0d..125ffb34 100644 --- a/packages/iles/src/node/plugin/wrap.ts +++ b/packages/iles/src/node/plugin/wrap.ts @@ -1,4 +1,4 @@ -import MagicString from 'magic-string' +import { RolldownMagicString as MagicString } from 'rolldown' import type { SFCBlock } from 'vue/compiler-sfc' import { parse } from 'vue/compiler-sfc' import type { ComponentInfo, PublicPluginAPI as ComponentsApi } from 'unplugin-vue-components/types' @@ -33,7 +33,7 @@ export async function wrapLayout (code: string, filename: string) { s.appendLeft(nodes[0].loc.start.offset, `<${Layout}>`) s.appendRight(nodes[nodes.length - 1].loc.end.offset, ``) - return { code: s.toString(), map: s.generateMap({ hires: true }) } + return { code: s } } const scriptClientRE = /]*\bclient:[^>]*)>([^]*?)<\/script>/ @@ -77,7 +77,7 @@ export async function wrapIslandsInSFC (config: AppConfig, code: string, filenam if (!scriptSetup && injectionOffset === 0) s.appendRight(0, '\n\n') - return { code: s.toString(), map: s.generateMap({ hires: true }) } + return { code: s } async function resolveComponentImport (strategy: string, tagName: string): Promise { debug.detect(`<${tagName} ${strategy}>`) @@ -114,11 +114,11 @@ async function visitSFCNode (node: ElementNode, s: MagicString, resolveComponent // Replace opening tag. s.overwrite(start.offset + 1, start.offset + 1 + tag.length, - `Island ${componentProps.replace(/\n\s*/g, ' ')}`, { contentOnly: true }) + `Island ${componentProps.replace(/\n\s*/g, ' ')}`) // Replace closing tag. if (!node.isSelfClosing) - s.overwrite(end.offset - 1 - tag.length, end.offset - 1, 'Island', { contentOnly: true }) + s.overwrite(end.offset - 1 - tag.length, end.offset - 1, 'Island') } if ('children' in node) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c5127973..fd136f3d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -289,9 +289,6 @@ importers: local-pkg: specifier: ^0.5.0 version: 0.5.0 - magic-string: - specifier: ^0.29 - version: 0.29.0 mdast-util-mdx-jsx: specifier: ^3.1.2 version: 3.1.2 From 9de2ba14df0672c5403b624daf6dc7b884e06714 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 7 Apr 2026 14:13:31 +0000 Subject: [PATCH 2/2] Pull magicString from transform meta instead of constructing new instances Reuse the lazily-created RolldownMagicString that Rolldown provides via the transform hook's meta parameter, avoiding redundant instance creation. The wrapLayout and wrapIslandsInSFC helpers now receive the magic string from their callers instead of constructing their own. https://claude.ai/code/session_01Gk9SG3CqsszLBotJRTf45S --- packages/iles/src/node/plugin/plugin.ts | 14 +++++++------- packages/iles/src/node/plugin/wrap.ts | 19 ++++++++++--------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/packages/iles/src/node/plugin/plugin.ts b/packages/iles/src/node/plugin/plugin.ts index 0c3a1e15..e6fe491d 100644 --- a/packages/iles/src/node/plugin/plugin.ts +++ b/packages/iles/src/node/plugin/plugin.ts @@ -3,7 +3,7 @@ import { basename, resolve, relative } from 'pathe' import type { PluginOption, ResolvedConfig, ViteDevServer } from 'vite' import { transformWithOxc } from 'vite' -import { RolldownMagicString as MagicString } from 'rolldown' +import type { RolldownMagicString as MagicString } from 'rolldown' import type { AppConfig, AppClientConfig } from '../shared' import { ILES_APP_ENTRY } from '../constants' @@ -128,25 +128,25 @@ export default function IslandsPlugins (appConfig: AppConfig): PluginOption[] { { name: 'iles:detect-islands-in-vue', enforce: 'pre', - async transform (code, id) { + async transform (code, id, meta) { 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 (isSFCMain(path, query) && code.includes('client:') && code.includes(' s.appendRight(sfcIndex, value ? `${key}:${value},` : `${key},`) diff --git a/packages/iles/src/node/plugin/wrap.ts b/packages/iles/src/node/plugin/wrap.ts index 125ffb34..17529bd6 100644 --- a/packages/iles/src/node/plugin/wrap.ts +++ b/packages/iles/src/node/plugin/wrap.ts @@ -1,4 +1,4 @@ -import { RolldownMagicString as MagicString } from 'rolldown' +import type { RolldownMagicString as MagicString } from 'rolldown' import type { SFCBlock } from 'vue/compiler-sfc' import { parse } from 'vue/compiler-sfc' import type { ComponentInfo, PublicPluginAPI as ComponentsApi } from 'unplugin-vue-components/types' @@ -15,12 +15,10 @@ interface SfcRootNode extends RootNode { export const unresolvedIslandKey = '__viteIslandComponent' -export async function wrapLayout (code: string, filename: string) { +export async function wrapLayout (code: string, filename: string, s: MagicString) { const { descriptor: { template }, errors } = parse(code, { filename }) if (errors.length > 0 || !template || !isString(template.attrs.layout)) return - const s = new MagicString(code) - const nodes = template.ast?.children if (!nodes?.length) { return @@ -38,9 +36,14 @@ export async function wrapLayout (code: string, filename: string) { const scriptClientRE = /]*\bclient:[^>]*)>([^]*?)<\/script>/ -export async function wrapIslandsInSFC (config: AppConfig, code: string, filename: string) { - code = code.replace(scriptClientRE, (_, attrs, content) => - `${content}`) +export async function wrapIslandsInSFC (config: AppConfig, code: string, filename: string, s: MagicString) { + const match = scriptClientRE.exec(code) + if (match) { + const [full, attrs, content] = match + const replacement = `${content}` + s.overwrite(match.index, match.index + full.length, replacement) + code = s.toString() + } const { descriptor: { template, script, scriptSetup, customBlocks }, errors } = parse(code, { filename }) const scriptClientIndex = customBlocks.findIndex(b => b.type === 'script-client') @@ -55,8 +58,6 @@ export async function wrapIslandsInSFC (config: AppConfig, code: string, filenam return } const sfcRootNode = template.ast as any as SfcRootNode - - const s = new MagicString(code) const components: ComponentsApi = config.namedPlugins.components.api if (scriptClient) { await injectClientScript(sfcRootNode, s, filename, scriptClientIndex, scriptClient) }