fix: bundle the content script to stop 'Unsafe attempt to load URL' errors
Dynamic import() of extension modules from a content script makes Chrome log 'Unsafe attempt to load URL chrome-extension://...' on YouTube pages, which fills the extension's error list even though the modules load (HTTP 200). The content script is now one classic file built by 'npm run build'; web_accessible_resources is no longer needed and is removed. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
38dabd9fa2
commit
b137776381
@@ -0,0 +1,55 @@
|
||||
// Bundles the content script and the modules it imports into one classic script.
|
||||
//
|
||||
// Content scripts cannot be ES modules, and loading modules with a dynamic import() makes Chrome log
|
||||
// "Unsafe attempt to load URL chrome-extension://..." on every YouTube page, which piles up in the
|
||||
// extension's error list. A single classic file avoids both.
|
||||
//
|
||||
// Supported source style: one-line `import ... from './x.js';`, and `export` only before
|
||||
// const / let / function / class. Top-level names must be unique across the bundled modules.
|
||||
import { readFileSync, writeFileSync } from 'node:fs';
|
||||
import { basename, dirname, resolve } from 'node:path';
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||
|
||||
const IMPORT = /^import\s+(?:[^'"]*?\s+from\s+)?'([^']+)';?[ \t]*\r?\n/gm;
|
||||
const EXPORT_DECLARATION = /^export\s+(?=(?:async\s+function|function|const|let|class)\b)/gm;
|
||||
const UNSUPPORTED_EXPORT = /^export\s+(?!(?:async\s+function|function|const|let|class)\b)/m;
|
||||
const TOP_LEVEL_NAME = /^(?:async\s+)?(?:function\*?|const|let|class)\s+([A-Za-z_$][\w$]*)/gm;
|
||||
|
||||
const HEADER =
|
||||
'// GENERATED by tools/build-content.js from src/content/main.js and the modules it imports.\n' +
|
||||
'// Edit those files and run `npm run build`; do not edit this file by hand.\n';
|
||||
|
||||
export function bundle(entry) {
|
||||
const order = [];
|
||||
const seen = new Set();
|
||||
const visit = (file) => {
|
||||
if (seen.has(file)) return;
|
||||
seen.add(file);
|
||||
for (const [, specifier] of readFileSync(file, 'utf8').matchAll(IMPORT)) visit(resolve(dirname(file), specifier));
|
||||
order.push(file);
|
||||
};
|
||||
visit(resolve(entry));
|
||||
|
||||
const owner = new Map();
|
||||
const parts = order.map((file) => {
|
||||
const source = readFileSync(file, 'utf8');
|
||||
if (UNSUPPORTED_EXPORT.test(source)) {
|
||||
throw new Error(`${file}: only "export const/let/function/class" declarations are supported`);
|
||||
}
|
||||
const code = source.replace(IMPORT, '').replace(EXPORT_DECLARATION, '').trim();
|
||||
for (const [, name] of code.matchAll(TOP_LEVEL_NAME)) {
|
||||
if (owner.has(name)) throw new Error(`Duplicate top-level name "${name}" in ${file} and ${owner.get(name)}`);
|
||||
owner.set(name, file);
|
||||
}
|
||||
return `// ---- ${basename(file)} ----\n${code}\n`;
|
||||
});
|
||||
|
||||
return `${HEADER}(() => {\n'use strict';\n\n${parts.join('\n')}})();\n`;
|
||||
}
|
||||
|
||||
const isMain = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
|
||||
if (isMain) {
|
||||
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
||||
writeFileSync(resolve(root, 'src/content.js'), bundle(resolve(root, 'src/content/main.js')));
|
||||
console.log('Wrote src/content.js');
|
||||
}
|
||||
Reference in New Issue
Block a user