|
|
const path = require("path"); |
|
|
const { CleanWebpackPlugin } = require("clean-webpack-plugin"); |
|
|
const CopyPlugin = require("copy-webpack-plugin"); |
|
|
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin; |
|
|
const Handlebars = require("handlebars"); |
|
|
const fs = require("fs"); |
|
|
|
|
|
|
|
|
const appConfig = JSON.parse(fs.readFileSync(path.resolve(__dirname, "config/app.json"), "utf8")); |
|
|
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin"); |
|
|
const HtmlMinimizerPlugin = require("html-minimizer-webpack-plugin"); |
|
|
|
|
|
const FRAGMENTS_PATH = "src/fragments"; |
|
|
|
|
|
|
|
|
const loadFragmentsMap = (() => { |
|
|
let cachedFragments = null; |
|
|
return async () => { |
|
|
if (cachedFragments === null) { |
|
|
cachedFragments = {}; |
|
|
const walkDir = async (dir, basePath = '') => { |
|
|
const files = fs.readdirSync(dir); |
|
|
await Promise.all(files.map(async file => { |
|
|
const filePath = path.join(dir, file); |
|
|
const relativePath = path.join(basePath, file); |
|
|
if (fs.statSync(filePath).isDirectory()) { |
|
|
await walkDir(filePath, relativePath); |
|
|
} else { |
|
|
const nameWithoutExt = relativePath.replace(/\.html$/, ''); |
|
|
const dottedPath = 'fragment-' + nameWithoutExt.replace(/\\/g, '-').replace(/\//g, '-').replace(/\./g, '-'); |
|
|
const content = fs.readFileSync(filePath, "utf8"); |
|
|
let minifiedContent; |
|
|
|
|
|
if (content.trim().startsWith('<!DOCTYPE') || content.trim().startsWith('<html')) { |
|
|
minifiedContent = content; |
|
|
} else { |
|
|
try { |
|
|
const minifiedRes = await HtmlMinimizerPlugin.swcMinifyFragment({"tmp.html": content}) |
|
|
if (minifiedRes.errors) { |
|
|
minifiedContent = content; |
|
|
} else { |
|
|
minifiedContent = minifiedRes.code; |
|
|
} |
|
|
} catch (error) { |
|
|
minifiedContent = content; |
|
|
} |
|
|
} |
|
|
cachedFragments[dottedPath] = minifiedContent; |
|
|
} |
|
|
})); |
|
|
}; |
|
|
await walkDir(FRAGMENTS_PATH); |
|
|
} |
|
|
return cachedFragments; |
|
|
}; |
|
|
})(); |
|
|
|
|
|
const transformMarkdownWithFragments = async (data, filepath) => { |
|
|
const fragments = await loadFragmentsMap(); |
|
|
console.log(`Available fragments: ${Object.keys(fragments).join(', ')}`); |
|
|
|
|
|
|
|
|
const markdown = require('markdown-it')({ |
|
|
html: true, |
|
|
linkify: true, |
|
|
typographer: true |
|
|
}); |
|
|
|
|
|
const markdownContent = data.toString('utf8'); |
|
|
const htmlContent = markdown.render(markdownContent); |
|
|
|
|
|
|
|
|
const template = Handlebars.compile(htmlContent); |
|
|
return template(fragments); |
|
|
}; |
|
|
|
|
|
module.exports = { |
|
|
entry: { |
|
|
distill: "./src/distill.js", |
|
|
main: "./src/index.js", |
|
|
}, |
|
|
output: { |
|
|
filename: "[name].bundle.js", |
|
|
path: path.resolve(__dirname, "dist"), |
|
|
}, |
|
|
module: { |
|
|
rules: [ |
|
|
{ test: /\.css$/, use: ["style-loader", "css-loader"] }, |
|
|
{ |
|
|
test: /\.(js|mjs)$/, |
|
|
exclude: /node_modules/, |
|
|
use: { |
|
|
loader: "babel-loader", |
|
|
options: { |
|
|
presets: ["@babel/preset-env"], |
|
|
}, |
|
|
}, |
|
|
} |
|
|
], |
|
|
}, |
|
|
plugins: [ |
|
|
new CleanWebpackPlugin(), |
|
|
new CopyPlugin({ |
|
|
patterns: [ |
|
|
{ from: "src/fragments/*", to: "fragments/[name].html" }, |
|
|
{ from: "src/style.css", to: "style.css" }, |
|
|
{ from: "src/transformers-custom.css", to: "transformers-custom.css" }, |
|
|
{ from: "content/*.png", to: "static/[name][ext]" }, |
|
|
{ from: "content/*.svg", to: "static/[name][ext]" }, |
|
|
{ from: "content/*.html", to: "static/[name][ext]" }, |
|
|
{ from: "content/hf-logo.svg", to: "hf-logo.svg" }, |
|
|
{ |
|
|
from: "content/article.md", |
|
|
to: "index.html", |
|
|
transform: async (content, path) => { |
|
|
const fragments = await loadFragmentsMap(); |
|
|
|
|
|
|
|
|
const markdown = require('markdown-it')({ |
|
|
html: true, |
|
|
linkify: true, |
|
|
typographer: true |
|
|
}); |
|
|
|
|
|
const markdownContent = content.toString('utf8'); |
|
|
const htmlContent = markdown.render(markdownContent); |
|
|
|
|
|
|
|
|
const tocScript = ` |
|
|
<script> |
|
|
function initializeTOC() { |
|
|
const article = document.querySelector('d-article'); |
|
|
const toc = document.querySelector('d-contents'); |
|
|
if (toc) { |
|
|
const headings = [...article.querySelectorAll('h1, h2, h3, h4')].filter(h => !h.hasAttribute('data-no-toc')); |
|
|
let ToC = '<nav role="navigation" class="l-text figcaption">'; |
|
|
ToC += '<div class="toc-header"><span class="toc-title">Table of Contents</span></div>'; |
|
|
ToC += '<div class="toc-content">'; |
|
|
|
|
|
headings.forEach((heading, index) => { |
|
|
const id = heading.id || 'heading-' + index; |
|
|
if (!heading.id) heading.id = id; |
|
|
const level = parseInt(heading.tagName.charAt(1)); |
|
|
const indent = level === 1 ? '' : 'style="margin-left: ' + ((level - 1) * 1.2) + 'em;"'; |
|
|
ToC += '<div ' + indent + '><a href="#' + id + '">' + heading.textContent + '</a></div>'; |
|
|
}); |
|
|
|
|
|
ToC += '</div></nav>'; |
|
|
toc.innerHTML = ToC; |
|
|
toc.setAttribute('prerendered', 'true'); |
|
|
|
|
|
// Extract tenet text for tooltips |
|
|
const tenetTooltips = { |
|
|
'source-of-truth': 'We aim be a source of truth for all model definitions. Model implementations should be reliable, reproducible, and faithful to the original performances.', |
|
|
'one-model-one-file': 'All inference and training core logic has to be visible, top‑to‑bottom, to maximize each model\\'s hackability.', |
|
|
'code-is-product': 'Optimize for reading, diffing, and tweaking, our users are power users. Variables can be explicit, full words, even several words, readability is primordial.', |
|
|
'standardize-dont-abstract': 'If it\\'s model behavior, keep it in the file; abstractions only for generic infra.', |
|
|
'do-repeat-yourself': 'Copy when it helps users; keep successors in sync without centralizing behavior.', |
|
|
'minimal-user-api': 'Config, model, preprocessing; from_pretrained, save_pretrained, push_to_hub. We want the least amount of codepaths.', |
|
|
'backwards-compatibility': 'Evolve by additive standardization, never break public APIs.', |
|
|
'consistent-public-surface': 'Same argument names, same outputs, hidden states and attentions exposed, enforced by tests.', |
|
|
}; |
|
|
|
|
|
// Add smooth scrolling and custom tooltips to all tenet links (TOC and article) |
|
|
const tocLinks = document.querySelectorAll('d-contents a'); |
|
|
tocLinks.forEach(link => { |
|
|
const href = link.getAttribute('href'); |
|
|
const anchor = href ? href.substring(1) : ''; |
|
|
|
|
|
if (tenetTooltips[anchor]) { |
|
|
link.setAttribute('data-tooltip', tenetTooltips[anchor]); |
|
|
link.style.position = 'relative'; |
|
|
} |
|
|
|
|
|
link.addEventListener('click', function(e) { |
|
|
e.preventDefault(); |
|
|
const target = document.querySelector(this.getAttribute('href')); |
|
|
if (target) { |
|
|
target.scrollIntoView({ behavior: 'smooth' }); |
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
// Add custom tooltips to tenet links in article content |
|
|
const articleLinks = document.querySelectorAll('d-article a[href^="#"]'); |
|
|
articleLinks.forEach(link => { |
|
|
const href = link.getAttribute('href'); |
|
|
const anchor = href ? href.substring(1) : ''; |
|
|
if (tenetTooltips[anchor]) { |
|
|
link.setAttribute('data-tooltip', tenetTooltips[anchor]); |
|
|
} |
|
|
}); |
|
|
|
|
|
// Update active state on scroll |
|
|
window.addEventListener('scroll', function() { |
|
|
const scrollPos = window.scrollY + 100; |
|
|
headings.forEach((heading) => { |
|
|
const link = document.querySelector('d-contents a[href="#' + heading.id + '"]'); |
|
|
if (link) { |
|
|
if (heading.offsetTop <= scrollPos && |
|
|
heading.offsetTop + heading.offsetHeight > scrollPos) { |
|
|
link.classList.add('active'); |
|
|
} else { |
|
|
link.classList.remove('active'); |
|
|
} |
|
|
} |
|
|
}); |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
// Initialize Prism syntax highlighting |
|
|
function initializeSyntaxHighlighting() { |
|
|
if (typeof Prism !== 'undefined') { |
|
|
Prism.highlightAll(); |
|
|
} |
|
|
} |
|
|
|
|
|
// Try multiple times to ensure it runs after distill.js |
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
initializeTOC(); |
|
|
initializeSyntaxHighlighting(); |
|
|
}); |
|
|
setTimeout(function() { |
|
|
initializeTOC(); |
|
|
initializeSyntaxHighlighting(); |
|
|
}, 100); |
|
|
setTimeout(function() { |
|
|
initializeTOC(); |
|
|
initializeSyntaxHighlighting(); |
|
|
}, 500); |
|
|
setTimeout(function() { |
|
|
initializeTOC(); |
|
|
initializeSyntaxHighlighting(); |
|
|
}, 1000); |
|
|
</script>`; |
|
|
|
|
|
|
|
|
const template = `<!DOCTYPE html> |
|
|
<html> |
|
|
<head> |
|
|
<script src="distill.bundle.js" type="module" fetchpriority="high" blocking></script> |
|
|
<script src="main.bundle.js" type="module" fetchpriority="low" defer></script> |
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-core.min.js"></script> |
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js"></script> |
|
|
<script src="https://d3js.org/d3.v7.min.js"></script> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1"> |
|
|
<meta charset="utf8"> |
|
|
<title>${appConfig.fullTitle}</title> |
|
|
<link rel="stylesheet" href="style.css"> |
|
|
<link rel="stylesheet" href="transformers-custom.css"> |
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism.min.css"> |
|
|
</head> |
|
|
<body> |
|
|
<d-front-matter> |
|
|
<script id='distill-front-matter' type="text/json">{ |
|
|
"title": "${appConfig.fullTitle}", |
|
|
"description": "${appConfig.description}", |
|
|
"published": "Aug 21, 2025", |
|
|
"authors": [{"author": "Pablo Montalvo", "authorURL": "https://huggingface.co/Molbap"}, |
|
|
{"author": "Lysandre Debut", "authorURL": "https://huggingface.co/lysandre"}, |
|
|
{"author": "Pedro Cuenca", "authorURL": "https://huggingface.co/pcuenq"} |
|
|
{"author": "Yoni Gozlan", "authorURL": "https://huggingface.co/yonigozlan"}] |
|
|
}</script> |
|
|
</d-front-matter> |
|
|
<d-title> |
|
|
<h1>${appConfig.fullTitle}</h1> |
|
|
<p>${appConfig.description}</p> |
|
|
</d-title> |
|
|
<d-byline></d-byline> |
|
|
<d-article> |
|
|
<d-contents> |
|
|
<nav role="navigation" class="l-text figcaption"> |
|
|
<div class="toc-header"><span class="toc-title">Table of Contents</span></div> |
|
|
<div class="toc-content"> |
|
|
<div><a href="#introduction">Introduction</a></div> |
|
|
<div style="margin-left: 1.2em;"><a href="#what-you-will-learn">What you will learn</a></div> |
|
|
<div><a href="#source-of-truth">0. Source of truth</a></div> |
|
|
<div><a href="#one-model-one-file">1. One model, one file</a></div> |
|
|
<div><a href="#code-is-product">2. Code is product</a></div> |
|
|
<div><a href="#standardize-dont-abstract">3. Standardize, don't abstract</a></div> |
|
|
<div><a href="#do-repeat-yourself">4. DRY* (DO Repeat Yourself)</a></div> |
|
|
<div><a href="#minimal-user-api">5. Minimal user API</a></div> |
|
|
<div><a href="#backwards-compatibility">6. Backwards compatibility</a></div> |
|
|
<div><a href="#consistent-public-surface">7. Consistent public surface</a></div> |
|
|
<div><a href="#modular">Going modular</a></div> |
|
|
<div><a href="#attention-classes">External Attention classes</a></div> |
|
|
<div><a href="#encoders-ftw">Encoders win!</a></div> |
|
|
</div> |
|
|
</nav> |
|
|
</d-contents> |
|
|
${htmlContent} |
|
|
</d-article> |
|
|
${tocScript} |
|
|
</body> |
|
|
</html>`; |
|
|
|
|
|
|
|
|
const handlebars = Handlebars.compile(template); |
|
|
return handlebars(fragments); |
|
|
} |
|
|
}, |
|
|
], |
|
|
}), |
|
|
], |
|
|
devtool: process.env.NODE_ENV === 'production' ? 'source-map' : 'eval-source-map', |
|
|
devServer: { |
|
|
static: { |
|
|
directory: path.join(__dirname, 'dist'), |
|
|
}, |
|
|
hot: true, |
|
|
watchFiles: ['src/**/*'], |
|
|
client: { |
|
|
overlay: true, |
|
|
}, |
|
|
}, |
|
|
mode: process.env.NODE_ENV === 'production' ? 'production' : 'development', |
|
|
optimization: { |
|
|
minimizer: [ |
|
|
new ImageMinimizerPlugin({ |
|
|
minimizer: [{ |
|
|
implementation: ImageMinimizerPlugin.sharpMinify, |
|
|
options: { |
|
|
encodeOptions: { |
|
|
jpeg: { |
|
|
quality: 80 |
|
|
}, |
|
|
png: { |
|
|
quality: 80 |
|
|
}, |
|
|
webp: { |
|
|
quality: 80 |
|
|
} |
|
|
} |
|
|
} |
|
|
}, |
|
|
{ |
|
|
implementation: ImageMinimizerPlugin.svgoMinify, |
|
|
options: { |
|
|
encodeOptions: { |
|
|
multipass: true, |
|
|
plugins: [ |
|
|
'preset-default', |
|
|
] |
|
|
} |
|
|
} |
|
|
} |
|
|
] |
|
|
}), |
|
|
] |
|
|
}, |
|
|
}; |
|
|
|
|
|
console.log(process.env.NODE_ENV) |
|
|
|