├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── snowpack.config.json
├── src
├── _redirects
├── es-module-shims.js
├── icons
│ └── sample.png
├── index.html
├── index.js
├── manifest.webmanifest
└── routes
│ ├── about
│ └── index.js
│ └── home
│ └── index.js
└── workbox-config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | src/web_modules/
3 | src/sw.js
4 | src/workbox-*.js
5 | src/workbox-*.js.map
6 | src/sw.js.map
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # es-react-pwa
2 |
3 | minimalist and modern react boilerplate designed for minimal configuration.
4 |
5 | [demo](https://es-react-pwa.netlify.com/)
6 |
7 | ## usage
8 |
9 | ### Install and build
10 |
11 | ```js
12 | npm install && npm run build && npm run pwa
13 | ```
14 |
15 | ### Serve locally
16 |
17 | ```js
18 | npm run serve
19 | ```
20 |
21 | ## contents:
22 |
23 | * [snowpack](https://snowpack.dev)
24 | * [styled-components](https://styled-components.com)
25 | * [es-module-shims](https://github.com/guybedford/es-module-shims)
26 | * [kv-storage](https://github.com/WICG/kv-storage)
27 | * [workbox](https://developers.google.com/web/tools/workbox)
28 | * [htm](https://github.com/developit/htm)
29 |
30 | ## concepts:
31 |
32 | ### import maps
33 |
34 | included is es module shims, which includes polyfill support for `importmaps`. these allow direct global imports. this is key in not requiring babel.
35 |
36 | ### web modules (snowpack)
37 |
38 | snowpack allows you to treeshake es module dependencies in a convinient way. there is a production command that will do the treeshaking, and a post npm install step which will localize the es modules.
39 |
40 | ```sh
41 | npm run prepare
42 | ```
43 |
44 | ```sh
45 | npm run optimize
46 | ```
47 |
48 | ### progressive web app (workbox)
49 |
50 | workbox is configured to be run during build to generate the caching configuration.
51 |
52 | intialization:
53 |
54 | ```sh
55 | npm run pwa:init
56 | ```
57 |
58 | to generate caching with each build
59 |
60 | ```sh
61 | npm run pwa
62 | ```
63 |
64 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "es-react-pwa",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "clean": "rm -rf node_modules && rm -rf src/web_modules && rm package-lock.json",
8 | "serve": "servor src",
9 | "prepare": "snowpack --dest \"src/web_modules/\"",
10 | "test": "echo \"Error: no test specified\" && exit 1",
11 | "optimize": "snowpack build --dest \"src/web_modules/\"",
12 | "pwa": "workbox generateSW workbox-config.js",
13 | "pwa:init": "workbox wizard"
14 | },
15 | "precommit": [
16 | "optimize",
17 | "pwa"
18 | ],
19 | "author": "Matt Hoffner",
20 | "license": "ISC",
21 | "devDependencies": {
22 | "servor": "^4.0.2",
23 | "snowpack": "^2.0.0",
24 | "workbox-cli": "latest"
25 | },
26 | "dependencies": {
27 | "htm": "^2.2.1",
28 | "react": "^16.0.0",
29 | "react-dom": "^16.0.0",
30 | "styled-components": "^4.4.1"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/snowpack.config.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "install": [
4 | "htm",
5 | "react",
6 | "react-dom",
7 | "styled-components"
8 | ],
9 | "scripts": {
10 | "mount:public": "mount public --to /",
11 | "mount:web_modules": "mount web_modules --to /src"
12 | }
13 | }
--------------------------------------------------------------------------------
/src/_redirects:
--------------------------------------------------------------------------------
1 | /* /index.html 200
--------------------------------------------------------------------------------
/src/es-module-shims.js:
--------------------------------------------------------------------------------
1 | /* ES Module Shims 0.4.5 */
2 | (function () {
3 | 'use strict';
4 |
5 | const resolvedPromise = Promise.resolve();
6 |
7 | let baseUrl;
8 |
9 | function createBlob (source) {
10 | return URL.createObjectURL(new Blob([source], { type: 'application/javascript' }));
11 | }
12 |
13 | const hasDocument = typeof document !== 'undefined';
14 |
15 | // support browsers without dynamic import support (eg Firefox 6x)
16 | let dynamicImport;
17 | try {
18 | dynamicImport = (0, eval)('u=>import(u)');
19 | }
20 | catch (e) {
21 | if (hasDocument) {
22 | self.addEventListener('error', e => importShim.e = e.error);
23 | dynamicImport = blobUrl => {
24 | const topLevelBlobUrl = createBlob(
25 | `import*as m from'${blobUrl}';self.importShim.l=m;self.importShim.e=null`
26 | );
27 | const s = document.createElement('script');
28 | s.type = 'module';
29 | s.src = topLevelBlobUrl;
30 | document.head.appendChild(s);
31 | return new Promise((resolve, reject) => {
32 | s.addEventListener('load', () => {
33 | document.head.removeChild(s);
34 | importShim.e ? reject(importShim.e) : resolve(importShim.l, baseUrl);
35 | });
36 | });
37 | };
38 | }
39 | }
40 |
41 | if (hasDocument) {
42 | const baseEl = document.querySelector('base[href]');
43 | if (baseEl)
44 | baseUrl = baseEl.href;
45 | }
46 |
47 | if (!baseUrl && typeof location !== 'undefined') {
48 | baseUrl = location.href.split('#')[0].split('?')[0];
49 | const lastSepIndex = baseUrl.lastIndexOf('/');
50 | if (lastSepIndex !== -1)
51 | baseUrl = baseUrl.slice(0, lastSepIndex + 1);
52 | }
53 |
54 | let esModuleShimsSrc;
55 | if (hasDocument) {
56 | esModuleShimsSrc = document.currentScript && document.currentScript.src;
57 | }
58 |
59 | const backslashRegEx = /\\/g;
60 | function resolveIfNotPlainOrUrl (relUrl, parentUrl) {
61 | // strip off any trailing query params or hashes
62 | parentUrl = parentUrl && parentUrl.split('#')[0].split('?')[0];
63 | if (relUrl.indexOf('\\') !== -1)
64 | relUrl = relUrl.replace(backslashRegEx, '/');
65 | // protocol-relative
66 | if (relUrl[0] === '/' && relUrl[1] === '/') {
67 | return parentUrl.slice(0, parentUrl.indexOf(':') + 1) + relUrl;
68 | }
69 | // relative-url
70 | else if (relUrl[0] === '.' && (relUrl[1] === '/' || relUrl[1] === '.' && (relUrl[2] === '/' || relUrl.length === 2 && (relUrl += '/')) ||
71 | relUrl.length === 1 && (relUrl += '/')) ||
72 | relUrl[0] === '/') {
73 | const parentProtocol = parentUrl.slice(0, parentUrl.indexOf(':') + 1);
74 | // Disabled, but these cases will give inconsistent results for deep backtracking
75 | //if (parentUrl[parentProtocol.length] !== '/')
76 | // throw new Error('Cannot resolve');
77 | // read pathname from parent URL
78 | // pathname taken to be part after leading "/"
79 | let pathname;
80 | if (parentUrl[parentProtocol.length + 1] === '/') {
81 | // resolving to a :// so we need to read out the auth and host
82 | if (parentProtocol !== 'file:') {
83 | pathname = parentUrl.slice(parentProtocol.length + 2);
84 | pathname = pathname.slice(pathname.indexOf('/') + 1);
85 | }
86 | else {
87 | pathname = parentUrl.slice(8);
88 | }
89 | }
90 | else {
91 | // resolving to :/ so pathname is the /... part
92 | pathname = parentUrl.slice(parentProtocol.length + (parentUrl[parentProtocol.length] === '/'));
93 | }
94 |
95 | if (relUrl[0] === '/')
96 | return parentUrl.slice(0, parentUrl.length - pathname.length - 1) + relUrl;
97 |
98 | // join together and split for removal of .. and . segments
99 | // looping the string instead of anything fancy for perf reasons
100 | // '../../../../../z' resolved to 'x/y' is just 'z'
101 | const segmented = pathname.slice(0, pathname.lastIndexOf('/') + 1) + relUrl;
102 |
103 | const output = [];
104 | let segmentIndex = -1;
105 | for (let i = 0; i < segmented.length; i++) {
106 | // busy reading a segment - only terminate on '/'
107 | if (segmentIndex !== -1) {
108 | if (segmented[i] === '/') {
109 | output.push(segmented.slice(segmentIndex, i + 1));
110 | segmentIndex = -1;
111 | }
112 | }
113 |
114 | // new segment - check if it is relative
115 | else if (segmented[i] === '.') {
116 | // ../ segment
117 | if (segmented[i + 1] === '.' && (segmented[i + 2] === '/' || i + 2 === segmented.length)) {
118 | output.pop();
119 | i += 2;
120 | }
121 | // ./ segment
122 | else if (segmented[i + 1] === '/' || i + 1 === segmented.length) {
123 | i += 1;
124 | }
125 | else {
126 | // the start of a new segment as below
127 | segmentIndex = i;
128 | }
129 | }
130 | // it is the start of a new segment
131 | else {
132 | segmentIndex = i;
133 | }
134 | }
135 | // finish reading out the last segment
136 | if (segmentIndex !== -1)
137 | output.push(segmented.slice(segmentIndex));
138 | return parentUrl.slice(0, parentUrl.length - pathname.length) + output.join('');
139 | }
140 | }
141 |
142 | /*
143 | * Import maps implementation
144 | *
145 | * To make lookups fast we pre-resolve the entire import map
146 | * and then match based on backtracked hash lookups
147 | *
148 | */
149 | const emptyImportMap = { imports: {}, scopes: {} };
150 |
151 | function resolveUrl (relUrl, parentUrl) {
152 | return resolveIfNotPlainOrUrl(relUrl, parentUrl) || (relUrl.indexOf(':') !== -1 ? relUrl : resolveIfNotPlainOrUrl('./' + relUrl, parentUrl));
153 | }
154 |
155 | async function hasStdModule (name) {
156 | try {
157 | await dynamicImport(name);
158 | return true;
159 | }
160 | catch (e) {
161 | return false;
162 | }
163 | }
164 |
165 | async function resolveAndComposePackages (packages, outPackages, baseUrl, parentMap, parentUrl) {
166 | outer: for (let p in packages) {
167 | const resolvedLhs = resolveIfNotPlainOrUrl(p, baseUrl) || p;
168 | let target = packages[p];
169 | if (typeof target === 'string')
170 | target = [target];
171 | else if (!Array.isArray(target))
172 | continue;
173 |
174 | for (const rhs of target) {
175 | if (typeof rhs !== 'string')
176 | continue;
177 | const mapped = resolveImportMap(parentMap, resolveIfNotPlainOrUrl(rhs, baseUrl) || rhs, parentUrl);
178 | if (mapped && (!mapped.startsWith('std:') || await hasStdModule(mapped))) {
179 | outPackages[resolvedLhs] = mapped;
180 | continue outer;
181 | }
182 | }
183 | targetWarning(p, packages[p], 'bare specifier did not resolve');
184 | }
185 | }
186 |
187 | async function resolveAndComposeImportMap (json, baseUrl, parentMap) {
188 | const outMap = { imports: Object.assign({}, parentMap.imports), scopes: Object.assign({}, parentMap.scopes) };
189 |
190 | if (json.imports)
191 | await resolveAndComposePackages(json.imports, outMap.imports, baseUrl, parentMap, null);
192 |
193 | if (json.scopes)
194 | for (let s in json.scopes) {
195 | const resolvedScope = resolveUrl(s, baseUrl);
196 | await resolveAndComposePackages(json.scopes[s], outMap.scopes[resolvedScope] || (outMap.scopes[resolvedScope] = {}), baseUrl, parentMap, resolvedScope);
197 | }
198 |
199 | return outMap;
200 | }
201 |
202 | function getMatch (path, matchObj) {
203 | if (matchObj[path])
204 | return path;
205 | let sepIndex = path.length;
206 | do {
207 | const segment = path.slice(0, sepIndex + 1);
208 | if (segment in matchObj)
209 | return segment;
210 | } while ((sepIndex = path.lastIndexOf('/', sepIndex - 1)) !== -1)
211 | }
212 |
213 | function applyPackages (id, packages) {
214 | const pkgName = getMatch(id, packages);
215 | if (pkgName) {
216 | const pkg = packages[pkgName];
217 | if (pkg === null) return;
218 | if (id.length > pkgName.length && pkg[pkg.length - 1] !== '/')
219 | targetWarning(pkgName, pkg, "should have a trailing '/'");
220 | else
221 | return pkg + id.slice(pkgName.length);
222 | }
223 | }
224 |
225 | function targetWarning (match, target, msg) {
226 | console.warn("Package target " + msg + ", resolving target '" + target + "' for " + match);
227 | }
228 |
229 | function resolveImportMap (importMap, resolvedOrPlain, parentUrl) {
230 | let scopeUrl = parentUrl && getMatch(parentUrl, importMap.scopes);
231 | while (scopeUrl) {
232 | const packageResolution = applyPackages(resolvedOrPlain, importMap.scopes[scopeUrl]);
233 | if (packageResolution)
234 | return packageResolution;
235 | scopeUrl = getMatch(scopeUrl.slice(0, scopeUrl.lastIndexOf('/')), importMap.scopes);
236 | }
237 | return applyPackages(resolvedOrPlain, importMap.imports) || resolvedOrPlain.indexOf(':') !== -1 && resolvedOrPlain;
238 | }
239 |
240 | /* es-module-lexer 0.3.13 */
241 | function parse(Q,B="@"){if(!A)return init.then(()=>parse(Q));const C=(A.__heap_base.value||A.__heap_base)+4*Q.length+-A.memory.buffer.byteLength;if(C>0&&A.memory.grow(Math.ceil(C/65536)),function(A,Q){const B=A.length;let C=0;for(;C"function"==typeof atob?Uint8Array.from(atob(A),A=>A.charCodeAt(0)):Buffer.from(A,"base64"))("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")).then(WebAssembly.instantiate).then(({exports:Q})=>{A=Q;});
242 |
243 | class WorkerShim {
244 | constructor(aURL, options = {}) {
245 | if (options.type !== 'module')
246 | return new Worker(aURL, options);
247 |
248 | if (!esModuleShimsSrc)
249 | throw new Error('es-module-shims.js must be loaded with a script tag for WorkerShim support.');
250 |
251 | options.importMap = options.importMap || emptyImportMap;
252 |
253 | const workerScriptUrl = createBlob(
254 | `importScripts('${esModuleShimsSrc}');importShim.map=${JSON.stringify(options.importMap)};importShim('${new URL(aURL, baseUrl).href}').catch(e=>setTimeout(()=>{throw e}))`
255 | );
256 |
257 | return new Worker(workerScriptUrl, Object.assign({}, options, { type: undefined }));
258 | }
259 | }
260 |
261 | let id = 0;
262 | const registry = {};
263 |
264 | async function loadAll (load, seen) {
265 | if (load.b || seen[load.u])
266 | return;
267 | seen[load.u] = 1;
268 | await load.L;
269 | return Promise.all(load.d.map(dep => loadAll(dep, seen)));
270 | }
271 |
272 | async function topLevelLoad (url, source) {
273 | await init;
274 | const load = getOrCreateLoad(url, source);
275 | const seen = {};
276 | await loadAll(load, seen);
277 | lastLoad = undefined;
278 | resolveDeps(load, seen);
279 | const module = await dynamicImport(load.b);
280 | // if the top-level load is a shell, run its update function
281 | if (load.s)
282 | (await dynamicImport(load.s)).u$_(module);
283 | return module;
284 | }
285 |
286 | async function importShim$1 (id, parentUrl) {
287 | return topLevelLoad(await resolve(id, parentUrl || baseUrl));
288 | }
289 |
290 | self.importShim = importShim$1;
291 |
292 | const meta = {};
293 | const wasmModules = {};
294 |
295 | const edge = navigator.userAgent.match(/Edge\/\d\d\.\d+$/);
296 |
297 | Object.defineProperties(importShim$1, {
298 | map: { value: emptyImportMap, writable: true },
299 | m: { value: meta },
300 | w: { value: wasmModules },
301 | l: { value: undefined, writable: true },
302 | e: { value: undefined, writable: true }
303 | });
304 | importShim$1.fetch = url => fetch(url);
305 |
306 | let lastLoad;
307 | function resolveDeps (load, seen) {
308 | if (load.b || !seen[load.u])
309 | return;
310 | seen[load.u] = 0;
311 |
312 | for (const dep of load.d)
313 | resolveDeps(dep, seen);
314 |
315 | // "execution"
316 | const source = load.S;
317 | // edge doesnt execute sibling in order, so we fix this up by ensuring all previous executions are explicit dependencies
318 | let resolvedSource = edge && lastLoad ? `import '${lastLoad}';` : '';
319 |
320 | const [imports] = load.a;
321 |
322 | if (!imports.length) {
323 | resolvedSource += source;
324 | }
325 | else {
326 | // once all deps have loaded we can inline the dependency resolution blobs
327 | // and define this blob
328 | let lastIndex = 0, depIndex = 0;
329 | for (const { s: start, e: end, d: dynamicImportIndex } of imports) {
330 | // dependency source replacements
331 | if (dynamicImportIndex === -1) {
332 | const depLoad = load.d[depIndex++];
333 | let blobUrl = depLoad.b;
334 | if (!blobUrl) {
335 | // circular shell creation
336 | if (!(blobUrl = depLoad.s)) {
337 | blobUrl = depLoad.s = createBlob(`export function u$_(m){${
338 | depLoad.a[1].map(
339 | name => name === 'default' ? `$_default=m.default` : `${name}=m.${name}`
340 | ).join(',')
341 | }}${
342 | depLoad.a[1].map(name =>
343 | name === 'default' ? `let $_default;export{$_default as default}` : `export let ${name}`
344 | ).join(';')
345 | }\n//# sourceURL=${depLoad.r}?cycle`);
346 | }
347 | }
348 | // circular shell execution
349 | else if (depLoad.s) {
350 | resolvedSource += source.slice(lastIndex, start - 1) + '/*' + source.slice(start - 1, end + 1) + '*/' + source.slice(start - 1, start) + blobUrl + source[end] + `;import*as m$_${depIndex} from'${depLoad.b}';import{u$_ as u$_${depIndex}}from'${depLoad.s}';u$_${depIndex}(m$_${depIndex})`;
351 | lastIndex = end + 1;
352 | depLoad.s = undefined;
353 | continue;
354 | }
355 | resolvedSource += source.slice(lastIndex, start - 1) + '/*' + source.slice(start - 1, end + 1) + '*/' + source.slice(start - 1, start) + blobUrl;
356 | lastIndex = end;
357 | }
358 | // import.meta
359 | else if (dynamicImportIndex === -2) {
360 | meta[load.r] = { url: load.r };
361 | resolvedSource += source.slice(lastIndex, start) + 'importShim.m[' + JSON.stringify(load.r) + ']';
362 | lastIndex = end;
363 | }
364 | // dynamic import
365 | else {
366 | resolvedSource += source.slice(lastIndex, dynamicImportIndex + 6) + 'Shim(' + source.slice(start, end) + ', ' + JSON.stringify(load.r);
367 | lastIndex = end;
368 | }
369 | }
370 |
371 | resolvedSource += source.slice(lastIndex);
372 | }
373 |
374 | let sourceMappingResolved = '';
375 | const sourceMappingIndex = resolvedSource.lastIndexOf('//# sourceMappingURL=');
376 | if (sourceMappingIndex > -1) {
377 | const sourceMappingEnd = resolvedSource.indexOf('\n',sourceMappingIndex);
378 | const sourceMapping = resolvedSource.slice(sourceMappingIndex, sourceMappingEnd > -1 ? sourceMappingEnd : undefined);
379 | sourceMappingResolved = `\n//# sourceMappingURL=` + resolveUrl(sourceMapping.slice(21), load.r);
380 | }
381 | load.b = lastLoad = createBlob(resolvedSource + sourceMappingResolved + '\n//# sourceURL=' + load.r);
382 | load.S = undefined;
383 | }
384 |
385 | function getOrCreateLoad (url, source) {
386 | let load = registry[url];
387 | if (load)
388 | return load;
389 |
390 | load = registry[url] = {
391 | // url
392 | u: url,
393 | // response url
394 | r: undefined,
395 | // fetchPromise
396 | f: undefined,
397 | // source
398 | S: undefined,
399 | // linkPromise
400 | L: undefined,
401 | // analysis
402 | a: undefined,
403 | // deps
404 | d: undefined,
405 | // blobUrl
406 | b: undefined,
407 | // shellUrl
408 | s: undefined,
409 | };
410 |
411 | if (url.startsWith('std:'))
412 | return Object.assign(load, {
413 | r: url,
414 | f: resolvedPromise,
415 | L: resolvedPromise,
416 | b: url
417 | });
418 |
419 | load.f = (async () => {
420 | if (!source) {
421 | const res = await importShim$1.fetch(url);
422 | if (!res.ok)
423 | throw new Error(`${res.status} ${res.statusText} ${res.url}`);
424 |
425 | load.r = res.url;
426 |
427 | const contentType = res.headers.get('content-type');
428 | if (contentType.match(/^(text|application)\/(x-)?javascript(;|$)/)) {
429 | source = await res.text();
430 | }
431 | else if (contentType.match(/^application\/json(;|$)/)) {
432 | source = `export default JSON.parse(${JSON.stringify(await res.text())})`;
433 | }
434 | else if (contentType.match(/^text\/css(;|$)/)) {
435 | source = `const s=new CSSStyleSheet();s.replaceSync(${JSON.stringify(await res.text())});export default s`;
436 | }
437 | else if (contentType.match(/^application\/wasm(;|$)/)) {
438 | const module = wasmModules[url] = await WebAssembly.compile(await res.arrayBuffer());
439 | let deps = WebAssembly.Module.imports ? WebAssembly.Module.imports(module).map(impt => impt.module) : [];
440 |
441 | const aDeps = [];
442 | load.a = [aDeps, WebAssembly.Module.exports(module).map(expt => expt.name)];
443 |
444 | const depStrs = deps.map(dep => JSON.stringify(dep));
445 |
446 | let curIndex = 0;
447 | load.S = depStrs.map((depStr, idx) => {
448 | const index = idx.toString();
449 | const strStart = curIndex + 17 + index.length;
450 | const strEnd = strStart + depStr.length - 2;
451 | aDeps.push({
452 | s: strStart,
453 | e: strEnd,
454 | d: -1
455 | });
456 | curIndex += strEnd + 3;
457 | return `import*as m${index} from${depStr};`
458 | }).join('') +
459 | `const module=importShim.w[${JSON.stringify(url)}],exports=new WebAssembly.Instance(module,{` +
460 | depStrs.map((depStr, idx) => `${depStr}:m${idx},`).join('') +
461 | `}).exports;` +
462 | load.a[1].map(name => name === 'default' ? `export default exports.${name}` : `export const ${name}=exports.${name}`).join(';');
463 | return deps;
464 | }
465 | else {
466 | throw new Error(`Unknown Content-Type "${contentType}"`);
467 | }
468 | }
469 | try {
470 | load.a = parse(source, load.u);
471 | }
472 | catch (e) {
473 | console.warn(e);
474 | load.a = [[], []];
475 | }
476 | load.S = source;
477 | return load.a[0].filter(d => d.d === -1).map(d => source.slice(d.s, d.e));
478 | })();
479 |
480 | load.L = load.f.then(async deps => {
481 | load.d = await Promise.all(deps.map(async depId => {
482 | const depLoad = getOrCreateLoad(await resolve(depId, load.r || load.u));
483 | await depLoad.f;
484 | return depLoad;
485 | }));
486 | });
487 |
488 | return load;
489 | }
490 |
491 | let importMapPromise;
492 |
493 | if (hasDocument) {
494 | // preload import maps
495 | for (const script of document.querySelectorAll('script[type="importmap-shim"][src]'))
496 | script._f = fetch(script.src);
497 | // load any module scripts
498 | for (const script of document.querySelectorAll('script[type="module-shim"]'))
499 | topLevelLoad(script.src || `${baseUrl}?${id++}`, script.src ? null : script.innerHTML);
500 | }
501 |
502 | async function resolve (id, parentUrl) {
503 | if (!importMapPromise) {
504 | importMapPromise = resolvedPromise;
505 | if (hasDocument)
506 | for (const script of document.querySelectorAll('script[type="importmap-shim"]')) {
507 | importMapPromise = importMapPromise.then(async () => {
508 | importShim$1.map = await resolveAndComposeImportMap(script.src ? await (await (script._f || fetch(script.src))).json() : JSON.parse(script.innerHTML), script.src || baseUrl, importShim$1.map);
509 | });
510 | }
511 | }
512 | await importMapPromise;
513 | return resolveImportMap(importShim$1.map, resolveIfNotPlainOrUrl(id, parentUrl) || id, parentUrl) || throwUnresolved(id, parentUrl);
514 | }
515 |
516 | function throwUnresolved (id, parentUrl) {
517 | throw Error("Unable to resolve specifier '" + id + (parentUrl ? "' from " + parentUrl : "'"));
518 | }
519 |
520 | self.WorkerShim = WorkerShim;
521 |
522 | }());
--------------------------------------------------------------------------------
/src/icons/sample.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/matthoffner/es-react-pwa/b3145471a0564cbbb9151bde42dc8562ae175857/src/icons/sample.png
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
21 | This is an example of a modern React template that doesn't require any transpiling or bundling thanks to ES modules, importmaps along with workbox for offline PWA capabilities. 22 |
23 | /> 24 | `; 25 | } 26 | -------------------------------------------------------------------------------- /src/routes/home/index.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | const Title = styled.h1` 5 | font-size: 1.5em; 6 | text-align: center; 7 | color: palevioletred; 8 | `; 9 | const Wrapper = styled.section` 10 | padding: 4em; 11 | background: lightblue; 12 | box-shadow: 10px; 13 | `; 14 | 15 | export default () => { 16 | return html` 17 | <${React.Fragment}> 18 | <${Wrapper}> 19 | <${Title}>es-react-pwa/> 20 | /> 21 | About 22 | /> 23 | `; 24 | } 25 | -------------------------------------------------------------------------------- /workbox-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "globDirectory": "./src", 3 | "globPatterns": [ 4 | "**/*.{html,js}" 5 | ], 6 | "swDest": "src/sw.js", 7 | "clientsClaim": true, 8 | "skipWaiting": true 9 | }; 10 | --------------------------------------------------------------------------------