-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathmodule.ts
More file actions
287 lines (241 loc) · 9.39 KB
/
Copy pathmodule.ts
File metadata and controls
287 lines (241 loc) · 9.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
import * as Fs from 'fs'
import Path from 'crosspath'
import { addPlugin, createResolver, defineNuxtModule } from '@nuxt/kit'
import { isImage, list, log, warn, makeIgnores, matchTokens, removeEntry, toPath } from './runtime/utils'
import { setupSocketServer } from './build/sockets/setup'
import { makeSourceManager } from './runtime/assets/source'
import { makeAssetsManager } from './runtime/assets/public'
import { rewriteContent } from './runtime/content/parsed'
import type { ModuleMeta, Nuxt, NuxtConfigLayer } from '@nuxt/schema'
import type { MountOptions } from '@nuxt/content'
import type { ImageSize, ModuleOptions } from './types'
// Re-export types for consumers
export type {
ModuleOptions,
ImageSize,
AssetConfig,
AssetMessage,
SocketInstance
} from './types'
const resolve = createResolver(import.meta.url).resolve
const meta: ModuleMeta = {
name: 'nuxt-content-assets',
configKey: 'contentAssets',
compatibility: {
nuxt: '>=3.0.0',
},
}
export default defineNuxtModule<ModuleOptions>({
meta,
defaults: {
imageSize: '',
contentExtensions: 'mdx? csv ya?ml json',
debug: false,
},
async setup (options: ModuleOptions, nuxt: Nuxt) {
// ---------------------------------------------------------------------------------------------------------------------
// paths
// ---------------------------------------------------------------------------------------------------------------------
// nuxt build folder (.nuxt)
const buildPath = nuxt.options.buildDir
// node modules folder (note: from v1.4.1 the assets cache moved from .nuxt/... to node_modules/... @see #76)
const modulesPath = nuxt.options.modulesDir.find((path: string) => Fs.existsSync(`${path}/nuxt-content-assets/cache`)) || ''
if (!modulesPath) {
warn('Unable to find cache folder!')
if (nuxt.options.rootDir.endsWith('/playground')) {
warn('Run "npm run dev:setup" to generate a new cache folder')
}
}
// assets cache (node_modules/nuxt-content-assets/cache)
const cachePath = modulesPath
? Path.resolve(modulesPath, 'nuxt-content-assets/cache')
: Path.resolve(buildPath, 'content-assets') // TODO check if fallback even works?
// public folder (node_modules/nuxt-content-assets/cache/public)
const publicPath = Path.join(cachePath, 'public')
// content cache (.nuxt/content-cache)
const contentPath = Path.join(buildPath, 'content-cache')
// ---------------------------------------------------------------------------------------------------------------------
// setup
// ---------------------------------------------------------------------------------------------------------------------
// options
const isDev = !!nuxt.options.dev
const isDebug = !!options.debug
// clear caches
if (isDebug) {
log('Cleaning content-cache')
log(`Cache path: "${Path.relative(".", cachePath)}"`)
}
// clear cached markdown so image paths get updated
removeEntry(contentPath)
// ---------------------------------------------------------------------------------------------------------------------
// options
// ---------------------------------------------------------------------------------------------------------------------
// set up content ignores
const { contentExtensions } = options
if (contentExtensions) {
// @ts-ignore
nuxt.options.content ||= {}
if (nuxt.options.content) {
nuxt.options.content.ignores ||= []
}
const ignores = makeIgnores(contentExtensions)
if (ignores.length) {
nuxt.options.content?.ignores.push(ignores)
}
}
// convert image size hints to array
const imageSizes: ImageSize = matchTokens(options.imageSize) as ImageSize
// collate sources
type Sources = Record<string, MountOptions>
const sources: Sources = Array
.from(nuxt.options._layers)
.map((layer: NuxtConfigLayer) => layer.config?.content?.sources)
.reduce((output: Sources, sources) => {
if (sources && !Array.isArray(sources)) {
Object.assign(output, <Sources>sources)
}
return output
}, {})
// add default content folder
if (Object.keys(sources).length === 0 || !sources.content) {
const content = nuxt.options.rootDir + '/content'
if (Fs.existsSync(content)) {
sources.content = {
driver: 'fs',
base: content,
}
}
}
// ---------------------------------------------------------------------------------------------------------------------
// assets
// ---------------------------------------------------------------------------------------------------------------------
/**
* Assets manager
*/
const assets = makeAssetsManager(publicPath, isDev)
// clear files from previous run
assets.init()
/**
* Callback for when assets change
*
* - if the asset is updated or deleted, we tell the browser to update the asset's properties
* - if the asset is an image and changes size, we also rewrite the cached content
*
* @param event The type of update
* @param absTrg The absolute path to the copied asset
*/
function onAssetChange (event: 'update' | 'remove', absTrg: string) {
let src: string = ''
let width: number | undefined
let height: number | undefined
// update
if (event === 'update') {
// 1. get the old asset config first...
const oldAsset = isImage(absTrg) && imageSizes.length
? assets.getAsset(absTrg)
: null
// 2. ...before the asset overwrites the image size
const newAsset = assets.setAsset(absTrg)
// sizes
width = newAsset.width
height = newAsset.height
// check for image size change
if (oldAsset) {
// special behaviour for image size change!
// we rewrite cached content directly so image size changes are permanent
if (oldAsset.width !== newAsset.width || oldAsset.height !== newAsset.height) {
newAsset.content.forEach(async (id: string) => {
const path = Path.join(contentPath, 'parsed', toPath(id))
rewriteContent(path, newAsset)
})
}
}
// set src
src = newAsset.srcAttr
}
// remove
else {
const asset = assets.removeAsset(absTrg)
if (asset) {
src = asset.srcAttr
}
}
// sockets
if (src && socket) {
socket.send({ event, src, width, height })
}
}
/**
* Socket to communicate changes to client
*/
addPlugin(resolve('./runtime/sockets/plugin'))
const socket = isDev && nuxt.options.content?.watch !== false
? await setupSocketServer('content-assets')
: null
// ---------------------------------------------------------------------------------------------------------------------
// sources
// ---------------------------------------------------------------------------------------------------------------------
// create source managers
const managers: Record<string, ReturnType<typeof makeSourceManager>> = {}
for (const [key, source] of Object.entries(sources)) {
// debug
if (isDebug) {
log(`Creating source "${key}"`)
}
// create manager
managers[key] = makeSourceManager(key, source, publicPath, onAssetChange)
}
// ---------------------------------------------------------------------------------------------------------------------
// nuxt hooks
// ---------------------------------------------------------------------------------------------------------------------
// copy assets to public folder
nuxt.hook('build:before', async function () {
for (const [key, manager] of Object.entries(managers)) {
// copy assets
const paths = await manager.init()
// update assets config
paths.forEach(path => assets.setAsset(path))
// debug
if (isDebug) {
list(`Copied "${key}" assets`, paths.map(path => Path.relative(publicPath, path)))
}
}
})
// cleanup when nuxt closes
nuxt.hook('close', async () => {
await assets.dispose()
for (const key in managers) {
await managers[key]?.dispose()
}
})
// ---------------------------------------------------------------------------------------------------------------------
// nitro hook
// ---------------------------------------------------------------------------------------------------------------------
// plugin
const pluginPath = resolve('./runtime/content/plugin')
// config
const makeVar = (name: string, value: any) => `export const ${name} = ${JSON.stringify(value)};`
const virtualConfig = [
makeVar('publicPath', publicPath),
makeVar('imageSizes', imageSizes),
makeVar('debug', isDebug),
].join('\n')
// setup server plugin
nuxt.hook('nitro:config', async (config) => {
// add plugin
config.plugins ||= []
config.plugins.push(pluginPath)
// make config available to nitro
config.virtual ||= {}
config.virtual[`#${meta.name}`] = () => {
return virtualConfig
}
// serve public assets
config.publicAssets ||= []
config.publicAssets.push({
dir: publicPath,
maxAge: (60 * 60 * 24) * 7, // 7 days
})
})
},
})