Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge next into master #2206

Merged
merged 11 commits into from
Dec 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: ci
on:
push:
branches: [master, next]
paths-ignore: ['**.md']
paths-ignore: ["**.md"]
pull_request: {}
workflow_dispatch: {}
concurrency:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on:
pull_request: {}
workflow_dispatch: {}
schedule:
- cron: '28 6 * * 4'
- cron: "28 6 * * 4"
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.head.label || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/semgrep-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on:
pull_request: {}
workflow_dispatch: {}
schedule:
- cron: '28 6 * * 4'
- cron: "28 6 * * 4"
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.head.label || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
Expand Down
3 changes: 3 additions & 0 deletions biome.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@
},
"style": {
"noNamespaceImport": "off"
},
"nursery": {
"noDynamicNamespaceImportAccess": "off"
}
}
},
Expand Down
108 changes: 64 additions & 44 deletions build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,9 @@ function compileCSS(src: string, from: string) {
}

/**
* Construct HTML and CSS files and save them to disk.
* Construct HTML file and save it to disk.
*/
async function makeHTML(pageName: string, stylePath: string) {
const styleSrc = await Bun.file(stylePath).text();
const css = compileCSS(styleSrc, stylePath);
async function makeHTML(pageName: string) {
const template = `
<!doctype html>
<meta charset=utf-8>
Expand All @@ -64,10 +62,18 @@ async function makeHTML(pageName: string, stylePath: string) {
.trim()
.replaceAll(/\n\s+/g, '\n'); // remove leading whitespace

await Bun.write(`dist/${pageName}.css`, css);
await Bun.write(`dist/${pageName}.html`, template);
}

/**
* Construct CSS file and save it to disk.
*/
async function makeCSS(pageName: string, cssEntrypoint: string) {
const cssSource = await Bun.file(cssEntrypoint).text();
const css = compileCSS(cssSource, cssEntrypoint);
await Bun.write(`dist/${pageName}.css`, css);
}

/**
* Compile all themes, combine into a single JSON file, and save it to disk.
*/
Expand All @@ -86,29 +92,37 @@ async function makeThemes() {
await Bun.write('dist/themes.json', JSON.stringify(themes));
}

async function minifyJS(artifact: BuildArtifact) {
let source = await artifact.text();

// Improve collapsing variables; terser doesn't do this so we do it manually.
source = source.replaceAll('const ', 'let ');

const result = await terser.minify(source, {
ecma: 2020,
module: true,
compress: {
reduce_funcs: false, // prevent functions being inlined
// XXX: Comment out to keep performance markers for debugging.
pure_funcs: ['performance.mark', 'performance.measure'],
passes: 3,
},
mangle: {
properties: {
regex: /^\$\$|^__click$/,
},
},
});

await Bun.write(artifact.path, result.code ?? '');
async function minifyJS(artifacts: BuildArtifact[]) {
for (const artifact of artifacts) {
if (artifact.kind === 'entry-point' || artifact.kind === 'chunk') {
const source = await artifact.text();
const result = await terser.minify(source, {
ecma: 2020,
module: true,
compress: {
comparisons: false,
negate_iife: false,
reduce_funcs: false, // prevent function inlining
passes: 3,
// XXX: Comment out to keep performance markers for debugging.
pure_funcs: ['performance.mark', 'performance.measure'],
},
mangle: {
properties: {
regex: /^\$\$|^__click$/,
},
},
format: {
wrap_func_args: true,
wrap_iife: true,
},
});

if (result.code) {
await Bun.write(artifact.path, result.code);
}
}
}
}

console.time('prebuild');
Expand All @@ -121,43 +135,49 @@ console.time('manifest');
await Bun.write('dist/manifest.json', JSON.stringify(createManifest()));
console.timeEnd('manifest');

console.time('html+css');
await makeHTML('newtab', 'src/css/newtab.xcss');
await makeHTML('settings', 'src/css/settings.xcss');
console.timeEnd('html+css');
console.time('html');
await makeHTML('newtab');
await makeHTML('settings');
console.timeEnd('html');

console.time('css');
await makeCSS('newtab', 'src/css/newtab.xcss');
await makeCSS('settings', 'src/css/settings.xcss');
console.timeEnd('css');

console.time('themes');
await makeThemes();
console.timeEnd('themes');

// New Tab & Settings apps
console.time('build');
const out = await Bun.build({
console.time('build:1');
const out1 = await Bun.build({
entrypoints: ['src/newtab.ts', 'src/settings.ts'],
outdir: 'dist',
target: 'browser',
minify: !dev,
sourcemap: dev ? 'linked' : 'none',
});
console.timeEnd('build');
console.log(out);
console.timeEnd('build:1');
console.log(out1.outputs);
if (!out1.success) throw new AggregateError(out1.logs, 'Build failed');

// Background service worker script
console.time('build2');
console.time('build:2');
const out2 = await Bun.build({
entrypoints: ['src/sw.ts'],
outdir: 'dist',
target: 'browser',
minify: !dev,
sourcemap: dev ? 'linked' : 'none',
});
console.timeEnd('build2');
console.log(out2);
console.timeEnd('build:2');
console.log(out2.outputs);
if (!out2.success) throw new AggregateError(out2.logs, 'Build failed');

if (!dev) {
console.time('minify');
await minifyJS(out.outputs[0]);
await minifyJS(out.outputs[1]);
await minifyJS(out2.outputs[0]);
console.timeEnd('minify');
console.time('minify:js');
await minifyJS(out1.outputs);
await minifyJS(out2.outputs);
console.timeEnd('minify:js');
}
Binary file modified bun.lockb
Binary file not shown.
5 changes: 3 additions & 2 deletions manifest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ function gitRef() {
}

// FIXME: Remove these once @types/chrome is updated.
// https://developer.chrome.com/docs/extensions/mv3/cross-origin-isolation/
interface ManifestExtra {
/** https://developer.chrome.com/docs/extensions/mv3/manifest/cross_origin_embedder_policy/ */
cross_origin_embedder_policy?: {
Expand All @@ -35,7 +36,7 @@ export const createManifest = (
name: 'New Tab',
description: pkg.description,
homepage_url: pkg.homepage,
version: pkg.version,
version: pkg.version.split('-')[0],
// shippable releases should not have a named version
version_name: debug ? gitRef() : undefined,
minimum_chrome_version: '123', // for light-dark() CSS function
Expand Down Expand Up @@ -68,10 +69,10 @@ export const createManifest = (
content_security_policy: {
extension_pages: [
"default-src 'none'",
"base-uri 'none'",
"script-src 'self'",
"style-src 'self'",
"img-src 'self'",
"base-uri 'none'",
'', // include trailing semicolon
].join(';'),
},
Expand Down
31 changes: 16 additions & 15 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"name": "new-tab",
"version": "0.23.0",
"description": "⚡ A high-performance new tab page that gets you where you need to go faster.",
"repository": "maxmilton/new-tab",
"repository": "github:maxmilton/new-tab",
"bugs": "https://github.com/maxmilton/new-tab/issues",
"homepage": "https://github.com/maxmilton/new-tab",
"author": "Max Milton <[email protected]>",
"license": "MIT",
Expand All @@ -19,27 +20,27 @@
"test:e2e": "playwright test"
},
"dependencies": {
"stage1": "0.8.0-next.13"
"stage1": "0.8.0-next.16"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@ekscss/plugin-import": "0.0.15",
"@eslint/js": "9.14.0",
"@maxmilton/eslint-config": "0.0.7",
"@eslint/js": "9.17.0",
"@maxmilton/eslint-config": "0.0.8",
"@maxmilton/stylelint-config": "0.1.2",
"@maxmilton/test-utils": "0.0.6",
"@playwright/test": "1.48.2",
"@types/bun": "1.1.13",
"@types/chrome": "0.0.280",
"@playwright/test": "1.49.1",
"@types/bun": "1.1.14",
"@types/chrome": "0.0.287",
"ekscss": "0.0.20",
"eslint": "9.14.0",
"eslint-plugin-unicorn": "56.0.0",
"happy-dom": "15.11.0",
"lightningcss": "1.28.1",
"stylelint": "16.10.0",
"eslint": "9.17.0",
"eslint-plugin-unicorn": "56.0.1",
"happy-dom": "15.11.7",
"lightningcss": "1.28.2",
"stylelint": "16.11.0",
"stylelint-config-standard": "36.0.1",
"terser": "5.36.0",
"typescript": "5.6.3",
"typescript-eslint": "8.13.0"
"terser": "5.37.0",
"typescript": "5.7.2",
"typescript-eslint": "8.18.0"
}
}
6 changes: 6 additions & 0 deletions src/components/BookmarkBar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export const BookmarkBar = (): BookmarkBarComponent => {
let index = 0;
let node: ReturnType<typeof BookmarkNode>;

// Add one bookmark at a time until we overflow the max width
for (; index < len; index++) {
node = append(BookmarkNode(bookmarks[index]), root);
width += node.clientWidth;
Expand Down Expand Up @@ -131,3 +132,8 @@ export const BookmarkBar = (): BookmarkBarComponent => {

return root;
};

// // Improve performance of lookups on DOM nodes
// // @ts-expect-error -- add new properties to HTMLElement
// // eslint-disable-next-line no-multi-assign
// Element.prototype.__mouseover = Element.prototype.__mouseout = undefined;
4 changes: 1 addition & 3 deletions src/components/SearchResult.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,7 @@ export const SearchResult = <T extends LinkProps & TabItem>(
root.$$filter = (text) =>
renderList(
rawData.filter((item) =>
(item.title + '[' + item.url)
.toLowerCase()
.includes(text.toLowerCase()),
(item.title + item.url).toLowerCase().includes(text.toLowerCase()),
),
);

Expand Down
12 changes: 6 additions & 6 deletions src/newtab.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
// Theme loader code must run first
import './theme';

import { append, createFragment } from 'stage1';
import { append, fragment } from 'stage1';
import { BookmarkBar } from './components/BookmarkBar';
import { Menu } from './components/Menu';
import { Search } from './components/Search';
import { handleClick, storage } from './utils';

performance.mark('Initialise Components');

const frag = createFragment();
const container = fragment();
// Create Search component first because it has asynchronous calls that can
// execute while the remaining components are constructed
append(Search(), frag);
append(Search(), container);
// Create BookmarkBar component near last because, after an async call, it needs
// to synchronously and sequentially calculate DOM layout multiple times and
// could cause reflow in extreme situations, so paint the rest of the app first
if (!storage.b) append(BookmarkBar(), frag);
append(Menu(), frag);
append(frag, document.body);
if (!storage.b) append(BookmarkBar(), container);
append(Menu(), container);
append(container, document.body);

performance.measure('Initialise Components', 'Initialise Components');

Expand Down
5 changes: 3 additions & 2 deletions src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ interface SettingsState {

type ItemIndex = [listIndex: 0 | 1, itemIndex: number];

/** Section drag-and-drop helper functions */
interface SectionScope {
indexOf(list: 0 | 1, item: SectionOrderItem): number;
moveItem(from: ItemIndex, to: ItemIndex): void;
Expand All @@ -24,7 +25,7 @@ const DEFAULT_THEME = 'auto';

// eslint-disable-next-line unicorn/prefer-top-level-await
const themesData = fetch('themes.json').then(
(res) => res.json() as Promise<ThemesData>,
(response) => response.json() as Promise<ThemesData>,
);

type SectionComponent = HTMLLIElement;
Expand Down Expand Up @@ -137,7 +138,7 @@ const meta = compile(`
<div class=row>
<label>Sections</label>
<fieldset>
<legend>DISPLAY ORDER</legend>
<legend>DISPLAY (in order)</legend>
<ul @se class=item-list></ul>
</fieldset>
<fieldset>
Expand Down
6 changes: 3 additions & 3 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ declare global {
}
}

/** JSON object with theme name keys and raw CSS code values. */
export type ThemesData = Record<string, string>;

export type SectionOrderItem = (typeof DEFAULT_SECTION_ORDER)[number];

export interface UserStorageData {
Expand All @@ -20,6 +23,3 @@ export interface UserStorageData {
/** Sections order user preference. */
o?: SectionOrderItem[];
}

/** JSON object with theme name keys and raw CSS code values. */
export type ThemesData = Record<string, string>;
4 changes: 2 additions & 2 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { UserStorageData } from './types';

performance.mark('Load Storage');
export const storage: UserStorageData = await chrome.storage.local.get();
export const storage = await chrome.storage.local.get<UserStorageData>();

// NOTE: When updating also update references that lookup items by index
export const DEFAULT_SECTION_ORDER = [
Expand All @@ -17,7 +17,7 @@ declare const s: HTMLInputElement;

// Simplified synthetic click event implementation of setupSyntheticEvent() from
// stage1, plus special handling for browser internal links (e.g. chrome://)
// https://github.com/maxmilton/stage1/blob/master/src/events.ts
// @see https://github.com/maxmilton/stage1/blob/master/src/events.ts
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type, consistent-return
export const handleClick = (event: MouseEvent): false | void => {
let node = event.target as
Expand Down
Loading
Loading