UNPKG

@vitepress-demo-preview/plugin

Version:

preview component of code and component in vitepress

346 lines (331 loc) 14.8 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var path = require('path'); var fs = require('fs'); var markdownItContainer = require('markdown-it-container'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var markdownItContainer__default = /*#__PURE__*/_interopDefaultLegacy(markdownItContainer); /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ var __assign = function() { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; // componentPreview check var isCheckPreviewCom1 = /^<preview (.*)><\/preview>$/; var isCheckPreviewCom2 = /^<preview (.*) \/>$/; var isCheckContainerPreview = /^demo-preview=(.+)$/; var scriptLangTsRE = /<\s*script[^>]*\blang=['"]ts['"][^>]*/; var scriptSetupRE = /<\s*script[^>]*\bsetup\b[^>]*/; var scriptSetupCommonRE = /<\s*script\s+(setup|lang='ts'|lang="ts")?\s*(setup|lang='ts'|lang="ts")?\s*>/; function normalizeOptions(options) { if (options === void 0) { options = {}; } return __assign({ clientOnly: false, alias: null }, options); } /** * 统一处理组件名称->驼峰命名 * @param componentName */ var handleComponentName = function (componentName) { var newName = componentName; newName = newName.replaceAll(/[_|-]+(\w)/g, function ($0, $1) { return $1.toUpperCase(); }); return newName; }; /** * 注入 script 脚本 * @param mdInstance * @param path * @param componentName */ var injectComponentImportScript = function (env, path, componentName, options) { var clientOnly = options.clientOnly; // https://github.com/vuejs/vitepress/issues/1258 __Path、__Relativepath、__data.Hoistedtags 被删除解决方案 // https://github.com/mdit-vue/mdit-vue/blob/main/packages/plugin-sfc/src/types.ts // https://github.com/mdit-vue/mdit-vue/blob/main/packages/plugin-sfc/tests/__snapshots__/sfc-plugin.spec.ts.snap var scriptsCode = env.sfcBlocks.scripts; // 判断MD文件内部是否本身就存在 <script setup> 脚本 var scriptsSetupIndex = scriptsCode.findIndex(function (script) { if (scriptSetupRE.test(script.tagOpen) || scriptLangTsRE.test(script.tagOpen)) return true; return false; }); // 统一处理组件名称为驼峰命名 var _componentName = handleComponentName(componentName); var importDefineClientComponent = "import { defineClientComponent } from 'vitepress'\n"; var importScripts = clientOnly ? "const ".concat(_componentName, " = defineClientComponent(() => import('").concat(path, "'))") : "import ".concat(_componentName, " from '").concat(path, "'"); // MD文件中没有 <script setup> 或 <script setup lang='ts'> 脚本文件 if (scriptsSetupIndex === -1) { var scriptBlockObj = { type: 'script', tagClose: '</script>', tagOpen: "<script setup lang='ts'>", content: "<script setup lang='ts'>\n ".concat(clientOnly ? importDefineClientComponent : "", "\n ").concat(importScripts, "\n </script>"), contentStripped: "import ".concat(_componentName, " from '").concat(path, "'") }; scriptsCode.push(scriptBlockObj); } else { // MD文件注入了 <script setup> 或 <script setup lang='ts'> 脚本 var content = scriptsCode[0].content; // MD文件中存在已经引入了组件 if (content.includes(path) || content.includes(_componentName)) return; // MD文件中不存在组件 添加组件 import ${_componentName} from '${path}'\n // 如果MD文件中存在 <script setup lang="ts">、<script lang="ts" setup> 或 <script setup> 代码块, 那么统一转换为 <script setup lang="ts"> var scriptCodeBlock = '<script lang="ts" setup>\n'; content = content.replace(scriptSetupCommonRE, scriptCodeBlock); // 如果是clientOnly模式,判断是否已经引入了 defineClientComponent,并在script后面添加组件引入代码 // 否则在script前面import组件 if (clientOnly) { if (!content.includes(importDefineClientComponent)) { content = content.replace(scriptCodeBlock, "<script setup>\n".concat(importDefineClientComponent, "\n")); } content = content.replace('</script>', "".concat(importScripts, "\n</script>")); } else { // 将组件引入的代码放进去 content = content.replace(scriptCodeBlock, "<script setup>\n\n import ".concat(_componentName, " from '").concat(path, "'\n")); } scriptsCode[0].content = content; } }; /** * 源码 => 代码块 * @param mdInstance * @param sourceCode * @param suffix * @returns */ var transformHighlightCode = function (mdInstance, sourceCode, suffix) { return mdInstance.options.highlight(sourceCode, suffix, ''); }; /** * 根据组件路径组合组件引用名称 * @param path * @returns */ var composeComponentName = function (path) { var isFlag = true; var componentList = []; while (isFlag) { var lastIndex = path.lastIndexOf('/'); if (lastIndex === -1) { isFlag = false; } else { var name = path.substring(lastIndex + 1); componentList.unshift(name); path = path.substring(0, lastIndex); } } componentList = componentList.filter(function (item) { return item !== '' && item !== '.' && item !== '..'; }); return componentList.join('-').split('.')[0]; }; /** * 检查路径是否为相对路径 * @param path * @returns */ var isRelativePath = function (path) { if (path.startsWith('./') || path.startsWith('../') || path.startsWith('/')) return true; return false; }; /** * 查找别名路径转换绝对路径 * @param alias * @param path * @returns */ var findAliasPathToAbsolutePath = function (alias, path) { var aliasKeyList = Object.keys(alias); var aliasKey = aliasKeyList.find(function (key) { return path.indexOf(key) !== -1; }); if (aliasKey) return path.replace(aliasKey, alias[aliasKey]); return path; }; var titleRegex = /title=['"](.*?)['"]/; var pathRegex = /path=['"](.*?)['"]/; var descriptionRegex = /description=['"](.*?)['"]/; /** * 编译预览组件 * @param md * @param token * @param env * @returns */ var transformPreview = function (md, token, env, options) { var alias = options.alias; var componentProps = { path: '', title: '默认标题', description: '描述内容' }; // 获取Props相关参数 var titleValue = token.content.match(titleRegex); var pathRegexValue = token.content.match(pathRegex); var descriptionRegexValue = token.content.match(descriptionRegex); if (!pathRegexValue) throw new Error('@vitepress-demo-preview/plugin: path is a required parameter'); // eslint-disable-next-line prefer-destructuring // 组件路径(相对路径|别名路径) componentProps.path = pathRegexValue[1]; componentProps.title = titleValue ? titleValue[1] : ''; componentProps.description = descriptionRegexValue ? descriptionRegexValue[1] : ''; // 组件绝对路径 var componentPath = ''; if (isRelativePath(pathRegexValue[1])) { // 相对路径 componentPath = path.resolve(path.dirname(env.path), componentProps.path || '.'); } else if (alias) { // 配置了别名配置 componentPath = findAliasPathToAbsolutePath(alias, pathRegexValue[1]); } else { throw new Error('@vitepress-demo-preview/plugin: path cannot be resolved'); } // 组件名 var componentName = composeComponentName(componentProps.path); // 后缀名 var suffixName = componentPath.substring(componentPath.lastIndexOf('.') + 1); // 注入组件导入语句 injectComponentImportScript(env, componentProps.path, componentName, options); // 组件源码 var componentSourceCode = fs.readFileSync(componentPath, { encoding: 'utf-8' }); // 源码代码块(经过处理) var compileHighlightCode = transformHighlightCode(md, componentSourceCode, suffixName); var code = encodeURI(componentSourceCode); var showCode = encodeURIComponent(compileHighlightCode); var sourceCode = "<demo-preview title=\"".concat(componentProps.title, "\" description=\"").concat(componentProps.description, "\" code=\"").concat(code, "\" showCode=\"").concat(showCode, "\" suffixName=\"").concat(suffixName, "\" absolutePath=\"").concat(componentPath, "\" relativePath=\"").concat(componentProps.path, "\">\n <").concat(componentName, "></").concat(componentName, ">\n </demo-preview>"); return sourceCode; }; var validateContainerRE = /^preview.*$/; var parseContainerParamRE = /^preview\s?(.*?)(?:\s\|\|\s(.*?))?$/; /** * 自定义容器的注册 * @param md */ var containerDirectiveMount = function (md) { md.use(markdownItContainer__default["default"], 'preview', { marker: ':', validate: function (params) { var validateContainer = params.trim().match(validateContainerRE); if (validateContainer && validateContainer.length !== 0) return true; return false; } }); }; /** * 解析自定义日期的Tag * @param md */ var parseContainerTag = function (md, options) { var alias = options.alias; // 开始标签 :::preview var defaultContainerPreviewOpenRender = md.renderer.rules.container_preview_open; md.renderer.rules.container_preview_open = function (tokens, idx, options, env, self) { var token = tokens[idx]; // 组件的相对路径 // 组件路径(相对路径|别名路径) var componentRelativePath = tokens[idx + 2].content.split('=')[1]; // 组件绝对路径 var componentPath = ''; if (isRelativePath(componentRelativePath)) { componentPath = path.resolve(path.dirname(env.path), componentRelativePath || '.'); } else if (alias) { // 配置了别名配置 componentPath = findAliasPathToAbsolutePath(alias, componentRelativePath); } else { throw new Error('@vitepress-demo-preview/plugin: path cannot be resolved'); } // 后缀名 var suffixName = componentPath.substring(componentPath.lastIndexOf('.') + 1); // 组件源码 var componentSourceCode = fs.readFileSync(componentPath, { encoding: 'utf-8' }); // 源码代码块(经过处理) var compileHighlightCode = transformHighlightCode(md, componentSourceCode, suffixName); var code = encodeURI(componentSourceCode); var showCode = encodeURIComponent(compileHighlightCode); var getParamArr = tokens[idx].info.trim().match(parseContainerParamRE); var title = getParamArr && getParamArr[1] ? getParamArr[1] : ''; var description = getParamArr && getParamArr[2] ? getParamArr[2] : ''; if (token.nesting === 1) return "<demo-preview title='".concat(title, "' description='").concat(description, "' code=\"").concat(code, "\" showCode=\"").concat(showCode, "\" suffixName=\"").concat(suffixName, "\" absolutePath=\"").concat(componentPath, "\" relativePath=\"").concat(componentRelativePath, "\">\n"); return defaultContainerPreviewOpenRender(tokens, idx, options, env, self); }; // 闭合标签 ::: var defaultContainerPreviewCloseRender = md.renderer.rules.container_preview_close; md.renderer.rules.container_preview_close = function (tokens, idx, options, env, self) { var token = tokens[idx]; if (token.nesting === -1) return "</demo-preview>\n"; return defaultContainerPreviewCloseRender(tokens, idx, options, env, self); }; }; /** * 解析自定义容器 * @param md */ var parseContainer = function (md, options) { var defaultHtmlTextRender = md.renderer.rules.text; md.renderer.rules.text = function (tokens, idx, mdOptions, env, self) { var token = tokens[idx]; if (token.type === 'text' && token.content.match(isCheckContainerPreview)) { var componentPath = token.content.match(isCheckContainerPreview)[1]; var componentName = composeComponentName(componentPath); injectComponentImportScript(env, componentPath, componentName, options); return "<".concat(componentName, "></").concat(componentName, ">"); } return defaultHtmlTextRender(tokens, idx, mdOptions, env, self); }; }; var componentPreview = function (md, options) { var defaultHtmlInlineRender = md.renderer.rules.html_inline; md.renderer.rules.html_inline = function (tokens, idx, mdOptions, env, self) { var token = tokens[idx]; if (isCheckPreviewCom1.test(token.content) || isCheckPreviewCom2.test(token.content)) { return transformPreview(md, token, env, normalizeOptions(options)); } return defaultHtmlInlineRender(tokens, idx, mdOptions, env, self); }; }; var containerPreview = function (md, options) { var normalizeOptionsVal = normalizeOptions(options); containerDirectiveMount(md); parseContainerTag(md, normalizeOptionsVal); parseContainer(md, normalizeOptionsVal); }; exports.componentPreview = componentPreview; exports.containerPreview = containerPreview;