mirror of
https://github.com/LemmyNet/lemmy-ui.git
synced 2024-11-22 04:11:12 +00:00
Lazy loading less common languages for syntax highlighting (#2388)
* Use fewer syntax highlighter languages. Reduces client.js size by about 250kB (800kB uncompressed) Common languages: bash, c, cpp, csharp, css, diff, go, graphql, ini, java, javascript, json, kotlin, less, lua, makefile, markdown, objectivec, perl, php-template, php, plaintext, python-repl, python, r, ruby, rust, scss, shell, sql, swift, typescript, vbnet, wasm, xml, yaml Additionally enabled languages: dockerfile, pgsql * Configurable syntax highlighter languages Allows to individually enable languages. * Lazy load syntax highlighter languages Allows to enable additional languages that will not be autodetected. * Include highlight.js in dynamic import check
This commit is contained in:
parent
e832cd2729
commit
201e5fcd53
23 changed files with 291 additions and 28 deletions
|
@ -1,5 +1,6 @@
|
||||||
generate_translations.js
|
generate_translations.js
|
||||||
webpack.config.js
|
webpack.config.js
|
||||||
|
src/shared/build-config.js
|
||||||
src/api_tests
|
src/api_tests
|
||||||
**/*.png
|
**/*.png
|
||||||
**/*.css
|
**/*.css
|
||||||
|
|
|
@ -48,6 +48,7 @@
|
||||||
"emoji-mart": "^5.5.2",
|
"emoji-mart": "^5.5.2",
|
||||||
"emoji-short-name": "^2.0.0",
|
"emoji-short-name": "^2.0.0",
|
||||||
"express": "~4.18.2",
|
"express": "~4.18.2",
|
||||||
|
"highlight.js": "^11.9.0",
|
||||||
"history": "^5.3.0",
|
"history": "^5.3.0",
|
||||||
"html-to-text": "^9.0.5",
|
"html-to-text": "^9.0.5",
|
||||||
"i18next": "^23.10.0",
|
"i18next": "^23.10.0",
|
||||||
|
|
|
@ -74,6 +74,9 @@ dependencies:
|
||||||
express:
|
express:
|
||||||
specifier: ~4.18.2
|
specifier: ~4.18.2
|
||||||
version: 4.18.2
|
version: 4.18.2
|
||||||
|
highlight.js:
|
||||||
|
specifier: ^11.9.0
|
||||||
|
version: 11.9.0
|
||||||
history:
|
history:
|
||||||
specifier: ^5.3.0
|
specifier: ^5.3.0
|
||||||
version: 5.3.0
|
version: 5.3.0
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { initializeSite } from "@utils/app";
|
||||||
import { hydrate } from "inferno-hydrate";
|
import { hydrate } from "inferno-hydrate";
|
||||||
import { BrowserRouter } from "inferno-router";
|
import { BrowserRouter } from "inferno-router";
|
||||||
import { App } from "../shared/components/app/app";
|
import { App } from "../shared/components/app/app";
|
||||||
|
import { lazyHighlightjs } from "../shared/lazy-highlightjs";
|
||||||
import { loadUserLanguage } from "../shared/services/I18NextService";
|
import { loadUserLanguage } from "../shared/services/I18NextService";
|
||||||
import { verifyDynamicImports } from "../shared/dynamic-imports";
|
import { verifyDynamicImports } from "../shared/dynamic-imports";
|
||||||
|
|
||||||
|
@ -17,6 +18,8 @@ async function startClient() {
|
||||||
|
|
||||||
initializeSite(window.isoData.site_res);
|
initializeSite(window.isoData.site_res);
|
||||||
|
|
||||||
|
lazyHighlightjs.enableLazyLoading();
|
||||||
|
|
||||||
await loadUserLanguage();
|
await loadUserLanguage();
|
||||||
|
|
||||||
const wrapper = (
|
const wrapper = (
|
||||||
|
|
2
src/shared/build-config.d.ts
vendored
Normal file
2
src/shared/build-config.d.ts
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export const bundledSyntaxHighlighters: ["plaintext", ...string[]];
|
||||||
|
export const lazySyntaxHighlighters: string[] | "*";
|
26
src/shared/build-config.js
Normal file
26
src/shared/build-config.js
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
// Don't import/require things here. This file is also imported in
|
||||||
|
// webpack.config.js. Needs dev server restart to apply changes.
|
||||||
|
|
||||||
|
/** Bundled highlighters can be autodetected in markdown.
|
||||||
|
* @type ["plaintext", ...string[]] **/
|
||||||
|
// prettier-ignore
|
||||||
|
const bundledSyntaxHighlighters = [
|
||||||
|
"plaintext",
|
||||||
|
// The 'Common' set of highlight.js languages.
|
||||||
|
"bash", "c", "cpp", "csharp", "css", "diff", "go", "graphql", "ini", "java",
|
||||||
|
"javascript", "json", "kotlin", "less", "lua", "makefile", "markdown",
|
||||||
|
"objectivec", "perl", "php-template", "php", "python-repl", "python", "r",
|
||||||
|
"ruby", "rust", "scss", "shell", "sql", "swift", "typescript", "vbnet",
|
||||||
|
"wasm", "xml", "yaml",
|
||||||
|
];
|
||||||
|
|
||||||
|
/** Lazy highlighters can't be autodetected, they have to be explicitly specified
|
||||||
|
* as the language. (e.g. ```dockerfile ...)
|
||||||
|
* "*" enables all non-bundled languages
|
||||||
|
* @type string[] | "*" **/
|
||||||
|
const lazySyntaxHighlighters = "*";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
bundledSyntaxHighlighters,
|
||||||
|
lazySyntaxHighlighters,
|
||||||
|
};
|
|
@ -305,8 +305,12 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
className="md-div"
|
className="md-div"
|
||||||
dangerouslySetInnerHTML={
|
dangerouslySetInnerHTML={
|
||||||
this.props.hideImages
|
this.props.hideImages
|
||||||
? mdToHtmlNoImages(this.commentUnlessRemoved)
|
? mdToHtmlNoImages(this.commentUnlessRemoved, () =>
|
||||||
: mdToHtml(this.commentUnlessRemoved)
|
this.forceUpdate(),
|
||||||
|
)
|
||||||
|
: mdToHtml(this.commentUnlessRemoved, () =>
|
||||||
|
this.forceUpdate(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -250,7 +250,9 @@ export class MarkdownTextArea extends Component<
|
||||||
{this.state.previewMode && this.state.content && (
|
{this.state.previewMode && this.state.content && (
|
||||||
<div
|
<div
|
||||||
className="card border-secondary card-body md-div"
|
className="card border-secondary card-body md-div"
|
||||||
dangerouslySetInnerHTML={mdToHtml(this.state.content)}
|
dangerouslySetInnerHTML={mdToHtml(this.state.content, () =>
|
||||||
|
this.forceUpdate(),
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{this.state.imageUploadStatus &&
|
{this.state.imageUploadStatus &&
|
||||||
|
|
|
@ -68,7 +68,12 @@ export class RegistrationApplication extends Component<
|
||||||
<MomentTime showAgo published={ra.published} />
|
<MomentTime showAgo published={ra.published} />
|
||||||
</div>
|
</div>
|
||||||
<div>{I18NextService.i18n.t("answer")}:</div>
|
<div>{I18NextService.i18n.t("answer")}:</div>
|
||||||
<div className="md-div" dangerouslySetInnerHTML={mdToHtml(ra.answer)} />
|
<div
|
||||||
|
className="md-div"
|
||||||
|
dangerouslySetInnerHTML={mdToHtml(ra.answer, () =>
|
||||||
|
this.forceUpdate(),
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
{a.admin && (
|
{a.admin && (
|
||||||
<div>
|
<div>
|
||||||
|
@ -88,7 +93,9 @@ export class RegistrationApplication extends Component<
|
||||||
{I18NextService.i18n.t("deny_reason")}:{" "}
|
{I18NextService.i18n.t("deny_reason")}:{" "}
|
||||||
<div
|
<div
|
||||||
className="md-div d-inline-flex"
|
className="md-div d-inline-flex"
|
||||||
dangerouslySetInnerHTML={mdToHtml(ra.deny_reason)}
|
dangerouslySetInnerHTML={mdToHtml(ra.deny_reason, () =>
|
||||||
|
this.forceUpdate(),
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -271,7 +271,10 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
||||||
const desc = this.props.community_view.community.description;
|
const desc = this.props.community_view.community.description;
|
||||||
return (
|
return (
|
||||||
desc && (
|
desc && (
|
||||||
<div className="md-div" dangerouslySetInnerHTML={mdToHtml(desc)} />
|
<div
|
||||||
|
className="md-div"
|
||||||
|
dangerouslySetInnerHTML={mdToHtml(desc, () => this.forceUpdate())}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -401,7 +401,9 @@ export class Home extends Component<any, HomeState> {
|
||||||
{tagline && (
|
{tagline && (
|
||||||
<div
|
<div
|
||||||
id="tagline"
|
id="tagline"
|
||||||
dangerouslySetInnerHTML={mdToHtml(tagline)}
|
dangerouslySetInnerHTML={mdToHtml(tagline, () =>
|
||||||
|
this.forceUpdate(),
|
||||||
|
)}
|
||||||
></div>
|
></div>
|
||||||
)}
|
)}
|
||||||
<div className="d-block d-md-none">{this.mobileView}</div>
|
<div className="d-block d-md-none">{this.mobileView}</div>
|
||||||
|
|
|
@ -32,7 +32,10 @@ export class Legal extends Component<any, LegalState> {
|
||||||
path={this.context.router.route.match.url}
|
path={this.context.router.route.match.url}
|
||||||
/>
|
/>
|
||||||
{legal && (
|
{legal && (
|
||||||
<div className="md-div" dangerouslySetInnerHTML={mdToHtml(legal)} />
|
<div
|
||||||
|
className="md-div"
|
||||||
|
dangerouslySetInnerHTML={mdToHtml(legal, () => this.forceUpdate())}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -221,6 +221,7 @@ export class Signup extends Component<any, State> {
|
||||||
className="md-div"
|
className="md-div"
|
||||||
dangerouslySetInnerHTML={mdToHtml(
|
dangerouslySetInnerHTML={mdToHtml(
|
||||||
siteView.local_site.application_question,
|
siteView.local_site.application_question,
|
||||||
|
() => this.forceUpdate(),
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -99,7 +99,10 @@ export class SiteSidebar extends Component<SiteSidebarProps, SiteSidebarState> {
|
||||||
|
|
||||||
siteSidebar(sidebar: string) {
|
siteSidebar(sidebar: string) {
|
||||||
return (
|
return (
|
||||||
<div className="md-div" dangerouslySetInnerHTML={mdToHtml(sidebar)} />
|
<div
|
||||||
|
className="md-div"
|
||||||
|
dangerouslySetInnerHTML={mdToHtml(sidebar, () => this.forceUpdate())}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -586,7 +586,9 @@ export class Profile extends Component<
|
||||||
<div className="d-flex align-items-center mb-2">
|
<div className="d-flex align-items-center mb-2">
|
||||||
<div
|
<div
|
||||||
className="md-div"
|
className="md-div"
|
||||||
dangerouslySetInnerHTML={mdToHtml(pv.person.bio)}
|
dangerouslySetInnerHTML={mdToHtml(pv.person.bio, () =>
|
||||||
|
this.forceUpdate(),
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -177,7 +177,10 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
||||||
{this.state.viewSource ? (
|
{this.state.viewSource ? (
|
||||||
<pre>{body}</pre>
|
<pre>{body}</pre>
|
||||||
) : (
|
) : (
|
||||||
<div className="md-div" dangerouslySetInnerHTML={mdToHtml(body)} />
|
<div
|
||||||
|
className="md-div"
|
||||||
|
dangerouslySetInnerHTML={mdToHtml(body, () => this.forceUpdate())}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</article>
|
</article>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
@ -52,7 +52,9 @@ export class PrivateMessageReport extends Component<Props, State> {
|
||||||
{I18NextService.i18n.t("message")}:
|
{I18NextService.i18n.t("message")}:
|
||||||
<div
|
<div
|
||||||
className="md-div"
|
className="md-div"
|
||||||
dangerouslySetInnerHTML={mdToHtml(pmr.original_pm_text)}
|
dangerouslySetInnerHTML={mdToHtml(pmr.original_pm_text, () =>
|
||||||
|
this.forceUpdate(),
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -135,7 +135,10 @@ export class PrivateMessage extends Component<
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
className="md-div"
|
className="md-div"
|
||||||
dangerouslySetInnerHTML={mdToHtml(this.messageUnlessRemoved)}
|
dangerouslySetInnerHTML={mdToHtml(
|
||||||
|
this.messageUnlessRemoved,
|
||||||
|
() => this.forceUpdate(),
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<ul className="list-inline mb-0 text-muted fw-bold">
|
<ul className="list-inline mb-0 text-muted fw-bold">
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
import { verifyTranslationImports } from "./services/I18NextService";
|
import { verifyTranslationImports } from "./services/I18NextService";
|
||||||
import { verifyDateFnsImports } from "@utils/app/setup-date-fns";
|
import { verifyDateFnsImports } from "@utils/app/setup-date-fns";
|
||||||
|
import { verifyHighlighjsImports } from "./lazy-highlightjs";
|
||||||
|
|
||||||
export class ImportReport {
|
export class ImportReport {
|
||||||
error: Array<{ id: string; error: Error | string | undefined }> = [];
|
error: Array<{ id: string; error: Error | string | undefined }> = [];
|
||||||
success: string[] = [];
|
success: string[] = [];
|
||||||
|
message?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ImportReportCollection = {
|
export type ImportReportCollection = {
|
||||||
translation?: ImportReport;
|
translation?: ImportReport;
|
||||||
"date-fns"?: ImportReport;
|
"date-fns"?: ImportReport;
|
||||||
|
"highlight.js"?: ImportReport;
|
||||||
};
|
};
|
||||||
|
|
||||||
function collect(
|
function collect(
|
||||||
|
@ -22,12 +25,15 @@ function collect(
|
||||||
for (const { id, error } of report.error) {
|
for (const { id, error } of report.error) {
|
||||||
console.warn(`${kind} "${id}" failed: ${error}`);
|
console.warn(`${kind} "${id}" failed: ${error}`);
|
||||||
}
|
}
|
||||||
|
const message = report.message ? ` (${report.message})` : "";
|
||||||
const good = report.success.length;
|
const good = report.success.length;
|
||||||
const bad = report.error.length;
|
const bad = report.error.length;
|
||||||
if (bad) {
|
if (bad) {
|
||||||
console.error(`${bad} out of ${bad + good} ${kind} imports failed.`);
|
console.error(
|
||||||
|
`${bad} out of ${bad + good} ${kind} imports failed.` + message,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
console.log(`${good} ${kind} imports verified.`);
|
console.log(`${good} ${kind} imports verified.` + message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,5 +51,8 @@ export async function verifyDynamicImports(
|
||||||
await verifyDateFnsImports().then(report =>
|
await verifyDateFnsImports().then(report =>
|
||||||
collect(verbose, "date-fns", collection, report),
|
collect(verbose, "date-fns", collection, report),
|
||||||
);
|
);
|
||||||
|
await verifyHighlighjsImports().then(report =>
|
||||||
|
collect(verbose, "highlight.js", collection, report),
|
||||||
|
);
|
||||||
return collection;
|
return collection;
|
||||||
}
|
}
|
||||||
|
|
145
src/shared/lazy-highlightjs.ts
Normal file
145
src/shared/lazy-highlightjs.ts
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
import { HLJSApi, HLJSPlugin, LanguageFn } from "highlight.js";
|
||||||
|
import hljs from "highlight.js/lib/core";
|
||||||
|
import {
|
||||||
|
bundledSyntaxHighlighters,
|
||||||
|
lazySyntaxHighlighters,
|
||||||
|
} from "./build-config";
|
||||||
|
import { isBrowser } from "@utils/browser";
|
||||||
|
import { default as MarkdownIt } from "markdown-it";
|
||||||
|
import { ImportReport } from "./dynamic-imports";
|
||||||
|
|
||||||
|
async function lazyLoad(lang: string): Promise<LanguageFn> {
|
||||||
|
return import(
|
||||||
|
/* webpackChunkName: "hljs-[request]" */
|
||||||
|
`highlight.js/lib/languages/${lang}.js`
|
||||||
|
).then(x => x.default);
|
||||||
|
}
|
||||||
|
|
||||||
|
class LazyHighlightjs implements HLJSPlugin {
|
||||||
|
public hljs: HLJSApi;
|
||||||
|
|
||||||
|
private loadedLanguages: Set<string> = new Set();
|
||||||
|
private failedLanguages: Set<string> = new Set();
|
||||||
|
|
||||||
|
constructor(hljs: HLJSApi) {
|
||||||
|
this.hljs = hljs;
|
||||||
|
this.hljs.addPlugin(this);
|
||||||
|
|
||||||
|
// For consistent autodetection behaviour.
|
||||||
|
this.hljs.configure({ languages: bundledSyntaxHighlighters });
|
||||||
|
|
||||||
|
for (const lang of bundledSyntaxHighlighters) {
|
||||||
|
//"eager" means bundled, imports are resolved pseudo synchronously
|
||||||
|
import(
|
||||||
|
/* webpackMode: "eager" */
|
||||||
|
`highlight.js/lib/languages/${lang}.js`
|
||||||
|
)
|
||||||
|
.then(x => {
|
||||||
|
hljs.registerLanguage(lang, x.default);
|
||||||
|
this.loadedLanguages.add(lang);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(`Syntax highlighter "${lang}" failed:`, err);
|
||||||
|
this.failedLanguages.add(lang);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadLanguage(lang: string): Promise<LanguageFn> {
|
||||||
|
const promise = lazyLoad(lang);
|
||||||
|
promise
|
||||||
|
.then(x => {
|
||||||
|
this.hljs.registerLanguage(lang, x);
|
||||||
|
this.loadedLanguages.add(lang);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.failedLanguages.add(lang);
|
||||||
|
});
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
private enabled: boolean = false;
|
||||||
|
private current?: {
|
||||||
|
readonly callback: () => void;
|
||||||
|
readonly pending: Set<string>;
|
||||||
|
};
|
||||||
|
|
||||||
|
"before:highlight"(context: { language: string }) {
|
||||||
|
const { language: lang } = context;
|
||||||
|
if (this.loadedLanguages.has(lang)) return;
|
||||||
|
context.language = "plaintext"; // Silences "Could not find language"
|
||||||
|
if (!this.enabled || this.failedLanguages.has(lang)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.current) {
|
||||||
|
console.warn(`Lazy highlightjs "${lang}" without callback.`);
|
||||||
|
this.loadLanguage(lang);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { callback, pending } = this.current;
|
||||||
|
pending.add(lang);
|
||||||
|
this.loadLanguage(lang)
|
||||||
|
.catch(() => {})
|
||||||
|
.finally(() => {
|
||||||
|
if (pending.delete(lang) && !pending.size) {
|
||||||
|
// last remaining language removed, can call callback
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(
|
||||||
|
md: MarkdownIt,
|
||||||
|
text: string,
|
||||||
|
shouldRerender: () => void,
|
||||||
|
): string {
|
||||||
|
if (this.current) throw "no nesting";
|
||||||
|
if (shouldRerender) {
|
||||||
|
this.current = { callback: shouldRerender, pending: new Set() };
|
||||||
|
}
|
||||||
|
const result = md.render(text);
|
||||||
|
this.current = undefined;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enableLazyLoading() {
|
||||||
|
// When the server renders in a lazy language, the client will replace it
|
||||||
|
// with a plaintext render until the language is loaded.
|
||||||
|
console.assert(isBrowser(), "No lazy loading on server.");
|
||||||
|
this.enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public disableLazyLoading() {
|
||||||
|
this.enabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const lazyHighlightjs = new LazyHighlightjs(hljs);
|
||||||
|
|
||||||
|
export async function verifyHighlighjsImports(): Promise<ImportReport> {
|
||||||
|
const report = new ImportReport();
|
||||||
|
let langs =
|
||||||
|
lazySyntaxHighlighters === "*"
|
||||||
|
? ["dockerfile", "pgsql", "django", "nginx"]
|
||||||
|
: lazySyntaxHighlighters;
|
||||||
|
if (lazySyntaxHighlighters === "*") {
|
||||||
|
langs = langs.filter(l => !bundledSyntaxHighlighters.includes(l));
|
||||||
|
// Avoid confusions about how few highlighters are enabled.
|
||||||
|
report.message = `Only testing ${langs.length} samples.`;
|
||||||
|
}
|
||||||
|
const promises = langs.map(lang =>
|
||||||
|
lazyLoad(lang)
|
||||||
|
.then(x => {
|
||||||
|
if (x && x instanceof Function && x(hljs).name) {
|
||||||
|
report.success.push(lang);
|
||||||
|
} else {
|
||||||
|
throw "unexpected format";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => report.error.push({ id: lang, error: err })),
|
||||||
|
);
|
||||||
|
await Promise.all(promises);
|
||||||
|
return report;
|
||||||
|
}
|
|
@ -14,10 +14,11 @@ import markdown_it_html5_embed from "markdown-it-html5-embed";
|
||||||
import markdown_it_ruby from "markdown-it-ruby";
|
import markdown_it_ruby from "markdown-it-ruby";
|
||||||
import markdown_it_sub from "markdown-it-sub";
|
import markdown_it_sub from "markdown-it-sub";
|
||||||
import markdown_it_sup from "markdown-it-sup";
|
import markdown_it_sup from "markdown-it-sup";
|
||||||
import markdown_it_highlightjs from "markdown-it-highlightjs";
|
import markdown_it_highlightjs from "markdown-it-highlightjs/core";
|
||||||
import Renderer from "markdown-it/lib/renderer";
|
import Renderer from "markdown-it/lib/renderer";
|
||||||
import Token from "markdown-it/lib/token";
|
import Token from "markdown-it/lib/token";
|
||||||
import { instanceLinkRegex, relTags } from "./config";
|
import { instanceLinkRegex, relTags } from "./config";
|
||||||
|
import { lazyHighlightjs } from "./lazy-highlightjs";
|
||||||
|
|
||||||
export let Tribute: any;
|
export let Tribute: any;
|
||||||
|
|
||||||
|
@ -44,12 +45,12 @@ if (isBrowser()) {
|
||||||
Tribute = require("tributejs");
|
Tribute = require("tributejs");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mdToHtml(text: string) {
|
export function mdToHtml(text: string, rerender: () => void) {
|
||||||
return { __html: md.render(text) };
|
return { __html: lazyHighlightjs.render(md, text, rerender) };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mdToHtmlNoImages(text: string) {
|
export function mdToHtmlNoImages(text: string, rerender: () => void) {
|
||||||
return { __html: mdNoImages.render(text) };
|
return { __html: lazyHighlightjs.render(mdNoImages, text, rerender) };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mdToHtmlInline(text: string) {
|
export function mdToHtmlInline(text: string) {
|
||||||
|
@ -74,6 +75,14 @@ const spoilerConfig = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const highlightjsConfig = {
|
||||||
|
inline: true,
|
||||||
|
hljs: lazyHighlightjs.hljs,
|
||||||
|
auto: true,
|
||||||
|
code: true,
|
||||||
|
ignoreIllegals: true,
|
||||||
|
};
|
||||||
|
|
||||||
const html5EmbedConfig = {
|
const html5EmbedConfig = {
|
||||||
html5embed: {
|
html5embed: {
|
||||||
useImageSyntax: true, // Enables video/audio embed with ![]() syntax (default)
|
useImageSyntax: true, // Enables video/audio embed with ![]() syntax (default)
|
||||||
|
@ -170,7 +179,7 @@ export function setupMarkdown() {
|
||||||
.use(markdown_it_footnote)
|
.use(markdown_it_footnote)
|
||||||
.use(markdown_it_html5_embed, html5EmbedConfig)
|
.use(markdown_it_html5_embed, html5EmbedConfig)
|
||||||
.use(markdown_it_container, "spoiler", spoilerConfig)
|
.use(markdown_it_container, "spoiler", spoilerConfig)
|
||||||
.use(markdown_it_highlightjs, { inline: true })
|
.use(markdown_it_highlightjs, highlightjsConfig)
|
||||||
.use(markdown_it_ruby)
|
.use(markdown_it_ruby)
|
||||||
.use(localInstanceLinkParser)
|
.use(localInstanceLinkParser)
|
||||||
.use(markdown_it_bidi);
|
.use(markdown_it_bidi);
|
||||||
|
@ -184,7 +193,7 @@ export function setupMarkdown() {
|
||||||
.use(markdown_it_footnote)
|
.use(markdown_it_footnote)
|
||||||
.use(markdown_it_html5_embed, html5EmbedConfig)
|
.use(markdown_it_html5_embed, html5EmbedConfig)
|
||||||
.use(markdown_it_container, "spoiler", spoilerConfig)
|
.use(markdown_it_container, "spoiler", spoilerConfig)
|
||||||
.use(markdown_it_highlightjs, { inline: true })
|
.use(markdown_it_highlightjs, highlightjsConfig)
|
||||||
.use(localInstanceLinkParser)
|
.use(localInstanceLinkParser)
|
||||||
.use(markdown_it_bidi)
|
.use(markdown_it_bidi)
|
||||||
// .use(markdown_it_emoji, {
|
// .use(markdown_it_emoji, {
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
|
"src/shared/build-config.d.ts",
|
||||||
"src/**/*.ts",
|
"src/**/*.ts",
|
||||||
"src/**/*.tsx",
|
"src/**/*.tsx",
|
||||||
"node_modules/inferno/dist/index.d.ts"
|
"node_modules/inferno/dist/index.d.ts"
|
||||||
|
|
|
@ -4,6 +4,10 @@ const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
||||||
const nodeExternals = require("webpack-node-externals");
|
const nodeExternals = require("webpack-node-externals");
|
||||||
const CopyPlugin = require("copy-webpack-plugin");
|
const CopyPlugin = require("copy-webpack-plugin");
|
||||||
const { ServiceWorkerPlugin } = require("service-worker-webpack");
|
const { ServiceWorkerPlugin } = require("service-worker-webpack");
|
||||||
|
const {
|
||||||
|
bundledSyntaxHighlighters,
|
||||||
|
lazySyntaxHighlighters,
|
||||||
|
} = require("./src/shared/build-config");
|
||||||
|
|
||||||
const banner = `
|
const banner = `
|
||||||
hash:[contentHash], chunkhash:[chunkhash], name:[name], filebase:[base], query:[query], file:[file]
|
hash:[contentHash], chunkhash:[chunkhash], name:[name], filebase:[base], query:[query], file:[file]
|
||||||
|
@ -12,6 +16,35 @@ const banner = `
|
||||||
@license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL v3.0
|
@license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL v3.0
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const contextPlugin = (() => {
|
||||||
|
const check = x => x.match(/^[0-9a-zA-Z-]+$/);
|
||||||
|
const eagerNames = bundledSyntaxHighlighters.filter(check).join("|");
|
||||||
|
const eagerHljs = new RegExp(`^[.][/\\\\](${eagerNames})[.]js\$`);
|
||||||
|
let lazyHljs;
|
||||||
|
if (lazySyntaxHighlighters === "*") {
|
||||||
|
lazyHljs = new RegExp(`^[.][/\\\\](?!(${eagerNames})[.]js\$)[^.]+[.]js\$`);
|
||||||
|
} else {
|
||||||
|
const lazyNames = lazySyntaxHighlighters.filter(check).join("|");
|
||||||
|
lazyHljs = new RegExp(`^[.][/\\\\](${lazyNames})[.]js\$`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plugin will be used for all parameterized dynamic imports.
|
||||||
|
return new webpack.ContextReplacementPlugin(/.*/, options => {
|
||||||
|
if (/^highlight.js\/lib\/languages$/.test(options.request)) {
|
||||||
|
if (options.mode == "eager") {
|
||||||
|
options.regExp = eagerHljs;
|
||||||
|
} else {
|
||||||
|
options.regExp = lazyHljs;
|
||||||
|
}
|
||||||
|
} else if (/^date-fns\/locale$/.test(options.request)) {
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
options.recursive = false;
|
||||||
|
options.request = resolve(__dirname, "node_modules/" + options.request);
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
module.exports = (env, argv) => {
|
module.exports = (env, argv) => {
|
||||||
const mode = argv.mode;
|
const mode = argv.mode;
|
||||||
|
|
||||||
|
@ -63,12 +96,7 @@ module.exports = (env, argv) => {
|
||||||
new webpack.BannerPlugin({
|
new webpack.BannerPlugin({
|
||||||
banner,
|
banner,
|
||||||
}),
|
}),
|
||||||
// helps import("date-fns/locale/${x}.mjs") find "date-fns/locale"
|
contextPlugin,
|
||||||
new webpack.ContextReplacementPlugin(
|
|
||||||
/date-fns\/locale/,
|
|
||||||
resolve(__dirname, "node_modules/date-fns/locale"),
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue