Skip to content

Commit 457e732

Browse files
authored
Build & minify JS modules using tsup (#174)
* Build JS modules using `tsup` * Add generated file warning
1 parent ccbc264 commit 457e732

File tree

10 files changed

+211
-154
lines changed

10 files changed

+211
-154
lines changed

docs/package.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
"dev": "astro dev",
1212
"build": "pnpm api && pnpm release-notes && astro build",
1313
"preview": "astro preview",
14-
"api": "tsm --require=./scripts/lib/filter-warnings.cjs ./scripts/api-reference.ts",
15-
"release-notes": "tsm --require=./scripts/lib/filter-warnings.cjs ./scripts/release-notes.ts"
14+
"api": "tsm --require=../scripts/lib/filter-warnings.cjs ./scripts/api-reference.ts",
15+
"release-notes": "tsm --require=../scripts/lib/filter-warnings.cjs ./scripts/release-notes.ts"
1616
},
1717
"dependencies": {
1818
"@astrojs/starlight": "^0.21.1",
@@ -31,7 +31,6 @@
3131
"mdast-util-to-string": "^4.0.0",
3232
"sharp": "^0.32.5",
3333
"starlight-links-validator": "^0.5.1",
34-
"tsm": "^2.3.0",
3534
"typedoc": "^0.25.4",
3635
"typedoc-plugin-markdown": "^4.0.0-next.21",
3736
"typedoc-plugin-missing-exports": "^2.1.0",

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
],
2323
"devDependencies": {
2424
"@changesets/cli": "^2.26.2",
25+
"@types/node": "^18.15.11",
2526
"@typescript-eslint/eslint-plugin": "^6.11.0",
2627
"@typescript-eslint/parser": "^6.11.0",
2728
"@vitest/coverage-v8": "^0.34.6",
@@ -33,6 +34,7 @@
3334
"happy-dom": "^12.10.3",
3435
"markdownlint-cli": "^0.37.0",
3536
"prettier": "^3.1.0",
37+
"tsm": "^2.3.0",
3638
"tsup": "^7.2.0",
3739
"typescript": "^5.2.2",
3840
"vite": "^4.2.1",

packages/@expressive-code/plugin-frames/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
"dist"
2424
],
2525
"scripts": {
26-
"build": "tsup ./src/index.ts --format esm,cjs --dts --sourcemap --clean",
26+
"build": "pnpm build-js-modules && tsup ./src/index.ts --format esm,cjs --dts --sourcemap --clean",
27+
"build-js-modules": "tsm --require=../../../scripts/lib/filter-warnings.cjs ../../../scripts/build-js-module.ts ./src/copy-js-module.ts",
2728
"coverage": "vitest run --coverage",
2829
"test": "vitest run --reporter verbose",
2930
"test-short": "vitest run --reporter basic",
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/*
2+
GENERATED FILE - DO NOT EDIT
3+
----------------------------
4+
This JS module code was built from the source file "copy-js-module.ts".
5+
To change it, modify the source file and then re-run the build script.
6+
*/
7+
8+
export default '(()=>{function i(n){let e=document.createElement("pre");Object.assign(e.style,{opacity:"0",pointerEvents:"none",position:"absolute",overflow:"hidden",left:"0",top:"0",width:"20px",height:"20px",webkitUserSelect:"auto",userSelect:"all"}),e.ariaHidden="true",e.textContent=n,document.body.appendChild(e);let r=document.createRange();r.selectNode(e);let o=getSelection();if(!o)return!1;o.removeAllRanges(),o.addRange(r);let a=!1;try{a=document.execCommand("copy")}finally{o.removeAllRanges(),document.body.removeChild(e)}return a}async function l(n){let e=n.currentTarget,r=e.dataset,o=!1,a=r.code.replace(/\\u007f/g,`\n`);try{await navigator.clipboard.writeText(a),o=!0}catch{o=i(a)}if(!o||e.parentNode?.querySelector(".feedback"))return;let t=document.createElement("div");t.classList.add("feedback"),t.append(r.copied),e.before(t),t.offsetWidth,requestAnimationFrame(()=>t?.classList.add("show"));let c=()=>!t||t.classList.remove("show"),d=()=>{!t||parseFloat(getComputedStyle(t).opacity)>0||(t.remove(),t=void 0)};setTimeout(c,1500),setTimeout(d,2500),e.addEventListener("blur",c),t.addEventListener("transitioncancel",d),t.addEventListener("transitionend",d)}var s=n=>{n.querySelectorAll&&n.querySelectorAll("[SELECTOR]").forEach(e=>e.addEventListener("click",l))};s(document);var u=new MutationObserver(n=>n.forEach(e=>e.addedNodes.forEach(r=>{s(r)})));u.observe(document.body,{childList:!0,subtree:!0});document.addEventListener("astro:page-load",()=>{s(document)});})();'
Lines changed: 81 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1+
/* eslint-disable @typescript-eslint/no-misused-promises */
2+
13
/**
2-
* Workaround code for cases in which the Clipboard API is not available.
4+
* Fallback approach to copy text to the clipboard in case the Clipboard API is not available.
35
*/
4-
const domCopy = [
5-
// Define copy function
6-
`function domCopy(text) {`,
6+
function domCopy(text: string) {
77
// Create a new DOM element to copy from and append it to the document,
88
// but make sure it's not visible and does not cause reflow
9-
`let n = document.createElement('pre');
10-
Object.assign(n.style, {
9+
const pre = document.createElement('pre')
10+
Object.assign(pre.style, {
1111
opacity: '0',
1212
pointerEvents: 'none',
1313
position: 'absolute',
@@ -17,112 +17,98 @@ const domCopy = [
1717
width: '20px',
1818
height: '20px',
1919
webkitUserSelect: 'auto',
20-
userSelect: 'all'
21-
});
22-
n.ariaHidden = 'true';
23-
n.textContent = text;
24-
document.body.appendChild(n);`,
20+
userSelect: 'all',
21+
})
22+
pre.ariaHidden = 'true'
23+
pre.textContent = text
24+
document.body.appendChild(pre)
25+
2526
// Select the DOM element's contents
26-
`let r = document.createRange();
27-
r.selectNode(n);
28-
let s = getSelection();
29-
s.removeAllRanges();
30-
s.addRange(r);`,
27+
const range = document.createRange()
28+
range.selectNode(pre)
29+
const selection = getSelection()
30+
if (!selection) return false
31+
selection.removeAllRanges()
32+
selection.addRange(range)
33+
3134
// Copy the selection to the clipboard
32-
`let ok = false;
35+
let ok = false
3336
try {
34-
ok = document.execCommand('copy');
37+
ok = document.execCommand('copy')
3538
} finally {
36-
s.removeAllRanges();
37-
document.body.removeChild(n);
39+
selection.removeAllRanges()
40+
document.body.removeChild(pre)
3841
}
39-
return ok;`,
40-
// End of function body
41-
`}`,
42-
]
42+
return ok
43+
}
4344

4445
/**
4546
* Function to handle clicks on a single copy button.
4647
*/
47-
const clickHandler = [
48-
// Define click handler function
49-
`async function clickHandler(event) {`,
50-
// Attempt to perform copy operation, first using the Clipboard API,
48+
async function clickHandler(event: Event) {
49+
// Attempt to perform the copy operation, first using the Clipboard API,
5150
// and then falling back to a DOM-based approach
52-
`let btn = event.currentTarget;
53-
let ok = false;
54-
let code = btn.dataset.code.replace(/\\u007f/g, '\\n');
51+
const button = event.currentTarget as HTMLButtonElement
52+
const dataset = button.dataset as { code: string; copied: string }
53+
let ok = false
54+
const code = dataset.code.replace(/\u007f/g, '\n')
5555
try {
56-
await navigator.clipboard.writeText(code);
57-
ok = true;
56+
await navigator.clipboard.writeText(code)
57+
ok = true
5858
} catch (err) {
59-
ok = domCopy(code);
60-
}`,
59+
ok = domCopy(code)
60+
}
61+
6162
// Exit if the copy operation failed or there is already a tooltip present
62-
`if (!ok || btn.parentNode.querySelector('.feedback')) return;`,
63+
if (!ok || button.parentNode?.querySelector('.feedback')) return
64+
6365
// Show feedback tooltip
64-
`let tt = document.createElement('div');
65-
tt.classList.add('feedback');
66-
tt.append(btn.dataset.copied);
67-
btn.before(tt);`,
66+
let tooltip: HTMLDivElement | undefined = document.createElement('div')
67+
tooltip.classList.add('feedback')
68+
tooltip.append(dataset.copied)
69+
button.before(tooltip)
70+
6871
// Use offsetWidth and requestAnimationFrame to opt out of DOM batching,
6972
// which helps to ensure that the transition on 'show' works
70-
`tt.offsetWidth;
71-
requestAnimationFrame(() => tt.classList.add('show'));`,
73+
tooltip.offsetWidth
74+
requestAnimationFrame(() => tooltip?.classList.add('show'))
75+
7276
// Hide & remove the tooltip again when we no longer need it
73-
`let h = () => !tt || tt.classList.remove('show');
74-
let r = () => {
75-
if (!(!tt || parseFloat(getComputedStyle(tt).opacity) > 0)) {
76-
tt.remove();
77-
tt = null;
77+
const hideTooltip = () => !tooltip || tooltip.classList.remove('show')
78+
const removeTooltip = () => {
79+
if (!(!tooltip || parseFloat(getComputedStyle(tooltip).opacity) > 0)) {
80+
tooltip.remove()
81+
tooltip = undefined
7882
}
79-
};
80-
setTimeout(h, 1500);
81-
setTimeout(r, 2500);
82-
btn.addEventListener('blur', h);
83-
tt.addEventListener('transitioncancel', r);
84-
tt.addEventListener('transitionend', r);`,
85-
// End of function body
86-
`}`,
87-
]
88-
89-
/**
90-
* Code to initialize all copy buttons on the page.
91-
*
92-
* It first attaches the click handler to all buttons that exist on the page right now,
93-
* and then registers a MutationObserver that handles any new buttons added later
94-
* (e.g. when the page dynamically loads more content or replaces existing content).
95-
*/
96-
const attachHandlers = [
97-
// Define a function that searches a node for matching buttons and initializes them
98-
// unless the node does not support querySelectorAll (e.g. a text node)
99-
`let initButtons = n => !n.querySelectorAll || n.querySelectorAll('[SELECTOR]').forEach(btn =>
100-
btn.addEventListener('click', clickHandler)
101-
);`,
102-
// Use the function to initialize all buttons that exist right now
103-
`initButtons(document);`,
104-
// Register a MutationObserver to initialize any new buttons added later
105-
`let obs = new MutationObserver(ms =>
106-
ms.forEach(m =>
107-
m.addedNodes.forEach(n =>
108-
initButtons(n)
109-
)
110-
)
111-
);
112-
obs.observe(document.body, { childList: true, subtree: true });`,
113-
// Also re-initialize all buttons after view transitions initiated by popular frameworks
114-
`document.addEventListener('astro:page-load', () => initButtons(document));`,
115-
]
83+
}
84+
setTimeout(hideTooltip, 1500)
85+
setTimeout(removeTooltip, 2500)
86+
button.addEventListener('blur', hideTooltip)
87+
tooltip.addEventListener('transitioncancel', removeTooltip)
88+
tooltip.addEventListener('transitionend', removeTooltip)
89+
}
11690

117-
export const getCopyJsModule = (buttonSelector: string) => {
118-
return [...domCopy, ...clickHandler, ...attachHandlers]
119-
.map((line) =>
120-
line
121-
.trim()
122-
.replace(/\s*[\r\n]\s*/g, '')
123-
.replace(/\s*([:;,={}()<>])\s*/g, '$1')
124-
.replace(/;}/g, '}')
125-
)
126-
.join('')
127-
.replace(/\[SELECTOR\]/g, buttonSelector)
91+
// Define a function that searches a node for matching buttons and initializes them
92+
// unless the node does not support querySelectorAll (e.g. a text node)
93+
const initButtons = (container: ParentNode | Document) => {
94+
if (!container.querySelectorAll) return
95+
container.querySelectorAll('[SELECTOR]').forEach((btn) => btn.addEventListener('click', clickHandler))
12896
}
97+
98+
// Use the function to initialize all buttons that exist right now
99+
initButtons(document)
100+
101+
// Register a MutationObserver to initialize any new buttons added later
102+
const newButtonsObserver = new MutationObserver((mutations) =>
103+
mutations.forEach((mutation) =>
104+
mutation.addedNodes.forEach((node) => {
105+
initButtons(node as ParentNode)
106+
})
107+
)
108+
)
109+
newButtonsObserver.observe(document.body, { childList: true, subtree: true })
110+
111+
// Also re-initialize all buttons after view transitions initiated by popular frameworks
112+
document.addEventListener('astro:page-load', () => {
113+
initButtons(document)
114+
})

packages/@expressive-code/plugin-frames/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
LanguageGroups,
1212
LanguagesWithFencedFrontmatter,
1313
} from './utils'
14-
import { getCopyJsModule } from './copy-js-module'
14+
import copyJsModule from './copy-js-module.min'
1515
export { FramesStyleSettings } from './styles'
1616

1717
export interface PluginFramesOptions {
@@ -89,7 +89,7 @@ export function pluginFrames(options: PluginFramesOptions = {}): ExpressiveCodeP
8989
name: 'Frames',
9090
styleSettings: framesStyleSettings,
9191
baseStyles: (context) => getFramesBaseStyles(context, options),
92-
jsModules: options.showCopyToClipboardButton ? [getCopyJsModule(`.expressive-code .copy button`)] : undefined,
92+
jsModules: options.showCopyToClipboardButton ? [copyJsModule.replace(/\[SELECTOR\]/g, '.expressive-code .copy button')] : undefined,
9393
hooks: {
9494
preprocessMetadata: ({ codeBlock }) => {
9595
// Transfer meta options (if any) to props

0 commit comments

Comments
 (0)