ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

在 webpack 中处理 Markdown 文件:examples/markdown 的转 HTML、原始字符串与字节流三种导入形态全解析

2026/9/8 23:36:31 拓冰建站 浏览量
在 webpack 中处理 Markdown 文件:examples/markdown 的转 HTML、原始字符串与字节流三种导入形态全解析 在 webpack 中处理 Markdown 文件examples/markdown 的转 HTML、原始字符串与字节流三种导入形态全解析【免费下载链接】webpackA bundler for javascript and friends. Packs many modules into a few bundled assets. Code Splitting allows for loading parts of the application on demand. Through loaders, modules can be CommonJs, AMD, ES6 modules, CSS, Images, JSON, Coffeescript, LESS, ... and your custom stuff.项目地址: https://gitcode.com/GitHub_Trending/web/webpack这篇技术指南以 webpack 官方示例examples/markdown其数据文件 file.md 与配置 webpack.config.mjs、入口 example.js为核心讲解如何把 Markdown 文件作为一等公民引入构建管线既可以用html-loader配合 remark/rehype 生态把 Markdown 语法转成可直接innerHTML使用的 HTML 字符串也可以借助asset/source模块类型原样拿到 Markdown 文本还能用with { type: bytes }导入属性获取Uint8Array字节流。读完本文你将掌握这套三态 Markdown 处理的完整配置、背后 webpack asset 模块的生成原理以及如何在自己的项目中复用并扩展该方案。一、示例总体目标同一份 Markdown 的三种消费方式examples/markdown/README.md 开宗明义This example demonstrates how to use Markdown files, convert (transform) them to HTML, or import them as is. 也就是说本示例围绕同一个例子目录验证三条加载路径导入语句目标形态应用场景import markdownToHTMLFile from ./file.md编译期转为 HTML 的字符串html-loader产物直接innerHTML渲染富文本import markdownToString from ./raw-to-string.md原始 Markdown 文本字符串需要自己处理原文如前端再次解析import markdownToUint8Array from ./raw-to-uint8-array.md with { type: bytes }Uint8Array字节流需要二进制语义、跨线程传输或流式处理对应的入口代码在 example.js 中非常直观三种导入各占一个展示区块用innerHTML渲染已转换 HTML用textContent展示原始文本避免把原文当 HTML 注入产生 XSS。// Import Markdown file and convert it to HTML import markdownToHTMLFile from ./file.md; // Import Markdown file and get a Uint8Array import markdownToUint8Array from ./raw-to-uint8-array.md with { type: bytes }; // Import Markdown file and get a string import markdownToString from ./raw-to-string.md; const container document.createElement(div); Object.assign(container.style, { display: flex, flexWrap: wrap, justifyContent: left }); document.body.appendChild(container); const h1 document.createElement(h1); h1.textContent Markdown examples; container.appendChild(h1); // To HTML const toHtmlContainer document.createElement(div); Object.assign(toHtmlContainer.style, { flex: 1 1 100%, paddingBottom: 24px, }); container.appendChild(toHtmlContainer); const h2ToHtmlContainer document.createElement(h2); h2ToHtmlContainer.textContent Markdown to HTML; toHtmlContainer.appendChild(h2ToHtmlContainer); const markdownToHTMLText document.createElement(div); markdownToHTMLText.innerHTML markdownToHTMLFile; toHtmlContainer.appendChild(markdownToHTMLText); // To Uint8Array const toRawContainerUsingUint8Array document.createElement(div); Object.assign(toRawContainerUsingUint8Array.style, { flex: 1 1 100%, paddingBottom: 24px, }); container.appendChild(toRawContainerUsingUint8Array); const h2ToRawUsingUint8Array document.createElement(h2); h2ToRawUsingUint8Array.textContent Raw Markdown (using Uint8Array and TextDecoder); toRawContainerUsingUint8Array.appendChild(h2ToRawUsingUint8Array); const markdownToRawTextUsingUint8Array document.createElement(div); const decoder new TextDecoder(utf-8); markdownToRawTextUsingUint8Array.textContent decoder.decode(markdownToUint8Array); toRawContainerUsingUint8Array.appendChild(markdownToRawTextUsingUint8Array); // To string const toRawContainerUsingString document.createElement(div); Object.assign(toRawContainerUsingString.style, { flex: 1 1 100%, paddingBottom: 24px, }); container.appendChild(toRawContainerUsingString); const h2ToRawUsingString document.createElement(h2); h2ToRawUsingString.textContent Raw Markdown (getting a string directly); toRawContainerUsingString.appendChild(h2ToRawUsingString); const markdownToRawText document.createElement(div); markdownToRawText.textContent markdownToString; toRawContainerUsingString.appendChild(markdownToRawText);注意示例刻意区分了安全语义已经过转换与净化sanitize的 HTML 才使用innerHTML未经处理的原文一律走textContent。这一习惯应直接迁移到生产代码中。二、file.md一份用于压力测试完整 Markdown 语法与资源的样本file.md 是整个示例的标本文件它不是几行占位符而是一份覆盖面极广的语法清单并且包含了 webpack 特有的资源引用能力。其主要区块如下YAML Front Matter文件头部的---区块声明了title: Just hackn、description: Nothing to see here、meta: some meta data这正是为了验证 Markdown 转 HTML 时必须能被正确剥离/解析 Front Matter否则会作为正文泄漏出来。标题体系#到######六级标题外加 Setext 风格/---下划线式H1/H2。强调与删除线*italic*、_italic_、**bold**、__bold__、~~strikethrough~~以及混合嵌套。列表有序/无序列表、2 空格缩进子列表、不同项目符号*/-/互相切换、列表内段落缩进等。任务列表- [x]/- [ ]勾选项以及夹杂链接、删除线、mentions、#refs与 HTML 标签的复杂条目。转义用\*our-new-project\*说明如何忽略 Markdown 格式。链接内联链接含 title、引用式链接含大小写不敏感的定义、数字引用链接、空链接文本、裸 URL / 尖括号 URL 自动链接。图片内联式、引用式、脚注式图片语法含alt与title。脚注[^first]、[^second]引用及同一脚注被重复引用的情况。代码块含language-javascript语言标注的内联代码与围栏代码块。表格、嵌套引用块Blockquote、内联 HTMLdl定义列表、水平分隔线---/***/___。本地资源引用文末Resolving resources一节用[![Webpack](https://raw.gitcode.com/GitHub_Trending/web/webpack/raw/896506966c25d1032c757c28c7422ec5ffc15705/examples/css/images/file.png?utm_sourcegitcode_repo_files)](https://link.gitcode.com/i/70a995198a6ee64b52e1f27ec80050d9)引用同目录图片 file.png用于验证 Markdown 内容中的相对图片 URL 能否被 webpack 识别并作为独立资源打包。此外同目录的 raw-to-string.md 与 raw-to-uint8-array.md 内容完全一致都包含 UTF-8 字符希腊字母 Θ、URL fragment 中不允许出现的字符、单词间两个连续空格以及文件中重复出现两次的相同标题## This heading is not unique in the file。它们被设计为两份原始文本输入文件内容相同却分别走字符串与字节流两条不同通道用于对比验证两种导入路径的差异。三、配置逐行拆解html-loader 与 unified/remark/rehype 转换管线webpack.config.mjs 是理解本例的关键。它在module.rules中定义了两类规则/** typedef {import(webpack).LoaderContextvoid} LoaderContext */ import rehypeSanitize from rehype-sanitize; import rehypeStringify from rehype-stringify; import remarkFrontmatter from remark-frontmatter; import remarkGfm from remark-gfm; import remarkParse from remark-parse; import remarkRehype from remark-rehype; import { unified } from unified; /** type {import(webpack).Configuration} */ const config { // mode: development || production, module: { rules: [ // To convert Markdown syntax to HTML { test: /\.(md|markdown|mdown|mkdn|mkd|mdwn|mkdown|ron)$/i, exclude: /(raw-to-string|raw-to-uint8-array)\.md/, loader: html-loader, options: { preprocessor: /** * param {string} content content * param {LoaderContext} loaderContext loader context * returns {Promisestring} result */ async (content, loaderContext) { const file await unified() .use(remarkParse) .use(remarkFrontmatter) .use(remarkGfm) .use(remarkRehype) .use(rehypeSanitize) .use(rehypeStringify) .process(content); return String(file); } } }, // Import Markdown as a string { test: /raw-to-string\.md$/, type: asset/source } ] } }; export default config;3.1 第一类规则把所有常见 Markdown 扩展名交给 html-loader匹配范围/\.(md|markdown|mdown|mkdn|mkd|mdwn|mkdown|ron)$/i覆盖了.md、.markdown、.mdown、.mkdn、.mkd、.mdwn、.mkdown乃至.ron大小写不敏感。排除项exclude: /(raw-to-string|raw-to-uint8-array)\.md/确保两个原始文本文件不经过Markdown→HTML 的 HTML 转换管线否则它们会被html-loader变成 HTML 字符串而丢失原始 Markdown语义。loaderhtml-loader该依赖版本声明在仓库根目录 package.json 中html-loader: ^5.1.0。核心options.preprocessorhtml-loader 允许提供一个异步预处理函数签名async (content, loaderContext) string。它拿到源码内容content后在真正的 html-loader 逻辑之前完成 Markdown→HTML 的转换再把 HTML 字符串返回给 html-loader 做后续模块化包装。3.2 unified 处理链remark 解析、rehype 输出职责分明preprocessor内部是用 unified 组合的单向管道本项目中的 remark/rehype 版本均声明在 package.json例如remark-parse ^11.0.0、remark-rehype ^11.1.2、rehype-sanitize ^6.0.0、rehype-stringify ^10.0.1、remark-gfm ^4.0.1、remark-frontmatter ^5.0.0。五个插件顺序不能随意调换其作用依次为阶段插件在管道中的职责1remarkParse把 Markdown 文本解析为 mdastMarkdown 抽象语法树2remarkFrontmatter识别并解析 YAML Front Matter本例的title/description/meta使其不会作为正文节点进入渲染3remarkGfm开启 GitHub Flavored Markdown 扩展表格、删除线、任务列表、自动链接等4remarkRehype将 mdast 转换为 hastHTML 抽象语法树实现 Markdown 语义到 HTML 语义的映射5rehypeSanitize按白名单净化 hast剔除危险节点与属性是渲染前最重要的一道安全闸门6rehypeStringify把净化后的 hast 序列化为 HTML 字符串返回3.3 第三类规则asset/source原样字符串导入{ test: /raw-to-string\.md$/, type: asset/source }type: asset/source是 webpack 的**资源模块类型asset module type**之一对应的常量在 lib/ModuleTypeConstants.js 中定义为ASSET_MODULE_TYPE_SOURCE asset/source。它会把文件内容以string形式导出不需要任何 loader。在 lib/asset/AssetModulesPlugin.js 中asset/source被映射到AssetSourceParserAssetSourceGenerator这对解析/生成器组合而 lib/asset/AssetSourceGenerator.js 的generate()对 JavaScript 类型的输出路径会直接产出类似module.exports …原始内容…的 JS 代码用JSON.stringify包裹保证换行、引号等特殊字符安全。由于exclude只放行了raw-to-string.md这一条规则对它精确生效。四、构建产物溯源三条导入路径在 dist 中的真实形态示例 README 中贴出的dist/output.js该产物由模板 template.md 用_{{webpack.config.mjs}}_、_{{example.js}}_、_{{dist/output.js}}_占位符注入生成清晰展示了四种模块的最终代码可以反推各条管线模块 1./file.mdHarmony 模块default导出code变量。code是一大段 HTML 字符串——正是上述 remark/rehype 管线的产物。同时模块内部声明了var ___HTML_LOADER_IMPORT_0___ new URL(/* asset import */ __webpack_require__(/*! ./file.png */ 2), __webpack_require__.b)说明 html-loader 把 Markdown 里的图片 URL 转成了对 webpack asset 模块的引用相对 URL 借助运行时__webpack_require__.bbaseURI与__webpack_require__.ppublicPathdist/解析。模块 2./file.pngmodule.exports __webpack_require__.p 89a353e9c515885abd8e.png——即 file.md 中[![Webpack](https://raw.gitcode.com/GitHub_Trending/web/webpack/raw/896506966c25d1032c757c28c7422ec5ffc15705/examples/css/images/file.png?utm_sourcegitcode_repo_files)](https://link.gitcode.com/i/70a995198a6ee64b52e1f27ec80050d9)指向的本地图片被打包成带内容哈希文件名的独立资源并随构建输出asset 89a353e9c515885abd8e.png。模块 3./raw-to-uint8-array.md字节通道module.exports __webpack_require__.tb(IyBFeGFtcGxlIG…)。内容被编码为 base64 字符串内联进 JS运行时经toBinary助手解码为Uint8Array。模块 4./raw-to-string.mdmodule.exports # Example headings\n\n## Sample Section…即asset/source产出的纯字符串。两个原始文件内容相同却走了完全不同的通道恰好对比出字符串与字节流两种形态的产物差异。五、Markdown→HTML 语法覆盖验证file.md 各区块在产物中的对应表现结合 README 中完整渲染出的 HTML 产物模块 1 的code字符串可以把 file.md 的每个语法区块与最终 HTML 一一对应起来验证转换正确性——这也是把 file.md 当作验收清单的价值所在file.md 中的区块转换后的 HTML 表现取自 dist 产物H1–H6 标题h1至h6一一对应Setext 风格标题也归一化为h1/h2强调/粗体/删除线em、strong含**asterisks and emunderscores/em**的嵌套、del无序列表*/-/混用拆分为独立ul2 空格缩进子列表渲染为嵌套ul有序/无序混合嵌套列表ol内嵌ol、ul的标准嵌套结构任务列表ul classcontains-task-listli classtask-list-iteminput typecheckbox checked disabled转义文本反斜杠转义的星号按普通字符输出\*our-new-project\*不再被当作强调链接内联/带 title/引用式/自动均变成a hreftitle 属性被编码为titleGoogle#x27;s Homepage引用式定义的正文被解析为链接图片img src alt title引用式图片定义同样生效脚注生成带data-footnote-ref/data-footnote-backref的链接与文末section>const encodedSource originalSource.buffer().toString(base64); runtimeRequirements.add(RuntimeGlobals.toBinary);即把源文件 buffer 编码为 base64 内联并向运行时登记toBinaryRuntimeGlobals.toBinary产物中即__webpack_require__.tb依赖。对应的运行时实现在 lib/runtime/ToBinaryRuntimeModule.js它在浏览器端维护一张 128 项 base64 解码表将内联的 base64 字符串实时解码为Uint8Array。产物中模块 3 头部标注的runtime requirements: __webpack_require__.*, __webpack_require__.tb, module正好与这条依赖链吻合。这样设计的收益在于同一份 Markdown 源码字符串形态直接以字面量进入 bundle字节形态则以 base64 内联并在运行时解码为不可变的Uint8Array为需要二进制语义例如 Web Worker 传输、避免字符串内存驻留、流式分块的消费方提供选择而无需在源码侧维护两份文件。example.js 里用new TextDecoder(utf-8)把字节流解码回文本后再以textContent展示正是字节消费路径的完整闭环。七、运行与验证本示例依赖的包html-loader、unified、各remark-*、rehype-*全部声明在仓库根目录 package.json 的 devDependencies 中因此无需额外安装。官方在 examples/README.md 的 Building an Example 一节给出的通用流程为在仓库根目录执行yarn执行yarn setup安装 CLIyarn add --dev webpack-cli在具体示例目录中构建例如cd examples/commonjs node build.js。对本示例而言用仓库根的 examples 工具链一键构建所有示例npm run build:examples入口为 examples/buildAll.js即可在本地复现。示例 README 中 Info 一节记录了构建统计可作为产出正确性的基准未优化模式output.js 18.2 KiB伴随资源89a353e9c515885abd8e.png 14.6 KiB来自file.png./example.js 4 modules其中 HTML 字符串模块 1约 8.89 KiB运行时模块 1.92 KiB。production 模式output.js压缩至10.9 KiB [minimized]HTML 产物被压缩为极小的 payloadfile.png仍作为独立哈希资源输出。八、扩展思路与使用注意扩展语法生态unified 管道是插件化的可在remarkRehype前继续use其他 remark 插件数学公式、目录、自定义容器等或在rehypeStringify前接入代码高亮 rehype 插件形成适合自己内容体系的Markdown 组件渲染器。多格式统一入口正则/\.(md|markdown|…|ron)$/i让 README 式的markdown、.mdown、.ron等冷门扩展名也能统一走转换管线适合在文档站点中接入既有内容库。文本导入安全asset/source与字节导入都返回未经净化的原文渲染时务必使用textContent只有经过 sanitize 的file.md产物才适合innerHTML。类型提示配置中通过/** typedef {import(webpack).LoaderContextvoid} LoaderContext */与 JSDoc 标注了 preprocessor 的参数类型在使用 TS 或编辑器类型检查时保持 API 契约清晰。资源解析提醒file.md 演示的[![Webpack](https://raw.gitcode.com/GitHub_Trending/web/webpack/raw/896506966c25d1032c757c28c7422ec5ffc15705/examples/css/images/file.png?utm_sourcegitcode_repo_files)](https://link.gitcode.com/i/70a995198a6ee64b52e1f27ec80050d9)说明 Markdown 中的相对图片路径会被 html-loader 交给 webpack asset 模块处理并产出带哈希的资源但这是解析 Markdown 内相对资源的能力若图片位于远程则保持原始 URL 不变。总而言之这个示例用最小的目录结构一份复杂样本、两份原始文本、一个入口、一份配置同时展示了 webpack 处理非 JS 内容的三种范式——loader 预处理管道、asset/source文本模块、以及基于导入属性的字节模块。理解 file.md 中每一种语法在该管线下的归宿也就掌握了把任意 Markdown 生态含 GFM 扩展、Front Matter、脚注与本地资源接入 webpack 构建的完整方法论。【免费下载链接】webpackA bundler for javascript and friends. Packs many modules into a few bundled assets. Code Splitting allows for loading parts of the application on demand. Through loaders, modules can be CommonJs, AMD, ES6 modules, CSS, Images, JSON, Coffeescript, LESS, ... and your custom stuff.项目地址: https://gitcode.com/GitHub_Trending/web/webpack创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考