Skip to content
Open
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
6 changes: 6 additions & 0 deletions _config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,12 @@ calendar:
# For more information: https://www.w3.org/TR/resource-hints/#preconnect
preconnect: false

# Performance optimization
performance:
# Lazy-load non-critical CSS (FontAwesome, Fancybox, KaTeX)
# This improves Lighthouse scores by making these CSS files non-render-blocking
lazy_css: false

# Set the text alignment in posts / pages.
text_align:
# Available values: start | end | left | right | center | justify | justify-all | match-parent
Expand Down
4 changes: 2 additions & 2 deletions layout/_partials/head/head.njk
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,14 @@

{{ next_font() }}

{{ next_vendors('fontawesome') }}
{{ next_vendors('fontawesome', { lazy: true }) }}

{%- if theme.motion.enable %}
{{ next_vendors('animate_css') }}
{%- endif %}

{%- if theme.fancybox %}
{{ next_vendors('fancybox_css') }}
{{ next_vendors('fancybox_css', { lazy: true }) }}
{%- endif %}

{%- if theme.pace.enable %}
Expand Down
2 changes: 1 addition & 1 deletion layout/_third-party/math/katex.njk
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{{ next_vendors('katex') }}
{{ next_vendors('katex', { lazy: true }) }}
{%- if theme.math.katex.copy_tex %}
{{ next_data('katex', {
copy_tex_js: theme.vendors.copy_tex_js
Expand Down
68 changes: 42 additions & 26 deletions scripts/events/lib/vendors.js
Original file line number Diff line number Diff line change
@@ -1,55 +1,71 @@
'use strict';
"use strict";

Check failure on line 1 in scripts/events/lib/vendors.js

View workflow job for this annotation

GitHub Actions / linter

Strings must use singlequote

const fs = require('fs');
const path = require('path');
const yaml = require('js-yaml');
const { url_for } = require('hexo-util');
const { getVendors } = require('./utils');
const fs = require("fs");

Check failure on line 3 in scripts/events/lib/vendors.js

View workflow job for this annotation

GitHub Actions / linter

Strings must use singlequote
const path = require("path");

Check failure on line 4 in scripts/events/lib/vendors.js

View workflow job for this annotation

GitHub Actions / linter

Strings must use singlequote
const yaml = require("js-yaml");

Check failure on line 5 in scripts/events/lib/vendors.js

View workflow job for this annotation

GitHub Actions / linter

Strings must use singlequote
const { url_for } = require("hexo-util");

Check failure on line 6 in scripts/events/lib/vendors.js

View workflow job for this annotation

GitHub Actions / linter

Strings must use singlequote
const { getVendors } = require("./utils");

Check failure on line 7 in scripts/events/lib/vendors.js

View workflow job for this annotation

GitHub Actions / linter

Strings must use singlequote

let internal;
try {
internal = require('@next-theme/plugins');
} catch {
}
const vendorsFile = fs.readFileSync(path.join(__dirname, '../../../_vendors.yml'));
internal = require("@next-theme/plugins");

Check failure on line 11 in scripts/events/lib/vendors.js

View workflow job for this annotation

GitHub Actions / linter

Strings must use singlequote
} catch {}
const vendorsFile = fs.readFileSync(
path.join(__dirname, "../../../_vendors.yml"),

Check failure on line 14 in scripts/events/lib/vendors.js

View workflow job for this annotation

GitHub Actions / linter

Unexpected trailing comma

Check failure on line 14 in scripts/events/lib/vendors.js

View workflow job for this annotation

GitHub Actions / linter

Strings must use singlequote
);
const dependencies = yaml.load(vendorsFile);

module.exports = hexo => {
module.exports = (hexo) => {

Check failure on line 18 in scripts/events/lib/vendors.js

View workflow job for this annotation

GitHub Actions / linter

Unexpected parentheses around single function argument
const { vendors, creative_commons, pace } = hexo.theme.config;
if (typeof internal === 'function') {
if (typeof internal === "function") {
internal(hexo, dependencies);
}
let { plugins = 'cdnjs' } = vendors;
if (plugins === 'local' && typeof internal === 'undefined') {
hexo.log.warn('Dependencies for `plugins: local` not found. The default CDN provider CDNJS is used instead.');
hexo.log.warn('Run `npm install @next-theme/plugins` in Hexo site root directory to install the plugin.');
plugins = 'cdnjs';
let { plugins = "cdnjs" } = vendors;
if (plugins === "local" && typeof internal === "undefined") {
hexo.log.warn(
"Dependencies for `plugins: local` not found. The default CDN provider CDNJS is used instead.",
);
hexo.log.warn(
"Run `npm install @next-theme/plugins` in Hexo site root directory to install the plugin.",
);
plugins = "cdnjs";
}
for (const [key, value] of Object.entries(dependencies)) {
// This script will be executed repeatedly when Hexo listens file changes
// But the variable vendors[key] only needs to be modified once
if (vendors[key] && typeof vendors[key] === 'string') {
if (vendors[key] && typeof vendors[key] === "string") {
vendors[key] = {
url: url_for.call(hexo, vendors[key])
url: url_for.call(hexo, vendors[key]),
};
continue;
}
if (key === 'creative_commons') {
value.file = `${value.dir}/${creative_commons.size}/${creative_commons.license.replace(/-/g, '_')}.svg`;
if (key === "creative_commons") {
value.file = `${value.dir}/${creative_commons.size}/${creative_commons.license.replace(/-/g, "_")}.svg`;
}
if (key === 'pace_css') {
if (key === "pace_css") {
value.file = `${value.dir}/${pace.color}/pace-theme-${pace.theme}.css`;
}
const { name, file } = value;
const links = getVendors({
...value,
minified: file,
local : url_for.call(hexo, `lib/${name}/${file}`),
custom : vendors.custom_cdn_url
local: url_for.call(hexo, `lib/${name}/${file}`),
custom: vendors.custom_cdn_url,
});

// For local plugins, use calculated integrity hash from the plugin
// For CDN, use the hardcoded hash from _vendors.yml
let integrityHash = value.integrity;
if (plugins === "local" && typeof internal === "function") {
const localHash = internal.getLocalIntegrity(`lib/${name}/${file}`);
if (localHash) {
integrityHash = localHash;
}
}

vendors[key] = {
url : links[plugins] || links.cdnjs,
integrity: value.integrity
url: links[plugins] || links.cdnjs,
integrity: integrityHash,
};
}
};
15 changes: 14 additions & 1 deletion scripts/helpers/engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,26 @@ hexo.extend.helper.register('next_js', function(file, {
return `<script ${pjax ? 'data-pjax ' : ''}${module ? 'type="module" ' : ''}src="${src}" defer></script>`;
});

hexo.extend.helper.register('next_vendors', function(name) {
hexo.extend.helper.register('next_vendors', function(name, options = {}) {
const { url, integrity } = this.theme.vendors[name];
const type = url.endsWith('css') ? 'css' : 'js';
const { lazy = false } = options;

if (type === 'css') {
const integrityAttr = integrity ? ` integrity="${integrity}" crossorigin="anonymous"` : '';

// Lazy-load CSS using preload + onload technique
if (lazy && this.theme.performance?.lazy_css) {
return `<link rel="preload" href="${url}" as="style" onload="this.onload=null;this.rel='stylesheet'"${integrityAttr}>
<noscript><link rel="stylesheet" href="${url}"${integrityAttr}></noscript>`;
}

// Default: render-blocking CSS
if (integrity) return `<link rel="stylesheet" href="${url}" integrity="${integrity}" crossorigin="anonymous">`;
return `<link rel="stylesheet" href="${url}">`;
}

// JS handling unchanged (already uses defer)
if (integrity) return `<script src="${url}" integrity="${integrity}" crossorigin="anonymous" defer></script>`;
return `<script src="${url}" defer></script>`;
});
Expand Down