v1.0 with SW PWA enabled
This commit is contained in:
318
frontend/node_modules/@surma/rollup-plugin-off-main-thread/index.js
generated
vendored
Normal file
318
frontend/node_modules/@surma/rollup-plugin-off-main-thread/index.js
generated
vendored
Normal file
@ -0,0 +1,318 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const { readFileSync } = require("fs");
|
||||
const { join } = require("path");
|
||||
const ejs = require("ejs");
|
||||
const MagicString = require("magic-string");
|
||||
const json5 = require("json5");
|
||||
// See https://github.com/surma/rollup-plugin-off-main-thread/issues/49
|
||||
const matchAll = require("string.prototype.matchall");
|
||||
|
||||
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,
|
||||
// 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
|
||||
};
|
||||
|
||||
// A regexp to find static `new Worker` invocations.
|
||||
// Matches `new Worker(...file part...`
|
||||
// File part matches one of:
|
||||
// - '...'
|
||||
// - "..."
|
||||
// - `import.meta.url`
|
||||
// - new URL('...', import.meta.url)
|
||||
// - new URL("...", import.meta.url)
|
||||
const workerRegexpForTransform = /(new\s+Worker\()\s*(('.*?'|".*?")|import\.meta\.url|new\s+URL\(('.*?'|".*?"),\s*import\.meta\.url\))/gs;
|
||||
|
||||
// A regexp to find static `new Worker` invocations we've rewritten during the transform phase.
|
||||
// Matches `new Worker(...file part..., ...options...`.
|
||||
// File part matches one of:
|
||||
// - new URL('...', module.uri)
|
||||
// - new URL("...", module.uri)
|
||||
const workerRegexpForOutput = /new\s+Worker\(new\s+URL\((?:'.*?'|".*?"),\s*module\.uri\)\s*(,([^)]+))/gs;
|
||||
|
||||
let longWarningAlreadyShown = 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 = () => { throw new Error("outputOptions hasn't been called yet") };
|
||||
return {
|
||||
name: "off-main-thread",
|
||||
|
||||
async buildStart(options) {
|
||||
workerFiles = [];
|
||||
},
|
||||
|
||||
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) {
|
||||
const ms = new MagicString(code);
|
||||
|
||||
const replacementPromises = [];
|
||||
|
||||
for (const match of matchAll(code, workerRegexpForTransform)) {
|
||||
let [
|
||||
fullMatch,
|
||||
partBeforeArgs,
|
||||
workerSource,
|
||||
directWorkerFile,
|
||||
workerFile,
|
||||
] = match;
|
||||
|
||||
const workerParametersEndIndex = match.index + fullMatch.length;
|
||||
const matchIndex = match.index;
|
||||
const workerParametersStartIndex = matchIndex + partBeforeArgs.length;
|
||||
|
||||
let workerIdPromise;
|
||||
if (workerSource === "import.meta.url") {
|
||||
// Turn the current file into a chunk
|
||||
workerIdPromise = Promise.resolve(id);
|
||||
} else {
|
||||
// Otherwise it's a string literal either directly or in the `new URL(...)`.
|
||||
if (directWorkerFile) {
|
||||
const fullMatchWithOpts = `${fullMatch}, …)`;
|
||||
const fullReplacement = `new Worker(new URL(${directWorkerFile}, import.meta.url), …)`;
|
||||
|
||||
if (!longWarningAlreadyShown) {
|
||||
this.warn(
|
||||
`rollup-plugin-off-main-thread:
|
||||
\`${fullMatchWithOpts}\` suggests that the Worker should be relative to the document, not the script.
|
||||
In the bundler, we don't know what the final document's URL will be, and instead assume it's a URL relative to the current module.
|
||||
This might lead to incorrect behaviour during runtime.
|
||||
If you did mean to use a URL relative to the current module, please change your code to the following form:
|
||||
\`${fullReplacement}\`
|
||||
This will become a hard error in the future.`,
|
||||
matchIndex
|
||||
);
|
||||
longWarningAlreadyShown = true;
|
||||
} else {
|
||||
this.warn(
|
||||
`rollup-plugin-off-main-thread: Treating \`${fullMatchWithOpts}\` as \`${fullReplacement}\``,
|
||||
matchIndex
|
||||
);
|
||||
}
|
||||
workerFile = directWorkerFile;
|
||||
}
|
||||
|
||||
// Cut off surrounding quotes.
|
||||
workerFile = workerFile.slice(1, -1);
|
||||
|
||||
if (!/^\.{1,2}\//.test(workerFile)) {
|
||||
let isError = false;
|
||||
if (directWorkerFile) {
|
||||
// If direct worker file, it must be in `./something` form.
|
||||
isError = true;
|
||||
} else {
|
||||
// If `new URL(...)` it can be in `new URL('something', import.meta.url)` form too,
|
||||
// so just check it's not absolute.
|
||||
if (/^(\/|https?:)/.test(workerFile)) {
|
||||
isError = true;
|
||||
} else {
|
||||
// If it does turn out to be `new URL('something', import.meta.url)` form,
|
||||
// prepend `./` so that it becomes valid module specifier.
|
||||
workerFile = `./${workerFile}`;
|
||||
}
|
||||
}
|
||||
if (isError) {
|
||||
this.warn(
|
||||
`Paths passed to the Worker constructor must be relative to the current file, i.e. start with ./ or ../ (just like dynamic import!). Ignoring "${workerFile}".`,
|
||||
matchIndex
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
workerIdPromise = this.resolve(workerFile, id).then(res => res.id);
|
||||
}
|
||||
|
||||
replacementPromises.push(
|
||||
(async () => {
|
||||
const resolvedWorkerFile = await workerIdPromise;
|
||||
workerFiles.push(resolvedWorkerFile);
|
||||
const chunkRefId = this.emitFile({
|
||||
id: resolvedWorkerFile,
|
||||
type: "chunk"
|
||||
});
|
||||
|
||||
ms.overwrite(
|
||||
workerParametersStartIndex,
|
||||
workerParametersEndIndex,
|
||||
`new URL(import.meta.ROLLUP_FILE_URL_${chunkRefId}, import.meta.url)`
|
||||
);
|
||||
})()
|
||||
);
|
||||
}
|
||||
|
||||
// No matches found.
|
||||
if (!replacementPromises.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait for all the scheduled replacements to finish.
|
||||
await Promise.all(replacementPromises);
|
||||
|
||||
return {
|
||||
code: ms.toString(),
|
||||
map: ms.generateMap({ hires: true })
|
||||
};
|
||||
},
|
||||
|
||||
resolveFileUrl(chunk) {
|
||||
return JSON.stringify(chunk.relativePath);
|
||||
},
|
||||
|
||||
outputOptions({ format }) {
|
||||
if (format === "esm" || format === "es") {
|
||||
if (!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}"`
|
||||
);
|
||||
} else {
|
||||
isEsmOutput = () => false;
|
||||
}
|
||||
},
|
||||
|
||||
renderDynamicImport() {
|
||||
if (isEsmOutput()) return;
|
||||
|
||||
// In our loader, `require` simply return a promise directly.
|
||||
// This is tinier and simpler output than the Rollup's default.
|
||||
return {
|
||||
left: 'require(',
|
||||
right: ')'
|
||||
};
|
||||
},
|
||||
|
||||
resolveImportMeta(property) {
|
||||
if (isEsmOutput()) return;
|
||||
|
||||
if (property === 'url') {
|
||||
// In our loader, `module.uri` is already fully resolved
|
||||
// so we can emit something shorter than the Rollup's default.
|
||||
return `module.uri`;
|
||||
}
|
||||
},
|
||||
|
||||
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);
|
||||
|
||||
for (const match of matchAll(code, workerRegexpForOutput)) {
|
||||
let [fullMatch, optionsWithCommaStr, optionsStr] = match;
|
||||
let options;
|
||||
try {
|
||||
options = json5.parse(optionsStr);
|
||||
} catch (e) {
|
||||
// If we couldn't parse the options object, maybe it's something dynamic or has nested
|
||||
// parentheses or something like that. In that case, treat it as a warning
|
||||
// and not a hard error, just like we wouldn't break on unmatched regex.
|
||||
console.warn("Couldn't match options object", fullMatch, ": ", e);
|
||||
continue;
|
||||
}
|
||||
if (!("type" in options)) {
|
||||
// Nothing to do.
|
||||
continue;
|
||||
}
|
||||
delete options.type;
|
||||
const replacementEnd = match.index + fullMatch.length;
|
||||
const replacementStart = replacementEnd - optionsWithCommaStr.length;
|
||||
optionsStr = json5.stringify(options);
|
||||
optionsWithCommaStr = optionsStr === "{}" ? "" : `, ${optionsStr}`;
|
||||
ms.overwrite(
|
||||
replacementStart,
|
||||
replacementEnd,
|
||||
optionsWithCommaStr
|
||||
);
|
||||
}
|
||||
|
||||
// Mangle define() call
|
||||
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}(`);
|
||||
|
||||
// Prepend loader if it’s an entry point or a worker file
|
||||
if (opts.prependLoader(chunk, workerFiles)) {
|
||||
ms.prepend(opts.loader);
|
||||
}
|
||||
|
||||
const newCode = ms.toString();
|
||||
const hasCodeChanged = code !== newCode;
|
||||
return {
|
||||
code: newCode,
|
||||
// Avoid generating sourcemaps if possible as it can be a very expensive operation
|
||||
map: hasCodeChanged ? ms.generateMap({ hires: true }) : null
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user