@aklinker1/viteshot 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -49,25 +49,82 @@ Your screenshot designs go in `store/designs/{name}@{width}x{height}.{ext}`.
49
49
  - `{width}x{height}`: The size your screenshot should be rendered at (ex: "1280x600", "640x400").
50
50
  - `{ext}`: ViteShot supports a variety of file extensions, so you can build your designs using your preferred frontend framework!
51
51
 
52
- ### HTML (Recommended)
52
+ ### HTML
53
53
 
54
- You don't need a frontend framework to build a simple static layout.
54
+ 1. Define an HTML fragment, this will be added to the `body` element automatically.
55
+ 2. `<link>` to your CSS for styles or use an inline `<style>` block
56
+ 3. Translations can be accessed using the handlebars syntax
55
57
 
56
58
  ```html
57
- <div>My screenshot design</div>
59
+ <!-- designs/example@640x400.html-->
60
+ <link rel="stylesheet" href="assets/tailwind.css" />
61
+ <div>{{path.to.translation}}</div>
58
62
  ```
59
63
 
64
+ > [!NOTE]
65
+ >
66
+ > You don't need a frontend framework to build a simple static layout. Using HTML design files is recommended.
67
+
60
68
  ### Vue
61
69
 
62
- TODO
70
+ 1. Add `@vitejs/plugin-vue` to `store/viteshot.config.ts`
71
+ 2. Define your screenshot in a `.vue` file
72
+ 3. Import any styles, or use Vue's `<style>` block
73
+ 4. Use the `t` prop to access translations for the current locale
74
+
75
+ ```vue
76
+ <!-- store/designs/example@640x480.vue -->
77
+ <script lang="ts" setup>
78
+ import "../assets/tailwind.css";
79
+
80
+ defineProps<{
81
+ t: Record<string, any>;
82
+ }>();
83
+ </script>
84
+
85
+ <template>
86
+ <p>{{ t.path.to.message }}</p>
87
+ </template>
88
+ ```
63
89
 
64
90
  ### Svelte
65
91
 
66
- TODO
92
+ > [!WARN]
93
+ >
94
+ > Experimental, not 100% working yet.
95
+
96
+ 1. Add `@sveltejs/vite-plugin-svelte` to `store/viteshot.config.ts`
97
+ 2. Export your component as the default module from your `.svelte` file
98
+ 3. Import any styles or use an inline `<style>` block
99
+ 4. Use the `t` prop to access translations for the current locale
100
+
101
+ ```svelte
102
+ <!-- store/designs/example@640x480.svelte -->
103
+ <script lang="ts">
104
+ import "../assets/tailwind.css"
105
+
106
+ export let t: Record<string, any>
107
+ </script>
108
+
109
+ <p>{t.path.to.translation}</p>
110
+ ```
67
111
 
68
112
  ### React
69
113
 
70
- TODO
114
+ 1. Add `@vitejs/plugin-react` to `store/viteshot.config.ts`
115
+ 2. Export your component as the default module from your `.tsx` or `.jsx` file
116
+ 3. Import any styles
117
+ 4. Use the `t` prop to access translations for the current locale
118
+
119
+ ```tsx
120
+ // store/designs/example@640x400.tsx
121
+ import React from "react";
122
+ import "../assets/tailwind.css";
123
+
124
+ export default function (props: { t: Record<string, any> }) {
125
+ return <p>{t.path.to.translation}</p>;
126
+ }
127
+ ```
71
128
 
72
129
  ## Styling
73
130
 
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { n as createServer, t as generateScreenshots } from "./generate-screenshots-BvkErTTX.mjs";
2
+ import { n as createServer, t as generateScreenshots } from "./generate-screenshots-W20DkMBq.mjs";
3
3
 
4
4
  //#region src/cli.ts
5
5
  const [command, ..._args] = process.argv.slice(2);
@@ -16,11 +16,23 @@ var dashboard_default = "<!doctype html>\n<html lang=\"en\">\n <head>\n <met
16
16
 
17
17
  //#endregion
18
18
  //#region src/templates/screenshot.html?raw
19
- var screenshot_default = "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>Screenshot</title>\n <link rel=\"icon\" type=\"image/svg+xml\" href=\"/favicon.svg\" />\n <style>\n body {\n margin: 0;\n padding: 0;\n }\n </style>\n </head>\n <body>\n <div id=\"app\" class=\"app\"></div>\n <script type=\"module\">\n import { renderScreenshot } from \"/viteshot-virtual/render-screenshot/{{screenshot.id}}\";\n import messages from \"/viteshot-virtual/messages/{{locale.id}}\";\n\n renderScreenshot(app, messages);\n <\/script>\n </body>\n</html>\n";
19
+ var screenshot_default = "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>Screenshot</title>\n <link rel=\"icon\" type=\"image/svg+xml\" href=\"/favicon.svg\" />\n <style>\n body {\n margin: 0;\n padding: 0;\n }\n </style>\n </head>\n <body>\n <div id=\"app\" class=\"app\"></div>\n <script type=\"module\">\n console.log(\"TEST\", location.pathname);\n import { renderScreenshot } from \"/viteshot-virtual/render-screenshot/{{screenshot.id}}.js\";\n import messages from \"/viteshot-virtual/messages/{{locale.id}}\";\n console.log({ messages, renderScreenshot });\n\n renderScreenshot(app, messages);\n <\/script>\n </body>\n</html>\n";
20
20
 
21
21
  //#endregion
22
22
  //#region src/templates/render-html-screenshot.js?raw
23
- var render_html_screenshot_default = "import HTML from \"/@fs/{{path}}?raw\";\n\nexport function renderScreenshot(container, messages) {\n container.innerHTML = Object.entries(messages).reduce(\n (text, [key, value]) => text.replaceAll(`{{${key}}}`, value),\n HTML,\n );\n}\n";
23
+ var render_html_screenshot_default = "import HTML from \"/@fs/{{path}}?raw\";\n\nexport function renderScreenshot(container, messages) {\n container.innerHTML = Object.entries(messages).reduce(\n (text, [key, value]) =>\n text.replace(new RegExp(`\\\\{\\\\{\\\\s*?${key}\\\\s*?\\\\}\\\\}`, \"g\"), value),\n HTML,\n );\n}\n";
24
+
25
+ //#endregion
26
+ //#region src/templates/render-vue-screenshot.js?raw
27
+ var render_vue_screenshot_default = "import Component from \"/@fs/{{path}}\";\nimport { createApp } from \"vue\";\n\nexport function renderScreenshot(container, messages) {\n const app = createApp(Component, { t: messages });\n app.mount(container);\n}\n";
28
+
29
+ //#endregion
30
+ //#region src/templates/render-react-screenshot.js?raw
31
+ var render_react_screenshot_default = "import Component from \"/@fs/{{path}}\";\nimport { createElement } from \"react\";\nimport { createRoot } from \"react-dom/client\";\n\nexport function renderScreenshot(container, messages) {\n const root = createRoot(container);\n root.render(createElement(Component, { t: messages }));\n}\n";
32
+
33
+ //#endregion
34
+ //#region src/templates/render-svelte-screenshot.js?raw
35
+ var render_svelte_screenshot_default = "import Component from \"/@fs/{{path}}\";\nimport { mount } from \"svelte\";\n\nexport function renderScreenshot(container, messages) {\n console.log(\"svelte\", container);\n\n const app = mount(Component, {\n target: container,\n props: { t: messages },\n });\n console.log(app);\n}\n";
24
36
 
25
37
  //#endregion
26
38
  //#region src/utils.ts
@@ -86,7 +98,13 @@ const VIRTUAL_LOCALES_FILTER = { id: [/viteshot-virtual\/locales/] };
86
98
  function applyTemplateVars(template, vars) {
87
99
  return Object.entries(vars).reduce((template, [key, value]) => template.replaceAll(`{{${key}}}`, value), template);
88
100
  }
89
- const RENDER_SCREENSHOT_JS_TEMPLATES = { ".html": render_html_screenshot_default };
101
+ const RENDER_SCREENSHOT_JS_TEMPLATES = {
102
+ ".html": render_html_screenshot_default,
103
+ ".vue": render_vue_screenshot_default,
104
+ ".tsx": render_react_screenshot_default,
105
+ ".jsx": render_react_screenshot_default,
106
+ ".svelte": render_svelte_screenshot_default
107
+ };
90
108
  const resolverPlugin = (config) => [
91
109
  {
92
110
  name: "viteshot:resolve-favicon",
@@ -164,7 +182,7 @@ const resolverPlugin = (config) => [
164
182
  load: {
165
183
  filter: { id: [/^\/viteshot-virtual\/render-screenshot/] },
166
184
  handler: (id) => {
167
- const screenshotId = decodeURIComponent(id.slice(36));
185
+ const screenshotId = decodeURIComponent(id.slice(36, -3));
168
186
  if (!screenshotId) throw Error(`Required query param "id" not provided for ${id}`);
169
187
  const ext = extname(screenshotId);
170
188
  const path = join(config.designsDir, screenshotId);
@@ -193,7 +211,7 @@ function defineConfig(config) {
193
211
  return config;
194
212
  }
195
213
  async function importConfig(root) {
196
- const configFileUrl = pathToFileURL(join(root, "viteshot.config")).href;
214
+ const configFileUrl = pathToFileURL(join(root, "viteshot.config.ts")).href;
197
215
  try {
198
216
  return (await import(configFileUrl)).default ?? {};
199
217
  } catch (err) {
@@ -203,15 +221,15 @@ async function importConfig(root) {
203
221
  }
204
222
  async function resolveConfig(dir = process.cwd()) {
205
223
  const root = resolve(dir);
206
- const { localesDir: _localesDir, designsDir: _designsDir, screenshotsDir: _screenshotsDir, screenshotsConcurrency: _screenshotsConcurrency, ...vite } = await importConfig(root);
207
- const designsDir = _designsDir ? resolve(root, _designsDir) : join(root, "designs");
208
- const screenshotsDir = _screenshotsDir ? resolve(root, _screenshotsDir) : join(root, "screenshots");
224
+ const { screenshots: _screenshots, ...vite } = await importConfig(root);
225
+ const designsDir = _screenshots?.designsDir ? resolve(root, _screenshots.designsDir) : join(root, "designs");
226
+ const screenshotsDir = _screenshots?.screenshotsDir ? resolve(root, _screenshots.screenshotsDir) : join(root, "screenshots");
209
227
  const config = {
210
228
  root,
211
- localesDir: _localesDir ? resolve(root, _localesDir) : join(root, "locales"),
229
+ localesDir: _screenshots?.localesDir ? resolve(root, _screenshots.localesDir) : join(root, "locales"),
212
230
  designsDir,
213
231
  screenshotsDir,
214
- screenshotsConcurrency: _screenshotsConcurrency || 1,
232
+ renderConcurrency: _screenshots?.renderConcurrency || 4,
215
233
  vite: {
216
234
  ...vite,
217
235
  configFile: false
@@ -251,16 +269,22 @@ async function generateScreenshots(dir) {
251
269
  server = await createServer(config.vite);
252
270
  server.listen();
253
271
  const { port } = server.config.server;
254
- browser = await puppeteer.launch({ executablePath: process.env.VITESHOT_CHROME_PATH });
272
+ browser = await puppeteer.launch({
273
+ executablePath: process.env.VITESHOT_CHROME_PATH,
274
+ ...config.puppeteer?.launchOptions
275
+ });
255
276
  const screenshotMutex = new Mutex();
256
277
  await pMap(screenshots.flatMap((screenshot) => locales.map((locale) => ({
257
278
  screenshot,
258
279
  locale
259
280
  }))), async ({ screenshot, locale }) => {
260
- const outputId = (locale ? `${locale.language}/` : "") + screenshot.id.slice(0, -screenshot.ext.length) + ".webp";
281
+ const outputId = (locale ? `${locale.language}/` : "") + screenshot.name + ".webp";
261
282
  const outputPath = join(config.screenshotsDir, outputId);
262
283
  await mkdir(dirname(outputPath), { recursive: true });
263
- const page = await browser.newPage({ background: true });
284
+ const page = await browser.newPage({
285
+ background: true,
286
+ ...config.puppeteer?.newPageOptions
287
+ });
264
288
  await page.goto(`http://localhost:${port}/screenshot/${locale?.id ?? "null"}/${screenshot.id}`, {
265
289
  waitUntil: "networkidle0",
266
290
  timeout: 5e3
@@ -269,21 +293,22 @@ async function generateScreenshots(dir) {
269
293
  await page.bringToFront();
270
294
  await page.screenshot({
271
295
  captureBeyondViewport: true,
296
+ type: "webp",
297
+ quality: 100,
298
+ ...config.puppeteer?.screenshotOptions,
272
299
  clip: {
273
300
  x: 0,
274
301
  y: 0,
275
302
  width: screenshot.width,
276
303
  height: screenshot.height
277
304
  },
278
- type: "webp",
279
- quality: 100,
280
305
  path: outputPath
281
306
  });
282
307
  });
283
308
  console.log(` ✅ \x1b[2m./${relative(cwd, config.screenshotsDir)}/\x1b[0m\x1b[36m${outputId}\x1b[0m`);
284
- await page.close();
309
+ await page.close({ runBeforeUnload: false });
285
310
  }, {
286
- concurrency: config.screenshotsConcurrency,
311
+ concurrency: config.renderConcurrency,
287
312
  stopOnError: true
288
313
  });
289
314
  } catch (err) {
package/dist/index.d.mts CHANGED
@@ -1,11 +1,25 @@
1
1
  import { InlineConfig as InlineConfig$1, UserConfig as UserConfig$1, ViteDevServer } from "vite";
2
+ import { CreatePageOptions, LaunchOptions, ScreenshotOptions } from "puppeteer-core";
2
3
 
3
4
  //#region src/core/config.d.ts
5
+ type PuppeteerOptions = {
6
+ launchOptions?: LaunchOptions;
7
+ newPageOptions?: CreatePageOptions;
8
+ screenshotOptions?: Omit<ScreenshotOptions, "clip" | "path">;
9
+ };
4
10
  type UserConfig = UserConfig$1 & {
5
- localesDir?: string;
6
- designsDir?: string;
7
- screenshotsDir?: string;
8
- screenshotsConcurrency?: number;
11
+ screenshots?: {
12
+ /** @default "locales" */localesDir?: string; /** @default "designs" */
13
+ designsDir?: string; /** @default "screenshots" */
14
+ screenshotsDir?: string;
15
+ /**
16
+ * How many screenshots can be generated concurrently.
17
+ *
18
+ * @default 4
19
+ */
20
+ renderConcurrency?: number; /** Override the options passed into puppeteer. */
21
+ puppeteer?: PuppeteerOptions;
22
+ };
9
23
  };
10
24
  type InlineConfig = UserConfig & {
11
25
  root: string;
@@ -15,7 +29,8 @@ type ResolvedConfig = {
15
29
  localesDir: string;
16
30
  designsDir: string;
17
31
  screenshotsDir: string;
18
- screenshotsConcurrency: number;
32
+ renderConcurrency: number;
33
+ puppeteer?: PuppeteerOptions;
19
34
  vite: InlineConfig$1;
20
35
  };
21
36
  declare function defineConfig(config: UserConfig): UserConfig;
@@ -40,4 +55,4 @@ type Screenshot = {
40
55
  };
41
56
  declare function getScreenshots(designsDir: string): Promise<Screenshot[]>;
42
57
  //#endregion
43
- export { InlineConfig, ResolvedConfig, Screenshot, UserConfig, createServer, defineConfig, generateScreenshots, getScreenshots, resolveConfig };
58
+ export { InlineConfig, PuppeteerOptions, ResolvedConfig, Screenshot, UserConfig, createServer, defineConfig, generateScreenshots, getScreenshots, resolveConfig };
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { a as getScreenshots, i as resolveConfig, n as createServer, r as defineConfig, t as generateScreenshots } from "./generate-screenshots-BvkErTTX.mjs";
1
+ import { a as getScreenshots, i as resolveConfig, n as createServer, r as defineConfig, t as generateScreenshots } from "./generate-screenshots-W20DkMBq.mjs";
2
2
 
3
3
  export { createServer, defineConfig, generateScreenshots, getScreenshots, resolveConfig };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@aklinker1/viteshot",
3
3
  "description": "Generate store screenshots and promo images with code, powered by Vite",
4
- "version": "0.1.1",
4
+ "version": "0.2.0",
5
5
  "type": "module",
6
6
  "packageManager": "bun@1.3.9",
7
7
  "scripts": {
@@ -11,6 +11,8 @@
11
11
  "build": "tsdown src/index.ts src/cli.ts"
12
12
  },
13
13
  "dependencies": {
14
+ "@vitejs/plugin-react": "^6.0.1",
15
+ "@vitejs/plugin-vue": "^6.0.5",
14
16
  "async-mutex": "^0.5.0",
15
17
  "natural-compare-lite": "^1.4.0",
16
18
  "p-map": "^7.0.4",
@@ -18,12 +20,18 @@
18
20
  "puppeteer-core": "^24.37.5"
19
21
  },
20
22
  "peerDependencies": {
21
- "vite": "^5 || ^6 || ^7"
23
+ "vite": ">=5",
24
+ "vue": ">=3",
25
+ "react": ">=19",
26
+ "react-dom": ">=19",
27
+ "svelte": ">=5"
22
28
  },
23
29
  "devDependencies": {
24
30
  "@aklinker1/check": "^2.2.0",
31
+ "@sveltejs/vite-plugin-svelte": "^7.0.0",
25
32
  "@types/bun": "latest",
26
33
  "@types/natural-compare-lite": "^1.4.2",
34
+ "@types/react": "^19.2.14",
27
35
  "@typescript/native-preview": "^7.0.0-dev.20260223.1",
28
36
  "oxlint": "^1.50.0",
29
37
  "prettier": "^3.8.1",
@@ -31,8 +39,7 @@
31
39
  "publint": "^0.3.17",
32
40
  "puppeteer": "^24.37.5",
33
41
  "tsdown": "^0.20.3",
34
- "typescript": "^5",
35
- "vite": "^7.3.1"
42
+ "typescript": "^5"
36
43
  },
37
44
  "types": "dist/index.d.mts",
38
45
  "module": "dist/index.mjs",