mirror of
https://github.com/hanzoai/workbox.git
synced 2026-09-24 17:04:35 +00:00
The contents of v3, reconciled with master (#1357)
* The contents of v3, reconciled with master. * Remove a few stray files. * Another extra file. * Two more deletions. * Setting up for stable release * v3.0.0 * Updating CDN and versions * Doc repair for wf (#1358) * Changing doc structure to work with WF * Changing doc structure to work with WF
This commit is contained in:
@@ -1,2 +0,0 @@
|
||||
packages/*/node_modules/
|
||||
packages/*/build/
|
||||
+82
-14
@@ -11,35 +11,103 @@ module.exports = {
|
||||
sourceType: 'module',
|
||||
},
|
||||
globals: {
|
||||
BroadcastChannel: false,
|
||||
WorkboxSW: false,
|
||||
workbox: false,
|
||||
goog: false,
|
||||
WorkboxSW: false,
|
||||
SyncEvent: false,
|
||||
BroadcastChannel: false,
|
||||
},
|
||||
rules: {
|
||||
'linebreak-style': 0,
|
||||
"jsdoc/check-types": 2,
|
||||
"jsdoc/newline-after-description": 2,
|
||||
},
|
||||
plugins: [
|
||||
'jsdoc',
|
||||
],
|
||||
parser: 'babel-eslint',
|
||||
overrides: [{
|
||||
files: ['**/test/**/*.js', '**/test.js'],
|
||||
files: ['test/**/*.{js,mjs}'],
|
||||
env: {
|
||||
mocha: true,
|
||||
},
|
||||
globals: {
|
||||
expect: true,
|
||||
sinon: true,
|
||||
goog: true,
|
||||
},
|
||||
rules: {
|
||||
'no-console': 0,
|
||||
'valid-jsdoc': 0,
|
||||
'require-jsdoc': 0,
|
||||
'max-len': 0,
|
||||
'require-jsdoc': 0,
|
||||
'valid-jsdoc': 0,
|
||||
'no-invalid-this': 0,
|
||||
},
|
||||
}, {
|
||||
files: ['gulp-tasks/*.js'],
|
||||
files: [
|
||||
'packages/workbox-core/_private/logger.mjs',
|
||||
'packages/workbox-sw/_default.mjs',
|
||||
'infra/testing/cli-test-helper.js',
|
||||
'infra/utils/log-helper.js',
|
||||
'packages/workbox-cli/src/lib/logger.js',
|
||||
],
|
||||
rules: {
|
||||
'no-console': 0,
|
||||
},
|
||||
}, {
|
||||
files: [
|
||||
'infra/**/*.js',
|
||||
],
|
||||
rules: {
|
||||
'max-len': 0,
|
||||
},
|
||||
}, {
|
||||
files: [
|
||||
'gulp-tasks/**/*.js',
|
||||
'infra/**/*.js'
|
||||
],
|
||||
rules: {
|
||||
'valid-jsdoc': 0,
|
||||
'require-jsdoc': 0,
|
||||
},
|
||||
}, {
|
||||
files: [
|
||||
'infra/testing/**/*',
|
||||
],
|
||||
env: {
|
||||
'mocha': true
|
||||
}
|
||||
}, {
|
||||
files: [
|
||||
'test/workbox-build/static/**/*.js',
|
||||
],
|
||||
rules: {
|
||||
'no-unused-vars': 0,
|
||||
'no-undef': 0,
|
||||
},
|
||||
}, {
|
||||
files: [
|
||||
'packages/workbox-build/src/templates/**/*.js',
|
||||
],
|
||||
rules: {
|
||||
'max-len': 0,
|
||||
},
|
||||
}
|
||||
, {
|
||||
files: [
|
||||
'packages/workbox-sw/**/*',
|
||||
],
|
||||
globals: {
|
||||
'workbox': false,
|
||||
},
|
||||
}, {
|
||||
files: [
|
||||
'infra/testing/env-it.js',
|
||||
],
|
||||
rules: {
|
||||
'no-invalid-this': 0,
|
||||
},
|
||||
}, {
|
||||
files: [
|
||||
'packages/**/*.{mjs,js}',
|
||||
],
|
||||
plugins: [
|
||||
'header',
|
||||
],
|
||||
rules: {
|
||||
'header/header': [2, 'block', {pattern: 'Copyright \\d{4} Google Inc.'}]
|
||||
}
|
||||
}],
|
||||
};
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
*.js text eol=lf
|
||||
*.mjs text eol=lf
|
||||
@@ -7,4 +7,11 @@ packages/**/LICENSE
|
||||
temp
|
||||
tmp-*
|
||||
!packages/workbox-cli/test/static/example-project-1/node_modules
|
||||
coverage/
|
||||
generated-release-files/
|
||||
workbox-*.json
|
||||
.esm-cache/
|
||||
.nyc_output/
|
||||
docs
|
||||
local-builds/
|
||||
firebase-debug.log
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
ruby-2.2.0
|
||||
+8
-20
@@ -10,30 +10,18 @@ cache:
|
||||
node_js:
|
||||
- 'stable'
|
||||
|
||||
env:
|
||||
- PROJECT="workbox-background-sync"
|
||||
- PROJECT="workbox-broadcast-cache-update"
|
||||
- PROJECT="workbox-build"
|
||||
- PROJECT="workbox-webpack-plugin"
|
||||
- PROJECT="workbox-cache-expiration"
|
||||
- PROJECT="workbox-cli"
|
||||
- PROJECT="workbox-sw"
|
||||
- PROJECT="workbox-google-analytics"
|
||||
- PROJECT="workbox-precaching"
|
||||
- PROJECT="workbox-routing"
|
||||
- PROJECT="workbox-runtime-caching"
|
||||
|
||||
install:
|
||||
- npm install -g gulp-cli
|
||||
- npm install
|
||||
- gulp lerna-bootstrap-scoped --project="${PROJECT}"
|
||||
|
||||
# Read more here: https://docs.travis-ci.com/user/gui-and-headless-browsers/#Using-xvfb-to-Run-Tests-That-Require-a-GUI
|
||||
before_script:
|
||||
- "export DISPLAY=:99.0"
|
||||
- "sh -e /etc/init.d/xvfb start || echo \"Unable to start virtual display.\""
|
||||
- sleep 3 # give xvfb some time to start
|
||||
|
||||
install:
|
||||
- npm install
|
||||
|
||||
script:
|
||||
# Building is handled in gulp test.
|
||||
- gulp test --project "${PROJECT}"
|
||||
- gulp test
|
||||
|
||||
after_success:
|
||||
- npm run coveralls
|
||||
- npm run bot
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
source "http://rubygems.org"
|
||||
|
||||
gem 'jekyll', '>=3.1.6'
|
||||
gem 'kramdown'
|
||||
gem 'jekyll-github-metadata'
|
||||
@@ -73,7 +73,7 @@ Please read the [guide to contributing](CONTRIBUTING.md) prior to filing any pul
|
||||
|
||||
## License
|
||||
|
||||
Copyright 2017 Google, Inc.
|
||||
Copyright 2018 Google, Inc.
|
||||
|
||||
Licensed under the [Apache License, Version 2.0](LICENSE) (the "License");
|
||||
you may not use this file except in compliance with the License. You may
|
||||
|
||||
+4
-11
@@ -1,21 +1,14 @@
|
||||
only_commits:
|
||||
files:
|
||||
- packages/workbox-cli/
|
||||
- packages/workbox-build/
|
||||
- test/
|
||||
- appveyor.yml
|
||||
- .travis.yml
|
||||
- package.json
|
||||
cache:
|
||||
- node_modules
|
||||
|
||||
install:
|
||||
- ps: Install-Product node Stable
|
||||
- npm install -g gulp
|
||||
- npm install
|
||||
- gulp lerna-bootstrap-scoped --project="workbox-cli"
|
||||
- npm install gulpjs/gulp-cli -g
|
||||
|
||||
test_script:
|
||||
- node --version
|
||||
- npm --version
|
||||
- gulp test --project "workbox-cli"
|
||||
- gulp test
|
||||
|
||||
build: off
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"origin": "https://storage.googleapis.com",
|
||||
"bucketName": "workbox-cdn",
|
||||
"releasesDir": "releases"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"latestUrl":"https://storage.googleapis.com/workbox-cdn/releases/3.0.0"}
|
||||
@@ -0,0 +1,104 @@
|
||||
const functions = require('firebase-functions');
|
||||
const express = require('express');
|
||||
const exphbs = require('express-handlebars');
|
||||
const path = require('path');
|
||||
const fs = require('fs-extra');
|
||||
|
||||
const cdnDetails = require('./cdn-details.json');
|
||||
|
||||
const workboxModules = [
|
||||
'workbox-background-sync',
|
||||
'workbox-broadcast-cache-update',
|
||||
'workbox-cache-expiration',
|
||||
'workbox-cacheable-response',
|
||||
'workbox-core',
|
||||
'workbox-google-analytics',
|
||||
'workbox-precaching',
|
||||
'workbox-range-requests',
|
||||
'workbox-routing',
|
||||
'workbox-strategies',
|
||||
'workbox-sw',
|
||||
];
|
||||
|
||||
const app = express();
|
||||
app.engine('hbs', exphbs({defaultLayout: 'main', extname: '.hbs'}));
|
||||
app.set('view engine', 'hbs');
|
||||
|
||||
app.get('/', function(req, res) {
|
||||
let moduleData = workboxModules.map((moduleName) => {
|
||||
const docPath = path.join(
|
||||
__dirname, 'views', 'demo', `${moduleName}.hbs`
|
||||
);
|
||||
|
||||
let exists = false;
|
||||
try {
|
||||
fs.accessSync(docPath);
|
||||
exists = true;
|
||||
} catch (err) {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
return {
|
||||
name: moduleName,
|
||||
hasDemo: exists,
|
||||
};
|
||||
});
|
||||
|
||||
res.render('home', {
|
||||
title: 'Workbox V3',
|
||||
modules: moduleData,
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/api/is-response-cacheable',
|
||||
function(req, res) {
|
||||
if (req.headers['x-is-cacheable']) {
|
||||
const value = req.headers['x-is-cacheable'];
|
||||
res.set('X-Is-Cacheable', value);
|
||||
res.send(`This response has 'X-Is-Cacheable' header set to '${value}'`);
|
||||
} else {
|
||||
res.send(`This response has no 'X-Is-Cacheable' header`);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
app.get('/demo/:moduleName', function(req, res) {
|
||||
res.render(`demo/${req.params.moduleName}`, {
|
||||
title: `${req.params.moduleName} Demo`,
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/demo/:moduleName/:swfile', function(req, res, next) {
|
||||
const swTemplate = path.basename(
|
||||
req.params.swfile,
|
||||
path.extname(req.params.swfile)
|
||||
);
|
||||
|
||||
let cdnUrl = cdnDetails.latestUrl;
|
||||
let extraConfig = '';
|
||||
if (process.env.WORKBOX_DEMO_ENV === 'local') {
|
||||
cdnUrl = `/local-builds`;
|
||||
extraConfig = `,
|
||||
modulePathPrefix: '${cdnUrl}'`;
|
||||
}
|
||||
|
||||
const swImport = `importScripts('${cdnUrl}/workbox-sw.js');
|
||||
|
||||
workbox.setConfig({
|
||||
debug: true${extraConfig}
|
||||
});
|
||||
`;
|
||||
|
||||
res.header('Content-Type', 'application/javascript');
|
||||
res.header('Cache-Control', 'no-cache');
|
||||
res.render(`demo/${req.params.moduleName}/${swTemplate}`, {
|
||||
title: `${req.params.moduleName} Demo`,
|
||||
CDN_URL: cdnUrl,
|
||||
WORKBOX_SW_IMPORT: swImport,
|
||||
layout: false,
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
app: functions.https.onRequest(app),
|
||||
};
|
||||
Generated
+3181
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "functions",
|
||||
"description": "Cloud Functions for Firebase",
|
||||
"dependencies": {
|
||||
"express": "^4.16.2",
|
||||
"express-handlebars": "^3.0.0",
|
||||
"firebase-admin": "~5.4.0",
|
||||
"firebase-functions": "^0.7.0",
|
||||
"fs-extra": "^4.0.2"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-8 mx-auto">
|
||||
<ol>
|
||||
<li><a href='https://developers.google.com/web/tools/chrome-devtools/#open'>Open DevTools.</a></li>
|
||||
<li>Switch to the Network panel.</li>
|
||||
<li>Click `Make Fetch request` button below.</li>
|
||||
<li>Observe that request is successfully completed with status 200.</li>
|
||||
<li>Disconnect from actual network(Devtools offline does not work for bgsync).</li>
|
||||
<li>Again, click `Make Fetch request` button below.</li>
|
||||
<li>Observe that request is abortedd due to no network.</li>
|
||||
<li>Connect to your network and observe the Network panel in devtools.</li>
|
||||
<li>Once the network is connected, you will see that the request is retried automatically upon the `sync event.</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="col-lg-8 mx-auto">
|
||||
<button class="fetch btn btn-primary">Make fetch request</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
document.querySelector('.fetch').addEventListener('click', () => {
|
||||
fetch('./example.txt');
|
||||
});
|
||||
|
||||
navigator.serviceWorker.register('/demo/workbox-background-sync/sw.js');
|
||||
</script>
|
||||
@@ -0,0 +1,13 @@
|
||||
{{{WORKBOX_SW_IMPORT}}}
|
||||
|
||||
const bgSyncPlugin = new workbox.backgroundSync.Plugin('myQueueName');
|
||||
|
||||
workbox.routing.registerRoute(
|
||||
({url}) => url.pathname === '/demo/workbox-background-sync/example.txt',
|
||||
new workbox.strategies.NetworkOnly({
|
||||
plugins: [bgSyncPlugin],
|
||||
}),
|
||||
);
|
||||
|
||||
workbox.skipWaiting();
|
||||
workbox.clientsClaim();
|
||||
@@ -0,0 +1,39 @@
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-8 mx-auto">
|
||||
<ol>
|
||||
<li>Open DevTools</li>
|
||||
<li>Go to the Console</li>
|
||||
<li>Click any of the buttons below and view the logs:
|
||||
<ul>
|
||||
<li><p><button class="trigger-broadcast btn btn-outline-primary">Trigger a Broadcast</button></p></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
const triggerBroadcast = document.querySelector('.trigger-broadcast');
|
||||
|
||||
const broadcastChannel = new BroadcastChannel('broadcast-demo');
|
||||
broadcastChannel.onmessage = (ev) => {
|
||||
console.log(`Received a message on 'broadcast-demo' channel.`);
|
||||
console.log(ev.data);
|
||||
};
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
navigator.serviceWorker.register('/demo/workbox-broadcast-cache-update/sw.js')
|
||||
.then((reg) => {
|
||||
triggerBroadcast.addEventListener('click', () => {
|
||||
const message = {
|
||||
command: 'trigger-broadcast',
|
||||
};
|
||||
reg.active.postMessage(message);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,30 @@
|
||||
{{{WORKBOX_SW_IMPORT}}}
|
||||
|
||||
const broadcastUpdate = new workbox.broadcastUpdate.BroadcastCacheUpdate(
|
||||
'broadcast-demo');
|
||||
|
||||
const initialResponse = new Response('Response 1', {
|
||||
headers: {
|
||||
'last-modified': Date.now().toString(),
|
||||
}
|
||||
});
|
||||
|
||||
self.addEventListener('message', (event) => {
|
||||
switch(event.data.command) {
|
||||
case 'trigger-broadcast':
|
||||
const secondResponse = new Response('Response 2', {
|
||||
headers: {
|
||||
'last-modified': Date.now().toString(),
|
||||
}
|
||||
});
|
||||
broadcastUpdate.notifyIfUpdated(
|
||||
initialResponse,
|
||||
secondResponse,
|
||||
'example-url',
|
||||
'example-cachename'
|
||||
);
|
||||
break;
|
||||
default:
|
||||
console.log(`Unknown command received in the service worker: `, event);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,76 @@
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-8 mx-auto">
|
||||
<ol>
|
||||
<li>Open DevTools</li>
|
||||
<li>Go to the Console</li>
|
||||
<li>Click the following buttons and look at the Cache and IndexedDB entries.
|
||||
<ol>
|
||||
<li><button class="entry-1 btn btn-outline-primary">Entry 1</button> (Expires in: <span class="entry-expire-1">Not Added Yet</span>)</li>
|
||||
<li><button class="entry-2 btn btn-outline-primary">Entry 2</button> (Expires in: <span class="entry-expire-2">Not Added Yet</span>)</li>
|
||||
<li><button class="entry-3 btn btn-outline-primary">Entry 3</button> (Expires in: <span class="entry-expire-3">Not Added Yet</span>)</li>
|
||||
<li><button class="entry-4 btn btn-outline-primary">Entry 4</button> (Expires in: <span class="entry-expire-4">Not Added Yet</span>)</li>
|
||||
<li><button class="entry-5 btn btn-outline-primary">Entry 5</button> (Expires in: <span class="entry-expire-5">Not Added Yet</span>)</li>
|
||||
</ol>
|
||||
</li>
|
||||
<li>To remove any expired entries click here:<br /><button class="expire btn btn-outline-primary">Expire Entries</button></li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
const expireBtn = document.querySelector('.expire');
|
||||
|
||||
const TIMEOUT_IN_SECS = 30;
|
||||
const MAX_ENTRIES = 3;
|
||||
|
||||
const setupTimeout = (btnElement, textElement) => {
|
||||
textElement.textContent = `${btnElement.__workbox_timeoutCount}s`;
|
||||
btnElement.__workbox_timeoutId = setTimeout(() => {
|
||||
btnElement.__workbox_timeoutCount -= 1;
|
||||
if (btnElement.__workbox_timeoutCount > 0) {
|
||||
setupTimeout(btnElement, textElement);
|
||||
} else {
|
||||
textElement.textContent = `Expired`;
|
||||
}
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
navigator.serviceWorker.register('/demo/workbox-cache-expiration/sw.js')
|
||||
.then((reg) => {
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const entryBtn = document.querySelector(`.entry-${i + 1}`);
|
||||
const expireText = document.querySelector(`.entry-expire-${i + 1}`);
|
||||
entryBtn.addEventListener('click', () => {
|
||||
const message = {
|
||||
command: 'update-entry',
|
||||
id: i + 1,
|
||||
};
|
||||
|
||||
reg.active.postMessage(message);
|
||||
|
||||
if (entryBtn.__workbox_timeoutId) {
|
||||
clearTimeout(entryBtn.__workbox_timeoutId);
|
||||
}
|
||||
|
||||
entryBtn.__workbox_timeoutCount = TIMEOUT_IN_SECS;
|
||||
setupTimeout(entryBtn, expireText);
|
||||
});
|
||||
}
|
||||
|
||||
expireBtn.addEventListener('click', () => {
|
||||
const message = {
|
||||
command: 'expire-entries',
|
||||
};
|
||||
|
||||
reg.active.postMessage(message);
|
||||
|
||||
// TODO: Would be good to find a way to highlight max entries.
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,31 @@
|
||||
{{{WORKBOX_SW_IMPORT}}}
|
||||
|
||||
const expirationManager = new workbox.expiration.CacheExpiration(
|
||||
'demo-cache-for-expiration', {
|
||||
maxEntries: 3,
|
||||
maxAgeSeconds: 30,
|
||||
});
|
||||
|
||||
const updateEntry = (entryID) => {
|
||||
caches.open('cache-expiration')
|
||||
.then((openCache) => {
|
||||
openCache.put(
|
||||
`example-entry-${entryID}`,
|
||||
new Response(`Hello from entry number ${entryID}`)
|
||||
);
|
||||
expirationManager.updateTimestamp(`example-entry-${entryID}`);
|
||||
});
|
||||
};
|
||||
|
||||
self.addEventListener('message', (event) => {
|
||||
switch(event.data.command) {
|
||||
case 'update-entry':
|
||||
updateEntry(event.data.id);
|
||||
break;
|
||||
case 'expire-entries':
|
||||
expirationManager.expireEntries();
|
||||
break;
|
||||
default:
|
||||
console.log(`Unknown command received in the service worker: `, event);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-8 mx-auto">
|
||||
<ol>
|
||||
<li>Open DevTools</li>
|
||||
<li>Go to the Console</li>
|
||||
<li>Click any of the buttons below and view the logs:
|
||||
<ul>
|
||||
<li><p><button class="with-good-header btn btn-outline-primary">With 'X-Is-Cacheable: true' Header</button></p></li>
|
||||
<li><p><button class="with-bad-header btn btn-outline-primary">With 'X-Is-Cacheable: false' Header</button></p></li>
|
||||
<li><p><button class="without-header btn btn-outline-primary">Without 'X-Is-Cacheable' Header</button></p></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
const withGoodHeaderBtn = document.querySelector('.with-good-header');
|
||||
const withBadHeaderBtn = document.querySelector('.with-bad-header');
|
||||
const withoutHeaderBtn = document.querySelector('.without-header');
|
||||
|
||||
const DEMO_REQUEST_URL = '/api/is-response-cacheable';
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
navigator.serviceWorker.register('/demo/workbox-cacheable-response/sw.js')
|
||||
.then(() => {
|
||||
withGoodHeaderBtn.addEventListener('click', () => {
|
||||
fetch(DEMO_REQUEST_URL, {
|
||||
headers: {
|
||||
'X-Is-Cacheable': true
|
||||
}
|
||||
});
|
||||
|
||||
withBadHeaderBtn.addEventListener('click', () => {
|
||||
fetch(DEMO_REQUEST_URL, {
|
||||
headers: {
|
||||
'X-Is-Cacheable': false
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
withoutHeaderBtn.addEventListener('click', () => {
|
||||
fetch(DEMO_REQUEST_URL);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,32 @@
|
||||
{{{WORKBOX_SW_IMPORT}}}
|
||||
|
||||
const cacheable = new workbox.cacheableResponse.CacheableResponse({
|
||||
statuses: [200],
|
||||
headers: {
|
||||
'X-Is-Cacheable': 'true',
|
||||
},
|
||||
});
|
||||
|
||||
const handleCachableResponse = (event) => {
|
||||
return fetch(event.request)
|
||||
.then((response) => {
|
||||
if (cacheable.isResponseCacheable(response)) {
|
||||
// Response meets the criteria
|
||||
} else {
|
||||
// Response does not meet the criteria
|
||||
}
|
||||
|
||||
return response;
|
||||
});
|
||||
};
|
||||
|
||||
self.addEventListener('fetch', (event) => {
|
||||
switch (new URL(event.request.url).pathname) {
|
||||
case '/api/is-response-cacheable':
|
||||
event.respondWith(handleCachableResponse(event));
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
workbox.skipWaiting();
|
||||
workbox.clientsClaim();
|
||||
@@ -0,0 +1,40 @@
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-8 mx-auto">
|
||||
<ol>
|
||||
<li>Open DevTools</li>
|
||||
<li>Go to the Console</li>
|
||||
<li>Click this button:<br /><button class="install-sw btn btn-outline-primary">Install Service Worker</button></li>
|
||||
<li>Checkout the logs for info on Workbox and getting started.</li>
|
||||
<li>Click this button to print out various types of logs:<br /><button class="show-logs btn btn-outline-primary">Show Me Workbox Logs</button></li>
|
||||
<li>Click this button to print out the Workbox Cache Names:<br /><button class="show-cache-names btn btn-outline-primary">Show Me Workbox Cache Names</button></li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
const installSWBtn = document.querySelector('.install-sw');
|
||||
const showLogsBtn = document.querySelector('.show-logs');
|
||||
const cacheNamesBtn = document.querySelector('.show-cache-names');
|
||||
|
||||
installSWBtn.addEventListener('click', () => {
|
||||
navigator.serviceWorker.register('/demo/workbox-core/sw.js')
|
||||
.then((reg) => {
|
||||
showLogsBtn.addEventListener('click', () => {
|
||||
const message = {
|
||||
command: 'printLogs',
|
||||
};
|
||||
reg.active.postMessage(message);
|
||||
});
|
||||
cacheNamesBtn.addEventListener('click', () => {
|
||||
const message = {
|
||||
command: 'printCacheNames',
|
||||
};
|
||||
reg.active.postMessage(message);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,32 @@
|
||||
{{{WORKBOX_SW_IMPORT}}}
|
||||
|
||||
workbox.core.setLogLevel(workbox.core.LOG_LEVELS.debug);
|
||||
|
||||
const printLogs = () => {
|
||||
// ☠️ You should never so this - this is just so we can show off our logging.
|
||||
workbox.core._private.logger.debug(`🐛 Oh hai! I'm a debug message.`);
|
||||
workbox.core._private.logger.log(`🔧 Good ole log message.`);
|
||||
workbox.core._private.logger.warn(`⚠️ Uh Oh.... I'm a warning.`);
|
||||
workbox.core._private.logger.error(`☠️ Eek, stuff is breaking. p.s. I'm an error.`);
|
||||
};
|
||||
|
||||
const printCacheNames = () => {
|
||||
workbox.core._private.logger.log(`The caches used by Workbox are...`);
|
||||
const cacheNames = workbox.core.cacheNames;
|
||||
Object.keys(cacheNames).forEach((cacheId) => {
|
||||
console.log(` ${cacheId}: ${cacheNames[cacheId]}`);
|
||||
});
|
||||
};
|
||||
|
||||
self.addEventListener('message', (event) => {
|
||||
switch(event.data.command) {
|
||||
case 'printLogs':
|
||||
printLogs();
|
||||
break;
|
||||
case 'printCacheNames':
|
||||
printCacheNames();
|
||||
break;
|
||||
default:
|
||||
console.log(`Unknown command received in the service worker: `, event);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-8 mx-auto">
|
||||
<ol>
|
||||
<li>Open DevTools</li>
|
||||
<li>Go to the Console</li>
|
||||
<li>Click this button:<br /><button class="make-analytics-call btn btn-outline-primary">Make Analytics Call</button></li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-77119321-5"></script>
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', 'UA-77119321-5', 'auto');
|
||||
</script>
|
||||
|
||||
<script>
|
||||
const analyticsCallBtn = document.querySelector('.make-analytics-call');
|
||||
|
||||
navigator.serviceWorker.register('/demo/workbox-google-analytics/sw.js')
|
||||
.then((reg) => {
|
||||
analyticsCallBtn.addEventListener('click', () => {
|
||||
ga('send', 'pageview');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
{{{WORKBOX_SW_IMPORT}}}
|
||||
|
||||
workbox.googleAnalytics.initialize();
|
||||
|
||||
workbox.skipWaiting();
|
||||
workbox.clientsClaim();
|
||||
@@ -0,0 +1,35 @@
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-8 mx-auto">
|
||||
<ol>
|
||||
<li>Open DevTools</li>
|
||||
<li>Go to the Console</li>
|
||||
<li>Click this button:<br /><button class="install-sw-1 btn btn-outline-primary">Install Service Worker</button></li>
|
||||
<li>Checkout the logs for info on what was precached.</li>
|
||||
<li>Explore Cache and indexedDB in the "Application" tab in DevTools. <i>Note: You may need to right-click and refresh.</i></li>
|
||||
<li>Click this button:<br /><button class="install-sw-2 btn btn-outline-primary">Install New Service Worker</button></li>
|
||||
<li>Check logs, cache and indexedDB to highlight the new, updated and deleted entry.</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
const firstBtn = document.querySelector('.install-sw-1');
|
||||
const secondBtn = document.querySelector('.install-sw-2');
|
||||
|
||||
firstBtn.addEventListener('click', () => {
|
||||
navigator.serviceWorker.register('/demo/workbox-precaching/sw-1.js');
|
||||
});
|
||||
secondBtn.addEventListener('click', () => {
|
||||
navigator.serviceWorker.getRegistration()
|
||||
.then((reg) => {
|
||||
return reg.unregister();
|
||||
})
|
||||
.then(() => {
|
||||
navigator.serviceWorker.register('/demo/workbox-precaching/sw-2.js');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,7 @@
|
||||
{{{WORKBOX_SW_IMPORT}}}
|
||||
|
||||
workbox.precaching.precache([
|
||||
{url: '/demo/workbox-precaching', revision: '1'},
|
||||
'test-file.txt',
|
||||
'hello-world.1234.txt',
|
||||
]);
|
||||
@@ -0,0 +1,7 @@
|
||||
{{{WORKBOX_SW_IMPORT}}}
|
||||
|
||||
workbox.precaching.precache([
|
||||
{url: '/demo/workbox-precaching', revision: '2'},
|
||||
'test-file.txt',
|
||||
'hello-world.5678.txt',
|
||||
]);
|
||||
@@ -0,0 +1,36 @@
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-8 mx-auto">
|
||||
<ol>
|
||||
<li>Open DevTools</li>
|
||||
<li>Go to the Console</li>
|
||||
<li>Click this button to request range 1 - 4 of 'hello, world.':<br /><button class="make-range-request btn btn-outline-primary">Make Range Request</button></li>
|
||||
<li>Checkout the logs for info on the range request that was handled.</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
const makeRequestBtn = document.querySelector('.make-range-request');
|
||||
|
||||
caches.open('range-requests-demo')
|
||||
.then((cache) => cache.put('/range-request-example', new Response('hello, world.')));
|
||||
|
||||
navigator.serviceWorker.register('/demo/workbox-range-requests/sw.js')
|
||||
.then(() => {
|
||||
makeRequestBtn.addEventListener('click', () => {
|
||||
fetch(new Request('/range-request-example', {
|
||||
headers: {
|
||||
Range: `bytes=1-4`,
|
||||
}
|
||||
}))
|
||||
.then((response) => response.text())
|
||||
.then((responseText) => {
|
||||
console.log(`Received response: '${responseText}'`);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,16 @@
|
||||
{{{WORKBOX_SW_IMPORT}}}
|
||||
|
||||
console.log(new workbox.rangeRequests.Plugin());
|
||||
|
||||
workbox.routing.registerRoute(
|
||||
new RegExp('/range-request-example'),
|
||||
workbox.strategies.cacheOnly({
|
||||
cacheName: 'range-requests-demo',
|
||||
plugins: [
|
||||
new workbox.rangeRequests.Plugin(),
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
workbox.skipWaiting();
|
||||
workbox.clientsClaim();
|
||||
@@ -0,0 +1,25 @@
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-8 mx-auto">
|
||||
<ol>
|
||||
<li>Open DevTools</li>
|
||||
<li>Go to the Console</li>
|
||||
<li>Click this button:<br /><button class="install-sw btn btn-outline-primary">Install Service Worker</button></li>
|
||||
<li><strong>Refresh the page</strong></li>
|
||||
<li>Notice that the image '/demo/workbox-routing/demo-img.png' was routed and is now a party popper</li>
|
||||
</ol>
|
||||
|
||||
<img src="/demo/workbox-routing/demo-img.png" style="width: 300px; height: 300px;"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
const firstBtn = document.querySelector('.install-sw');
|
||||
|
||||
firstBtn.addEventListener('click', () => {
|
||||
navigator.serviceWorker.register('/demo/workbox-routing/sw.js');
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,26 @@
|
||||
{{{WORKBOX_SW_IMPORT}}}
|
||||
|
||||
// Set logs level to `debug` to view all logs
|
||||
// workbox.core.setLogLevel(workbox.core.LOG_LEVELS.debug);
|
||||
|
||||
// Set up a route to alter the demo-img
|
||||
const matchCb = ({url, event}) => {
|
||||
return url.pathname === '/demo/workbox-routing/demo-img.png';
|
||||
};
|
||||
|
||||
const handlerCb = ({url, event, params}) => {
|
||||
return fetch('/demo/workbox-routing/demo-img-intercepted.png');
|
||||
};
|
||||
|
||||
workbox.routing.registerRoute(matchCb, handlerCb);
|
||||
|
||||
// TODO: Switch to just a string match when supported.
|
||||
const JQUERY_URL = 'https://code.jquery.com/jquery-3.2.1.slim.min.js';
|
||||
workbox.routing.registerRoute(new RegExp(JQUERY_URL), () => {
|
||||
// Demonstrating when an error is thrown by a Route.
|
||||
throw new Error(`Example error thrown from the default handler`);
|
||||
});
|
||||
|
||||
workbox.routing.setCatchHandler(({event}) => {
|
||||
return fetch(event.request);
|
||||
});
|
||||
@@ -0,0 +1,85 @@
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-8 mx-auto">
|
||||
<ol>
|
||||
<li>Open DevTools</li>
|
||||
<li>Go to the Console</li>
|
||||
<li>Click any of the buttons below and view the logs:
|
||||
<ul>
|
||||
<li>
|
||||
<p>This will attempt to get a request from an empty cache.</p>
|
||||
<p><button class="cache-only-empty-cache btn btn-outline-primary">Cache Only Request (To Empty Cache)</button></p>
|
||||
</li>
|
||||
<li><p><button class="cache-only-populated-cache btn btn-outline-primary">Cache Only Request (To Populated Cache)</button></p></li>
|
||||
<li><p><button class="cache-first btn btn-outline-primary">Cache First Request (Try it Several Times)</button></p></li>
|
||||
<li><p><button class="network-only btn btn-outline-primary">Network Only Request</button></p></li>
|
||||
<li><p><button class="network-first-valid btn btn-outline-primary">Network First Request (Valid URL)</button></p></li>
|
||||
<li><p><button class="network-first-failing btn btn-outline-primary">Network First Request (Failing URL)</button></p></li>
|
||||
<li><p><button class="stale-while-revalidate btn btn-outline-primary">Stale While Revalidate Request</button></p></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
const cacheOnlyEmpty = document.querySelector('.cache-only-empty-cache');
|
||||
const cacheOnlyPopulated = document.querySelector('.cache-only-populated-cache');
|
||||
const cacheFirst = document.querySelector('.cache-first');
|
||||
const networkOnly = document.querySelector('.network-only');
|
||||
const networkFirstValid = document.querySelector('.network-first-valid');
|
||||
const networkFirstFailing = document.querySelector('.network-first-failing');
|
||||
const staleWhileRevalidate = document.querySelector('.stale-while-revalidate');
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
navigator.serviceWorker.register('/demo/workbox-strategies/sw.js')
|
||||
.then(() => {
|
||||
cacheOnlyEmpty.addEventListener('click', () => {
|
||||
fetch('cache-only-empty-cache.txt').catch(() => {});
|
||||
});
|
||||
|
||||
cacheOnlyPopulated.addEventListener('click', () => {
|
||||
fetch('cache-only-populated-cache')
|
||||
.then((response) => {
|
||||
return response.text();
|
||||
});
|
||||
});
|
||||
|
||||
cacheFirst.addEventListener('click', () => {
|
||||
fetch('cache-first.txt')
|
||||
.then((response) => {
|
||||
return response.text();
|
||||
});
|
||||
});
|
||||
|
||||
networkOnly.addEventListener('click', () => {
|
||||
fetch('network-only.txt')
|
||||
.then((response) => {
|
||||
return response.text();
|
||||
});
|
||||
});
|
||||
|
||||
networkFirstValid.addEventListener('click', () => {
|
||||
fetch('network-first.txt')
|
||||
.then((response) => {
|
||||
return response.text();
|
||||
});
|
||||
});
|
||||
|
||||
networkFirstFailing.addEventListener('click', () => {
|
||||
fetch('network-first-404.txt')
|
||||
.catch(() => {});
|
||||
});
|
||||
|
||||
staleWhileRevalidate.addEventListener('click', () => {
|
||||
fetch('stale-while-revalidate.txt')
|
||||
.then((response) => {
|
||||
return response.text();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,52 @@
|
||||
{{{WORKBOX_SW_IMPORT}}}
|
||||
|
||||
self.addEventListener('fetch', (event) => {
|
||||
switch (new URL(event.request.url).pathname) {
|
||||
case '/demo/workbox-strategies/cache-only-empty-cache.txt':
|
||||
const cacheOnlyEmpty = new workbox.strategies.CacheOnly();
|
||||
event.respondWith(cacheOnlyEmpty.handle({event}));
|
||||
break;
|
||||
case '/demo/workbox-strategies/cache-only-populated-cache':
|
||||
const cacheOnlyPopulated = new workbox.strategies.CacheOnly();
|
||||
event.respondWith(cacheOnlyPopulated.handle({event}));
|
||||
break;
|
||||
case '/demo/workbox-strategies/cache-first.txt':
|
||||
const cacheFirst = new workbox.strategies.CacheFirst();
|
||||
event.respondWith(cacheFirst.handle({event}));
|
||||
break;
|
||||
case '/demo/workbox-strategies/network-only.txt':
|
||||
const networkOnly = new workbox.strategies.NetworkOnly();
|
||||
event.respondWith(networkOnly.handle({event}));
|
||||
break;
|
||||
case '/demo/workbox-strategies/network-first.txt':
|
||||
const networkFirst = new workbox.strategies.NetworkFirst();
|
||||
event.respondWith(networkFirst.handle({event}));
|
||||
break;
|
||||
case '/demo/workbox-strategies/network-first-404.txt':
|
||||
const networkFirstInvalid = new workbox.strategies.NetworkFirst();
|
||||
event.respondWith(networkFirstInvalid.handle({event}));
|
||||
break;
|
||||
case '/demo/workbox-strategies/stale-while-revalidate.txt':
|
||||
const staleWhileRevalidate = new workbox.strategies.StaleWhileRevalidate();
|
||||
event.respondWith(staleWhileRevalidate.handle({event}));
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// The following code is here to demonstrate different aspects of the
|
||||
// strategies in the demo.
|
||||
|
||||
workbox.skipWaiting();
|
||||
workbox.clientsClaim();
|
||||
|
||||
self.addEventListener('install', (event) => {
|
||||
event.waitUntil(
|
||||
caches.open(workbox.core.cacheNames.runtime)
|
||||
.then((cache) => {
|
||||
return cache.put(
|
||||
new Request('/demo/workbox-strategies/cache-only-populated-cache'),
|
||||
new Response('Hello from the populated cache.')
|
||||
);
|
||||
})
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-8 mx-auto">
|
||||
<ol>
|
||||
<li>Open DevTools</li>
|
||||
<li>Go to the Console</li>
|
||||
<li>Click this button:<br /><button class="install-sw btn btn-outline-primary">Install Service Worker</button></li>
|
||||
<li>You should see a 'Welcome to Workbox' message.</li>
|
||||
<li>Refresh the page and see the requests being passed through Workbox.</li>
|
||||
</ol>
|
||||
|
||||
<!-- Image will be stored in a custom cache -->
|
||||
<img src="/demo/workbox-routing/demo-img.png" style="width: 300px; height: 300px;"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
const installSWBtn = document.querySelector('.install-sw');
|
||||
installSWBtn.addEventListener('click', () => {
|
||||
navigator.serviceWorker.register('/demo/workbox-sw/sw.js');
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,24 @@
|
||||
{{{WORKBOX_SW_IMPORT}}}
|
||||
|
||||
workbox.precaching.precacheAndRoute([
|
||||
'https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css'
|
||||
]);
|
||||
|
||||
// Demonstrates using default cache
|
||||
workbox.routing.registerRoute(
|
||||
/.*\.(?:js)/g,
|
||||
new workbox.strategies.NetworkFirst()
|
||||
);
|
||||
|
||||
// Demonstrates a custom cache name for a route.
|
||||
workbox.routing.registerRoute(
|
||||
/.*\.(?:png|jpg|jpeg|svg|gif)/g,
|
||||
workbox.strategies.cacheFirst({
|
||||
cacheName: 'image-cache',
|
||||
plugins: [
|
||||
new workbox.expiration.Plugin({
|
||||
maxEntries: 3
|
||||
})
|
||||
]
|
||||
})
|
||||
);
|
||||
@@ -0,0 +1,17 @@
|
||||
{{#each modules}}
|
||||
<section id="{{name}}">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-8 mx-auto">
|
||||
<h2>{{name}}</h2>
|
||||
<p>
|
||||
{{#if hasDemo}}
|
||||
<a class="btn btn-outline-primary" href="/demo/{{name}}/">Demo</a>
|
||||
{{/if}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{{/each}}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- Required meta tags -->
|
||||
<title>Workbox CDN Demos</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
||||
<!-- Bootstrap CSS -->
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
|
||||
<!-- Theme from https://blackrockdigital.github.io/startbootstrap-scrolling-nav/ -->
|
||||
<style>
|
||||
header {
|
||||
padding: 154px 0 100px;
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
header {
|
||||
padding: 156px 0 100px;
|
||||
}
|
||||
}
|
||||
|
||||
section {
|
||||
padding: 40px 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body id="page-top">
|
||||
<!-- Navigation -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top" id="mainNav">
|
||||
<div class="container">
|
||||
<a class="navbar-brand js-scroll-trigger" href="#page-top">Workbox V3</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ml-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link js-scroll-trigger" href="/">Home</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<header class="bg-primary text-white">
|
||||
<div class="container text-center">
|
||||
<h1>{{title}}</h1>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{{{body}}}
|
||||
|
||||
<footer class="py-5 bg-dark">
|
||||
<div class="container">
|
||||
<p class="m-0 text-center text-white">
|
||||
<a class="text-white" href="https://developers.google.com/web/">Docs</a> |
|
||||
<a class="text-white" href="https://github.com/googlechrome/workbox">Github</a> |
|
||||
<a class="text-white" href="https://twitter.com/workboxjs">@workboxjs</a>
|
||||
</p>
|
||||
</div>
|
||||
<!-- /.container -->
|
||||
</footer>
|
||||
|
||||
<!-- Optional JavaScript -->
|
||||
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
|
||||
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.11.0/umd/popper.min.js" integrity="sha384-b/U6ypiBEHpOf/4+1nzFpr53nxSS+GLCkfwBdFNTxtclqqenISfwAzpKaMNFNmj4" crossorigin="anonymous"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/js/bootstrap.min.js" integrity="sha384-h0AbiXch4ZDo7tp9hKZ4TsHbi047NrKGLO3SEJAg45jXxnGIfYzk4Si90RDIqNm1" crossorigin="anonymous"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
example text
|
||||
@@ -0,0 +1,3 @@
|
||||
Hello, World!
|
||||
|
||||
I'm the first version of this file.
|
||||
@@ -0,0 +1,3 @@
|
||||
Hello, World!
|
||||
|
||||
I'm the second version of this file.
|
||||
@@ -0,0 +1,3 @@
|
||||
#Danger
|
||||
|
||||
This file is not revisioned.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.4 KiB |
@@ -0,0 +1 @@
|
||||
Hello from CacheFirst
|
||||
@@ -0,0 +1 @@
|
||||
Hello from NetworkFirst
|
||||
@@ -0,0 +1 @@
|
||||
Hello from NetworkOnly
|
||||
@@ -0,0 +1 @@
|
||||
Hello from StaleWhileRevalidate
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"functions": {
|
||||
"source": "demos/functions"
|
||||
},
|
||||
"hosting": {
|
||||
"public": "demos/public",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"**/node_modules/**",
|
||||
"*.sh",
|
||||
"./*"
|
||||
],
|
||||
"cleanUrls": true,
|
||||
"rewrites": [
|
||||
{
|
||||
"source": "/", "function": "app"
|
||||
}, {
|
||||
"source": "/demo/*", "function": "app"
|
||||
}, {
|
||||
"source": "/demo/*/sw*.js", "function": "app"
|
||||
}, {
|
||||
"source": "/api/*", "function": "app"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
const gulp = require('gulp');
|
||||
|
||||
const AnalyseBuildForProperties = require('./utils/analyse-properties');
|
||||
|
||||
gulp.task('analyze-properties:run', async () => {
|
||||
const analysisTool = new AnalyseBuildForProperties();
|
||||
const results = await analysisTool.run();
|
||||
results.forEach((entry) => {
|
||||
analysisTool.printDetails(entry);
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task('analyze-properties',
|
||||
gulp.series(['build', 'analyze-properties:run']));
|
||||
@@ -0,0 +1,29 @@
|
||||
const gulp = require('gulp');
|
||||
|
||||
const buildBrowserBundle = require('./utils/build-browser-bundle');
|
||||
const versionModule = require('./utils/version-module');
|
||||
const constants = require('./utils/constants');
|
||||
const packageRunnner = require('./utils/package-runner');
|
||||
|
||||
gulp.task('build-browser-packages:browser-bundle', gulp.series(
|
||||
Object.keys(constants.BUILD_TYPES).map((buildKey) => packageRunnner(
|
||||
'build-browser-packages:browser-bundle',
|
||||
'browser',
|
||||
buildBrowserBundle,
|
||||
constants.BUILD_TYPES[buildKey],
|
||||
))
|
||||
));
|
||||
|
||||
gulp.task('build-browser-packages:version-module', gulp.series(
|
||||
Object.keys(constants.BUILD_TYPES).map((buildKey) => packageRunnner(
|
||||
'build-browser-packages:version-module',
|
||||
'browser',
|
||||
versionModule,
|
||||
constants.BUILD_TYPES[buildKey],
|
||||
))
|
||||
));
|
||||
|
||||
gulp.task('build-browser-packages', gulp.series(
|
||||
'build-browser-packages:version-module',
|
||||
'build-browser-packages:browser-bundle',
|
||||
));
|
||||
@@ -0,0 +1,12 @@
|
||||
const gulp = require('gulp');
|
||||
|
||||
const buildNodePackage = require('./utils/build-node-package');
|
||||
const packageRunnner = require('./utils/package-runner');
|
||||
|
||||
gulp.task('build-node-packages', gulp.series(
|
||||
packageRunnner(
|
||||
'build-node-packages',
|
||||
'node',
|
||||
buildNodePackage
|
||||
)
|
||||
));
|
||||
@@ -0,0 +1,30 @@
|
||||
const fse = require('fs-extra');
|
||||
const gulp = require('gulp');
|
||||
const path = require('path');
|
||||
|
||||
const constants = require('./utils/constants');
|
||||
const packageRunnner = require('./utils/package-runner');
|
||||
|
||||
const cleanPackage = (packagePath) => {
|
||||
const outputDirectory = path.join(packagePath,
|
||||
constants.PACKAGE_BUILD_DIRNAME);
|
||||
return fse.remove(outputDirectory);
|
||||
};
|
||||
|
||||
gulp.task('build-packages:clean', gulp.series(
|
||||
packageRunnner(
|
||||
'build-packages:clean',
|
||||
'all',
|
||||
cleanPackage
|
||||
)
|
||||
));
|
||||
|
||||
gulp.task('build-packages:build', gulp.parallel(
|
||||
'build-node-packages',
|
||||
'build-browser-packages'
|
||||
));
|
||||
|
||||
gulp.task('build-packages', gulp.series(
|
||||
'build-packages:clean',
|
||||
'build-packages:build'
|
||||
));
|
||||
+33
-133
@@ -1,145 +1,45 @@
|
||||
/*
|
||||
Copyright 2016 Google Inc. All Rights Reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable no-console */
|
||||
|
||||
const chalk = require('chalk');
|
||||
const fse = require('fs-extra');
|
||||
const gulp = require('gulp');
|
||||
const lernaWrapper = require('./utils/lerna-wrapper');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const {taskHarness, buildJSBundle} = require('../utils/build');
|
||||
const commonjs = require('rollup-plugin-commonjs');
|
||||
const multiEntry = require('rollup-plugin-multi-entry');
|
||||
const resolve = require('rollup-plugin-node-resolve');
|
||||
|
||||
const printHeading = (heading) => {
|
||||
process.stdout.write(chalk.inverse(` ⚒ ${heading} `));
|
||||
};
|
||||
gulp.task('lerna-bootstrap', () => {
|
||||
// If it's a star, build all projects (I.e. bootstrap everything.)
|
||||
if (global.packageOrStar === '*') {
|
||||
return lernaWrapper.bootstrap();
|
||||
}
|
||||
|
||||
const printBuildTime = (buildTime) => {
|
||||
process.stdout.write(chalk.inverse(`(${buildTime})\n`));
|
||||
};
|
||||
// If it's not a star, we can scope the build to a specific project.
|
||||
return lernaWrapper.bootstrap(
|
||||
'--include-filtered-dependencies',
|
||||
'--scope', global.packageOrStar
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* Builds a given project.
|
||||
* @param {String} projectPath The path to a project directory.
|
||||
* @return {Promise} Resolves if building succeeds, rejects if it fails.
|
||||
*/
|
||||
const buildPackage = (projectPath) => {
|
||||
printHeading(`Building ${path.basename(projectPath)}`);
|
||||
const startTime = Date.now();
|
||||
const buildDir = `${projectPath}/build`;
|
||||
const projectBuildProcess = require(`${projectPath}/build.js`);
|
||||
// This is needed for workbox-build but is also used by the rollup-helper
|
||||
// to add CDN details to workbox-sw.
|
||||
// Make sure this runs **before** lerna-bootstrap.
|
||||
gulp.task('build:update-cdn-details', async function() {
|
||||
const cdnDetails = await fs.readJSON(path.join(
|
||||
__dirname, '..', 'cdn-details.json'
|
||||
));
|
||||
|
||||
return fse.emptyDir(buildDir)
|
||||
.then(() => projectBuildProcess())
|
||||
.then(() => buildTestBundles(projectPath))
|
||||
.then(() => {
|
||||
fse.copy(path.join(__dirname, '..', 'LICENSE'),
|
||||
path.join(projectPath, 'LICENSE'));
|
||||
})
|
||||
.then(() => printBuildTime(`${(Date.now() - startTime) / 1000}s`));
|
||||
};
|
||||
const lernaPkg = await fs.readJSON(path.join(
|
||||
__dirname, '..', 'lerna.json'
|
||||
));
|
||||
|
||||
/**
|
||||
* Builds the browser and service worker test bundles.
|
||||
* @param {String} projectPath The path to a project directory.
|
||||
* @return {Promise} Resolves if building succeeds, rejects if it fails.
|
||||
*/
|
||||
const buildTestBundles = (projectPath) => {
|
||||
const getPlugins = () => [
|
||||
// multiEntry uses glob so it requires `/` separators in paths.
|
||||
multiEntry({exports: false}),
|
||||
resolve({jsnext: true, main: true, browser: true}),
|
||||
commonjs(),
|
||||
];
|
||||
cdnDetails.latestVersion = lernaPkg.version;
|
||||
|
||||
const project = path.basename(projectPath);
|
||||
const workboxBuildPath = path.join(
|
||||
__dirname, '..', 'packages', 'workbox-build', 'src', 'cdn-details.json'
|
||||
);
|
||||
|
||||
return Promise.all([
|
||||
buildJSBundle({
|
||||
rollupConfig: {
|
||||
entry: `./packages/${project}/test/browser/*.js`,
|
||||
plugins: getPlugins(),
|
||||
},
|
||||
writeConfig: {
|
||||
sourceMap: true,
|
||||
format: 'es',
|
||||
dest: `./packages/${project}/build/test/browser-bundle.js`,
|
||||
},
|
||||
}),
|
||||
buildJSBundle({
|
||||
rollupConfig: {
|
||||
entry: [
|
||||
`./utils/sw-test-utils.js`,
|
||||
`./packages/${project}/test/sw/*.js`,
|
||||
],
|
||||
plugins: getPlugins(),
|
||||
},
|
||||
writeConfig: {
|
||||
sourceMap: true,
|
||||
format: 'es',
|
||||
dest: `./packages/${project}/build/test/sw-bundle.js`,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
};
|
||||
|
||||
gulp.task('build:shared', () => {
|
||||
const basePlugins = [
|
||||
resolve({
|
||||
jsnext: true,
|
||||
main: true,
|
||||
browser: true,
|
||||
}),
|
||||
commonjs(),
|
||||
];
|
||||
|
||||
return buildJSBundle({
|
||||
rollupConfig: {
|
||||
entry: path.join(__dirname, '..', 'lib', 'log-helper.js'),
|
||||
plugins: [],
|
||||
},
|
||||
writeConfig: {
|
||||
sourceMap: true,
|
||||
format: 'umd',
|
||||
moduleName: 'goog.logHelper',
|
||||
dest: path.join(__dirname, '..', 'build', 'log-helper.js'),
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
return buildJSBundle({
|
||||
rollupConfig: {
|
||||
entry: path.join(__dirname, '..', 'lib', 'assert.js'),
|
||||
plugins: basePlugins,
|
||||
},
|
||||
writeConfig: {
|
||||
sourceMap: true,
|
||||
format: 'umd',
|
||||
moduleName: 'goog.assert',
|
||||
dest: path.join(__dirname, '..', 'build', 'assert.js'),
|
||||
},
|
||||
});
|
||||
await fs.writeJson(workboxBuildPath, cdnDetails, {
|
||||
spaces: 2,
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task('build', () => {
|
||||
return taskHarness(buildPackage, global.projectOrStar);
|
||||
});
|
||||
|
||||
gulp.task('build:watch', ['build'], (unusedCallback) => {
|
||||
gulp.watch(`packages/${global.projectOrStar}/+(src|test)/**/*`, ['build']);
|
||||
gulp.watch(`lib/**/*`, ['build']);
|
||||
});
|
||||
gulp.task('build', gulp.series(
|
||||
'build:update-cdn-details',
|
||||
'lerna-bootstrap',
|
||||
));
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
const gulp = require('gulp');
|
||||
const path = require('path');
|
||||
const fs = require('fs-extra');
|
||||
const glob = require('glob');
|
||||
|
||||
const constants = require('./utils/constants');
|
||||
const getNpmCmd = require('./utils/get-npm-cmd');
|
||||
const spawn = require('./utils/spawn-promise-wrapper');
|
||||
|
||||
gulp.task('demos:groupBuildFiles', async () => {
|
||||
const pattern = path.posix.join(
|
||||
__dirname, '..', 'packages', '**',
|
||||
constants.PACKAGE_BUILD_DIRNAME, '*.{js,map}');
|
||||
|
||||
const localBuildPath = path.join(__dirname, '..', 'demos', 'public',
|
||||
constants.LOCAL_BUILDS_DIR);
|
||||
await fs.remove(localBuildPath);
|
||||
await fs.ensureDir(localBuildPath);
|
||||
|
||||
// Copy files from the source code and move into the grouped build
|
||||
// directory. In others, have a flat file structure of just the built files.
|
||||
const filesToIncludeInBundle = glob.sync(pattern);
|
||||
for (const fileToInclude of filesToIncludeInBundle) {
|
||||
await fs.ensureSymlink(
|
||||
fileToInclude,
|
||||
path.join(localBuildPath, path.basename(fileToInclude)),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
gulp.task('demos:firebaseServe:local', () => {
|
||||
process.env.WORKBOX_DEMO_ENV = 'local';
|
||||
return spawn(getNpmCmd(), [
|
||||
'run', 'demos-serve',
|
||||
]);
|
||||
});
|
||||
|
||||
gulp.task('demos:firebaseServe:cdn', () => {
|
||||
process.env.WORKBOX_DEMO_ENV = 'cdn';
|
||||
return spawn(getNpmCmd(), [
|
||||
'run', 'demos-serve',
|
||||
]);
|
||||
});
|
||||
|
||||
gulp.task('demos:serve:local', gulp.series([
|
||||
'demos:groupBuildFiles',
|
||||
'demos:firebaseServe:local',
|
||||
]));
|
||||
|
||||
gulp.task('demos:serve:cdn', gulp.series([
|
||||
'demos:groupBuildFiles',
|
||||
'demos:firebaseServe:cdn',
|
||||
]));
|
||||
|
||||
gulp.task('demos:cdn', gulp.series([
|
||||
'demos:serve:local',
|
||||
]));
|
||||
|
||||
gulp.task('demos', gulp.series([
|
||||
'demos:serve:local',
|
||||
]));
|
||||
@@ -0,0 +1,97 @@
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const gulp = require('gulp');
|
||||
const getNpmCmd = require('./utils/get-npm-cmd');
|
||||
const browserSync = require('browser-sync').create();
|
||||
|
||||
const spawn = require('./utils/spawn-promise-wrapper');
|
||||
const logHelper = require('../infra/utils/log-helper');
|
||||
|
||||
const DOCS_DIRECTORY = path.join(__dirname, '..', 'docs');
|
||||
|
||||
gulp.task('docs:clean', () => {
|
||||
return fs.remove(DOCS_DIRECTORY);
|
||||
});
|
||||
|
||||
const getJSDocFunc = (debug) => {
|
||||
return () => {
|
||||
logHelper.log(`Building docs...`);
|
||||
const queryString = [
|
||||
`projectRoot=/`,
|
||||
`basepath=/`,
|
||||
`productName=Workbox`,
|
||||
].join('&');
|
||||
|
||||
const params = [
|
||||
'run', 'local-jsdoc', '--',
|
||||
'-c', path.join(__dirname, '..', 'jsdoc.conf'),
|
||||
'-d', DOCS_DIRECTORY,
|
||||
];
|
||||
|
||||
|
||||
if (!global.cliOptions.pretty) {
|
||||
logHelper.warn(`
|
||||
|
||||
These docs will look ugly, but they will more accurately match what
|
||||
is shown on developers.google.com.
|
||||
|
||||
You can view a friendlier UI by running
|
||||
|
||||
'gulp docs --pretty'
|
||||
`);
|
||||
params.push(
|
||||
'--template', path.join(
|
||||
__dirname, '..', 'infra', 'templates', 'reference-docs', 'jsdoc'
|
||||
),
|
||||
'--query', queryString,
|
||||
);
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
params.push('--debug');
|
||||
}
|
||||
|
||||
return spawn(getNpmCmd(), params, {
|
||||
cwd: path.join(__dirname, '..'),
|
||||
})
|
||||
.then(() => {
|
||||
logHelper.log(`Docs built successfully`);
|
||||
browserSync.reload();
|
||||
})
|
||||
.catch((err) => {
|
||||
logHelper.error(`Docs failed to build: `, err);
|
||||
throw err;
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
gulp.task('docs:build-debug', gulp.series([
|
||||
'docs:clean',
|
||||
getJSDocFunc(true),
|
||||
]));
|
||||
|
||||
gulp.task('docs:build', gulp.series([
|
||||
'docs:clean',
|
||||
getJSDocFunc(false),
|
||||
]));
|
||||
|
||||
gulp.task('docs:watch', () => {
|
||||
const watcher = gulp.watch('packages/**/*',
|
||||
gulp.series(['docs:build']));
|
||||
watcher.on('error', (err) => {
|
||||
logHelper.error(`Docs failed to build: `, err);
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task('docs:serve', () => {
|
||||
browserSync.init({
|
||||
server: {
|
||||
baseDir: DOCS_DIRECTORY,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task('docs', gulp.series([
|
||||
'docs:build',
|
||||
gulp.parallel(['docs:serve', 'docs:watch']),
|
||||
]));
|
||||
@@ -1,111 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 Google Inc. All Rights Reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable no-console */
|
||||
|
||||
const fse = require('fs-extra');
|
||||
const gulp = require('gulp');
|
||||
const path = require('path');
|
||||
const runSequence = require('run-sequence');
|
||||
|
||||
const {taskHarness, lernaWrapper} = require('../utils/build');
|
||||
|
||||
/**
|
||||
* Updates the fields in package.json that contain version string to match
|
||||
* the latest version from lerna.json.
|
||||
*
|
||||
* @param {String} projectPath The path to a project directory.
|
||||
* @return {Promise} Resolves if updating succeeds, and rejects if it fails.
|
||||
*/
|
||||
const updateVersionedBundles = (projectPath) => {
|
||||
const packageJsonPath = path.join(projectPath, 'package.json');
|
||||
|
||||
return fse.readJson(packageJsonPath).then((pkg) => {
|
||||
const regexp = /v\d+\.\d+\.\d+/;
|
||||
for (let field of ['main', 'module']) {
|
||||
if (field in pkg) {
|
||||
pkg[field] = pkg[field].replace(regexp, `v${pkg.version}`);
|
||||
}
|
||||
}
|
||||
|
||||
return fse.writeJson(packageJsonPath, pkg, {spaces: 2});
|
||||
});
|
||||
};
|
||||
|
||||
gulp.task('lerna-bootstrap', () => {
|
||||
return lernaWrapper('bootstrap');
|
||||
});
|
||||
|
||||
gulp.task('lerna-bootstrap-scoped', () => {
|
||||
return lernaWrapper('bootstrap', '--include-filtered-dependencies', '--scope',
|
||||
global.projectOrStar);
|
||||
});
|
||||
|
||||
/**
|
||||
* Helper task, used only within lerna-publish.
|
||||
*/
|
||||
gulp.task('_lerna-publish-dry-run', () => {
|
||||
return lernaWrapper('publish', '--skip-npm', '--skip-git');
|
||||
});
|
||||
|
||||
gulp.task('_lerna-publish-dry-run:force-all', () => {
|
||||
return lernaWrapper('publish', '--skip-npm', '--skip-git',
|
||||
'--force-publish=*');
|
||||
});
|
||||
|
||||
/**
|
||||
* Helper task, used only within lerna-publish.
|
||||
*/
|
||||
gulp.task('_update-versioned-bundles', () => {
|
||||
return taskHarness(updateVersionedBundles, global.projectOrStar);
|
||||
});
|
||||
|
||||
/**
|
||||
* Helper task, used only within lerna-publish.
|
||||
*/
|
||||
gulp.task('_lerna-publish-repo-version', () => {
|
||||
return fse.readJson('lerna.json').then((lernaConfig) => {
|
||||
return lernaWrapper('publish', '--yes', '--repo-version',
|
||||
lernaConfig.version);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* This is the task you should use to publish a new release of updated
|
||||
* modules to npm.
|
||||
*/
|
||||
gulp.task('lerna-publish', (callback) => {
|
||||
runSequence(
|
||||
'lerna-bootstrap',
|
||||
'test:dev',
|
||||
'test:prod',
|
||||
'_lerna-publish-dry-run',
|
||||
'_update-versioned-bundles',
|
||||
'_lerna-publish-repo-version',
|
||||
callback
|
||||
);
|
||||
});
|
||||
|
||||
gulp.task('lerna-publish:force-all', (callback) => {
|
||||
runSequence(
|
||||
'lerna-bootstrap',
|
||||
'test:dev',
|
||||
'test:prod',
|
||||
'_lerna-publish-dry-run:force-all',
|
||||
'_update-versioned-bundles',
|
||||
'_lerna-publish-repo-version',
|
||||
callback
|
||||
);
|
||||
});
|
||||
+10
-32
@@ -1,35 +1,13 @@
|
||||
/*
|
||||
Copyright 2016 Google Inc. All Rights Reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
const gulp = require('gulp');
|
||||
const eslint = require('gulp-eslint');
|
||||
const spawn = require('./utils/spawn-promise-wrapper');
|
||||
const getNpmCmd = require('./utils/get-npm-cmd');
|
||||
const logHelper = require('../infra/utils/log-helper');
|
||||
|
||||
gulp.task('lint:repo', () => {
|
||||
return gulp.src(['{gulp-tasks,lib,templates,test,utils}/**/*.js', '*.js'])
|
||||
.pipe(eslint())
|
||||
.pipe(eslint.format())
|
||||
.pipe(eslint.failAfterError())
|
||||
// See https://github.com/adametry/gulp-eslint/issues/36#issuecomment-109595391
|
||||
.on('data', () => {});
|
||||
});
|
||||
|
||||
gulp.task('lint', ['lint:repo'], () => {
|
||||
return gulp.src([`packages/${global.projectOrStar}/**/*.js`])
|
||||
.pipe(eslint())
|
||||
.pipe(eslint.format())
|
||||
.pipe(eslint.failAfterError())
|
||||
// See https://github.com/adametry/gulp-eslint/issues/36#issuecomment-109595391
|
||||
.on('data', () => {});
|
||||
// Use npm run lint to ensure we are using local eslint
|
||||
gulp.task('lint', () => {
|
||||
return spawn(getNpmCmd(), ['run', 'lint'])
|
||||
.catch((err) => {
|
||||
logHelper.error(err);
|
||||
throw new Error(`[Workbox Error Msg] 'gulp lint' discovered errors.`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
const gulp = require('gulp');
|
||||
const path = require('path');
|
||||
|
||||
const cdnUploadHelper = require('./utils/cdn-helper');
|
||||
const publishHelpers = require('./utils/publish-helpers');
|
||||
const githubHelper = require('./utils/github-helper');
|
||||
const logHelper = require('../infra/utils/log-helper');
|
||||
|
||||
// Git adds 'v' to tag name, lerna does not in package.json version.
|
||||
// We are going to publish to CDN *without* the 'v'
|
||||
const cleanTagName = (name) => {
|
||||
let friendlyTagName = name;
|
||||
if (friendlyTagName.indexOf('v') === 0) {
|
||||
friendlyTagName = friendlyTagName.substring(1);
|
||||
}
|
||||
return friendlyTagName;
|
||||
};
|
||||
|
||||
const findMissingCDNTags = async (tagsData) => {
|
||||
const missingTags = [];
|
||||
for (let tagData of tagsData) {
|
||||
let exists = await cdnUploadHelper.tagExists(cleanTagName(tagData.name));
|
||||
|
||||
if (!exists) {
|
||||
missingTags.push(tagData);
|
||||
}
|
||||
}
|
||||
return missingTags;
|
||||
};
|
||||
|
||||
const handleCDNUpload = async (tagName, gitBranch) => {
|
||||
const buildFilesDir =
|
||||
await publishHelpers.groupBuildFiles(tagName, gitBranch);
|
||||
|
||||
const friendlyTagName = cleanTagName(tagName);
|
||||
|
||||
logHelper.log(`Uploading '${tagName}' to CDN as ${friendlyTagName}.`);
|
||||
const publishUrls = await cdnUploadHelper.upload(
|
||||
friendlyTagName, buildFilesDir);
|
||||
|
||||
logHelper.log(`The following URLs are now avaiable:`);
|
||||
publishUrls.forEach((url) => {
|
||||
// Only print out the js files just for cleaner logs.
|
||||
if (path.extname(url) === '.map') {
|
||||
return;
|
||||
}
|
||||
|
||||
logHelper.log(` ${url}`);
|
||||
});
|
||||
};
|
||||
|
||||
gulp.task('publish-cdn:generate-from-tags', async () => {
|
||||
// Get all of the tags in the repo.
|
||||
const tags = await githubHelper.getTags();
|
||||
const missingTags = await findMissingCDNTags(tags);
|
||||
|
||||
if (missingTags.length === 0) {
|
||||
logHelper.log(`No tags missing from CDN.`);
|
||||
}
|
||||
|
||||
for (let tagData of missingTags) {
|
||||
// Override the git branch here since we aren't actually
|
||||
// using a tagged release.
|
||||
await handleCDNUpload(tagData.name, tagData.name);
|
||||
}
|
||||
});
|
||||
|
||||
gulp.task('publish-cdn', gulp.series(
|
||||
'publish-cdn:generate-from-tags',
|
||||
));
|
||||
@@ -0,0 +1,39 @@
|
||||
const gulp = require('gulp');
|
||||
const path = require('path');
|
||||
const fs = require('fs-extra');
|
||||
|
||||
const getVersionsCDNUrl = require('./utils/versioned-cdn-url');
|
||||
const getNpmCmd = require('./utils/get-npm-cmd');
|
||||
const spawn = require('./utils/spawn-promise-wrapper');
|
||||
const constants = require('./utils/constants');
|
||||
|
||||
// TODO: This should publish based on git tags, similar to Github and CDN
|
||||
// releases
|
||||
|
||||
gulp.task('publish-demos:updateCDNDetails', () => {
|
||||
const details = {
|
||||
latestUrl: getVersionsCDNUrl(),
|
||||
};
|
||||
const filePath = path.join(__dirname, '..', 'demos',
|
||||
'functions', 'cdn-details.json');
|
||||
|
||||
return fs.writeJSON(filePath, details);
|
||||
});
|
||||
|
||||
gulp.task('publish-demos:deploy', () => {
|
||||
return spawn(getNpmCmd(), ['run', 'demos-deploy'], {
|
||||
cwd: path.join(__dirname, '..'),
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task('publish-demos:clean', () => {
|
||||
return fs.remove(
|
||||
path.join(__dirname, '..', 'demos', 'public', constants.LOCAL_BUILDS_DIR)
|
||||
);
|
||||
});
|
||||
|
||||
gulp.task('publish-demos', gulp.series([
|
||||
'publish-demos:clean',
|
||||
'publish-demos:updateCDNDetails',
|
||||
'publish-demos:deploy',
|
||||
]));
|
||||
@@ -0,0 +1,86 @@
|
||||
const gulp = require('gulp');
|
||||
const path = require('path');
|
||||
const semver = require('semver');
|
||||
const fs = require('fs-extra');
|
||||
|
||||
const publishHelpers = require('./utils/publish-helpers');
|
||||
const githubHelper = require('./utils/github-helper');
|
||||
const logHelper = require('../infra/utils/log-helper');
|
||||
|
||||
const publishReleaseOnGithub =
|
||||
async (tagName, releaseInfo, tarPath, zipPath) => {
|
||||
if (!releaseInfo) {
|
||||
const releaseData = await githubHelper.createRelease({
|
||||
tag_name: tagName,
|
||||
draft: true,
|
||||
name: `Workbox ${tagName}`,
|
||||
prerelease: semver.prerelease(tagName) !== null,
|
||||
});
|
||||
releaseInfo = releaseData.data;
|
||||
}
|
||||
|
||||
const tarBuffer = await fs.readFile(tarPath);
|
||||
await githubHelper.uploadAsset({
|
||||
url: releaseInfo.upload_url,
|
||||
file: tarBuffer,
|
||||
contentType: 'application/gzip',
|
||||
contentLength: tarBuffer.length,
|
||||
name: path.basename(tarPath),
|
||||
label: path.basename(tarPath),
|
||||
});
|
||||
|
||||
const zipBuffer = await fs.readFile(zipPath);
|
||||
await githubHelper.uploadAsset({
|
||||
url: releaseInfo.upload_url,
|
||||
file: zipBuffer,
|
||||
contentType: 'application/zip',
|
||||
contentLength: zipBuffer.length,
|
||||
name: path.basename(zipPath),
|
||||
label: path.basename(zipPath),
|
||||
});
|
||||
};
|
||||
|
||||
const handleGithubRelease = async (tagName, gitBranch, releaseInfo) => {
|
||||
logHelper.log(`Creating Github release ${logHelper.highlight(tagName)}.`);
|
||||
|
||||
const {tarPath, zipPath} =
|
||||
await publishHelpers.createBundles(tagName, gitBranch);
|
||||
|
||||
return publishReleaseOnGithub(tagName, releaseInfo, tarPath, zipPath);
|
||||
};
|
||||
|
||||
const filterTagsWithReleaseBundles = (allTags, taggedReleases) => {
|
||||
return allTags.filter((tagInfo) => {
|
||||
const release = taggedReleases[tagInfo.name];
|
||||
if (release && release.assets.length > 0) {
|
||||
// If a tag has a release and there is an asset let's assume the
|
||||
// the release is fine. Note: Github's source doesn't count as an
|
||||
// asset
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
gulp.task('publish-github:generate-from-tags', async () => {
|
||||
// Get all of the tags in the repo.
|
||||
const allTags = await githubHelper.getTags();
|
||||
const taggedReleases = await githubHelper.getTaggedReleases();
|
||||
const tagsToBuild = await filterTagsWithReleaseBundles(
|
||||
allTags, taggedReleases);
|
||||
|
||||
if (tagsToBuild.length === 0) {
|
||||
logHelper.log(`No tags missing from Github.`);
|
||||
return;
|
||||
}
|
||||
|
||||
for (let tagInfo of tagsToBuild) {
|
||||
await handleGithubRelease(
|
||||
tagInfo.name, tagInfo.name, taggedReleases[tagInfo.name]);
|
||||
}
|
||||
});
|
||||
|
||||
gulp.task('publish-github', gulp.series(
|
||||
'publish-github:generate-from-tags',
|
||||
));
|
||||
@@ -0,0 +1,17 @@
|
||||
const gulp = require('gulp');
|
||||
|
||||
const getNpmCmd = require('./utils/get-npm-cmd');
|
||||
const spawn = require('./utils/spawn-promise-wrapper');
|
||||
|
||||
gulp.task('publish-lerna', () => {
|
||||
return spawn(getNpmCmd(), [
|
||||
'run', 'local-lerna',
|
||||
'--',
|
||||
'publish',
|
||||
|
||||
// The following flags can be used if publishing non-stable versions
|
||||
// '--cd-version=prerelease',
|
||||
// '--preid=beta',
|
||||
// '--npm-tag', 'beta',
|
||||
]);
|
||||
});
|
||||
@@ -0,0 +1,48 @@
|
||||
const gulp = require('gulp');
|
||||
const path = require('path');
|
||||
const fs = require('fs-extra');
|
||||
|
||||
const getNpmCmd = require('./utils/get-npm-cmd');
|
||||
const spawn = require('./utils/spawn-promise-wrapper');
|
||||
const logHelper = require('../infra/utils/log-helper');
|
||||
const constants = require('./utils/constants');
|
||||
|
||||
gulp.task('publish:clean', () => {
|
||||
return fs.remove(path.join(__dirname, '..',
|
||||
constants.GENERATED_RELEASE_FILES_DIRNAME));
|
||||
});
|
||||
|
||||
gulp.task('publish:signin', async () => {
|
||||
try {
|
||||
await spawn(getNpmCmd(), [
|
||||
'whoami',
|
||||
]);
|
||||
} catch (err) {
|
||||
// Sign in
|
||||
logHelper.warn('');
|
||||
logHelper.warn(' You must be signed in to NPM to publish.');
|
||||
logHelper.warn(' Please run `npm login` to publish.');
|
||||
logHelper.warn('');
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
gulp.task('publish-assets', gulp.series([
|
||||
'publish:clean',
|
||||
'publish-github',
|
||||
'publish-cdn',
|
||||
'publish-demos',
|
||||
]));
|
||||
|
||||
gulp.task('publish', gulp.series([
|
||||
'publish:signin',
|
||||
'test',
|
||||
'publish-lerna',
|
||||
() => {
|
||||
logHelper.log(`The release was successful!\n\n`);
|
||||
logHelper.log(`Now run ${logHelper.highlight('gulp publish-assets')}\n\n`);
|
||||
|
||||
// Gulp requires a promise.
|
||||
return Promise.resolve();
|
||||
},
|
||||
]));
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 Google Inc. All Rights Reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable no-console, valid-jsdoc */
|
||||
|
||||
const gulp = require('gulp');
|
||||
const testServerGen = require('../utils/test-server-generator.js');
|
||||
|
||||
gulp.task('serve', (unusedCallback) => {
|
||||
return testServerGen().start('.', global.port)
|
||||
.then((port) => {
|
||||
console.log(`Primary Server http://localhost:${port}/`);
|
||||
console.log(`Secondary Server http://localhost:${port + 1}/`);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,149 @@
|
||||
const gulp = require('gulp');
|
||||
const oneLine = require('common-tags').oneLine;
|
||||
const glob = require('glob');
|
||||
const seleniumAssistant = require('selenium-assistant');
|
||||
const path = require('path');
|
||||
const clearRequire = require('clear-require');
|
||||
|
||||
const constants = require('./utils/constants');
|
||||
const logHelper = require('../infra/utils/log-helper');
|
||||
const testServer = require('../infra/testing/test-server');
|
||||
|
||||
const runFiles = (filePaths) => {
|
||||
// Mocha can't be run multiple times, which we need for NODE_ENV.
|
||||
// More info: https://github.com/mochajs/mocha/issues/995
|
||||
clearRequire.all();
|
||||
const Mocha = require('mocha');
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const mocha = new Mocha({
|
||||
retries: process.env.TRAVIS ? 2 : 1,
|
||||
timeout: 3 * 60 * 1000,
|
||||
});
|
||||
|
||||
for (const filePath of filePaths) {
|
||||
mocha.addFile(filePath);
|
||||
}
|
||||
|
||||
// Run the tests.
|
||||
mocha.run(function(failureCount) {
|
||||
if (failureCount > 0) {
|
||||
return reject(`${failureCount} tests failed.`);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const runIntegrationTestSuite = async (testPath, nodeEnv, seleniumBrowser,
|
||||
webdriver) => {
|
||||
logHelper.log(oneLine`
|
||||
Running Integration test on ${logHelper.highlight(testPath)}
|
||||
with NODE_ENV '${nodeEnv}'
|
||||
and browser '${logHelper.highlight(seleniumBrowser.getPrettyName())}'
|
||||
`);
|
||||
|
||||
const options = [];
|
||||
if (global.cliOptions.grep) {
|
||||
options.push('--grep', global.cliOptions.grep);
|
||||
}
|
||||
const originalNodeEnv = process.env.NODE_ENV;
|
||||
|
||||
process.env.NODE_ENV = nodeEnv;
|
||||
|
||||
try {
|
||||
global.__workbox = {
|
||||
webdriver,
|
||||
seleniumBrowser,
|
||||
server: testServer,
|
||||
};
|
||||
|
||||
const testFiles = glob.sync(path.posix.join(__dirname, '..', testPath,
|
||||
'*.js'));
|
||||
|
||||
testServer.reset();
|
||||
await runFiles(testFiles);
|
||||
|
||||
process.env.NODE_ENV = originalNodeEnv;
|
||||
} catch (err) {
|
||||
process.env.NODE_ENV = originalNodeEnv;
|
||||
|
||||
logHelper.error(err);
|
||||
throw new Error(
|
||||
`[Workbox Error Msg] 'gulp test-integration' discovered errors.`);
|
||||
}
|
||||
};
|
||||
|
||||
const runIntegrationForBrowser = async (browser) => {
|
||||
const packagesToTest = glob.sync(`test/${global.packageOrStar}/integration`);
|
||||
|
||||
for (const buildKey of Object.keys(constants.BUILD_TYPES)) {
|
||||
const webdriver = await browser.getSeleniumDriver();
|
||||
// Safari Tech Preview can take a long time when working with SW APIs
|
||||
webdriver.manage().timeouts().setScriptTimeout(2 * 60 * 1000);
|
||||
|
||||
for (const packageToTest of packagesToTest) {
|
||||
const nodeEnv = constants.BUILD_TYPES[buildKey];
|
||||
try {
|
||||
await runIntegrationTestSuite(packageToTest, nodeEnv, browser,
|
||||
webdriver);
|
||||
} catch (error) {
|
||||
await seleniumAssistant.killWebDriver(webdriver);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
await seleniumAssistant.killWebDriver(webdriver);
|
||||
}
|
||||
};
|
||||
|
||||
gulp.task('test-integration', async () => {
|
||||
if (process.platform === 'win32') {
|
||||
logHelper.warn(`Skipping integration tests on Windows.`);
|
||||
return;
|
||||
}
|
||||
|
||||
logHelper.log(`Downloading browsers......`);
|
||||
const expiration = 24;
|
||||
await seleniumAssistant.downloadLocalBrowser('chrome', 'stable', expiration);
|
||||
await seleniumAssistant.downloadLocalBrowser('chrome', 'beta', expiration);
|
||||
await seleniumAssistant.downloadLocalBrowser('firefox', 'stable', expiration);
|
||||
await seleniumAssistant.downloadLocalBrowser('firefox', 'beta', expiration);
|
||||
|
||||
const packagesToTest = glob.sync(`test/${global.packageOrStar}/integration`);
|
||||
if (packagesToTest.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
await testServer.start();
|
||||
|
||||
try {
|
||||
const localBrowsers = seleniumAssistant.getLocalBrowsers();
|
||||
for (const localBrowser of localBrowsers) {
|
||||
switch (localBrowser.getId()) {
|
||||
case 'chrome':
|
||||
case 'firefox':
|
||||
if (localBrowser.getReleaseName() !== 'unstable') {
|
||||
await runIntegrationForBrowser(localBrowser);
|
||||
}
|
||||
break;
|
||||
case 'safari':
|
||||
if (localBrowser.getReleaseName() === 'beta') {
|
||||
await runIntegrationForBrowser(localBrowser);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
logHelper.warn(oneLine`
|
||||
Skipping integration tests for ${localBrowser.getPrettyName()}.
|
||||
`);
|
||||
}
|
||||
}
|
||||
await testServer.stop();
|
||||
} catch (err) {
|
||||
await testServer.stop();
|
||||
throw err;
|
||||
}
|
||||
|
||||
// TODO Saucelabs browsers for latest - 1 browser
|
||||
// TODO Saucelabs browsers for latest - 2 browser
|
||||
});
|
||||
@@ -0,0 +1,90 @@
|
||||
const gulp = require('gulp');
|
||||
const oneLine = require('common-tags').oneLine;
|
||||
const glob = require('glob');
|
||||
const fse = require('fs-extra');
|
||||
const path = require('path');
|
||||
|
||||
const spawn = require('./utils/spawn-promise-wrapper');
|
||||
const getNpmCmd = require('./utils/get-npm-cmd');
|
||||
const constants = require('./utils/constants');
|
||||
const logHelper = require('../infra/utils/log-helper');
|
||||
|
||||
const runNodeTestSuite = async (testPath, nodeEnv) => {
|
||||
logHelper.log(oneLine`
|
||||
Running Node test on ${logHelper.highlight(testPath)}
|
||||
with NODE_ENV '${nodeEnv}'
|
||||
`);
|
||||
|
||||
const options = [];
|
||||
if (global.cliOptions.grep) {
|
||||
options.push('--grep', global.cliOptions.grep);
|
||||
}
|
||||
const originalNodeEnv = process.env.NODE_ENV;
|
||||
|
||||
process.env.NODE_ENV = nodeEnv;
|
||||
try {
|
||||
await spawn(getNpmCmd(), ['run', 'test', '--',
|
||||
`${testPath}/**/*.{js,mjs}`,
|
||||
...options,
|
||||
]);
|
||||
process.env.NODE_ENV = originalNodeEnv;
|
||||
} catch (err) {
|
||||
process.env.NODE_ENV = originalNodeEnv;
|
||||
|
||||
logHelper.error(err);
|
||||
throw new Error(`[Workbox Error Msg] 'gulp test-node' discovered errors.`);
|
||||
}
|
||||
};
|
||||
|
||||
const runNodeTestsWithEnv = async (testGroup, nodeEnv) => {
|
||||
let globConfig = {
|
||||
ignore: [
|
||||
'**/all/**',
|
||||
],
|
||||
};
|
||||
|
||||
if (testGroup === 'all') {
|
||||
globConfig.ignore = [];
|
||||
}
|
||||
|
||||
const packagesToTest = glob.sync(`test/${testGroup}/node`, globConfig);
|
||||
for (const packageToTest of packagesToTest) {
|
||||
await runNodeTestSuite(packageToTest, nodeEnv);
|
||||
}
|
||||
};
|
||||
|
||||
gulp.task('test-node:prod', gulp.series(
|
||||
() => runNodeTestsWithEnv(global.packageOrStar, constants.BUILD_TYPES.prod),
|
||||
));
|
||||
|
||||
gulp.task('test-node:dev', gulp.series(
|
||||
() => runNodeTestsWithEnv(global.packageOrStar, constants.BUILD_TYPES.dev),
|
||||
));
|
||||
|
||||
gulp.task('test-node:all', gulp.series(
|
||||
() => runNodeTestsWithEnv('all', constants.BUILD_TYPES.prod),
|
||||
));
|
||||
|
||||
gulp.task('test-node:clean', () => {
|
||||
return fse.remove(path.join(__dirname, '..', '.nyc_output'));
|
||||
});
|
||||
|
||||
gulp.task('test-node:coverage', () => {
|
||||
const runOptions = ['run', 'coverage-report'];
|
||||
if (global.packageOrStar !== '*') {
|
||||
runOptions.push('--');
|
||||
runOptions.push('--include');
|
||||
runOptions.push(
|
||||
path.posix.join('packages', global.packageOrStar, '**', '*')
|
||||
);
|
||||
}
|
||||
return spawn(getNpmCmd(), runOptions);
|
||||
});
|
||||
|
||||
gulp.task('test-node', gulp.series(
|
||||
'test-node:clean',
|
||||
'test-node:dev',
|
||||
'test-node:prod',
|
||||
'test-node:all',
|
||||
'test-node:coverage',
|
||||
));
|
||||
@@ -0,0 +1,34 @@
|
||||
const gulp = require('gulp');
|
||||
|
||||
const constants = require('./utils/constants');
|
||||
const testServer = require('../infra/testing/test-server');
|
||||
|
||||
const handleExit = () => {
|
||||
testServer.stop();
|
||||
process.exit(0);
|
||||
};
|
||||
|
||||
const startServer = () => {
|
||||
const eventNames = [
|
||||
'exit',
|
||||
'SIGINT',
|
||||
'SIGUSR1',
|
||||
'SIGUSR2',
|
||||
'uncaughtException',
|
||||
];
|
||||
eventNames.forEach((eventName) => {
|
||||
process.on(eventName, handleExit);
|
||||
});
|
||||
|
||||
return testServer.start();
|
||||
};
|
||||
|
||||
gulp.task('test-server:prod', () => {
|
||||
process.env.NODE_ENV = constants.BUILD_TYPES.prod;
|
||||
startServer();
|
||||
});
|
||||
|
||||
gulp.task('test-server', () => {
|
||||
process.env.NODE_ENV = constants.BUILD_TYPES.dev;
|
||||
startServer();
|
||||
});
|
||||
+6
-81
@@ -1,83 +1,8 @@
|
||||
/*
|
||||
Copyright 2016 Google Inc. All Rights Reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
const seleniumAssistant = require('selenium-assistant');
|
||||
const gulp = require('gulp');
|
||||
const mocha = require('gulp-spawn-mocha');
|
||||
const runSequence = require('run-sequence');
|
||||
|
||||
gulp.task('download-browsers', function() {
|
||||
if (process.platform === 'win32') {
|
||||
// We only support CLI testing on Windows at the moment.
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
console.log(' Starting browser download.....');
|
||||
return Promise.all([
|
||||
seleniumAssistant.downloadLocalBrowser('firefox', 'stable', 48),
|
||||
seleniumAssistant.downloadLocalBrowser('firefox', 'beta', 48),
|
||||
seleniumAssistant.downloadLocalBrowser('firefox', 'unstable', 48),
|
||||
seleniumAssistant.downloadLocalBrowser('chrome', 'stable', 48),
|
||||
seleniumAssistant.downloadLocalBrowser('chrome', 'beta', 48),
|
||||
seleniumAssistant.downloadLocalBrowser('chrome', 'unstable', 48),
|
||||
])
|
||||
.then(() => {
|
||||
console.log(' Browser download complete.');
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task('mocha', ['build'], () => {
|
||||
const mochaOptions = {
|
||||
env: {
|
||||
projectOrStar: global.projectOrStar,
|
||||
},
|
||||
};
|
||||
|
||||
if (global.cliOptions.grep) {
|
||||
mochaOptions.grep = global.cliOptions.grep;
|
||||
}
|
||||
|
||||
return gulp.src('test/bootstrap.js', {read: false})
|
||||
.pipe(mocha(mochaOptions))
|
||||
.once('error', (error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task('test', ['lint', 'download-browsers'], (callback) => {
|
||||
runSequence('mocha', callback);
|
||||
});
|
||||
|
||||
const testInEnv = (env, callback) => {
|
||||
const supportedEnvs = ['dev', 'prod'];
|
||||
if (!supportedEnvs.includes(env)) {
|
||||
throw Error(`Environment ${env} is not supported. ` +
|
||||
`Please use one of: ${JSON.stringify(supportedEnvs)}`);
|
||||
}
|
||||
|
||||
process.env.WB_TEST_BUNDLE = env;
|
||||
runSequence('test', (error) => {
|
||||
delete process.env.WB_TEST_BUNDLE;
|
||||
callback(error);
|
||||
});
|
||||
};
|
||||
|
||||
gulp.task('test:prod', (callback) => {
|
||||
testInEnv('prod', callback);
|
||||
});
|
||||
|
||||
gulp.task('test:dev', (callback) => {
|
||||
testInEnv('dev', callback);
|
||||
});
|
||||
gulp.task('test', gulp.series(
|
||||
'build',
|
||||
'test-node',
|
||||
'test-integration',
|
||||
'lint',
|
||||
));
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* This file should be run as a node script to analyse the content of the
|
||||
* browser bundles.
|
||||
*
|
||||
* It's an extremely naive approach to picking property names out and seeing
|
||||
* how often they are used in the final browser bundles. This can be
|
||||
* used to detect variable names that are long and used repeatedly meaning
|
||||
* that a small refactor allowing `uglify-es` to mangle these properties,
|
||||
* could lead to reasonable improvement in final bundle size.
|
||||
*
|
||||
* This is very rough and vague. It needs to be run manually and if it's not
|
||||
* used can and should be removed from Workbox repo.
|
||||
*/
|
||||
const glob = require('glob');
|
||||
const path = require('path');
|
||||
const fs = require('fs-extra');
|
||||
const babylon = require('babylon');
|
||||
|
||||
const constants = require('./constants');
|
||||
const logHelper = require('../../infra/utils/log-helper');
|
||||
|
||||
class AnalyseBuildForProperties {
|
||||
run() {
|
||||
const filePaths = this.getBuildFiles();
|
||||
return Promise.all(filePaths.map((filePath) => {
|
||||
const rawAnalysis = this.analyzeFile(filePath);
|
||||
const analysis = this.tidyData(rawAnalysis);
|
||||
|
||||
return {
|
||||
filePath,
|
||||
analysis,
|
||||
};
|
||||
}));
|
||||
}
|
||||
|
||||
getBuildFiles() {
|
||||
// workbox-sw doesn't include .prod. in the build name.
|
||||
const buildGlob = path.join(__dirname, '..', '..', 'packages',
|
||||
'*', constants.PACKAGE_BUILD_DIRNAME, '{*.prod.js,workbox-sw.js}');
|
||||
return glob.sync(buildGlob);
|
||||
}
|
||||
|
||||
analyzeFile(filePath) {
|
||||
const fileContents = fs.readFileSync(filePath).toString();
|
||||
|
||||
const parsedCode = babylon.parse(fileContents);
|
||||
const tokens = parsedCode.tokens;
|
||||
const nameTokens = tokens.filter((token) => {
|
||||
return token.type.label === 'name';
|
||||
});
|
||||
|
||||
const matches = {};
|
||||
|
||||
nameTokens.forEach((token) => {
|
||||
const variableName = token.value;
|
||||
if (!matches[variableName]) {
|
||||
matches[variableName] = 0;
|
||||
}
|
||||
matches[variableName]++;
|
||||
});
|
||||
|
||||
return Object.keys(matches).map((matchKey) => {
|
||||
return {
|
||||
propertyName: matchKey,
|
||||
propertyCount: matches[matchKey],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
tidyData(analysisEntries) {
|
||||
return analysisEntries.filter((entry) => {
|
||||
// If there is only one entry or it's a single character it's
|
||||
// either not important or it's already been minified.
|
||||
return entry.propertyCount > 1 && entry.propertyName.length > 1;
|
||||
})
|
||||
.filter((entry) => {
|
||||
switch (entry.propertyName) {
|
||||
case 'await':
|
||||
case 'async':
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
})
|
||||
.sort((a, b) => {
|
||||
return b.propertyCount - a.propertyCount;
|
||||
});
|
||||
}
|
||||
|
||||
printDetails({filePath, analysis}) {
|
||||
let longestPropertyName = 0;
|
||||
analysis.forEach((entry) => {
|
||||
if (entry.propertyName.length > longestPropertyName) {
|
||||
longestPropertyName = entry.propertyName.length;
|
||||
}
|
||||
});
|
||||
|
||||
logHelper.log();
|
||||
logHelper.log(`Results for '${path.relative(process.cwd(), filePath)}'`);
|
||||
logHelper.log();
|
||||
|
||||
analysis.forEach((entry) => {
|
||||
const numberOfSpaces = longestPropertyName - entry.propertyName.length;
|
||||
const extraSpace = ' '.repeat(numberOfSpaces);
|
||||
logHelper.log(` ${entry.propertyName} ` +
|
||||
`${extraSpace} ${entry.propertyCount}`);
|
||||
});
|
||||
logHelper.log();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AnalyseBuildForProperties;
|
||||
@@ -0,0 +1,195 @@
|
||||
const buffer = require('vinyl-buffer');
|
||||
const fs = require('fs-extra');
|
||||
const gulp = require('gulp');
|
||||
const oneLine = require('common-tags').oneLine;
|
||||
const path = require('path');
|
||||
const rename = require('gulp-rename');
|
||||
const rollup = require('rollup');
|
||||
const rollupStream = require('rollup-stream');
|
||||
const source = require('vinyl-source-stream');
|
||||
const sourcemaps = require('gulp-sourcemaps');
|
||||
|
||||
const constants = require('./constants');
|
||||
const logHelper = require('../../infra/utils/log-helper');
|
||||
const pkgPathToName = require('./pkg-path-to-name');
|
||||
const rollupHelper = require('./rollup-helper');
|
||||
|
||||
/*
|
||||
* To test sourcemaps are valid and working, use:
|
||||
* http://paulirish.github.io/source-map-visualization/#custom-choose
|
||||
*/
|
||||
const ERROR_NO_MODULE_BROWSER = `Could not find the modules browser.mjs file: `;
|
||||
const ERROR_NO_NAMSPACE = oneLine`
|
||||
You must define a 'browserNamespace' parameter in the 'package.json'.
|
||||
Example: 'workbox-precaching' would have a browserNamespace param of
|
||||
'workbox.precaching' in 'package.json', meaning developers would use
|
||||
'workbox.precaching' in their JavaScript. Please fix for:
|
||||
`;
|
||||
|
||||
// This makes Rollup assume workbox-* will be added to the global
|
||||
// scope and replace references with the core namespace
|
||||
const globals = (moduleId) => {
|
||||
if (moduleId === 'workbox') {
|
||||
return moduleId;
|
||||
}
|
||||
|
||||
const splitImportPath = moduleId.split('/');
|
||||
if (splitImportPath[0].indexOf('workbox-') !== 0) {
|
||||
throw new Error(`Unknown global module ID: ${moduleId}`);
|
||||
}
|
||||
|
||||
const packageName = splitImportPath.shift();
|
||||
const packagePath = path.join(__dirname, '..', '..', 'packages', packageName);
|
||||
const namespacePathParts = splitImportPath.map((importPathPiece) => {
|
||||
// The browser namespace will need the file extension removed
|
||||
return path.basename(importPathPiece, path.extname(importPathPiece));
|
||||
});
|
||||
|
||||
let additionalNamespace;
|
||||
if (namespacePathParts.length === 0) {
|
||||
// Tried to pull in default export of module - this isn't allowed.
|
||||
// A specific file must be referenced
|
||||
throw new Error(oneLine`
|
||||
You cannot use a module directly - you must specify
|
||||
file, this is to force a best practice for tree shaking (i.e. only
|
||||
pulling in what you use). Please fix the import: '${moduleId}'
|
||||
`);
|
||||
}
|
||||
|
||||
if (namespacePathParts.length > 1) {
|
||||
if (namespacePathParts[0] !== '_private' || namespacePathParts.length > 2) {
|
||||
// Tried to pull in default export of module - this isn't allowed.
|
||||
// A specific file must be referenced
|
||||
throw new Error(oneLine`
|
||||
You cannot use nested files. It must be a top level (and public) file
|
||||
or a file under '_private' in a module. Please fix the import:
|
||||
'${moduleId}'
|
||||
`);
|
||||
}
|
||||
additionalNamespace = namespacePathParts[0];
|
||||
}
|
||||
|
||||
// Get a package's browserNamespace so we know where it will be
|
||||
// on the global scope (i.e. workbox.<name space>)
|
||||
try {
|
||||
const pkg = require(path.join(packagePath, 'package.json'));
|
||||
return [
|
||||
pkg.workbox.browserNamespace,
|
||||
additionalNamespace,
|
||||
]
|
||||
.filter((value) => {
|
||||
return (value && value.length > 0);
|
||||
})
|
||||
.join('.');
|
||||
} catch (err) {
|
||||
logHelper.error(`Unable to get browserNamespace for package: ` +
|
||||
`'${packageName}'`);
|
||||
logHelper.error(err);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
// This ensures all workbox-* modules are treated as external and are
|
||||
// referenced as globals.
|
||||
const externalAndPure = (importPath) => {
|
||||
return (importPath.indexOf('workbox-') === 0);
|
||||
};
|
||||
|
||||
module.exports = (packagePath, buildType) => {
|
||||
const packageName = pkgPathToName(packagePath);
|
||||
const moduleBrowserPath = path.join(packagePath, `browser.mjs`);
|
||||
|
||||
// First check if the bundle file exists, if it doesn't
|
||||
// there is nothing to build
|
||||
if (!fs.existsSync(moduleBrowserPath)) {
|
||||
logHelper.error(ERROR_NO_MODULE_BROWSER + packageName);
|
||||
return Promise.reject(ERROR_NO_MODULE_BROWSER + packageName);
|
||||
}
|
||||
|
||||
const pkgJson = require(path.join(packagePath, 'package.json'));
|
||||
if (!pkgJson.workbox || !pkgJson.workbox.browserNamespace) {
|
||||
logHelper.error(ERROR_NO_NAMSPACE + ' ' + packageName);
|
||||
return Promise.reject(ERROR_NO_NAMSPACE + ' ' + packageName);
|
||||
}
|
||||
|
||||
let outputFilename = `${packageName}.${buildType.slice(0, 4)}.js`;
|
||||
if (pkgJson.workbox.prodOnly) {
|
||||
// Bail out early if this is a non-prod build.
|
||||
if (buildType !== constants.BUILD_TYPES.prod) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
// If it is a prod build, then we don't have to bother including the
|
||||
// buildType in the filename, since there is no dev build.
|
||||
outputFilename = `${packageName}.js`;
|
||||
}
|
||||
|
||||
const namespace = pkgJson.workbox.browserNamespace;
|
||||
const outputDirectory = path.join(packagePath,
|
||||
constants.PACKAGE_BUILD_DIRNAME);
|
||||
|
||||
logHelper.log(oneLine`
|
||||
Building Browser Bundle for
|
||||
${logHelper.highlight(packageName)}.
|
||||
`);
|
||||
logHelper.log(` Namespace: ${logHelper.highlight(namespace)}`);
|
||||
logHelper.log(` Filename: ${logHelper.highlight(outputFilename)}`);
|
||||
|
||||
const plugins = rollupHelper.getDefaultPlugins(buildType);
|
||||
|
||||
const banner = pkgJson.workbox.includeBabelHelpers ?
|
||||
fs.readFileSync(path.join(__dirname, 'external-helpers.js')) :
|
||||
'';
|
||||
|
||||
return rollupStream({
|
||||
input: moduleBrowserPath,
|
||||
rollup,
|
||||
output: {
|
||||
name: namespace,
|
||||
sourcemap: true,
|
||||
format: 'iife',
|
||||
banner,
|
||||
globals,
|
||||
},
|
||||
external: externalAndPure,
|
||||
treeshake: {
|
||||
pureExternalModules: externalAndPure,
|
||||
},
|
||||
plugins,
|
||||
onwarn: (warning) => {
|
||||
if (buildType === constants.BUILD_TYPES.prod &&
|
||||
warning.code === 'UNUSED_EXTERNAL_IMPORT') {
|
||||
// This can occur when using rollup-plugin-replace.
|
||||
logHelper.warn(`[${warning.code}] ${warning.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// The final builds should have no warnings.
|
||||
if (warning.code && warning.message) {
|
||||
throw new Error(`Unhandled Rollup Warning: [${warning.code}] ` +
|
||||
`${warning.message}`);
|
||||
} else {
|
||||
throw new Error(`Unhandled Rollup Warning: ${warning}`);
|
||||
}
|
||||
},
|
||||
})
|
||||
.on('error', (err) => {
|
||||
const args = [];
|
||||
Object.keys(err).forEach((key) => {
|
||||
args.push(`${key}: ${err[key]}`);
|
||||
});
|
||||
logHelper.error(err, `\n\n${args.join('\n')}`);
|
||||
throw err;
|
||||
})
|
||||
// We must give the generated stream the same name as the entry file
|
||||
// for the sourcemaps to work correctly
|
||||
.pipe(source(moduleBrowserPath))
|
||||
// gulp-sourcemaps don't work with streams so we need
|
||||
.pipe(buffer())
|
||||
// This tells gulp-sourcemaps to load the inline sourcemap
|
||||
.pipe(sourcemaps.init({loadMaps: true}))
|
||||
// This renames the output file
|
||||
.pipe(rename(outputFilename))
|
||||
// This writes the sourcemap alongside the final build file
|
||||
.pipe(sourcemaps.write('.'))
|
||||
.pipe(gulp.dest(outputDirectory));
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
const babel = require('gulp-babel');
|
||||
const gulp = require('gulp');
|
||||
const oneLine = require('common-tags').oneLine;
|
||||
const path = require('path');
|
||||
|
||||
const constants = require('./constants');
|
||||
const logHelper = require('../../infra/utils/log-helper');
|
||||
const pkgPathToName = require('./pkg-path-to-name');
|
||||
|
||||
module.exports = (packagePath) => {
|
||||
const packageName = pkgPathToName(packagePath);
|
||||
const outputDirectory = path.join(packagePath,
|
||||
constants.PACKAGE_BUILD_DIRNAME);
|
||||
|
||||
logHelper.log(oneLine`
|
||||
Building Node Package for
|
||||
${logHelper.highlight(packageName)}.
|
||||
`);
|
||||
|
||||
return gulp.src(`${packagePath}/src/**`).pipe(babel({
|
||||
only: /\.js$/,
|
||||
presets: [
|
||||
['env', {
|
||||
targets: {
|
||||
// Change this when our minimum required node version changes.
|
||||
node: '4.0',
|
||||
},
|
||||
}],
|
||||
],
|
||||
plugins: [
|
||||
// This ensures that the helpers used by Babel to accompany transpilation
|
||||
// are only included in our Rollup bundles once, even if they're used
|
||||
// in multiple source files.
|
||||
// See https://github.com/rollup/rollup-plugin-babel#helpers
|
||||
'transform-runtime',
|
||||
],
|
||||
})).pipe(gulp.dest(outputDirectory));
|
||||
};
|
||||
@@ -0,0 +1,98 @@
|
||||
const path = require('path');
|
||||
const glob = require('glob');
|
||||
const fs = require('fs-extra');
|
||||
const oneLine = require('common-tags').oneLine;
|
||||
const storage = require('@google-cloud/storage');
|
||||
|
||||
const cdnDetails = require('../../cdn-details.json');
|
||||
const logHelper = require('../../infra/utils/log-helper');
|
||||
|
||||
const PROJECT_ID = 'workbox-bab1f';
|
||||
const SERVICE_ACCOUNT_PATH = path.join(__dirname, '..', '..',
|
||||
`workbox-9d39634504ad.json`);
|
||||
|
||||
const ERROR_SERVICE_ACCOUNT = oneLine`
|
||||
Unable to find the service account file that is required to upload
|
||||
to the CDN.
|
||||
Please get the service account file and put it at the root of the
|
||||
workbox repo.
|
||||
|
||||
File Path Tested: '${SERVICE_ACCOUNT_PATH}'.
|
||||
`;
|
||||
|
||||
class CDNHelper {
|
||||
constructor() {
|
||||
this._gcs = null;
|
||||
}
|
||||
|
||||
_getReleaseTagPath(tagName) {
|
||||
return `${cdnDetails.releasesDir}/${tagName}`;
|
||||
}
|
||||
|
||||
getGCS() {
|
||||
if (!this._gcs) {
|
||||
try {
|
||||
fs.accessSync(SERVICE_ACCOUNT_PATH);
|
||||
} catch (err) {
|
||||
throw new Error(ERROR_SERVICE_ACCOUNT);
|
||||
}
|
||||
|
||||
this._gcs = storage({
|
||||
projectId: PROJECT_ID,
|
||||
keyFilename: SERVICE_ACCOUNT_PATH,
|
||||
});
|
||||
}
|
||||
|
||||
return this._gcs;
|
||||
}
|
||||
|
||||
async tagExists(tagName) {
|
||||
const gcs = this.getGCS();
|
||||
try {
|
||||
const bucket = gcs.bucket(cdnDetails.bucketName);
|
||||
// bucket.file('some/path/').exists() doesn't seem to work
|
||||
// for nested directories. Instead we are checking if there are
|
||||
// files in the expected release directory.
|
||||
const response = await bucket.getFiles({
|
||||
prefix: `${this._getReleaseTagPath(tagName)}/`,
|
||||
});
|
||||
const files = response[0];
|
||||
return files.length > 0;
|
||||
} catch (err) {
|
||||
logHelper.error(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async upload(tagName, directoryToUpload) {
|
||||
const gcs = this.getGCS();
|
||||
|
||||
const globPattern = path.posix.join(directoryToUpload, '*');
|
||||
const filePaths = glob.sync(globPattern, {
|
||||
absolute: true,
|
||||
});
|
||||
|
||||
const publicUrls = [];
|
||||
const bucket = gcs.bucket(cdnDetails.bucketName);
|
||||
for (let filePath of filePaths) {
|
||||
const destination =
|
||||
`${this._getReleaseTagPath(tagName)}/${path.basename(filePath)}`;
|
||||
|
||||
try {
|
||||
await bucket.upload(filePath, {
|
||||
destination,
|
||||
public: true,
|
||||
});
|
||||
} catch (err) {
|
||||
logHelper.error(`Failed to upload file to GCS bucket: '${filePath}'`);
|
||||
throw err;
|
||||
}
|
||||
publicUrls.push(
|
||||
`${cdnDetails.origin}/${cdnDetails.bucketName}/${destination}`
|
||||
);
|
||||
}
|
||||
return publicUrls;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new CDNHelper();
|
||||
@@ -0,0 +1,22 @@
|
||||
module.exports = {
|
||||
// This is a directory that should not be commited
|
||||
// to git and will be removed and rebuilt between
|
||||
// test runs.
|
||||
PACKAGE_BUILD_DIRNAME: 'build',
|
||||
GENERATED_RELEASE_FILES_DIRNAME: 'generated-release-files',
|
||||
|
||||
// This is used in the publish-bundle step to avoid doing anything
|
||||
// with tags < v3.0.0.
|
||||
MIN_RELEASE_TAG_TO_PUBLISH: 'v3.0.0',
|
||||
GITHUB_OWNER: 'GoogleChrome',
|
||||
GITHUB_REPO: 'workbox',
|
||||
|
||||
// This is the environments that we should use for NODE_ENV.
|
||||
BUILD_TYPES: {
|
||||
dev: 'dev',
|
||||
prod: 'production',
|
||||
},
|
||||
|
||||
// This is used in demos directory when referencing local files
|
||||
LOCAL_BUILDS_DIR: 'local-builds',
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
/* eslint-disable */
|
||||
// This is extracted from the Babel runtime (original source: https://github.com/babel/babel/blob/9e0f5235b1ca5167c368a576ad7c5af62d20b0e3/packages/babel-helpers/src/helpers.js#L240).
|
||||
// As part of the Rollup bundling process, it's injected once into workbox-core
|
||||
// and reused throughout all of the other modules, avoiding code duplication.
|
||||
// See https://github.com/GoogleChrome/workbox/pull/1048#issuecomment-344698046
|
||||
// for further background.
|
||||
self.babelHelpers = {
|
||||
asyncToGenerator: function(fn) {
|
||||
return function() {
|
||||
var gen = fn.apply(this, arguments);
|
||||
return new Promise(function(resolve, reject) {
|
||||
function step(key, arg) {
|
||||
try {
|
||||
var info = gen[key](arg);
|
||||
var value = info.value;
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (info.done) {
|
||||
resolve(value);
|
||||
} else {
|
||||
return Promise.resolve(value).then(function(value) {
|
||||
step('next', value);
|
||||
}, function(err) {
|
||||
step('throw', err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return step('next');
|
||||
});
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
module.exports = () => process.platform === 'win32' ?
|
||||
'npm.cmd' : 'npm';
|
||||
@@ -0,0 +1,14 @@
|
||||
const glob = require('glob');
|
||||
const path = require('path');
|
||||
|
||||
module.exports = (root, type) => {
|
||||
const pathToPkgJsons = glob.sync('packages/*/package.json', {cwd: root});
|
||||
return pathToPkgJsons.filter((pathToPkgJson) => {
|
||||
const pkg = require(`${path.resolve(root)}/${pathToPkgJson}`);
|
||||
return pkg.workbox && (pkg.workbox.packageType === type);
|
||||
}).map((pathToPkgJson) => {
|
||||
// Since we matched the glob pattern then we know that this will always
|
||||
// return the name of the package.
|
||||
return pathToPkgJson.split('/')[1];
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,70 @@
|
||||
const GitHubApi = require('github');
|
||||
const semver = require('semver');
|
||||
|
||||
const constants = require('./constants');
|
||||
|
||||
const github = new GitHubApi();
|
||||
|
||||
// github.authenticate() is synchronous, and it only stores the credentials for
|
||||
// the next request, so it should be called once per method that requires auth.
|
||||
// See https://github.com/mikedeboer/node-github#authentication
|
||||
const authenticate = () => {
|
||||
if (!process.env.GITHUB_TOKEN) {
|
||||
throw new Error('You must set a GITHUB_TOKEN in your environment to ' +
|
||||
'public a Github release.');
|
||||
}
|
||||
|
||||
github.authenticate({
|
||||
type: 'token',
|
||||
token: process.env.GITHUB_TOKEN,
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
createRelease: (args) => {
|
||||
authenticate();
|
||||
args.owner = constants.GITHUB_OWNER;
|
||||
args.repo = constants.GITHUB_REPO;
|
||||
return github.repos.createRelease(args);
|
||||
},
|
||||
|
||||
uploadAsset: (args) => {
|
||||
authenticate();
|
||||
args.owner = constants.GITHUB_OWNER;
|
||||
args.repo = constants.GITHUB_REPO;
|
||||
return github.repos.uploadAsset(args);
|
||||
},
|
||||
|
||||
getTaggedReleases: async () => {
|
||||
authenticate();
|
||||
const releasesData = await github.repos.getReleases({
|
||||
owner: constants.GITHUB_OWNER,
|
||||
repo: constants.GITHUB_REPO,
|
||||
});
|
||||
|
||||
const releases = releasesData.data;
|
||||
const releasesByTags = {};
|
||||
releases.forEach((release) => {
|
||||
const tagName = release.tag_name;
|
||||
if (semver.gte(tagName, constants.MIN_RELEASE_TAG_TO_PUBLISH)) {
|
||||
releasesByTags[tagName] = release;
|
||||
}
|
||||
});
|
||||
|
||||
return releasesByTags;
|
||||
},
|
||||
|
||||
getTags: async () => {
|
||||
authenticate();
|
||||
const tagsResponse = await github.repos.getTags({
|
||||
owner: constants.GITHUB_OWNER,
|
||||
repo: constants.GITHUB_REPO,
|
||||
});
|
||||
|
||||
// We only want tags that are v3.0.0 or above.
|
||||
const tagsData = tagsResponse.data;
|
||||
return tagsData.filter((tagData) => {
|
||||
return semver.gte(tagData.name, constants.MIN_RELEASE_TAG_TO_PUBLISH);
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
const path = require('path');
|
||||
|
||||
const spawnPromiseWrapper = require('./spawn-promise-wrapper');
|
||||
|
||||
// Use require.resolve() to find the lerna module, then get the lerna
|
||||
// binary from that. This allows the use of lerna with command line
|
||||
// flags.
|
||||
const lernaBinPath = path.join(
|
||||
require.resolve('lerna'), '..', '..', 'bin', 'lerna.js');
|
||||
|
||||
module.exports = {
|
||||
bootstrap: (...args) => {
|
||||
// Have to run with node in front for Windows.
|
||||
return spawnPromiseWrapper('node', [lernaBinPath, 'bootstrap', ...args]);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,130 @@
|
||||
const path = require('path');
|
||||
const glob = require('glob');
|
||||
const oneLine = require('common-tags').oneLine;
|
||||
|
||||
const pkgPathToName = require('./pkg-path-to-name');
|
||||
|
||||
/**
|
||||
* @param {string} typeFilter The type of packages to return: 'node', 'browser',
|
||||
* or 'all'.
|
||||
* @return Array<string> Paths to package.json files for the matching packages.
|
||||
*/
|
||||
function getPackages(typeFilter) {
|
||||
return glob.sync(
|
||||
`packages/${global.packageOrStar}/package.json`, {
|
||||
absolute: true,
|
||||
}
|
||||
).filter((pathToPackageJson) => {
|
||||
const pkgInfo = require(pathToPackageJson);
|
||||
const packageType = pkgInfo.workbox.packageType;
|
||||
if (!packageType) {
|
||||
throw Error(oneLine`Unable to determine package type. Please add
|
||||
workbox.packageType metadata to ${pathToPackageJson}`);
|
||||
}
|
||||
|
||||
return typeFilter === 'all' || typeFilter === packageType;
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* This methods only purpose is to call a function with
|
||||
* the package path that needs to be acted upon.
|
||||
*
|
||||
* The specific function might vary depending on whether a give project targets
|
||||
* Node or the browser, so you can specify 'browser', 'node', or 'all'.
|
||||
*
|
||||
* If we ran gulp as `gulp build` we would want the
|
||||
* 'build' task to run against all packages.
|
||||
*
|
||||
* If we ran gulp as `gulp build --project workbox-core`
|
||||
* we would want the 'build' task to run against `workbox-core`
|
||||
* **only**.
|
||||
*
|
||||
* This method simplifies how we would write the 'build'
|
||||
* task and should be the only way we write functions.
|
||||
*
|
||||
* ```javascript
|
||||
* gulp.task('build',
|
||||
* gulp.series(
|
||||
* packageRunner(displayName, 'all', func, arg1, arg2)
|
||||
* )
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* Package runner will return an array of functions that
|
||||
* will call `func` where the first argument
|
||||
* is the absolute path to a package,
|
||||
* like `/user/matt/workbox/packages/workbox-core` and
|
||||
* the remaining arguments will be whatever is passed to
|
||||
* packageRunner, in the above sample this would be
|
||||
* `arg1` and `arg2`.
|
||||
*
|
||||
* In the example above, `func` would be written as:
|
||||
*
|
||||
* ```javascript
|
||||
* function functionForEachProjectType(packagePath, arg1, arg2) {
|
||||
* // Return a Promise
|
||||
* return Promise.resolve().then(() => ...)
|
||||
*
|
||||
* // OR
|
||||
*
|
||||
* // Return a gulp stream
|
||||
* return gulp.src(path.posix.join(packagePath, 'example-dir', '*.js'))
|
||||
* .pipe(...)
|
||||
* .pipe(gulp.dest(...));
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* You can use this with both `gulp.series` and
|
||||
* `gulp.parallel`.
|
||||
*
|
||||
* ```javascript
|
||||
* gulp.series(packageRunner(displayName, 'all', func))
|
||||
* gulp.parallel(packageRunner(displayName, 'all', func))
|
||||
* ```
|
||||
*
|
||||
* If you need to call the runner with multiple functions
|
||||
* you can do this and gulp will merge the arrays returned
|
||||
* by the runner.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* ```javascript
|
||||
* gulp.parallel(
|
||||
* packageRunner(displayName, 'browser', func, true),
|
||||
* packageRunner(displayName, 'browser', func, false),
|
||||
* )
|
||||
* ```
|
||||
*
|
||||
* If we run the above, it would call the appropriate
|
||||
* `func` for all packages in workbox twice, once with
|
||||
* argument `true` and once with `false`. `gulp.parallel` will run all of these
|
||||
* calls to `func` in parallel.
|
||||
*
|
||||
* @param {string} displayName A friendly name to log.
|
||||
* @param Array<string> The type of package we want to run func against:
|
||||
* 'node', 'browser', or 'all'.
|
||||
* @param {function} func The function that we want to run for each package.
|
||||
* @param {*} args Any arguments that should be passed in to the func.
|
||||
* @return Array<function> All of the function wrappers.
|
||||
*/
|
||||
module.exports = (displayName, packageType, func, ...args) => {
|
||||
const packagePaths = getPackages(packageType);
|
||||
// We need to return a single no-op rather than an empty array, or else gulp
|
||||
// will throw 'One or more tasks should be combined using series or parallel'.
|
||||
if (packagePaths.length === 0) {
|
||||
const noOp = (done) => done();
|
||||
noOp.displayName = displayName;
|
||||
return [noOp];
|
||||
}
|
||||
return packagePaths.map((packagePath) => {
|
||||
const packageRootPath = path.dirname(packagePath);
|
||||
|
||||
const wrappedFunc = () => func(packageRootPath, ...args);
|
||||
wrappedFunc.displayName = oneLine`
|
||||
${displayName}
|
||||
(${pkgPathToName(packageRootPath)})
|
||||
${args.length === 0 ? '' : JSON.stringify(args)}`;
|
||||
return wrappedFunc;
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
const path = require('path');
|
||||
|
||||
const packagesPath = path.join(__dirname, '..', '..', 'packages');
|
||||
|
||||
// A helper method that should be used when you want to log
|
||||
// the package name ONLY.
|
||||
module.exports = (pkgPath) => {
|
||||
return path.relative(packagesPath, pkgPath);
|
||||
};
|
||||
@@ -0,0 +1,179 @@
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const glob = require('glob');
|
||||
const archiver = require('archiver');
|
||||
const oneLine = require('common-tags').oneLine;
|
||||
|
||||
const constants = require('./constants');
|
||||
const getPackagesOfType = require('./get-packages-of-type');
|
||||
const logHelper = require('../../infra/utils/log-helper');
|
||||
const spawn = require('./spawn-promise-wrapper');
|
||||
|
||||
const SOURCE_CODE_DIR = 'source-code';
|
||||
const GROUPED_BUILD_FILES = 'grouped-build-files';
|
||||
|
||||
const doesDirectoryExist = async (directoryPath) => {
|
||||
let stats = null;
|
||||
try {
|
||||
stats = await fs.stat(directoryPath);
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
return stats.isDirectory();
|
||||
};
|
||||
|
||||
const getBuildPath = (tagName) => {
|
||||
const tempReleasePath = path.join(
|
||||
__dirname, '..', '..', constants.GENERATED_RELEASE_FILES_DIRNAME);
|
||||
return path.join(tempReleasePath, tagName);
|
||||
};
|
||||
|
||||
const downloadGitCommit = async (tagName, gitBranch) => {
|
||||
if (!tagName) {
|
||||
throw new Error(`You must provide a tagName to 'downloadGitCommit()`);
|
||||
}
|
||||
|
||||
if (!gitBranch) {
|
||||
throw new Error(`You must provide a gitBranch to 'downloadGitCommit()`);
|
||||
}
|
||||
|
||||
const sourceCodePath = path.join(getBuildPath(tagName), SOURCE_CODE_DIR);
|
||||
|
||||
logHelper.log(oneLine`
|
||||
Download Git Commit ${logHelper.highlight(gitBranch)}.
|
||||
`);
|
||||
|
||||
const dirExists = await doesDirectoryExist(sourceCodePath);
|
||||
if (!dirExists) {
|
||||
logHelper.log(` Clone Git repo for branch: '${gitBranch}'.`);
|
||||
|
||||
await spawn('git', [
|
||||
'clone',
|
||||
'--branch', gitBranch,
|
||||
'--depth', '1',
|
||||
`http://github.com/${constants.GITHUB_OWNER}/${constants.GITHUB_REPO}.git`,
|
||||
sourceCodePath,
|
||||
]);
|
||||
} else {
|
||||
logHelper.log(` Git repo for branch '${gitBranch}' is cloned already.`);
|
||||
}
|
||||
|
||||
return sourceCodePath;
|
||||
};
|
||||
|
||||
const buildGitCommit = async (tagName) => {
|
||||
const sourceCodePath = path.join(getBuildPath(tagName), SOURCE_CODE_DIR);
|
||||
|
||||
logHelper.log(oneLine`
|
||||
Building Commit
|
||||
${logHelper.highlight(path.relative(process.cwd(), sourceCodePath))}.
|
||||
`);
|
||||
|
||||
await spawn('npm', ['install'], {
|
||||
cwd: sourceCodePath,
|
||||
});
|
||||
|
||||
await spawn('gulp', ['build'], {
|
||||
cwd: sourceCodePath,
|
||||
});
|
||||
|
||||
// This is to try and fix Github and CDN steps from having file read / close
|
||||
// issues by removing any risk of spawn tasks being out of sync
|
||||
return new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
};
|
||||
|
||||
/*
|
||||
* This function will create a directory with the same name as the
|
||||
* .tar.gz file it generates. This way when the file is extracted
|
||||
* the folder structure will be the same.
|
||||
*/
|
||||
const groupBuildFiles = async (tagName, gitBranch) => {
|
||||
const groupedBuildFiles =
|
||||
path.join(getBuildPath(tagName), GROUPED_BUILD_FILES);
|
||||
const dirExists = await doesDirectoryExist(groupedBuildFiles);
|
||||
|
||||
if (!dirExists) {
|
||||
await downloadGitCommit(tagName, gitBranch);
|
||||
await buildGitCommit(tagName);
|
||||
|
||||
const sourceCodePath = path.join(getBuildPath(tagName), SOURCE_CODE_DIR);
|
||||
|
||||
const browserPackages = getPackagesOfType(sourceCodePath, 'browser');
|
||||
|
||||
const pattern = path.posix.join(
|
||||
sourceCodePath, 'packages', `{${browserPackages.join(',')}}`,
|
||||
constants.PACKAGE_BUILD_DIRNAME, '*.{js,map}');
|
||||
|
||||
logHelper.log(oneLine`
|
||||
Grouping Build Files into
|
||||
${logHelper.highlight(path.relative(process.cwd(), groupedBuildFiles))}.
|
||||
`);
|
||||
|
||||
// Copy files from the source code and move into the grouped build
|
||||
// directory. In others, have a flat file structure of just the built files.
|
||||
const filesToIncludeInBundle = glob.sync(pattern);
|
||||
for (const fileToInclude of filesToIncludeInBundle) {
|
||||
await fs.copy(
|
||||
fileToInclude,
|
||||
path.join(groupedBuildFiles, path.basename(fileToInclude)),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
logHelper.log(` Build files already grouped.`);
|
||||
}
|
||||
|
||||
return groupedBuildFiles;
|
||||
};
|
||||
|
||||
const createArchive = async (tagName, fileExtension, format, options) => {
|
||||
const archiveFilesPath = path.join(getBuildPath(tagName), 'archives');
|
||||
const archiveFilename = `workbox-${tagName}.${fileExtension}`;
|
||||
const outputFilePath = path.join(archiveFilesPath, archiveFilename);
|
||||
|
||||
logHelper.log(oneLine`
|
||||
Creating ${format} and saving to
|
||||
${logHelper.highlight(path.relative(process.cwd(), outputFilePath))}.
|
||||
`);
|
||||
|
||||
fs.ensureDirSync(path.dirname(outputFilePath));
|
||||
|
||||
const groupedBuildFiles =
|
||||
path.join(getBuildPath(tagName), GROUPED_BUILD_FILES);
|
||||
|
||||
const writeStream = fs.createWriteStream(outputFilePath);
|
||||
const archive = archiver(format, options);
|
||||
archive.pipe(writeStream);
|
||||
// Adds the directory contents to the zip.
|
||||
archive.directory(groupedBuildFiles, false);
|
||||
await archive.finalize();
|
||||
|
||||
return outputFilePath;
|
||||
};
|
||||
|
||||
const createTarGz = (tagName) => {
|
||||
return createArchive(tagName, 'tar.gz', 'tar', {
|
||||
gzip: true,
|
||||
});
|
||||
};
|
||||
|
||||
const createZip = (tagName) => {
|
||||
return createArchive(tagName, 'zip', 'zip', {
|
||||
zlib: {level: 9},
|
||||
});
|
||||
};
|
||||
|
||||
const createBundles = async (tagName, gitBranch) => {
|
||||
await groupBuildFiles(tagName, gitBranch);
|
||||
|
||||
const tarPath = await createTarGz(tagName);
|
||||
const zipPath = await createZip(tagName);
|
||||
return {
|
||||
tarPath,
|
||||
zipPath,
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
groupBuildFiles,
|
||||
createBundles,
|
||||
};
|
||||
@@ -0,0 +1,77 @@
|
||||
const babel = require('rollup-plugin-babel');
|
||||
const minify = require('uglify-es').minify;
|
||||
const replace = require('rollup-plugin-replace');
|
||||
const uglifyPlugin = require('rollup-plugin-uglify');
|
||||
|
||||
const constants = require('./constants');
|
||||
const getVersionsCDNUrl = require('./versioned-cdn-url');
|
||||
|
||||
module.exports = {
|
||||
// Every use of rollup should have minification and the replace
|
||||
// plugin set up and used to ensure as consist set of tests
|
||||
// as possible.
|
||||
getDefaultPlugins: (buildType) => {
|
||||
const plugins = [];
|
||||
|
||||
const babelConfig = {
|
||||
presets: [['env', {
|
||||
targets: {
|
||||
// This corresponds to the version of Chroumium used by
|
||||
// Samsung Internet 5.x, which is the minimum non-evergreen browser
|
||||
// we currently support.
|
||||
browsers: ['chrome >= 51'],
|
||||
},
|
||||
modules: false,
|
||||
}]],
|
||||
// This will result in references to `babelHelpers.asyncToGenerators()`
|
||||
// in the transpiled code. The declaration of this method lives in the
|
||||
// external-helpers.js file and is injected by Rollup once into the
|
||||
// workbox-core bundle.
|
||||
plugins: ['external-helpers'],
|
||||
externalHelpers: true,
|
||||
};
|
||||
plugins.push(babel(babelConfig));
|
||||
|
||||
let minifyBuild = buildType === constants.BUILD_TYPES.prod;
|
||||
if (minifyBuild) {
|
||||
const uglifyOptions = {
|
||||
mangle: {
|
||||
properties: {
|
||||
reserved: [
|
||||
// Chai will break unless we reserve this private variable.
|
||||
'_obj',
|
||||
// We need this to be exported correctly.
|
||||
'_private',
|
||||
],
|
||||
// mangle > properties > regex will allow uglify-es to minify
|
||||
// private variable and names that start with a single underscore
|
||||
// followed by a letter. This restriction to avoid mangling
|
||||
// unintentional fields in our or other libraries code.
|
||||
regex: /^_[A-Za-z]/,
|
||||
// If you are getting an error due to a property mangle
|
||||
// set this flag to true and the property will be changed
|
||||
// from '_foo' to '$_foo$' to help diagnose the problem.
|
||||
debug: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
plugins.push(
|
||||
uglifyPlugin(uglifyOptions, minify),
|
||||
);
|
||||
}
|
||||
|
||||
// This is what the build should be
|
||||
const replaceOptions = {
|
||||
'WORKBOX_CDN_ROOT_URL': getVersionsCDNUrl(),
|
||||
};
|
||||
|
||||
if (buildType) {
|
||||
replaceOptions['process.env.NODE_ENV'] = JSON.stringify(buildType);
|
||||
}
|
||||
|
||||
// Replace allows us to input NODE_ENV and strip code accordingly
|
||||
plugins.push(replace(replaceOptions));
|
||||
|
||||
return plugins;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
const spawn = require('child_process').spawn;
|
||||
|
||||
module.exports = (command, args, options = {}) => {
|
||||
options.stdio = options.stdio || 'inherit';
|
||||
return new Promise((resolve, reject) => {
|
||||
const process = spawn(command, args, options);
|
||||
process.on('error', reject);
|
||||
process.on('close', (code) => {
|
||||
if (code === 0) {
|
||||
resolve();
|
||||
} else {
|
||||
reject(`Error ${code} returned from ${command} ${args}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
|
||||
const pkgPathToName = require('./pkg-path-to-name');
|
||||
|
||||
const getDetails = (packagePath) => {
|
||||
// Read file from filesystem to avoid require caching
|
||||
const packageJsonPath = path.join(packagePath, 'package.json');
|
||||
const pkgJson = fs.readJSONSync(packageJsonPath);
|
||||
const name = pkgPathToName(packagePath).replace('workbox-', '');
|
||||
return ['workbox', name, pkgJson.version].join(':');
|
||||
};
|
||||
|
||||
module.exports = (packagePath, buildType) => {
|
||||
const versionString =
|
||||
`try{self.workbox.v['${getDetails(packagePath)}']=1;}catch(e){} ` +
|
||||
`// eslint-disable-line`;
|
||||
return fs.writeFile(path.join(packagePath, '_version.mjs'), versionString);
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
const {getCDNOrigin} = require(
|
||||
'../../packages/workbox-build/src/lib/cdn-utils');
|
||||
const lernaPkg = require('../../lerna.json');
|
||||
|
||||
module.exports = () => `${getCDNOrigin()}/${lernaPkg.version}`;
|
||||
@@ -0,0 +1,21 @@
|
||||
const gulp = require('gulp');
|
||||
|
||||
const logHelper = require('../infra/utils/log-helper');
|
||||
|
||||
gulp.task('watch', gulp.series(
|
||||
'build',
|
||||
() => {
|
||||
gulp.watch(
|
||||
[
|
||||
`packages/${global.packageOrStar}/**/*.mjs`,
|
||||
'!packages/**/_version.mjs',
|
||||
'!packages/**/build/**/*',
|
||||
'!packages/**/node_modules/**/*',
|
||||
], gulp.series('build'),
|
||||
)
|
||||
.on('error', () => {})
|
||||
.on('change', function(path, stats) {
|
||||
logHelper.log(`gulp.watch() is running due to a change in '${path}'`);
|
||||
});
|
||||
}
|
||||
));
|
||||
+31
-9
@@ -21,21 +21,43 @@ const path = require('path');
|
||||
|
||||
const options = minimist(process.argv.slice(2));
|
||||
|
||||
if (options.project) {
|
||||
if (options.package) {
|
||||
// Ensure the project is valid before running tasks
|
||||
try {
|
||||
fs.statSync(path.join(__dirname, 'packages', options.project));
|
||||
fs.statSync(path.posix.join(__dirname, 'packages', options.package));
|
||||
} catch (err) {
|
||||
throw new Error(`The supplied project '${options.project}' is invalid.`);
|
||||
throw new Error(`The supplied project '${options.package}' is invalid.`);
|
||||
}
|
||||
}
|
||||
|
||||
global.port = options.port || 3000;
|
||||
global.projectOrStar = options.project || '*';
|
||||
global.packageOrStar = options.package || '*';
|
||||
global.cliOptions = options;
|
||||
|
||||
require('./gulp-tasks/lint.js');
|
||||
require('./gulp-tasks/lerna.js');
|
||||
require('./gulp-tasks/build.js');
|
||||
require('./gulp-tasks/test.js');
|
||||
require('./gulp-tasks/serve.js');
|
||||
// Forward referencing means the order of gulp-task
|
||||
// requires is important
|
||||
const gulpTaskFiles = [
|
||||
'build-node-packages',
|
||||
'build-browser-packages',
|
||||
'build-packages',
|
||||
'build',
|
||||
'lint',
|
||||
'test-node',
|
||||
'test-integration',
|
||||
'test',
|
||||
'test-server',
|
||||
'analyze-properties',
|
||||
'publish-github',
|
||||
'publish-cdn',
|
||||
'publish-lerna',
|
||||
'publish-demos',
|
||||
'publish',
|
||||
'watch',
|
||||
'docs',
|
||||
'demos',
|
||||
];
|
||||
|
||||
gulpTaskFiles.forEach((gulpTaskFile) => {
|
||||
// Requiring will be enough to register the tasks with gulp.
|
||||
require(path.join(__dirname, 'gulp-tasks', gulpTaskFile));
|
||||
});
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
const PluginInterface = require('pr-bot').PluginInterface;
|
||||
const bytes = require('bytes');
|
||||
const fs = require('fs-extra');
|
||||
const gzipSize = require('gzip-size');
|
||||
const oneLine = require('common-tags').oneLine;
|
||||
const path = require('path');
|
||||
|
||||
// 15 KB max size
|
||||
const MAX_SIZE = 15 * 1000;
|
||||
|
||||
class AggregateSizePlugin extends PluginInterface {
|
||||
constructor() {
|
||||
super(`Workbox Aggregate Size Plugin`);
|
||||
}
|
||||
|
||||
run({afterPath} = {}) {
|
||||
const packagesToAggregate = [
|
||||
`workbox-cache-expiration`,
|
||||
`workbox-cacheable-response`,
|
||||
`workbox-core`,
|
||||
`workbox-precaching`,
|
||||
`workbox-routing`,
|
||||
`workbox-strategies`,
|
||||
`workbox-sw`,
|
||||
];
|
||||
const files = packagesToAggregate.map((pkgName) => {
|
||||
const prefix = `${afterPath}/packages/${pkgName}/`;
|
||||
const pkgJson = require(`${prefix}package.json`);
|
||||
const posixPath = prefix + pkgJson.main;
|
||||
return posixPath.split('/').join(path.sep);
|
||||
});
|
||||
|
||||
let totalSize = 0;
|
||||
let totalGzipSize = 0;
|
||||
files.forEach((filePath) => {
|
||||
const fileContents = fs.readFileSync(filePath);
|
||||
const stat = fs.statSync(filePath);
|
||||
totalSize += stat.size;
|
||||
totalGzipSize += gzipSize.sync(fileContents);
|
||||
});
|
||||
|
||||
const percentValue = (totalSize / MAX_SIZE) * 100;
|
||||
const percentString = parseFloat(percentValue).toFixed(0);
|
||||
|
||||
let totalSizeString = bytes(totalSize);
|
||||
let totalGzipString = bytes(totalGzipSize);
|
||||
|
||||
let markdownWarning = ``;
|
||||
if (percentValue >= 90) {
|
||||
const markdownMoji = percentValue >= 95 ? '☠️' : '⚠️';
|
||||
markdownWarning = oneLine`
|
||||
<h3 align="center">${markdownMoji} WARNING ${markdownMoji}</h3>
|
||||
<p align="center">
|
||||
We are using <strong>${percentString}%</strong>
|
||||
of our max size budget.
|
||||
</p>
|
||||
`;
|
||||
}
|
||||
|
||||
// TODO: Enable *if* we can get under target size.
|
||||
const failPR = false; // totalSize >= MAX_SIZE;
|
||||
|
||||
const markdownLog = `${markdownWarning}\n\n`+
|
||||
`**Total Size:** ${totalSizeString}\n` +
|
||||
`**Percentage of Size Used:** ${percentString}%\n\n` +
|
||||
`**Gzipped:** ${totalGzipString}`;
|
||||
const prettyLog =
|
||||
`Total Size: ${totalSizeString}\n` +
|
||||
`**Percentage of Size Used:** ${percentString}%\n\n` +
|
||||
`Gzipped: ${totalGzipString}`;
|
||||
return Promise.resolve({
|
||||
prettyLog,
|
||||
markdownLog,
|
||||
failPR,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AggregateSizePlugin;
|
||||
@@ -0,0 +1,58 @@
|
||||
# Copyright 2017 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
constructor:
|
||||
prefix: 'new '
|
||||
returnTypesSeparator: ' returns '
|
||||
tables:
|
||||
body:
|
||||
defaultValue: 'Defaults to {valueString}.'
|
||||
defaultValueString: '<code>{value}</code>'
|
||||
eachValueHasProperties: 'Values in <code>{name}</code> have the following properties:'
|
||||
isOptional: 'Optional'
|
||||
isRequired: ' '
|
||||
nonNullable:
|
||||
long: 'Value must not be null.'
|
||||
short: 'non-null'
|
||||
nullable:
|
||||
long: 'Value may be null.'
|
||||
short: 'nullable'
|
||||
repeatable:
|
||||
long: 'Value may be repeated.'
|
||||
short: 'repeatable'
|
||||
unknownType: '?'
|
||||
emptyCellPlaceholder: ' '
|
||||
header:
|
||||
description: 'Description'
|
||||
name: 'Name'
|
||||
optional: 'Optional'
|
||||
type: 'Type'
|
||||
notApplicable: '—'
|
||||
params:
|
||||
all: '({params})'
|
||||
joiner: |
|
||||
{items, plural,
|
||||
=0 {{param}}
|
||||
other {, {param}}}
|
||||
optional: '{param}'
|
||||
repeatable: '...{param}'
|
||||
headings:
|
||||
enums: |
|
||||
{items, plural,
|
||||
=1 {Enumeration}
|
||||
other {Enumerations}}
|
||||
values: |
|
||||
{items, plural,
|
||||
=1 {Value}
|
||||
other {Values}}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* Copyright 2017 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-env node */
|
||||
/* eslint-disable valid-jsdoc, require-jsdoc */
|
||||
|
||||
const name = require('jsdoc/name');
|
||||
const BaselinePublishJob = require('jsdoc-baseline/lib/publishjob');
|
||||
|
||||
class PublishJob extends BaselinePublishJob {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this._symbolNames = {};
|
||||
this._data;
|
||||
}
|
||||
|
||||
generateTocYaml(symbols, basepath) {
|
||||
symbols.sort(function(a, b) {
|
||||
let aName = a.kind === 'namespace' ? a.longname : a.name;
|
||||
let bName = b.kind === 'namespace' ? b.longname : b.name;
|
||||
|
||||
aName = aName.toLowerCase();
|
||||
bName = bName.toLowerCase();
|
||||
|
||||
if (aName > bName) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (aName < bName) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
// check for duplicated names
|
||||
symbols.forEach((symbol) => {
|
||||
const symbolName = symbol.kind === 'namespace' ?
|
||||
symbol.longname : symbol.name;
|
||||
if ({}.hasOwnProperty.call(this._symbolNames, symbolName)) {
|
||||
this._symbolNames[symbolName] = true;
|
||||
} else {
|
||||
this._symbolNames[symbolName] = false;
|
||||
}
|
||||
});
|
||||
|
||||
// To disambiguate duplicated names, prepend the name and scope of the
|
||||
// parent. For example, if you have foo.bar.Qux and foo.baz.Qux, the
|
||||
// disambiguated names are bar.Quxand baz.Qux.
|
||||
symbols.forEach((symbol) => {
|
||||
let mappedName = null;
|
||||
const symbolName = symbol.kind === 'namespace' ?
|
||||
symbol.longname : symbol.name;
|
||||
|
||||
if (this._symbolNames[symbolName] === true) {
|
||||
if (symbol.kind !== 'namespace') {
|
||||
// get info for the parent's name
|
||||
const atomized = name.shorten(symbol.longname);
|
||||
const atomizedParent = name.shorten(atomized.memberof);
|
||||
// prepend the parent's name and the symbol's scope to the name
|
||||
mappedName = atomizedParent.name + atomized.scope + symbol.name;
|
||||
} else {
|
||||
mappedName = symbol.longname;
|
||||
}
|
||||
}
|
||||
|
||||
symbol.tocYamlName = mappedName || (symbol.kind === 'namespace' ?
|
||||
symbol.longname : symbol.name);
|
||||
});
|
||||
|
||||
const data = {
|
||||
symbols,
|
||||
basepath,
|
||||
};
|
||||
|
||||
this.generate('toc-yaml', data, '_toc.yaml');
|
||||
}
|
||||
|
||||
generateIndex(readme) {
|
||||
const data = {
|
||||
allLongnamesTree: this.allLongnamesTree,
|
||||
package: this.package,
|
||||
pageTitle: global.env.opts.query ? global.env.opts.query.productName : '',
|
||||
readme: readme || null,
|
||||
};
|
||||
|
||||
this.generate('index', data, this.indexUrl);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
generateIndexAll(longnames) {
|
||||
longnames.sort();
|
||||
const data = {
|
||||
longnames: longnames,
|
||||
pageTitle: global.env.opts.query ?
|
||||
global.env.opts.query.productName : '',
|
||||
};
|
||||
|
||||
this.generate('index-all', data, 'index-all.html');
|
||||
}
|
||||
}
|
||||
|
||||
/** const PublishJob = module.exports = function(...args) {
|
||||
Parent.apply(this, [].slice.call(args, 0));
|
||||
};
|
||||
util.inherits(PublishJob, Parent);**/
|
||||
|
||||
module.exports = PublishJob;
|
||||
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Copyright 2017 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-env node */
|
||||
|
||||
const baselineConfig = require('jsdoc-baseline/lib/config');
|
||||
const fileFinder = require('jsdoc-baseline/lib/filefinder');
|
||||
const helper = require('jsdoc/util/templateHelper');
|
||||
const path = require('jsdoc/path');
|
||||
|
||||
exports.publish = function(data, opts, tutorials) {
|
||||
baselineConfig.loadSync();
|
||||
baselineConfig.set('l10n', path.join(__dirname, 'lang'))
|
||||
.set('static', path.join(__dirname, 'static'))
|
||||
.set('views.partials', path.join(__dirname, 'views'))
|
||||
.set('views.layouts', path.join(__dirname, 'views'))
|
||||
.set('modules', path.join(__dirname, 'lib'));
|
||||
|
||||
const config = baselineConfig.get();
|
||||
|
||||
// load the core modules using the file finder
|
||||
const modulesFinder = fileFinder.get('modules', config.modules);
|
||||
|
||||
const DocletHelper = modulesFinder.require('./doclethelper');
|
||||
const PublishJob = modulesFinder.require('./publishjob');
|
||||
const Template = modulesFinder.require('./template');
|
||||
|
||||
const docletHelper = new DocletHelper();
|
||||
const template = new Template(config);
|
||||
const job = new PublishJob(template, opts);
|
||||
|
||||
// set up tutorials
|
||||
helper.setTutorials(tutorials);
|
||||
|
||||
docletHelper.addDoclets(data);
|
||||
|
||||
job.setPackage(docletHelper.getPackage())
|
||||
.setNavTree(docletHelper.navTree)
|
||||
.setAllLongnamesTree(docletHelper.allLongnamesTree);
|
||||
|
||||
// create the output directory so we can start generating files
|
||||
job.createOutputDirectory()
|
||||
// then generate the source files so we can link to them
|
||||
.generateSourceFiles(docletHelper.shortPaths);
|
||||
|
||||
// generate globals page if necessary
|
||||
job.generateGlobals(docletHelper.globals);
|
||||
|
||||
// generate TOC data and index page
|
||||
job.generateTocData({hasGlobals: docletHelper.hasGlobals()})
|
||||
.generateIndex(opts.readme);
|
||||
|
||||
// generate the rest of the output files (excluding tutorials)
|
||||
docletHelper.getOutputLongnames().forEach(function(longname) {
|
||||
job.generateByLongname(longname, docletHelper.getLongname(longname),
|
||||
docletHelper.getMemberof(longname));
|
||||
});
|
||||
|
||||
// finally, generate the tutorials, and copy static files to the output
|
||||
// directory
|
||||
job.generateTutorials(tutorials)
|
||||
.copyStaticFiles();
|
||||
|
||||
// custom Cast pages
|
||||
job.generateTocYaml(
|
||||
docletHelper.getCategory('classes')
|
||||
.concat(docletHelper.getCategory('namespaces'))
|
||||
.concat(docletHelper.getCategory('interfaces')),
|
||||
global.env.opts.query ? global.env.opts.query.basepath : ''
|
||||
);
|
||||
job.generateIndexAll(Object.keys(docletHelper.longname));
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
|
||||
.label {
|
||||
font-style: italic;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.dl-compact dt {
|
||||
margin:8px 0px 0px 0px;
|
||||
}
|
||||
|
||||
h1.devsite-page-title {
|
||||
display: none;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 48px 0px 0px 0px;
|
||||
}
|
||||
|
||||
h3.symbol-name {
|
||||
margin: 32px 0px 8px 0px;
|
||||
}
|
||||
|
||||
h3.class-list:first-child {
|
||||
display: block;
|
||||
margin: 16px 0px 0px 0px;
|
||||
}
|
||||
|
||||
h3.class-list {
|
||||
display: block;
|
||||
margin: 12px 0px 0px 0px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
h3.class-list + p {
|
||||
margin: 0px 0px 0px 0px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin: 8px 0px 0px 0px;
|
||||
}
|
||||
|
||||
td p {
|
||||
margin: 6px 0px 0px 0px;
|
||||
}
|
||||
|
||||
p.type-signature {
|
||||
font-family: 'Roboto Mono', monospace;
|
||||
font-size: 80%;
|
||||
font-style: normal;
|
||||
font-variant: normal;
|
||||
font-weight: 500;
|
||||
font-stretch: normal;
|
||||
line-height: normal;
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
p.symbol-index-name {
|
||||
margin: 8px 0px 0px 0px;
|
||||
}
|
||||
|
||||
h2.symbol-header {
|
||||
margin: 36px 0px 0px 0px;
|
||||
}
|
||||
|
||||
table.function.param tr {
|
||||
background: #80A7B9;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{{#any augments}}
|
||||
<dt>{{translateHeading 'augments' augments}}</dt>
|
||||
{{#each augments}}
|
||||
<dd><small>{{link this}}</small></dd>
|
||||
{{/each}}
|
||||
{{/any}}
|
||||
@@ -0,0 +1,13 @@
|
||||
{{#any items}}
|
||||
<h>{{translateHeading headingKey items}}</h>
|
||||
<section id='members-links'>
|
||||
{{#each items}}
|
||||
<h3 {{~cssClass '!class-list'}}>{{link longname name}}</h3>
|
||||
{{#if classdesc}}
|
||||
{{#markdown}}{{resolveLinks classdesc}}{{/markdown}}
|
||||
{{else}}
|
||||
{{#markdown}}{{resolveLinks description}}{{/markdown}}
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
</section>
|
||||
{{/any}}
|
||||
@@ -0,0 +1,40 @@
|
||||
<tr>
|
||||
{{#block 'details-table-name'}}
|
||||
<td {{~cssClass '!details-table-name'}}>
|
||||
<p>{{#if name}}{{name}}{{else}}{{translate 'tables.notApplicable'}}{{/if}}</p>
|
||||
</td>
|
||||
{{/block}}
|
||||
<td>
|
||||
{{#block 'details-table-optional'}}
|
||||
{{#if optional}}
|
||||
<p {{~cssClass '!details-table-optional'}}>{{translate 'tables.body.isOptional'}}</p>
|
||||
{{/if}}
|
||||
{{/block}}
|
||||
{{#block 'details-table-types'}}
|
||||
{{#if type}}
|
||||
{{#all type type.parsedType}}
|
||||
<p {{~cssClass '!details-table-types'}}>{{describeType type.parsedType 'extended'}}</p>
|
||||
{{/all}}
|
||||
{{/if}}
|
||||
{{/block}}
|
||||
{{#block 'details-table-description'}}
|
||||
{{#if description}}
|
||||
{{#markdown}}{{resolveLinks description}}{{/markdown}}
|
||||
{{/if}}
|
||||
{{#if (hasModifiers this)}}
|
||||
<p>{{modifierText this}}</p>
|
||||
{{/if}}
|
||||
{{#if ../children}}
|
||||
<p>{{translate 'tables.body.eachValueHasProperties' name=name}}</p>
|
||||
{{#withOnly values=../children}}
|
||||
{{#embed 'details-table'}}{{/embed}}
|
||||
{{/withOnly}}
|
||||
{{/if}}
|
||||
{{/block}}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{{!-- This produces extra <p>'s in the output
|
||||
<p {{~cssClass '!details-table-description'}}>
|
||||
{{#markdown}}{{resolveLinks description}}{{/markdown}}</p>
|
||||
--}}
|
||||
@@ -0,0 +1,20 @@
|
||||
<table class="function param responsive">
|
||||
{{#block 'details-table-header'}}
|
||||
<tr>
|
||||
<th colspan="2">
|
||||
{{#if isEnum}}
|
||||
<h>{{translateHeading 'values' params}}</h>
|
||||
{{else}}
|
||||
<h>{{translateHeading 'parameters' params}}</h>
|
||||
{{/if}}
|
||||
</th>
|
||||
</tr>
|
||||
{{/block}}
|
||||
{{#block 'details-table-body'}}
|
||||
<tbody>
|
||||
{{#each values}}
|
||||
{{#embed 'details-table-row'}}{{/embed}}
|
||||
{{/each}}
|
||||
</tbody>
|
||||
{{/block}}
|
||||
</table>
|
||||
@@ -0,0 +1,6 @@
|
||||
{{#any implements}}
|
||||
<dt>{{translateHeading 'implements' implements}}</dt>
|
||||
{{#each implements}}
|
||||
<dd><small>{{link this}}</small></dd>
|
||||
{{/each}}
|
||||
{{/any}}
|
||||
@@ -0,0 +1,12 @@
|
||||
{{#extend 'layout'}}
|
||||
{{#content 'title'}}
|
||||
<title>Index All</title>
|
||||
{{/content}}
|
||||
{{#content 'body-main-content'}}
|
||||
<ul>
|
||||
{{#each longnames}}
|
||||
<li>{{link this}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{/content}}
|
||||
{{/extend}}
|
||||
@@ -0,0 +1,14 @@
|
||||
{{#extend 'layout'}}
|
||||
{{#content 'title'}}
|
||||
<title>Overview</title>
|
||||
{{/content}}
|
||||
{{#content 'body-main-content'}}
|
||||
{{#if readme}}
|
||||
{{{ readme }}}
|
||||
{{else}}
|
||||
{{#block 'body-symbol-index'}}
|
||||
{{#embed 'symbol-index'}}{{/embed}}
|
||||
{{/block}}
|
||||
{{/if}}
|
||||
{{/content}}
|
||||
{{/extend}}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user