dom-to-code
Version:
navigate to the code through the dom
201 lines (192 loc) • 6.52 kB
JavaScript
const core = require('@babel/core');
const MagicString = require('magic-string');
const constant = require('./dom-to-code.401158b7.cjs');
const compilerDom = require('@vue/compiler-dom');
const vueJsxPlugin = require('@vue/babel-plugin-jsx');
const typescriptPlugin = require('@babel/plugin-transform-typescript');
const importMeta = require('@babel/plugin-syntax-import-meta');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e["default"] : e; }
const MagicString__default = /*#__PURE__*/_interopDefaultLegacy(MagicString);
const vueJsxPlugin__default = /*#__PURE__*/_interopDefaultLegacy(vueJsxPlugin);
const typescriptPlugin__default = /*#__PURE__*/_interopDefaultLegacy(typescriptPlugin);
const importMeta__default = /*#__PURE__*/_interopDefaultLegacy(importMeta);
function createDomAttrLineInfo(filePath, line, column = 0) {
return `${constant.DOM_ATTR}="${filePath}:${line}:${column}"`;
}
function resolveOption(options) {
const mode = options.mode || "vue";
if (!constant.SUPPORT_MODE.includes(mode))
throw new Error(`dom-to-code: mode ${mode} is not supported`);
const defaultIncludeMap = {
vue: [constant.REGEX_VUE_SFC, constant.REGEX_SETUP_SFC, constant.REGEX_JSX_FILE],
react: [constant.REGEX_JSX_FILE]
};
return {
mode,
include: options.include || defaultIncludeMap[mode],
exclude: options.exclude || [/node_modules/, /\.git/, /\.nuxt/]
};
}
function parseJSXIdentifier(name) {
if (name.type === "JSXIdentifier")
return name.name || "";
else if (name.type === "JSXNamespacedName")
return parseJSXIdentifier(name.name) || "";
else
return `${parseJSXIdentifier(name.object)}.${parseJSXIdentifier(name.property)}`;
}
function getJsxElementName(openingElement) {
return parseJSXIdentifier(openingElement.name);
}
function hasOwn(obj, key) {
return Object.prototype.hasOwnProperty.call(obj, key);
}
const getTransformResult = (s, id) => {
return {
code: s?.toString() || "",
get map() {
return s?.generateMap({
source: id,
includeContent: true,
hires: true
});
}
};
};
const transformReact = (code, id) => {
if (id.endsWith(".tsx") || id.endsWith(".jsx")) {
const transformedCode = code;
const s = new MagicString__default(transformedCode);
const ast = core.parseSync(code, {
configFile: false,
filename: id,
ast: true,
presets: [
"@babel/preset-env",
"@babel/preset-react",
"@babel/preset-typescript"
]
});
core.traverse(ast, {
enter({ node }) {
if (node.type === "JSXElement" && !getJsxElementName(node.openingElement).endsWith("Fragment")) {
if (node?.openingElement?.name?.object?.name === "React")
return;
const { start } = node;
const { column, line } = node?.loc?.start;
const toInsertPosition = (start || 0) + parseJSXIdentifier(node.openingElement.name).length + 1;
const content = ` ${createDomAttrLineInfo(id, line, column)}`;
s.appendLeft(toInsertPosition, content);
}
}
});
const sourceMap = s.generateMap({
source: id,
includeContent: true
});
return {
code: s.toString(),
map: sourceMap
};
}
};
const EXCLUDE_TAG = ["template", "script", "style"];
async function compileSFCTemplate({ code, id, type }) {
const s = new MagicString__default(code);
switch (type) {
case "template": {
const ast = compilerDom.parse(code, { comments: true });
compilerDom.transform(ast, {
nodeTransforms: [
(node) => {
if (node.type === 1) {
if (node.tagType === 0 && !EXCLUDE_TAG.includes(node.tag)) {
if (node.loc.source.includes(constant.DOM_ATTR))
return;
const insertPosition = node.loc.start.offset + node.tag.length + 1;
const { line, column } = node.loc.start;
const content = ` ${createDomAttrLineInfo(id, line, column)}`;
s.prependLeft(
insertPosition,
content
);
}
}
}
]
});
break;
}
case "jsx": {
const ast = core.parse(code, {
babelrc: false,
comments: true,
plugins: [
importMeta__default,
[vueJsxPlugin__default, {}],
[
typescriptPlugin__default,
{ isTSX: true, allowExtensions: true }
]
]
});
core.traverse(ast, {
enter({ node }) {
if (node.type === "JSXElement") {
if (node.openingElement.attributes.some(
(attr) => attr.type !== "JSXSpreadAttribute" && attr.name.name === constant.DOM_ATTR
))
return;
const insertPosition = (node.start || 0) + parseJSXIdentifier(node.openingElement.name).length + 1;
const { line = 1, column = 0 } = node.loc?.start || {};
const content = ` ${createDomAttrLineInfo(id, line, column)}`;
s.prependLeft(
insertPosition,
content
);
}
}
});
break;
}
}
return getTransformResult(s, id);
}
function parseVueRequest(id) {
const [filename] = id.split("?", 2);
const url = new URL(id, "http://domain.inspector");
const query = Object.fromEntries(url.searchParams.entries());
if (query.vue != null)
query.vue = true;
if (query.src != null)
query.src = true;
if (query.index != null)
query.index = Number(query.index);
if (query.raw != null)
query.raw = true;
if (hasOwn(query, "lang.tsx") || hasOwn(query, "lang.jsx"))
query.isJsx = true;
return {
filename,
query
};
}
function transformVue(code, id) {
const { filename, query } = parseVueRequest(id);
const isJsx = filename.endsWith(".jsx") || filename.endsWith(".tsx") || filename.endsWith(".vue") && query.isJsx;
const isTpl = filename.endsWith(".vue") && query.type !== "style";
if (isJsx || isTpl)
return compileSFCTemplate({ code, id: filename, type: isJsx ? "jsx" : "template" });
return { code };
}
function transform(code, id, options) {
if (options.mode === "vue")
return transformVue(code, id);
else if (options.mode === "react")
return transformReact(code, id);
else
return code;
}
exports.resolveOption = resolveOption;
exports.transform = transform;
;