Skip to content

Commit

Permalink
feat(pwa): initial ServiceWorker and Web App Manifest support
Browse files Browse the repository at this point in the history
This package adds ServiceWorker and Web App Manifest capabilities to
your Hops application.
To use it:
- add `hops-pwa` as a dependency to your project
- create a Web App Manifest and load it in your app:
  ```jsx
  <Helmet>
      <link rel="manifest" href={require('./manifest.webmanifest')} />
  </Helmet>
  ```
- specify the path to the Service Worker file in your Hops config:
  ```json
    "hops": {
      "workerFile": "hops-pwa/worker.js"
    }
  ```
  You can either use the default worker provided by the `hops-pwa`
  package or you can specify a path to your own worker implementation.
  • Loading branch information
ZauberNerd authored and dmbch committed Feb 19, 2018
1 parent ec7f016 commit 6fe9ddb
Show file tree
Hide file tree
Showing 14 changed files with 233 additions and 4 deletions.
2 changes: 2 additions & 0 deletions packages/build-config/configs/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ var ExtractTextPlugin = require('extract-text-webpack-plugin');
var UglifyPlugin = require('uglifyjs-webpack-plugin');
var StatsWriterPlugin = require('webpack-stats-plugin').StatsWriterPlugin;
var WriteFilePlugin = require('../plugins/write-file');
var ServiceWorkerPlugin = require('../plugins/webpack-service-worker');

var hopsConfig = require('hops-config');

Expand All @@ -29,6 +30,7 @@ module.exports = {
plugins: [
new WriteFilePlugin(/^manifest\.js(\.map)?$/),
new StatsWriterPlugin({ fields: null }),
new ServiceWorkerPlugin(),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function(module) {
Expand Down
3 changes: 3 additions & 0 deletions packages/build-config/configs/develop.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ var path = require('path');

var webpack = require('webpack');

var ServiceWorkerPlugin = require('../plugins/webpack-service-worker');

var hopsConfig = require('hops-config');

var getAssetPath = path.join.bind(path, hopsConfig.assetPath);
Expand All @@ -25,6 +27,7 @@ module.exports = {
rules: require('../sections/module-rules')('develop'),
},
plugins: [
new ServiceWorkerPlugin(),
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(),
new webpack.EnvironmentPlugin(
Expand Down
2 changes: 2 additions & 0 deletions packages/build-config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,10 @@
"style-loader": "^0.20.0",
"uglifyjs-webpack-plugin": "^1.1.6",
"url-loader": "^0.6.2",
"web-app-manifest-loader": "^0.1.1",
"webpack": "^3.6.0",
"webpack-node-externals": "^1.6.0",
"webpack-sources": "^1.1.0",
"webpack-stats-plugin": "^0.1.5"
}
}
56 changes: 56 additions & 0 deletions packages/build-config/plugins/webpack-service-worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
'use strict';

var SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin');
var RawSource = require('webpack-sources').RawSource;

var hopsConfig = require('hops-config');

var PLUGIN_NAME = 'hops-service-worker-plugin';

module.exports = function ServiceWorkerPlugin() {
var assetPath = hopsConfig.workerPath.replace(/^\/+/, '');

this.apply = function(compiler) {
if (!hopsConfig.workerFile) {
return;
}

function onMake(compilation, callback) {
compilation
.createChildCompiler(PLUGIN_NAME, { filename: assetPath }, [
new SingleEntryPlugin(
compiler.context,
require.resolve('../shims/worker-shim'),
'worker'
),
])
.runAsChild(callback);
}

function onEmit(compilation, callback) {
compilation.assets[assetPath] = new RawSource(
'HOPS_ASSETS = ' +
JSON.stringify(
Object.keys(compilation.assets).filter(function(item) {
return !item.match(/\.map|stats\.json$/) && item !== assetPath;
})
) +
';\n' +
compilation.assets[assetPath].source()
);
callback();
}

if (typeof compiler.hooks !== 'undefined') {
compiler.hooks.make.tapAsync(PLUGIN_NAME, onMake);
} else {
compiler.plugin('make', onMake);
}

if (typeof compiler.hooks !== 'undefined') {
compiler.hooks.emit.tap(PLUGIN_NAME, onEmit);
} else {
compiler.plugin('emit', onEmit);
}
};
};
1 change: 1 addition & 0 deletions packages/build-config/sections/module-rules.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module.exports = function getModuleRules(target) {
require('./module-rules/graphql'),
require('./module-rules/postcss'),
require('./module-rules/config'),
require('./module-rules/webmanifest'),
require('./module-rules/tpl'),
require('./module-rules/url'),
require('./module-rules/file'),
Expand Down
36 changes: 36 additions & 0 deletions packages/build-config/sections/module-rules/webmanifest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
'use strict';

var path = require('path');

var hopsConfig = require('hops-config');

exports.default = {
test: /\.webmanifest$/,
use: [
{
loader: require.resolve('file-loader'),
options: {
name: path.join(hopsConfig.assetPath, '[name]-[hash:16].[ext]'),
},
},
{
loader: require.resolve('web-app-manifest-loader'),
},
],
};

exports.node = {
test: /\.webmanifest$/,
use: [
{
loader: require.resolve('file-loader'),
options: {
name: path.join(hopsConfig.assetPath, '[name]-[hash:16].[ext]'),
emitFile: false,
},
},
{
loader: require.resolve('web-app-manifest-loader'),
},
],
};
11 changes: 8 additions & 3 deletions packages/build-config/sections/resolve.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@ var hopsConfig = require('hops-config');
module.exports = function getResolveConfig(target) {
var platform = target === 'node' ? 'server' : 'browser';
return {
alias: {
'hops-entry-point': hopsConfig.appDir,
},
alias: Object.assign(
{
'hops-entry-point': hopsConfig.appDir,
},
hopsConfig.workerFile && {
'hops-worker-entry-point': hopsConfig.workerFile,
}
),
mainFields: [
'esnext:' + platform,
'jsnext:' + platform,
Expand Down
19 changes: 19 additions & 0 deletions packages/build-config/shims/worker-shim.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
'use strict';

require('babel-polyfill');

(function execute() {
var entryPoint = require('hops-worker-entry-point');
if (
typeof entryPoint !== 'function' &&
typeof entryPoint.default === 'function'
) {
entryPoint = entryPoint.default;
}
entryPoint(HOPS_ASSETS); // eslint-disable-line no-undef
if (module.hot) {
module.hot.accept(require.resolve('hops-worker-entry-point'), function() {
setTimeout(execute);
});
}
})();
2 changes: 1 addition & 1 deletion packages/config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ function applyDefaultConfig(config) {
locations: [],
basePath: '',
assetPath: '',
workerPath: '<assetPath>/sw.js',
workerPath: '<basePath>/sw.js',
workerFile: null,
browsers: '> 1%, last 2 versions, Firefox ESR',
node: 'current',
Expand Down
22 changes: 22 additions & 0 deletions packages/pwa/dom.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use strict';

var hopsConfig = require('hops-config');

module.exports = function installServiceWorker() {
return new Promise(function(resolve, reject) {
if (!('serviceWorker' in navigator)) {
return reject(
new Error('ServiceWorkers are not supported in this browser')
);
}

if (
window.location.protocol === 'https' ||
window.location.hostname === 'localhost'
) {
window.addEventListener('load', function() {
resolve(navigator.serviceWorker.register('/' + hopsConfig.workerPath));
});
}
});
};
7 changes: 7 additions & 0 deletions packages/pwa/node.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use strict';

module.exports = function installServiceWorke() {
return new Promise(function() {
/* intentionally left empty */
});
};
13 changes: 13 additions & 0 deletions packages/pwa/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "hops-pwa",
"version": "10.1.0",
"description": "ServiceWorker and Web App Manifest support for Hops",
"keywords": ["hops", "serviceworker", "pwa", "web app manifest"],
"license": "MIT",
"main": "node.js",
"browser": "dom.js",
"files": ["dom.js", "node.js", "worker.js"],
"dependencies": {
"hops-config": "^10.0.1"
}
}
7 changes: 7 additions & 0 deletions packages/pwa/worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use strict';

var hopsConfig = require('hops-config');

module.exports = function(assets) {
console.log('hello from worker', assets, hopsConfig);
};
56 changes: 56 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2789,10 +2789,36 @@ fast-levenshtein@~2.0.4:
version "2.0.6"
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"

fastfall@^1.5.0:
version "1.5.1"
resolved "https://registry.yarnpkg.com/fastfall/-/fastfall-1.5.1.tgz#3fee03331a49d1d39b3cdf7a5e9cd66f475e7b94"
dependencies:
reusify "^1.0.0"

fastparallel@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/fastparallel/-/fastparallel-2.3.0.tgz#1e709bfb6a03993f3857e3ce7f01311ce7602613"
dependencies:
reusify "^1.0.0"
xtend "^4.0.1"

fastparse@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8"

fastq@^1.3.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.5.0.tgz#05e32ffb999ec2d945dda27461bf08941436448b"
dependencies:
reusify "^1.0.0"

fastseries@^1.7.0:
version "1.7.2"
resolved "https://registry.yarnpkg.com/fastseries/-/fastseries-1.7.2.tgz#d22ce13b9433dff3388d91dbd6b8bda9b21a0f4b"
dependencies:
reusify "^1.0.0"
xtend "^4.0.0"

fb-watchman@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.0.tgz#54e9abf7dfa2f26cd9b1636c588c1afc05de5d58"
Expand Down Expand Up @@ -4501,6 +4527,15 @@ loader-runner@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2"

loader-utils@^0.2.12:
version "0.2.17"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348"
dependencies:
big.js "^3.1.3"
emojis-list "^2.0.0"
json5 "^0.5.0"
object-assign "^4.0.1"

loader-utils@^1.0.2, loader-utils@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd"
Expand Down Expand Up @@ -6551,6 +6586,10 @@ restore-cursor@^2.0.0:
onetime "^2.0.0"
signal-exit "^3.0.2"

reusify@^1.0.0:
version "1.0.4"
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"

revalidator@0.1.x:
version "0.1.8"
resolved "https://registry.yarnpkg.com/revalidator/-/revalidator-0.1.8.tgz#fece61bfa0c1b52a206bd6b18198184bdd523a3b"
Expand Down Expand Up @@ -6893,6 +6932,16 @@ stealthy-require@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b"

steed@^1.1.2:
version "1.1.3"
resolved "https://registry.yarnpkg.com/steed/-/steed-1.1.3.tgz#f1525dd5adb12eb21bf74749537668d625b9abc5"
dependencies:
fastfall "^1.5.0"
fastparallel "^2.2.0"
fastq "^1.3.0"
fastseries "^1.7.0"
reusify "^1.0.0"

stream-browserify@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db"
Expand Down Expand Up @@ -7534,6 +7583,13 @@ wcwidth@^1.0.0:
dependencies:
defaults "^1.0.3"

web-app-manifest-loader@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/web-app-manifest-loader/-/web-app-manifest-loader-0.1.1.tgz#861186a70f37b69ee10496b7ed0353d56fbdd11a"
dependencies:
loader-utils "^0.2.12"
steed "^1.1.2"

webidl-conversions@^4.0.1, webidl-conversions@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
Expand Down

0 comments on commit 6fe9ddb

Please sign in to comment.