From ddb48eda52827aae16aff720212d7b6d2d8647f9 Mon Sep 17 00:00:00 2001 From: Cotes Chung <11371340+cotes2020@users.noreply.github.com> Date: Sat, 11 May 2024 10:29:14 +0800 Subject: [PATCH] perf: lean bootstrap javascript (#1734) --- _data/origin/basic.yml | 1 - _data/origin/cors.yml | 1 - _includes/js-selector.html | 4 +- .../modules/components/category-collapse.js | 3 ++ _javascript/modules/components/clipboard.js | 30 ++++++++---- .../modules/components/tooltip-loader.js | 7 ++- _javascript/pwa/_frontmatter | 3 ++ {assets/js => _javascript}/pwa/app.js | 19 +++----- {assets/js => _javascript}/pwa/sw.js | 11 ++--- package.json | 19 +++++--- rollup.config.js | 46 +++++++++++++++---- 11 files changed, 89 insertions(+), 55 deletions(-) create mode 100644 _javascript/pwa/_frontmatter rename {assets/js => _javascript}/pwa/app.js (73%) rename {assets/js => _javascript}/pwa/sw.js (83%) diff --git a/_data/origin/basic.yml b/_data/origin/basic.yml index 738225150271..9f3415a7ef74 100644 --- a/_data/origin/basic.yml +++ b/_data/origin/basic.yml @@ -6,7 +6,6 @@ webfonts: /assets/lib/fonts/main.css bootstrap: css: /assets/lib/bootstrap/bootstrap.min.css - js: /assets/lib/bootstrap/bootstrap.bundle.min.js toc: css: /assets/lib/tocbot/tocbot.min.css diff --git a/_data/origin/cors.yml b/_data/origin/cors.yml index ff249e6ea9ef..220d131eeb15 100644 --- a/_data/origin/cors.yml +++ b/_data/origin/cors.yml @@ -21,7 +21,6 @@ webfonts: https://fonts.googleapis.com/css2?family=Lato:wght@300;400&family=Sour bootstrap: css: https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css - js: https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js toc: css: https://cdn.jsdelivr.net/npm/tocbot@4.25.0/dist/tocbot.min.css diff --git a/_includes/js-selector.html b/_includes/js-selector.html index 3e1eb4716261..b229b70d7388 100644 --- a/_includes/js-selector.html +++ b/_includes/js-selector.html @@ -2,9 +2,7 @@ -{%- capture urls -%} - {{ site.data.origin[type].bootstrap.js }},{{ site.data.origin[type].search.js }} -{%- endcapture -%} +{% assign urls = site.data.origin[type].search.js %} diff --git a/_javascript/modules/components/category-collapse.js b/_javascript/modules/components/category-collapse.js index b530626e5321..0c53cb4bc936 100644 --- a/_javascript/modules/components/category-collapse.js +++ b/_javascript/modules/components/category-collapse.js @@ -1,6 +1,9 @@ /** * Tab 'Categories' expand/close effect. */ + +import 'bootstrap/js/src/collapse.js'; + const childPrefix = 'l_'; const parentPrefix = 'h_'; const children = document.getElementsByClassName('collapse'); diff --git a/_javascript/modules/components/clipboard.js b/_javascript/modules/components/clipboard.js index 8d4b23ffb930..9566e9d311a8 100644 --- a/_javascript/modules/components/clipboard.js +++ b/_javascript/modules/components/clipboard.js @@ -2,10 +2,11 @@ * Clipboard functions * * Dependencies: - * - popper.js (https://github.com/popperjs/popper-core) - * - clipboard.js (https://github.com/zenorocha/clipboard.js) + * clipboard.js (https://github.com/zenorocha/clipboard.js) */ +import Tooltip from 'bootstrap/js/src/tooltip'; + const clipboardSelector = '.code-header>button'; const ICON_DEFAULT = 'far fa-clipboard'; @@ -38,11 +39,11 @@ function unlock(node) { function showTooltip(btn) { const succeedTitle = btn.getAttribute(ATTR_TITLE_SUCCEED); btn.setAttribute(ATTR_TITLE_ORIGIN, succeedTitle); - bootstrap.Tooltip.getInstance(btn).show(); + Tooltip.getInstance(btn).show(); } function hideTooltip(btn) { - bootstrap.Tooltip.getInstance(btn).hide(); + Tooltip.getInstance(btn).hide(); btn.removeAttribute(ATTR_TITLE_ORIGIN); } @@ -56,7 +57,7 @@ function resumeIcon(btn) { icon.setAttribute('class', ICON_DEFAULT); } -export function initClipboard() { +function setCodeClipboard() { const clipboardList = document.querySelectorAll(clipboardSelector); if (clipboardList.length === 0) { @@ -73,7 +74,7 @@ export function initClipboard() { [...clipboardList].map( (elem) => - new bootstrap.Tooltip(elem, { + new Tooltip(elem, { placement: 'left' }) ); @@ -97,11 +98,15 @@ export function initClipboard() { unlock(trigger); }, TIMEOUT); }); +} - /* --- Post link sharing --- */ - +function setLinkClipboard() { const btnCopyLink = document.getElementById('copy-link'); + if (btnCopyLink === null) { + return; + } + btnCopyLink.addEventListener('click', (e) => { const target = e.target; @@ -116,7 +121,7 @@ export function initClipboard() { // Switch tooltip title target.setAttribute(ATTR_TITLE_ORIGIN, succeedTitle); - bootstrap.Tooltip.getInstance(target).show(); + Tooltip.getInstance(target).show(); lock(target); @@ -128,6 +133,11 @@ export function initClipboard() { }); btnCopyLink.addEventListener('mouseleave', (e) => { - bootstrap.Tooltip.getInstance(e.target).hide(); + Tooltip.getInstance(e.target).hide(); }); } + +export function initClipboard() { + setCodeClipboard(); + setLinkClipboard(); +} diff --git a/_javascript/modules/components/tooltip-loader.js b/_javascript/modules/components/tooltip-loader.js index a90660020fbf..c36c87996994 100644 --- a/_javascript/modules/components/tooltip-loader.js +++ b/_javascript/modules/components/tooltip-loader.js @@ -1,12 +1,11 @@ -/** - * Initial Bootstrap Tooltip. - */ +import Tooltip from 'bootstrap/js/src/tooltip'; + export function loadTooptip() { const tooltipTriggerList = document.querySelectorAll( '[data-bs-toggle="tooltip"]' ); [...tooltipTriggerList].map( - (tooltipTriggerEl) => new bootstrap.Tooltip(tooltipTriggerEl) + (tooltipTriggerEl) => new Tooltip(tooltipTriggerEl) ); } diff --git a/_javascript/pwa/_frontmatter b/_javascript/pwa/_frontmatter new file mode 100644 index 000000000000..97ecf0a6bde2 --- /dev/null +++ b/_javascript/pwa/_frontmatter @@ -0,0 +1,3 @@ +--- +permalink: /:basename +--- diff --git a/assets/js/pwa/app.js b/_javascript/pwa/app.js similarity index 73% rename from assets/js/pwa/app.js rename to _javascript/pwa/app.js index 173ea2938bf5..c71036a56a9f 100644 --- a/assets/js/pwa/app.js +++ b/_javascript/pwa/app.js @@ -1,19 +1,15 @@ ---- -layout: compress -permalink: /assets/js/dist/:basename.min.js ---- +import { pwa, baseurl } from '../../_config.yml'; +import Toast from 'bootstrap/js/src/toast'; if ('serviceWorker' in navigator) { - const isEnabled = '{{ site.pwa.enabled }}' === 'true'; - - if (isEnabled) { - const swUrl = '{{ '/sw.min.js' | relative_url }}'; + if (pwa.enabled) { + const swUrl = `${baseurl}/sw.min.js`; const notification = document.getElementById('notification'); const btnRefresh = notification.querySelector('.toast-body>button'); - const popupWindow = bootstrap.Toast.getOrCreateInstance(notification); + const popupWindow = Toast.getOrCreateInstance(notification); navigator.serviceWorker.register(swUrl).then((registration) => { - {% comment %}In case the user ignores the notification{% endcomment %} + // In case the user ignores the notification if (registration.waiting) { popupWindow.show(); } @@ -32,14 +28,13 @@ if ('serviceWorker' in navigator) { if (registration.waiting) { registration.waiting.postMessage('SKIP_WAITING'); } - popupWindow.hide(); }); }); let refreshing = false; - {% comment %}Detect controller change and refresh all the opened tabs{% endcomment %} + // Detect controller change and refresh all the opened tabs navigator.serviceWorker.addEventListener('controllerchange', () => { if (!refreshing) { window.location.reload(); diff --git a/assets/js/pwa/sw.js b/_javascript/pwa/sw.js similarity index 83% rename from assets/js/pwa/sw.js rename to _javascript/pwa/sw.js index e9609b13179c..bc67bd8307b2 100644 --- a/assets/js/pwa/sw.js +++ b/_javascript/pwa/sw.js @@ -1,12 +1,7 @@ ---- -layout: compress -permalink: /:basename.min.js -# PWA service worker ---- +import { baseurl } from '../../_config.yml'; -const swconfUrl = '{{ '/assets/js/data/swconf.js' | relative_url }}'; +importScripts(`${baseurl}/assets/js/data/swconf.js`); -importScripts(swconfUrl); const purge = swconf.purge; function verifyUrl(url) { @@ -74,7 +69,7 @@ self.addEventListener('fetch', (event) => { return response; } - {% comment %}See: {% endcomment %} + // See: let responseToCache = response.clone(); caches.open(swconf.cacheName).then((cache) => { diff --git a/package.json b/package.json index 3fa3931f8e0f..ef094a125b93 100644 --- a/package.json +++ b/package.json @@ -13,12 +13,16 @@ }, "homepage": "https://github.com/cotes2020/jekyll-theme-chirpy/", "scripts": { - "prebuild": "npx rimraf assets/js/dist", - "build": "NODE_ENV=production npx rollup -c --bundleConfigAsCjs", - "prewatch": "npx rimraf assets/js/dist", - "watch": "npx rollup -c --bundleConfigAsCjs -w", - "test": "npx stylelint _sass/**/*.scss", - "fixlint": "npm run test -- --fix" + "build": "npm run build:js", + "build:js": "rollup -c --bundleConfigAsCjs --environment BUILD:production", + "watch:js": "rollup -c --bundleConfigAsCjs -w", + "lint:style": "stylelint _sass/**/*.scss", + "lint:fix:style": "npm run lint:style -- --fix", + "test": "npm run lint:scss" + }, + "dependencies": { + "@popperjs/core": "^2.11.8", + "bootstrap": "^5.3.3" }, "devDependencies": { "@babel/core": "^7.24.4", @@ -27,13 +31,14 @@ "@commitlint/cli": "^19.2.2", "@commitlint/config-conventional": "^19.2.2", "@rollup/plugin-babel": "^6.0.4", + "@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-terser": "^0.4.4", + "@rollup/plugin-yaml": "^4.1.2", "@semantic-release/changelog": "^6.0.3", "@semantic-release/exec": "^6.0.3", "@semantic-release/git": "^10.0.1", "conventional-changelog-conventionalcommits": "^7.0.2", "husky": "^9.0.11", - "rimraf": "^5.0.5", "rollup": "^4.15.0", "rollup-plugin-license": "^3.3.1", "semantic-release": "^23.0.8", diff --git a/rollup.config.js b/rollup.config.js index 6226080c26fb..823401e0dad3 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,23 +1,42 @@ import babel from '@rollup/plugin-babel'; import terser from '@rollup/plugin-terser'; import license from 'rollup-plugin-license'; +import { nodeResolve } from '@rollup/plugin-node-resolve'; +import fs from 'fs'; import path from 'path'; +import yaml from '@rollup/plugin-yaml'; const SRC_DEFAULT = '_javascript'; const DIST_DEFAULT = 'assets/js/dist'; -const isProd = process.env.NODE_ENV === 'production'; +const SRC_PWA = `${SRC_DEFAULT}/pwa`; + +const isProd = process.env.BUILD === 'production'; + +if (fs.existsSync(DIST_DEFAULT)) { + fs.rm(DIST_DEFAULT, { recursive: true, force: true }, (err) => { + if (err) { + throw err; + } + }); +} + +function build(filename, opts = {}) { + const src = opts.src || SRC_DEFAULT; + const dist = opts.dist || DIST_DEFAULT; + const bannerUrl = + opts.bannerUrl || path.join(__dirname, SRC_DEFAULT, '_copyright'); + const commentStyle = opts.commentStyle || 'ignored'; -function build(filename) { return { - input: [`${SRC_DEFAULT}/${filename}.js`], + input: [`${src}/${filename}.js`], output: { - file: `${DIST_DEFAULT}/${filename}.min.js`, + file: `${dist}/${filename}.min.js`, format: 'iife', name: 'Chirpy', sourcemap: !isProd }, watch: { - include: `${SRC_DEFAULT}/**` + include: `${src}/**` }, plugins: [ babel({ @@ -25,13 +44,16 @@ function build(filename) { presets: ['@babel/env'], plugins: ['@babel/plugin-transform-class-properties'] }), + nodeResolve(), + yaml(), + isProd && commentStyle === 'none' && terser(), license({ banner: { - commentStyle: 'ignored', - content: { file: path.join(__dirname, SRC_DEFAULT, '_copyright') } + commentStyle, + content: { file: bannerUrl } } }), - isProd && terser() + isProd && commentStyle !== 'none' && terser() ] }; } @@ -42,5 +64,11 @@ export default [ build('categories'), build('page'), build('post'), - build('misc') + build('misc'), + build('app', { src: SRC_PWA }), + build('sw', { + src: SRC_PWA, + bannerUrl: path.join(__dirname, SRC_PWA, '_frontmatter'), + commentStyle: 'none' + }) ];