261 lines
9.4 KiB
JavaScript
261 lines
9.4 KiB
JavaScript
// @ts-check
|
|
// This plugin resolves location of malevic module
|
|
import rollupPluginNodeResolve from '@rollup/plugin-node-resolve';
|
|
/** @type {any} */
|
|
import rollupPluginReplace from '@rollup/plugin-replace';
|
|
/** @type {any} */
|
|
import rollupPluginTypescript from '@rollup/plugin-typescript';
|
|
import * as rollup from 'rollup';
|
|
import typescript from 'typescript';
|
|
|
|
import {getDestDir, absolutePath} from './paths.js';
|
|
import {PLATFORM} from './platform.js';
|
|
import * as reload from './reload.js';
|
|
const {PORT} = reload;
|
|
import {createTask} from './task.js';
|
|
|
|
/** @typedef {import('chokidar').FSWatcher} FSWatcher */
|
|
/** @typedef {import('./types.d.ts').JSEntry} JSEntry */
|
|
/** @typedef {import('./types.d.ts').TaskOptions} TaskOptions */
|
|
|
|
/** @type {JSEntry[]} */
|
|
const jsEntries = [
|
|
{
|
|
src: 'src/background/index.ts',
|
|
dest: 'background/index.js',
|
|
reloadType: reload.FULL,
|
|
},
|
|
{
|
|
src: 'src/inject/index.ts',
|
|
dest: 'inject/index.js',
|
|
reloadType: reload.FULL,
|
|
},
|
|
{
|
|
src: 'src/inject/dynamic-theme/mv3-proxy.ts',
|
|
dest: 'inject/proxy.js',
|
|
reloadType: reload.FULL,
|
|
platform: PLATFORM.CHROMIUM_MV3,
|
|
},
|
|
{
|
|
src: 'src/inject/fallback.ts',
|
|
dest: 'inject/fallback.js',
|
|
reloadType: reload.FULL,
|
|
},
|
|
{
|
|
src: 'src/inject/color-scheme-watcher.ts',
|
|
dest: 'inject/color-scheme-watcher.js',
|
|
reloadType: reload.FULL,
|
|
platform: PLATFORM.CHROMIUM_MV3,
|
|
},
|
|
{
|
|
src: 'src/ui/devtools/index.tsx',
|
|
dest: 'ui/devtools/index.js',
|
|
reloadType: reload.UI,
|
|
},
|
|
{
|
|
src: 'src/ui/options/index.tsx',
|
|
dest: 'ui/options/index.js',
|
|
reloadType: reload.UI,
|
|
},
|
|
{
|
|
src: 'src/ui/popup/index.tsx',
|
|
dest: 'ui/popup/index.js',
|
|
reloadType: reload.UI,
|
|
},
|
|
{
|
|
src: 'src/ui/stylesheet-editor/index.tsx',
|
|
dest: 'ui/stylesheet-editor/index.js',
|
|
reloadType: reload.UI,
|
|
},
|
|
];
|
|
|
|
/** @type {Record<string, any>} */
|
|
const rollupCache = {};
|
|
|
|
async function bundleJS(/** @type {JSEntry} */entry, platform, debug, watch, log, test) {
|
|
const {src, dest} = entry;
|
|
|
|
let replace = {};
|
|
switch (platform) {
|
|
case PLATFORM.FIREFOX_MV2:
|
|
case PLATFORM.THUNDERBIRD:
|
|
if (entry.src === 'src/ui/popup/index.tsx') {
|
|
break;
|
|
}
|
|
replace = {
|
|
'chrome.fontSettings.getFontList': `chrome['font' + 'Settings']['get' + 'Font' + 'List']`,
|
|
'chrome.fontSettings': `chrome['font' + 'Settings']`,
|
|
};
|
|
break;
|
|
case PLATFORM.CHROMIUM_MV3:
|
|
replace = {
|
|
'chrome.browserAction.setIcon': 'chrome.action.setIcon',
|
|
'chrome.browserAction.setBadgeBackgroundColor': 'chrome.action.setBadgeBackgroundColor',
|
|
'chrome.browserAction.setBadgeText': 'chrome.action.setBadgeText',
|
|
};
|
|
break;
|
|
}
|
|
|
|
// See comment below
|
|
// TODO(anton): remove this once Firefox supports tab.eval() via WebDriver BiDi
|
|
const mustRemoveEval = !test && (platform === PLATFORM.FIREFOX_MV2) && (entry.src === 'src/inject/index.ts');
|
|
|
|
const cacheId = `${entry.src}-${platform}-${debug}-${watch}-${log}-${test}`;
|
|
const outDir = getDestDir({debug, platform});
|
|
|
|
const bundle = await rollup.rollup({
|
|
input: absolutePath(src),
|
|
preserveSymlinks: true,
|
|
onwarn: (error) => {
|
|
// TODO(anton): remove this once Firefox supports tab.eval() via WebDriver BiDi
|
|
if (error.code === 'EVAL' && !mustRemoveEval) {
|
|
return;
|
|
}
|
|
|
|
throw error;
|
|
},
|
|
plugins: [
|
|
// Firefox WebDriver implementation does not currently support tab.eval() functions fully,
|
|
// so we have to manually polyfill it via regular eval().
|
|
// This plugin is necessary to avoid (benign) warnings in the console during builds, it just replaces
|
|
// literally one occurrence of eval() in our code even before TypeScript even encounters it.
|
|
// With this plugin, warning appears only on Firefox test builds.
|
|
// TODO(anton): remove this once Firefox supports tab.eval() via WebDriver BiDi
|
|
// @ts-expect-error This expression is not callable
|
|
rollupPluginReplace({
|
|
preventAssignment: true,
|
|
'eval(': 'void(',
|
|
}),
|
|
// @ts-expect-error This expression is not callable
|
|
rollupPluginNodeResolve(),
|
|
// @ts-expect-error This expression is not callable
|
|
rollupPluginTypescript({
|
|
rootDir: absolutePath('.'),
|
|
typescript,
|
|
tsconfig: absolutePath('src/tsconfig.json'),
|
|
compilerOptions: platform === PLATFORM.CHROMIUM_MV3 ? {
|
|
target: 'ES2022',
|
|
} : undefined,
|
|
noImplicitAny: debug ? false : true,
|
|
noUnusedLocals: debug ? false : true,
|
|
strictNullChecks: debug ? false : true,
|
|
strictPropertyInitialization: false,
|
|
removeComments: debug ? false : true,
|
|
sourceMap: debug ? true : false,
|
|
inlineSources: debug ? true : false,
|
|
noEmitOnError: watch ? false : true,
|
|
outDir,
|
|
paths: platform === PLATFORM.CHROMIUM_MV2_PLUS ? {
|
|
'@plus/*': ['./plus/*'],
|
|
} : {
|
|
'@plus/*': ['./stubs/*'],
|
|
},
|
|
}),
|
|
// @ts-expect-error This expression is not callable
|
|
rollupPluginReplace({
|
|
preventAssignment: true,
|
|
...replace,
|
|
__DEBUG__: debug,
|
|
__CHROMIUM_MV2__: platform === PLATFORM.CHROMIUM_MV2 || platform === PLATFORM.CHROMIUM_MV2_PLUS,
|
|
__CHROMIUM_MV3__: platform === PLATFORM.CHROMIUM_MV3,
|
|
__FIREFOX_MV2__: platform === PLATFORM.FIREFOX_MV2,
|
|
__THUNDERBIRD__: platform === PLATFORM.THUNDERBIRD,
|
|
__PLUS__: platform === PLATFORM.CHROMIUM_MV2_PLUS,
|
|
__PORT__: watch ? String(PORT) : '-1',
|
|
__TEST__: test,
|
|
__WATCH__: watch,
|
|
__LOG__: log ? `"${log}"` : false,
|
|
}),
|
|
].filter(Boolean),
|
|
cache: rollupCache[cacheId],
|
|
});
|
|
rollupCache[cacheId] = bundle.cache;
|
|
entry.watchFiles = bundle.watchFiles;
|
|
await bundle.write({
|
|
file: `${outDir}/${dest}`,
|
|
strict: true,
|
|
format: 'iife',
|
|
sourcemap: debug ? 'inline' : false,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param {JSEntry[]} jsEntries
|
|
* @returns {ReturnType<typeof createTask>}
|
|
*/
|
|
export function createBundleJSTask(jsEntries) {
|
|
/** @type {string[]} */
|
|
let currentWatchFiles;
|
|
|
|
const getRelevantWatchFiles = () => {
|
|
const watchFiles = new Set();
|
|
jsEntries.forEach((entry) => {
|
|
entry.watchFiles?.forEach((file) => watchFiles.add(file));
|
|
});
|
|
return Array.from(watchFiles);
|
|
};
|
|
|
|
/** @type {(options: Partial<TaskOptions> & {platforms: TaskOptions['platforms']}, entries?: JSEntry[]) => Promise<void>} */
|
|
const bundleEachPlatform = async ({platforms, debug, watch, log, test}, entries) => {
|
|
const allPlatforms = Object.values(PLATFORM).filter((platform) => platform !== PLATFORM.API);
|
|
for (const entry of (entries || jsEntries)) {
|
|
const possiblePlatforms = entry.platform ? [entry.platform] : allPlatforms;
|
|
const targetPlatforms = possiblePlatforms.filter((platform) => platforms[platform]);
|
|
for (const platform of targetPlatforms) {
|
|
await bundleJS(entry, platform, debug, watch, log, test);
|
|
}
|
|
}
|
|
};
|
|
|
|
/** @type {(changedFiles: string[], watcher: FSWatcher, platforms: any) => Promise<void>} */
|
|
const onChange = async (changedFiles, watcher, initialPlatforms) => {
|
|
/** @type {any} */
|
|
let platforms = {};
|
|
const connectedBrowsers = reload.getConnectedBrowsers();
|
|
if (connectedBrowsers.includes('chrome')) {
|
|
platforms.chrome = initialPlatforms.chrome;
|
|
platforms['chrome-mv3'] = initialPlatforms['chrome-mv3'];
|
|
platforms['chrome-plus'] = initialPlatforms['chrome-plus'];
|
|
}
|
|
if (connectedBrowsers.includes('firefox')) {
|
|
platforms.firefox = true;
|
|
}
|
|
if (connectedBrowsers.length === 0) {
|
|
platforms = initialPlatforms;
|
|
}
|
|
|
|
const entries = jsEntries.filter((entry) => {
|
|
return changedFiles.some((changed) => {
|
|
return entry.watchFiles?.includes(changed);
|
|
});
|
|
});
|
|
await bundleEachPlatform({platforms, debug: true, watch: true}, entries);
|
|
|
|
const newWatchFiles = getRelevantWatchFiles();
|
|
watcher.unwatch(
|
|
currentWatchFiles.filter((oldFile) => !newWatchFiles.includes(oldFile))
|
|
);
|
|
watcher.add(
|
|
newWatchFiles.filter((newFile) => currentWatchFiles.includes(newFile))
|
|
);
|
|
|
|
const isUIOnly = entries.every((entry) => entry.reloadType === reload.UI);
|
|
reload.reload({
|
|
type: isUIOnly ? reload.UI : reload.FULL,
|
|
});
|
|
};
|
|
|
|
return createTask(
|
|
'bundle-js',
|
|
bundleEachPlatform,
|
|
).addWatcher(
|
|
() => {
|
|
currentWatchFiles = getRelevantWatchFiles();
|
|
return currentWatchFiles;
|
|
},
|
|
onChange,
|
|
);
|
|
}
|
|
|
|
export default createBundleJSTask(jsEntries);
|