-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
extension: switch to rollup for building instead of webpack
mainly because - rollup has a much better builtin ES6 module support (in webpack it's STILL experimental!) - minimal transfromation to the source code, even possible to read without source maps
- Loading branch information
Showing
18 changed files
with
342 additions
and
358 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
import assert from 'assert' | ||
|
||
import pkg from './package.json' with { type: "json" } | ||
|
||
const T = { | ||
CHROME : 'chrome', | ||
FIREFOX: 'firefox', | ||
} | ||
|
||
|
||
// ugh. declarative formats are shit. | ||
export function generateManifest({ | ||
target, // str | ||
version, // str | ||
release, // bool | ||
ext_id // str | ||
} = {}) { | ||
assert(target) | ||
assert(version) | ||
assert(release !== null) | ||
assert(ext_id) | ||
|
||
const v3 = version == '3' | ||
|
||
// Firefox wouldn't let you rebind its default shortcuts most of which use Shift | ||
// On the other hand, Chrome wouldn't let you use Alt | ||
const modifier = target === T.CHROME ? 'Shift' : 'Alt' | ||
|
||
const action_name = v3 ? 'action' : 'browser_action' | ||
|
||
const commands = { | ||
"capture-simple": { | ||
"description": "Quick capture: url, title and selection", | ||
"suggested_key": { | ||
"default": `Ctrl+${modifier}+H`, | ||
"mac": `Command+${modifier}+H`, | ||
}, | ||
}, | ||
} | ||
|
||
commands[`_execute_${action_name}`] = { | ||
"description": "Capture page, with extra information", | ||
"suggested_key": { | ||
"default": `Ctrl+${modifier}+Y`, | ||
"mac": `Command+${modifier}+Y`, | ||
}, | ||
} | ||
|
||
|
||
const action = { | ||
"default_icon": "img/unicorn.png", | ||
"default_popup": "popup.html", | ||
"default_title": "Capture page, with extra information", | ||
} | ||
|
||
|
||
const endpoints = (domain) => [ | ||
"http://" + domain + "/capture", | ||
"https://" + domain + "/capture", | ||
] | ||
|
||
|
||
// prepare for manifest v3 | ||
const host_permissions = endpoints('localhost') | ||
const optional_host_permissions = endpoints('*') | ||
|
||
|
||
// TODO make permissions literate | ||
const permissions = [ | ||
// for keeping extension settings | ||
"storage", | ||
|
||
// for showing notification about successful capture or errors | ||
"notifications", | ||
|
||
// need to query active tab and get its url/title | ||
"activeTab", | ||
] | ||
|
||
|
||
const optional_permissions = [] | ||
|
||
if (target === T.FIREFOX || v3) { | ||
// chrome v2 doesn't support scripting api | ||
// code has a fallback just for that | ||
// (needed to get selected text) | ||
permissions.push("scripting") | ||
} | ||
|
||
|
||
const content_security_policy = [ | ||
"script-src 'self'", // this must be specified when overriding, otherwise it complains | ||
/// also this works, but it seems that default-src somehow shadows style-src??? | ||
// "default-src 'self'", | ||
// "style-src 'unsafe-inline'", // FFS, otherwise <style> directives on extension's pages not working?? | ||
/// | ||
|
||
// also need to override it to eclude 'upgrade-insecure-requests' in manifest v3? | ||
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_Security_Policy#upgrade_insecure_network_requests_in_manifest_v3 | ||
// NOTE: could be connect-src http: https: to allow all? | ||
// but we're specifically allowing endpoints that have /capture in them | ||
"connect-src " + endpoints('*:*').join(' '), | ||
].join('; ') | ||
|
||
|
||
const background = {} | ||
if (v3) { | ||
if (target === T.CHROME) { | ||
// webext lint will warn about this since it's not supported in firefox yet | ||
background['service_worker'] = 'background.js' | ||
|
||
// this isn't supported in chrome manifest v3 (chrome warns about unsupported field) | ||
// but without it webext lint fails | ||
background['scripts'] = ['background.js'] | ||
} else { | ||
background['scripts'] = ['background.js'] | ||
} | ||
} else { | ||
background['scripts'] = ['background.js'] | ||
background['persistent'] = false | ||
} | ||
background['type'] = 'module' // hmm seems like it works in firefox v2 too now?? | ||
|
||
const manifest = { | ||
name: pkg.name + (release ? '' : ' [dev]'), | ||
version: pkg.version, | ||
description: pkg.description, | ||
permissions: permissions, | ||
commands: commands, | ||
optional_permissions: optional_permissions, | ||
manifest_version: v3 ? 3 : 2, | ||
background: background, | ||
icons: { | ||
'128': 'img/unicorn.png' | ||
}, | ||
options_ui: { | ||
page: 'options.html', | ||
open_in_tab: true, | ||
}, | ||
} | ||
manifest[action_name] = action | ||
|
||
if (target === T.FIREFOX) { | ||
// NOTE: chrome v3 works without content_security_policy?? | ||
// but in firefox it refuses to make a request even when we allow hostname permission?? | ||
manifest.content_security_policy = (v3 ? {extension_pages: content_security_policy} : content_security_policy) | ||
} | ||
|
||
manifest.content_scripts = [ | ||
{ | ||
"matches": ["<all_urls>"], | ||
"js": ["detect_dark_mode.js"], | ||
}, | ||
] | ||
|
||
if (v3) { | ||
if (target === T.FIREFOX) { | ||
// firefox doesn't support optional host permissions | ||
// note that these will still have to be granted by user (unlike in chrome) | ||
manifest['host_permissions'] = [...host_permissions, ...optional_host_permissions] | ||
} else { | ||
manifest['host_permissions'] = host_permissions | ||
manifest['optional_host_permissions'] = optional_host_permissions | ||
} | ||
} else { | ||
manifest.permissions.push(...host_permissions) | ||
manifest.optional_permissions.push(...optional_host_permissions) | ||
} | ||
|
||
if (v3) { | ||
// this isn't really required in chrome, but without it, webext lint fails for chrome addon | ||
const gecko_id = target === T.FIREFOX ? ext_id : '{00000000-0000-0000-0000-000000000000}' | ||
manifest['browser_specific_settings'] = { | ||
'gecko': { | ||
'id': gecko_id, | ||
}, | ||
} | ||
} | ||
return manifest | ||
} |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import assert from 'assert' | ||
import fs from 'fs' | ||
const { globSync } = import('node:fs') | ||
import path from 'path' | ||
import { fileURLToPath } from 'url' | ||
|
||
import typescript from '@rollup/plugin-typescript' | ||
import { nodeResolve } from '@rollup/plugin-node-resolve' | ||
import commonjs from '@rollup/plugin-commonjs' | ||
import copy from 'rollup-plugin-copy' | ||
|
||
import {generateManifest} from './generate_manifest.js' | ||
|
||
|
||
const env = { | ||
RELEASE: process.env.RELEASE, | ||
PUBLISH: process.env.PUBLISH, | ||
MANIFEST: process.env.MANIFEST, | ||
} | ||
|
||
const target = process.env.TARGET; assert(target) | ||
const manifest_version = process.env.MANIFEST; assert(manifest_version) | ||
const ext_id = process.env.EXT_ID; assert(ext_id) | ||
const release = env.RELEASE === 'YES' // TODO use --environment=production for rollup? | ||
const publish = env.PUBLISH === 'YES' | ||
|
||
|
||
const thisDir = path.dirname(fileURLToPath(import.meta.url)); assert(path.isAbsolute(thisDir)) | ||
const srcDir = path.join(thisDir, 'src') | ||
const buildDir = path.join(thisDir, 'dist', target) | ||
|
||
|
||
// kinda annoying it's not a builtin.. | ||
function cleanOutputDir() { | ||
return { | ||
name: 'clean-output-dir', | ||
buildStart(options) { | ||
const outDir = buildDir | ||
// we don't just want to rm -rf outputDir to respect if it's a symlink or something like that | ||
if (!fs.existsSync(outDir)) { | ||
return | ||
} | ||
fs.readdirSync(outDir).forEach(f => { | ||
// console.debug("removing %s", f) | ||
fs.rmSync(path.join(outDir, f), {recursive: true}) | ||
}) | ||
}, | ||
} | ||
} | ||
|
||
|
||
function generateManifestPlugin() { | ||
return { | ||
name: 'generate-manifest', | ||
generateBundle(outputOptions, bundle) { | ||
const manifest = generateManifest({ | ||
target: target, | ||
version: manifest_version, | ||
releas: release, | ||
ext_id: ext_id, | ||
}) | ||
const mjs = JSON.stringify(manifest, null, 2) | ||
const outputPath = path.join(outputOptions.dir, 'manifest.json') | ||
fs.mkdirSync(outputOptions.dir, { recursive: true }) | ||
fs.writeFileSync(outputPath, mjs, 'utf8') | ||
} | ||
} | ||
} | ||
|
||
|
||
const compile = inputs => { return { | ||
input: inputs, | ||
output: { | ||
dir: buildDir, | ||
// format: 'esm', // default?? | ||
// format: 'iife', // inlines? e.g. could use for bg page if we disable splitting.. | ||
|
||
// huh! so if I build all files in one go, it figures out the shared files properly it seems | ||
// however it still inlines webextension stuff into one of the files? e.g. common | ||
manualChunks: id => { // ugh, seems a bit shit? | ||
if (id.includes('webextension-polyfill')) { | ||
return 'webextension-polyfill' // move it in a separate chunk | ||
} | ||
}, | ||
}, | ||
plugins: [ | ||
cleanOutputDir(), | ||
copy({ | ||
targets: [ | ||
{src: 'src/**/*.html', dest: buildDir}, | ||
{src: 'src/**/*.png' , dest: buildDir}, | ||
], | ||
flatten: false, | ||
}), | ||
typescript({ | ||
outDir: buildDir, | ||
noEmitOnError: true, // fail on errors | ||
}), | ||
commonjs(), // needed for webext polyfill | ||
nodeResolve(), | ||
generateManifestPlugin(), | ||
], | ||
}} | ||
|
||
|
||
export default [ | ||
compile([ | ||
path.join(srcDir, 'background.ts'), | ||
path.join(srcDir, 'options_page.ts'), | ||
path.join(srcDir, 'popup.ts'), | ||
path.join(srcDir, 'detect_dark_mode.ts'), | ||
]), | ||
] |
Oops, something went wrong.