nuxt-og-image 0.3.4 → 0.4.2
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 +115 -132
- package/dist/module.d.ts +1 -6
- package/dist/module.json +1 -1
- package/dist/module.mjs +81 -31
- package/dist/runtime/browserService.d.ts +5 -0
- package/dist/runtime/browserService.mjs +56 -0
- package/dist/runtime/composables/defineOgImage.d.ts +0 -5
- package/dist/runtime/composables/defineOgImage.mjs +2 -6
- package/dist/runtime/nitro/html.d.ts +0 -2
- package/dist/runtime/nitro/html.mjs +2 -3
- package/dist/runtime/nitro/image.d.ts +0 -6
- package/dist/runtime/nitro/image.mjs +4 -37
- package/dist/types.d.ts +1 -1
- package/package.json +3 -9
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
<p align="center">
|
|
13
|
-
Generate social share images for
|
|
13
|
+
Generate dynamic social share images for you Nuxt v3 app.
|
|
14
14
|
</p>
|
|
15
15
|
|
|
16
16
|
<p align="center">
|
|
@@ -18,7 +18,7 @@ Generate social share images for your pre-rendered Nuxt v3 app.
|
|
|
18
18
|
<tbody>
|
|
19
19
|
<td align="center">
|
|
20
20
|
<img width="800" height="0" /><br>
|
|
21
|
-
<i>Status:</i> Early Access
|
|
21
|
+
<i>Status:</i> 🤫 Early Access - Active Development 🤫</b> <br>
|
|
22
22
|
<sup> Please report any issues 🐛</sup><br>
|
|
23
23
|
<sub>Made possible by my <a href="https://github.com/sponsors/harlan-zw">Sponsor Program 💖</a><br> Follow me <a href="https://twitter.com/harlan_zw">@harlan_zw</a> 🐦 • Join <a href="https://discord.gg/275MBUBvgP">Discord</a> for help</sub><br>
|
|
24
24
|
<img width="800" height="0" />
|
|
@@ -29,19 +29,33 @@ Generate social share images for your pre-rendered Nuxt v3 app.
|
|
|
29
29
|
|
|
30
30
|
## Features
|
|
31
31
|
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
32
|
+
- 🧙 Generate images for your entire site in minutes with minimal config
|
|
33
|
+
- 🎨 Build your own template with Vue (powered by Nuxt Islands)
|
|
34
|
+
- 📸 OR just generates page screenshots
|
|
35
35
|
|
|
36
36
|
## Install
|
|
37
37
|
|
|
38
|
+
⚠️ This module is still in development. Please get in touch with me over [Twitter](https://twitter.com/harlan_zw) or Discord if you are going to attempt to use this.
|
|
39
|
+
|
|
38
40
|
```bash
|
|
39
41
|
npm install --save-dev nuxt-og-image
|
|
40
|
-
|
|
41
42
|
# Using yarn
|
|
42
43
|
yarn add --dev nuxt-og-image
|
|
43
44
|
```
|
|
44
45
|
|
|
46
|
+
### Chromium Dependency
|
|
47
|
+
|
|
48
|
+
By default, this module does not install chromium binaries for you, instead relying on locally
|
|
49
|
+
installed chrome.
|
|
50
|
+
|
|
51
|
+
If you want to use this module in a CI or SSR environment, you will need the binaries.
|
|
52
|
+
|
|
53
|
+
You can either use `puppeteer` or `chrome-aws-lambda`.
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm install --save-dev puppeteer # or chrome-aws-lambda
|
|
57
|
+
```
|
|
58
|
+
|
|
45
59
|
## Setup
|
|
46
60
|
|
|
47
61
|
_nuxt.config.ts_
|
|
@@ -54,7 +68,29 @@ export default defineNuxtConfig({
|
|
|
54
68
|
})
|
|
55
69
|
```
|
|
56
70
|
|
|
57
|
-
|
|
71
|
+
### Add your host name
|
|
72
|
+
|
|
73
|
+
The `og:image` meta tag requires the full URL, so you must provide your site host.
|
|
74
|
+
|
|
75
|
+
_nuxt.config.ts_
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
export default defineNuxtConfig({
|
|
79
|
+
// Recommended
|
|
80
|
+
runtimeConfig: {
|
|
81
|
+
siteUrl: 'https://example.com',
|
|
82
|
+
},
|
|
83
|
+
// OR
|
|
84
|
+
ogImage: {
|
|
85
|
+
host: 'https://example.com',
|
|
86
|
+
},
|
|
87
|
+
})
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Pre-render routes
|
|
91
|
+
|
|
92
|
+
While the module is in early access, you should ensure that you pre-render any pages you want to
|
|
93
|
+
generate images for.
|
|
58
94
|
|
|
59
95
|
```ts
|
|
60
96
|
export default defineNuxtConfig({
|
|
@@ -71,38 +107,73 @@ export default defineNuxtConfig({
|
|
|
71
107
|
})
|
|
72
108
|
```
|
|
73
109
|
|
|
74
|
-
|
|
110
|
+
### Recommended: Enable Nuxt Islands
|
|
75
111
|
|
|
76
|
-
|
|
112
|
+
To be able to preview the image in development and generate template images, you'll need
|
|
113
|
+
to enable Nuxt Islands.
|
|
77
114
|
|
|
78
|
-
|
|
115
|
+
If you're using Nuxt 3.0.0, you will need to switch to the [edge-release channel](https://nuxt.com/docs/guide/going-further/edge-channel#edge-release-channel).
|
|
79
116
|
|
|
80
|
-
|
|
81
|
-
Nuxt islands.
|
|
117
|
+
Once that's done, you can enable the flag for islands.
|
|
82
118
|
|
|
83
|
-
|
|
119
|
+
_nuxt.config.ts_
|
|
84
120
|
|
|
85
|
-
|
|
121
|
+
```ts
|
|
122
|
+
export default defineNuxtConfig({
|
|
123
|
+
experimental: {
|
|
124
|
+
componentIslands: true
|
|
125
|
+
},
|
|
126
|
+
})
|
|
127
|
+
```
|
|
86
128
|
|
|
87
|
-
|
|
129
|
+
## Generating Screenshots
|
|
88
130
|
|
|
89
|
-
|
|
131
|
+
```vue
|
|
132
|
+
<script lang="ts" setup>
|
|
133
|
+
defineOgImageScreenshot()
|
|
134
|
+
</script>
|
|
135
|
+
<template>
|
|
136
|
+
<div>
|
|
137
|
+
<!-- Your page / app.vue / layout -->
|
|
138
|
+
</div>
|
|
139
|
+
</template>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Generating Template Images
|
|
143
|
+
|
|
144
|
+
The template image generator is powered by Nuxt Islands. This means that you can use any Vue
|
|
145
|
+
component you want to generate your images.
|
|
146
|
+
|
|
147
|
+
```vue
|
|
148
|
+
<script lang="ts" setup>
|
|
149
|
+
defineOgImage({
|
|
150
|
+
component: 'OgImage', // Nuxt Island component
|
|
151
|
+
// pass in any custom props
|
|
152
|
+
myCustomTitle: 'My Title'
|
|
153
|
+
})
|
|
154
|
+
</script>
|
|
155
|
+
<template>
|
|
156
|
+
<div>
|
|
157
|
+
<!-- Your page / app.vue / layout -->
|
|
158
|
+
</div>
|
|
159
|
+
</template>
|
|
160
|
+
```
|
|
90
161
|
|
|
91
|
-
|
|
162
|
+
### Creating your own template
|
|
92
163
|
|
|
93
|
-
|
|
164
|
+
Create a new component with `.island.vue` as the suffix, such as `components/Banner.island.vue`.
|
|
94
165
|
|
|
95
|
-
|
|
166
|
+
Use the below template to test it works, then modify it how you like.
|
|
96
167
|
|
|
97
168
|
```vue
|
|
98
169
|
<script setup lang="ts">
|
|
99
170
|
const props = defineProps({
|
|
100
|
-
//
|
|
171
|
+
// these will always be provided
|
|
101
172
|
path: String,
|
|
102
173
|
title: String,
|
|
103
174
|
description: String,
|
|
104
|
-
//
|
|
105
|
-
|
|
175
|
+
// anything custom comes here
|
|
176
|
+
backgroundImage: String
|
|
106
177
|
})
|
|
107
178
|
</script>
|
|
108
179
|
|
|
@@ -125,143 +196,50 @@ const props = defineProps({
|
|
|
125
196
|
align-items: center;
|
|
126
197
|
justify-content: center;
|
|
127
198
|
color: white;
|
|
128
|
-
font-weight: bold;
|
|
129
|
-
font-family: sans-serif;
|
|
130
199
|
background: linear-gradient(to bottom, #30e8bf, #ff8235);
|
|
131
200
|
}
|
|
132
201
|
|
|
133
202
|
h1 {
|
|
134
203
|
font-size: 4rem;
|
|
135
|
-
margin: 0;
|
|
136
204
|
}
|
|
137
205
|
</style>
|
|
138
206
|
```
|
|
139
207
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
Within a page
|
|
208
|
+
Make sure you reference this component when using `defineOgImage` and any props to pass.
|
|
143
209
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
export default defineNuxtConfig({
|
|
150
|
-
// Recommended
|
|
151
|
-
runtimeConfig: {
|
|
152
|
-
siteUrl: 'https://example.com',
|
|
153
|
-
},
|
|
154
|
-
// OR
|
|
155
|
-
sitemap: {
|
|
156
|
-
hostname: 'https://example.com',
|
|
157
|
-
},
|
|
158
|
-
})
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
## Route Rules
|
|
163
|
-
|
|
164
|
-
To change the behavior of the sitemap, you can use route rules. Route rules are provided as [Nitro route rules](https://v3.nuxtjs.org/docs/directory-structure/nitro/#route-rules).
|
|
165
|
-
|
|
166
|
-
_nuxt.config.ts_
|
|
167
|
-
|
|
168
|
-
```ts
|
|
169
|
-
export default defineNuxtConfig({
|
|
170
|
-
routeRules: {
|
|
171
|
-
// Don't add any /secret/** URLs to the sitemap
|
|
172
|
-
'/secret/**': { index: false },
|
|
173
|
-
// modify the sitemap entry for specific URLs
|
|
174
|
-
'/about': { sitemap: { changefreq: 'daily', priority: 0.3 } }
|
|
175
|
-
}
|
|
210
|
+
```vue
|
|
211
|
+
<script lang="ts" setup>
|
|
212
|
+
defineOgImage({
|
|
213
|
+
component: 'Banner',
|
|
214
|
+
backgroundImage: 'https://example.com/my-background-image.jpg',
|
|
176
215
|
})
|
|
216
|
+
</script>
|
|
177
217
|
```
|
|
178
218
|
|
|
179
|
-
|
|
219
|
+
## Previewing Images
|
|
180
220
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
- `
|
|
221
|
+
Once you have defined the og:image using the composable, you can preview the image by visiting
|
|
222
|
+
the following URLs:
|
|
223
|
+
- `/your-path/__og-image` Renders the HTML output
|
|
224
|
+
- `/your-path/og-image.png` Renders the og:image
|
|
184
225
|
|
|
185
226
|
## Module Config
|
|
186
227
|
|
|
187
|
-
If you need further control over the sitemap URLs, you can provide config on the `sitemap` key.
|
|
188
|
-
|
|
189
228
|
### `host`
|
|
190
229
|
|
|
191
230
|
- Type: `string`
|
|
192
231
|
- Default: `undefined`
|
|
193
232
|
- Required: `true`
|
|
194
233
|
|
|
195
|
-
The host of your site. This is required to generate the
|
|
234
|
+
The host of your site. This is required to generate the absolute path of the og:image.
|
|
196
235
|
|
|
197
|
-
### `
|
|
236
|
+
### `runtimeImages`
|
|
198
237
|
|
|
199
238
|
- Type: `boolean`
|
|
200
|
-
- Default: `
|
|
201
|
-
|
|
202
|
-
Whether to add a trailing slash to the URLs in the sitemap.xml.
|
|
203
|
-
|
|
204
|
-
### `enabled`
|
|
205
|
-
|
|
206
|
-
- Type: `boolean`
|
|
207
|
-
- Default: `true`
|
|
208
|
-
|
|
209
|
-
Whether to generate the sitemap.xml.
|
|
210
|
-
|
|
211
|
-
### `include`
|
|
212
|
-
|
|
213
|
-
- Type: `string[]`
|
|
214
|
-
- Default: `undefined`
|
|
215
|
-
|
|
216
|
-
Filter routes that match the given rules.
|
|
239
|
+
- Default: `process.dev`
|
|
217
240
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
sitemap: {
|
|
221
|
-
include: [
|
|
222
|
-
'/my-hidden-url'
|
|
223
|
-
]
|
|
224
|
-
}
|
|
225
|
-
})
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
### `exclude`
|
|
229
|
-
|
|
230
|
-
- Type: `string[]`
|
|
231
|
-
- Default: `undefined`
|
|
232
|
-
|
|
233
|
-
Filter routes that match the given rules.
|
|
234
|
-
|
|
235
|
-
```ts
|
|
236
|
-
export default defineNuxtConfig({
|
|
237
|
-
sitemap: {
|
|
238
|
-
exclude: [
|
|
239
|
-
'/my-secret-section/**'
|
|
240
|
-
]
|
|
241
|
-
}
|
|
242
|
-
})
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
Additional config extends [sitemap.js](https://github.com/ekalinin/sitemap.js).
|
|
246
|
-
|
|
247
|
-
## Examples
|
|
248
|
-
|
|
249
|
-
### Add custom routes without pre-rendering
|
|
250
|
-
|
|
251
|
-
```ts
|
|
252
|
-
export default defineNuxtConfig({
|
|
253
|
-
hooks: {
|
|
254
|
-
'sitemap:generate': (ctx) => {
|
|
255
|
-
// add custom URLs
|
|
256
|
-
ctx.urls.push({
|
|
257
|
-
url: '/my-custom-url',
|
|
258
|
-
changefreq: 'daily',
|
|
259
|
-
priority: 0.3
|
|
260
|
-
})
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
})
|
|
264
|
-
```
|
|
241
|
+
Allows you to generate images at runtime in production. This uses a headless browser to generate images
|
|
242
|
+
and may have deployment issues.
|
|
265
243
|
|
|
266
244
|
## Sponsors
|
|
267
245
|
|
|
@@ -271,6 +249,11 @@ export default defineNuxtConfig({
|
|
|
271
249
|
</a>
|
|
272
250
|
</p>
|
|
273
251
|
|
|
252
|
+
## Credits
|
|
253
|
+
|
|
254
|
+
- Pooya Parsa [Kachick](https://github.com/unjs/kachik)
|
|
255
|
+
- Nuxt Team
|
|
256
|
+
|
|
274
257
|
|
|
275
258
|
## License
|
|
276
259
|
|
package/dist/module.d.ts
CHANGED
|
@@ -25,7 +25,6 @@ declare module 'nitropack' {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
interface ModuleOptions extends ScreenshotOptions {
|
|
28
|
-
defaultIslandComponent: string;
|
|
29
28
|
/**
|
|
30
29
|
* The directory within `public` where the og images will be stored.
|
|
31
30
|
*
|
|
@@ -41,10 +40,6 @@ interface ModuleOptions extends ScreenshotOptions {
|
|
|
41
40
|
*/
|
|
42
41
|
runtimeImages: boolean;
|
|
43
42
|
}
|
|
44
|
-
declare const HtmlRendererRoute = "__og_image";
|
|
45
|
-
declare const PayloadScriptId = "nuxt-og-image-payload";
|
|
46
|
-
declare const MetaOgImageContentPlaceholder = "__NUXT_OG_IMAGE_PLACEHOLDER__";
|
|
47
|
-
declare const LinkPrerenderId = "nuxt-og-image-screenshot-path";
|
|
48
43
|
declare const _default: _nuxt_schema.NuxtModule<ModuleOptions>;
|
|
49
44
|
|
|
50
|
-
export {
|
|
45
|
+
export { ModuleOptions, _default as default };
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -5,19 +5,43 @@ import { hash } from 'ohash';
|
|
|
5
5
|
import chalk from 'chalk';
|
|
6
6
|
import defu from 'defu';
|
|
7
7
|
import { toRouteMatcher, createRouter } from 'radix3';
|
|
8
|
-
import { withBase } from 'ufo';
|
|
8
|
+
import { withBase, joinURL } from 'ufo';
|
|
9
9
|
import fg from 'fast-glob';
|
|
10
|
+
import { join } from 'pathe';
|
|
10
11
|
|
|
11
12
|
async function createBrowser() {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
13
|
+
try {
|
|
14
|
+
const playwrightCore = await import('playwright-core');
|
|
15
|
+
process.env.AWS_LAMBDA_FUNCTION_NAME = process.env.AWS_LAMBDA_FUNCTION_NAME || "RUNTIME_HACK";
|
|
16
|
+
const awsChrome = await import(String("chrome-aws-lambda"));
|
|
17
|
+
return await playwrightCore.chromium.launch({
|
|
18
|
+
args: awsChrome.args,
|
|
19
|
+
executablePath: await awsChrome.executablePath,
|
|
20
|
+
headless: awsChrome.headless
|
|
21
|
+
});
|
|
22
|
+
} catch (e) {
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const playwrightCore = await import('playwright-core');
|
|
26
|
+
const { Launcher } = await import('chrome-launcher');
|
|
27
|
+
const chromePath = Launcher.getFirstInstallation();
|
|
28
|
+
return await playwrightCore.chromium.launch({
|
|
29
|
+
headless: true,
|
|
30
|
+
executablePath: chromePath
|
|
31
|
+
});
|
|
32
|
+
} catch (e) {
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
const playwright = await import(String("playwright"));
|
|
36
|
+
return await playwright.chromium.launch({
|
|
37
|
+
headless: true
|
|
38
|
+
});
|
|
39
|
+
} catch (e) {
|
|
40
|
+
throw new Error(`
|
|
41
|
+
Missing chromium binary. You need either 'playwright' or 'chrome-aws-lambda'.
|
|
42
|
+
Please run 'yarn add --dev playwright' or 'npm install --save-dev playwright'
|
|
43
|
+
`);
|
|
44
|
+
}
|
|
21
45
|
}
|
|
22
46
|
async function screenshot(browser, url, options) {
|
|
23
47
|
const page = await browser.newPage({
|
|
@@ -46,6 +70,15 @@ const HtmlRendererRoute = "__og_image";
|
|
|
46
70
|
const PayloadScriptId = "nuxt-og-image-payload";
|
|
47
71
|
const MetaOgImageContentPlaceholder = "__NUXT_OG_IMAGE_PLACEHOLDER__";
|
|
48
72
|
const LinkPrerenderId = "nuxt-og-image-screenshot-path";
|
|
73
|
+
const DefaultRuntimeImageSuffix = "og-image.png";
|
|
74
|
+
const Constants = {
|
|
75
|
+
HtmlRendererRoute,
|
|
76
|
+
PayloadScriptId,
|
|
77
|
+
MetaOgImageContentPlaceholder,
|
|
78
|
+
LinkPrerenderId,
|
|
79
|
+
DefaultRuntimeImageSuffix
|
|
80
|
+
};
|
|
81
|
+
|
|
49
82
|
const module = defineNuxtModule({
|
|
50
83
|
meta: {
|
|
51
84
|
name: "nuxt-og-image",
|
|
@@ -106,19 +139,32 @@ declare module 'nitropack' {
|
|
|
106
139
|
filePath: resolve("./runtime/components/OgImage.vue"),
|
|
107
140
|
island: true
|
|
108
141
|
});
|
|
142
|
+
const runtimeDir = resolve("./runtime");
|
|
143
|
+
nuxt.options.build.transpile.push(runtimeDir);
|
|
144
|
+
const constScript = Object.entries(Constants).map(([k, v]) => `export const ${k} = '${v}'`).join("\n");
|
|
145
|
+
nuxt.options.alias["#nuxt-og-image/constants"] = addTemplate({
|
|
146
|
+
filename: "nuxt-og-image-constants.mjs",
|
|
147
|
+
getContents: () => constScript
|
|
148
|
+
}).dst;
|
|
149
|
+
nuxt.hooks.hook("nitro:config", (nitroConfig) => {
|
|
150
|
+
nitroConfig.externals = defu(nitroConfig.externals || {}, {
|
|
151
|
+
inline: [runtimeDir]
|
|
152
|
+
});
|
|
153
|
+
nitroConfig.virtual["#nuxt-og-image/constants"] = constScript;
|
|
154
|
+
});
|
|
109
155
|
nuxt.hooks.hook("nitro:init", async (nitro) => {
|
|
110
156
|
let entries = [];
|
|
111
157
|
const _routeRulesMatcher = toRouteMatcher(
|
|
112
158
|
createRouter({ routes: nitro.options.routeRules })
|
|
113
159
|
);
|
|
114
|
-
const outputPath =
|
|
160
|
+
const outputPath = join(nitro.options.output.publicDir, config.outputDir);
|
|
115
161
|
nitro.hooks.hook("prerender:generate", async (ctx) => {
|
|
116
162
|
if (ctx.route.includes(".") || ctx.route.endsWith(HtmlRendererRoute))
|
|
117
163
|
return;
|
|
118
164
|
let html = ctx.contents;
|
|
119
165
|
if (!html)
|
|
120
166
|
return;
|
|
121
|
-
if (!html.includes(
|
|
167
|
+
if (!html.includes(`id="${PayloadScriptId}"`))
|
|
122
168
|
return;
|
|
123
169
|
const routeRules = defu({}, ..._routeRulesMatcher.matchAll(ctx.route).reverse());
|
|
124
170
|
if (routeRules.ogImage === false)
|
|
@@ -129,8 +175,8 @@ declare module 'nitropack' {
|
|
|
129
175
|
const entry = {
|
|
130
176
|
fileName,
|
|
131
177
|
absoluteUrl,
|
|
132
|
-
outputPath:
|
|
133
|
-
linkingHtml: ctx.fileName,
|
|
178
|
+
outputPath: joinURL(nitro.options.output.publicDir, config.outputDir, fileName),
|
|
179
|
+
linkingHtml: joinURL(nitro.options.output.publicDir, ctx.fileName),
|
|
134
180
|
route: ctx.route,
|
|
135
181
|
hasPayload: screenshotPath,
|
|
136
182
|
routeRules: routeRules.ogImage || "",
|
|
@@ -149,7 +195,8 @@ declare module 'nitropack' {
|
|
|
149
195
|
await mkdir(outputPath, { recursive: true });
|
|
150
196
|
} catch (e) {
|
|
151
197
|
}
|
|
152
|
-
const previewProcess = execa("npx", ["serve",
|
|
198
|
+
const previewProcess = execa("npx", ["serve", nitro.options.output.publicDir]);
|
|
199
|
+
let browser = null;
|
|
153
200
|
try {
|
|
154
201
|
previewProcess.stderr?.pipe(process.stderr);
|
|
155
202
|
const host = (await new Promise((resolve2) => {
|
|
@@ -159,39 +206,42 @@ declare module 'nitropack' {
|
|
|
159
206
|
}
|
|
160
207
|
});
|
|
161
208
|
})).trim();
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
209
|
+
browser = await createBrowser();
|
|
210
|
+
if (browser) {
|
|
211
|
+
nitro.logger.info(`Generating ${entries.length} og:images...`);
|
|
165
212
|
for (const k in entries) {
|
|
166
213
|
const entry = entries[k];
|
|
167
214
|
const start = Date.now();
|
|
168
|
-
|
|
169
|
-
|
|
215
|
+
let hasError = false;
|
|
216
|
+
try {
|
|
217
|
+
const imgBuffer = await screenshot(browser, `${host}${entry.screenshotPath}`, config);
|
|
218
|
+
await writeFile(entry.outputPath, imgBuffer);
|
|
219
|
+
} catch (e) {
|
|
220
|
+
hasError = true;
|
|
221
|
+
console.error(e);
|
|
222
|
+
}
|
|
170
223
|
const generateTimeMS = Date.now() - start;
|
|
171
|
-
nitro.logger.log(chalk
|
|
224
|
+
nitro.logger.log(chalk[hasError ? "red" : "gray"](
|
|
172
225
|
` ${Number(k) === entries.length - 1 ? "\u2514\u2500" : "\u251C\u2500"} /${config.outputDir}/${entry.fileName} (${generateTimeMS}ms) ${Math.round(Number(k) / (entries.length - 1) * 100)}%`
|
|
173
226
|
));
|
|
174
227
|
}
|
|
175
|
-
} catch (e) {
|
|
176
|
-
console.error(e);
|
|
177
|
-
} finally {
|
|
178
|
-
await browser.close();
|
|
179
228
|
}
|
|
180
229
|
} catch (e) {
|
|
181
230
|
console.error(e);
|
|
182
231
|
} finally {
|
|
232
|
+
await browser?.close();
|
|
183
233
|
previewProcess.kill();
|
|
184
234
|
}
|
|
185
|
-
for (const entry of entries
|
|
186
|
-
const html = await readFile(
|
|
235
|
+
for (const entry of entries) {
|
|
236
|
+
const html = await readFile(entry.linkingHtml, "utf-8");
|
|
187
237
|
const newHtml = html.replace(new RegExp(`<link id="${LinkPrerenderId}" rel="prerender" href="(.*?)">`), "").replace(new RegExp(`<script id="${PayloadScriptId}" type="application/json">(.*?)<\/script>`), "").replace("\n\n", "\n");
|
|
188
238
|
if (html !== newHtml) {
|
|
189
|
-
await writeFile(
|
|
239
|
+
await writeFile(entry.linkingHtml, newHtml, { encoding: "utf-8" });
|
|
190
240
|
}
|
|
191
241
|
}
|
|
192
|
-
const ogImageFolders = await fg([`**/${HtmlRendererRoute}`], { cwd: nitro.options.output.
|
|
242
|
+
const ogImageFolders = await fg([`**/${HtmlRendererRoute}`], { cwd: nitro.options.output.publicDir, onlyDirectories: true });
|
|
193
243
|
for (const ogImageFolder of ogImageFolders)
|
|
194
|
-
await rm(
|
|
244
|
+
await rm(join(nitro.options.output.publicDir, ogImageFolder), { recursive: true, force: true });
|
|
195
245
|
entries = [];
|
|
196
246
|
};
|
|
197
247
|
nitro.hooks.hook("rollup:before", async () => {
|
|
@@ -204,4 +254,4 @@ declare module 'nitropack' {
|
|
|
204
254
|
}
|
|
205
255
|
});
|
|
206
256
|
|
|
207
|
-
export {
|
|
257
|
+
export { module as default };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import type { Browser } from 'playwright-core';
|
|
3
|
+
import type { ScreenshotOptions } from '../types';
|
|
4
|
+
export declare function createBrowser(): Promise<any>;
|
|
5
|
+
export declare function screenshot(browser: Browser, url: string, options: ScreenshotOptions): Promise<Buffer>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export async function createBrowser() {
|
|
2
|
+
try {
|
|
3
|
+
const playwrightCore = await import("playwright-core");
|
|
4
|
+
process.env.AWS_LAMBDA_FUNCTION_NAME = process.env.AWS_LAMBDA_FUNCTION_NAME || "RUNTIME_HACK";
|
|
5
|
+
const awsChrome = await import(String("chrome-aws-lambda"));
|
|
6
|
+
return await playwrightCore.chromium.launch({
|
|
7
|
+
args: awsChrome.args,
|
|
8
|
+
executablePath: await awsChrome.executablePath,
|
|
9
|
+
headless: awsChrome.headless
|
|
10
|
+
});
|
|
11
|
+
} catch (e) {
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
const playwrightCore = await import("playwright-core");
|
|
15
|
+
const { Launcher } = await import("chrome-launcher");
|
|
16
|
+
const chromePath = Launcher.getFirstInstallation();
|
|
17
|
+
return await playwrightCore.chromium.launch({
|
|
18
|
+
headless: true,
|
|
19
|
+
executablePath: chromePath
|
|
20
|
+
});
|
|
21
|
+
} catch (e) {
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const playwright = await import(String("playwright"));
|
|
25
|
+
return await playwright.chromium.launch({
|
|
26
|
+
headless: true
|
|
27
|
+
});
|
|
28
|
+
} catch (e) {
|
|
29
|
+
throw new Error(`
|
|
30
|
+
Missing chromium binary. You need either 'playwright' or 'chrome-aws-lambda'.
|
|
31
|
+
Please run 'yarn add --dev playwright' or 'npm install --save-dev playwright'
|
|
32
|
+
`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export async function screenshot(browser, url, options) {
|
|
36
|
+
const page = await browser.newPage({
|
|
37
|
+
colorScheme: options.colorScheme
|
|
38
|
+
});
|
|
39
|
+
await page.setViewportSize({
|
|
40
|
+
width: options.width,
|
|
41
|
+
height: options.height
|
|
42
|
+
});
|
|
43
|
+
await page.goto(url, {
|
|
44
|
+
timeout: 1e4,
|
|
45
|
+
waitUntil: "networkidle"
|
|
46
|
+
});
|
|
47
|
+
if (options.mask) {
|
|
48
|
+
await page.evaluate((mask) => {
|
|
49
|
+
for (const el of document.querySelectorAll(mask))
|
|
50
|
+
el.style.display = "none";
|
|
51
|
+
}, options.mask);
|
|
52
|
+
}
|
|
53
|
+
if (options.selector)
|
|
54
|
+
await page.locator(options.selector).screenshot();
|
|
55
|
+
return await page.screenshot();
|
|
56
|
+
}
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
export declare const HtmlRendererRoute = "__og_image";
|
|
2
|
-
export declare const RuntimeImageSuffix = "og-image.png";
|
|
3
|
-
export declare const PayloadScriptId = "nuxt-og-image-payload";
|
|
4
|
-
export declare const MetaOgImageContentPlaceholder = "__NUXT_OG_IMAGE_PLACEHOLDER__";
|
|
5
|
-
export declare const LinkPrerenderId = "nuxt-og-image-screenshot-path";
|
|
6
1
|
export interface OgImagePayload {
|
|
7
2
|
runtime?: boolean;
|
|
8
3
|
title?: string;
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import { useServerHead } from "@vueuse/head";
|
|
2
2
|
import { useRouter } from "#imports";
|
|
3
|
-
|
|
4
|
-
export const RuntimeImageSuffix = "og-image.png";
|
|
5
|
-
export const PayloadScriptId = "nuxt-og-image-payload";
|
|
6
|
-
export const MetaOgImageContentPlaceholder = "__NUXT_OG_IMAGE_PLACEHOLDER__";
|
|
7
|
-
export const LinkPrerenderId = "nuxt-og-image-screenshot-path";
|
|
3
|
+
import { DefaultRuntimeImageSuffix, HtmlRendererRoute, LinkPrerenderId, MetaOgImageContentPlaceholder, PayloadScriptId } from "#nuxt-og-image/constants";
|
|
8
4
|
export function defineOgImageScreenshot() {
|
|
9
5
|
defineOgImage();
|
|
10
6
|
}
|
|
@@ -16,7 +12,7 @@ export function defineOgImage(options = {}) {
|
|
|
16
12
|
meta: [
|
|
17
13
|
{
|
|
18
14
|
property: "og:image",
|
|
19
|
-
content: () => options.runtime ? `${route}/${
|
|
15
|
+
content: () => options.runtime ? `${route}/${DefaultRuntimeImageSuffix}` : MetaOgImageContentPlaceholder
|
|
20
16
|
}
|
|
21
17
|
],
|
|
22
18
|
link: options.component ? [
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
export declare const HtmlRendererRoute = "__og_image";
|
|
2
|
-
export declare const PayloadScriptId = "nuxt-og-image-payload";
|
|
3
1
|
export declare const extractOgPayload: (html: string) => any;
|
|
4
2
|
export declare const inferOgPayload: (html: string) => Record<string, any>;
|
|
5
3
|
declare const _default: import("h3").EventHandler<string | undefined>;
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { withQuery, withoutTrailingSlash } from "ufo";
|
|
2
2
|
import { renderSSRHead } from "@unhead/ssr";
|
|
3
3
|
import { createHeadCore } from "@unhead/vue";
|
|
4
4
|
import { defineEventHandler, getQuery } from "h3";
|
|
5
|
-
|
|
6
|
-
export const PayloadScriptId = "nuxt-og-image-payload";
|
|
5
|
+
import { HtmlRendererRoute, PayloadScriptId } from "#nuxt-og-image/constants";
|
|
7
6
|
export const extractOgPayload = (html) => {
|
|
8
7
|
const payload = html.match(new RegExp(`<script id="${PayloadScriptId}" type="application/json">(.+?)<\/script>`))?.[1];
|
|
9
8
|
if (payload) {
|
|
@@ -1,9 +1,3 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
import type { Browser } from 'playwright-core';
|
|
3
|
-
import type { ScreenshotOptions } from '../../types';
|
|
4
|
-
export declare const HtmlRendererRoute = "__og_image";
|
|
5
|
-
export declare const RuntimeImageSuffix = "og-image.png";
|
|
6
|
-
export declare function screenshot(browser: Browser, url: string, options: ScreenshotOptions): Promise<Buffer>;
|
|
7
|
-
export declare function createBrowser(): Promise<Browser>;
|
|
8
2
|
declare const _default: import("h3").EventHandler<Buffer | undefined>;
|
|
9
3
|
export default _default;
|
|
@@ -1,43 +1,10 @@
|
|
|
1
1
|
import { defineEventHandler, getRequestHeader, setHeader } from "h3";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export async function screenshot(browser, url, options) {
|
|
5
|
-
const page = await browser.newPage({
|
|
6
|
-
colorScheme: options.colorScheme
|
|
7
|
-
});
|
|
8
|
-
await page.setViewportSize({
|
|
9
|
-
width: options.width,
|
|
10
|
-
height: options.height
|
|
11
|
-
});
|
|
12
|
-
await page.goto(url, {
|
|
13
|
-
timeout: 1e4,
|
|
14
|
-
waitUntil: "networkidle"
|
|
15
|
-
});
|
|
16
|
-
if (options.mask) {
|
|
17
|
-
await page.evaluate((mask) => {
|
|
18
|
-
for (const el of document.querySelectorAll(mask))
|
|
19
|
-
el.style.display = "none";
|
|
20
|
-
}, options.mask);
|
|
21
|
-
}
|
|
22
|
-
if (options.selector)
|
|
23
|
-
await page.locator(options.selector).screenshot();
|
|
24
|
-
return await page.screenshot();
|
|
25
|
-
}
|
|
26
|
-
export async function createBrowser() {
|
|
27
|
-
if (!process.env.AWS_LAMBDA_FUNCTION_NAME)
|
|
28
|
-
process.env.AWS_LAMBDA_FUNCTION_NAME = "test";
|
|
29
|
-
const playwright = await import("playwright-core");
|
|
30
|
-
const awsChrome = await import("chrome-aws-lambda");
|
|
31
|
-
return await playwright.chromium.launch({
|
|
32
|
-
args: awsChrome.args,
|
|
33
|
-
executablePath: await awsChrome.executablePath,
|
|
34
|
-
headless: awsChrome.headless
|
|
35
|
-
});
|
|
36
|
-
}
|
|
2
|
+
import { createBrowser, screenshot } from "../browserService.mjs";
|
|
3
|
+
import { DefaultRuntimeImageSuffix, HtmlRendererRoute } from "#nuxt-og-image/constants";
|
|
37
4
|
export default defineEventHandler(async (e) => {
|
|
38
|
-
if (!e.path?.endsWith(
|
|
5
|
+
if (!e.path?.endsWith(DefaultRuntimeImageSuffix))
|
|
39
6
|
return;
|
|
40
|
-
const path = e.path.replace(
|
|
7
|
+
const path = e.path.replace(DefaultRuntimeImageSuffix, HtmlRendererRoute);
|
|
41
8
|
const host = getRequestHeader(e, "host") || "localhost:3000";
|
|
42
9
|
const browser = await createBrowser();
|
|
43
10
|
setHeader(e, "Content-Type", "image/png");
|
package/dist/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-og-image",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.4.2",
|
|
5
5
|
"packageManager": "pnpm@7.8.0",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"funding": "https://github.com/sponsors/harlan-zw",
|
|
@@ -28,21 +28,16 @@
|
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@nuxt/kit": "3.0.0",
|
|
30
30
|
"chalk": "^5.2.0",
|
|
31
|
-
"chrome-
|
|
31
|
+
"chrome-launcher": "^0.15.1",
|
|
32
32
|
"defu": "^6.1.1",
|
|
33
33
|
"execa": "^6.1.0",
|
|
34
34
|
"fast-glob": "^3.2.12",
|
|
35
35
|
"ohash": "^1.0.0",
|
|
36
|
+
"pathe": "^1.0.0",
|
|
36
37
|
"playwright-core": "^1.28.1",
|
|
37
38
|
"radix3": "^1.0.0",
|
|
38
39
|
"ufo": "^1.0.1"
|
|
39
40
|
},
|
|
40
|
-
"unbuild": {
|
|
41
|
-
"externals": [
|
|
42
|
-
"playwright-core",
|
|
43
|
-
"chrome-aws-lambda"
|
|
44
|
-
]
|
|
45
|
-
},
|
|
46
41
|
"devDependencies": {
|
|
47
42
|
"@antfu/eslint-config": "^0.33.1",
|
|
48
43
|
"@nuxt/kit": "3.0.0",
|
|
@@ -52,7 +47,6 @@
|
|
|
52
47
|
"bumpp": "^8.2.1",
|
|
53
48
|
"eslint": "8.29.0",
|
|
54
49
|
"nuxt": "npm:nuxt3@latest",
|
|
55
|
-
"pathe": "^1.0.0",
|
|
56
50
|
"puppeteer": "^19.4.0",
|
|
57
51
|
"vitest": "^0.25.5"
|
|
58
52
|
},
|