123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193 |
- /**
- * Copyright 2018 Google Inc. All Rights Reserved.
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * http://www.apache.org/licenses/LICENSE-2.0
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- const { readFileSync } = require("fs");
- const { join } = require("path");
- const ejs = require("ejs");
- const MagicString = require("magic-string");
- const defaultOpts = {
- // A string containing the EJS template for the amd loader. If `undefined`,
- // OMT will use `loader.ejs`.
- loader: readFileSync(join(__dirname, "/loader.ejs"), "utf8"),
- // Use `fetch()` + `eval()` to load dependencies instead of `<script>` tags
- // and `importScripts()`. _This is not CSP compliant, but is required if you
- // want to use dynamic imports in ServiceWorker_.
- useEval: false,
- // A RegExp to find `new Workers()` calls. The second capture group _must_
- // capture the provided file name without the quotes.
- workerRegexp: /new Worker\((["'])(.+?)\1(,[^)]+)?\)/g,
- // Function name to use instead of AMD’s `define`.
- amdFunctionName: "define",
- // A function that determines whether the loader code should be prepended to a
- // certain chunk. Should return true if the load is supposed to be prepended.
- prependLoader: (chunk, workerFiles) =>
- chunk.isEntry || workerFiles.includes(chunk.facadeModuleId),
- // The scheme used when importing workers as a URL.
- urlLoaderScheme: "omt",
- // Silence the warning about ESM being badly supported in workers.
- silenceESMWorkerWarning: false,
- };
- module.exports = function(opts = {}) {
- opts = Object.assign({}, defaultOpts, opts);
- opts.loader = ejs.render(opts.loader, opts);
- const urlLoaderPrefix = opts.urlLoaderScheme + ":";
- let workerFiles;
- let isEsmOutput = false;
- return {
- name: "off-main-thread",
- async buildStart(options) {
- workerFiles = [];
- },
- outputOptions({ format }) {
- if ((format === "esm" || format === "es") && !opts.silenceESMWorkerWarning) {
- this.warn(
- 'Very few browsers support ES modules in Workers. If you want to your code to run in all browsers, set `output.format = "amd";`'
- );
- // In ESM, we never prepend a loader.
- isEsmOutput = true;
- } else if (format !== "amd") {
- this.error(
- `\`output.format\` must either be "amd" or "esm", got "${format}"`
- );
- }
- },
- async resolveId(id, importer) {
- if (!id.startsWith(urlLoaderPrefix)) return;
- const path = id.slice(urlLoaderPrefix.length);
- const resolved = await this.resolve(path, importer);
- if (!resolved) throw Error(`Cannot find module '${path}' from '${importer}'`);
- const newId = resolved.id;
- return urlLoaderPrefix + newId;
- },
- load(id) {
- if (!id.startsWith(urlLoaderPrefix)) return;
- const realId = id.slice(urlLoaderPrefix.length);
- const chunkRef = this.emitFile({ id: realId, type: "chunk" });
- return `export default import.meta.ROLLUP_FILE_URL_${chunkRef};`;
- },
- async transform(code, id) {
- // Copy the regexp as they are stateful and this hook is async.
- const workerRegexp = new RegExp(
- opts.workerRegexp.source,
- opts.workerRegexp.flags
- );
- if (!workerRegexp.test(code)) {
- return;
- }
- const ms = new MagicString(code);
- // Reset the regexp
- workerRegexp.lastIndex = 0;
- while (true) {
- const match = workerRegexp.exec(code);
- if (!match) {
- break;
- }
- const workerFile = match[2];
- let optionsObject = {};
- // Parse the optional options object
- if (match[3] && match[3].length > 0) {
- // FIXME: ooooof!
- optionsObject = new Function(`return ${match[3].slice(1)};`)();
- }
- if (!isEsmOutput) {
- delete optionsObject.type;
- }
- if (!new RegExp("^.*/").test(workerFile)) {
- this.warn(
- `Paths passed to the Worker constructor must be relative or absolute, i.e. start with /, ./ or ../ (just like dynamic import!). Ignoring "${workerFile}".`
- );
- continue;
- }
- const resolvedWorkerFile = (await this.resolve(workerFile, id)).id;
- workerFiles.push(resolvedWorkerFile);
- const chunkRefId = this.emitFile({
- id: resolvedWorkerFile,
- type: "chunk"
- });
- const workerParametersStartIndex = match.index + "new Worker(".length;
- const workerParametersEndIndex =
- match.index + match[0].length - ")".length;
- ms.overwrite(
- workerParametersStartIndex,
- workerParametersEndIndex,
- `import.meta.ROLLUP_FILE_URL_${chunkRefId}, ${JSON.stringify(
- optionsObject
- )}`
- );
- }
- return {
- code: ms.toString(),
- map: ms.generateMap({ hires: true })
- };
- },
- resolveFileUrl(chunk) {
- return `"./${chunk.fileName}"`;
- },
- renderChunk(code, chunk, outputOptions) {
- // We don’t need to do any loader processing when targeting ESM format.
- if (isEsmOutput) {
- return;
- }
- if (outputOptions.banner && outputOptions.banner.length > 0) {
- this.error(
- "OMT currently doesn’t work with `banner`. Feel free to submit a PR at https://github.com/surma/rollup-plugin-off-main-thread"
- );
- return;
- }
- const ms = new MagicString(code);
- // Mangle define() call
- const id = `./${chunk.fileName}`;
- ms.remove(0, "define(".length);
- // If the module does not have any dependencies, it’s technically okay
- // to skip the dependency array. But our minimal loader expects it, so
- // we add it back in.
- if (!code.startsWith("define([")) {
- ms.prepend("[],");
- }
- ms.prepend(`${opts.amdFunctionName}("${id}",`);
- // Prepend loader if it’s an entry point or a worker file
- if (opts.prependLoader(chunk, workerFiles)) {
- ms.prepend(opts.loader);
- }
- return {
- code: ms.toString(),
- map: ms.generateMap({ hires: true })
- };
- }
- };
- };
|