@surma/rollup-plugin-off-main-thread
Advanced tools
Comparing version 2.0.0 to 2.1.0
192
index.js
@@ -19,2 +19,3 @@ /** | ||
const tippex = require("tippex"); | ||
const json5 = require("json5"); | ||
@@ -41,2 +42,21 @@ const defaultOpts = { | ||
// 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 = {}) { | ||
@@ -50,3 +70,3 @@ opts = Object.assign({}, defaultOpts, opts); | ||
let workerFiles; | ||
let isEsmOutput = false; | ||
let isEsmOutput = () => { throw new Error("outputOptions hasn't been called yet") }; | ||
return { | ||
@@ -59,18 +79,2 @@ name: "off-main-thread", | ||
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}"` | ||
); | ||
} | ||
}, | ||
async resolveId(id, importer) { | ||
@@ -97,12 +101,2 @@ if (!id.startsWith(urlLoaderPrefix)) return; | ||
async transform(code, id) { | ||
// A regexp to find static `new Worker` invocations. | ||
// File part matches one of: | ||
// - '...' | ||
// - "..." | ||
// - `import.meta.url` | ||
// - `new URL('...', import.meta.url) | ||
// - `new URL("...", import.meta.url) | ||
// Also matches optional options param. | ||
const workerRegexp = /new\s+Worker\(\s*('.*?'|".*?"|import\.meta\.url|new\s+URL\(('.*?'|".*?"),\s*import\.meta\.url\))\s*(?:,(.+?))?\)/gs; | ||
const ms = new MagicString(code); | ||
@@ -116,22 +110,54 @@ | ||
code, | ||
workerRegexp, | ||
workerRegexpForTransform, | ||
( | ||
fullMatch, | ||
partBeforeArgs, | ||
workerSource, | ||
directWorkerFile, | ||
workerFile = directWorkerFile, | ||
optionsStr = "" | ||
workerFile, | ||
) => { | ||
// We need to get this before the `await`, otherwise `lastIndex` | ||
// will be already overridden. | ||
const workerParametersEndIndex = workerRegexpForTransform.lastIndex; | ||
const matchIndex = workerParametersEndIndex - fullMatch.length; | ||
const workerParametersStartIndex = matchIndex + partBeforeArgs.length; | ||
let workerIdPromise; | ||
if (directWorkerFile === "import.meta.url") { | ||
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 URL | ||
// constructor, which we treat as the same thing. | ||
// 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 (!/^\.{0,2}\//.test(workerFile)) { | ||
if (!/^\.{1,2}\//.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}".` | ||
`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 | ||
); | ||
@@ -144,20 +170,2 @@ return; | ||
// We need to get this before the `await`, otherwise `lastIndex` | ||
// will be already overridden. | ||
const matchIndex = workerRegexp.lastIndex - fullMatch.length; | ||
const workerParametersStartIndex = matchIndex + "new Worker(".length; | ||
const workerParametersEndIndex = | ||
matchIndex + fullMatch.length - ")".length; | ||
// Parse the optional options object if provided. | ||
optionsStr = optionsStr.trim(); | ||
if (optionsStr) { | ||
let optionsObject = new Function(`return ${optionsStr};`)(); | ||
if (!isEsmOutput) { | ||
delete optionsObject.type; | ||
} | ||
optionsStr = JSON.stringify(optionsObject); | ||
optionsStr = optionsStr === "{}" ? "" : `, ${optionsStr}`; | ||
} | ||
// tippex.match accepts only sync callback, but we want to perform & | ||
@@ -177,3 +185,3 @@ // wait for async job here, so we track those promises separately. | ||
workerParametersEndIndex, | ||
`import.meta.ROLLUP_FILE_URL_${chunkRefId}${optionsStr}` | ||
`new URL(import.meta.ROLLUP_FILE_URL_${chunkRefId}, import.meta.url)` | ||
); | ||
@@ -200,10 +208,48 @@ })() | ||
resolveFileUrl(chunk) { | ||
return `"./${chunk.fileName}"`; | ||
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 (isEsmOutput()) return; | ||
if (outputOptions.banner && outputOptions.banner.length > 0) { | ||
@@ -217,4 +263,30 @@ this.error( | ||
tippex.match(code, workerRegexpForOutput, (fullMatch, optionsWithCommaStr, optionsStr) => { | ||
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); | ||
return; | ||
} | ||
if (!("type" in options)) { | ||
// Nothing to do. | ||
return; | ||
} | ||
delete options.type; | ||
const replacementEnd = workerRegexpForOutput.lastIndex; | ||
const replacementStart = replacementEnd - optionsWithCommaStr.length; | ||
optionsStr = json5.stringify(options); | ||
optionsWithCommaStr = optionsStr === "{}" ? "" : `, ${optionsStr}`; | ||
ms.overwrite( | ||
replacementStart, | ||
replacementEnd, | ||
optionsWithCommaStr | ||
); | ||
}); | ||
// Mangle define() call | ||
const id = `./${chunk.fileName}`; | ||
ms.remove(0, "define(".length); | ||
@@ -227,3 +299,3 @@ // If the module does not have any dependencies, it’s technically okay | ||
} | ||
ms.prepend(`${opts.amdFunctionName}("${id}",`); | ||
ms.prepend(`${opts.amdFunctionName}(`); | ||
@@ -230,0 +302,0 @@ // Prepend loader if it’s an entry point or a worker file |
{ | ||
"name": "@surma/rollup-plugin-off-main-thread", | ||
"version": "2.0.0", | ||
"version": "2.1.0", | ||
"description": "Use Rollup with workers and ES6 modules today.", | ||
@@ -14,2 +14,3 @@ "main": "index.js", | ||
"chai": "4.2.0", | ||
"chalk": "^2.4.2", | ||
"karma": "4.2.0", | ||
@@ -24,3 +25,3 @@ "karma-chai": "0.1.0", | ||
"prettier": "1.18.2", | ||
"rollup": "2.0.0-0" | ||
"rollup": "2.2.0" | ||
}, | ||
@@ -33,2 +34,3 @@ "repository": { | ||
"ejs": "^2.6.1", | ||
"json5": "^2.2.0", | ||
"magic-string": "^0.25.0", | ||
@@ -35,0 +37,0 @@ "tippex": "^3.0.0" |
@@ -18,2 +18,3 @@ /** | ||
const fs = require("fs"); | ||
const chalk = require("chalk"); | ||
@@ -57,3 +58,23 @@ const karma = require("karma"); | ||
input, | ||
strictDeprecations: true | ||
strictDeprecations: true, | ||
// Copied / adapted from default `onwarn` in Rollup CLI. | ||
onwarn: warning => { | ||
console.warn(`⚠️ ${chalk.bold(warning.message)}`); | ||
if (warning.url) { | ||
console.warn(chalk.cyan(warning.url)); | ||
} | ||
if (warning.loc) { | ||
console.warn( | ||
`${warning.loc.file} (${warning.loc.line}:${warning.loc.column})` | ||
); | ||
} | ||
if (warning.frame) { | ||
console.warn(chalk.dim(warning.frame)); | ||
} | ||
console.warn(""); | ||
} | ||
}; | ||
@@ -60,0 +81,0 @@ const rollupConfigPath = "./" + path.join(pathName, "rollup.config.js"); |
@@ -14,5 +14,8 @@ /** | ||
export default async function() { | ||
const { default: f } = await import("./b.js"); | ||
export default async function(f) { | ||
self.postMessage(f()); | ||
} | ||
export function someValue() { | ||
return self.String("a"); | ||
} |
@@ -14,4 +14,8 @@ /** | ||
// Import for side-effects (of which there are none) | ||
// to make sure that a.js gets its own chunk. | ||
import {someValue} from "./a.js"; | ||
export default function() { | ||
return "a"; | ||
return someValue(); | ||
} |
@@ -15,4 +15,4 @@ /** | ||
import a from "./a.js"; | ||
a(); | ||
import("./b.js"); | ||
import("./b.js").then(({default: f}) => { | ||
a(f); | ||
}) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
90807
1552
2
4
12
+ Addedjson5@^2.2.0
+ Addedjson5@2.2.3(transitive)