@salesforcedevs/dx-components 1.20.7 → 1.20.8-fine-shiki

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforcedevs/dx-components",
3
- "version": "1.20.7",
3
+ "version": "1.20.8-fine-shiki",
4
4
  "description": "DX Lightning web components",
5
5
  "license": "MIT",
6
6
  "engines": {
@@ -46,6 +46,5 @@
46
46
  "volta": {
47
47
  "node": "20.19.0",
48
48
  "yarn": "1.22.19"
49
- },
50
- "gitHead": "fffbef4d7b536a321824457c6a987e53830a78f1"
49
+ }
51
50
  }
@@ -2,7 +2,7 @@ import { LightningElement, api } from "lwc";
2
2
  import { CodeBlockTheme, CodeBlockLanguage } from "typings/custom";
3
3
  import cx from "classnames";
4
4
  import { track as gtmTrack } from "dxUtils/analytics";
5
- import { highlightCode } from "dxUtils/shiki";
5
+ import { highlightCode, escapeHtml } from "dxUtils/shiki";
6
6
 
7
7
  /*
8
8
  Custom language support is handled by the Shiki wrapper module
@@ -118,10 +118,34 @@ export default class CodeBlock extends LightningElement {
118
118
  this.initializeTheme();
119
119
  }
120
120
 
121
+ // Show plain text immediately, no waiting for highlighting
122
+ private renderPlainCode(
123
+ cleanCodeBlock: string,
124
+ templateEl: HTMLTemplateElement,
125
+ divEl: HTMLElement | null
126
+ ) {
127
+ if (divEl) {
128
+ // Theme-specific colors to match Shiki's output
129
+ const bgColor = this.theme === DARK ? "#212121" : "#FFFFFF";
130
+ const textColor = this.theme === DARK ? "#EEFFFF" : "#000000";
131
+
132
+ // eslint-disable-next-line
133
+ templateEl.innerHTML = `<pre class='shiki' style='background-color: ${bgColor}; color: ${textColor};'><code>${escapeHtml(
134
+ cleanCodeBlock
135
+ )}</code></pre>`;
136
+ // eslint-disable-next-line
137
+ divEl.innerHTML = "";
138
+ divEl.append(templateEl.content);
139
+ }
140
+ }
141
+
121
142
  async formatCodeBlock() {
122
143
  const divEl = this.template.querySelector("div.code-block-content");
123
144
  const templateEl = document.createElement("template");
124
145
 
146
+ // Render plain code immediately
147
+ this.renderPlainCode(this.codeBlock, templateEl, divEl);
148
+
125
149
  // Decode HTML entities if the code is already encoded
126
150
  let cleanCodeBlock = this.codeBlock;
127
151
  if (this.isEncoded) {
@@ -1,13 +1,39 @@
1
- import * as shiki from "shiki";
2
-
3
- import type { BundledLanguage, BundledTheme } from "shiki";
4
- import { transformerColorizedBrackets } from "@shikijs/colorized-brackets";
1
+ import type { BundledLanguage, HighlighterCore } from "shiki";
5
2
  import { getCustomLanguageGrammars } from "dxUtils/shikiGrammars";
6
3
 
4
+ // Fine-grained bundle promises for lazy loading
5
+ let shikiCorePromise: Promise<typeof import("shiki/core")> | null = null;
6
+ let shikiEnginePromise: Promise<
7
+ typeof import("shiki/engine/oniguruma")
8
+ > | null = null;
9
+ let shikiWasmPromise: Promise<typeof import("shiki/wasm")> | null = null;
10
+ let bracketsModulePromise: Promise<
11
+ typeof import("@shikijs/colorized-brackets")
12
+ > | null = null;
13
+
14
+ // Lazy load core Shiki functions
15
+ async function getShikiCore() {
16
+ return (shikiCorePromise ??= import("shiki/core"));
17
+ }
18
+
19
+ // Lazy load Oniguruma engine
20
+ async function getShikiEngine() {
21
+ return (shikiEnginePromise ??= import("shiki/engine/oniguruma"));
22
+ }
23
+
24
+ // Lazy load WASM module
25
+ async function getShikiWasm() {
26
+ return (shikiWasmPromise ??= import("shiki/wasm"));
27
+ }
28
+
29
+ async function getBrackets() {
30
+ return (bracketsModulePromise ??= import("@shikijs/colorized-brackets"));
31
+ }
32
+
7
33
  interface ShikiSingleton {
8
- highlighter: shiki.HighlighterCore | null;
34
+ highlighter: HighlighterCore | null;
9
35
  initialized: boolean;
10
- initPromise: Promise<shiki.HighlighterCore> | null;
36
+ initPromise: Promise<HighlighterCore> | null;
11
37
  }
12
38
 
13
39
  const shikiInstance: ShikiSingleton = {
@@ -17,10 +43,22 @@ const shikiInstance: ShikiSingleton = {
17
43
  };
18
44
 
19
45
  // Theme mapping for light/dark modes
20
- const THEME_MAP: Record<string, BundledTheme> = {
46
+ const THEME_MAP = {
21
47
  light: "light-plus",
22
48
  dark: "material-theme-darker"
23
- };
49
+ } as const;
50
+
51
+ // Lazy load themes
52
+ async function getTheme(themeName: string) {
53
+ if (themeName === "light-plus") {
54
+ return import("@shikijs/themes/light-plus").then((m) => m.default);
55
+ } else if (themeName === "material-theme-darker") {
56
+ return import("@shikijs/themes/material-theme-darker").then(
57
+ (m) => m.default
58
+ );
59
+ }
60
+ throw new Error(`Unknown theme: ${themeName}`);
61
+ }
24
62
 
25
63
  const THEME_MAP_COLOR_REPLACEMENTS: Record<
26
64
  string,
@@ -50,8 +88,8 @@ const LANGUAGE_MAP: Record<string, BundledLanguage> = {
50
88
  sh: "bash"
51
89
  };
52
90
 
53
- // Core languages that should be loaded immediately for best performance
54
- const CORE_LANGUAGES = [
91
+ // Core languages loaded eagerly (keep minimal; load others on demand)
92
+ const CORE_LANGUAGES: BundledLanguage[] = [
55
93
  "apex",
56
94
  "javascript",
57
95
  "html",
@@ -61,10 +99,79 @@ const CORE_LANGUAGES = [
61
99
  "bash",
62
100
  "xml",
63
101
  "graphql",
64
- "jsx",
65
- getCustomLanguageGrammars().ampscript
102
+ "jsx"
66
103
  ];
67
104
 
105
+ // Custom language names
106
+ const CUSTOM_LANGUAGE_NAMES = ["ampscript", "dataweave", "agentscript"];
107
+
108
+ // Lazy load language grammars from fine-grained bundles
109
+ // Using static imports to satisfy LWC's strict mode requirements
110
+ async function getLanguageGrammar(lang: BundledLanguage | string) {
111
+ const customGrammars = getCustomLanguageGrammars();
112
+
113
+ // Check if it's a custom grammar first
114
+ if (lang === "ampscript") {
115
+ return customGrammars.ampscript;
116
+ }
117
+ if (lang === "dataweave") {
118
+ return customGrammars.dataweave;
119
+ }
120
+ if (lang === "agentscript") {
121
+ return customGrammars.agentscript;
122
+ }
123
+
124
+ // Static imports from @shikijs/langs (LWC strict mode requirement)
125
+ try {
126
+ switch (lang) {
127
+ case "apex":
128
+ return (await import("@shikijs/langs/apex")).default;
129
+ case "javascript":
130
+ return (await import("@shikijs/langs/javascript")).default;
131
+ case "html":
132
+ return (await import("@shikijs/langs/html")).default;
133
+ case "css":
134
+ return (await import("@shikijs/langs/css")).default;
135
+ case "json":
136
+ return (await import("@shikijs/langs/json")).default;
137
+ case "sql":
138
+ return (await import("@shikijs/langs/sql")).default;
139
+ case "bash":
140
+ return (await import("@shikijs/langs/bash")).default;
141
+ case "xml":
142
+ return (await import("@shikijs/langs/xml")).default;
143
+ case "graphql":
144
+ return (await import("@shikijs/langs/graphql")).default;
145
+ case "jsx":
146
+ return (await import("@shikijs/langs/jsx")).default;
147
+ case "typescript":
148
+ return (await import("@shikijs/langs/typescript")).default;
149
+ case "markdown":
150
+ return (await import("@shikijs/langs/markdown")).default;
151
+ case "python":
152
+ return (await import("@shikijs/langs/python")).default;
153
+ case "java":
154
+ return (await import("@shikijs/langs/java")).default;
155
+ case "yaml":
156
+ return (await import("@shikijs/langs/yaml")).default;
157
+ case "php":
158
+ return (await import("@shikijs/langs/php")).default;
159
+ case "swift":
160
+ return (await import("@shikijs/langs/swift")).default;
161
+ case "kotlin":
162
+ return (await import("@shikijs/langs/kotlin")).default;
163
+ case "handlebars":
164
+ return (await import("@shikijs/langs/handlebars")).default;
165
+ default:
166
+ console.warn(`Language grammar not available for: ${lang}`);
167
+ return null;
168
+ }
169
+ } catch (error) {
170
+ console.warn(`Failed to load language grammar for ${lang}:`, error);
171
+ return null;
172
+ }
173
+ }
174
+
68
175
  // Non-critical languages that can be loaded asynchronously when needed
69
176
  const OPTIONAL_LANGUAGES: Record<string, any> = {
70
177
  // @ts-ignore
@@ -91,8 +198,8 @@ const OPTIONAL_LANGUAGES: Record<string, any> = {
91
198
  agentscript: getCustomLanguageGrammars().agentscript
92
199
  };
93
200
 
94
- // Initialize Shiki highlighter with fine-grained modules for better performance
95
- async function initializeShiki(): Promise<shiki.HighlighterCore> {
201
+ // Initialize Shiki highlighter (lazy-load module and keep initial set minimal)
202
+ async function initializeShiki(): Promise<HighlighterCore> {
96
203
  if (shikiInstance.highlighter) {
97
204
  return shikiInstance.highlighter;
98
205
  }
@@ -101,10 +208,39 @@ async function initializeShiki(): Promise<shiki.HighlighterCore> {
101
208
  return shikiInstance.initPromise;
102
209
  }
103
210
 
104
- shikiInstance.initPromise = shiki.createHighlighter({
105
- themes: ["light-plus", "material-theme-darker"],
106
- langs: CORE_LANGUAGES
107
- });
211
+ shikiInstance.initPromise = (async () => {
212
+ // Load core modules with fine-grained bundles
213
+ const [shikiCore, engine, wasm] = await Promise.all([
214
+ getShikiCore(),
215
+ getShikiEngine(),
216
+ getShikiWasm()
217
+ ]);
218
+
219
+ // Load themes
220
+ const [lightTheme, darkTheme] = await Promise.all([
221
+ getTheme("light-plus"),
222
+ getTheme("material-theme-darker")
223
+ ]);
224
+
225
+ // Load core language grammars (bundled + custom)
226
+ const allCoreLanguages = [...CORE_LANGUAGES, ...CUSTOM_LANGUAGE_NAMES];
227
+ const coreLanguageGrammars = await Promise.all(
228
+ allCoreLanguages.map((lang) => getLanguageGrammar(lang))
229
+ );
230
+ // Filter out null values with type guard
231
+ const validLanguageGrammars = coreLanguageGrammars.filter(
232
+ (grammar): grammar is NonNullable<typeof grammar> =>
233
+ grammar !== null
234
+ );
235
+
236
+ // Create highlighter with fine-grained bundles
237
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
238
+ return shikiCore.createHighlighterCore({
239
+ themes: [lightTheme, darkTheme],
240
+ langs: validLanguageGrammars as any,
241
+ engine: engine.createOnigurumaEngine(wasm.default)
242
+ });
243
+ })();
108
244
 
109
245
  try {
110
246
  shikiInstance.highlighter = await shikiInstance.initPromise;
@@ -119,18 +255,25 @@ async function initializeShiki(): Promise<shiki.HighlighterCore> {
119
255
 
120
256
  // Async function to load additional languages when needed
121
257
  async function loadLanguageIfNeeded(
122
- highlighter: shiki.HighlighterCore,
258
+ highlighter: HighlighterCore,
123
259
  language: string
124
260
  ): Promise<void> {
125
261
  const loadedLanguages = highlighter.getLoadedLanguages();
126
262
 
127
263
  if (!loadedLanguages.includes(language as BundledLanguage)) {
128
264
  // Check if it's an optional language that needs to be loaded
129
- const languageLoader = OPTIONAL_LANGUAGES[language];
265
+ const languageKey = OPTIONAL_LANGUAGES[language];
130
266
 
131
- if (languageLoader) {
267
+ if (languageKey) {
132
268
  try {
133
- await highlighter.loadLanguage(languageLoader);
269
+ // Load the language grammar using fine-grained bundles
270
+ const langGrammar = await getLanguageGrammar(
271
+ typeof languageKey === "string" ? languageKey : language
272
+ );
273
+ if (langGrammar) {
274
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
275
+ await highlighter.loadLanguage(langGrammar as any);
276
+ }
134
277
  } catch (error) {
135
278
  console.warn(
136
279
  `Failed to load optional language ${language}:`,
@@ -167,6 +310,8 @@ export async function highlightCode(
167
310
  mappedLanguage = "text" as BundledLanguage;
168
311
  }
169
312
 
313
+ const { transformerColorizedBrackets } = await getBrackets();
314
+
170
315
  return highlighter.codeToHtml(code, {
171
316
  lang: mappedLanguage,
172
317
  theme: THEME_MAP[theme],
@@ -184,7 +329,7 @@ export async function highlightCode(
184
329
  }
185
330
 
186
331
  // Utility function to escape HTML
187
- function escapeHtml(text: string): string {
332
+ export function escapeHtml(text: string): string {
188
333
  const div = document.createElement("div");
189
334
  div.textContent = text;
190
335
  // eslint-disable-next-line @lwc/lwc/no-inner-html
package/LICENSE DELETED
@@ -1,12 +0,0 @@
1
- Copyright (c) 2020, Salesforce.com, Inc.
2
- All rights reserved.
3
-
4
- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5
-
6
- * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7
-
8
- * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9
-
10
- * Neither the name of Salesforce.com nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
11
-
12
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.