diff --git a/packages/cta-engine/src/create-app.ts b/packages/cta-engine/src/create-app.ts index d466309..8e8970a 100644 --- a/packages/cta-engine/src/create-app.ts +++ b/packages/cta-engine/src/create-app.ts @@ -145,11 +145,7 @@ async function createPackageJSON( templateDir: string, routerDir: string, targetDir: string, - addOns: Array<{ - dependencies?: Record - devDependencies?: Record - scripts?: Record - }>, + addOns: Array, ) { let packageJSON = JSON.parse( await environment.readFile(resolve(templateDir, 'package.json'), 'utf8'), @@ -237,20 +233,28 @@ async function createPackageJSON( } for (const addOn of addOns) { + const { dependencies, devDependencies, scripts, pnpm, ...rest } = addOn packageJSON = { ...packageJSON, dependencies: { ...packageJSON.dependencies, - ...addOn.dependencies, + ...dependencies, }, devDependencies: { ...packageJSON.devDependencies, - ...addOn.devDependencies, + ...devDependencies, }, scripts: { ...packageJSON.scripts, - ...addOn.scripts, + ...scripts, }, + ...rest, + } + if (options.packageManager === 'pnpm') { + packageJSON.pnpm = { + ...(packageJSON?.pnpm ?? {}), + ...pnpm, + } } } diff --git a/packages/cta-engine/src/types.ts b/packages/cta-engine/src/types.ts index 0710381..d40e445 100644 --- a/packages/cta-engine/src/types.ts +++ b/packages/cta-engine/src/types.ts @@ -94,6 +94,8 @@ export type AddOn = { dependencies?: Record devDependencies?: Record scripts?: Record + } & { + [key: string]: Record } command?: { command: string diff --git a/packages/cta-engine/templates/react/add-on/pwa/README.md b/packages/cta-engine/templates/react/add-on/pwa/README.md new file mode 100644 index 0000000..975515d --- /dev/null +++ b/packages/cta-engine/templates/react/add-on/pwa/README.md @@ -0,0 +1,12 @@ +## Vite PWA + +- You application is now a PWA, using Vite PWA. + +### Notes + +- If you are using Deno, make sure that sharp postinstall script is run after installation. +```bash +deno install --allow-scripts=npm:sharp +``` + + diff --git a/packages/cta-engine/templates/react/add-on/pwa/assets/public/logo.svg b/packages/cta-engine/templates/react/add-on/pwa/assets/public/logo.svg new file mode 100644 index 0000000..d6c2da2 --- /dev/null +++ b/packages/cta-engine/templates/react/add-on/pwa/assets/public/logo.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/cta-engine/templates/react/add-on/pwa/assets/pwa-assets.config.ts b/packages/cta-engine/templates/react/add-on/pwa/assets/pwa-assets.config.ts new file mode 100644 index 0000000..5891daa --- /dev/null +++ b/packages/cta-engine/templates/react/add-on/pwa/assets/pwa-assets.config.ts @@ -0,0 +1,12 @@ +import { + defineConfig, + minimal2023Preset as preset, +} from '@vite-pwa/assets-generator/config' + +export default defineConfig({ + headLinkOptions: { + preset: '2023', + }, + preset, + images: ['public/logo.svg'], +}) diff --git a/packages/cta-engine/templates/react/add-on/pwa/assets/src/components/PWABadge/PWABadge.module.css b/packages/cta-engine/templates/react/add-on/pwa/assets/src/components/PWABadge/PWABadge.module.css new file mode 100644 index 0000000..0d4f9ae --- /dev/null +++ b/packages/cta-engine/templates/react/add-on/pwa/assets/src/components/PWABadge/PWABadge.module.css @@ -0,0 +1,29 @@ +.Container { + padding: 0; + margin: 0; + width: 0; + height: 0; +} +.Toast { + position: fixed; + right: 0; + bottom: 0; + margin: 16px; + padding: 12px; + border: 1px solid #8885; + border-radius: 4px; + z-index: 1; + text-align: left; + box-shadow: 3px 4px 5px 0 #8885; + background-color: white; +} +.Message { + margin-bottom: 8px; +} +.Toast-button { + border: 1px solid #8885; + outline: none; + margin-right: 5px; + border-radius: 2px; + padding: 3px 10px; +} diff --git a/packages/cta-engine/templates/react/add-on/pwa/assets/src/components/PWABadge/index.tsx b/packages/cta-engine/templates/react/add-on/pwa/assets/src/components/PWABadge/index.tsx new file mode 100644 index 0000000..a394b8f --- /dev/null +++ b/packages/cta-engine/templates/react/add-on/pwa/assets/src/components/PWABadge/index.tsx @@ -0,0 +1,81 @@ +import { useRegisterSW } from 'virtual:pwa-register/react' + +import styles from './PWABadge.module.css' + +function PWABadge() { + // check for updates every hour + const period = 60 * 60 * 1000 + + const { + offlineReady: [offlineReady, setOfflineReady], + needRefresh: [needRefresh, setNeedRefresh], + updateServiceWorker, + } = useRegisterSW({ + onRegisteredSW(swUrl, r) { + if (period <= 0) return + if (r?.active?.state === 'activated') { + registerPeriodicSync(period, swUrl, r) + } + else if (r?.installing) { + r.installing.addEventListener('statechange', (e) => { + const sw = e.target as ServiceWorker + if (sw.state === 'activated') + registerPeriodicSync(period, swUrl, r) + }) + } + }, + }) + + function close() { + setOfflineReady(false) + setNeedRefresh(false) + } + + return ( +
+ { (needRefresh || offlineReady) ? + ( +
+
+ {offlineReady ? ( + App ready to work offline + ) : ( + New content available, click on reload button to update. + )} +
+
+ {needRefresh ? ( + + ) : null} + +
+
+ ): null} +
+ ) +} + +export default PWABadge + +/** + * This function will register a periodic sync check every hour, you can modify the interval as needed. + */ +function registerPeriodicSync(period: number, swUrl: string, r: ServiceWorkerRegistration) { + if (period <= 0) return + + setInterval(async () => { + if ('onLine' in navigator && !navigator.onLine) + return + + const resp = await fetch(swUrl, { + cache: 'no-store', + headers: { + 'cache': 'no-store', + 'cache-control': 'no-cache', + }, + }) + + if (resp?.status === 200) + await r.update() + }, period) +} diff --git a/packages/cta-engine/templates/react/add-on/pwa/assets/src/integrations/pwa/layout.tsx b/packages/cta-engine/templates/react/add-on/pwa/assets/src/integrations/pwa/layout.tsx new file mode 100644 index 0000000..c60ff09 --- /dev/null +++ b/packages/cta-engine/templates/react/add-on/pwa/assets/src/integrations/pwa/layout.tsx @@ -0,0 +1,5 @@ +import PWABadge from '@/components/PWABadge' + +export default function LayoutAddition() { + return +} diff --git a/packages/cta-engine/templates/react/add-on/pwa/info.json b/packages/cta-engine/templates/react/add-on/pwa/info.json new file mode 100644 index 0000000..2104ea4 --- /dev/null +++ b/packages/cta-engine/templates/react/add-on/pwa/info.json @@ -0,0 +1,10 @@ +{ + "name": "Vite PWA", + "description": "Add PWA support to your application", + "phase": "add-on", + "link": "https://github.com/vite-pwa/vite-plugin-pwa", + "templates": [ + "file-router", + "code-router" + ] +} \ No newline at end of file diff --git a/packages/cta-engine/templates/react/add-on/pwa/package.json b/packages/cta-engine/templates/react/add-on/pwa/package.json new file mode 100644 index 0000000..67c759c --- /dev/null +++ b/packages/cta-engine/templates/react/add-on/pwa/package.json @@ -0,0 +1,17 @@ +{ + "devDependencies": { + "@vite-pwa/assets-generator": "^0.2.6", + "vite-plugin-pwa": "^0.21.1", + "workbox-core": "^7.3.0", + "workbox-window": "^7.3.0" + }, + "resolutions": { + "sharp": "0.32.6", + "sharp-ico": "0.1.5" + }, + "pnpm": { + "onlyBuiltDependencies": [ + "sharp" + ] + } +} \ No newline at end of file diff --git a/packages/cta-engine/templates/react/base/tsconfig.json.ejs b/packages/cta-engine/templates/react/base/tsconfig.json.ejs index 761e845..2471b81 100644 --- a/packages/cta-engine/templates/react/base/tsconfig.json.ejs +++ b/packages/cta-engine/templates/react/base/tsconfig.json.ejs @@ -6,7 +6,7 @@ "jsx": "react-jsx", "module": "ESNext", "lib": ["ES2022", "DOM", "DOM.Iterable"], - "types": ["vite/client"], + "types": ["vite/client"<% if (addOnEnabled['pwa']) { %>, "vite-plugin-pwa/react"<% } %>], /* Bundler mode */ "moduleResolution": "bundler", diff --git a/packages/cta-engine/templates/react/base/vite.config.js.ejs b/packages/cta-engine/templates/react/base/vite.config.js.ejs index c3990ee..4d04ae7 100644 --- a/packages/cta-engine/templates/react/base/vite.config.js.ejs +++ b/packages/cta-engine/templates/react/base/vite.config.js.ejs @@ -1,6 +1,9 @@ import { defineConfig } from "vite"; import viteReact from "@vitejs/plugin-react";<% if (tailwind) { %> import tailwindcss from "@tailwindcss/vite"; +<% } %><% if (addOnEnabled['pwa']) { %> +import { VitePWA } from 'vite-plugin-pwa'; +import pkg from './package.json'; <% } %><%if (fileRouter) { %> import { TanStackRouterVite } from "@tanstack/router-plugin/vite";<% } %><% if (addOnEnabled['module-federation']) { %> import { federation } from "@module-federation/vite";<% } %> @@ -10,7 +13,35 @@ import federationConfig from "./module-federation.config.js"; // https://vitejs.dev/config/ export default defineConfig({ - plugins: [<% if(fileRouter) { %>TanStackRouterVite({ autoCodeSplitting: true }), <% } %>viteReact()<% if (tailwind) { %>, tailwindcss()<% } %><% if (addOnEnabled['module-federation']) { %>, federation(federationConfig)<% } %>], + plugins: [<% if(fileRouter) { %>TanStackRouterVite({ autoCodeSplitting: true }), <% } %>viteReact()<% if (tailwind) { %>, tailwindcss()<% } %><% if (addOnEnabled['module-federation']) { %>, federation(federationConfig)<% } %><% if (addOnEnabled['pwa']) { %>, VitePWA({ + registerType: 'autoUpdate', + injectRegister: false, + + pwaAssets: { + disabled: false, + config: true, + }, + manifestFilename: 'manifest.json', + manifest: { + name: pkg.name, + short_name: pkg.name, + description: pkg.description || pkg.name, + theme_color: '#ffffff', + }, + + workbox: { + globPatterns: ['**/*.{js,css,html,svg,png,ico}'], + cleanupOutdatedCaches: true, + clientsClaim: true, + }, + + devOptions: { + enabled: false, + navigateFallback: 'index.html', + suppressWarnings: true, + type: 'module', + }, + })<% } %>], test: { globals: true, environment: "jsdom", diff --git a/packages/cta-engine/templates/solid/add-on/pwa/README.md b/packages/cta-engine/templates/solid/add-on/pwa/README.md new file mode 100644 index 0000000..975515d --- /dev/null +++ b/packages/cta-engine/templates/solid/add-on/pwa/README.md @@ -0,0 +1,12 @@ +## Vite PWA + +- You application is now a PWA, using Vite PWA. + +### Notes + +- If you are using Deno, make sure that sharp postinstall script is run after installation. +```bash +deno install --allow-scripts=npm:sharp +``` + + diff --git a/packages/cta-engine/templates/solid/add-on/pwa/assets/public/logo.svg b/packages/cta-engine/templates/solid/add-on/pwa/assets/public/logo.svg new file mode 100644 index 0000000..d6c2da2 --- /dev/null +++ b/packages/cta-engine/templates/solid/add-on/pwa/assets/public/logo.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/cta-engine/templates/solid/add-on/pwa/assets/pwa-assets.config.ts b/packages/cta-engine/templates/solid/add-on/pwa/assets/pwa-assets.config.ts new file mode 100644 index 0000000..5891daa --- /dev/null +++ b/packages/cta-engine/templates/solid/add-on/pwa/assets/pwa-assets.config.ts @@ -0,0 +1,12 @@ +import { + defineConfig, + minimal2023Preset as preset, +} from '@vite-pwa/assets-generator/config' + +export default defineConfig({ + headLinkOptions: { + preset: '2023', + }, + preset, + images: ['public/logo.svg'], +}) diff --git a/packages/cta-engine/templates/solid/add-on/pwa/assets/src/components/PWABadge/PWABadge.module.css b/packages/cta-engine/templates/solid/add-on/pwa/assets/src/components/PWABadge/PWABadge.module.css new file mode 100644 index 0000000..0d4f9ae --- /dev/null +++ b/packages/cta-engine/templates/solid/add-on/pwa/assets/src/components/PWABadge/PWABadge.module.css @@ -0,0 +1,29 @@ +.Container { + padding: 0; + margin: 0; + width: 0; + height: 0; +} +.Toast { + position: fixed; + right: 0; + bottom: 0; + margin: 16px; + padding: 12px; + border: 1px solid #8885; + border-radius: 4px; + z-index: 1; + text-align: left; + box-shadow: 3px 4px 5px 0 #8885; + background-color: white; +} +.Message { + margin-bottom: 8px; +} +.Toast-button { + border: 1px solid #8885; + outline: none; + margin-right: 5px; + border-radius: 2px; + padding: 3px 10px; +} diff --git a/packages/cta-engine/templates/solid/add-on/pwa/assets/src/components/PWABadge/index.tsx b/packages/cta-engine/templates/solid/add-on/pwa/assets/src/components/PWABadge/index.tsx new file mode 100644 index 0000000..831b322 --- /dev/null +++ b/packages/cta-engine/templates/solid/add-on/pwa/assets/src/components/PWABadge/index.tsx @@ -0,0 +1,83 @@ +import type { Component } from 'solid-js' +import { Show } from 'solid-js' +import { useRegisterSW } from 'virtual:pwa-register/solid' + +import styles from './PWABadge.module.css' + +const PWABadge: Component = () => { + // check for updates every hour + const period = 60 * 60 * 1000 + + const { + offlineReady: [offlineReady, setOfflineReady], + needRefresh: [needRefresh, setNeedRefresh], + updateServiceWorker, + } = useRegisterSW({ + onRegisteredSW(swUrl, r) { + if (period <= 0) return + if (r?.active?.state === 'activated') { + registerPeriodicSync(period, swUrl, r) + } + else if (r?.installing) { + r.installing.addEventListener('statechange', (e) => { + const sw = e.target as ServiceWorker + if (sw.state === 'activated') + registerPeriodicSync(period, swUrl, r) + }) + } + }, + }) + + function close() { + setOfflineReady(false) + setNeedRefresh(false) + } + + return ( + + ) +} + +export default PWABadge + +/** + * This function will register a periodic sync check every hour, you can modify the interval as needed. + */ +function registerPeriodicSync(period: number, swUrl: string, r: ServiceWorkerRegistration) { + if (period <= 0) return + + setInterval(async () => { + if ('onLine' in navigator && !navigator.onLine) + return + + const resp = await fetch(swUrl, { + cache: 'no-store', + headers: { + 'cache': 'no-store', + 'cache-control': 'no-cache', + }, + }) + + if (resp?.status === 200) + await r.update() + }, period) +} diff --git a/packages/cta-engine/templates/solid/add-on/pwa/assets/src/integrations/pwa/layout.tsx b/packages/cta-engine/templates/solid/add-on/pwa/assets/src/integrations/pwa/layout.tsx new file mode 100644 index 0000000..c0ee2d6 --- /dev/null +++ b/packages/cta-engine/templates/solid/add-on/pwa/assets/src/integrations/pwa/layout.tsx @@ -0,0 +1,5 @@ +import PWABadge from '~/components/PWABadge' + +export default function LayoutAddition() { + return +} diff --git a/packages/cta-engine/templates/solid/add-on/pwa/info.json b/packages/cta-engine/templates/solid/add-on/pwa/info.json new file mode 100644 index 0000000..2104ea4 --- /dev/null +++ b/packages/cta-engine/templates/solid/add-on/pwa/info.json @@ -0,0 +1,10 @@ +{ + "name": "Vite PWA", + "description": "Add PWA support to your application", + "phase": "add-on", + "link": "https://github.com/vite-pwa/vite-plugin-pwa", + "templates": [ + "file-router", + "code-router" + ] +} \ No newline at end of file diff --git a/packages/cta-engine/templates/solid/add-on/pwa/package.json b/packages/cta-engine/templates/solid/add-on/pwa/package.json new file mode 100644 index 0000000..67c759c --- /dev/null +++ b/packages/cta-engine/templates/solid/add-on/pwa/package.json @@ -0,0 +1,17 @@ +{ + "devDependencies": { + "@vite-pwa/assets-generator": "^0.2.6", + "vite-plugin-pwa": "^0.21.1", + "workbox-core": "^7.3.0", + "workbox-window": "^7.3.0" + }, + "resolutions": { + "sharp": "0.32.6", + "sharp-ico": "0.1.5" + }, + "pnpm": { + "onlyBuiltDependencies": [ + "sharp" + ] + } +} \ No newline at end of file diff --git a/packages/cta-engine/templates/solid/base/tsconfig.json.ejs b/packages/cta-engine/templates/solid/base/tsconfig.json.ejs index cccc6c9..e0caa1a 100644 --- a/packages/cta-engine/templates/solid/base/tsconfig.json.ejs +++ b/packages/cta-engine/templates/solid/base/tsconfig.json.ejs @@ -7,7 +7,7 @@ "jsxImportSource": "solid-js", "module": "ESNext", "lib": ["ES2022", "DOM", "DOM.Iterable"], - "types": ["vite/client"], + "types": ["vite/client"<% if (addOnEnabled['pwa']) { %>, "vite-plugin-pwa/solid"<% } %>], /* Bundler mode */ "moduleResolution": "bundler", @@ -21,11 +21,10 @@ "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true<% if (addOnEnabled['solid-ui']) { %>, + "noUncheckedSideEffectImports": true, "baseUrl": ".", "paths": { "~/*": ["./src/*"] } - <% } %> } } diff --git a/packages/cta-engine/templates/solid/base/vite.config.js.ejs b/packages/cta-engine/templates/solid/base/vite.config.js.ejs index a446358..d7c3247 100644 --- a/packages/cta-engine/templates/solid/base/vite.config.js.ejs +++ b/packages/cta-engine/templates/solid/base/vite.config.js.ejs @@ -1,8 +1,10 @@ -import { defineConfig } from 'vite' -<%if (fileRouter) { %>import { TanStackRouterVite } from '@tanstack/router-plugin/vite'<% } %> -import solidPlugin from 'vite-plugin-solid' -import tailwindcss from '@tailwindcss/vite'<% if (addOnEnabled['solid-ui']) { %> -import { resolve } from 'node:path'<% } %><% if (addOnEnabled['module-federation']) { %> +import { defineConfig } from 'vite'<% if (addOnEnabled['pwa']) { %> +import { VitePWA } from 'vite-plugin-pwa'; +import pkg from './package.json'; +<% } %><%if (fileRouter) { %>import { TanStackRouterVite } from '@tanstack/router-plugin/vite'<% } %> +import solidPlugin from 'vite-plugin-solid'; +import tailwindcss from '@tailwindcss/vite'; +import { resolve } from 'node:path';<% if (addOnEnabled['module-federation']) { %> import {federation} from "@module-federation/vite";<% } %><% if (addOnEnabled['module-federation']) { %> import federationConfig from "./module-federation.config.js"; <% } %> @@ -12,11 +14,40 @@ export default defineConfig({ plugins: [<% if (addOnEnabled['module-federation']) { %>federation(federationConfig), <% } %><%if (fileRouter) { %> TanStackRouterVite({ target: 'solid', autoCodeSplitting: true }),<% } %> solidPlugin(), - tailwindcss(), - ],<% if (addOnEnabled['solid-ui']) { %> + tailwindcss(),<% if (addOnEnabled['pwa']) { %> + VitePWA({ + registerType: 'autoUpdate', + injectRegister: false, + + pwaAssets: { + disabled: false, + config: true, + }, + manifestFilename: 'manifest.json', + manifest: { + name: pkg.name, + short_name: pkg.name, + description: pkg.description || pkg.name, + theme_color: '#ffffff', + }, + + workbox: { + globPatterns: ['**/*.{js,css,html,svg,png,ico}'], + cleanupOutdatedCaches: true, + clientsClaim: true, + }, + + devOptions: { + enabled: false, + navigateFallback: 'index.html', + suppressWarnings: true, + type: 'module', + }, + }),<% } %> + ], resolve: { alias: { '~': resolve(__dirname, './src'), }, - },<% } %> + } }) diff --git a/packages/cta-engine/templates/solid/file-router/src/routes/__root.tsx.ejs b/packages/cta-engine/templates/solid/file-router/src/routes/__root.tsx.ejs index bbf7028..6d44655 100644 --- a/packages/cta-engine/templates/solid/file-router/src/routes/__root.tsx.ejs +++ b/packages/cta-engine/templates/solid/file-router/src/routes/__root.tsx.ejs @@ -27,7 +27,7 @@ function RootComponent() {
<% } %> - {/* */} + <% for(const integration of integrations.filter(i => i.type === 'layout')) { %> <<%= integration.name %> />