├── .eslintignore ├── .eslintrc.cjs ├── .gitignore ├── .npmrc ├── .prettierignore ├── README.md ├── TODO.md ├── example ├── .eslintignore ├── .eslintrc.cjs ├── .gitignore ├── .node-version ├── .prettierignore ├── README.md ├── adapters │ └── cloudflare-pages │ │ └── vite.config.ts ├── package.json ├── pnpm-lock.yaml ├── public │ ├── _headers │ ├── _redirects │ ├── favicon.svg │ ├── fonts │ │ ├── poppins-400.woff2 │ │ ├── poppins-500.woff2 │ │ └── poppins-700.woff2 │ ├── manifest.json │ ├── robots.txt │ └── screenshot.png ├── pwa-assets.config.ts ├── src │ ├── components │ │ ├── router-head │ │ │ └── router-head.tsx │ │ └── starter │ │ │ ├── counter │ │ │ ├── counter.module.css │ │ │ └── counter.tsx │ │ │ ├── footer │ │ │ ├── footer.module.css │ │ │ └── footer.tsx │ │ │ ├── gauge │ │ │ ├── gauge.module.css │ │ │ └── index.tsx │ │ │ ├── header │ │ │ ├── header.module.css │ │ │ └── header.tsx │ │ │ ├── hero │ │ │ ├── hero.module.css │ │ │ └── hero.tsx │ │ │ ├── icons │ │ │ └── qwik.tsx │ │ │ ├── infobox │ │ │ ├── infobox.module.css │ │ │ └── infobox.tsx │ │ │ └── next-steps │ │ │ ├── next-steps.module.css │ │ │ └── next-steps.tsx │ ├── entry.cloudflare-pages.tsx │ ├── entry.dev.tsx │ ├── entry.preview.tsx │ ├── entry.ssr.tsx │ ├── global.css │ ├── media │ │ └── thunder.png │ ├── root.tsx │ └── routes │ │ ├── demo │ │ ├── flower │ │ │ ├── flower.css │ │ │ └── index.tsx │ │ └── todolist │ │ │ ├── index.tsx │ │ │ └── todolist.module.css │ │ ├── dynamic │ │ └── [id] │ │ │ └── index.tsx │ │ ├── index.tsx │ │ ├── layout.tsx │ │ ├── service-worker.ts │ │ ├── static │ │ └── [id] │ │ │ └── index.tsx │ │ └── styles.css ├── tsconfig.json └── vite.config.ts ├── package.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── scripts └── postbuild.mjs ├── src ├── assets │ ├── build.ts │ ├── config.ts │ ├── dev.ts │ ├── generator.ts │ ├── html.ts │ ├── manifest.ts │ ├── options.ts │ ├── types.ts │ └── utils.ts ├── context.ts ├── head.ts ├── index.ts ├── plugins │ ├── assets.ts │ ├── client.ts │ ├── main.ts │ └── ssr.ts ├── sw.ts └── types.ts ├── tsconfig.json └── vite.config.ts /.eslintignore: -------------------------------------------------------------------------------- 1 | **/*.log 2 | **/.DS_Store 3 | *. 4 | .vscode/settings.json 5 | .history 6 | .yarn 7 | bazel-* 8 | bazel-bin 9 | bazel-out 10 | bazel-qwik 11 | bazel-testlogs 12 | dist 13 | dist-dev 14 | lib 15 | lib-types 16 | etc 17 | external 18 | node_modules 19 | temp 20 | tsc-out 21 | tsdoc-metadata.json 22 | target 23 | output 24 | rollup.config.js 25 | build 26 | .cache 27 | .vscode 28 | .rollup.cache 29 | dist 30 | tsconfig.tsbuildinfo 31 | vite.config.ts 32 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | es2021: true, 6 | node: true, 7 | }, 8 | extends: [ 9 | "eslint:recommended", 10 | "plugin:@typescript-eslint/recommended", 11 | "plugin:qwik/recommended", 12 | ], 13 | parser: "@typescript-eslint/parser", 14 | parserOptions: { 15 | tsconfigRootDir: __dirname, 16 | project: ["./tsconfig.json"], 17 | ecmaVersion: 2021, 18 | sourceType: "module", 19 | ecmaFeatures: { 20 | jsx: true, 21 | }, 22 | }, 23 | plugins: ["@typescript-eslint"], 24 | rules: { 25 | "@typescript-eslint/no-explicit-any": "off", 26 | "@typescript-eslint/explicit-module-boundary-types": "off", 27 | "@typescript-eslint/no-inferrable-types": "off", 28 | "@typescript-eslint/no-non-null-assertion": "off", 29 | "@typescript-eslint/no-empty-interface": "off", 30 | "@typescript-eslint/no-namespace": "off", 31 | "@typescript-eslint/no-empty-function": "off", 32 | "@typescript-eslint/no-this-alias": "off", 33 | "@typescript-eslint/ban-types": "off", 34 | "@typescript-eslint/ban-ts-comment": "off", 35 | "prefer-spread": "off", 36 | "no-case-declarations": "off", 37 | "no-console": "off", 38 | "@typescript-eslint/no-unused-vars": ["error"], 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build 2 | dist 3 | lib 4 | lib-types 5 | server 6 | 7 | # Development 8 | node_modules 9 | # Cache 10 | .cache 11 | .mf 12 | .vscode 13 | .rollup.cache 14 | tsconfig.tsbuildinfo 15 | 16 | # Logs 17 | logs 18 | *.log 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | pnpm-debug.log* 23 | lerna-debug.log* 24 | 25 | # Editor 26 | !.vscode/extensions.json 27 | .idea 28 | .DS_Store 29 | *.suo 30 | *.ntvs* 31 | *.njsproj 32 | *.sln 33 | *.sw? 34 | 35 | # Yarn 36 | .yarn/* 37 | !.yarn/releases 38 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shell-emulator=true 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Files Prettier should not format 2 | **/*.log 3 | **/.DS_Store 4 | *. 5 | dist 6 | node_modules 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Qwik PWA 📱 2 | 3 | Turn your Qwik Application into an offline compatible PWA (Progressive Web Application) using Workbox but without the hassle. 4 | 5 | ## Installation 6 | 7 | ```sh 8 | npm install --save-dev @qwikdev/pwa 9 | ``` 10 | 11 | `vite.config.ts`: 12 | 13 | ```ts 14 | import { qwikPwa } from "@qwikdev/pwa"; 15 | 16 | export default defineConfig(() => { 17 | return { 18 | define: { 19 | // (optional) enables debugging in workbox 20 | "process.env.NODE_ENV": JSON.stringify("development"), 21 | }, 22 | plugins: [ 23 | qwikCity(), 24 | qwikVite(), 25 | // The options are set by default 26 | qwikPwa({ 27 | /* options */ 28 | }), 29 | ], 30 | }; 31 | }); 32 | ``` 33 | 34 | `src/routes/service-worker.ts`: 35 | 36 | ```diff 37 | import { setupServiceWorker } from "@builder.io/qwik-city/service-worker"; 38 | import { setupPwa } from "@qwikdev/pwa/sw"; 39 | 40 | setupServiceWorker(); 41 | 42 | +setupPwa(); 43 | 44 | - addEventListener("install", () => self.skipWaiting()); 45 | 46 | - addEventListener("activate", () => self.clients.claim()); 47 | 48 | - declare const self: ServiceWorkerGlobalScope; 49 | ``` 50 | 51 | By default, your application will be auto-updated when there's a new version of the service worker available and it is installed: in a future version, you will be able to customize this behavior to use `prompt` for update: 52 | ```ts 53 | import { setupServiceWorker } from "@builder.io/qwik-city/service-worker"; 54 | import { setupPwa } from "@qwikdev/pwa/sw"; 55 | 56 | setupServiceWorker(); 57 | setupPwa("prompt"); 58 | ``` 59 | 60 | `public/manifest.json`: 61 | ```diff 62 | "background_color": "#fff", 63 | + "theme_color": "#fff", 64 | ``` 65 | 66 | For more information, check the following pages: 67 | - [PWA Minimal Icons Requirements](https://vite-pwa-org.netlify.app/assets-generator/#pwa-minimal-icons-requirements) 68 | - [PWA Minimal Requirements](https://vite-pwa-org.netlify.app/guide/pwa-minimal-requirements.html) 69 | - [Add a web app manifest](https://web.dev/articles/add-manifest) 70 | 71 | `src/components/router-head/router-head.tsx`: 72 | 73 | ```tsx 74 | // PWA compatible generated icons for different browsers 75 | import * as pwaHead from "@qwikdev/pwa/head"; 76 | 77 | export const RouterHead = component$(() => { 78 | ... 79 | {pwaHead.meta.map((l) => ( 80 | 81 | ))} 82 | {pwaHead.links.map((l) => ( 83 | 84 | ))} 85 | ... 86 | ``` 87 | 88 | Make sure you remove the `` line in your router-head file. 89 | 90 | Now your application is PWA-friendly. 91 | 92 | ## Precache 93 | 94 | > One feature of service workers is the ability to save a set of files to the cache when the service worker is installing. This is often referred to as "precaching", since you are caching content ahead of the service worker being used. [Chrome for Developers](https://developer.chrome.com/docs/workbox/modules/workbox-precaching/) 95 | 96 | ### Assets 97 | 98 | Assets like js modules generated by qwik (`q-*.js`), images, `public/` assets, or any file that's emitted in the `dist/` directory by the build step would be precached when the service worker instantiates, so the plugin makes sure it provides the best client-side offline experience. 99 | 100 | ### Routes 101 | 102 | By default in this plugin, every route that does not include params (`/` or `/demo/flower`) is precached on the first run of the application when the browser registers the service worker. 103 | 104 | For the rest of the defined routes (routes with params like `/dynamic/[id]`, SSG routes, or API routes), they are not precached, but there are [workbox navigation routes](https://developer.chrome.com/docs/workbox/modules/workbox-routing) defined that would cache them on-demand and per request, the reason is precaching too many assets on the first run would cause a laggy experience for the user, especially when these kind of routes have the potential to generate so many more files. 105 | 106 | Imagine there's an SSG `/blog/[id]` route that generates 120 blog posts, in this case, fetching 120 pages of blog posts in the application startup would not seem ideal. 107 | 108 | #### Solution 109 | 110 | Just fetch the desired page or asset so it gets cached for later uses. 111 | 112 | ```ts 113 | fetch("/blog/how-to-use-qwik"); 114 | ``` 115 | 116 | ### API Routes 117 | 118 | For API routes and any other routes that do not meet the conditions mentioned above, there's a [Network-First](https://developer.chrome.com/docs/workbox/modules/workbox-strategies/#network_first_network_falling_back_to_cach) handler. 119 | 120 | ## Manifest 121 | 122 | The plugin would generate all of the [adaptive](https://web.dev/articles/maskable-icon) icons needed for different devices with different ratios in `manifest.json` based on your main icon in the build process using [@vite-pwa/assets-generator](https://vite-pwa-org.netlify.app/assets-generator/api.html#api). 123 | 124 | ### Screenshots 125 | 126 | For full PWA compatibility, you can put your [screenshots](https://developer.mozilla.org/en-US/docs/Web/Manifest/screenshots) with the following pattern in the `public/manifest.json` file. 127 | 128 | ```json 129 | ... 130 | "screenshots": [ 131 | { 132 | "src": "/screenshot.png", 133 | "type": "image/png", 134 | "sizes": "862x568" 135 | }, 136 | { 137 | "src": "/screenshot.png", 138 | "type": "image/png", 139 | "sizes": "862x568", 140 | "form_factor": "wide" 141 | } 142 | ] 143 | ``` 144 | 145 | For beautiful screenshots, you can use [Progressier Screenshots Generator](https://progressier.com/pwa-screenshots-generator). 146 | 147 | ### Cloudflare deployment 148 | 149 | **PNPM** 150 | 1. Need set resolutions w/ `"sharp": "0.32.6"` on **package.json**; 151 | 2. It is necessary to create a file **npm-lock.yaml** by `pnpm install`. 152 | 153 | **Bun** 154 | 1. Need set resolutions w/ `"sharp": "0.32.6"` on **package.json**; 155 | 2. It is necessary to create a file **bun.lockb** by `bun install`. 156 | 3. Set [ENV](https://developers.cloudflare.com/pages/configuration/language-support-and-tools/#supported-languages-and-tools) BUN_VERSION=1.0.5 (or higher) in Cloudflare. Cuz the default version of **Bun** - 1.0.1 doesn't work. 157 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO LIST 2 | 3 | - [ ] fix eslint/prettier: it is a pain to have to include the type in static imports 4 | - [x] fix workbox runtime warnings: there are a few workbox runtime warnings in the example that should be checked (build/q-*.[webp|css]) 5 | - [ ] feat add prompt for update strategy: the user can lost form data if filling a form when the update is triggered 6 | - [x] test custom pwa assets generator config file: on change the app should receive a page reload (no dev server restart), maybe with a new example 7 | - [x] don't inject web manifest icons when present in the manifest 8 | - [x] include id and scope in the web manifest when missing 9 | - [x] warn when missing `theme_color` in the web manifest 10 | 11 | ## Fix workbox runtime warnings 12 | 13 | We must include the revision with `null` value in the precache manifest for `build/q-**` assets. 14 | -------------------------------------------------------------------------------- /example/.eslintignore: -------------------------------------------------------------------------------- 1 | **/*.log 2 | **/.DS_Store 3 | *. 4 | .vscode/settings.json 5 | .history 6 | .yarn 7 | bazel-* 8 | bazel-bin 9 | bazel-out 10 | bazel-qwik 11 | bazel-testlogs 12 | dist 13 | dist-dev 14 | lib 15 | lib-types 16 | etc 17 | external 18 | node_modules 19 | temp 20 | tsc-out 21 | tsdoc-metadata.json 22 | target 23 | output 24 | rollup.config.js 25 | build 26 | .cache 27 | .vscode 28 | .rollup.cache 29 | dist 30 | tsconfig.tsbuildinfo 31 | vite.config.ts 32 | *.spec.tsx 33 | *.spec.ts 34 | .netlify 35 | pnpm-lock.yaml 36 | package-lock.json 37 | yarn.lock 38 | server 39 | -------------------------------------------------------------------------------- /example/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | es2021: true, 6 | node: true, 7 | }, 8 | extends: [ 9 | "eslint:recommended", 10 | "plugin:@typescript-eslint/recommended", 11 | "plugin:qwik/recommended", 12 | ], 13 | parser: "@typescript-eslint/parser", 14 | parserOptions: { 15 | tsconfigRootDir: __dirname, 16 | project: ["./tsconfig.json"], 17 | ecmaVersion: 2021, 18 | sourceType: "module", 19 | ecmaFeatures: { 20 | jsx: true, 21 | }, 22 | }, 23 | plugins: ["@typescript-eslint"], 24 | rules: { 25 | "@typescript-eslint/no-explicit-any": "off", 26 | "@typescript-eslint/explicit-module-boundary-types": "off", 27 | "@typescript-eslint/no-inferrable-types": "off", 28 | "@typescript-eslint/no-non-null-assertion": "off", 29 | "@typescript-eslint/no-empty-interface": "off", 30 | "@typescript-eslint/no-namespace": "off", 31 | "@typescript-eslint/no-empty-function": "off", 32 | "@typescript-eslint/no-this-alias": "off", 33 | "@typescript-eslint/ban-types": "off", 34 | "@typescript-eslint/ban-ts-comment": "off", 35 | "prefer-spread": "off", 36 | "no-case-declarations": "off", 37 | "no-console": "off", 38 | "@typescript-eslint/no-unused-vars": ["error"], 39 | "@typescript-eslint/consistent-type-imports": "warn", 40 | "@typescript-eslint/no-unnecessary-condition": "warn", 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Cloudflare 2 | functions/**/*.js 3 | -------------------------------------------------------------------------------- /example/.node-version: -------------------------------------------------------------------------------- 1 | 16 2 | -------------------------------------------------------------------------------- /example/.prettierignore: -------------------------------------------------------------------------------- 1 | **/*.log 2 | **/.DS_Store 3 | *. 4 | .vscode/settings.json 5 | .history 6 | .yarn 7 | bazel-* 8 | bazel-bin 9 | bazel-out 10 | bazel-qwik 11 | bazel-testlogs 12 | dist 13 | dist-dev 14 | lib 15 | lib-types 16 | etc 17 | external 18 | node_modules 19 | temp 20 | tsc-out 21 | tsdoc-metadata.json 22 | target 23 | output 24 | rollup.config.js 25 | build 26 | .cache 27 | .vscode 28 | .rollup.cache 29 | dist 30 | tsconfig.tsbuildinfo 31 | vite.config.ts 32 | *.spec.tsx 33 | *.spec.ts 34 | .netlify 35 | pnpm-lock.yaml 36 | package-lock.json 37 | yarn.lock 38 | server 39 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # Qwik City App ⚡️ 2 | 3 | - [Qwik Docs](https://qwik.builder.io/) 4 | - [Discord](https://qwik.builder.io/chat) 5 | - [Qwik GitHub](https://github.com/BuilderIO/qwik) 6 | - [@QwikDev](https://twitter.com/QwikDev) 7 | - [Vite](https://vitejs.dev/) 8 | 9 | --- 10 | 11 | ## Project Structure 12 | 13 | This project is using Qwik with [QwikCity](https://qwik.builder.io/qwikcity/overview/). QwikCity is just an extra set of tools on top of Qwik to make it easier to build a full site, including directory-based routing, layouts, and more. 14 | 15 | Inside your project, you'll see the following directory structure: 16 | 17 | ``` 18 | ├── public/ 19 | │ └── ... 20 | └── src/ 21 | ├── components/ 22 | │ └── ... 23 | └── routes/ 24 | └── ... 25 | ``` 26 | 27 | - `src/routes`: Provides the directory-based routing, which can include a hierarchy of `layout.tsx` layout files, and an `index.tsx` file as the page. Additionally, `index.ts` files are endpoints. Please see the [routing docs](https://qwik.builder.io/qwikcity/routing/overview/) for more info. 28 | 29 | - `src/components`: Recommended directory for components. 30 | 31 | - `public`: Any static assets, like images, can be placed in the public directory. Please see the [Vite public directory](https://vitejs.dev/guide/assets.html#the-public-directory) for more info. 32 | 33 | ## Add Integrations and deployment 34 | 35 | Use the `pnpm qwik add` command to add additional integrations. Some examples of integrations includes: Cloudflare, Netlify or Express Server, and the [Static Site Generator (SSG)](https://qwik.builder.io/qwikcity/guides/static-site-generation/). 36 | 37 | ```shell 38 | pnpm qwik add # or `pnpm qwik add` 39 | ``` 40 | 41 | ## Development 42 | 43 | Development mode uses [Vite's development server](https://vitejs.dev/). The `dev` command will server-side render (SSR) the output during development. 44 | 45 | ```shell 46 | npm start # or `pnpm start` 47 | ``` 48 | 49 | > Note: during dev mode, Vite may request a significant number of `.js` files. This does not represent a Qwik production build. 50 | 51 | ## Preview 52 | 53 | The preview command will create a production build of the client modules, a production build of `src/entry.preview.tsx`, and run a local server. The preview server is only for convenience to preview a production build locally and should not be used as a production server. 54 | 55 | ```shell 56 | pnpm preview # or `pnpm preview` 57 | ``` 58 | 59 | ## Production 60 | 61 | The production build will generate client and server modules by running both client and server build commands. The build command will use Typescript to run a type check on the source code. 62 | 63 | ```shell 64 | pnpm build # or `pnpm build` 65 | ``` 66 | 67 | ## Static Site Generator (Node.js) 68 | 69 | ```shell 70 | pnpm build.server 71 | ``` 72 | 73 | ## Cloudflare Pages 74 | 75 | Cloudflare's [wrangler](https://github.com/cloudflare/wrangler) CLI can be used to preview a production build locally. To start a local server, run: 76 | 77 | ``` 78 | npm run serve 79 | ``` 80 | 81 | Then visit [http://localhost:8787/](http://localhost:8787/) 82 | 83 | ### Deployments 84 | 85 | [Cloudflare Pages](https://pages.cloudflare.com/) are deployable through their [Git provider integrations](https://developers.cloudflare.com/pages/platform/git-integration/). 86 | 87 | If you don't already have an account, then [create a Cloudflare account here](https://dash.cloudflare.com/sign-up/pages). Next go to your dashboard and follow the [Cloudflare Pages deployment guide](https://developers.cloudflare.com/pages/framework-guides/deploy-anything/). 88 | 89 | Within the projects "Settings" for "Build and deployments", the "Build command" should be `npm run build`, and the "Build output directory" should be set to `dist`. 90 | 91 | ### Function Invocation Routes 92 | 93 | Cloudflare Page's [function-invocation-routes config](https://developers.cloudflare.com/pages/platform/functions/routing/#functions-invocation-routes) can be used to include, or exclude, certain paths to be used by the worker functions. Having a `_routes.json` file gives developers more granular control over when your Function is invoked. 94 | This is useful to determine if a page response should be Server-Side Rendered (SSR) or if the response should use a static-site generated (SSG) `index.html` file. 95 | 96 | By default, the Cloudflare pages adaptor _does not_ include a `public/_routes.json` config, but rather it is auto-generated from the build by the Cloudflare adaptor. An example of an auto-generate `dist/_routes.json` would be: 97 | 98 | ``` 99 | { 100 | "include": [ 101 | "/*" 102 | ], 103 | "exclude": [ 104 | "/_headers", 105 | "/_redirects", 106 | "/build/*", 107 | "/favicon.ico", 108 | "/manifest.json", 109 | "/service-worker.js", 110 | "/about" 111 | ], 112 | "version": 1 113 | } 114 | ``` 115 | 116 | In the above example, it's saying _all_ pages should be SSR'd. However, the root static files such as `/favicon.ico` and any static assets in `/build/*` should be excluded from the Functions, and instead treated as a static file. 117 | 118 | In most cases the generated `dist/_routes.json` file is ideal. However, if you need more granular control over each path, you can instead provide you're own `public/_routes.json` file. When the project provides its own `public/_routes.json` file, then the Cloudflare adaptor will not auto-generate the routes config and instead use the committed one within the `public` directory. 119 | -------------------------------------------------------------------------------- /example/adapters/cloudflare-pages/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { cloudflarePagesAdapter } from "@builder.io/qwik-city/adapters/cloudflare-pages/vite"; 2 | import { extendConfig } from "@builder.io/qwik-city/vite"; 3 | import baseConfig from "../../vite.config"; 4 | 5 | export default extendConfig(baseConfig, () => { 6 | return { 7 | build: { 8 | ssr: true, 9 | rollupOptions: { 10 | input: ["src/entry.cloudflare-pages.tsx", "@qwik-city-plan"], 11 | }, 12 | }, 13 | plugins: [cloudflarePagesAdapter()], 14 | }; 15 | }); 16 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "description": "Demo App with Routing built-in (recommended)", 4 | "engines": { 5 | "node": ">=15.0.0" 6 | }, 7 | "trustedDependencies": [ 8 | "sharp" 9 | ], 10 | "scripts": { 11 | "build": "qwik build", 12 | "build.client": "vite build", 13 | "build.preview": "vite build --ssr src/entry.preview.tsx", 14 | "build.server": "vite build -c adapters/cloudflare-pages/vite.config.ts", 15 | "deploy": "wrangler pages publish ./dist", 16 | "dev": "vite --mode ssr", 17 | "dev.custom": "CUSTOM_CONFIG=true vite --mode ssr", 18 | "dev.debug": "node --inspect-brk ./node_modules/vite/bin/vite.js --mode ssr --force", 19 | "fmt": "prettier --write .", 20 | "fmt.check": "prettier --check .", 21 | "preview": "qwik build preview && vite preview", 22 | "serve": "wrangler pages dev ./dist", 23 | "start": "vite --open --mode ssr", 24 | "qwik": "qwik" 25 | }, 26 | "devDependencies": { 27 | "@builder.io/qwik": "^1.4.5", 28 | "@builder.io/qwik-city": "^1.4.5", 29 | "@qwikdev/pwa": "workspace:../", 30 | "@types/eslint": "^8.56.3", 31 | "@types/node": "^20.11.20", 32 | "@typescript-eslint/eslint-plugin": "^6.21.0", 33 | "@typescript-eslint/parser": "^6.21.0", 34 | "@vite-pwa/assets-generator": "^0.2.4", 35 | "eslint": "^8.57.0", 36 | "eslint-plugin-qwik": "^1.4.5", 37 | "fast-glob": "^3.3.2", 38 | "prettier": "^3.2.5", 39 | "typescript": "^5.3.3", 40 | "undici": "^5.28.3", 41 | "vite": "^4.5.2", 42 | "vite-tsconfig-paths": "^4.3.1", 43 | "wrangler": "^3.29.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /example/public/_headers: -------------------------------------------------------------------------------- 1 | # https://developers.cloudflare.com/pages/platform/headers/ 2 | 3 | /build/* 4 | Cache-Control: public, max-age=31536000, s-maxage=31536000, immutable 5 | -------------------------------------------------------------------------------- /example/public/_redirects: -------------------------------------------------------------------------------- 1 | # https://developers.cloudflare.com/pages/platform/redirects/ 2 | -------------------------------------------------------------------------------- /example/public/favicon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/public/fonts/poppins-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QwikCommunity/pwa/775f6f1ab2fc3762dfaca44b28782ea18472213a/example/public/fonts/poppins-400.woff2 -------------------------------------------------------------------------------- /example/public/fonts/poppins-500.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QwikCommunity/pwa/775f6f1ab2fc3762dfaca44b28782ea18472213a/example/public/fonts/poppins-500.woff2 -------------------------------------------------------------------------------- /example/public/fonts/poppins-700.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QwikCommunity/pwa/775f6f1ab2fc3762dfaca44b28782ea18472213a/example/public/fonts/poppins-700.woff2 -------------------------------------------------------------------------------- /example/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/web-manifest-combined.json", 3 | "name": "qwik-project-name", 4 | "short_name": "Welcome to Qwik", 5 | "start_url": ".", 6 | "display": "standalone", 7 | "background_color": "#fff", 8 | "theme_color": "#fff", 9 | "description": "A Qwik project app.", 10 | "screenshots": [ 11 | { 12 | "src": "/screenshot.png", 13 | "type": "image/png", 14 | "sizes": "862x568" 15 | }, 16 | { 17 | "src": "/screenshot.png", 18 | "type": "image/png", 19 | "sizes": "862x568", 20 | "form_factor": "wide" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /example/public/robots.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QwikCommunity/pwa/775f6f1ab2fc3762dfaca44b28782ea18472213a/example/public/robots.txt -------------------------------------------------------------------------------- /example/public/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QwikCommunity/pwa/775f6f1ab2fc3762dfaca44b28782ea18472213a/example/public/screenshot.png -------------------------------------------------------------------------------- /example/pwa-assets.config.ts: -------------------------------------------------------------------------------- 1 | import { 2 | defineConfig, 3 | minimal2023Preset as preset, 4 | } from "@vite-pwa/assets-generator/config"; 5 | 6 | export default defineConfig({ 7 | headLinkOptions: { 8 | preset: "2023", 9 | }, 10 | preset, 11 | images: ["public/favicon.svg"], 12 | }); 13 | -------------------------------------------------------------------------------- /example/src/components/router-head/router-head.tsx: -------------------------------------------------------------------------------- 1 | import { useDocumentHead, useLocation } from "@builder.io/qwik-city"; 2 | 3 | import { component$ } from "@builder.io/qwik"; 4 | import * as pwaHead from "@qwikdev/pwa/head"; 5 | 6 | /** 7 | * The RouterHead component is placed inside of the document `` element. 8 | */ 9 | export const RouterHead = component$(() => { 10 | const head = useDocumentHead(); 11 | const loc = useLocation(); 12 | 13 | return ( 14 | <> 15 | {head.title} 16 | 17 | 18 | {head.meta.map((m) => ( 19 | 20 | ))} 21 | {pwaHead.meta.map((m) => ( 22 | 23 | ))} 24 | {pwaHead.links.map((l) => ( 25 | 26 | ))} 27 | {head.links.map((l) => ( 28 | 29 | ))} 30 | {head.styles.map((s) => ( 31 |