Skip to content

Commit 16af831

Browse files
committed
feat: experimental auto export of data loaders
1 parent 5e78f70 commit 16af831

File tree

10 files changed

+242
-159
lines changed

10 files changed

+242
-159
lines changed

Diff for: package.json

+1
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@
172172
"fast-glob": "^3.3.2",
173173
"json5": "^2.2.3",
174174
"local-pkg": "^0.5.0",
175+
"magic-string": "^0.30.11",
175176
"mlly": "^1.7.1",
176177
"pathe": "^1.1.2",
177178
"scule": "^1.3.0",

Diff for: playground/src/loaders/colada-loaders.ts

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { defineColadaLoader } from 'unplugin-vue-router/data-loaders/pinia-colada'
2+
import { ref } from 'vue'
3+
4+
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
5+
export const simulateError = ref(false)
6+
7+
export const useUserData = defineColadaLoader('/users/colada-loader.[id]', {
8+
async query(to, { signal }) {
9+
console.log('[🍹] coladaLoader', to.fullPath)
10+
// signal.addEventListener('abort', () => {
11+
// console.log('[🍹❌] aborted', to.fullPath)
12+
// })
13+
// we need to read these before the delay
14+
const id = to.params.id
15+
// @ts-expect-error: no param "name"!
16+
const name = to.params.name
17+
18+
await delay(500)
19+
if (simulateError.value) {
20+
throw new Error('Simulated Error')
21+
}
22+
23+
const user = {
24+
id,
25+
name,
26+
when: new Date().toUTCString(),
27+
}
28+
29+
return user
30+
},
31+
key: (to) => {
32+
// console.log('[🍹] key', to.fullPath)
33+
return ['loader-users', to.params.id]
34+
},
35+
staleTime: 10000,
36+
// lazy: (to, from) => to.name && to.name === from?.name,
37+
})

Diff for: playground/src/loaders/todos.ts

Whitespace-only changes.

Diff for: playground/src/pages/users/colada-loader.[id].vue

+1-43
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,5 @@
1-
<script lang="ts">
2-
import { defineColadaLoader } from 'unplugin-vue-router/data-loaders/pinia-colada'
3-
4-
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
5-
6-
const simulateError = ref(false)
7-
8-
// th
9-
10-
export const useUserData = defineColadaLoader('/users/colada-loader.[id]', {
11-
async query(to, { signal }) {
12-
console.log('[🍹] coladaLoader', to.fullPath)
13-
// signal.addEventListener('abort', () => {
14-
// console.log('[🍹❌] aborted', to.fullPath)
15-
// })
16-
// we need to read these before the delay
17-
const id = to.params.id
18-
// @ts-expect-error: no param "name"!
19-
const name = to.params.name
20-
21-
await delay(500)
22-
if (simulateError.value) {
23-
throw new Error('Simulated Error')
24-
}
25-
26-
const user = {
27-
id,
28-
name,
29-
when: new Date().toUTCString(),
30-
}
31-
32-
return user
33-
},
34-
key: (to) => {
35-
// console.log('[🍹] key', to.fullPath)
36-
return ['loader-users', to.params.id]
37-
},
38-
staleTime: 10000,
39-
lazy: (to, from) => to.name && to.name === from.name,
40-
})
41-
</script>
42-
431
<script lang="ts" setup>
44-
import { computed, ref } from 'vue'
2+
import { simulateError, useUserData } from '@/loaders/colada-loaders'
453
import { serialize } from '@pinia/colada'
464
import { getActivePinia } from 'pinia'
475

Diff for: playground/vite.config.ts

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export default defineConfig({
3030
VueRouter({
3131
extensions: ['.page.vue', '.vue'],
3232
importMode: 'async',
33+
experimental: { autoExportsDataLoaders: true },
3334
extendRoute(route) {
3435
route.params.forEach((param, i) => {
3536
// transform kebab-case to camelCase

Diff for: pnpm-lock.yaml

+4-13
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: src/data-loaders/auto-exports.ts

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { createFilter } from '@rollup/pluginutils'
2+
import MagicString from 'magic-string'
3+
import { findStaticImports, parseStaticImport } from 'mlly'
4+
import { type UnpluginOptions } from 'unplugin'
5+
6+
const ignoredSpecifiers = ['vue', 'vue-router', 'pinia']
7+
8+
export function extractLoadersToExport(code: string): string[] {
9+
const imports = findStaticImports(code)
10+
const importNames = imports.flatMap((i) => {
11+
const parsed = parseStaticImport(i)
12+
13+
// bail out faster for anything that is not a data loader
14+
if (
15+
// NOTE: move out to a regexp if the option is exposed
16+
parsed.specifier.startsWith('@vueuse/') &&
17+
ignoredSpecifiers.includes(parsed.specifier)
18+
)
19+
return []
20+
21+
return [
22+
parsed.defaultImport,
23+
...Object.values(parsed.namedImports || {}),
24+
].filter((v): v is string => !!v && !v.startsWith('_'))
25+
})
26+
27+
return importNames
28+
}
29+
30+
export function createAutoExportPlugin(): UnpluginOptions {
31+
const filterVueComponents = createFilter(
32+
[/\.vue$/, /\.vue\?vue/, /\.vue\?v=/]
33+
// [/[\\/]node_modules[\\/]/, /[\\/]\.git[\\/]/, /[\\/]\.nuxt[\\/]/]
34+
)
35+
36+
return {
37+
name: 'unplugin-vue-router:data-loaders-auto-export',
38+
enforce: 'post',
39+
vite: {
40+
transform: {
41+
order: 'post',
42+
handler(code, id) {
43+
if (!filterVueComponents(id)) {
44+
return
45+
}
46+
47+
const loadersToExports = extractLoadersToExport(code)
48+
49+
if (loadersToExports.length <= 0) return
50+
51+
const s = new MagicString(code)
52+
s.append(
53+
`\nexport const __loaders = [\n${loadersToExports.join(',\n')}\n];\n`
54+
)
55+
56+
return {
57+
code: s.toString(),
58+
map: s.generateMap(),
59+
}
60+
},
61+
},
62+
},
63+
}
64+
}

Diff for: src/data-loaders/navigation-guard.ts

+7
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,13 @@ export function setupLoaderGuard({
109109
record.meta[LOADER_SET_KEY]!.add(exportValue)
110110
}
111111
}
112+
if (Array.isArray(viewModule.__loaders)) {
113+
for (const loader of viewModule.__loaders) {
114+
if (isDataLoader(loader)) {
115+
record.meta[LOADER_SET_KEY]!.add(loader)
116+
}
117+
}
118+
}
112119
}
113120
)
114121

0 commit comments

Comments
 (0)