├── .editorconfig ├── .github └── workflows │ └── main.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── loader.js ├── package.json ├── src ├── index.js ├── loader.js └── symbol.js └── test ├── _page.js ├── _server.js ├── _util.js ├── fixtures ├── basic │ ├── dep.js │ ├── entry.js │ ├── index.html │ └── worker.js ├── code-splitting │ ├── dep.js │ ├── entry.js │ ├── index.html │ └── worker.js ├── loader │ ├── entry.js │ └── worker.js ├── multiple │ ├── dep.js │ ├── entry.js │ ├── index.html │ ├── worker-0.js │ └── worker-1.js ├── named │ ├── entry.js │ ├── index.html │ └── worker.js ├── no-trailing-comma │ ├── entry.js │ ├── index.html │ └── worker.js ├── shared │ ├── dep.js │ ├── entry.js │ ├── index.html │ └── worker.js ├── skip-blobs │ └── entry.js ├── strict │ ├── entry.mjs │ ├── index.html │ └── worker.mjs ├── wasm │ ├── add.wasm │ ├── entry.js │ ├── index.html │ └── worker.js └── watch │ ├── dep.js │ ├── entry.js │ ├── index.html │ └── worker.js ├── index.test.js └── integration.test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - uses: actions/setup-node@v1 17 | with: 18 | node-version: 12 19 | - name: npm install, build, and test 20 | run: | 21 | npm install 22 | npm run build --if-present 23 | npm test 24 | env: 25 | CI: true 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | package-lock.json 5 | yarn.lock 6 | worker-plugin-*.tgz 7 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution, 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Code reviews 19 | 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. Consult 22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 23 | information on using pull requests. 24 | 25 | ## Community Guidelines 26 | 27 | This project follows [Google's Open Source Community 28 | Guidelines](https://opensource.google.com/conduct/). 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2018 Google Inc. 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | worker-plugin 3 |

4 |

👩‍🏭 worker-plugin

5 |

Automatically bundle & compile Web Workers within Webpack.

6 | 7 | 8 | ### Features 9 | 10 | Automatically compiles modules loaded in Web Workers: 11 | 12 | ```js 13 | const worker = new Worker('./foo.js', { type: 'module' }); 14 | ^^^^^^^^^^ 15 | gets bundled using webpack 16 | ``` 17 | 18 | The best part? That worker constructor works just fine without bundling turned on, but when bundled the result is **supported in all browsers** that support Web Workers - all the way back to IE 10! 19 | 20 | Workers with fully dynamic URLs, Blob URLs, data URLs or with no `{ type:'module' }` option are left unchanged. 21 | 22 | > _**Compatibility Note:** Webpack 5 now includes worker bundling. It uses a slightly different syntax:_
23 | > _`new Worker(new URL("./my_worker.js", import.meta.url))`_ 24 | 25 | ## Installation 26 | 27 | ```sh 28 | npm install -D worker-plugin 29 | ``` 30 | 31 | Then drop it into your **webpack.config.js:** 32 | 33 | ```diff 34 | + const WorkerPlugin = require('worker-plugin'); 35 | 36 | module.exports = { 37 | <...> 38 | plugins: [ 39 | + new WorkerPlugin() 40 | ] 41 | <...> 42 | } 43 | ``` 44 | 45 | > **Note:** If you're planning on having more than one worker, you'll need to make sure [`output.filename`](https://webpack.js.org/configuration/output/#outputfilename) is set to something dynamic, e.g. `"[name].bundle.js"` otherwise the generated filenames will overwrite one another. 46 | 47 | ## Usage 48 | 49 | **worker.js**: _(our worker module)_ 50 | 51 | ```js 52 | // This is a module worker, so we can use imports (in the browser too!) 53 | import { calculatePi } from './some-other-module'; 54 | 55 | addEventListener('message', event => { 56 | postMessage(calculatePi(event.data)); 57 | }); 58 | ``` 59 | 60 | **main.js**: _(our demo, on the main thread)_ 61 | 62 | ```js 63 | const piWorker = new Worker('./worker.js', { type: 'module' }); 64 | piWorker.onmessage = event => { 65 | console.log('pi: ' + event.data); 66 | }; 67 | piWorker.postMessage(42); 68 | ``` 69 | 70 | > **Note:** in order to ensure WorkerPlugin bundles your worker, make sure you're passing a **string** URL/filename to the Worker constructor. WorkerPlugin cannot bundle workers with dynamic/variable filenames, Blob or data URLs - it will leave them unmodified and print a warning during your build. 71 | 72 | ## Options 73 | 74 | In most cases, no options are necessary to use WorkerPlugin. 75 | 76 | ### `globalObject` _(string | false)_ 77 | 78 | WorkerPlugin will print a warning if your Webpack configuration has `output.globalObject` set to `window`, since doing so breaks Hot Module Replacement in web workers. 79 | 80 | If you're not using HMR and want to disable this warning, pass `globalObject:false`: 81 | 82 | ```js 83 | new WorkerPlugin({ 84 | // disable warnings about "window" breaking HMR: 85 | globalObject: false 86 | }) 87 | ``` 88 | 89 | To configure the value of `output.globalObject` for WorkerPlugin's internal Webpack Compiler, set `globalObject` to any String: 90 | 91 | ```js 92 | new WorkerPlugin({ 93 | // use "self" as the global object when receiving hot updates. 94 | globalObject: 'self' // <-- this is the default value 95 | }) 96 | ``` 97 | 98 | ### `plugins` _(array)_ 99 | 100 | By default, WorkerPlugin doesn't run any of your configured Webpack plugins when bundling worker code - this avoids running things like `html-webpack-plugin` twice. For cases where it's necessary to apply a plugin to Worker code, use the `plugins` option. 101 | 102 | Here you can specify the names of plugins to "copy" from your existing Webpack configuration, or provide specific plugins to apply only to worker code: 103 | 104 | ```js 105 | module.exports = { 106 | <...> 107 | plugins: [ 108 | // an example of a plugin already being used: 109 | new SomeExistingPlugin({ <...> }), 110 | 111 | new WorkerPlugin({ 112 | plugins: [ 113 | // A string here will copy the named plugin from your configuration: 114 | 'SomeExistingPlugin', 115 | 116 | // Or you can specify a plugin directly, only applied to Worker code: 117 | new SomePluginToApplyOnlyToWorkers({ <...> }) 118 | ] 119 | }) 120 | ] 121 | <...> 122 | } 123 | ``` 124 | 125 | ### `sharedWorker` _(boolean)_ 126 | 127 | If set to `true`, this option enables the bundling of [SharedWorker](https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker): 128 | 129 | ```js 130 | const shared = new SharedWorker('./my-shared-worker.js', { type: 'module' }); 131 | ``` 132 | 133 | ### `worker` _(boolean)_ 134 | 135 | If set to `false`, this option disables the bundling of [Worker]. Intended to be used with `{ sharedWorker: true }` to allow bundling of [SharedWorker] only without also bundling [Worker]. 136 | 137 | ### `preserveTypeModule` _(boolean)_ 138 | ### `workerType` _(string)_ 139 | 140 | Normally, WorkerPlugin will transform `new Worker('./a.js', { type: 'module' })` to completely remove the `type` option, outputting something like `new Worker('a.worker.js')`. This allows the plugin to compile Module Workers to Classic Workers, which are supported in all browsers. 141 | 142 | To instead retain `{type:'module'}` in bundled output, set the `preserveTypeModule` option to `true`: 143 | 144 | ```js 145 | plugins: [ 146 | new WorkerPlugin({ 147 | preserveTypeModule: true 148 | }) 149 | ] 150 | ``` 151 | 152 | Similarly, if you need to have WorkerPlugin output a specific `type` value, use the `workerType` option to specify it: 153 | 154 | ```js 155 | plugins: [ 156 | new WorkerPlugin({ 157 | workerType: 'foo' // note: this isn't a thing! 158 | }) 159 | ] 160 | ``` 161 | 162 | ## Loader 163 | 164 | At its core, worker-plugin provides two features: parsing and handling of `new Worker()`, and standalone bundling of modules for use in a different JavaScript context. 165 | 166 | If all you want is to compile separate bundles for a module, `worker-plugin/loader` provides the bundling functionality of worker-plugin as a standalone Webpack loader. This is useful for generating bundles for use in iframes, Service Workers or Worklets. Applying `worker-plugin/loader` to an import will bundle that module and return its URL: 167 | 168 | ```js 169 | import workerUrl from 'worker-plugin/loader!./my-worker'; 170 | 171 | console.log(workerUrl); // "/0.worker.js" 172 | 173 | CSS.paintWorklet.addModule(workerUrl); 174 | ``` 175 | 176 | Two options are available: 177 | 178 | | Option | Type | Description 179 | |---|---|:--| 180 | | `name` | _string_ | Controls the name of the generated chunk.
The name is used to generate a URL according to `output.chunkFilename`. 181 | | `esModule` | _boolean_ | Export the URL from an ES Module (`export default url`).
The default is CommonJS (`module.exports = url`). 182 | 183 | Options can be supplied inline: 184 | 185 | ```js 186 | import url from 'worker-plugin/loader?name=foo&esModule!./foo'; 187 | ``` 188 | 189 | ... or by setting up a loader alias: 190 | 191 | ```js 192 | // webpack.config.js to enable this: 193 | // import url from 'worker!./foo'; 194 | { 195 | resolveLoader: { 196 | alias: { 197 | worker: 'worker-plugin/loader?esModule' 198 | } 199 | } 200 | } 201 | ``` 202 | 203 | 204 | ## License 205 | 206 | Apache-2.0 207 | -------------------------------------------------------------------------------- /loader.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist/loader'); 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "worker-plugin", 3 | "version": "5.0.1", 4 | "description": "Webpack plugin to bundle Workers automagically.", 5 | "main": "dist/worker-plugin.js", 6 | "repository": "GoogleChromeLabs/worker-plugin", 7 | "scripts": { 8 | "build": "microbundle --raw --inline none --format cjs --no-compress src/*.js", 9 | "prepack": "npm run build", 10 | "dev": "jest --verbose --watchAll", 11 | "test": "npm run build && jest --verbose", 12 | "release": "npm t && git commit -am $npm_package_version && git tag $npm_package_version && git push && git push --tags && npm publish" 13 | }, 14 | "eslintConfig": { 15 | "extends": "eslint-config-standard", 16 | "env": { 17 | "browser": true, 18 | "jest": true 19 | }, 20 | "rules": { 21 | "indent": [ 22 | "error", 23 | 2 24 | ], 25 | "semi": [ 26 | "error", 27 | "always" 28 | ] 29 | } 30 | }, 31 | "jest": { 32 | "watchPathIgnorePatterns": [ 33 | "/node_modules/", 34 | "/test/fixtures/" 35 | ] 36 | }, 37 | "babel": { 38 | "plugins": [ 39 | "transform-es2015-modules-commonjs" 40 | ] 41 | }, 42 | "files": [ 43 | "src", 44 | "dist", 45 | "loader.js" 46 | ], 47 | "keywords": [ 48 | "webpack", 49 | "plugin", 50 | "worker", 51 | "web worker", 52 | "thread" 53 | ], 54 | "author": "The Chromium Authors", 55 | "contributors": [ 56 | { 57 | "name": "Jason Miller", 58 | "email": "developit@google.com" 59 | } 60 | ], 61 | "license": "Apache-2.0", 62 | "devDependencies": { 63 | "@file-services/memory": "^1.0.3", 64 | "@file-services/node": "^1.0.3", 65 | "@file-services/overlay": "^1.0.3", 66 | "babel-plugin-transform-es2015-modules-commonjs": "^6.26.2", 67 | "clean-webpack-plugin": "^1.0.0", 68 | "eslint": "^5.9.0", 69 | "eslint-config-standard": "^12.0.0", 70 | "eslint-plugin-import": "^2.14.0", 71 | "eslint-plugin-node": "^8.0.0", 72 | "eslint-plugin-promise": "^4.0.1", 73 | "eslint-plugin-standard": "^4.0.0", 74 | "jest": "^23.6.0", 75 | "memory-fs": "^0.4.1", 76 | "microbundle": "^0.8.2", 77 | "puppeteer": "^3.0.2", 78 | "serve-handler": "^5.0.7", 79 | "terser-webpack-plugin": "^1.1.0", 80 | "webpack": "^4.26.1" 81 | }, 82 | "dependencies": { 83 | "loader-utils": "^1.1.0" 84 | }, 85 | "peerDependencies": { 86 | "webpack": ">= 4" 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | import path from 'path'; 18 | import WORKER_PLUGIN_SYMBOL from './symbol'; 19 | let ParserHelpers; 20 | try { 21 | ParserHelpers = require('webpack/lib/javascript/JavascriptParserHelpers'); // Webpack 5 22 | } catch (e) {} 23 | ParserHelpers = ParserHelpers || require('webpack/lib/ParserHelpers'); // Webpack 4 24 | let HarmonyImportSpecifierDependency; 25 | try { 26 | HarmonyImportSpecifierDependency = require('webpack/lib/dependencies/HarmonyImportSpecifierDependency'); 27 | } catch (e) {} 28 | 29 | const NAME = 'WorkerPlugin'; 30 | const workerLoader = path.resolve(__dirname, 'loader.js'); 31 | 32 | export default class WorkerPlugin { 33 | constructor (options) { 34 | this.options = options || {}; 35 | this[WORKER_PLUGIN_SYMBOL] = true; 36 | } 37 | 38 | apply (compiler) { 39 | compiler.hooks.normalModuleFactory.tap(NAME, factory => { 40 | let workerId = 0; 41 | factory.hooks.parser.for('javascript/auto').tap(NAME, parser => parse(parser, false)); 42 | factory.hooks.parser.for('javascript/dynamic').tap(NAME, parser => parse(parser, false)); 43 | factory.hooks.parser.for('javascript/esm').tap(NAME, parser => parse(parser, true)); 44 | 45 | const parse = (parser, esModule) => { 46 | const handleWorker = workerTypeString => expr => { 47 | const dep = parser.evaluateExpression(expr.arguments[0]); 48 | 49 | const optsExpr = expr.arguments[1]; 50 | let hasInitOptions = false; 51 | let typeModuleExpr; 52 | let opts; 53 | if (optsExpr && optsExpr.properties) { 54 | opts = {}; 55 | for (let i = optsExpr.properties.length; i--;) { 56 | const prop = optsExpr.properties[i]; 57 | if (prop.type === 'Property' && !prop.computed && !prop.shorthand && !prop.method) { 58 | opts[prop.key.name] = parser.evaluateExpression(prop.value).string; 59 | 60 | if (prop.key.name === 'type') { 61 | typeModuleExpr = prop; 62 | } else { 63 | hasInitOptions = true; 64 | } 65 | } 66 | } 67 | } 68 | 69 | if (!opts || opts.type !== 'module') { 70 | // If an unknown type value is passed, it's probably an error and we can warn the developer: 71 | if (opts && opts.type !== 'classic') { 72 | parser.state.module.warnings.push({ 73 | message: `new ${workerTypeString}() will only be bundled if passed options that include { type: 'module' }.${opts ? `\n Received: new ${workerTypeString}()(${JSON.stringify(dep.string)}, ${JSON.stringify(opts)})` : ''}` 74 | }); 75 | } 76 | return false; 77 | } 78 | 79 | if (!dep.isString()) { 80 | parser.state.module.warnings.push({ 81 | message: `new ${workerTypeString}("..", { type: "module" }) will only be bundled if passed a String.` 82 | }); 83 | return false; 84 | } 85 | 86 | const isStrictModule = esModule || (parser.state.buildMeta && parser.state.buildMeta.strictHarmonyModule); 87 | 88 | // Querystring-encoded loader prefix (faster/cleaner than JSON parameters): 89 | const loaderRequest = `${workerLoader}?name=${encodeURIComponent(opts.name || workerId)}${isStrictModule ? '&esModule' : ''}!${dep.string}`; 90 | 91 | // Unique ID for the worker URL variable: 92 | const id = `__webpack__worker__${workerId++}`; 93 | 94 | // .mjs / strict harmony mode 95 | if (isStrictModule) { 96 | const module = parser.state.current; 97 | 98 | if (!HarmonyImportSpecifierDependency) { 99 | throw Error(`${NAME}: Failed to import HarmonyImportSpecifierDependency. This plugin requires Webpack version 4.`); 100 | } 101 | 102 | // This is essentially the internals of "prepend an import to the module": 103 | const dependency = new HarmonyImportSpecifierDependency( 104 | loaderRequest, 105 | module, 106 | workerId, // no idea if this actually needs to be unique. 0 seemed to work. safety first? 107 | parser.scope, 108 | 'default', 109 | id, // this never gets used 110 | expr.arguments[0].range, // replace the usage/callsite with the generated reference: X_IMPORT_0["default"] 111 | true 112 | ); 113 | // avoid serializing the full loader filepath: (this gets prepended to unique suffix) 114 | dependency.userRequest = dep.string; 115 | 116 | module.addDependency(dependency); 117 | } else { 118 | // For CommonJS/Auto 119 | const req = `require(${JSON.stringify(loaderRequest)})`; 120 | ParserHelpers.toConstantDependency(parser, id)(expr.arguments[0]); 121 | ParserHelpers.addParsedVariableToModule(parser, id, req); 122 | } 123 | 124 | // update/remove the WorkerInitOptions argument 125 | if (this.options.workerType) { 126 | ParserHelpers.toConstantDependency(parser, JSON.stringify(this.options.workerType))(typeModuleExpr.value); 127 | } else if (this.options.preserveTypeModule !== true) { 128 | if (hasInitOptions) { 129 | // there might be other options - to avoid trailing comma issues, replace the type value with undefined but *leave the key*: 130 | ParserHelpers.toConstantDependency(parser, 'type:undefined')(typeModuleExpr); 131 | } else { 132 | // there was only a `{type}` option, we replace the opts argument with undefined to avoid trailing comma issues: 133 | ParserHelpers.toConstantDependency(parser, 'undefined')(optsExpr); 134 | } 135 | } 136 | 137 | return true; 138 | }; 139 | 140 | if (this.options.worker !== false) { 141 | parser.hooks.new.for('Worker').tap(NAME, handleWorker('Worker')); 142 | } 143 | if (this.options.sharedWorker) { 144 | parser.hooks.new.for('SharedWorker').tap(NAME, handleWorker('SharedWorker')); 145 | } 146 | }; 147 | }); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/loader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | import loaderUtils from 'loader-utils'; 18 | import SingleEntryPlugin from 'webpack/lib/SingleEntryPlugin'; 19 | import WebWorkerTemplatePlugin from 'webpack/lib/webworker/WebWorkerTemplatePlugin'; 20 | import WORKER_PLUGIN_SYMBOL from './symbol'; 21 | 22 | let FetchCompileWasmPlugin; 23 | try { 24 | FetchCompileWasmPlugin = require('webpack/lib/web/FetchCompileWasmPlugin'); // Webpack 5 25 | } catch (e) {} 26 | FetchCompileWasmPlugin = FetchCompileWasmPlugin || require('webpack/lib/web/FetchCompileWasmTemplatePlugin'); // Webpack 4 27 | 28 | const NAME = 'WorkerPluginLoader'; 29 | let hasWarned = false; 30 | 31 | export function pitch (request) { 32 | this.cacheable(false); 33 | const cb = this.async(); 34 | 35 | const compilerOptions = this._compiler.options || {}; 36 | 37 | const plugin = compilerOptions.plugins.find(p => p[WORKER_PLUGIN_SYMBOL]) || {}; 38 | const pluginOptions = (plugin && plugin.options) || {}; 39 | 40 | if (pluginOptions.globalObject == null && !hasWarned && compilerOptions.output && compilerOptions.output.globalObject === 'window') { 41 | hasWarned = true; 42 | console.warn('Warning (worker-plugin): output.globalObject is set to "window". It must be set to "self" to support HMR in Workers.'); 43 | } 44 | 45 | const options = loaderUtils.getOptions(this) || {}; 46 | const chunkFilename = compilerOptions.output.chunkFilename.replace(/\.([a-z]+)(\?.+)?$/i, '.worker.$1$2'); 47 | const workerOptions = { 48 | filename: (options.filename || pluginOptions.filename || chunkFilename).replace(/\[(?:chunkhash|contenthash)(:\d+(?::\d+)?)?\]/g, '[hash$1]'), 49 | chunkFilename: options.chunkFilename || pluginOptions.chunkFilename || chunkFilename, 50 | globalObject: pluginOptions.globalObject || 'self' 51 | }; 52 | 53 | const plugins = (pluginOptions.plugins || []).map(plugin => { 54 | if (typeof plugin !== 'string') { 55 | return plugin; 56 | } 57 | const found = compilerOptions.plugins.find(p => p.constructor.name === plugin); 58 | if (!found) { 59 | console.warn(`Warning (worker-plugin): Plugin "${plugin}" is not found.`); 60 | } 61 | return found; 62 | }); 63 | 64 | const workerCompiler = this._compilation.createChildCompiler(NAME, workerOptions, plugins); 65 | workerCompiler.context = this._compiler.context; 66 | (new WebWorkerTemplatePlugin()).apply(workerCompiler); 67 | (new FetchCompileWasmPlugin({ 68 | mangleImports: compilerOptions.optimization.mangleWasmImports 69 | })).apply(workerCompiler); 70 | (new SingleEntryPlugin(this.context, request, options.name)).apply(workerCompiler); 71 | 72 | const subCache = `subcache ${__dirname} ${request}`; 73 | workerCompiler.hooks.compilation.tap(NAME, compilation => { 74 | if (compilation.cache) { 75 | if (!compilation.cache[subCache]) compilation.cache[subCache] = {}; 76 | compilation.cache = compilation.cache[subCache]; 77 | } 78 | }); 79 | 80 | workerCompiler.runAsChild((err, entries, compilation) => { 81 | if (!err && compilation.errors && compilation.errors.length) { 82 | err = compilation.errors[0]; 83 | } 84 | const entry = entries && entries[0] && entries[0].files.values().next().value; // compatible with Array (v4) and Set (v5) prototypes 85 | if (!err && !entry) err = Error(`WorkerPlugin: no entry for ${request}`); 86 | if (err) return cb(err); 87 | return cb(null, `${options.esModule ? 'export default' : 'module.exports ='} __webpack_public_path__ + ${JSON.stringify(entry)}`); 88 | }); 89 | }; 90 | 91 | export default { pitch }; 92 | -------------------------------------------------------------------------------- /src/symbol.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | const WORKER_PLUGIN_SYMBOL = Symbol('WORKER_PLUGIN_SYMBOL'); 18 | export default WORKER_PLUGIN_SYMBOL; 19 | -------------------------------------------------------------------------------- /test/_page.js: -------------------------------------------------------------------------------- 1 | import puppeteer from 'puppeteer'; 2 | 3 | export async function evaluatePage (url, matches, timeout = 10000) { 4 | const args = await puppeteer.defaultArgs(); 5 | const browser = await puppeteer.launch({ 6 | args: [ 7 | ...args, 8 | '--no-sandbox', 9 | '--disable-setuid-sandbox', 10 | '--enable-experimental-web-platform-features' 11 | ] 12 | }); 13 | const page = await browser.newPage(); 14 | await page.goto(url); 15 | 16 | try { 17 | return await new Promise((resolve, reject) => { 18 | page.on('console', msg => { 19 | const text = msg.text(); 20 | if (text.match(matches)) { 21 | clearTimeout(timer); 22 | resolve(text); 23 | } 24 | }); 25 | 26 | const timer = setTimeout(() => reject(Error('Timed Out')), timeout); 27 | }); 28 | } finally { 29 | await browser.close(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/_server.js: -------------------------------------------------------------------------------- 1 | import http from 'http'; 2 | import handler from 'serve-handler'; 3 | 4 | export function createStaticServer (pathRoot) { 5 | const opts = { 6 | public: pathRoot 7 | }; 8 | 9 | const server = http.createServer((request, response) => handler(request, response, opts)); 10 | 11 | server.stop = () => new Promise((resolve, reject) => { 12 | server.close(error => { 13 | error ? reject(error) : resolve(); 14 | }); 15 | }); 16 | 17 | return new Promise((resolve, reject) => { 18 | server.on('error', reject); 19 | server.listen(0, '0.0.0.0', () => { 20 | const { address, port } = server.address(); 21 | server.url = `http://${address}:${port}`; 22 | resolve(server); 23 | }); 24 | }); 25 | }; 26 | -------------------------------------------------------------------------------- /test/_util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | import path from 'path'; 18 | import webpack from 'webpack'; 19 | import CleanPlugin from 'clean-webpack-plugin'; 20 | import TerserPlugin from 'terser-webpack-plugin'; 21 | 22 | export function sleep (ms) { 23 | return new Promise(resolve => setTimeout(resolve, ms)); 24 | } 25 | 26 | /** 27 | * @param {string} fixture 28 | * @param {{ terserOptions?: import('terser').MinifyOptions } & Partial} [options] 29 | */ 30 | export function runWebpack (fixture, { output, plugins, terserOptions, ...config } = {}) { 31 | return run(callback => webpack({ 32 | mode: 'production', 33 | devtool: false, 34 | context: path.resolve(__dirname, 'fixtures', fixture), 35 | entry: './entry', 36 | output: { 37 | publicPath: 'dist/', 38 | path: path.resolve(__dirname, 'fixtures', fixture, 'dist'), 39 | ...(output || {}) 40 | }, 41 | optimization: { 42 | minimizer: [ 43 | new TerserPlugin({ 44 | terserOptions: terserOptions || { 45 | mangle: false, 46 | output: { 47 | beautify: true 48 | } 49 | }, 50 | sourceMap: false 51 | }) 52 | ] 53 | }, 54 | plugins: [ 55 | new CleanPlugin([ 56 | path.resolve(__dirname, 'fixtures', fixture, 'dist', '**') 57 | ], {}), 58 | ...(Array.isArray(plugins) ? plugins : []) 59 | ], 60 | ...config 61 | }, callback)); 62 | } 63 | 64 | /** 65 | * @param {string} fixture 66 | * @param {Partial} [options] 67 | */ 68 | export function watchWebpack (fixture, { output, plugins, context, terserOptions, ...config } = {}) { 69 | context = context || path.resolve(__dirname, 'fixtures', fixture); 70 | /** @type {Partial }>} */ 71 | const compiler = webpack({ 72 | mode: 'production', 73 | context, 74 | entry: './entry.js', 75 | output: { 76 | publicPath: 'dist/', 77 | path: path.resolve(context, 'dist'), 78 | ...(output || {}) 79 | }, 80 | optimization: { 81 | minimize: true, 82 | minimizer: [ 83 | new TerserPlugin({ 84 | terserOptions: terserOptions || { 85 | mangle: false, 86 | output: { 87 | beautify: true 88 | } 89 | }, 90 | sourceMap: false 91 | }) 92 | ] 93 | }, 94 | plugins: plugins || [] 95 | }); 96 | // compiler.watch({}); 97 | compiler.doRun = () => run(compiler.run.bind(compiler)); 98 | return compiler; 99 | } 100 | 101 | export class CountApplyWebpackPlugin { 102 | constructor () { 103 | this.count = 0; 104 | } 105 | apply () { 106 | this.count++; 107 | } 108 | } 109 | 110 | export function statsWithAssets (stats) { 111 | stats.assets = Object.keys(stats.compilation.assets).reduce((acc, name) => { 112 | acc[name] = stats.compilation.assets[name].source(); 113 | return acc; 114 | }, {}); 115 | return stats; 116 | } 117 | 118 | function run (runner) { 119 | return new Promise((resolve, reject) => { 120 | runner((err, stats) => { 121 | if (err) return reject(err); 122 | 123 | statsWithAssets(stats); 124 | 125 | stats.info = stats.toJson({ assets: true, chunks: true }); 126 | 127 | if (stats.hasWarnings()) { 128 | stats.info.warnings.forEach(warning => { 129 | console.warn('Webpack warning: ', warning); 130 | }); 131 | console.warn('\nWebpack build generated ' + stats.info.warnings.length + ' warnings(s), shown above.\n\n'); 132 | } 133 | if (stats.hasErrors()) { 134 | return reject(stats.info.errors.join('\n')); 135 | } 136 | resolve(stats); 137 | }); 138 | }); 139 | } 140 | -------------------------------------------------------------------------------- /test/fixtures/basic/dep.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | export const foo = 'bar'; 18 | -------------------------------------------------------------------------------- /test/fixtures/basic/entry.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | const worker = new Worker('./worker', { type: 'module', }); 18 | worker.onmessage = ({ data }) => { 19 | console.log('page got data: ', data); 20 | }; 21 | worker.postMessage('hello'); 22 | -------------------------------------------------------------------------------- /test/fixtures/basic/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /test/fixtures/basic/worker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | import { foo } from './dep'; 18 | 19 | console.log('hello from worker'); 20 | 21 | addEventListener('message', ({ data }) => { 22 | console.log('worker got message', data); 23 | if (data === 'hello') { 24 | postMessage(foo); 25 | } 26 | }); 27 | -------------------------------------------------------------------------------- /test/fixtures/code-splitting/dep.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | export const foo = 'bar'; 18 | -------------------------------------------------------------------------------- /test/fixtures/code-splitting/entry.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | const worker = new Worker('./worker', { type: 'module' }); 18 | worker.onmessage = ({ data }) => { 19 | console.log('page got data: ', data); 20 | }; 21 | worker.postMessage('hello'); 22 | -------------------------------------------------------------------------------- /test/fixtures/code-splitting/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /test/fixtures/code-splitting/worker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | console.log('hello from worker'); 18 | 19 | addEventListener('message', ({ data }) => { 20 | console.log('worker got message', data); 21 | import('./dep').then(m => { 22 | if (data === 'hello') { 23 | postMessage(m.foo); 24 | } 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /test/fixtures/loader/entry.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | import workerUrl from 'worker-plugin/loader!./worker'; 18 | console.log(workerUrl); 19 | -------------------------------------------------------------------------------- /test/fixtures/loader/worker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | console.log('hello from worker'); 18 | -------------------------------------------------------------------------------- /test/fixtures/multiple/dep.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | export const foo = 'bar'; 18 | -------------------------------------------------------------------------------- /test/fixtures/multiple/entry.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | const worker0 = new Worker('./worker-0', { type: 'module' }); 18 | const worker1 = new Worker('./worker-1', { type: 'module' }); 19 | 20 | worker0.onmessage = ({ data }) => { 21 | console.log('page got data: ', data); 22 | }; 23 | worker0.postMessage('hello 0'); 24 | 25 | worker1.onmessage = ({ data }) => { 26 | console.log('page got data: ', data); 27 | }; 28 | worker1.postMessage('hello 1'); 29 | -------------------------------------------------------------------------------- /test/fixtures/multiple/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /test/fixtures/multiple/worker-0.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | import { foo } from './dep'; 18 | 19 | console.log('hello from worker 0'); 20 | 21 | addEventListener('message', ({ data }) => { 22 | console.log('worker 0 got message', data); 23 | if (data === 'hello 0') { 24 | postMessage(foo); 25 | } 26 | }); 27 | -------------------------------------------------------------------------------- /test/fixtures/multiple/worker-1.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | import { foo } from './dep'; 18 | 19 | console.log('hello from worker 1'); 20 | 21 | addEventListener('message', ({ data }) => { 22 | console.log('worker 1 got message', data); 23 | if (data === 'hello 1') { 24 | postMessage(foo); 25 | } 26 | }); 27 | -------------------------------------------------------------------------------- /test/fixtures/named/entry.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | export default new Worker('./worker', { name: 'foo', type: 'module' }); 18 | -------------------------------------------------------------------------------- /test/fixtures/named/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /test/fixtures/named/worker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | console.log('hello from worker'); 18 | -------------------------------------------------------------------------------- /test/fixtures/no-trailing-comma/entry.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | const workerOne = new Worker('./worker', { type: 'module', }); 18 | workerOne.onmessage = ({ data }) => { 19 | console.log('page got data: ', data); 20 | }; 21 | 22 | const workerTwo = new Worker('./worker', { type: 'module', name: 'foo' }); 23 | workerTwo.onmessage = ({ data }) => { 24 | console.log('page got data: ', data); 25 | }; 26 | -------------------------------------------------------------------------------- /test/fixtures/no-trailing-comma/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /test/fixtures/no-trailing-comma/worker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | 18 | console.log('hello from worker'); 19 | -------------------------------------------------------------------------------- /test/fixtures/shared/dep.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | export const foo = 'bar'; 18 | -------------------------------------------------------------------------------- /test/fixtures/shared/entry.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | const worker = new SharedWorker('./worker', { type: 'module' }); 18 | worker.port.onmessage = ({ data }) => { 19 | console.log('page got data: ', data); 20 | }; 21 | worker.port.start(); 22 | worker.port.postMessage('hello'); 23 | -------------------------------------------------------------------------------- /test/fixtures/shared/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /test/fixtures/shared/worker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | import { foo } from './dep'; 18 | 19 | console.log('hello from worker'); 20 | 21 | onconnect = (e) => { 22 | var port = e.ports[0]; 23 | 24 | port.onmessage = ({ data }) => { 25 | console.log('worker got message', data); 26 | if (data === 'hello') { 27 | port.postMessage(foo); 28 | } 29 | }; 30 | } -------------------------------------------------------------------------------- /test/fixtures/skip-blobs/entry.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | const worker = new Worker(new Blob(['onmessage=()=>{postMessage("right back at ya")}'])); 18 | worker.onmessage = ({ data }) => { 19 | console.log('page got data: ', data); 20 | }; 21 | worker.postMessage('hello'); 22 | -------------------------------------------------------------------------------- /test/fixtures/strict/entry.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | const worker = new Worker('./worker', { type: 'module' }); 18 | worker.onmessage = ({ data }) => { 19 | console.log('page got data: ', data); 20 | }; 21 | -------------------------------------------------------------------------------- /test/fixtures/strict/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /test/fixtures/strict/worker.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | console.log('loaded worker'); 18 | postMessage('hello'); 19 | -------------------------------------------------------------------------------- /test/fixtures/wasm/add.wasm: -------------------------------------------------------------------------------- 1 | asm`add 2 |  j nameadd lhsrhs -------------------------------------------------------------------------------- /test/fixtures/wasm/entry.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | export default new Worker('./worker', { name: 'wasm', type: 'module' }); 18 | -------------------------------------------------------------------------------- /test/fixtures/wasm/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /test/fixtures/wasm/worker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | (async () => { 18 | const wasm = await import('./add'); 19 | console.log('worker: %s', wasm.add(10, 20)); 20 | })().catch(console.error); 21 | -------------------------------------------------------------------------------- /test/fixtures/watch/dep.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | export const foo = 'bar'; 18 | -------------------------------------------------------------------------------- /test/fixtures/watch/entry.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | const worker = new Worker('./worker', { type: 'module' }); 18 | worker.onmessage = ({ data }) => { 19 | console.log('page got data: ', data); 20 | }; 21 | worker.postMessage('hello'); 22 | -------------------------------------------------------------------------------- /test/fixtures/watch/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /test/fixtures/watch/worker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | import { foo } from './dep'; 18 | 19 | console.log('hello from worker'); 20 | 21 | addEventListener('message', ({ data }) => { 22 | console.log('worker got message', data); 23 | if (data === 'hello') { 24 | postMessage(foo); 25 | } 26 | }); 27 | -------------------------------------------------------------------------------- /test/index.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | import { resolve } from 'path'; 18 | import { readFileSync, writeFileSync } from 'fs'; 19 | import WorkerPlugin from '../src'; 20 | import { runWebpack, CountApplyWebpackPlugin, watchWebpack, statsWithAssets, sleep } from './_util'; 21 | 22 | jest.setTimeout(30000); 23 | 24 | describe('worker-plugin', () => { 25 | test('exports a class', () => { 26 | expect(WorkerPlugin).toBeInstanceOf(Function); 27 | const inst = new WorkerPlugin(); 28 | expect(inst).toBeInstanceOf(WorkerPlugin); 29 | expect(inst).toHaveProperty('apply', expect.any(Function)); 30 | }); 31 | 32 | test('it replaces Worker constructor with require(worker-loader)', async () => { 33 | const stats = await runWebpack('basic', { 34 | plugins: [ 35 | new WorkerPlugin() 36 | ] 37 | }); 38 | 39 | const assetNames = Object.keys(stats.assets); 40 | expect(assetNames).toHaveLength(2); 41 | expect(assetNames).toContainEqual('0.worker.js'); 42 | 43 | const main = stats.assets['main.js']; 44 | expect(main).toMatch(/[^\n]*new\s+Worker\s*\([^)]*\)[^\n]*/g); 45 | expect(main).toMatch(/module.exports = __webpack_require__\.p\s*\+\s*"0\.worker\.js"/g); 46 | }); 47 | 48 | test('it replaces multiple Worker exports with __webpack_require__', async () => { 49 | const stats = await runWebpack('multiple', { 50 | plugins: [ 51 | new WorkerPlugin() 52 | ] 53 | }); 54 | 55 | const assetNames = Object.keys(stats.assets); 56 | expect(assetNames).toHaveLength(3); 57 | expect(assetNames).toContainEqual('0.worker.js'); 58 | expect(assetNames).toContainEqual('1.worker.js'); 59 | 60 | const main = stats.assets['main.js']; 61 | expect(main).toMatch(/module.exports = __webpack_require__\.p\s*\+\s*"0\.worker\.js"/g); 62 | expect(main).toMatch(/module.exports = __webpack_require__\.p\s*\+\s*"1\.worker\.js"/g); 63 | }); 64 | 65 | test('retainModule:true leaves {type:module} in worker init', async () => { 66 | const { assets } = await runWebpack('basic', { 67 | plugins: [ 68 | new WorkerPlugin({ 69 | preserveTypeModule: true 70 | }) 71 | ] 72 | }); 73 | 74 | const workerInit = assets['main.js'].match(/[^\n]*new\s+Worker\s*\([^)]*\)[^\n]*/g)[0]; 75 | expect(workerInit).toMatch(/new\s+Worker\s*\(\s*__webpack__worker__\d\s*(,\s*\{[\s\n]*type\s*:\s*"module"[\s\n]*\}\s*)?\)/g); 76 | }); 77 | 78 | test('workerType:x modifies the resulting {type} in worker init', async () => { 79 | const { assets } = await runWebpack('basic', { 80 | plugins: [ 81 | new WorkerPlugin({ 82 | workerType: 'classic' 83 | }) 84 | ] 85 | }); 86 | 87 | const workerInit = assets['main.js'].match(/[^\n]*new\s+Worker\s*\([^)]*\)[^\n]*/g)[0]; 88 | expect(workerInit).toMatch(/new\s+Worker\s*\(\s*__webpack__worker__\d\s*(,\s*\{[\s\n]*type\s*:\s*"classic"[\s\n]*\}\s*)?\)/g); 89 | }); 90 | 91 | test('it does not enable other plugins when building worker script', async () => { 92 | const countPlugin = new CountApplyWebpackPlugin(); 93 | await runWebpack('basic', { 94 | plugins: [ 95 | countPlugin, 96 | new WorkerPlugin() 97 | ] 98 | }); 99 | expect(countPlugin.count).toStrictEqual(1); 100 | }); 101 | 102 | test('plugins: instance enables plugins when building worker script', async () => { 103 | const countPlugin = new CountApplyWebpackPlugin(); 104 | await runWebpack('basic', { 105 | plugins: [ 106 | new WorkerPlugin({ 107 | plugins: [countPlugin] 108 | }) 109 | ] 110 | }); 111 | expect(countPlugin.count).toStrictEqual(1); 112 | }); 113 | 114 | test('plugins: string passes plugins from main config', async () => { 115 | const countPlugin = new CountApplyWebpackPlugin(); 116 | await runWebpack('basic', { 117 | plugins: [ 118 | countPlugin, 119 | new WorkerPlugin({ 120 | plugins: ['CountApplyWebpackPlugin'] 121 | }) 122 | ] 123 | }); 124 | expect(countPlugin.count).toStrictEqual(2); 125 | }); 126 | 127 | test('it uses the Worker constructor\'s name option and chunkFilename to generate asset filenames', async () => { 128 | const stats = await runWebpack('named', { 129 | output: { 130 | chunkFilename: '[name].[hash:5].js' 131 | }, 132 | plugins: [ 133 | new WorkerPlugin() 134 | ] 135 | }); 136 | 137 | const assetNames = Object.keys(stats.assets); 138 | expect(assetNames).toHaveLength(2); 139 | expect(assetNames).toContainEqual(expect.stringMatching(/^foo\.[a-zA-Z0-9]+\.worker\.js$/)); 140 | expect(stats.assets['main.js']).toMatch(/module.exports = __webpack_require__\.p\s*\+\s*"foo\.[a-zA-Z0-9]+\.worker\.js"/g); 141 | }); 142 | 143 | describe('options.filename / options.chunkFilename', () => { 144 | test('it uses the provided filename when specified', async () => { 145 | const stats = await runWebpack('named', { 146 | plugins: [ 147 | new WorkerPlugin({ 148 | filename: 'my-custom-name.[hash:3].js' 149 | }) 150 | ] 151 | }); 152 | 153 | const assetNames = Object.keys(stats.assets); 154 | expect(assetNames).toHaveLength(2); 155 | expect(assetNames).toContainEqual(expect.stringMatching(/^my-custom-name\.[a-zA-Z0-9]{3}\.js$/)); 156 | expect(stats.assets['main.js']).toMatch(/module.exports = __webpack_require__\.p\s*\+\s*"my-custom-name\.[a-zA-Z0-9]{3}\.js"/g); 157 | }); 158 | 159 | test('it supports [name] in filename templates', async () => { 160 | const stats = await runWebpack('named', { 161 | plugins: [ 162 | new WorkerPlugin({ 163 | filename: '[name]_worker.js' 164 | }) 165 | ] 166 | }); 167 | 168 | const assetNames = Object.keys(stats.assets); 169 | expect(assetNames).toHaveLength(2); 170 | expect(assetNames).toContainEqual(expect.stringMatching(/^foo_worker\.js$/)); 171 | expect(stats.assets['main.js']).toMatch(/module.exports = __webpack_require__\.p\s*\+\s*"foo_worker\.js"/g); 172 | }); 173 | 174 | test('it supports custom chunkFilename templates when code-splitting', async () => { 175 | const stats = await runWebpack('code-splitting', { 176 | output: { 177 | publicPath: '/dist/' 178 | }, 179 | plugins: [ 180 | new WorkerPlugin({ 181 | filename: 'worker.js', 182 | chunkFilename: '[id]_worker_chunk.js' 183 | }) 184 | ] 185 | }); 186 | 187 | const assetNames = Object.keys(stats.assets); 188 | expect(assetNames).toHaveLength(3); 189 | expect(assetNames).toContainEqual(expect.stringMatching(/^worker\.js$/)); 190 | expect(assetNames).toContainEqual(expect.stringMatching(/^1_worker_chunk\.js$/)); 191 | expect(stats.assets['main.js']).toMatch(/module.exports = __webpack_require__\.p\s*\+\s*"worker\.js"/g); 192 | }); 193 | }); 194 | 195 | test('it bundles WASM file which imported dynamically', async () => { 196 | const stats = await runWebpack('wasm', { 197 | plugins: [ 198 | new WorkerPlugin() 199 | ] 200 | }); 201 | 202 | const assetNames = Object.keys(stats.assets); 203 | expect(assetNames).toHaveLength(4); 204 | expect(assetNames).toContainEqual(expect.stringMatching(/^[a-zA-Z0-9]+\.module\.wasm$/)); 205 | expect(stats.assets['wasm.worker.js']).toMatch(/WebAssembly\.instantiate/); 206 | }); 207 | 208 | test('it skips Worker constructor with non-string 1st argument', async () => { 209 | const stats = await runWebpack('skip-blobs', { 210 | plugins: [ 211 | new WorkerPlugin() 212 | ] 213 | }); 214 | 215 | const assetNames = Object.keys(stats.assets); 216 | expect(assetNames).toHaveLength(1); 217 | 218 | expect(stats.assets['main.js']).not.toMatch(/new\s+Worker\s*\(\s*__webpack__worker__\d\s*(,\s*\{[\s\n]*type\s*:\s*"module"[\s\n]*\}\s*)?\)/g); 219 | 220 | expect(stats.assets['main.js']).toMatch(/new\s+Worker\s*\(\s*new\s+Blob\s*\(\s*\[\s*'onmessage=\(\)=>\{postMessage\("right back at ya"\)\}'\s*\]\s*\)\s*\)/g); 221 | }); 222 | 223 | describe('ESM strict mode', () => { 224 | test('it should work in strict ESM mode', async () => { 225 | const stats = await runWebpack('strict', { 226 | plugins: [ 227 | new WorkerPlugin() 228 | ], 229 | mode: 'development' 230 | }); 231 | 232 | const assetNames = Object.keys(stats.assets); 233 | expect(assetNames).toHaveLength(2); 234 | expect(assetNames).toContainEqual('0.worker.js'); 235 | 236 | const main = stats.assets['main.js']; 237 | expect(main).toMatch(/[^\n]*new Worker\s*\([^)]*\)[^\n]*/g); 238 | 239 | const log = main.match(/new Worker\s*\(([^)]*)\)[^\n]*/)[1]; 240 | expect(log).toMatch(/_worker__WEBPACK_IMPORTED_MODULE_\d__\["default"\]/gi); 241 | 242 | // should also put the loader into ESM mode: 243 | expect(main).toMatch(/__webpack_exports__\["default"\]\s*=\s*\(?\s*__webpack_require__\.p\s*\+\s*"0\.worker\.js"\)?;?/g); 244 | // the output (in dev mode) looks like this: 245 | // /* harmony default export */ __webpack_exports__[\"default\"] = (__webpack_require__.p + \"0.worker.js\"); 246 | }); 247 | 248 | test('it should inline for production', async () => { 249 | const stats = await runWebpack('strict', { 250 | plugins: [ 251 | new WorkerPlugin() 252 | ], 253 | terserOptions: { 254 | compress: { 255 | pure_getters: true 256 | } 257 | } 258 | }); 259 | 260 | const assetNames = Object.keys(stats.assets); 261 | expect(assetNames).toHaveLength(2); 262 | expect(assetNames).toContainEqual('0.worker.js'); 263 | 264 | const main = stats.assets['main.js']; 265 | expect(main).toMatch(/[^\n]*new Worker\s*\([^)]*\)[^\n]*/g); 266 | 267 | const log = main.match(/new Worker\s*\(([^)]*)\)[^\n]*/)[1]; 268 | expect(log).toMatch(/^[a-z0-9$_]+\.p\s*\+\s*(['"])0\.worker\.js\1/gi); 269 | 270 | // shouldn't be any trace of the intermediary url provider module left 271 | expect(main).not.toMatch(/export default/g); 272 | }); 273 | }); 274 | 275 | test('should not emit trailing commas', async () => { 276 | const stats = await runWebpack('no-trailing-comma', { 277 | plugins: [ 278 | new WorkerPlugin() 279 | ] 280 | }); 281 | 282 | const assetNames = Object.keys(stats.assets); 283 | expect(assetNames).toHaveLength(3); 284 | 285 | // As it replaces the value of the `type` property with `undefined` 286 | // it will emit a string that contains line breaks, like: 287 | // `{\n type: void 0 \n}`. 288 | // We have to replace those line breaks thus it will become one-line string, like: 289 | const main = stats.assets['main.js'].replace(/\n/g, ''); 290 | 291 | // Verify that we replace the second parameter when it's `{ type: module }` with `undefined` 292 | // and there are no trailing commas. 293 | // Match `new Worker(__webpack__worker__0, { type: void 0 })` 294 | expect(main).toMatch(/new Worker\s*\(__webpack__worker__\d, void 0\)/); 295 | 296 | // Match `new Worker(__webpack__worker__0, { type: void 0, name: "foo" })` 297 | expect(main).toMatch(/new Worker\s*\(__webpack__worker__\d, {\s*type: void 0,\s*name: "foo"\s*}\)/); 298 | }); 299 | 300 | describe('worker-plugin/loader', () => { 301 | test('it returns a URL when applied to an import', async () => { 302 | const stats = await runWebpack('loader', { 303 | resolveLoader: { 304 | alias: { 305 | 'worker-plugin/loader': resolve(__dirname, '../loader.js') 306 | } 307 | } 308 | }); 309 | 310 | const assetNames = Object.keys(stats.assets); 311 | expect(assetNames).toHaveLength(2); 312 | expect(assetNames).toContainEqual('0.worker.js'); 313 | 314 | const main = stats.assets['main.js']; 315 | expect(main).toMatch(/[^\n]*console.log\s*\([^)]*\)[^\n]*/g); 316 | 317 | const log = main.match(/\bconsole\.log\s*\(([^)]*)\)[^\n]*/)[1]; 318 | expect(log).toMatch(/worker_plugin_loader_worker__WEBPACK_IMPORTED_MODULE_\d___default.[a-z0-9]+/gi); 319 | 320 | expect(main).toMatch(/module.exports = __webpack_require__\.p\s*\+\s*"0\.worker\.js"/g); 321 | }); 322 | }); 323 | 324 | describe('watch mode', () => { 325 | const workerFile = resolve(__dirname, 'fixtures', 'watch', 'worker.js'); 326 | const workerCode = readFileSync(workerFile, 'utf-8'); 327 | afterAll(() => { 328 | writeFileSync(workerFile, workerCode); 329 | }); 330 | 331 | test('it produces consistent modules in watch mode', async () => { 332 | const compiler = watchWebpack('watch', { 333 | plugins: [ 334 | new WorkerPlugin() 335 | ] 336 | }); 337 | 338 | /** @returns {Partial} */ 339 | function Deferred () { 340 | const controller = {}; 341 | return Object.assign(new Promise((resolve, reject) => { 342 | controller.resolve = resolve; 343 | controller.reject = reject; 344 | }), controller); 345 | } 346 | 347 | let stats; 348 | let ready = Deferred(); 349 | 350 | const watcher = compiler.watch({ 351 | aggregateTimeout: 1, 352 | poll: 50, 353 | ignored: /node_modules|dist/ 354 | }, (err, stats) => { 355 | if (err) ready.reject(err); 356 | else ready.resolve(statsWithAssets(stats)); 357 | }); 358 | 359 | try { 360 | for (let i = 1; i < 5; i++) { 361 | ready = Deferred(); 362 | writeFileSync(workerFile, workerCode.replace(/console\.log\('hello from worker( \d+)?'\)/, `console.log('hello from worker ${i}')`)); 363 | await sleep(1000); 364 | stats = await ready; 365 | await sleep(1000); 366 | expect(Object.keys(stats.assets).sort()).toEqual(['0.worker.js', 'main.js']); 367 | expect(stats.assets['0.worker.js']).toContain(`hello from worker ${i}`); 368 | } 369 | } finally { 370 | watcher.close(() => {}); 371 | } 372 | 373 | await sleep(1000); 374 | }); 375 | }); 376 | }); 377 | -------------------------------------------------------------------------------- /test/integration.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | import WorkerPlugin from '../src'; 18 | import path from 'path'; 19 | import { createStaticServer } from './_server'; 20 | import { runWebpack } from './_util'; 21 | import { evaluatePage } from './_page'; 22 | 23 | jest.setTimeout(30000); 24 | 25 | describe('Integration', () => { 26 | test('The resulting Worker is instantiated correctly', async () => { 27 | const fixture = 'basic'; 28 | 29 | await runWebpack(fixture, { 30 | plugins: [new WorkerPlugin()] 31 | }); 32 | 33 | const server = await createStaticServer(path.resolve(__dirname, 'fixtures', fixture)); 34 | 35 | const consoleText = await evaluatePage(server.url, /page got data/g); 36 | 37 | expect(consoleText).toMatch(/page got data/g); 38 | 39 | await server.stop(); 40 | }); 41 | 42 | test('SharedWorker is instantiated correctly', async () => { 43 | const fixture = 'shared'; 44 | 45 | await runWebpack(fixture, { 46 | plugins: [new WorkerPlugin({ 47 | sharedWorker: true 48 | })] 49 | }); 50 | 51 | const server = await createStaticServer(path.resolve(__dirname, 'fixtures', fixture)); 52 | 53 | const consoleText = await evaluatePage(server.url, /page got data/g); 54 | 55 | expect(consoleText).toMatch(/page got data/g); 56 | 57 | await server.stop(); 58 | }); 59 | }); 60 | --------------------------------------------------------------------------------