Skip to content

Commit

Permalink
Bundling JS with ESBuild (#702)
Browse files Browse the repository at this point in the history
* add npm dependencies used in this theme

* implement helper to configure JS and ESBuild

* migrate jquery popper.js bootstrap fontawesome to js bundle

* refactor main.js into smaller pieces, and moved navbar.js to assets

* remove list.js. It adjusts post card height to be the same, but is actually not needed.

* refactored notes.js, search.js, single.js into application.js

* move ityped to js asset, implement experiences horizontal vertical line in css

* align recent post height via css

* migrated home.js and refactored into various sections

* migrated darkMode feature to js bundle

* moved mermaid feature to js bundle

* migrate syntax highlight to js bundle

* migrate katex ( js portion ) to js bundle

* migrate pdf-js to js bundle by delegating to cdn

* set explicit comparisions for feature envvars so js can properly optimize

* removed goat-counter

* more fixes for broken achievements and small bugs

* more bug fixes

* allow configuration of hightlight.js, fix video-player shortcode

* remove jquery all together

* add null handling and fix merge conflicts

Co-authored-by: Aaron Qian <[email protected]>
  • Loading branch information
aq1018 and Aaron Qian authored Jan 5, 2023
1 parent fe14b0f commit 02db3d3
Show file tree
Hide file tree
Showing 491 changed files with 5,031 additions and 151,456 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
12 changes: 12 additions & 0 deletions .eslintrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
env:
browser: true
es2021: true
extends:
- standard
- plugin:no-jquery/all
- prettier
plugins:
- no-jquery
parserOptions:
ecmaVersion: latest
sourceType: module
5 changes: 5 additions & 0 deletions .prettierrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
printWidth: 100
tabWidth: 2
semi: false
singleQuote: true
trailingComma: "all"
1 change: 1 addition & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nodejs 18.12.1
8 changes: 8 additions & 0 deletions assets/scripts/application.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import 'popper.js'
import 'bootstrap'
import '@fortawesome/fontawesome-free/js/all'

import './core'
import './features'
import './sections'
import './pages'
36 changes: 36 additions & 0 deletions assets/scripts/core/device.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
let deviceState = {
isMobile: false,
isTablet: false,
isLaptop: false
}

function detectDeviceState () {
if (window.innerWidth <= 425) {
deviceState = {
isMobile: true,
isTablet: false,
isLaptop: false
}
} else if (window.innerWidth <= 768) {
deviceState = {
isMobile: false,
isTablet: true,
isLaptop: false
}
} else {
deviceState = {
isMobile: false,
isTablet: false,
isLaptop: true
}
}
}

detectDeviceState()
window.addEventListener('resize', detectDeviceState)

// returns a copy of the device state
// so other parts of code can't override this.
export function getDeviceState () {
return { ...deviceState }
}
2 changes: 2 additions & 0 deletions assets/scripts/core/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './device'
export * from './insertScript'
14 changes: 14 additions & 0 deletions assets/scripts/core/insertScript.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export const insertScript = (id, src, onload) => {
// script is already inserted, do nothing
if (document.getElementById(id)) return

// insert script
const firstScriptTag = document.getElementsByTagName('script')[0]
const scriptTag = document.createElement('script')
scriptTag.id = id
scriptTag.onload = onload
scriptTag.src = src
scriptTag.defer = true
scriptTag.async = true
firstScriptTag.parentNode.insertBefore(scriptTag, firstScriptTag)
}
30 changes: 30 additions & 0 deletions assets/scripts/features/darkmode/darkreader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { enable, disable, auto, setFetchMethod } from 'darkreader'
import * as params from '@params'

const darkreader = params?.darkmode?.darkreader || {}
const defaultColorScheme = darkreader.defaultColorScheme || 'system'
const theme = {
brightness: 100,
contrast: 100,
sepia: 0,
...(darkreader.theme || {})
}
const fixes = {
invert: ['img[src$=".svg"]'],
...(darkreader.fixes || {})
}
setFetchMethod(window.fetch)

export function setSchemeDark () {
enable(theme, fixes)
}

export function setSchemeLight () {
disable()
}

export function setSchemeSystem () {
auto(theme, fixes)
}

export { defaultColorScheme }
60 changes: 60 additions & 0 deletions assets/scripts/features/darkmode/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
const PERSISTENCE_KEY = 'darkmode:color-scheme'

async function getService () {
if (process.env.FEATURE_DARKMODE_DARKREADER === '1') {
return await import('./darkreader')
}

throw Error(' No service defined for feature darkMode.')
}

window.addEventListener('DOMContentLoaded', async () => {
const menu = document.getElementById('themeMenu')
const $icon = document.getElementById('navbar-theme-icon-svg')
if (menu == null || $icon == null) return

const btns = menu.getElementsByTagName('a')
const iconMap = Array.from(btns).reduce((map, btn) => {
const $img = btn.getElementsByTagName('img')[0]
map[btn.dataset.scheme] = $img.src
return map
}, {})

const {
setSchemeDark,
setSchemeLight,
setSchemeSystem,
defaultColorScheme
} = await getService()

function loadScheme () {
return localStorage.getItem(PERSISTENCE_KEY) || defaultColorScheme
}

function saveScheme (scheme) {
localStorage.setItem(PERSISTENCE_KEY, scheme)
}

function setScheme (newScheme) {
$icon.src = iconMap[newScheme]

if (newScheme === 'dark') {
setSchemeDark()
} else if (newScheme === 'system') {
setSchemeSystem()
} else {
setSchemeLight()
}

saveScheme(newScheme)
}

setScheme(loadScheme())

Array.from(menu.getElementsByTagName('a')).forEach((btn) => {
btn.addEventListener('click', () => {
const { scheme } = btn.dataset
setScheme(scheme)
})
})
})
165 changes: 165 additions & 0 deletions assets/scripts/features/embedpdf/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import { insertScript } from '../../core'

const PDFJS_BUNDLE = 'https://cdn.jsdelivr.net/npm/[email protected]/build/pdf.min.js'
const WORKER_BUNDLE = 'https://cdn.jsdelivr.net/npm/[email protected]/build/pdf.worker.min.js'

class PDFViewer {
constructor (el) {
const {
url,
hidePaginator,
hideLoader,
scale,
pageNum
} = el.dataset

if (url == null) {
throw new Error('Cannot load PDF! Attribute `data-url` is not set.')
}

// props
this.url = url
this.hidePaginator = hidePaginator !== 'false'
this.hideLoader = hideLoader !== 'false'
this.scale = scale || 3

// initial state
this.pageNum = parseInt(pageNum, 10) || 1
this.loaded = false
this.pageRendering = false
this.pageNumPending = null

// DOM elements
this.canvas = el.getElementsByClassName('pdf-canvas')[0]
if (this.canvas == null) {
throw new Error('canvas element not found!')
};
this.paginator = el.getElementsByClassName('paginator')[0]
this.loadingWrapper = el.getElementsByClassName('loading-wrapper')[0]
this.next = el.getElementsByClassName('next')[0]
this.prev = el.getElementsByClassName('prev')[0]
this.pageNum = el.getElementsByClassName('page-num')[0]
this.pageCount = el.getElementsByClassName('page-count')[0]

// context
this.ctx = this.canvas.getContext('2d')

// events
this.next.addEventListener('click', this.handleNextPage.bind(this))
this.prev.addEventListener('click', this.handlePrevPage.bind(this))

this.showPaginator()
this.showLoader()
this.loadPDF()
}

/**
* If we haven't disabled the loader, show loader and hide canvas
*/
showLoader () {
if (this.hideLoader) return
this.loadingWrapper.style.display = 'flex'
this.canvas.style.display = 'none'
}

/**
* If we haven't disabled the paginator, show paginator
*/
showPaginator () {
if (this.hidePaginator) return
this.paginator.style.display = 'block'
}

/**
* Hides loader and shows canvas
*/
showContent () {
this.loadingWrapper.style.display = 'none'
this.canvas.style.display = 'block'
}

/**
* Asynchronously downloads PDF.
*/
async loadPDF () {
this.pdfDoc = await window.pdfjsLib.getDocument(this.url).promise

this.pageCount.textContent = this.pdfDoc.numPages

// If the user passed in a number that is out of range, render the last page.
if (this.pageNum > this.pdfDoc.numPages) {
this.pageNum = this.pdfDoc.numPages
}

this.renderPage(this.pageNum)
}

/**
* Get page info from document, resize canvas accordingly, and render page.
* @param num Page number.
*/
async renderPage (num) {
this.pageRendering = true

const page = await this.pdfDoc.getPage(num)
const viewport = page.getViewport({ scale: this.scale })
this.canvas.height = viewport.height
this.canvas.width = viewport.width

// Wait for rendering to finish
await page.render({
canvasContext: this.ctx,
viewport
}).promise

this.pageRendering = false
this.showContent()

if (this.pageNumPending !== null) {
// New page rendering is pending
this.renderPage(this.pageNumPending)
this.pageNumPending = null
}
// Update page counters
this.pageNum.textContent = num
}

/**
* If another page rendering in progress, waits until the rendering is
* finished. Otherwise, executes rendering immediately.
*/
queueRenderPage (num) {
if (this.pageRendering) {
this.pageNumPending = num
} else {
this.renderPage(num)
}
}

/**
* Displays previous page.
*/
handlePrevPage () {
if (this.pageNum <= 1) {
return
}
this.pageNum--
this.queueRenderPage(this.pageNum)
}

/**
* Displays next page.
*/
handleNextPage () {
if (this.pageNum >= this.pdfDoc.numPages) {
return
}
this.pageNum++
this.queueRenderPage(this.pageNum)
}
}

insertScript('pdfjs', PDFJS_BUNDLE, () => {
window.pdfjsLib.GlobalWorkerOptions.workerSrc = WORKER_BUNDLE
Array.from(document.getElementsByClassName('pdf-viewer')).forEach(el => new PDFViewer(el))
})
3 changes: 3 additions & 0 deletions assets/scripts/features/flowchart/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
if (process.env.FEATURE_FLOWCHART_MERMAID === '1') {
import('./mermaid')
}
7 changes: 7 additions & 0 deletions assets/scripts/features/flowchart/mermaid.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import mermaid from 'mermaid'
import * as params from '@params'

const mermaidOptions = params.flowchart?.mermaid || {}
const options = Object.assign({}, mermaidOptions, { startOnLoad: true })

mermaid.initialize(options)
27 changes: 27 additions & 0 deletions assets/scripts/features/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
if (process.env.FEATURE_VIDEOPLAYER === '1') {
import('./videoplayer')
}

if (process.env.FEATURE_TOC === '1') {
import('./toc')
}

if (process.env.FEATURE_DARKMODE === '1') {
import('./darkmode')
}

if (process.env.FEATURE_FLOWCHART === '1') {
import('./flowchart')
}

if (process.env.FEATURE_SYNTAXHIGHLIGHT === '1') {
import('./syntaxhighlight')
}

if (process.env.FEATURE_MATH === '1') {
import('./math')
}

if (process.env.FEATURE_EMBEDPDF === '1') {
import('./embedpdf')
}
3 changes: 3 additions & 0 deletions assets/scripts/features/math/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
if (process.env.FEATURE_MATH_KATEX === '1') {
import('./katex')
}
Loading

0 comments on commit 02db3d3

Please sign in to comment.