Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: use magic-string to create sourcemaps #4

Merged
merged 2 commits into from
Feb 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 44 additions & 29 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import { resolve } from 'pathe'
import { existsSync } from 'fs'
import { parse } from '@vue/compiler-dom'
import type { RootNode, ElementNode, AttributeNode } from '@vue/compiler-dom'
import MagicString from 'magic-string'

const FORMKIT_CONFIG_ID = 'virtual:formkit-config'
const FORMKIT_PROVIDER_IMPORT_STATEMENT = [
`import { FormKitProvider } from "@formkit/vue";`,
`import __formkitConfig from "${FORMKIT_CONFIG_ID}";`,
].join('\n')
const FORMKIT_PROVIDER_IMPORT_STATEMENT = `
import { FormKitProvider } from "@formkit/vue";
import __formkitConfig from "${FORMKIT_CONFIG_ID}";
`
/**
* A relatively cheap, albeit not foolproof, regex to determine if the code
* being processed contains FormKit usage.
Expand Down Expand Up @@ -67,72 +68,68 @@ function langAttr(node?: ElementNode): string {
* Imports `FormKitProvider` component into the script block of the SFC.
* @param code - The SFC source code.
* @param id - The ID of the SFC file.
* @param s - A MagicString instance, for tracking sourcemaps.
*/
function injectProviderImport(code: string): string {
function injectProviderImport(
code: string,
s = new MagicString(code),
): MagicString | undefined {
let root: RootNode
try {
root = parse(code)
} catch (err) {
console.warn('Failed to parse SFC:', code)
console.error(err)
return code
return
}
const script = getRootBlock(root, 'script')
const setupScript = root.children.find(
(node): node is ElementNode =>
node.type === 1 && node.tag === 'script' && isSetupScript(node),
)
if (!setupScript) {
return [
`<script setup${langAttr(script)}>`,
FORMKIT_PROVIDER_IMPORT_STATEMENT,
`</script>`,
code,
].join('\n')
const block = `<script setup${langAttr(script)}>${FORMKIT_PROVIDER_IMPORT_STATEMENT}</script>\n`
return s.prepend(block)
}

const startAt = setupScript.children[0].loc.start.offset
const before = code.substring(0, startAt)
const after = code.substring(startAt)
return `${before}\n${FORMKIT_PROVIDER_IMPORT_STATEMENT}${after}`
return s.appendLeft(startAt, FORMKIT_PROVIDER_IMPORT_STATEMENT)
}

/**
* Injects the `<FormKitProvider>` component import into the SFC.
* @param code - The SFC source code.
* @param id - The ID of the SFC file.
* @param s - A MagicString instance, for tracking sourcemaps.
*/
function injectProviderComponent(
code: string,
id: string,
): { code: string; map?: null } {
s = new MagicString(code),
): MagicString | undefined {
let root: RootNode
try {
root = parse(code)
} catch (err) {
console.warn('Failed to parse SFC:', code)
console.error(err)
return { code }
return
}

const template = getRootBlock(root, 'template')
if (!template) {
console.warn(
`No <template> block found in ${id}. Skipping FormKitProvider injection.`,
)
return { code, map: null }
return
}

const startInsertAt = template.children[0].loc.start.offset
const endInsertAt =
template.children[template.children.length - 1].loc.end.offset

code = [
code.substring(0, startInsertAt),
`<FormKitProvider :config="__formkitConfig">`,
code.substring(startInsertAt, endInsertAt),
'</FormKitProvider>',
code.substring(endInsertAt),
].join('\n')

return { code, map: null }
s.appendRight(startInsertAt, `<FormKitProvider :config="__formkitConfig">`)
s.appendLeft(endInsertAt, '</FormKitProvider>')
}

/**
Expand All @@ -158,6 +155,7 @@ export const unpluginFactory: UnpluginFactory<Options | undefined> = (
options = {
configFile: './formkit.config',
defaultConfig: true,
sourcemap: false,
},
) => {
return {
Expand Down Expand Up @@ -204,8 +202,25 @@ export const unpluginFactory: UnpluginFactory<Options | undefined> = (
// just like rollup transform
async transform(code, id) {
// Test if the given code is a likely candidate for FormKit usage.
if (id.endsWith('.vue') && CONTAINS_FORMKIT_RE.test(code)) {
return injectProviderComponent(injectProviderImport(code), id)
if (!id.endsWith('.vue') || !CONTAINS_FORMKIT_RE.test(code)) {
return
}

// Generate a MagicString instance to track changes to code
const s = new MagicString(code)

injectProviderComponent(code, id, s)

// We can save extra parsing time by not returning anything or adding imports if we haven't added the wrapper
if (!s.hasChanged()) {
return
}

injectProviderImport(code, s)

return {
code: s.toString(),
map: options.sourcemap ? s.generateMap({ hires: true }) : undefined,
}
},
}
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export interface Options {
configFile?: string
defaultConfig?: boolean
sourcemap?: boolean
}
47 changes: 13 additions & 34 deletions test/__snapshots__/index.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,16 @@ exports[`vue file transformations > injects import into script setup block 1`] =
"<script setup lang=\\"ts\\">
import { FormKitProvider } from \\"@formkit/vue\\";
import __formkitConfig from \\"virtual:formkit-config\\";

import { FormKit } from '@formkit/vue'
</script>

<template>

<FormKitProvider :config=\\"__formkitConfig\\">
<FormKit
<FormKitProvider :config=\\"__formkitConfig\\"><FormKit
type=\\"text\\"
label=\\"Your name\\"
help=\\"Enter your name\\"
/>
</FormKitProvider>

/></FormKitProvider>
</template>"
`;

Expand All @@ -51,11 +48,7 @@ import __formkitConfig from \\"virtual:formkit-config\\";
</script>
<template>
<div class=\\"fizzbuzz\\">

<FormKitProvider :config=\\"__formkitConfig\\">
<FormKit />
</FormKitProvider>

<FormKitProvider :config=\\"__formkitConfig\\"><FormKit /></FormKitProvider>
</div>
</template>"
`;
Expand All @@ -64,6 +57,7 @@ exports[`vue file transformations > injects inside root node with full sfc 1`] =
"<script lang=\\"ts\\" setup>
import { FormKitProvider } from \\"@formkit/vue\\";
import __formkitConfig from \\"virtual:formkit-config\\";

function handleLoginSubmit(values: any) {
window.alert(\\"You are logged in. Credentials:
\\" + JSON.stringify(values));
Expand All @@ -72,14 +66,10 @@ function handleLoginSubmit(values: any) {

<template>
<div>

<FormKitProvider :config=\\"__formkitConfig\\">
<FormKit type=\\"form\\" submit-label=\\"login\\" @submit=\\"handleLoginSubmit\\">
<FormKitProvider :config=\\"__formkitConfig\\"><FormKit type=\\"form\\" submit-label=\\"login\\" @submit=\\"handleLoginSubmit\\">
<FormKit type=\\"email\\" label=\\"Email\\" name=\\"email\\" />
<FormKit type=\\"password\\" label=\\"Password\\" name=\\"password\\" />
</FormKit>
</FormKitProvider>

</FormKit></FormKitProvider>
</div>
</template>
"
Expand All @@ -89,6 +79,7 @@ exports[`vue file transformations > injects inside root node with multiple child
"<script lang=\\"ts\\" setup>
import { FormKitProvider } from \\"@formkit/vue\\";
import __formkitConfig from \\"virtual:formkit-config\\";

function handleLoginSubmit(values: any) {
window.alert(\\"You are logged in. Credentials:
\\" + JSON.stringify(values));
Expand All @@ -97,19 +88,15 @@ function handleLoginSubmit(values: any) {

<template>
<div>

<FormKitProvider :config=\\"__formkitConfig\\">
<main>
<FormKitProvider :config=\\"__formkitConfig\\"><main>
<p>
<FormKit type=\\"form\\" submit-label=\\"login\\" @submit=\\"handleLoginSubmit\\">
<FormKit type=\\"email\\" label=\\"Email\\" name=\\"email\\" />
<FormKit type=\\"password\\" label=\\"Password\\" name=\\"password\\" />
</FormKit>
</p>
</main>
<div class=\\"filler\\">Here we go</div>
</FormKitProvider>

<div class=\\"filler\\">Here we go</div></FormKitProvider>
</div>
</template>
"
Expand All @@ -132,12 +119,8 @@ export default {

<template>
<div>

<FormKitProvider :config=\\"__formkitConfig\\">
<h1>Nothing to see here</h1>
<FormKit type=\\"text\\" label=\\"Check me out\\" />
</FormKitProvider>

<FormKitProvider :config=\\"__formkitConfig\\"><h1>Nothing to see here</h1>
<FormKit type=\\"text\\" label=\\"Check me out\\" /></FormKitProvider>
</div>
</template>"
`;
Expand All @@ -148,10 +131,6 @@ import { FormKitProvider } from \\"@formkit/vue\\";
import __formkitConfig from \\"virtual:formkit-config\\";
</script>
<template>

<FormKitProvider :config=\\"__formkitConfig\\">
<FormKit />
</FormKitProvider>

<FormKitProvider :config=\\"__formkitConfig\\"><FormKit /></FormKitProvider>
</template>"
`;
Loading