├── src
├── index.css
├── exampleModule.js
├── App.test.js
├── echo.webworker.js
├── App.css
├── index.js
├── App.js
├── registerServiceWorker.js
└── logo.svg
├── public
├── favicon.ico
├── manifest.json
└── index.html
├── .gitignore
├── config
├── jest
│ ├── fileTransform.js
│ └── cssTransform.js
├── polyfills.js
├── paths.js
├── env.js
├── webpackDevServer.config.js
├── webpack.config.dev.js
└── webpack.config.prod.js
├── scripts
├── test.js
├── start.js
└── build.js
├── README.md
└── package.json
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | }
6 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stereobooster/cra-webworker-example/master/public/favicon.ico
--------------------------------------------------------------------------------
/src/exampleModule.js:
--------------------------------------------------------------------------------
1 | export default function exampleModule(argument) {
2 | console.log(argument)
3 | };
4 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | });
9 |
--------------------------------------------------------------------------------
/src/echo.webworker.js:
--------------------------------------------------------------------------------
1 | import { default as exampleModule } from './exampleModule';
2 |
3 | self.onmessage = function(e) {
4 | console.log('[WORKER]', e.data.message);
5 | exampleModule('test');
6 | self.postMessage({
7 | message: 'Message from worker'
8 | });
9 | };
10 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/config/jest/fileTransform.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 |
5 | // This is a custom Jest transformer turning file imports into filenames.
6 | // http://facebook.github.io/jest/docs/tutorial-webpack.html
7 |
8 | module.exports = {
9 | process(src, filename) {
10 | return `module.exports = ${JSON.stringify(path.basename(filename))};`;
11 | },
12 | };
13 |
--------------------------------------------------------------------------------
/config/jest/cssTransform.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // This is a custom Jest transformer turning style imports into empty objects.
4 | // http://facebook.github.io/jest/docs/tutorial-webpack.html
5 |
6 | module.exports = {
7 | process() {
8 | return 'module.exports = {};';
9 | },
10 | getCacheKey() {
11 | // The output is always the same.
12 | return 'cssTransform';
13 | },
14 | };
15 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | animation: App-logo-spin infinite 20s linear;
7 | height: 80px;
8 | }
9 |
10 | .App-header {
11 | background-color: #222;
12 | height: 150px;
13 | padding: 20px;
14 | color: white;
15 | }
16 |
17 | .App-intro {
18 | font-size: large;
19 | }
20 |
21 | @keyframes App-logo-spin {
22 | from { transform: rotate(0deg); }
23 | to { transform: rotate(360deg); }
24 | }
25 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 | import registerServiceWorker from './registerServiceWorker';
5 | import './index.css';
6 |
7 | import {default as echoWorker} from './echo.webworker.js';
8 |
9 | const worker = new Worker(echoWorker);
10 | worker.onmessage = function(e) {
11 | console.log('[MAIN]', e.data.message);
12 | };
13 | worker.postMessage({
14 | message: 'Message from window'
15 | });
16 |
17 | ReactDOM.render(, document.getElementById('root'));
18 | registerServiceWorker();
19 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import logo from './logo.svg';
3 | import './App.css';
4 |
5 | class App extends Component {
6 | render() {
7 | return (
8 |
9 |
10 |

11 |
Welcome to React
12 |
13 |
14 | To get started, edit src/App.js and save to reload.
15 |
16 |
17 | );
18 | }
19 | }
20 |
21 | export default App;
22 |
--------------------------------------------------------------------------------
/config/polyfills.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | if (typeof Promise === 'undefined') {
4 | // Rejection tracking prevents a common issue where React gets into an
5 | // inconsistent state due to an error, but it gets swallowed by a Promise,
6 | // and the user has no idea what causes React's erratic future behavior.
7 | require('promise/lib/rejection-tracking').enable();
8 | window.Promise = require('promise/lib/es6-extensions.js');
9 | }
10 |
11 | // fetch() polyfill for making API calls.
12 | require('whatwg-fetch');
13 |
14 | // Object.assign() is commonly used with React.
15 | // It will use the native implementation if it's present and isn't buggy.
16 | Object.assign = require('object-assign');
17 |
--------------------------------------------------------------------------------
/scripts/test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | process.env.NODE_ENV = 'test';
4 | process.env.PUBLIC_URL = '';
5 |
6 | // Makes the script crash on unhandled rejections instead of silently
7 | // ignoring them. In the future, promise rejections that are not handled will
8 | // terminate the Node.js process with a non-zero exit code.
9 | process.on('unhandledRejection', err => {
10 | throw err;
11 | });
12 |
13 | // Ensure environment variables are read.
14 | require('../config/env');
15 |
16 | const jest = require('jest');
17 | const argv = process.argv.slice(2);
18 |
19 | // Watch unless on CI or in coverage mode
20 | if (!process.env.CI && argv.indexOf('--coverage') < 0) {
21 | argv.push('--watch');
22 | }
23 |
24 |
25 | jest.run(argv);
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Webworkers for CRA
2 |
3 | Issue in CRA repo: https://github.com/facebookincubator/create-react-app/issues/1277
4 |
5 | This is CRA ejected project in which I prototype webpack configuration for webworker.
6 |
7 | Idea: use `*.webworker.js` as convention over configuration.
8 |
9 | For example [mapbox-gl-js/src/util/browser/web_worker.js](https://github.com/mapbox/mapbox-gl-js/blob/b9e10b939c6a3fe5d7ecac209f751b4871970ede/src/util/browser/web_worker.js) instead of:
10 |
11 | ```js
12 | 'use strict';
13 |
14 | const WebWorkify = require('webworkify');
15 | const window = require('../window');
16 | const workerURL = window.URL.createObjectURL(new WebWorkify(require('../../source/worker'), {bare: true}));
17 |
18 | module.exports = function () {
19 | return new window.Worker(workerURL);
20 | };
21 | ```
22 |
23 | Will do:
24 | ```js
25 | 'use strict';
26 |
27 | const workerURL = require('../../source/worker.webworker');
28 |
29 | module.exports = function () {
30 | return new window.Worker(workerURL);
31 | };
32 | ```
33 |
34 | It will be up to bundler (browserify, webpack) to decide what to do. For example:
35 | - mapbox can decide to continue to use Browserify + Webworkify and generate single file e.g. `require('../../source/worker.webworker');` will be transformed to `window.URL.createObjectURL(new WebWorkify(require('../../source/worker'), {bare: true}));` behind the scene.
36 | - or in case of [react-map-gl](https://github.com/uber/react-map-gl) + CRA, webpack can be instructed to generate URLs
37 |
38 | ## TODO
39 |
40 | - How to instruct webpack to generate URLs for `require('*.webworker')` in node folder and generate corresponding files to web folder.
41 | - Change ESLint settings to suport `self` keyword in webworkers
42 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 | React App
23 |
24 |
25 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | export default function register() {
12 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
13 | window.addEventListener('load', () => {
14 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
15 | navigator.serviceWorker
16 | .register(swUrl)
17 | .then(registration => {
18 | registration.onupdatefound = () => {
19 | const installingWorker = registration.installing;
20 | installingWorker.onstatechange = () => {
21 | if (installingWorker.state === 'installed') {
22 | if (navigator.serviceWorker.controller) {
23 | // At this point, the old content will have been purged and
24 | // the fresh content will have been added to the cache.
25 | // It's the perfect time to display a "New content is
26 | // available; please refresh." message in your web app.
27 | console.log('New content is available; please refresh.');
28 | } else {
29 | // At this point, everything has been precached.
30 | // It's the perfect time to display a
31 | // "Content is cached for offline use." message.
32 | console.log('Content is cached for offline use.');
33 | }
34 | }
35 | };
36 | };
37 | })
38 | .catch(error => {
39 | console.error('Error during service worker registration:', error);
40 | });
41 | });
42 | }
43 | }
44 |
45 | export function unregister() {
46 | if ('serviceWorker' in navigator) {
47 | navigator.serviceWorker.ready.then(registration => {
48 | registration.unregister();
49 | });
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webworker-example",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^15.5.4",
7 | "react-dom": "^15.5.4"
8 | },
9 | "devDependencies": {
10 | "autoprefixer": "7.1.0",
11 | "babel-core": "6.24.1",
12 | "babel-eslint": "7.2.3",
13 | "babel-jest": "20.0.3",
14 | "babel-loader": "7.0.0",
15 | "babel-preset-react-app": "^3.0.0",
16 | "babel-runtime": "6.23.0",
17 | "case-sensitive-paths-webpack-plugin": "1.1.4",
18 | "chalk": "1.1.3",
19 | "css-loader": "0.28.1",
20 | "dotenv": "4.0.0",
21 | "eslint": "3.19.0",
22 | "eslint-config-react-app": "^1.0.4",
23 | "eslint-loader": "1.7.1",
24 | "eslint-plugin-flowtype": "2.33.0",
25 | "eslint-plugin-import": "2.2.0",
26 | "eslint-plugin-jsx-a11y": "5.0.3",
27 | "eslint-plugin-react": "7.0.1",
28 | "extract-text-webpack-plugin": "2.1.0",
29 | "file-loader": "0.11.1",
30 | "fs-extra": "3.0.1",
31 | "html-webpack-plugin": "2.28.0",
32 | "jest": "20.0.3",
33 | "object-assign": "4.1.1",
34 | "postcss-flexbugs-fixes": "3.0.0",
35 | "postcss-loader": "2.0.5",
36 | "promise": "7.1.1",
37 | "react-dev-utils": "^2.0.1",
38 | "react-error-overlay": "^1.0.6",
39 | "style-loader": "0.17.0",
40 | "sw-precache-webpack-plugin": "0.9.1",
41 | "url-loader": "0.5.8",
42 | "webpack": "2.5.1",
43 | "webpack-dev-server": "2.4.5",
44 | "webpack-manifest-plugin": "1.1.0",
45 | "whatwg-fetch": "2.0.3"
46 | },
47 | "scripts": {
48 | "start": "node scripts/start.js",
49 | "build": "node scripts/build.js",
50 | "test": "node scripts/test.js --env=jsdom"
51 | },
52 | "jest": {
53 | "collectCoverageFrom": [
54 | "src/**/*.{js,jsx}"
55 | ],
56 | "setupFiles": [
57 | "/config/polyfills.js"
58 | ],
59 | "testMatch": [
60 | "/src/**/__tests__/**/*.js?(x)",
61 | "/src/**/?(*.)(spec|test).js?(x)"
62 | ],
63 | "testEnvironment": "node",
64 | "testURL": "http://localhost",
65 | "transform": {
66 | "^.+\\.(js|jsx)$": "/node_modules/babel-jest",
67 | "^.+\\.css$": "/config/jest/cssTransform.js",
68 | "^(?!.*\\.(js|jsx|css|json)$)": "/config/jest/fileTransform.js"
69 | },
70 | "transformIgnorePatterns": [
71 | "[/\\\\]node_modules[/\\\\].+\\.(js|jsx)$"
72 | ],
73 | "moduleNameMapper": {
74 | "^react-native$": "react-native-web"
75 | }
76 | },
77 | "babel": {
78 | "presets": [
79 | "react-app"
80 | ]
81 | },
82 | "eslintConfig": {
83 | "extends": "react-app"
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/config/paths.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 | const fs = require('fs');
5 | const url = require('url');
6 | const glob = require('glob');
7 |
8 | // Make sure any symlinks in the project folder are resolved:
9 | // https://github.com/facebookincubator/create-react-app/issues/637
10 | const appDirectory = fs.realpathSync(process.cwd());
11 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
12 |
13 | const envPublicUrl = process.env.PUBLIC_URL;
14 |
15 | function ensureSlash(path, needsSlash) {
16 | const hasSlash = path.endsWith('/');
17 | if (hasSlash && !needsSlash) {
18 | return path.substr(path, path.length - 1);
19 | } else if (!hasSlash && needsSlash) {
20 | return `${path}/`;
21 | } else {
22 | return path;
23 | }
24 | }
25 |
26 | const getPublicUrl = appPackageJson =>
27 | envPublicUrl || require(appPackageJson).homepage;
28 |
29 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer
30 | // "public path" at which the app is served.
31 | // Webpack needs to know it to put the right