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:
Jeffrey Posnick
2018-03-13 15:20:33 -04:00
committed by GitHub
parent 8ef0f96aab
commit 9d88f2abae
824 changed files with 51894 additions and 27529 deletions
-2
View File
@@ -1,2 +0,0 @@
packages/*/node_modules/
packages/*/build/
+82 -14
View File
@@ -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.'}]
}
}],
};
+2
View File
@@ -0,0 +1,2 @@
*.js text eol=lf
*.mjs text eol=lf
+7
View File
@@ -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
View File
@@ -1 +0,0 @@
ruby-2.2.0
+8 -20
View File
@@ -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
-5
View File
@@ -1,5 +0,0 @@
source "http://rubygems.org"
gem 'jekyll', '>=3.1.6'
gem 'kramdown'
gem 'jekyll-github-metadata'
+1 -1
View File
@@ -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
View File
@@ -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
+5
View File
@@ -0,0 +1,5 @@
{
"origin": "https://storage.googleapis.com",
"bucketName": "workbox-cdn",
"releasesDir": "releases"
}
+1
View File
@@ -0,0 +1 @@
{"latestUrl":"https://storage.googleapis.com/workbox-cdn/releases/3.0.0"}
+104
View File
@@ -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),
};
+3181
View File
File diff suppressed because it is too large Load Diff
+12
View File
@@ -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.')
);
})
);
});
+25
View File
@@ -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
})
]
})
);
+17
View File
@@ -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}}
+70
View File
@@ -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
+26
View File
@@ -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"
}
]
}
}
+14
View File
@@ -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']));
+29
View File
@@ -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',
));
+12
View File
@@ -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
)
));
+30
View File
@@ -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'
));
+37 -137
View File
@@ -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`));
};
/**
* 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`);
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`));
};
/**
* 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(),
];
const project = path.basename(projectPath);
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'),
},
});
});
// If it's not a star, we can scope the build to a specific project.
return lernaWrapper.bootstrap(
'--include-filtered-dependencies',
'--scope', global.packageOrStar
);
});
gulp.task('build', () => {
return taskHarness(buildPackage, global.projectOrStar);
// 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'
));
const lernaPkg = await fs.readJSON(path.join(
__dirname, '..', 'lerna.json'
));
cdnDetails.latestVersion = lernaPkg.version;
const workboxBuildPath = path.join(
__dirname, '..', 'packages', 'workbox-build', 'src', 'cdn-details.json'
);
await fs.writeJson(workboxBuildPath, cdnDetails, {
spaces: 2,
});
});
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',
));
+61
View File
@@ -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',
]));
+97
View File
@@ -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']),
]));
-111
View File
@@ -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
);
});
+9 -31
View File
@@ -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', () => {});
// 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.`);
});
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', () => {});
});
+70
View File
@@ -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',
));
+39
View File
@@ -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',
]));
+86
View File
@@ -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',
));
+17
View File
@@ -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',
]);
});
+48
View File
@@ -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();
},
]));
-27
View File
@@ -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}/`);
});
});
+149
View File
@@ -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
});
+90
View File
@@ -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',
));
+34
View File
@@ -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
View File
@@ -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',
));
+112
View File
@@ -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;
+195
View File
@@ -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));
};
+38
View File
@@ -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));
};
+98
View File
@@ -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();
+22
View File
@@ -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',
};
+36
View File
@@ -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');
});
};
},
};
+2
View File
@@ -0,0 +1,2 @@
module.exports = () => process.platform === 'win32' ?
'npm.cmd' : 'npm';
+14
View File
@@ -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];
});
};
+70
View File
@@ -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);
});
},
};
+16
View File
@@ -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]);
},
};
+130
View File
@@ -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;
});
};
+9
View File
@@ -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);
};
+179
View File
@@ -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,
};
+77
View File
@@ -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;
},
};
+16
View File
@@ -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}`);
}
});
});
};
+19
View File
@@ -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);
};
+5
View File
@@ -0,0 +1,5 @@
const {getCDNOrigin} = require(
'../../packages/workbox-build/src/lib/cdn-utils');
const lernaPkg = require('../../lerna.json');
module.exports = () => `${getCDNOrigin()}/${lernaPkg.version}`;
+21
View File
@@ -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
View File
@@ -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));
});
+79
View File
@@ -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: '&nbsp;returns&nbsp;'
tables:
body:
defaultValue: 'Defaults to {valueString}.'
defaultValueString: '<code>{value}</code>'
eachValueHasProperties: 'Values in <code>{name}</code> have the following properties:'
isOptional: 'Optional'
isRequired: '&nbsp;'
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: '&nbsp;'
header:
description: 'Description'
name: 'Name'
optional: 'Optional'
type: 'Type'
notApplicable: '&#8212;'
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
View File
@@ -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}}
+12
View File
@@ -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