@dxup/nuxt 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -24,6 +24,12 @@ This is a TypeScript plugin that improves Nuxt DX.
24
24
  experimental: {
25
25
  typescriptPlugin: true,
26
26
  },
27
+ dxup: {
28
+ features: {
29
+ // Enable opt-in features
30
+ namedLayoutSlots: true,
31
+ },
32
+ },
27
33
  });
28
34
  ```
29
35
 
@@ -50,7 +56,40 @@ import.meta.glob("~/assets/*.webp");
50
56
  // ^^^^^^^^^^^^^^^^^
51
57
  ```
52
58
 
53
- ### 3. nitroRoutes
59
+ ### 3. namedLayoutSlots
60
+
61
+ **Default:** `false`
62
+
63
+ Write top-level named slots in your pages:
64
+
65
+ ```vue
66
+ <!-- layouts/center.vue -->
67
+ <template>
68
+ <slot></slot>
69
+ <slot name="side" one="one"></slot>
70
+ </template>
71
+ ```
72
+
73
+ ```vue
74
+ <!-- pages/about.vue -->
75
+ <script lang="ts" setup>
76
+ definePageMeta({
77
+ layout: "center",
78
+ });
79
+ </script>
80
+
81
+ <template>
82
+ <template #side="{ one }">
83
+ This "{{ one }}" comes from the layout slot.
84
+ <!-- ^^^ -->
85
+ </template>
86
+ <div>About Page</div>
87
+ </template>
88
+ ```
89
+
90
+ And them will be forwarded to the active layout automatically.
91
+
92
+ ### 4. nitroRoutes
54
93
 
55
94
  Go to definition for nitro routes in data fetching methods.
56
95
 
@@ -62,7 +101,7 @@ useFetch("/api/foo");
62
101
 
63
102
  It will fallback to resolve the URL from your `public` directory when no nitro routes match.
64
103
 
65
- ### 4. pageMeta
104
+ ### 5. pageMeta
66
105
 
67
106
  Go to definition for page metadata.
68
107
 
@@ -75,7 +114,7 @@ definePageMeta({
75
114
  });
76
115
  ```
77
116
 
78
- ### 5. runtimeConfig
117
+ ### 6. runtimeConfig
79
118
 
80
119
  Go to definition for runtime config.
81
120
 
@@ -86,7 +125,7 @@ Go to definition for runtime config.
86
125
  </template>
87
126
  ```
88
127
 
89
- ### 6. typedPages
128
+ ### 7. typedPages
90
129
 
91
130
  Go to definition for typed pages.
92
131
 
@@ -99,11 +138,11 @@ Go to definition for typed pages.
99
138
 
100
139
  It can be triggered on the `name` property of an object literal constrained by the `RouteLocationRaw` type.
101
140
 
102
- ### 7. unimport
141
+ ### 8. unimport
103
142
 
104
143
  Please refer to the [@dxup/unimport](/packages/unimport) package for more details.
105
144
 
106
- ### 8. unofficial
145
+ ### 9. unofficial
107
146
 
108
147
  Find references for SFC on `<template>`.
109
148
 
@@ -0,0 +1,4 @@
1
+ //#region src/module/named-layout-slots/components/forward.d.ts
2
+ declare const _default: import("vue").DefineSetupFnComponent<Record<string, any>, {}, {}, Record<string, any> & {}, import("vue").PublicProps>;
3
+ //#endregion
4
+ export { _default as default };
@@ -0,0 +1,10 @@
1
+ import { defineComponent } from "vue";
2
+ import { injectLayoutSlots } from "#build/dxup/layouts.mjs";
3
+ //#region src/module/named-layout-slots/components/forward.ts
4
+ var forward_default = defineComponent((props, ctx) => {
5
+ const slots = injectLayoutSlots();
6
+ slots.value = ctx.slots;
7
+ return () => ctx.slots.default?.();
8
+ });
9
+ //#endregion
10
+ export { forward_default as default };
@@ -0,0 +1,78 @@
1
+ require("@vue/compiler-dom");
2
+ let pathe = require("pathe");
3
+ //#region src/module/named-layout-slots/utils.ts
4
+ function isInDir(path, dir) {
5
+ const rel = (0, pathe.relative)(dir, path);
6
+ return rel !== ".." && !rel.startsWith("../") && !(0, pathe.isAbsolute)(rel);
7
+ }
8
+ //#endregion
9
+ //#region src/module/named-layout-slots/language.ts
10
+ const resolvedAsts = /* @__PURE__ */ new WeakSet();
11
+ const plugin = ({ modules: { typescript: ts, "@vue/compiler-dom": CompilerDOM }, config: { options } }) => ({
12
+ version: 2.2,
13
+ order: -1,
14
+ resolveEmbeddedCode(fileName, sfc, embeddedCode) {
15
+ if (!embeddedCode.id.startsWith("script_")) return;
16
+ if (!options.dirs.some((dir) => isInDir(fileName, dir))) return;
17
+ if (!sfc.template?.ast || resolvedAsts.has(sfc.template.ast)) return;
18
+ resolvedAsts.add(sfc.template.ast);
19
+ let layoutName = "default";
20
+ if (sfc.scriptSetup) visit(sfc.scriptSetup.ast);
21
+ function visit(node) {
22
+ if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === "definePageMeta" && node.arguments.length && ts.isObjectLiteralExpression(node.arguments[0])) {
23
+ for (const prop of node.arguments[0].properties) if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === "layout" && ts.isStringLiteral(prop.initializer)) {
24
+ layoutName = prop.initializer.text;
25
+ break;
26
+ }
27
+ } else ts.forEachChild(node, visit);
28
+ }
29
+ const expression = `\n// @ts-ignore\n{} as import("#build/dxup/layouts").Layouts["${layoutName}"]\n`;
30
+ const children = sfc.template.ast.children;
31
+ sfc.template.ast.children = [{
32
+ type: CompilerDOM.NodeTypes.ELEMENT,
33
+ ns: CompilerDOM.Namespaces.HTML,
34
+ tag: "component",
35
+ tagType: CompilerDOM.ElementTypes.COMPONENT,
36
+ loc: createVirtualLoc(),
37
+ props: [{
38
+ type: CompilerDOM.NodeTypes.DIRECTIVE,
39
+ name: "bind",
40
+ arg: {
41
+ type: CompilerDOM.NodeTypes.SIMPLE_EXPRESSION,
42
+ content: "is",
43
+ isStatic: true,
44
+ constType: CompilerDOM.ConstantTypes.CAN_STRINGIFY,
45
+ loc: createVirtualLoc("is")
46
+ },
47
+ exp: {
48
+ type: CompilerDOM.NodeTypes.SIMPLE_EXPRESSION,
49
+ content: expression,
50
+ isStatic: false,
51
+ constType: CompilerDOM.ConstantTypes.NOT_CONSTANT,
52
+ loc: createVirtualLoc(expression)
53
+ },
54
+ modifiers: [],
55
+ loc: createVirtualLoc(`:is="${expression}"`)
56
+ }],
57
+ children,
58
+ codegenNode: void 0
59
+ }];
60
+ }
61
+ });
62
+ function createVirtualLoc(source = "") {
63
+ return {
64
+ start: {
65
+ line: Number.MAX_VALUE,
66
+ column: Number.MAX_VALUE,
67
+ offset: Number.MAX_VALUE
68
+ },
69
+ end: {
70
+ line: Number.MAX_VALUE,
71
+ column: Number.MAX_VALUE,
72
+ offset: Number.MAX_VALUE
73
+ },
74
+ source
75
+ };
76
+ }
77
+ //#endregion
78
+ module.exports = plugin;
@@ -0,0 +1,10 @@
1
+ import { VueLanguagePlugin } from "@vue/language-core";
2
+
3
+ //#region src/module/named-layout-slots/language.d.ts
4
+ interface Config {
5
+ options: {
6
+ dirs: string[];
7
+ };
8
+ }
9
+ declare const plugin: VueLanguagePlugin<Config>;
10
+ export = plugin;
package/dist/module.d.mts CHANGED
@@ -1,5 +1,3 @@
1
- import * as _$_nuxt_schema0 from "@nuxt/schema";
2
-
3
1
  //#region src/module/index.d.ts
4
2
  interface ModuleOptions {
5
3
  features?: {
@@ -13,6 +11,11 @@ interface ModuleOptions {
13
11
  * @default true
14
12
  */
15
13
  importGlob?: boolean;
14
+ /**
15
+ * Whether to enable named layout slots support in the pages.
16
+ * @default false
17
+ */
18
+ namedLayoutSlots?: boolean;
16
19
  /**
17
20
  * Whether to enable Go to Definition for nitro routes in data fetching methods.
18
21
  * @default true
@@ -46,10 +49,11 @@ interface ModuleOptions {
46
49
  unofficial?: boolean;
47
50
  };
48
51
  }
49
- declare const _default: _$_nuxt_schema0.NuxtModule<ModuleOptions, {
52
+ declare const _default: import("@nuxt/schema").NuxtModule<ModuleOptions, {
50
53
  features: {
51
54
  components: true;
52
55
  importGlob: true;
56
+ namedLayoutSlots: false;
53
57
  nitroRoutes: true;
54
58
  pageMeta: true;
55
59
  runtimeConfig: true;
package/dist/module.mjs CHANGED
@@ -1,9 +1,14 @@
1
- import { addTemplate, defineNuxtModule, useNitro } from "@nuxt/kit";
1
+ import { addBuildPlugin, addTemplate, addTypeTemplate, createResolver, defineNuxtModule, useNitro } from "@nuxt/kit";
2
2
  import { Buffer } from "node:buffer";
3
3
  import { EventEmitter } from "node:events";
4
4
  import { mkdir, open, readFile, writeFile } from "node:fs/promises";
5
5
  import { watch } from "chokidar";
6
- import { dirname, join } from "pathe";
6
+ import { dirname, isAbsolute, join, relative } from "pathe";
7
+ import { genExport, genImport, genInlineTypeImport, genObjectKey } from "knitwork";
8
+ import { ElementTypes, NodeTypes, parse } from "@vue/compiler-dom";
9
+ import MagicString from "magic-string";
10
+ import { createUnplugin } from "unplugin";
11
+ import { parseAndWalk } from "oxc-walker";
7
12
  //#region package.json
8
13
  var name = "@dxup/nuxt";
9
14
  //#endregion
@@ -59,6 +64,161 @@ async function onComponentsRename(nuxt, { fileName, references }) {
59
64
  await Promise.all(tasks);
60
65
  }
61
66
  //#endregion
67
+ //#region src/module/named-layout-slots/utils.ts
68
+ const vueRE = /[?&]vue(?:&|$)/;
69
+ const typeRE = /[?&]type=[^&]*/;
70
+ function isVue(id) {
71
+ const index = id.indexOf("?");
72
+ const query = index !== -1 ? id.slice(index) : void 0;
73
+ if (query === void 0) return id.endsWith(".vue");
74
+ if (query === "?macro=true") return true;
75
+ if (!vueRE.test(query)) return false;
76
+ if (typeRE.test(query)) return false;
77
+ return true;
78
+ }
79
+ function isInDir(path, dir) {
80
+ const rel = relative(dir, path);
81
+ return rel !== ".." && !rel.startsWith("../") && !isAbsolute(rel);
82
+ }
83
+ function parseSFC(code) {
84
+ const sfc = parse(code, { parseMode: "sfc" });
85
+ let scriptSetup;
86
+ let template;
87
+ for (const node of sfc.children) {
88
+ if (node.type !== NodeTypes.ELEMENT) continue;
89
+ if (node.tag === "script" && node.props.some((prop) => prop.type === NodeTypes.ATTRIBUTE && (prop.name === "setup" || prop.name === "vapor"))) scriptSetup = node;
90
+ else if (node.tag === "template") template = node;
91
+ }
92
+ return {
93
+ scriptSetup,
94
+ template
95
+ };
96
+ }
97
+ //#endregion
98
+ //#region src/module/named-layout-slots/plugins/transform-layout.ts
99
+ const TransformLayoutPlugin = (options) => createUnplugin(() => ({
100
+ name: name + ":transform-layout",
101
+ enforce: "pre",
102
+ transformInclude: isVue,
103
+ transform: {
104
+ filter: { code: /<(?:nuxt-layout|NuxtLayout)/ },
105
+ handler(code) {
106
+ const { scriptSetup, template } = parseSFC(code);
107
+ const layout = template?.children.find((node) => node.type === NodeTypes.ELEMENT && (node.tag === "nuxt-layout" || node.tag === "NuxtLayout"));
108
+ if (!layout?.children.length) return;
109
+ const s = new MagicString(code);
110
+ const prefix = "\n" + genImport("#build/dxup/layouts.mjs", ["provideLayoutSlots"]);
111
+ const suffix = `\nconst __dxup_layoutSlots = provideLayoutSlots();\n`;
112
+ if (scriptSetup) {
113
+ s.appendLeft(scriptSetup.innerLoc.start.offset, prefix);
114
+ s.appendLeft(scriptSetup.innerLoc.end.offset, suffix);
115
+ } else s.prepend(`<script setup>${prefix + suffix}<\/script>\n\n`);
116
+ s.appendLeft(layout.children.at(-1).loc.end.offset, `
117
+ <template v-for="name in $route.meta.layoutSlots ?? []" :key="name" #[name]="props">
118
+ <component :is="() => __dxup_layoutSlots[name]?.(props)"/>
119
+ </template>`);
120
+ return {
121
+ code: s.toString(),
122
+ map: options.sourcemap ? s.generateMap({ hires: true }) : void 0
123
+ };
124
+ }
125
+ }
126
+ }));
127
+ //#endregion
128
+ //#region src/module/named-layout-slots/plugins/transform-page.ts
129
+ const TransformPagePlugin = (options) => createUnplugin(() => ({
130
+ name: name + ":transform-page",
131
+ enforce: "pre",
132
+ transformInclude: isVue,
133
+ transform(code, id) {
134
+ if (!options.dirs.some((dir) => isInDir(id, dir))) return;
135
+ const { scriptSetup, template } = parseSFC(code);
136
+ if (!template) return;
137
+ const slots = [];
138
+ for (const node of template.children) {
139
+ if (node.type !== NodeTypes.ELEMENT || node.tagType !== ElementTypes.TEMPLATE) continue;
140
+ const dir = node.props.find((prop) => prop.type === NodeTypes.DIRECTIVE && prop.name === "slot");
141
+ if (dir?.arg?.type === NodeTypes.SIMPLE_EXPRESSION && dir.arg.isStatic && dir.arg.content !== "" && dir.arg.content !== "default") slots.push(dir.arg.content);
142
+ }
143
+ if (!slots.length) return;
144
+ const s = new MagicString(code);
145
+ const imports = genImport("#build/dxup/layouts.mjs", ["LayoutSlotsForward"]);
146
+ const expression = `layoutSlots: [${slots.map((slot) => JSON.stringify(slot)).join(", ")}],\n`;
147
+ if (scriptSetup) {
148
+ let meta;
149
+ parseAndWalk(scriptSetup.innerLoc.source, id, {
150
+ parseOptions: { lang: scriptSetup.props.find((prop) => prop.type === NodeTypes.ATTRIBUTE && prop.name === "lang")?.value?.content ?? "ts" },
151
+ enter(node) {
152
+ if (node.type === "CallExpression" && node.callee.type === "Identifier" && node.callee.name === "definePageMeta" && node.arguments[0]?.type === "ObjectExpression") {
153
+ meta = node.arguments[0];
154
+ this.skip();
155
+ }
156
+ }
157
+ });
158
+ const start = scriptSetup.innerLoc.start.offset;
159
+ s.appendLeft(start, `\n${imports}\n`);
160
+ if (meta) s.appendLeft(meta.properties[0].start + start, expression);
161
+ else s.appendLeft(scriptSetup.innerLoc.start.offset, `\ndefinePageMeta({\n${expression}});\n`);
162
+ } else s.prepend(`<script setup>\n${imports}\ndefinePageMeta({\n${expression}});\n<\/script>\n\n`);
163
+ s.appendLeft(template.innerLoc.start.offset, `<LayoutSlotsForward>`);
164
+ s.appendLeft(template.innerLoc.end.offset, "</LayoutSlotsForward>");
165
+ return {
166
+ code: s.toString(),
167
+ map: options.sourcemap ? s.generateMap({ hires: true }) : void 0
168
+ };
169
+ }
170
+ }));
171
+ //#endregion
172
+ //#region src/module/named-layout-slots/module.ts
173
+ function setup(nuxt, pluginsVue) {
174
+ const resolver = createResolver(import.meta.url);
175
+ const pageDirs = nuxt.options._layers.map((layer) => join(layer.config.srcDir, layer.config.dir?.pages ?? "pages"));
176
+ pluginsVue.push({
177
+ name: "@dxup/nuxt/languages/named-layout-slots.cjs",
178
+ options: { dirs: pageDirs }
179
+ });
180
+ addTemplate({
181
+ filename: "dxup/layouts.mjs",
182
+ getContents() {
183
+ return `
184
+ ${genImport("vue", [
185
+ "inject",
186
+ "provide",
187
+ "shallowRef"
188
+ ])}
189
+ ${genExport(resolver.resolve("components/forward"), [{
190
+ name: "default",
191
+ as: "LayoutSlotsForward"
192
+ }])}
193
+ const injectionKey = Symbol();
194
+ export function provideLayoutSlots() {
195
+ const slots = shallowRef({});
196
+ provide(injectionKey, slots);
197
+ return slots;
198
+ }
199
+ export function injectLayoutSlots() {
200
+ return inject(injectionKey);
201
+ }
202
+ `.trimStart();
203
+ }
204
+ });
205
+ addTypeTemplate({
206
+ filename: "dxup/layouts.d.ts",
207
+ getContents({ app }) {
208
+ return `
209
+ export interface Layouts {
210
+ ${Object.values(app.layouts).map((layout) => ` ${genObjectKey(layout.name)}: ${genInlineTypeImport(layout.file)};`).join("\n")}
211
+ }
212
+ `.trimStart();
213
+ }
214
+ });
215
+ addBuildPlugin(TransformPagePlugin({
216
+ dirs: pageDirs,
217
+ sourcemap: !!nuxt.options.sourcemap.client
218
+ }));
219
+ addBuildPlugin(TransformLayoutPlugin({ sourcemap: !!nuxt.options.sourcemap.client }));
220
+ }
221
+ //#endregion
62
222
  //#region src/module/index.ts
63
223
  var module_default = defineNuxtModule().with({
64
224
  meta: {
@@ -68,6 +228,7 @@ var module_default = defineNuxtModule().with({
68
228
  defaults: { features: {
69
229
  components: true,
70
230
  importGlob: true,
231
+ namedLayoutSlots: false,
71
232
  nitroRoutes: true,
72
233
  pageMeta: true,
73
234
  runtimeConfig: true,
@@ -77,11 +238,14 @@ var module_default = defineNuxtModule().with({
77
238
  } },
78
239
  async setup(options, nuxt) {
79
240
  const pluginsTs = [{ name: "@dxup/nuxt" }];
80
- if (options.features?.unimport) pluginsTs.unshift({ name: "@dxup/unimport" });
241
+ const pluginsVue = [];
242
+ if (options.features.namedLayoutSlots) setup(nuxt, pluginsVue);
243
+ if (options.features.unimport) pluginsTs.unshift({ name: "@dxup/unimport" });
81
244
  append(pluginsTs, nuxt.options, "typescript", "tsConfig", "compilerOptions");
82
245
  append(pluginsTs, nuxt.options.nitro, "typescript", "tsConfig", "compilerOptions");
83
246
  append(pluginsTs, nuxt.options, "typescript", "sharedTsConfig", "compilerOptions");
84
247
  append(pluginsTs, nuxt.options, "typescript", "nodeTsConfig", "compilerOptions");
248
+ append(pluginsVue, nuxt.options, "typescript", "tsConfig", "vueCompilerOptions");
85
249
  addTemplate({
86
250
  filename: "dxup/data.json",
87
251
  write: true,
package/package.json CHANGED
@@ -1,13 +1,18 @@
1
1
  {
2
2
  "name": "@dxup/nuxt",
3
3
  "type": "module",
4
- "version": "0.4.1",
4
+ "version": "0.5.0",
5
5
  "description": "TypeScript plugin for Nuxt",
6
6
  "author": "KazariEX",
7
7
  "license": "MIT",
8
- "repository": "KazariEX/dxup",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "KazariEX/dxup"
11
+ },
9
12
  "exports": {
10
13
  ".": "./dist/module.mjs",
14
+ "./components/*": "./dist/components/*",
15
+ "./languages/*": "./dist/languages/*",
11
16
  "./package.json": "./package.json"
12
17
  },
13
18
  "main": "./dist/typescript.cjs",
@@ -15,26 +20,23 @@
15
20
  "files": [
16
21
  "dist"
17
22
  ],
18
- "peerDependencies": {
19
- "typescript": "*"
20
- },
21
- "peerDependenciesMeta": {
22
- "typescript": {
23
- "optional": true
24
- }
25
- },
26
23
  "dependencies": {
27
- "@nuxt/kit": "^4.4.2",
24
+ "@nuxt/kit": "^4.4.8",
25
+ "@vue/compiler-dom": "^3.5.38",
28
26
  "chokidar": "^5.0.0",
27
+ "knitwork": "^1.3.0",
28
+ "magic-string": "^0.30.21",
29
+ "oxc-walker": "^1.0.0",
29
30
  "pathe": "^2.0.3",
30
- "tinyglobby": "^0.2.16",
31
+ "tinyglobby": "^0.2.17",
32
+ "unplugin": "^3.0.0",
31
33
  "@dxup/unimport": "^0.1.2"
32
34
  },
33
35
  "devDependencies": {
34
36
  "@volar/language-core": "^2.4.28",
35
37
  "@volar/typescript": "^2.4.28",
36
- "@vue/language-core": "^3.2.7",
37
- "nuxt": "^4.4.2",
38
+ "@vue/language-core": "^3.3.5",
39
+ "nuxt": "^4.4.8",
38
40
  "typescript": "^6.0.3",
39
41
  "@dxup/shared": "0.0.0"
40
42
  },