├── LICENSE ├── README.md ├── collection.json ├── package.json ├── pwa ├── files │ └── assets │ │ ├── icons │ │ ├── icon-128x128.png │ │ ├── icon-144x144.png │ │ ├── icon-152x152.png │ │ ├── icon-192x192.png │ │ ├── icon-384x384.png │ │ ├── icon-512x512.png │ │ ├── icon-72x72.png │ │ └── icon-96x96.png │ │ └── manifest.webmanifest ├── index.d.ts ├── index.js ├── schema.d.ts ├── schema.js └── schema.json └── uniqueId /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2010-2025 Google LLC. https://angular.dev/license 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Snapshot build of @angular/pwa 3 | 4 | This repository is a snapshot of a commit on the original repository. The original code used to 5 | generate this is located at http://github.com/angular/angular-cli. 6 | 7 | We do not accept PRs or Issues opened on this repository. You should not use this over a tested and 8 | released version of this package. 9 | 10 | To test this snapshot in your own project, use 11 | 12 | ```bash 13 | npm install git+https://github.com/angular/angular-pwa-builds.git 14 | ``` 15 | 16 | ---- 17 | # `@angular/pwa` 18 | 19 | This is a [schematic](https://angular.dev/tools/cli/schematics) for adding 20 | [Progressive Web App](https://web.dev/progressive-web-apps/) support to an Angular project. Run the 21 | schematic with the [Angular CLI](https://angular.dev/tools/cli): 22 | 23 | ```shell 24 | ng add @angular/pwa --project 25 | ``` 26 | 27 | Executing the command mentioned above will perform the following actions: 28 | 29 | 1. Adds [`@angular/service-worker`](https://npmjs.com/@angular/service-worker) as a dependency to your project. 30 | 1. Enables service worker builds in the Angular CLI. 31 | 1. Imports and registers the service worker in the application module. 32 | 1. Updates the `index.html` file to inlclude a link to add the [manifest.webmanifest](https://developer.mozilla.org/en-US/docs/Web/Manifest) file. 33 | 1. Installs icon files to support the installed Progressive Web App (PWA). 34 | 1. Creates the service worker configuration file called `ngsw-config.json`, specifying caching behaviors and other settings. 35 | 36 | See [Getting started with service workers](https://angular.dev/ecosystem/service-workers/getting-started) 37 | for more information. 38 | -------------------------------------------------------------------------------- /collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "schematics": { 3 | "ng-add": { 4 | "factory": "./pwa", 5 | "description": "Update an application with PWA defaults.", 6 | "schema": "./pwa/schema.json", 7 | "private": true, 8 | "hidden": true 9 | }, 10 | "pwa": { 11 | "factory": "./pwa", 12 | "description": "Update an application with PWA defaults.", 13 | "schema": "./pwa/schema.json" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@angular/pwa", 3 | "version": "20.1.0-next.0+sha-1c19e0d", 4 | "description": "PWA schematics for Angular", 5 | "keywords": [ 6 | "Angular CLI", 7 | "Angular DevKit", 8 | "angular", 9 | "blueprints", 10 | "code generation", 11 | "devkit", 12 | "schematics", 13 | "sdk" 14 | ], 15 | "schematics": "./collection.json", 16 | "ng-add": { 17 | "save": false 18 | }, 19 | "dependencies": { 20 | "@angular-devkit/schematics": "github:angular/angular-devkit-schematics-builds#1c19e0d", 21 | "@schematics/angular": "github:angular/schematics-angular-builds#1c19e0d", 22 | "parse5-html-rewriting-stream": "7.1.0" 23 | }, 24 | "peerDependencies": { 25 | "@angular/cli": "github:angular/cli-builds#1c19e0d" 26 | }, 27 | "peerDependenciesMeta": { 28 | "@angular/cli": { 29 | "optional": true 30 | } 31 | }, 32 | "repository": { 33 | "type": "git", 34 | "url": "https://github.com/angular/angular-cli.git" 35 | }, 36 | "packageManager": "pnpm@9.15.9", 37 | "engines": { 38 | "node": "^20.19.0 || ^22.12.0 || >=24.0.0", 39 | "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", 40 | "yarn": ">= 1.13.0" 41 | }, 42 | "author": "Angular Authors", 43 | "license": "MIT", 44 | "bugs": { 45 | "url": "https://github.com/angular/angular-cli/issues" 46 | }, 47 | "homepage": "https://github.com/angular/angular-cli" 48 | } 49 | -------------------------------------------------------------------------------- /pwa/files/assets/icons/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular/angular-pwa-builds/5190021102af72d03c2f3c1f9a77c505286264e7/pwa/files/assets/icons/icon-128x128.png -------------------------------------------------------------------------------- /pwa/files/assets/icons/icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular/angular-pwa-builds/5190021102af72d03c2f3c1f9a77c505286264e7/pwa/files/assets/icons/icon-144x144.png -------------------------------------------------------------------------------- /pwa/files/assets/icons/icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular/angular-pwa-builds/5190021102af72d03c2f3c1f9a77c505286264e7/pwa/files/assets/icons/icon-152x152.png -------------------------------------------------------------------------------- /pwa/files/assets/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular/angular-pwa-builds/5190021102af72d03c2f3c1f9a77c505286264e7/pwa/files/assets/icons/icon-192x192.png -------------------------------------------------------------------------------- /pwa/files/assets/icons/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular/angular-pwa-builds/5190021102af72d03c2f3c1f9a77c505286264e7/pwa/files/assets/icons/icon-384x384.png -------------------------------------------------------------------------------- /pwa/files/assets/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular/angular-pwa-builds/5190021102af72d03c2f3c1f9a77c505286264e7/pwa/files/assets/icons/icon-512x512.png -------------------------------------------------------------------------------- /pwa/files/assets/icons/icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular/angular-pwa-builds/5190021102af72d03c2f3c1f9a77c505286264e7/pwa/files/assets/icons/icon-72x72.png -------------------------------------------------------------------------------- /pwa/files/assets/icons/icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angular/angular-pwa-builds/5190021102af72d03c2f3c1f9a77c505286264e7/pwa/files/assets/icons/icon-96x96.png -------------------------------------------------------------------------------- /pwa/files/assets/manifest.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "<%= title %>", 3 | "short_name": "<%= title %>", 4 | "display": "standalone", 5 | "scope": "./", 6 | "start_url": "./", 7 | "icons": [ 8 | { 9 | "src": "<%= iconsPath %>/icon-72x72.png", 10 | "sizes": "72x72", 11 | "type": "image/png", 12 | "purpose": "maskable any" 13 | }, 14 | { 15 | "src": "<%= iconsPath %>/icon-96x96.png", 16 | "sizes": "96x96", 17 | "type": "image/png", 18 | "purpose": "maskable any" 19 | }, 20 | { 21 | "src": "<%= iconsPath %>/icon-128x128.png", 22 | "sizes": "128x128", 23 | "type": "image/png", 24 | "purpose": "maskable any" 25 | }, 26 | { 27 | "src": "<%= iconsPath %>/icon-144x144.png", 28 | "sizes": "144x144", 29 | "type": "image/png", 30 | "purpose": "maskable any" 31 | }, 32 | { 33 | "src": "<%= iconsPath %>/icon-152x152.png", 34 | "sizes": "152x152", 35 | "type": "image/png", 36 | "purpose": "maskable any" 37 | }, 38 | { 39 | "src": "<%= iconsPath %>/icon-192x192.png", 40 | "sizes": "192x192", 41 | "type": "image/png", 42 | "purpose": "maskable any" 43 | }, 44 | { 45 | "src": "<%= iconsPath %>/icon-384x384.png", 46 | "sizes": "384x384", 47 | "type": "image/png", 48 | "purpose": "maskable any" 49 | }, 50 | { 51 | "src": "<%= iconsPath %>/icon-512x512.png", 52 | "sizes": "512x512", 53 | "type": "image/png", 54 | "purpose": "maskable any" 55 | } 56 | ] 57 | } 58 | -------------------------------------------------------------------------------- /pwa/index.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | import { Rule } from '@angular-devkit/schematics'; 9 | import { Schema as PwaOptions } from './schema'; 10 | export default function (options: PwaOptions): Rule; 11 | -------------------------------------------------------------------------------- /pwa/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /** 3 | * @license 4 | * Copyright Google LLC All Rights Reserved. 5 | * 6 | * Use of this source code is governed by an MIT-style license that can be 7 | * found in the LICENSE file at https://angular.dev/license 8 | */ 9 | Object.defineProperty(exports, "__esModule", { value: true }); 10 | exports.default = default_1; 11 | const schematics_1 = require("@angular-devkit/schematics"); 12 | const utility_1 = require("@schematics/angular/utility"); 13 | const node_path_1 = require("node:path"); 14 | const node_stream_1 = require("node:stream"); 15 | const promises_1 = require("node:stream/promises"); 16 | function updateIndexFile(path) { 17 | return async (host) => { 18 | const originalContent = host.readText(path); 19 | const { RewritingStream } = await loadEsmModule('parse5-html-rewriting-stream'); 20 | const rewriter = new RewritingStream(); 21 | let needsNoScript = true; 22 | rewriter.on('startTag', (startTag) => { 23 | if (startTag.tagName === 'noscript') { 24 | needsNoScript = false; 25 | } 26 | rewriter.emitStartTag(startTag); 27 | }); 28 | rewriter.on('endTag', (endTag) => { 29 | if (endTag.tagName === 'head') { 30 | rewriter.emitRaw(' \n'); 31 | } 32 | else if (endTag.tagName === 'body' && needsNoScript) { 33 | rewriter.emitRaw(' \n'); 34 | } 35 | rewriter.emitEndTag(endTag); 36 | }); 37 | return (0, promises_1.pipeline)(node_stream_1.Readable.from(originalContent), rewriter, async function (source) { 38 | const chunks = []; 39 | for await (const chunk of source) { 40 | chunks.push(Buffer.from(chunk)); 41 | } 42 | host.overwrite(path, Buffer.concat(chunks)); 43 | }); 44 | }; 45 | } 46 | function default_1(options) { 47 | return async (host) => { 48 | if (!options.title) { 49 | options.title = options.project; 50 | } 51 | const workspace = await (0, utility_1.readWorkspace)(host); 52 | if (!options.project) { 53 | throw new schematics_1.SchematicsException('Option "project" is required.'); 54 | } 55 | const project = workspace.projects.get(options.project); 56 | if (!project) { 57 | throw new schematics_1.SchematicsException(`Project is not defined in this workspace.`); 58 | } 59 | if (project.extensions['projectType'] !== 'application') { 60 | throw new schematics_1.SchematicsException(`PWA requires a project type of "application".`); 61 | } 62 | // Find all the relevant targets for the project 63 | if (project.targets.size === 0) { 64 | throw new schematics_1.SchematicsException(`Targets are not defined for this project.`); 65 | } 66 | const buildTargets = []; 67 | const testTargets = []; 68 | for (const target of project.targets.values()) { 69 | if (target.builder === '@angular-devkit/build-angular:browser' || 70 | target.builder === '@angular-devkit/build-angular:application' || 71 | target.builder === '@angular/build:application') { 72 | buildTargets.push(target); 73 | } 74 | else if (target.builder === '@angular-devkit/build-angular:karma' || 75 | target.builder === '@angular/build:karma') { 76 | testTargets.push(target); 77 | } 78 | } 79 | // Find all index.html files in build targets 80 | const indexFiles = new Set(); 81 | let checkForDefaultIndex = false; 82 | for (const target of buildTargets) { 83 | if (typeof target.options?.index === 'string') { 84 | indexFiles.add(target.options.index); 85 | } 86 | else if (target.options?.index === undefined) { 87 | checkForDefaultIndex = true; 88 | } 89 | if (!target.configurations) { 90 | continue; 91 | } 92 | for (const options of Object.values(target.configurations)) { 93 | if (typeof options?.index === 'string') { 94 | indexFiles.add(options.index); 95 | } 96 | else if (options?.index === undefined) { 97 | checkForDefaultIndex = true; 98 | } 99 | } 100 | } 101 | // Setup sources for the assets files to add to the project 102 | const sourcePath = project.sourceRoot ?? node_path_1.posix.join(project.root, 'src'); 103 | // Check for a default index file if a configuration path allows for a default usage 104 | if (checkForDefaultIndex) { 105 | const defaultIndexFile = node_path_1.posix.join(sourcePath, 'index.html'); 106 | if (host.exists(defaultIndexFile)) { 107 | indexFiles.add(defaultIndexFile); 108 | } 109 | } 110 | // Setup service worker schematic options 111 | const { title, ...swOptions } = options; 112 | await (0, utility_1.writeWorkspace)(host, workspace); 113 | let assetsDir = node_path_1.posix.join(sourcePath, 'assets'); 114 | let iconsPath; 115 | if (host.exists(assetsDir)) { 116 | // Add manifest to asset configuration 117 | const assetEntry = node_path_1.posix.join(project.sourceRoot ?? node_path_1.posix.join(project.root, 'src'), 'manifest.webmanifest'); 118 | for (const target of [...buildTargets, ...testTargets]) { 119 | if (target.options) { 120 | if (Array.isArray(target.options.assets)) { 121 | target.options.assets.push(assetEntry); 122 | } 123 | else { 124 | target.options.assets = [assetEntry]; 125 | } 126 | } 127 | else { 128 | target.options = { assets: [assetEntry] }; 129 | } 130 | } 131 | iconsPath = 'assets'; 132 | } 133 | else { 134 | assetsDir = node_path_1.posix.join(project.root, 'public'); 135 | iconsPath = 'icons'; 136 | } 137 | return (0, schematics_1.chain)([ 138 | (0, schematics_1.externalSchematic)('@schematics/angular', 'service-worker', swOptions), 139 | (0, schematics_1.mergeWith)((0, schematics_1.apply)((0, schematics_1.url)('./files/assets'), [(0, schematics_1.template)({ ...options, iconsPath }), (0, schematics_1.move)(assetsDir)])), 140 | ...[...indexFiles].map((path) => updateIndexFile(path)), 141 | ]); 142 | }; 143 | } 144 | /** 145 | * This uses a dynamic import to load a module which may be ESM. 146 | * CommonJS code can load ESM code via a dynamic import. Unfortunately, TypeScript 147 | * will currently, unconditionally downlevel dynamic import into a require call. 148 | * require calls cannot load ESM code and will result in a runtime error. To workaround 149 | * this, a Function constructor is used to prevent TypeScript from changing the dynamic import. 150 | * Once TypeScript provides support for keeping the dynamic import this workaround can 151 | * be dropped. 152 | * 153 | * @param modulePath The path of the module to load. 154 | * @returns A Promise that resolves to the dynamically imported module. 155 | */ 156 | function loadEsmModule(modulePath) { 157 | return new Function('modulePath', `return import(modulePath);`)(modulePath); 158 | } 159 | -------------------------------------------------------------------------------- /pwa/schema.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Transforms your Angular application into a Progressive Web App (PWA). PWAs provide a 3 | * native app-like experience, allowing users to install your app on their devices and 4 | * access it offline. This schematic configures your project for PWA functionality, adding a 5 | * service worker, a web app manifest, and other necessary features. 6 | */ 7 | export type Schema = { 8 | /** 9 | * The name of the project to transform into a PWA. If not specified, the CLI will determine 10 | * the project from the current directory. 11 | */ 12 | project?: string; 13 | /** 14 | * The build target to apply the service worker to. This is typically `build`, indicating 15 | * that the service worker should be generated during the standard build process. 16 | */ 17 | target?: string; 18 | /** 19 | * The title of the application. This will be used in the web app manifest, which is a JSON 20 | * file that provides metadata about your PWA (e.g., name, icons, display options). 21 | */ 22 | title?: string; 23 | [property: string]: any; 24 | }; 25 | -------------------------------------------------------------------------------- /pwa/schema.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | // THIS FILE IS AUTOMATICALLY GENERATED. TO UPDATE THIS FILE YOU NEED TO CHANGE THE 3 | // CORRESPONDING JSON SCHEMA FILE, THEN RUN devkit-admin build (or bazel build ...). 4 | Object.defineProperty(exports, "__esModule", { value: true }); 5 | -------------------------------------------------------------------------------- /pwa/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema", 3 | "$id": "SchematicsAngularPWA", 4 | "title": "Angular PWA Options Schema", 5 | "type": "object", 6 | "description": "Transforms your Angular application into a Progressive Web App (PWA). PWAs provide a native app-like experience, allowing users to install your app on their devices and access it offline. This schematic configures your project for PWA functionality, adding a service worker, a web app manifest, and other necessary features.", 7 | "properties": { 8 | "project": { 9 | "type": "string", 10 | "description": "The name of the project to transform into a PWA. If not specified, the CLI will determine the project from the current directory.", 11 | "$default": { 12 | "$source": "projectName" 13 | } 14 | }, 15 | "target": { 16 | "type": "string", 17 | "description": "The build target to apply the service worker to. This is typically `build`, indicating that the service worker should be generated during the standard build process.", 18 | "default": "build" 19 | }, 20 | "title": { 21 | "type": "string", 22 | "description": "The title of the application. This will be used in the web app manifest, which is a JSON file that provides metadata about your PWA (e.g., name, icons, display options)." 23 | } 24 | }, 25 | "required": [] 26 | } 27 | -------------------------------------------------------------------------------- /uniqueId: -------------------------------------------------------------------------------- 1 | Fri Jun 06 2025 16:46:50 GMT+0000 (Coordinated Universal Time) --------------------------------------------------------------------------------