diff --git a/babel.config.js b/babel.config.js index 190c338..2cfa79c 100644 --- a/babel.config.js +++ b/babel.config.js @@ -15,5 +15,20 @@ module.exports = (api) => { }, ], ], + overrides: [ + { + test: './src/runtime', + presets: [ + [ + '@babel/preset-env', + { + targets: { + node: '0.12', + }, + }, + ], + ], + }, + ], }; }; diff --git a/src/options.json b/src/options.json index 50d1a70..ad26e95 100644 --- a/src/options.json +++ b/src/options.json @@ -1,6 +1,29 @@ { "type": "object", "properties": { + "worker": { + "anyOf": [ + { + "type": "string", + "minLength": 1 + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "minLength": 1 + }, + "options": { + "additionalProperties": true, + "type": "object" + } + }, + "required": ["type"] + } + ] + }, "name": { "type": "string" }, @@ -12,9 +35,6 @@ }, "publicPath": { "type": "string" - }, - "workerType": { - "type": "string" } }, "additionalProperties": false diff --git a/src/runtime/inline.js b/src/runtime/inline.js new file mode 100644 index 0000000..4fa7095 --- /dev/null +++ b/src/runtime/inline.js @@ -0,0 +1,48 @@ +/* eslint-env browser */ +/* eslint-disable no-undef, no-use-before-define, new-cap */ + +module.exports = function inline( + content, + url, + workerConstructor, + workerOptions +) { + try { + try { + let blob; + + try { + // BlobBuilder = Deprecated, but widely implemented + const BlobBuilder = + BlobBuilder || WebKitBlobBuilder || MozBlobBuilder || MSBlobBuilder; + + blob = new BlobBuilder(); + + blob.append(content); + + blob = blob.getBlob(); + } catch (e) { + // New API + blob = new Blob([content]); + } + + const objectURL = URL.createObjectURL(blob); + const worker = new window[workerConstructor](objectURL, workerOptions); + + URL.revokeObjectURL(objectURL); + + return worker; + } catch (e) { + return new window[workerConstructor]( + `data:application/javascript,${encodeURIComponent(content)}`, + workerOptions + ); + } + } catch (e) { + if (!url) { + throw Error('Inline worker is not supported'); + } + + return new window[workerConstructor](url, workerOptions); + } +}; diff --git a/src/supportWebpack4.js b/src/supportWebpack4.js index 5b3701f..f7c7978 100644 --- a/src/supportWebpack4.js +++ b/src/supportWebpack4.js @@ -1,4 +1,4 @@ -import getWorker from './workers'; +import { getWorker } from './utils'; export default function runAsChild(worker, request, options, callback) { const subCache = `subcache ${__dirname} ${request}`; diff --git a/src/supportWebpack5.js b/src/supportWebpack5.js index e18e58c..8bc9d2e 100644 --- a/src/supportWebpack5.js +++ b/src/supportWebpack5.js @@ -1,4 +1,4 @@ -import getWorker from './workers'; +import { getWorker } from './utils'; export default function runAsChild(worker, options, callback) { // eslint-disable-next-line import/no-unresolved, global-require diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..403670b --- /dev/null +++ b/src/utils.js @@ -0,0 +1,40 @@ +function getWorker(file, content, options) { + const publicPath = + typeof options.publicPath === 'undefined' + ? '__webpack_public_path__' + : JSON.stringify(options.publicPath); + const publicWorkerPath = `${publicPath} + ${JSON.stringify(file)}`; + + let workerConstructor; + let workerOptions; + + if (typeof options.worker === 'undefined') { + workerConstructor = 'Worker'; + } else if (typeof options.worker === 'string') { + workerConstructor = options.worker; + } else { + ({ type: workerConstructor, options: workerOptions } = options.worker); + } + + if (options.inline) { + const InlineWorkerPath = JSON.stringify( + `!!${require.resolve('./runtime/inline.js')}` + ); + + const fallbackWorkerPath = + options.fallback === false ? 'null' : publicWorkerPath; + + return `require(${InlineWorkerPath})(${JSON.stringify( + content + )}, ${fallbackWorkerPath}, ${JSON.stringify( + workerConstructor + )}, ${JSON.stringify(workerOptions)})`; + } + + return `new ${workerConstructor}(${publicWorkerPath}${ + workerOptions ? `, ${JSON.stringify(workerOptions)}` : '' + })`; +} + +// eslint-disable-next-line import/prefer-default-export +export { getWorker }; diff --git a/src/workers/InlineWorker.js b/src/workers/InlineWorker.js deleted file mode 100644 index 180e479..0000000 --- a/src/workers/InlineWorker.js +++ /dev/null @@ -1,57 +0,0 @@ -// http://stackoverflow.com/questions/10343913/how-to-create-a-web-worker-from-a-string - -/* eslint-env browser */ -/* eslint-disable no-var, vars-on-top, prefer-template, no-undef, no-use-before-define */ -var URL = URL || webkitURL; - -function CreateWorker(url, workerType) { - switch (workerType) { - case 'SharedWorker': - return new SharedWorker(url); - case 'ServiceWorker': - return new ServiceWorker(url); - default: - return new Worker(url); - } -} - -module.exports = function inlineWorker(content, url, workerType) { - try { - try { - var blob; - - try { - // BlobBuilder = Deprecated, but widely implemented - var BlobBuilder = - BlobBuilder || WebKitBlobBuilder || MozBlobBuilder || MSBlobBuilder; - - blob = new BlobBuilder(); - - blob.append(content); - - blob = blob.getBlob(); - } catch (e) { - // The proposed API - blob = new Blob([content]); - } - - var objectURL = URL.createObjectURL(blob); - var worker = CreateWorker(objectURL, workerType); - - URL.revokeObjectURL(objectURL); - - return worker; - } catch (e) { - return CreateWorker( - 'data:application/javascript,' + encodeURIComponent(content), - workerType - ); - } - } catch (e) { - if (!url) { - throw Error('Inline worker is not supported'); - } - - return CreateWorker(url, workerType); - } -}; diff --git a/src/workers/index.js b/src/workers/index.js deleted file mode 100644 index 02b7dcc..0000000 --- a/src/workers/index.js +++ /dev/null @@ -1,39 +0,0 @@ -import path from 'path'; - -const getWorker = (file, content, options) => { - const publicPath = options.publicPath - ? JSON.stringify(options.publicPath) - : '__webpack_public_path__'; - - const publicWorkerPath = `${publicPath} + ${JSON.stringify(file)}`; - - if (options.inline) { - const InlineWorkerPath = JSON.stringify( - `!!${path.join(__dirname, 'InlineWorker.js')}` - ); - - const fallbackWorkerPath = - options.fallback === false ? 'null' : publicWorkerPath; - - return `require(${InlineWorkerPath})(${JSON.stringify( - content - )}, ${fallbackWorkerPath}, ${options.workerType})`; - } - - let worker; - - switch (options.workerType) { - case 'SharedWorker': - worker = 'SharedWorker'; - break; - case 'ServiceWorker': - worker = 'ServiceWorker'; - break; - default: - worker = 'Worker'; - } - - return `new ${worker}(${publicWorkerPath})`; -}; - -export default getWorker; diff --git a/test/__snapshots__/worker-option.test.js.snap b/test/__snapshots__/worker-option.test.js.snap new file mode 100644 index 0000000..393dc2c --- /dev/null +++ b/test/__snapshots__/worker-option.test.js.snap @@ -0,0 +1,43 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`"workerType" option should support the "Worker" object value for inline workers: errors 1`] = `Array []`; + +exports[`"workerType" option should support the "Worker" object value for inline workers: result 1`] = `"{\\"postMessage\\":true,\\"onmessage\\":true}"`; + +exports[`"workerType" option should support the "Worker" object value for inline workers: warnings 1`] = `Array []`; + +exports[`"workerType" option should support the "Worker" object value: errors 1`] = `Array []`; + +exports[`"workerType" option should support the "Worker" object value: module 1`] = ` +"module.exports = function() { + return new Worker(__webpack_public_path__ + \\"test.worker.js\\", {\\"type\\":\\"classic\\",\\"name\\":\\"worker-name\\"}); +};" +`; + +exports[`"workerType" option should support the "Worker" object value: result 1`] = `"{\\"postMessage\\":true,\\"onmessage\\":true}"`; + +exports[`"workerType" option should support the "Worker" object value: warnings 1`] = `Array []`; + +exports[`"workerType" option should support the "Worker" string value: errors 1`] = `Array []`; + +exports[`"workerType" option should support the "Worker" string value: module 1`] = ` +"module.exports = function() { + return new Worker(__webpack_public_path__ + \\"test.worker.js\\"); +};" +`; + +exports[`"workerType" option should support the "Worker" string value: result 1`] = `"{\\"postMessage\\":true,\\"onmessage\\":true}"`; + +exports[`"workerType" option should support the "Worker" string value: warnings 1`] = `Array []`; + +exports[`"workerType" option should use "Worker" by default: errors 1`] = `Array []`; + +exports[`"workerType" option should use "Worker" by default: module 1`] = ` +"module.exports = function() { + return new Worker(__webpack_public_path__ + \\"test.worker.js\\"); +};" +`; + +exports[`"workerType" option should use "Worker" by default: result 1`] = `"{\\"postMessage\\":true,\\"onmessage\\":true}"`; + +exports[`"workerType" option should use "Worker" by default: warnings 1`] = `Array []`; diff --git a/test/__snapshots__/workerType-option.test.js.snap b/test/__snapshots__/workerType-option.test.js.snap deleted file mode 100644 index 9336f6f..0000000 --- a/test/__snapshots__/workerType-option.test.js.snap +++ /dev/null @@ -1,13 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`"workerType" option should use "Worker" by default: errors 1`] = `Array []`; - -exports[`"workerType" option should use "Worker" by default: module 1`] = ` -"module.exports = function() { - return new Worker(__webpack_public_path__ + \\"test.worker.js\\"); -};" -`; - -exports[`"workerType" option should use "Worker" by default: result 1`] = `"{\\"postMessage\\":true,\\"onmessage\\":true}"`; - -exports[`"workerType" option should use "Worker" by default: warnings 1`] = `Array []`; diff --git a/test/helpers/getResultFromBrowser.js b/test/helpers/getResultFromBrowser.js index ef1fef7..ed82bd9 100644 --- a/test/helpers/getResultFromBrowser.js +++ b/test/helpers/getResultFromBrowser.js @@ -27,6 +27,25 @@ export default async function getResultFromBrowser(stats) { const browser = await puppeteer.launch(); const page = await browser.newPage(); + + page + .on('console', (message) => + // eslint-disable-next-line no-console + console.log(message) + ) + .on('pageerror', ({ message }) => + // eslint-disable-next-line no-console + console.log(message) + ) + // .on('response', (response) => + // // eslint-disable-next-line no-console + // console.log(`${response.status()} ${response.url()}`) + // ) + .on('requestfailed', (request) => + // eslint-disable-next-line no-console + console.log(`${request.failure().errorText} ${request.url()}`) + ); + await page.goto(`http://127.0.0.1:${port}/`); await page.waitForSelector('button'); await page.click('button'); diff --git a/test/worker-option.test.js b/test/worker-option.test.js new file mode 100644 index 0000000..fbf7b28 --- /dev/null +++ b/test/worker-option.test.js @@ -0,0 +1,80 @@ +import { + compile, + getCompiler, + getErrors, + getModuleSource, + getResultFromBrowser, + getWarnings, +} from './helpers'; + +describe('"workerType" option', () => { + it('should use "Worker" by default', async () => { + const compiler = getCompiler('./basic/entry.js'); + const stats = await compile(compiler); + const result = await getResultFromBrowser(stats); + + expect(getModuleSource('./basic/worker.js', stats)).toMatchSnapshot( + 'module' + ); + expect(result).toMatchSnapshot('result'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); + + it('should support the "Worker" string value', async () => { + const compiler = getCompiler('./basic/entry.js', { worker: 'Worker' }); + const stats = await compile(compiler); + const result = await getResultFromBrowser(stats); + + expect(getModuleSource('./basic/worker.js', stats)).toMatchSnapshot( + 'module' + ); + expect(result).toMatchSnapshot('result'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); + + it('should support the "Worker" object value', async () => { + const compiler = getCompiler('./basic/entry.js', { + worker: { + type: 'Worker', + options: { + type: 'classic', + name: 'worker-name', + }, + }, + }); + const stats = await compile(compiler); + const result = await getResultFromBrowser(stats); + + expect(getModuleSource('./basic/worker.js', stats)).toMatchSnapshot( + 'module' + ); + expect(result).toMatchSnapshot('result'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); + + it('should support the "Worker" object value for inline workers', async () => { + const compiler = getCompiler('./basic/entry.js', { + inline: true, + worker: { + type: 'Worker', + options: { + type: 'classic', + name: 'worker-name', + }, + }, + }); + const stats = await compile(compiler); + const result = await getResultFromBrowser(stats); + + // TODO fix + // expect(getModuleSource('./basic/worker.js', stats)).toMatchSnapshot( + // 'module' + // ); + expect(result).toMatchSnapshot('result'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); +}); diff --git a/test/workerType-option.test.js b/test/workerType-option.test.js deleted file mode 100644 index 602438c..0000000 --- a/test/workerType-option.test.js +++ /dev/null @@ -1,23 +0,0 @@ -import { - compile, - getCompiler, - getErrors, - getModuleSource, - getResultFromBrowser, - getWarnings, -} from './helpers'; - -describe('"workerType" option', () => { - it('should use "Worker" by default', async () => { - const compiler = getCompiler('./basic/entry.js'); - const stats = await compile(compiler); - const result = await getResultFromBrowser(stats); - - expect(getModuleSource('./basic/worker.js', stats)).toMatchSnapshot( - 'module' - ); - expect(result).toMatchSnapshot('result'); - expect(getWarnings(stats)).toMatchSnapshot('warnings'); - expect(getErrors(stats)).toMatchSnapshot('errors'); - }); -});