nuxt-content-assets 0.6.1 → 0.7.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 +28 -23
- package/dist/module.json +1 -1
- package/dist/module.mjs +208 -90
- package/dist/runtime/options.mjs +3 -3
- package/dist/runtime/plugin.mjs +21 -28
- package/dist/runtime/services/assets.d.ts +18 -0
- package/dist/runtime/services/assets.mjs +31 -0
- package/dist/runtime/services/index.d.ts +3 -0
- package/dist/runtime/services/index.mjs +3 -0
- package/dist/runtime/{utils → services}/paths.d.ts +2 -3
- package/dist/runtime/{utils → services}/paths.mjs +6 -4
- package/dist/runtime/services/sources.d.ts +10 -0
- package/dist/runtime/services/sources.mjs +39 -0
- package/dist/runtime/utils/debug.d.ts +1 -1
- package/dist/runtime/utils/debug.mjs +5 -8
- package/dist/runtime/utils/fs.d.ts +4 -0
- package/dist/runtime/utils/fs.mjs +20 -0
- package/dist/runtime/utils/index.d.ts +3 -4
- package/dist/runtime/utils/index.mjs +3 -4
- package/package.json +6 -2
- package/dist/runtime/utils/content.d.ts +0 -7
- package/dist/runtime/utils/content.mjs +0 -12
- /package/dist/runtime/utils/{assets.d.ts → assert.d.ts} +0 -0
- /package/dist/runtime/utils/{assets.mjs → assert.mjs} +0 -0
- /package/dist/runtime/utils/{config.d.ts → string.d.ts} +0 -0
- /package/dist/runtime/utils/{config.mjs → string.mjs} +0 -0
package/README.md
CHANGED
|
@@ -97,7 +97,7 @@ Relative paths are defined by anything not starting with a slash or `http`, for
|
|
|
97
97
|
|
|
98
98
|
```
|
|
99
99
|
image.jpg
|
|
100
|
-
|
|
100
|
+
assets/featured.png
|
|
101
101
|
../assets/cv.pdf
|
|
102
102
|
```
|
|
103
103
|
|
|
@@ -119,16 +119,16 @@ However, you can use relative paths in frontmatter:
|
|
|
119
119
|
---
|
|
120
120
|
title: Portfolio Item 1
|
|
121
121
|
images:
|
|
122
|
-
-
|
|
123
|
-
-
|
|
124
|
-
-
|
|
122
|
+
- assets/image-1.jpg
|
|
123
|
+
- assets/image-2.jpg
|
|
124
|
+
- assets/image-3.jpg
|
|
125
125
|
---
|
|
126
126
|
```
|
|
127
127
|
|
|
128
128
|
Then pass these to components like so:
|
|
129
129
|
|
|
130
130
|
```markdown
|
|
131
|
-
::gallery{:
|
|
131
|
+
::gallery{:data="images"}
|
|
132
132
|
::
|
|
133
133
|
```
|
|
134
134
|
|
|
@@ -196,7 +196,7 @@ export default defineNuxtConfig({
|
|
|
196
196
|
// where to generate and serve the assets from
|
|
197
197
|
output: 'assets/content/[path]/[file]',
|
|
198
198
|
|
|
199
|
-
//
|
|
199
|
+
// include additional extensions
|
|
200
200
|
additionalExtensions: 'html',
|
|
201
201
|
|
|
202
202
|
// completely replace supported extensions
|
|
@@ -216,38 +216,43 @@ export default defineNuxtConfig({
|
|
|
216
216
|
The output path can be customised using a template string:
|
|
217
217
|
|
|
218
218
|
```
|
|
219
|
-
assets/
|
|
219
|
+
assets/[name]-[hash].[ext]
|
|
220
220
|
```
|
|
221
221
|
|
|
222
222
|
The first part of the path is where you want content assets to be served from:
|
|
223
223
|
|
|
224
224
|
```
|
|
225
|
-
assets/
|
|
225
|
+
assets/
|
|
226
226
|
```
|
|
227
227
|
|
|
228
|
-
The optional second part of the path indicates the relative location of each
|
|
228
|
+
The optional second part of the path indicates the relative location of each asset.
|
|
229
229
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
|
233
|
-
|
|
234
|
-
| `[
|
|
235
|
-
| `[
|
|
236
|
-
| `[
|
|
237
|
-
| `[
|
|
230
|
+
The table below shows replacements for the asset `content/posts/2023-01-01/featured.jpg`:
|
|
231
|
+
|
|
232
|
+
| Token | Description | Example |
|
|
233
|
+
|-------------|----------------------------------------------------------------------------------------------------|----------------------------|
|
|
234
|
+
| `[key]` | The config key of the source (see [sources](https://content.nuxtjs.org/api/configuration#sources)) | `content` |
|
|
235
|
+
| `[path]` | The relative path of the source | `content/posts/2023-01-01` |
|
|
236
|
+
| `[folder]` | The relative path of the file's folder | `posts/2023-01-01` |
|
|
237
|
+
| `[file]` | The full filename of the file | `featured.jpg` |
|
|
238
|
+
| `[name]` | The name of the file without the extension | `featured` |
|
|
239
|
+
| `[hash]` | A hash of the absolute source path | `9M00N4l9A0` |
|
|
240
|
+
| `[extname]` | The full extension with the dot | `.jpg` |
|
|
241
|
+
| `[ext]` | The extension without the dot | `jpg` |
|
|
238
242
|
|
|
239
243
|
For example:
|
|
240
244
|
|
|
241
|
-
| Template
|
|
242
|
-
|
|
243
|
-
| `assets/
|
|
244
|
-
| `assets/
|
|
245
|
-
| `
|
|
245
|
+
| Template | Output |
|
|
246
|
+
|------------------------------|------------------------------------------------|
|
|
247
|
+
| `assets/[path]/[file]` | `assets/content/posts/2023-01-01/featured.jpg` |
|
|
248
|
+
| `assets/[folder]/[file]` | `assets/posts/2023-01-01/featured.jpg` |
|
|
249
|
+
| `assets/[name]-[hash].[ext]` | `assets/featured-9M00N4l9A0.jpg` |
|
|
250
|
+
| `assets/[hash].[ext]` | `assets/9M00N4l9A0.jpg` |
|
|
246
251
|
|
|
247
252
|
Note that the module defaults to:
|
|
248
253
|
|
|
249
254
|
```
|
|
250
|
-
/assets/
|
|
255
|
+
/assets/[path]/[file]
|
|
251
256
|
```
|
|
252
257
|
|
|
253
258
|
### Extensions
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
import { createResolver, defineNuxtModule, addTemplate } from '@nuxt/kit';
|
|
2
|
-
import getImageSize from 'image-size';
|
|
3
|
-
import glob from 'glob';
|
|
4
1
|
import * as Fs from 'fs';
|
|
5
2
|
import * as Path from 'path';
|
|
6
3
|
import Path__default from 'path';
|
|
4
|
+
import { createResolver, defineNuxtModule, addTemplate } from '@nuxt/kit';
|
|
5
|
+
import getImageSize from 'image-size';
|
|
7
6
|
import { hash } from 'ohash';
|
|
7
|
+
import glob from 'glob';
|
|
8
|
+
import { createStorage } from 'unstorage';
|
|
9
|
+
import githubDriver from 'unstorage/drivers/github';
|
|
8
10
|
|
|
9
11
|
function matchWords(value) {
|
|
10
12
|
return typeof value === "string" ? value.match(/\w+/g) || [] : [];
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
const defaults = {
|
|
14
|
-
assetsDir: "assets/
|
|
15
|
-
assetsPattern: "[
|
|
16
|
+
assetsDir: "/assets/",
|
|
17
|
+
assetsPattern: "[path]/[file]"
|
|
16
18
|
};
|
|
17
19
|
const imageExtensions = matchWords("png jpg jpeg gif svg webp ico");
|
|
18
20
|
const mediaExtensions = matchWords("mp3 m4a wav mp4 mov webm ogg avi flv avchd");
|
|
@@ -28,40 +30,57 @@ function isImage(path) {
|
|
|
28
30
|
return imageExtensions.includes(ext);
|
|
29
31
|
}
|
|
30
32
|
|
|
31
|
-
function getSources(sources) {
|
|
32
|
-
return Object.keys(sources).reduce((output, key) => {
|
|
33
|
-
const source = sources[key];
|
|
34
|
-
if (source) {
|
|
35
|
-
const { driver, base } = source;
|
|
36
|
-
if (driver === "fs") {
|
|
37
|
-
output[key] = base;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
return output;
|
|
41
|
-
}, {});
|
|
42
|
-
}
|
|
43
|
-
|
|
44
33
|
const moduleName = "nuxt-content-assets";
|
|
45
34
|
const moduleKey = "content-assets";
|
|
46
35
|
|
|
47
36
|
function log(...data) {
|
|
48
37
|
console.info(`[${moduleKey}]`, ...data);
|
|
49
38
|
}
|
|
39
|
+
function warn(...data) {
|
|
40
|
+
console.warn(`[${moduleKey}]`, ...data);
|
|
41
|
+
}
|
|
42
|
+
function list(message, items) {
|
|
43
|
+
log(`${message}:
|
|
44
|
+
|
|
45
|
+
${items.map((item) => ` - ${item}`).join("\n")}
|
|
46
|
+
`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function writeFile(path, data) {
|
|
50
|
+
const text = typeof data === "object" ? JSON.stringify(data, null, " ") : String(data);
|
|
51
|
+
createFolder(Path.dirname(path));
|
|
52
|
+
Fs.writeFileSync(path, text, { encoding: "utf8" });
|
|
53
|
+
}
|
|
54
|
+
function copyFile(src, trg) {
|
|
55
|
+
createFolder(Path.dirname(trg));
|
|
56
|
+
Fs.copyFileSync(src, trg);
|
|
57
|
+
}
|
|
58
|
+
function createFolder(path) {
|
|
59
|
+
Fs.mkdirSync(path, { recursive: true });
|
|
60
|
+
}
|
|
61
|
+
function removeFolder(path) {
|
|
62
|
+
const isDownstream = path.startsWith(Path.resolve());
|
|
63
|
+
if (isDownstream) {
|
|
64
|
+
Fs.rmSync(path, { recursive: true, force: true });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
50
67
|
|
|
51
68
|
const replacers = {
|
|
52
|
-
|
|
69
|
+
key: (src) => Path__default.dirname(src).split("/").filter((e) => e).shift() || "",
|
|
70
|
+
path: (src) => Path__default.dirname(src),
|
|
71
|
+
folder: (src) => Path__default.dirname(src).replace(/[^/]+\//, ""),
|
|
53
72
|
file: (src) => Path__default.basename(src),
|
|
54
73
|
name: (src) => Path__default.basename(src, Path__default.extname(src)),
|
|
55
74
|
extname: (src) => Path__default.extname(src),
|
|
56
75
|
ext: (src) => Path__default.extname(src).substring(1),
|
|
57
76
|
hash: (src) => hash({ src })
|
|
58
77
|
};
|
|
59
|
-
function interpolatePattern(pattern, src,
|
|
78
|
+
function interpolatePattern(pattern, src, warn = false) {
|
|
60
79
|
return Path__default.join(pattern.replace(/\[\w+]/g, (match) => {
|
|
61
80
|
const name = match.substring(1, match.length - 1);
|
|
62
81
|
const fn = replacers[name];
|
|
63
82
|
if (fn) {
|
|
64
|
-
return fn(src
|
|
83
|
+
return fn(src);
|
|
65
84
|
}
|
|
66
85
|
if (warn) {
|
|
67
86
|
log(`Unknown output token ${match}`, true);
|
|
@@ -70,6 +89,69 @@ function interpolatePattern(pattern, src, dir, warn = false) {
|
|
|
70
89
|
}));
|
|
71
90
|
}
|
|
72
91
|
|
|
92
|
+
function getAssetConfig(srcDir, srcAbs, pattern, hints) {
|
|
93
|
+
let width = void 0;
|
|
94
|
+
let height = void 0;
|
|
95
|
+
let ratio = void 0;
|
|
96
|
+
let query = void 0;
|
|
97
|
+
if (hints.length && isImage(srcAbs)) {
|
|
98
|
+
try {
|
|
99
|
+
const size = getImageSize(srcAbs);
|
|
100
|
+
if (hints.includes("style")) {
|
|
101
|
+
ratio = `${size.width}/${size.height}`;
|
|
102
|
+
}
|
|
103
|
+
if (hints.includes("attrs")) {
|
|
104
|
+
width = size.width;
|
|
105
|
+
height = size.height;
|
|
106
|
+
}
|
|
107
|
+
if (hints.includes("url")) {
|
|
108
|
+
query = `?width=${width}&height=${height}`;
|
|
109
|
+
}
|
|
110
|
+
} catch (err) {
|
|
111
|
+
warn(`could not read image "${srcAbs}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const srcRel = Path.basename(srcDir) + srcAbs.substring(srcDir.length);
|
|
115
|
+
const srcAttr = interpolatePattern(pattern, srcRel);
|
|
116
|
+
const id = srcRel.replaceAll("/", ":");
|
|
117
|
+
return { id, srcRel, srcAttr, width, height, ratio, query };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function getGithubAssets(key, source, tempPath, extensions) {
|
|
121
|
+
const storage = createStorage();
|
|
122
|
+
storage.mount(key, githubDriver({
|
|
123
|
+
repo: source.repo,
|
|
124
|
+
branch: source.branch || "main",
|
|
125
|
+
dir: source.dir || "/",
|
|
126
|
+
ttl: source.ttl || 600
|
|
127
|
+
}));
|
|
128
|
+
const rx = new RegExp(`.${extensions.join("|")}$`);
|
|
129
|
+
const keys = await storage.getKeys();
|
|
130
|
+
const assetKeys = keys.filter((key2) => rx.test(key2));
|
|
131
|
+
const assetItems = await Promise.all(assetKeys.map(async (id) => {
|
|
132
|
+
const data = await storage.getItem(id);
|
|
133
|
+
return { id, data };
|
|
134
|
+
}));
|
|
135
|
+
const prefix = source.prefix || "";
|
|
136
|
+
const paths = [];
|
|
137
|
+
for (const { id, data } of assetItems) {
|
|
138
|
+
if (data) {
|
|
139
|
+
const path = id.replaceAll(":", "/");
|
|
140
|
+
const absPath = Path.join(tempPath, path.replace(key, `${key}/${prefix}`));
|
|
141
|
+
const absFolder = Path.dirname(absPath);
|
|
142
|
+
const buffer = data.constructor.name === "Blob" ? Buffer.from(await data.arrayBuffer()) : typeof data === "object" ? JSON.stringify(data, null, " ") : String(data);
|
|
143
|
+
Fs.mkdirSync(absFolder, { recursive: true });
|
|
144
|
+
Fs.writeFileSync(absPath, buffer);
|
|
145
|
+
paths.push(absPath);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return paths;
|
|
149
|
+
}
|
|
150
|
+
function getFsAssets(path, extensions) {
|
|
151
|
+
const pattern = `${path}/**/*.{${extensions.join(",")}}`;
|
|
152
|
+
return glob.globSync(pattern) || [];
|
|
153
|
+
}
|
|
154
|
+
|
|
73
155
|
const resolve = createResolver(import.meta.url).resolve;
|
|
74
156
|
const module = defineNuxtModule({
|
|
75
157
|
meta: {
|
|
@@ -86,98 +168,134 @@ const module = defineNuxtModule({
|
|
|
86
168
|
imageSize: "",
|
|
87
169
|
debug: false
|
|
88
170
|
},
|
|
89
|
-
setup(options, nuxt) {
|
|
171
|
+
async setup(options, nuxt) {
|
|
90
172
|
var _a, _b;
|
|
91
173
|
const pluginPath = resolve("./runtime/plugin");
|
|
92
174
|
const buildPath = nuxt.options.buildDir;
|
|
93
|
-
const cachePath = Path.
|
|
175
|
+
const cachePath = Path.join(buildPath, "content-assets");
|
|
176
|
+
const publicPath = Path.join(cachePath, "public");
|
|
177
|
+
const tempPath = Path.resolve("node_modules/.nuxt-content-assets");
|
|
178
|
+
const dump = (name, data) => {
|
|
179
|
+
const path = `${cachePath}/debug/${name}.json`;
|
|
180
|
+
log(`Dumping "${Path.relative("", path)}"`);
|
|
181
|
+
writeFile(path, data);
|
|
182
|
+
};
|
|
94
183
|
if (options.debug) {
|
|
95
184
|
log("Removing cache folders...");
|
|
96
185
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
return output2;
|
|
104
|
-
}, {});
|
|
105
|
-
if (Object.keys(sources).length === 0 || !sources.content) {
|
|
106
|
-
const content = nuxt.options.srcDir + "/content";
|
|
107
|
-
if (Fs.existsSync(content)) {
|
|
108
|
-
sources.content = content;
|
|
109
|
-
}
|
|
186
|
+
removeFolder(Path.join(buildPath, "content-cache"));
|
|
187
|
+
removeFolder(cachePath);
|
|
188
|
+
removeFolder(tempPath);
|
|
189
|
+
(_a = nuxt.options).content || (_a.content = {});
|
|
190
|
+
if (nuxt.options.content) {
|
|
191
|
+
(_b = nuxt.options.content).ignores || (_b.ignores = []);
|
|
110
192
|
}
|
|
111
193
|
const output = options.output || defaults.assetsDir;
|
|
112
194
|
const matches = output.match(/([^[]+)(.*)?/);
|
|
113
|
-
const assetsDir = matches ? matches[1] : defaults.assetsDir;
|
|
195
|
+
const assetsDir = matches ? matches[1].replace(/^\/*/, "/").replace(/\/*$/, "") : defaults.assetsDir;
|
|
114
196
|
const assetsPattern = (matches ? matches[2] : "") || defaults.assetsPattern;
|
|
115
|
-
interpolatePattern(assetsPattern, "",
|
|
197
|
+
interpolatePattern(assetsPattern, "", true);
|
|
116
198
|
const imageFlags = matchWords(options.imageSize);
|
|
117
199
|
if (options.extensions?.trim()) {
|
|
118
200
|
extensions.splice(0, extensions.length, ...matchWords(options.extensions));
|
|
119
201
|
} else if (options.additionalExtensions) {
|
|
120
202
|
extensions.push(...matchWords(options.additionalExtensions));
|
|
121
203
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
let height = void 0;
|
|
125
|
-
let ratio = void 0;
|
|
126
|
-
let query = void 0;
|
|
127
|
-
if (imageFlags.length && isImage(src)) {
|
|
128
|
-
const size = getImageSize(src);
|
|
129
|
-
if (imageFlags.includes("style")) {
|
|
130
|
-
ratio = `${size.width}/${size.height}`;
|
|
131
|
-
}
|
|
132
|
-
if (imageFlags.includes("attrs")) {
|
|
133
|
-
width = size.width;
|
|
134
|
-
height = size.height;
|
|
135
|
-
}
|
|
136
|
-
if (imageFlags.includes("url")) {
|
|
137
|
-
query = `?width=${width}&height=${height}`;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
const id = Path.join(Path.basename(dir), Path.relative(dir, src)).replaceAll("/", ":");
|
|
141
|
-
const file = interpolatePattern(pattern, src, dir);
|
|
142
|
-
const trg = Path.join(cachePath, assetsDir, file);
|
|
143
|
-
const rel = Path.join("/", assetsDir, file);
|
|
144
|
-
return { id, file, trg, rel, width, height, ratio, query };
|
|
204
|
+
if (options.debug) {
|
|
205
|
+
log("Preparing sources...");
|
|
145
206
|
}
|
|
146
|
-
const publicFolder = Path.join(cachePath, assetsDir);
|
|
147
|
-
const sourceFolders = Object.values(sources);
|
|
148
207
|
const assets = {};
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
208
|
+
const sources = nuxt.options._layers.map((layer) => layer.config?.content?.sources).reduce((output2, sources2) => {
|
|
209
|
+
if (sources2) {
|
|
210
|
+
Object.assign(output2, sources2);
|
|
211
|
+
}
|
|
212
|
+
return output2;
|
|
213
|
+
}, {});
|
|
214
|
+
if (Object.keys(sources).length === 0 || !sources.content) {
|
|
215
|
+
const content = nuxt.options.srcDir + "/content";
|
|
216
|
+
if (Fs.existsSync(content)) {
|
|
217
|
+
sources.content = {
|
|
218
|
+
driver: "fs",
|
|
219
|
+
base: content
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
for (const [key, source] of Object.entries(sources)) {
|
|
224
|
+
const { driver } = source;
|
|
225
|
+
let srcDir = "";
|
|
226
|
+
let paths = [];
|
|
227
|
+
switch (driver) {
|
|
228
|
+
case "fs":
|
|
229
|
+
paths = getFsAssets(source.base, extensions);
|
|
230
|
+
srcDir = source.base;
|
|
231
|
+
break;
|
|
232
|
+
case "github":
|
|
233
|
+
paths = await getGithubAssets(key, source, tempPath, extensions);
|
|
234
|
+
srcDir = Path.join(tempPath, key);
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
if (options.debug) {
|
|
238
|
+
log(`Prepared ${paths.length} paths for source "${key}"`);
|
|
239
|
+
}
|
|
240
|
+
if (paths.length) {
|
|
241
|
+
paths.forEach((src) => {
|
|
242
|
+
const {
|
|
243
|
+
id,
|
|
244
|
+
srcRel,
|
|
245
|
+
srcAttr,
|
|
246
|
+
width,
|
|
247
|
+
height,
|
|
248
|
+
ratio,
|
|
249
|
+
query
|
|
250
|
+
} = getAssetConfig(srcDir, src, assetsPattern, imageFlags);
|
|
251
|
+
nuxt.options.content.ignores.push(id);
|
|
252
|
+
const trg = Path.join(publicPath, assetsDir, srcAttr);
|
|
253
|
+
assets[srcRel] = {
|
|
254
|
+
src,
|
|
255
|
+
trg,
|
|
256
|
+
config: {
|
|
257
|
+
srcAttr: Path.join(assetsDir, srcAttr),
|
|
258
|
+
width,
|
|
259
|
+
height,
|
|
260
|
+
ratio,
|
|
261
|
+
query
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
});
|
|
265
|
+
}
|
|
152
266
|
}
|
|
153
|
-
sourceFolders.forEach((folder) => {
|
|
154
|
-
const pattern = `${folder}/**/*.{${extensions.join(",")}}`;
|
|
155
|
-
const paths = glob.globSync(pattern);
|
|
156
|
-
paths.forEach((src) => {
|
|
157
|
-
const config = getAssetConfig(assetsPattern, src, folder);
|
|
158
|
-
nuxt.options.content.ignores.push(config.id);
|
|
159
|
-
assets[src] = config;
|
|
160
|
-
});
|
|
161
|
-
});
|
|
162
267
|
nuxt.hook("build:before", function() {
|
|
163
|
-
Fs.mkdirSync(publicFolder, { recursive: true });
|
|
164
268
|
if (options.debug) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
${
|
|
169
|
-
|
|
269
|
+
dump("assets", assets);
|
|
270
|
+
}
|
|
271
|
+
if (options.debug) {
|
|
272
|
+
log(`Copying ${Object.keys(assets).length} assets...`);
|
|
273
|
+
}
|
|
274
|
+
const copied = [];
|
|
275
|
+
const failed = [];
|
|
276
|
+
for (const [key, { src, trg }] of Object.entries(assets)) {
|
|
277
|
+
if (Fs.existsSync(src)) {
|
|
278
|
+
copyFile(src, trg);
|
|
279
|
+
copied.push(key);
|
|
280
|
+
} else {
|
|
281
|
+
failed.push(key);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
if (options.debug) {
|
|
285
|
+
if (copied.length) {
|
|
286
|
+
list("Copied", copied);
|
|
287
|
+
}
|
|
288
|
+
if (failed.length) {
|
|
289
|
+
list("Failed to copy", failed);
|
|
290
|
+
}
|
|
170
291
|
}
|
|
171
|
-
Object.keys(assets).forEach((src) => {
|
|
172
|
-
const { trg } = assets[src];
|
|
173
|
-
const trgFolder = Path.dirname(trg);
|
|
174
|
-
Fs.mkdirSync(trgFolder, { recursive: true });
|
|
175
|
-
Fs.copyFileSync(src, trg);
|
|
176
|
-
});
|
|
177
292
|
});
|
|
293
|
+
const nitroAssets = Object.entries(assets).reduce((output2, [key, value]) => {
|
|
294
|
+
output2[key] = value.config;
|
|
295
|
+
return output2;
|
|
296
|
+
}, {});
|
|
178
297
|
const virtualConfig = [
|
|
179
|
-
`export const assets = ${JSON.stringify(
|
|
180
|
-
`export const sources = ${JSON.stringify(sources)}`
|
|
298
|
+
`export const assets = ${JSON.stringify(nitroAssets, null, " ")}`
|
|
181
299
|
].join("\n");
|
|
182
300
|
nuxt.options.alias[`#${moduleName}`] = addTemplate({
|
|
183
301
|
filename: `${moduleName}.mjs`,
|
|
@@ -190,7 +308,7 @@ ${paths.join("\n")}
|
|
|
190
308
|
config.virtual[`#${moduleName}`] = virtualConfig;
|
|
191
309
|
config.publicAssets || (config.publicAssets = []);
|
|
192
310
|
config.publicAssets.push({
|
|
193
|
-
dir:
|
|
311
|
+
dir: publicPath
|
|
194
312
|
// maxAge: 60 * 60 * 24 * 365 // 1 year
|
|
195
313
|
});
|
|
196
314
|
});
|
package/dist/runtime/options.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { matchWords } from "./utils/
|
|
1
|
+
import { matchWords } from "./utils/string.mjs";
|
|
2
2
|
export const defaults = {
|
|
3
|
-
assetsDir: "assets/
|
|
4
|
-
assetsPattern: "[
|
|
3
|
+
assetsDir: "/assets/",
|
|
4
|
+
assetsPattern: "[path]/[file]"
|
|
5
5
|
};
|
|
6
6
|
export const imageExtensions = matchWords("png jpg jpeg gif svg webp ico");
|
|
7
7
|
export const mediaExtensions = matchWords("mp3 m4a wav mp4 mov webm ogg avi flv avchd");
|
package/dist/runtime/plugin.mjs
CHANGED
|
@@ -2,49 +2,42 @@ import Path from "path";
|
|
|
2
2
|
import { visit } from "unist-util-visit";
|
|
3
3
|
import { isValidAsset, walk } from "./utils/index.mjs";
|
|
4
4
|
import { tags } from "./options.mjs";
|
|
5
|
-
import { assets
|
|
6
|
-
function
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
const relPath = parts.join("/");
|
|
10
|
-
const absBase = sources[key];
|
|
11
|
-
return Path.join(absBase, relPath);
|
|
12
|
-
}
|
|
13
|
-
function getAsset(absDoc, relAsset) {
|
|
14
|
-
const absAsset = Path.join(Path.dirname(absDoc), relAsset);
|
|
15
|
-
return assets[absAsset] || {};
|
|
5
|
+
import { assets } from "#nuxt-content-assets";
|
|
6
|
+
function getAsset(srcDoc, relAsset) {
|
|
7
|
+
const srcAsset = Path.join(Path.dirname(srcDoc), relAsset);
|
|
8
|
+
return assets[srcAsset] || {};
|
|
16
9
|
}
|
|
17
10
|
const plugin = async (nitro) => {
|
|
18
11
|
nitro.hooks.hook("content:file:afterParse", async (file) => {
|
|
19
12
|
if (file._id.endsWith(".md")) {
|
|
20
|
-
const
|
|
13
|
+
const srcDoc = file._id.split(":").join("/");
|
|
21
14
|
const filter = (value, key) => !(String(key).startsWith("_") || key === "body");
|
|
22
15
|
walk(file, (value, parent, key) => {
|
|
23
16
|
if (isValidAsset(value)) {
|
|
24
|
-
const {
|
|
25
|
-
if (
|
|
26
|
-
parent[key] =
|
|
17
|
+
const { srcAttr, query } = getAsset(srcDoc, value);
|
|
18
|
+
if (srcAttr) {
|
|
19
|
+
parent[key] = srcAttr + (query || "");
|
|
27
20
|
}
|
|
28
21
|
}
|
|
29
22
|
}, filter);
|
|
30
23
|
visit(file.body, (n) => tags.includes(n.tag), (node) => {
|
|
31
24
|
if (node.props.src) {
|
|
32
|
-
const {
|
|
33
|
-
if (
|
|
34
|
-
node.props.src =
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
25
|
+
const { srcAttr, width, height, ratio } = getAsset(srcDoc, node.props.src);
|
|
26
|
+
if (srcAttr) {
|
|
27
|
+
node.props.src = srcAttr;
|
|
28
|
+
if (width && height) {
|
|
29
|
+
node.props.width = width;
|
|
30
|
+
node.props.height = height;
|
|
31
|
+
}
|
|
32
|
+
if (ratio) {
|
|
33
|
+
node.props.style = `aspect-ratio:${ratio}`;
|
|
34
|
+
}
|
|
42
35
|
}
|
|
43
36
|
} else if (node.tag === "a") {
|
|
44
37
|
if (node.props.href) {
|
|
45
|
-
const {
|
|
46
|
-
if (
|
|
47
|
-
node.props.href =
|
|
38
|
+
const { srcAttr } = getAsset(srcDoc, node.props.href);
|
|
39
|
+
if (srcAttr) {
|
|
40
|
+
node.props.href = srcAttr;
|
|
48
41
|
node.props.target = "_blank";
|
|
49
42
|
}
|
|
50
43
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type AssetConfig = {
|
|
2
|
+
id: string;
|
|
3
|
+
srcRel: string;
|
|
4
|
+
srcAttr: string;
|
|
5
|
+
width?: number;
|
|
6
|
+
height?: number;
|
|
7
|
+
ratio?: string;
|
|
8
|
+
query?: string;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Get config for asset
|
|
12
|
+
*
|
|
13
|
+
* @param srcDir The absolute path to the asset's source folder
|
|
14
|
+
* @param srcAbs The absolute path to the asset itself
|
|
15
|
+
* @param pattern The user-defined pattern to create the public src attribute
|
|
16
|
+
* @param hints A list of named image size hints, i.e. 'style', 'attrs', etc
|
|
17
|
+
*/
|
|
18
|
+
export declare function getAssetConfig(srcDir: string, srcAbs: string, pattern: string, hints: string[]): AssetConfig;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as Path from "path";
|
|
2
|
+
import getImageSize from "image-size";
|
|
3
|
+
import { isImage, warn } from "../utils/index.mjs";
|
|
4
|
+
import { interpolatePattern } from "./paths.mjs";
|
|
5
|
+
export function getAssetConfig(srcDir, srcAbs, pattern, hints) {
|
|
6
|
+
let width = void 0;
|
|
7
|
+
let height = void 0;
|
|
8
|
+
let ratio = void 0;
|
|
9
|
+
let query = void 0;
|
|
10
|
+
if (hints.length && isImage(srcAbs)) {
|
|
11
|
+
try {
|
|
12
|
+
const size = getImageSize(srcAbs);
|
|
13
|
+
if (hints.includes("style")) {
|
|
14
|
+
ratio = `${size.width}/${size.height}`;
|
|
15
|
+
}
|
|
16
|
+
if (hints.includes("attrs")) {
|
|
17
|
+
width = size.width;
|
|
18
|
+
height = size.height;
|
|
19
|
+
}
|
|
20
|
+
if (hints.includes("url")) {
|
|
21
|
+
query = `?width=${width}&height=${height}`;
|
|
22
|
+
}
|
|
23
|
+
} catch (err) {
|
|
24
|
+
warn(`could not read image "${srcAbs}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const srcRel = Path.basename(srcDir) + srcAbs.substring(srcDir.length);
|
|
28
|
+
const srcAttr = interpolatePattern(pattern, srcRel);
|
|
29
|
+
const id = srcRel.replaceAll("/", ":");
|
|
30
|
+
return { id, srcRel, srcAttr, width, height, ratio, query };
|
|
31
|
+
}
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
* Interpolate assets path pattern
|
|
3
3
|
*
|
|
4
4
|
* @param pattern A path pattern with tokens
|
|
5
|
-
* @param src The
|
|
6
|
-
* @param dir The absolute path to its containing folder
|
|
5
|
+
* @param src The relative path to a src asset
|
|
7
6
|
* @param warn An optional flag to warn for unknown tokens
|
|
8
7
|
*/
|
|
9
|
-
export declare function interpolatePattern(pattern: string, src: string,
|
|
8
|
+
export declare function interpolatePattern(pattern: string, src: string, warn?: boolean): string;
|
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
import Path from "path";
|
|
2
2
|
import { hash } from "ohash";
|
|
3
|
-
import { log } from "
|
|
3
|
+
import { log } from "../utils/index.mjs";
|
|
4
4
|
const replacers = {
|
|
5
|
-
|
|
5
|
+
key: (src) => Path.dirname(src).split("/").filter((e) => e).shift() || "",
|
|
6
|
+
path: (src) => Path.dirname(src),
|
|
7
|
+
folder: (src) => Path.dirname(src).replace(/[^/]+\//, ""),
|
|
6
8
|
file: (src) => Path.basename(src),
|
|
7
9
|
name: (src) => Path.basename(src, Path.extname(src)),
|
|
8
10
|
extname: (src) => Path.extname(src),
|
|
9
11
|
ext: (src) => Path.extname(src).substring(1),
|
|
10
12
|
hash: (src) => hash({ src })
|
|
11
13
|
};
|
|
12
|
-
export function interpolatePattern(pattern, src,
|
|
14
|
+
export function interpolatePattern(pattern, src, warn = false) {
|
|
13
15
|
return Path.join(pattern.replace(/\[\w+]/g, (match) => {
|
|
14
16
|
const name = match.substring(1, match.length - 1);
|
|
15
17
|
const fn = replacers[name];
|
|
16
18
|
if (fn) {
|
|
17
|
-
return fn(src
|
|
19
|
+
return fn(src);
|
|
18
20
|
}
|
|
19
21
|
if (warn) {
|
|
20
22
|
log(`Unknown output token ${match}`, true);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type GithubOptions = {
|
|
2
|
+
repo: string;
|
|
3
|
+
branch?: string;
|
|
4
|
+
dir?: string;
|
|
5
|
+
prefix?: string;
|
|
6
|
+
ttl?: number;
|
|
7
|
+
};
|
|
8
|
+
export declare function getGithubAssets(key: string, source: GithubOptions, tempPath: string, extensions: string[]): Promise<string[]>;
|
|
9
|
+
export declare function getFsAssets(path: string, extensions: string[]): string[];
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as Fs from "fs";
|
|
2
|
+
import * as Path from "path";
|
|
3
|
+
import glob from "glob";
|
|
4
|
+
import { createStorage } from "unstorage";
|
|
5
|
+
import githubDriver from "unstorage/drivers/github";
|
|
6
|
+
export async function getGithubAssets(key, source, tempPath, extensions) {
|
|
7
|
+
const storage = createStorage();
|
|
8
|
+
storage.mount(key, githubDriver({
|
|
9
|
+
repo: source.repo,
|
|
10
|
+
branch: source.branch || "main",
|
|
11
|
+
dir: source.dir || "/",
|
|
12
|
+
ttl: source.ttl || 600
|
|
13
|
+
}));
|
|
14
|
+
const rx = new RegExp(`.${extensions.join("|")}$`);
|
|
15
|
+
const keys = await storage.getKeys();
|
|
16
|
+
const assetKeys = keys.filter((key2) => rx.test(key2));
|
|
17
|
+
const assetItems = await Promise.all(assetKeys.map(async (id) => {
|
|
18
|
+
const data = await storage.getItem(id);
|
|
19
|
+
return { id, data };
|
|
20
|
+
}));
|
|
21
|
+
const prefix = source.prefix || "";
|
|
22
|
+
const paths = [];
|
|
23
|
+
for (const { id, data } of assetItems) {
|
|
24
|
+
if (data) {
|
|
25
|
+
const path = id.replaceAll(":", "/");
|
|
26
|
+
const absPath = Path.join(tempPath, path.replace(key, `${key}/${prefix}`));
|
|
27
|
+
const absFolder = Path.dirname(absPath);
|
|
28
|
+
const buffer = data.constructor.name === "Blob" ? Buffer.from(await data.arrayBuffer()) : typeof data === "object" ? JSON.stringify(data, null, " ") : String(data);
|
|
29
|
+
Fs.mkdirSync(absFolder, { recursive: true });
|
|
30
|
+
Fs.writeFileSync(absPath, buffer);
|
|
31
|
+
paths.push(absPath);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return paths;
|
|
35
|
+
}
|
|
36
|
+
export function getFsAssets(path, extensions) {
|
|
37
|
+
const pattern = `${path}/**/*.{${extensions.join(",")}}`;
|
|
38
|
+
return glob.globSync(pattern) || [];
|
|
39
|
+
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import Path from "path";
|
|
2
|
-
import Fs from "fs";
|
|
3
1
|
import { moduleKey } from "../config.mjs";
|
|
4
2
|
export function log(...data) {
|
|
5
3
|
console.info(`[${moduleKey}]`, ...data);
|
|
@@ -7,10 +5,9 @@ export function log(...data) {
|
|
|
7
5
|
export function warn(...data) {
|
|
8
6
|
console.warn(`[${moduleKey}]`, ...data);
|
|
9
7
|
}
|
|
10
|
-
export function
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
Fs.writeFileSync(path, JSON.stringify(data, null, " "), { encoding: "utf8" });
|
|
8
|
+
export function list(message, items) {
|
|
9
|
+
log(`${message}:
|
|
10
|
+
|
|
11
|
+
${items.map((item) => ` - ${item}`).join("\n")}
|
|
12
|
+
`);
|
|
16
13
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function writeFile(path: string, data: null | string | number | boolean | object): void;
|
|
2
|
+
export declare function copyFile(src: string, trg: string): void;
|
|
3
|
+
export declare function createFolder(path: string): void;
|
|
4
|
+
export declare function removeFolder(path: string): void;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as Path from "path";
|
|
2
|
+
import * as Fs from "fs";
|
|
3
|
+
export function writeFile(path, data) {
|
|
4
|
+
const text = typeof data === "object" ? JSON.stringify(data, null, " ") : String(data);
|
|
5
|
+
createFolder(Path.dirname(path));
|
|
6
|
+
Fs.writeFileSync(path, text, { encoding: "utf8" });
|
|
7
|
+
}
|
|
8
|
+
export function copyFile(src, trg) {
|
|
9
|
+
createFolder(Path.dirname(trg));
|
|
10
|
+
Fs.copyFileSync(src, trg);
|
|
11
|
+
}
|
|
12
|
+
export function createFolder(path) {
|
|
13
|
+
Fs.mkdirSync(path, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
export function removeFolder(path) {
|
|
16
|
+
const isDownstream = path.startsWith(Path.resolve());
|
|
17
|
+
if (isDownstream) {
|
|
18
|
+
Fs.rmSync(path, { recursive: true, force: true });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
export * from "./
|
|
2
|
-
export * from "./config.mjs";
|
|
3
|
-
export * from "./content.mjs";
|
|
1
|
+
export * from "./assert.mjs";
|
|
4
2
|
export * from "./debug.mjs";
|
|
3
|
+
export * from "./fs.mjs";
|
|
4
|
+
export * from "./string.mjs";
|
|
5
5
|
export * from "./object.mjs";
|
|
6
|
-
export * from "./paths.mjs";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-content-assets",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Enable locally-located assets in Nuxt Content",
|
|
5
5
|
"repository": "davestewart/nuxt-content-assets",
|
|
6
6
|
"license": "MIT",
|
|
@@ -35,7 +35,8 @@
|
|
|
35
35
|
"glob": "^9.3.2",
|
|
36
36
|
"image-size": "^1.0.2",
|
|
37
37
|
"ohash": "^1.0.0",
|
|
38
|
-
"unist-util-visit": "^4.1.2"
|
|
38
|
+
"unist-util-visit": "^4.1.2",
|
|
39
|
+
"unstorage": "^1.4.1"
|
|
39
40
|
},
|
|
40
41
|
"peerDependencies": {
|
|
41
42
|
"@nuxt/content": "latest"
|
|
@@ -50,5 +51,8 @@
|
|
|
50
51
|
"eslint": "^8.36.0",
|
|
51
52
|
"nuxt": "^3.3.2",
|
|
52
53
|
"vitest": "^0.29.7"
|
|
54
|
+
},
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=14.0.0"
|
|
53
57
|
}
|
|
54
58
|
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export function getSources(sources) {
|
|
2
|
-
return Object.keys(sources).reduce((output, key) => {
|
|
3
|
-
const source = sources[key];
|
|
4
|
-
if (source) {
|
|
5
|
-
const { driver, base } = source;
|
|
6
|
-
if (driver === "fs") {
|
|
7
|
-
output[key] = base;
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
return output;
|
|
11
|
-
}, {});
|
|
12
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|