Skip to content

Commit af31fe8

Browse files
committed
perf: use <FormKitProvider> and synchronous config import
1 parent 9f68888 commit af31fe8

File tree

4 files changed

+162
-100
lines changed

4 files changed

+162
-100
lines changed

playground/formkit.config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { defineFormKitConfig } from '@formkit/vue'
2+
3+
export default defineFormKitConfig(() => ({}))

src/index.ts

Lines changed: 67 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,17 @@ import { existsSync } from 'fs'
66
import { parse } from '@vue/compiler-dom'
77
import type { RootNode, ElementNode, AttributeNode } from '@vue/compiler-dom'
88

9+
const FORMKIT_CONFIG_ID = 'virtual:formkit-config'
10+
const FORMKIT_PROVIDER_IMPORT_STATEMENT = [
11+
`import { FormKitProvider } from "@formkit/vue";`,
12+
`import __formkitConfig from "${FORMKIT_CONFIG_ID}";`,
13+
].join('\n')
14+
/**
15+
* A relatively cheap, albeit not foolproof, regex to determine if the code
16+
* being processed contains FormKit usage.
17+
*/
18+
const CONTAINS_FORMKIT_RE = /<FormKit|<form-kit/
19+
920
function getRootBlock(
1021
root: RootNode,
1122
block: 'template' | 'script' | 'style',
@@ -53,7 +64,7 @@ function langAttr(node?: ElementNode): string {
5364
}
5465

5566
/**
56-
* Imports `FormKitLazyProvider` component into the script block of the SFC.
67+
* Imports `FormKitProvider` component into the script block of the SFC.
5768
* @param code - The SFC source code.
5869
* @param id - The ID of the SFC file.
5970
*/
@@ -67,31 +78,33 @@ function injectProviderImport(code: string): string {
6778
return code
6879
}
6980
const script = getRootBlock(root, 'script')
70-
const importStatement = `import { FormKitLazyProvider } from '@formkit/vue'`
7181
const setupScript = root.children.find(
72-
(node) => node.type === 1 && node.tag === 'script' && isSetupScript(node),
73-
) as ElementNode | undefined
82+
(node): node is ElementNode =>
83+
node.type === 1 && node.tag === 'script' && isSetupScript(node),
84+
)
7485
if (!setupScript) {
75-
return `<script setup${langAttr(script)}>${importStatement}</script>
76-
${code}`
86+
return [
87+
`<script setup${langAttr(script)}>`,
88+
FORMKIT_PROVIDER_IMPORT_STATEMENT,
89+
`</script>`,
90+
code,
91+
].join('\n')
7792
}
7893
const startAt = setupScript.children[0].loc.start.offset
7994
const before = code.substring(0, startAt)
8095
const after = code.substring(startAt)
81-
return `${before}\n${importStatement}${after}`
96+
return `${before}\n${FORMKIT_PROVIDER_IMPORT_STATEMENT}${after}`
8297
}
8398

8499
/**
85-
* Injects the `<FormKitLazyProvider>` component import into the SFC.
100+
* Injects the `<FormKitProvider>` component import into the SFC.
86101
* @param code - The SFC source code.
87102
* @param id - The ID of the SFC file.
88103
*/
89104
function injectProviderComponent(
90105
code: string,
91106
id: string,
92-
config?: boolean,
93-
defaultConfig?: boolean,
94-
): { code: string; map?: any } {
107+
): { code: string; map?: null } {
95108
let root: RootNode
96109
try {
97110
root = parse(code)
@@ -100,24 +113,25 @@ function injectProviderComponent(
100113
console.error(err)
101114
return { code }
102115
}
103-
const open = `<FormKitLazyProvider${config ? ' config-file="true"' : ''}${
104-
defaultConfig ? '' : ' :default-config="false"'
105-
}>`
106-
const close = '</FormKitLazyProvider>'
107116
const template = getRootBlock(root, 'template')
108117
if (!template) {
109118
console.warn(
110-
`No <template> block found in ${id}. Skipping FormKitLazyProvider injection.`,
119+
`No <template> block found in ${id}. Skipping FormKitProvider injection.`,
111120
)
112121
return { code, map: null }
113122
}
114123
const startInsertAt = template.children[0].loc.start.offset
115124
const endInsertAt =
116125
template.children[template.children.length - 1].loc.end.offset
117-
const before = code.substring(0, startInsertAt)
118-
const content = code.substring(startInsertAt, endInsertAt)
119-
const after = code.substring(endInsertAt)
120-
code = `${before}\n${open}\n${content}\n${close}\n${after}`
126+
127+
code = [
128+
code.substring(0, startInsertAt),
129+
`<FormKitProvider :config="__formkitConfig">`,
130+
code.substring(startInsertAt, endInsertAt),
131+
'</FormKitProvider>',
132+
code.substring(endInsertAt),
133+
].join('\n')
134+
121135
return { code, map: null }
122136
}
123137

@@ -140,80 +154,59 @@ function resolveConfig(configFile: string): string | undefined {
140154
return paths.find((path) => existsSync(path))
141155
}
142156

143-
/**
144-
* A relatively cheap, albeit not foolproof, regex to determine if the code
145-
* being processed contains FormKit usage.
146-
*/
147-
const CONTAINS_FORMKIT_RE = /<FormKit|<form-kit/
148-
149-
/**
150-
* A regex to find the @__formkit_config__ comment in the code.
151-
*/
152-
const FORMKIT_CONFIG_RE =
153-
/(\/\*\s?@__formkit\.config\.ts__\s?\*\/(?:.|\n)+?)\)/g
154-
155157
export const unpluginFactory: UnpluginFactory<Options | undefined> = (
156158
options = {
157159
configFile: './formkit.config',
158160
defaultConfig: true,
159161
},
160162
) => {
161-
const configPath = resolveConfig(options.configFile || './formkit.config')
162-
163163
return {
164164
name: 'unplugin-formkit',
165165
enforce: 'pre',
166-
vite: {
167-
config() {
168-
return {
169-
optimizeDeps: {
170-
exclude: ['@formkit/vue'],
171-
},
166+
167+
resolveId(id) {
168+
if (id === FORMKIT_CONFIG_ID) {
169+
return id
170+
}
171+
},
172+
173+
load(id) {
174+
if (id === FORMKIT_CONFIG_ID) {
175+
// Resolve FormKit configuration file path on-demand in case user has created/removed it since plugin was initialized.
176+
const configPath = resolveConfig(
177+
options.configFile || './formkit.config',
178+
)
179+
const customConfigDefinition = configPath
180+
? [
181+
`import _config from "${configPath}";`,
182+
`const config = typeof _config === 'function' ? _config() : _config;`,
183+
].join('\n')
184+
: 'const config = {};'
185+
186+
if (options.defaultConfig !== false) {
187+
return [
188+
`import { defaultConfig } from "@formkit/vue";`,
189+
customConfigDefinition,
190+
`export default defaultConfig(config);`,
191+
].join('\n')
172192
}
173-
},
193+
194+
return [customConfigDefinition, `export default config;`].join('\n')
195+
}
174196
},
197+
175198
// webpack's id filter is outside of loader logic,
176199
// an additional hook is needed for better perf on webpack
177-
transformInclude() {
178-
// TODO: resolve why @formkit/vue is not always identifiable by the id
179-
// and remove this early return workaround:
180-
return true
181-
// return (
182-
// id.endsWith('.vue') ||
183-
// id.includes('@formkit/vue') ||
184-
// id.includes('@formkit_vue') ||
185-
// id.endsWith('packages/vue/dist/index.mjs')
186-
// )
200+
transformInclude(id) {
201+
return id.endsWith('.vue')
187202
},
188203

189204
// just like rollup transform
190205
async transform(code, id) {
191-
// Replace all instances of `/* @__formkit_config__ */` in the code
192-
// with the resolved path to the formkit.config.{ts,js,mjs} file.
193-
if (configPath && FORMKIT_CONFIG_RE.test(code)) {
194-
code = code.replace(FORMKIT_CONFIG_RE, `"${configPath}")`)
195-
if (options.defaultConfig === false) {
196-
// If the user has explicitly disabled the default config, we need
197-
// to remove the defaultConfig from the FormKitConfigLoader. We can
198-
// do this by cutting the /* @__default-config__ */ comment area.
199-
code = code.replace(
200-
/\/\* @__default-config__ \*\/(?:.|\n)+?\/\* @__default-config__ \*\//gi,
201-
'',
202-
)
203-
}
204-
// Parse the modified code using recast and return the code with a sourcemap.
205-
return { code, map: null }
206-
}
207206
// Test if the given code is a likely candidate for FormKit usage.
208207
if (id.endsWith('.vue') && CONTAINS_FORMKIT_RE.test(code)) {
209-
return injectProviderComponent(
210-
injectProviderImport(code),
211-
id,
212-
!!configPath,
213-
options.defaultConfig,
214-
)
208+
return injectProviderComponent(injectProviderImport(code), id)
215209
}
216-
return
217210
},
218211
}
219212
}
Lines changed: 54 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,67 @@
11
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
22

3-
exports[`index > injects import into script setup block 1`] = `
3+
exports[`virtual configuration file > generates correct configuration when custom config is available 1`] = `
4+
"import { defaultConfig } from \\"@formkit/vue\\"
5+
import _config from \\"~/playground/formkit.config.ts\\";
6+
const config = typeof _config === 'function' ? _config() : _config;
7+
export default defaultConfig(config);"
8+
`;
9+
10+
exports[`virtual configuration file > generates correct configuration when custom config is available and default config is disabled 1`] = `
11+
"import _config from \\"~/playground/formkit.config.ts\\";
12+
const config = typeof _config === 'function' ? _config() : _config;
13+
export default config;"
14+
`;
15+
16+
exports[`virtual configuration file > generates correct configuration when default config is disabled 1`] = `
17+
"const config = {};
18+
export default config;"
19+
`;
20+
21+
exports[`virtual configuration file > generates correct virtual configuration with default options 1`] = `
22+
"import { defaultConfig } from \\"@formkit/vue\\"
23+
const config = {};
24+
export default defaultConfig(config);"
25+
`;
26+
27+
exports[`vue file transformations > injects import into script setup block 1`] = `
428
"<script setup lang=\\"ts\\">
5-
import { FormKitLazyProvider } from '@formkit/vue'
29+
import { FormKitProvider } from \\"@formkit/vue\\";
30+
import __formkitConfig from \\"virtual:formkit-config\\";
631
import { FormKit } from '@formkit/vue'
732
</script>
833
934
<template>
1035
11-
<FormKitLazyProvider>
36+
<FormKitProvider :config=\\"__formkitConfig\\">
1237
<FormKit
1338
type=\\"text\\"
1439
label=\\"Your name\\"
1540
help=\\"Enter your name\\"
1641
/>
17-
</FormKitLazyProvider>
42+
</FormKitProvider>
1843
1944
</template>"
2045
`;
2146

22-
exports[`index > injects inside root node if there is one 1`] = `
23-
"<script setup>import { FormKitLazyProvider } from '@formkit/vue'</script>
47+
exports[`vue file transformations > injects inside root node if there is one 1`] = `
48+
"<script setup>import { FormKitProvider } from \\"@formkit/vue\\";
49+
import __formkitConfig from \\"virtual:formkit-config\\";</script>
2450
<template>
2551
<div class=\\"fizzbuzz\\">
2652
27-
<FormKitLazyProvider>
53+
<FormKitProvider :config=\\"__formkitConfig\\">
2854
<FormKit />
29-
</FormKitLazyProvider>
55+
</FormKitProvider>
3056
3157
</div>
3258
</template>"
3359
`;
3460

35-
exports[`index > injects inside root node with full sfc 1`] = `
61+
exports[`vue file transformations > injects inside root node with full sfc 1`] = `
3662
"<script lang=\\"ts\\" setup>
37-
import { FormKitLazyProvider } from '@formkit/vue'
63+
import { FormKitProvider } from \\"@formkit/vue\\";
64+
import __formkitConfig from \\"virtual:formkit-config\\";
3865
function handleLoginSubmit(values: any) {
3966
window.alert(\\"You are logged in. Credentials:
4067
\\" + JSON.stringify(values));
@@ -44,21 +71,22 @@ function handleLoginSubmit(values: any) {
4471
<template>
4572
<div>
4673
47-
<FormKitLazyProvider>
74+
<FormKitProvider :config=\\"__formkitConfig\\">
4875
<FormKit type=\\"form\\" submit-label=\\"login\\" @submit=\\"handleLoginSubmit\\">
4976
<FormKit type=\\"email\\" label=\\"Email\\" name=\\"email\\" />
5077
<FormKit type=\\"password\\" label=\\"Password\\" name=\\"password\\" />
5178
</FormKit>
52-
</FormKitLazyProvider>
79+
</FormKitProvider>
5380
5481
</div>
5582
</template>
5683
"
5784
`;
5885
59-
exports[`index > injects inside root node with multiple child elements 1`] = `
86+
exports[`vue file transformations > injects inside root node with multiple child elements 1`] = `
6087
"<script lang=\\"ts\\" setup>
61-
import { FormKitLazyProvider } from '@formkit/vue'
88+
import { FormKitProvider } from \\"@formkit/vue\\";
89+
import __formkitConfig from \\"virtual:formkit-config\\";
6290
function handleLoginSubmit(values: any) {
6391
window.alert(\\"You are logged in. Credentials:
6492
\\" + JSON.stringify(values));
@@ -68,7 +96,7 @@ function handleLoginSubmit(values: any) {
6896
<template>
6997
<div>
7098
71-
<FormKitLazyProvider>
99+
<FormKitProvider :config=\\"__formkitConfig\\">
72100
<main>
73101
<p>
74102
<FormKit type=\\"form\\" submit-label=\\"login\\" @submit=\\"handleLoginSubmit\\">
@@ -78,15 +106,16 @@ function handleLoginSubmit(values: any) {
78106
</p>
79107
</main>
80108
<div class=\\"filler\\">Here we go</div>
81-
</FormKitLazyProvider>
109+
</FormKitProvider>
82110
83111
</div>
84112
</template>
85113
"
86114
`;
87115
88-
exports[`index > injects setup block when using options api 1`] = `
89-
"<script setup lang=\\"ts\\">import { FormKitLazyProvider } from '@formkit/vue'</script>
116+
exports[`vue file transformations > injects setup block when using options api 1`] = `
117+
"<script setup lang=\\"ts\\">import { FormKitProvider } from \\"@formkit/vue\\";
118+
import __formkitConfig from \\"virtual:formkit-config\\";</script>
90119
<script lang=\\"ts\\">
91120
import { FormKit } from '@formkit/vue'
92121
@@ -100,22 +129,23 @@ export default {
100129
<template>
101130
<div>
102131
103-
<FormKitLazyProvider>
132+
<FormKitProvider :config=\\"__formkitConfig\\">
104133
<h1>Nothing to see here</h1>
105134
<FormKit type=\\"text\\" label=\\"Check me out\\" />
106-
</FormKitLazyProvider>
135+
</FormKitProvider>
107136
108137
</div>
109138
</template>"
110139
`;
111140
112-
exports[`index > injects the template block into an normally structured sfc 1`] = `
113-
"<script setup>import { FormKitLazyProvider } from '@formkit/vue'</script>
141+
exports[`vue file transformations > injects the template block into an normally structured sfc 1`] = `
142+
"<script setup>import { FormKitProvider } from \\"@formkit/vue\\";
143+
import __formkitConfig from \\"virtual:formkit-config\\";</script>
114144
<template>
115145
116-
<FormKitLazyProvider>
146+
<FormKitProvider :config=\\"__formkitConfig\\">
117147
<FormKit />
118-
</FormKitLazyProvider>
148+
</FormKitProvider>
119149
120150
</template>"
121151
`;

0 commit comments

Comments
 (0)