nuxt-loaders 1.0.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 +84 -0
- package/dist/bin/loaders.d.mts +2 -0
- package/dist/bin/loaders.mjs +218 -0
- package/dist/module.d.mts +13 -0
- package/dist/module.json +9 -0
- package/dist/module.mjs +82 -0
- package/dist/runtime/components/Loader.d.vue.ts +3 -0
- package/dist/runtime/components/Loader.vue +20 -0
- package/dist/runtime/components/Loader.vue.d.ts +3 -0
- package/dist/runtime/composables/useLoader.d.ts +3 -0
- package/dist/runtime/composables/useLoader.js +5 -0
- package/dist/runtime/lib/log.d.ts +3 -0
- package/dist/runtime/lib/log.js +10 -0
- package/dist/runtime/lib/utils/route-rules.d.ts +3 -0
- package/dist/runtime/lib/utils/route-rules.js +91 -0
- package/dist/runtime/plugin.d.ts +2 -0
- package/dist/runtime/plugin.js +24 -0
- package/dist/runtime/plugins/loader-registry.d.ts +2 -0
- package/dist/runtime/plugins/loader-registry.js +16 -0
- package/dist/runtime/server/tsconfig.json +3 -0
- package/dist/runtime/tailwind.css +1 -0
- package/dist/types.d.mts +3 -0
- package/package.json +60 -0
package/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Get your module up and running quickly.
|
|
3
|
+
|
|
4
|
+
Find and replace all on all files (CMD+SHIFT+F):
|
|
5
|
+
- Name: My Module
|
|
6
|
+
- Package name: my-module
|
|
7
|
+
- Description: My new Nuxt module
|
|
8
|
+
-->
|
|
9
|
+
|
|
10
|
+
# My Module
|
|
11
|
+
|
|
12
|
+
[![npm version][npm-version-src]][npm-version-href]
|
|
13
|
+
[![npm downloads][npm-downloads-src]][npm-downloads-href]
|
|
14
|
+
[![License][license-src]][license-href]
|
|
15
|
+
[![Nuxt][nuxt-src]][nuxt-href]
|
|
16
|
+
|
|
17
|
+
My new Nuxt module for doing amazing things.
|
|
18
|
+
|
|
19
|
+
- [✨ Release Notes](/CHANGELOG.md)
|
|
20
|
+
<!-- - [🏀 Online playground](https://stackblitz.com/github/your-org/my-module?file=playground%2Fapp.vue) -->
|
|
21
|
+
<!-- - [📖 Documentation](https://example.com) -->
|
|
22
|
+
|
|
23
|
+
## Features
|
|
24
|
+
|
|
25
|
+
<!-- Highlight some of the features your module provide here -->
|
|
26
|
+
- ⛰ Foo
|
|
27
|
+
- 🚠 Bar
|
|
28
|
+
- 🌲 Baz
|
|
29
|
+
|
|
30
|
+
## Quick Setup
|
|
31
|
+
|
|
32
|
+
Install the module to your Nuxt application with one command:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npx nuxi module add my-module
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
That's it! You can now use My Module in your Nuxt app ✨
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
## Contribution
|
|
42
|
+
|
|
43
|
+
<details>
|
|
44
|
+
<summary>Local development</summary>
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# Install dependencies
|
|
48
|
+
npm install
|
|
49
|
+
|
|
50
|
+
# Generate type stubs
|
|
51
|
+
npm run dev:prepare
|
|
52
|
+
|
|
53
|
+
# Develop with the playground
|
|
54
|
+
npm run dev
|
|
55
|
+
|
|
56
|
+
# Build the playground
|
|
57
|
+
npm run dev:build
|
|
58
|
+
|
|
59
|
+
# Run ESLint
|
|
60
|
+
npm run lint
|
|
61
|
+
|
|
62
|
+
# Run Vitest
|
|
63
|
+
npm run test
|
|
64
|
+
npm run test:watch
|
|
65
|
+
|
|
66
|
+
# Release new version
|
|
67
|
+
npm run release
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
</details>
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
<!-- Badges -->
|
|
74
|
+
[npm-version-src]: https://img.shields.io/npm/v/my-module/latest.svg?style=flat&colorA=020420&colorB=00DC82
|
|
75
|
+
[npm-version-href]: https://npmjs.com/package/my-module
|
|
76
|
+
|
|
77
|
+
[npm-downloads-src]: https://img.shields.io/npm/dm/my-module.svg?style=flat&colorA=020420&colorB=00DC82
|
|
78
|
+
[npm-downloads-href]: https://npm.chart.dev/my-module
|
|
79
|
+
|
|
80
|
+
[license-src]: https://img.shields.io/npm/l/my-module.svg?style=flat&colorA=020420&colorB=00DC82
|
|
81
|
+
[license-href]: https://npmjs.com/package/my-module
|
|
82
|
+
|
|
83
|
+
[nuxt-src]: https://img.shields.io/badge/Nuxt-020420?logo=nuxt.js
|
|
84
|
+
[nuxt-href]: https://nuxt.com
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { readFile, writeFile, readdir } from 'node:fs/promises';
|
|
2
|
+
import { existsSync, mkdirSync, unlinkSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
|
|
5
|
+
const PREFIX = "[nuxt-loaders-cli]";
|
|
6
|
+
const logWarnCli = (...log) => {
|
|
7
|
+
console.warn(`${PREFIX}:warn`, ...log);
|
|
8
|
+
};
|
|
9
|
+
const logErrorCli = (...log) => {
|
|
10
|
+
console.error(`${PREFIX}:error`, ...log);
|
|
11
|
+
};
|
|
12
|
+
const logInfoCli = (...log) => {
|
|
13
|
+
console.log(`${PREFIX}:info`, ...log);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function omitKey(obj, key) {
|
|
17
|
+
const { [key]: _, ...rest } = obj;
|
|
18
|
+
return rest;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const ALLOWED_OPERATIONS = ["add", "remove"];
|
|
22
|
+
const REMOTE_TEMPLATE_STORE = "https://raw.githubusercontent.com/haileabt/nuxt-loaders/main/src/templates";
|
|
23
|
+
const operationAllowed = (operation) => {
|
|
24
|
+
if (!ALLOWED_OPERATIONS.includes(operation)) {
|
|
25
|
+
logErrorCli(`Invalid operation: ${operation}`);
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
return true;
|
|
29
|
+
};
|
|
30
|
+
const getLoadersConfig = async () => {
|
|
31
|
+
const path = join(process.cwd(), "loaders.config.json");
|
|
32
|
+
if (!existsSync(path)) {
|
|
33
|
+
logErrorCli(`No loaders.config.json found at ${path}`);
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
const config = await readFile(path, "utf-8");
|
|
37
|
+
return JSON.parse(config);
|
|
38
|
+
};
|
|
39
|
+
const addLoaderToConfig = async (slug, loaderName, version) => {
|
|
40
|
+
const path = join(process.cwd(), "loaders.config.json");
|
|
41
|
+
if (!existsSync(path)) {
|
|
42
|
+
logErrorCli(`No loaders.config.json found at ${path}`);
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
const configFile = await readFile(path, "utf-8");
|
|
46
|
+
const config = JSON.parse(configFile);
|
|
47
|
+
const newInstalledLoaders = {
|
|
48
|
+
...config.installedLoaders,
|
|
49
|
+
[slug]: {
|
|
50
|
+
file: loaderName,
|
|
51
|
+
version
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
const newConfig = {
|
|
55
|
+
...config,
|
|
56
|
+
installedLoaders: { ...newInstalledLoaders }
|
|
57
|
+
};
|
|
58
|
+
const newConfigFile = JSON.stringify(newConfig);
|
|
59
|
+
await writeFile(path, newConfigFile);
|
|
60
|
+
};
|
|
61
|
+
const removeLoaderFromConfig = async (slug) => {
|
|
62
|
+
const path = join(process.cwd(), "loaders.config.json");
|
|
63
|
+
if (!existsSync(path)) {
|
|
64
|
+
logErrorCli(`No loaders.config.json found at ${path}`);
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
const configFile = await readFile(path, "utf-8");
|
|
68
|
+
const config = JSON.parse(configFile);
|
|
69
|
+
config.installedLoaders = omitKey(config.installedLoaders, slug);
|
|
70
|
+
const newConfigFile = JSON.stringify(config);
|
|
71
|
+
await writeFile(path, newConfigFile);
|
|
72
|
+
return;
|
|
73
|
+
};
|
|
74
|
+
const getRemoteLoader = async (slug, index) => {
|
|
75
|
+
const loader = index[slug];
|
|
76
|
+
if (!loader) {
|
|
77
|
+
logErrorCli(`No loader found for slug: ${slug}`);
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
const res = await fetch(`${REMOTE_TEMPLATE_STORE}/${loader.file}`);
|
|
81
|
+
if (!res.ok) {
|
|
82
|
+
logErrorCli(`Failed to read remote loader: ${slug}`);
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
return res.text();
|
|
86
|
+
};
|
|
87
|
+
const getTemplatesIndex = async () => {
|
|
88
|
+
const res = await fetch(`${REMOTE_TEMPLATE_STORE}/index.json`);
|
|
89
|
+
if (!res.ok) {
|
|
90
|
+
logErrorCli(`Failed to read remote loader index`);
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
const indexJson = await res.text();
|
|
94
|
+
return JSON.parse(indexJson);
|
|
95
|
+
};
|
|
96
|
+
const handleAddLoader = async (slug, loadersConfig, index) => {
|
|
97
|
+
logInfoCli(`Adding loader: ${slug}...`);
|
|
98
|
+
let configSync = true;
|
|
99
|
+
if (!index[slug]) {
|
|
100
|
+
logErrorCli(`No loader found for slug: ${slug}`);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const loadersPath = join(process.cwd(), loadersConfig.loadersDir);
|
|
104
|
+
if (!existsSync(loadersPath)) {
|
|
105
|
+
mkdirSync(loadersPath);
|
|
106
|
+
logInfoCli(`Created loaders directory at ${loadersPath}`);
|
|
107
|
+
}
|
|
108
|
+
const loader = loadersConfig.installedLoaders[slug];
|
|
109
|
+
if (loader) {
|
|
110
|
+
if (existsSync(`${loadersPath}/${loader.file}`)) {
|
|
111
|
+
logErrorCli(`Loader already installed: ${slug}`);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
configSync = false;
|
|
115
|
+
}
|
|
116
|
+
const remoteLoader = await getRemoteLoader(slug, index);
|
|
117
|
+
if (!remoteLoader) {
|
|
118
|
+
logErrorCli("Error finding loader", slug);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const loaderPath = join(loadersPath, index[slug].file);
|
|
122
|
+
if (!configSync && loader) {
|
|
123
|
+
await addLoaderToConfig(slug, index[slug].file, index[slug].version);
|
|
124
|
+
logInfoCli(`Updated config for loader: ${slug}`);
|
|
125
|
+
} else if (!loader) {
|
|
126
|
+
await addLoaderToConfig(slug, index[slug].file, index[slug].version);
|
|
127
|
+
logInfoCli(`Updated config for loader: ${slug}`);
|
|
128
|
+
}
|
|
129
|
+
await writeFile(loaderPath, remoteLoader);
|
|
130
|
+
logInfoCli(`Successfully added loader: ${slug}`);
|
|
131
|
+
};
|
|
132
|
+
const handleRemoveLoader = async (slug, loadersConfig, index) => {
|
|
133
|
+
logInfoCli(`Removing loader: ${slug}...`);
|
|
134
|
+
const loadersPath = join(process.cwd(), loadersConfig.loadersDir);
|
|
135
|
+
if (!existsSync(loadersPath)) {
|
|
136
|
+
logWarnCli(
|
|
137
|
+
`Loaders path '${loadersPath}' not found. Skipping loader deletion.`
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
const loader = loadersConfig.installedLoaders[slug];
|
|
141
|
+
if (!loader) {
|
|
142
|
+
if (existsSync(`${loadersPath}/${index[slug]?.file}`)) ; else {
|
|
143
|
+
logWarnCli(`Loader '${slug}' not found in config or file system.`);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (loader && existsSync(`${loadersPath}/${loader.file}`)) {
|
|
148
|
+
await removeLoaderFromConfig(slug);
|
|
149
|
+
logInfoCli(`Removed loader '${slug}' from config.`);
|
|
150
|
+
unlinkSync(`${loadersPath}/${loader.file}`);
|
|
151
|
+
logInfoCli(`Deleted loader file: ${loader.file}`);
|
|
152
|
+
} else {
|
|
153
|
+
await removeLoaderFromConfig(slug);
|
|
154
|
+
logInfoCli(`Removed loader '${slug}' from config.`);
|
|
155
|
+
}
|
|
156
|
+
logInfoCli(`Successfully removed loader: ${slug}`);
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const dir = await readdir(process.cwd());
|
|
160
|
+
if (!dir.includes("nuxt.config.ts") && !dir.includes("nuxt.config.js")) {
|
|
161
|
+
logErrorCli(
|
|
162
|
+
"Not a Nuxt project. Please run this command in the root directory of your Nuxt project."
|
|
163
|
+
);
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
if (process.argv.length < 3) {
|
|
167
|
+
logErrorCli(
|
|
168
|
+
'No operation specified. Please use "nuxt-loaders add <loader>" or "nuxt-loaders remove <loader>"'
|
|
169
|
+
);
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
const args = process.argv.slice(2);
|
|
173
|
+
if (args.length < 1) {
|
|
174
|
+
logErrorCli(
|
|
175
|
+
'No loader specified. Please use "nuxt-loaders add <loader>" or "nuxt-loaders remove <loader>"'
|
|
176
|
+
);
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
const operation = args[0];
|
|
180
|
+
if (!operationAllowed(operation || "")) {
|
|
181
|
+
logErrorCli("Invalid operation: " + operation, "User 'add' or 'remove'");
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
const loaders = args.slice(1);
|
|
185
|
+
if (loaders.length < 1) {
|
|
186
|
+
logErrorCli(
|
|
187
|
+
'No loader specified. Please use "nuxt-loaders add <loader>" or "nuxt-loaders remove <loader>"'
|
|
188
|
+
);
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
logInfoCli(`Starting nuxt-loaders CLI...`);
|
|
192
|
+
const loadersConfig = await getLoadersConfig();
|
|
193
|
+
if (!loadersConfig) {
|
|
194
|
+
logErrorCli("Could not find loaders config. Please initialize the module.");
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
const templatesIndex = await getTemplatesIndex();
|
|
198
|
+
if (!templatesIndex) {
|
|
199
|
+
logErrorCli(
|
|
200
|
+
"Could not find any loader templates. Please stay tuned for some."
|
|
201
|
+
);
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}
|
|
204
|
+
for (const loader of loaders) {
|
|
205
|
+
if (!templatesIndex[loader]) {
|
|
206
|
+
logErrorCli(`No loader found for slug: ${loader}`);
|
|
207
|
+
process.exit(1);
|
|
208
|
+
}
|
|
209
|
+
if (operation === "add") {
|
|
210
|
+
await handleAddLoader(loader, loadersConfig, templatesIndex);
|
|
211
|
+
} else if (operation === "remove") {
|
|
212
|
+
await handleRemoveLoader(loader, loadersConfig, templatesIndex);
|
|
213
|
+
} else {
|
|
214
|
+
logErrorCli(`Invalid operation: ${operation}`);
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
logInfoCli(`Operation ${operation} completed successfully.`);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
|
+
|
|
3
|
+
interface ModuleOptions {
|
|
4
|
+
autoSetup: boolean;
|
|
5
|
+
loadersDir?: string;
|
|
6
|
+
routeRules: Record<string, string>;
|
|
7
|
+
_activeLoader: string;
|
|
8
|
+
_defaultLoader: string;
|
|
9
|
+
}
|
|
10
|
+
declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
|
|
11
|
+
|
|
12
|
+
export { _default as default };
|
|
13
|
+
export type { ModuleOptions };
|
package/dist/module.json
ADDED
package/dist/module.mjs
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { defineNuxtModule, createResolver, addPlugin, addComponentsDir, addImportsDir } from '@nuxt/kit';
|
|
2
|
+
import tailwindcss from '@tailwindcss/vite';
|
|
3
|
+
import { validateLoaderRules, getDefaultLoader } from '../dist/runtime/lib/utils/route-rules.js';
|
|
4
|
+
import { logInfo } from '../dist/runtime/lib/log.js';
|
|
5
|
+
import { existsSync } from 'node:fs';
|
|
6
|
+
import { writeFile } from 'node:fs/promises';
|
|
7
|
+
|
|
8
|
+
const DEFAULT_LOADERS_PATH = "app/components/loaders";
|
|
9
|
+
const module$1 = defineNuxtModule({
|
|
10
|
+
meta: {
|
|
11
|
+
name: "nuxt-loaders",
|
|
12
|
+
configKey: "loaders"
|
|
13
|
+
},
|
|
14
|
+
defaults: {
|
|
15
|
+
autoSetup: true,
|
|
16
|
+
routeRules: {},
|
|
17
|
+
_defaultLoader: "",
|
|
18
|
+
_activeLoader: ""
|
|
19
|
+
},
|
|
20
|
+
async setup(options, nuxt) {
|
|
21
|
+
const resolver = createResolver(import.meta.url);
|
|
22
|
+
nuxt.options.css ??= [];
|
|
23
|
+
nuxt.options.css.push(resolver.resolve("./runtime/tailwind.css"));
|
|
24
|
+
const path = await import('node:path');
|
|
25
|
+
const loadersDirPath = options.loadersDir ? path.resolve(nuxt.options.rootDir, options.loadersDir) : path.resolve(nuxt.options.rootDir, DEFAULT_LOADERS_PATH);
|
|
26
|
+
const configExists = existsSync("loaders.config.json");
|
|
27
|
+
if (!configExists) {
|
|
28
|
+
await writeFile(
|
|
29
|
+
"loaders.config.json",
|
|
30
|
+
JSON.stringify({
|
|
31
|
+
loadersDir: options.loadersDir ?? DEFAULT_LOADERS_PATH,
|
|
32
|
+
installedLoaders: {}
|
|
33
|
+
})
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
logInfo("Registering loaders directory:", loadersDirPath);
|
|
37
|
+
const { resolveFiles, addTemplate } = await import('@nuxt/kit');
|
|
38
|
+
const loaderFiles = await resolveFiles(loadersDirPath, "**/*.vue");
|
|
39
|
+
const template = addTemplate({
|
|
40
|
+
filename: "loader-plugin.mjs",
|
|
41
|
+
getContents: () => {
|
|
42
|
+
const imports = loaderFiles.map((file, index) => {
|
|
43
|
+
const name = file.split("/").pop()?.replace(".vue", "") || `Loader${index}`;
|
|
44
|
+
return `import ${name} from '${file}'`;
|
|
45
|
+
}).join("\n");
|
|
46
|
+
const registrations = loaderFiles.map((file, index) => {
|
|
47
|
+
const name = file.split("/").pop()?.replace(".vue", "") || `Loader${index}`;
|
|
48
|
+
return `nuxtApp.vueApp.component('${name}', ${name})`;
|
|
49
|
+
}).join("\n");
|
|
50
|
+
return `
|
|
51
|
+
import { defineNuxtPlugin } from '#app'
|
|
52
|
+
${imports}
|
|
53
|
+
|
|
54
|
+
export default defineNuxtPlugin((nuxtApp) => {
|
|
55
|
+
${registrations}
|
|
56
|
+
})
|
|
57
|
+
`;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
addPlugin(template.dst);
|
|
61
|
+
addComponentsDir({
|
|
62
|
+
path: resolver.resolve("./runtime/components"),
|
|
63
|
+
global: true
|
|
64
|
+
});
|
|
65
|
+
addImportsDir(resolver.resolve("./runtime/composables"));
|
|
66
|
+
nuxt.options.vite.plugins ??= [];
|
|
67
|
+
nuxt.options.vite.plugins.push(tailwindcss());
|
|
68
|
+
const validatedLoaderRules = validateLoaderRules(options.routeRules);
|
|
69
|
+
nuxt.options.runtimeConfig.public.loaders = {
|
|
70
|
+
...options,
|
|
71
|
+
routeRules: validatedLoaderRules,
|
|
72
|
+
loadersDir: loadersDirPath
|
|
73
|
+
};
|
|
74
|
+
if (options.routeRules && Object.keys(options.routeRules).length > 0) {
|
|
75
|
+
nuxt.options.runtimeConfig.public.loaders._activeLoader = getDefaultLoader(validatedLoaderRules) ?? "";
|
|
76
|
+
}
|
|
77
|
+
nuxt.options.runtimeConfig.public.loaders._defaultLoader = getDefaultLoader(validatedLoaderRules) ?? "";
|
|
78
|
+
addPlugin(resolver.resolve("./runtime/plugin"));
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
export { module$1 as default };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<component :is="loaderName" v-if="loaderName" />
|
|
4
|
+
</div>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup>
|
|
8
|
+
import { useNuxtApp } from "#app";
|
|
9
|
+
import { computed } from "vue";
|
|
10
|
+
import { logWarn } from "../lib/log";
|
|
11
|
+
const nuxt = useNuxtApp();
|
|
12
|
+
const loaderName = computed(() => {
|
|
13
|
+
const name = nuxt.$config.public.loaders._activeLoader || nuxt.$config.public.loaders._defaultLoader;
|
|
14
|
+
if (!name || !name.trim()) {
|
|
15
|
+
logWarn("No loaders have been set.");
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
return name;
|
|
19
|
+
});
|
|
20
|
+
</script>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const PREFIX = "[nuxt-loaders]";
|
|
2
|
+
export const logWarn = (...log) => {
|
|
3
|
+
console.warn(`${PREFIX}:warn`, ...log);
|
|
4
|
+
};
|
|
5
|
+
export const logError = (...log) => {
|
|
6
|
+
console.error(`${PREFIX}:error`, ...log);
|
|
7
|
+
};
|
|
8
|
+
export const logInfo = (...log) => {
|
|
9
|
+
console.log(`${PREFIX}:info`, ...log);
|
|
10
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare const validateLoaderRules: (routeRules: Record<string, string>) => Record<string, string>;
|
|
2
|
+
export declare const getDefaultLoader: (routeRules: Record<string, string>) => string | undefined;
|
|
3
|
+
export declare const getActiveLoader: (routeRules: Record<string, string>, path: string) => string | undefined;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { logInfo, logWarn } from "../log.js";
|
|
2
|
+
const checkLoaderNameValidity = (loaderName) => {
|
|
3
|
+
const loaderNamePattern = /^[A-Za-z][A-Za-z0-9_-]*$/;
|
|
4
|
+
if (!loaderNamePattern.test(loaderName)) {
|
|
5
|
+
return {
|
|
6
|
+
valid: false,
|
|
7
|
+
reason: `Loader name '${loaderName}' is invalid. Must start with a letter and contain only alphanumeric characters, hyphens, or underscores.`
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
return { valid: true };
|
|
11
|
+
};
|
|
12
|
+
const checkRoutePatternValidity = (route) => {
|
|
13
|
+
if (route === "*" || route === "/") {
|
|
14
|
+
return { valid: true };
|
|
15
|
+
}
|
|
16
|
+
const routePattern = /^\/[\w\-/]*$/;
|
|
17
|
+
if (!routePattern.test(route)) {
|
|
18
|
+
return {
|
|
19
|
+
valid: false,
|
|
20
|
+
reason: "Route pattern is invalid. Must start with '/' and contain only alphanumeric characters, hyphens, underscores, or slashes."
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
return { valid: true };
|
|
24
|
+
};
|
|
25
|
+
const checkRuleValidity = (route, loaderName) => {
|
|
26
|
+
const routeValidation = checkRoutePatternValidity(route);
|
|
27
|
+
if (!routeValidation.valid) {
|
|
28
|
+
return routeValidation;
|
|
29
|
+
}
|
|
30
|
+
return checkLoaderNameValidity(loaderName);
|
|
31
|
+
};
|
|
32
|
+
export const validateLoaderRules = (routeRules) => {
|
|
33
|
+
const newRules = {};
|
|
34
|
+
if (!routeRules) return {};
|
|
35
|
+
Object.keys(routeRules).forEach((route) => {
|
|
36
|
+
if (!route) return;
|
|
37
|
+
if (!route.trim()) {
|
|
38
|
+
logWarn(
|
|
39
|
+
`Loader rule is invalid. Failed trying to parse '${route}'. Route is empty so it is skipped.`
|
|
40
|
+
);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (!routeRules[route] || (routeRules[route]?.trim().length || 0) < 1 || routeRules[route] == void 0) {
|
|
44
|
+
logWarn(
|
|
45
|
+
`Loader rule is invalid. Failed trying to parse '${route}: ${routeRules[route]}'. Loader name is invalid so it is skipped.`
|
|
46
|
+
);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
let normalizedRoute = route;
|
|
50
|
+
if (route.endsWith("/") && route.length > 1) {
|
|
51
|
+
normalizedRoute = route.slice(0, -1);
|
|
52
|
+
}
|
|
53
|
+
const validResult = checkRuleValidity(normalizedRoute, routeRules[route]);
|
|
54
|
+
if (!validResult.valid) {
|
|
55
|
+
logWarn(
|
|
56
|
+
`Failed to parse rule '${normalizedRoute}'. Reason: ${validResult.reason}. Rule is skipped.`
|
|
57
|
+
);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
newRules[normalizedRoute] = routeRules[route];
|
|
61
|
+
logInfo(`Loaded rule '${normalizedRoute}' -> '${routeRules[route]}'`);
|
|
62
|
+
});
|
|
63
|
+
return newRules;
|
|
64
|
+
};
|
|
65
|
+
export const getDefaultLoader = (routeRules) => {
|
|
66
|
+
const defaultLoader = Object.keys(routeRules).filter((key) => {
|
|
67
|
+
return key === "*" || key === "/";
|
|
68
|
+
});
|
|
69
|
+
if (defaultLoader.length <= 0) {
|
|
70
|
+
return "";
|
|
71
|
+
}
|
|
72
|
+
if (defaultLoader.length > 1) {
|
|
73
|
+
return routeRules["*"];
|
|
74
|
+
}
|
|
75
|
+
return routeRules[defaultLoader[0]];
|
|
76
|
+
};
|
|
77
|
+
export const getActiveLoader = (routeRules, path) => {
|
|
78
|
+
const rules = Object.keys(routeRules).filter((rule2) => {
|
|
79
|
+
if (!path.startsWith(rule2)) return false;
|
|
80
|
+
if (!path.endsWith("*") && path != rule2) return false;
|
|
81
|
+
return true;
|
|
82
|
+
});
|
|
83
|
+
if (rules.length <= 0) {
|
|
84
|
+
return "";
|
|
85
|
+
}
|
|
86
|
+
const rule = rules.reduce((prev, curr) => {
|
|
87
|
+
if (curr.split("/").length > prev.split("/").length) return curr;
|
|
88
|
+
return prev;
|
|
89
|
+
});
|
|
90
|
+
return routeRules[rule];
|
|
91
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { defineNuxtPlugin } from "#app";
|
|
2
|
+
import { getActiveLoader } from "./lib/utils/route-rules.js";
|
|
3
|
+
import { useLoader } from "./composables/useLoader.js";
|
|
4
|
+
export default defineNuxtPlugin((nuxt) => {
|
|
5
|
+
const pageLoadingStartHooks = ["page:loading:start", "app:created"];
|
|
6
|
+
const pageLoadingEndHooks = ["page:loading:end", "app:mounted"];
|
|
7
|
+
const states = useLoader();
|
|
8
|
+
const { isLoading } = states;
|
|
9
|
+
const ctx = nuxt.$config.public.loaders;
|
|
10
|
+
const { autoSetup, routeRules } = ctx;
|
|
11
|
+
if (autoSetup) {
|
|
12
|
+
nuxt.hooks.beforeEach((e) => {
|
|
13
|
+
if (pageLoadingStartHooks.includes(e.name)) {
|
|
14
|
+
const newActive = getActiveLoader(routeRules, nuxt._route.fullPath);
|
|
15
|
+
if (newActive && newActive !== ctx._activeLoader) {
|
|
16
|
+
ctx._activeLoader = newActive;
|
|
17
|
+
}
|
|
18
|
+
isLoading.value = true;
|
|
19
|
+
} else if (pageLoadingEndHooks.includes(e.name)) {
|
|
20
|
+
isLoading.value = false;
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { defineNuxtPlugin } from "#app";
|
|
2
|
+
import { markRaw } from "vue";
|
|
3
|
+
export default defineNuxtPlugin((nuxtApp) => {
|
|
4
|
+
const loaderRegistry = /* @__PURE__ */ new Map();
|
|
5
|
+
nuxtApp.provide("loaderRegistry", {
|
|
6
|
+
register: (name, component) => {
|
|
7
|
+
loaderRegistry.set(name, markRaw(component));
|
|
8
|
+
},
|
|
9
|
+
get: (name) => {
|
|
10
|
+
return loaderRegistry.get(name);
|
|
11
|
+
},
|
|
12
|
+
has: (name) => {
|
|
13
|
+
return loaderRegistry.has(name);
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import "tailwindcss";
|
package/dist/types.d.mts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nuxt-loaders",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "My new Nuxt module",
|
|
5
|
+
"repository": "your-org/my-module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/types.d.mts",
|
|
11
|
+
"import": "./dist/module.mjs"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"main": "./dist/module.mjs",
|
|
15
|
+
"typesVersions": {
|
|
16
|
+
"*": {
|
|
17
|
+
".": [
|
|
18
|
+
"./dist/types.d.mts"
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist"
|
|
24
|
+
],
|
|
25
|
+
"bin": {
|
|
26
|
+
"loaders": "./dist/bin/loaders.mjs"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@nuxt/kit": "^4.2.1",
|
|
30
|
+
"@tailwindcss/vite": "^4.1.18",
|
|
31
|
+
"tailwindcss": "^4.1.18"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@nuxt/devtools": "^3.1.1",
|
|
35
|
+
"@nuxt/eslint-config": "^1.11.0",
|
|
36
|
+
"@nuxt/module-builder": "^1.0.2",
|
|
37
|
+
"@nuxt/schema": "^4.2.1",
|
|
38
|
+
"@nuxt/test-utils": "^3.21.0",
|
|
39
|
+
"@types/node": "^25.0.3",
|
|
40
|
+
"changelogen": "^0.6.2",
|
|
41
|
+
"eslint": "^9.39.1",
|
|
42
|
+
"eslint-config-prettier": "^10.1.8",
|
|
43
|
+
"nuxt": "^4.2.1",
|
|
44
|
+
"ts-node": "^10.9.2",
|
|
45
|
+
"typescript": "~5.9.3",
|
|
46
|
+
"vitest": "^3.2.0",
|
|
47
|
+
"vue-tsc": "^3.1.7"
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"dev": "npm run dev:prepare && nuxi dev playground",
|
|
51
|
+
"dev:build": "nuxi build playground",
|
|
52
|
+
"cli:build": "tsc",
|
|
53
|
+
"dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground",
|
|
54
|
+
"release": "npm run lint && npm run test && npm run prepack && changelogen --release && npm publish && git push --follow-tags",
|
|
55
|
+
"lint": "eslint .",
|
|
56
|
+
"test": "vitest run",
|
|
57
|
+
"test:watch": "vitest watch",
|
|
58
|
+
"test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit"
|
|
59
|
+
}
|
|
60
|
+
}
|