@marko/translator-interop-class-tags
Version:
Combines the Class API translator from Marko 5 and the Tags API translator from Marko 6
509 lines (502 loc) • 17 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
analyze: () => analyze,
getRuntimeEntryFiles: () => getRuntimeEntryFiles,
tagDiscoveryDirs: () => tagDiscoveryDirs,
taglibs: () => taglibs,
transform: () => transform,
translate: () => translate
});
module.exports = __toCommonJS(index_exports);
var import_generator = __toESM(require("@babel/generator"));
var import_compiler = require("@marko/compiler");
var import_babel_utils2 = require("@marko/compiler/babel-utils");
var import_translator3 = require("@marko/runtime-tags/translator");
var import_translator4 = require("marko/translator");
var import_path2 = __toESM(require("path"));
// src/feature-detection.ts
var import_babel_utils = require("@marko/compiler/babel-utils");
var import_translator = require("@marko/runtime-tags/translator");
var import_translator2 = require("marko/translator");
// src/build-aggregate-error.ts
var import_code_frame = require("@babel/code-frame");
var import_path = __toESM(require("path"));
var CWD = process.cwd();
function buildAggregateError(file, rootMsg, ...paths) {
const err = new SyntaxError();
const fileName = import_path.default.relative(CWD, file.opts.filename);
const finalMsg = `${rootMsg}:
${paths.map(
([msg, path3]) => `\x1B[90m${msg} at ${getFileNameWithLoc(
fileName,
path3
)}:\x1B[0m
${getFrame(file, path3)}`
).join("\n\n")}`;
if (!("MARKO_DEBUG" in process.env)) {
err.stack = finalMsg;
}
Object.defineProperty(err, "message", {
get() {
return finalMsg;
},
set() {
}
});
return err;
}
function getFrame(file, { node: { loc } }) {
return loc ? (0, import_code_frame.codeFrameColumns)(
file.code,
{
start: {
line: loc.start.line,
column: loc.start.column + 1
},
end: loc.end && loc.start.line === loc.end.line ? {
line: loc.end.line,
column: loc.end.column + 1
} : void 0
},
{ highlightCode: true }
) : "";
}
function getFileNameWithLoc(fileName, { node: { loc } }) {
if (loc) {
return `${fileName}(${loc.start.line},${loc.start.column + 1})`;
}
return fileName;
}
// src/feature-detection.ts
var DEFAULT_FEATURE_TYPE = "class" /* Class */;
function isTagsAPI(file = (0, import_babel_utils.getFile)()) {
let featureType = file.path.node.extra?.featureType;
if (!featureType) {
const forceTags = isTagsAPIFromFileName(file.opts.filename);
if (file.___compileStage === "parse") {
featureType = forceTags ? "tags" /* Tags */ : DEFAULT_FEATURE_TYPE;
} else {
const program = file.path;
const state = {};
program.node.extra ??= {};
program.traverse(featureDetectionVisitor, state);
if (forceTags) {
if (state.feature?.type === "class" /* Class */) {
throw buildAggregateError(
file,
'Cannot use "class api" features under a "tags/" directory',
[state.feature.name, state.feature.path]
);
}
}
featureType = program.node.extra.featureType = forceTags ? "tags" /* Tags */ : state.feature?.type || DEFAULT_FEATURE_TYPE;
}
}
return featureType === "tags" /* Tags */;
}
var PATH_SEPARATOR_REGEX = /\/|\\/;
var TAGS_LENGTH = "tags".length;
var COMPONENTS_LENGTH = "components".length;
function isTagsAPIFromFileName(filename) {
const pathSeparator = PATH_SEPARATOR_REGEX.exec(filename)?.[0];
if (pathSeparator) {
let previousIndex = filename.length - 1;
while (previousIndex > 0) {
const index = filename.lastIndexOf(pathSeparator, previousIndex);
switch (previousIndex - index) {
case TAGS_LENGTH: {
if (filename.startsWith("tags", index + 1)) {
return true;
}
break;
}
case COMPONENTS_LENGTH: {
if (filename.startsWith("components", index + 1)) {
return false;
}
}
}
previousIndex = index - 1;
}
}
return false;
}
var featureDetectionVisitor = {
MarkoComment(comment, state) {
if (/^\s*use tags\s*$/.test(comment.node.value)) {
addFeature(state, "tags" /* Tags */, "<!-- use tags -->", comment);
}
},
MarkoScriptlet(scriptlet, state) {
if (!scriptlet.node.static) {
addFeature(state, "class" /* Class */, "Scriptlet", scriptlet);
}
},
MarkoClass(markoClass, state) {
addFeature(state, "class" /* Class */, "Class block", markoClass.get("body"));
},
ReferencedIdentifier(ref, state) {
const name = ref.node.name;
if ((name === "component" || name === "out") && !ref.scope.hasBinding(name)) {
addFeature(state, "class" /* Class */, `${name} template global`, ref);
}
},
MarkoTag(tag, state) {
if (tag.node.var) {
addFeature(
state,
"tags" /* Tags */,
"Tag variable",
tag.get("var")
);
}
for (const attr of tag.get("attributes")) {
if (attr.isMarkoAttribute()) {
if (attr.node.arguments?.length) {
addFeature(
state,
"class" /* Class */,
"Attribute arguments",
attr.get("arguments")[0]
);
break;
} else if (attr.node.modifier) {
addFeature(state, "class" /* Class */, "Attribute modifier", attr);
break;
} else if (attr.node.bound) {
addFeature(state, "tags" /* Tags */, "Bound attribute", attr);
break;
} else {
switch (attr.node.name) {
case "key":
case "no-update":
case "no-update-if":
case "no-update-body-if":
addFeature(
state,
"class" /* Class */,
`"${attr.node.name}" attribute`,
attr
);
break;
}
}
}
}
const tagDef = (0, import_babel_utils.getTagDef)(tag);
if (tagDef) {
const feature = getFeatureByTagName(tagDef.name);
if (feature) {
addFeature(state, feature, `<${tagDef.name}> tag`, tag.get("name"));
}
}
}
};
var getFeatureByTagName = (() => {
const taglib5UniqueTags = new Set(
import_translator2.taglibs.flatMap(
(taglib2) => Object.keys(taglib2[1]).map((key) => /^<(.*)>$/.exec(key)?.[1])
)
);
const taglib6UniqueTags = new Set(
import_translator.taglibs.flatMap(
(taglib2) => Object.keys(taglib2[1]).map((key) => /^<(.*)>$/.exec(key)?.[1])
)
);
for (const tagName of taglib5UniqueTags) {
if (taglib6UniqueTags.has(tagName)) {
taglib5UniqueTags.delete(tagName);
taglib6UniqueTags.delete(tagName);
}
}
return (tagName) => {
switch (tagName) {
case "html":
case "head":
case "body":
case "script":
case "style":
return;
}
if (taglib5UniqueTags.has(tagName)) return "class" /* Class */;
if (taglib6UniqueTags.has(tagName)) return "tags" /* Tags */;
};
})();
function addFeature(state, type, name, path3) {
if (state.feature) {
if (state.feature.type !== type) {
throw buildAggregateError(
path3.hub.file,
'Cannot mix "tags api" and "class api" features',
[state.feature.name, state.feature.path],
[name, path3]
);
}
} else {
state.feature = {
name,
path: path3,
type
};
}
}
// src/index.ts
var UNMERGABLE_TAGDEF_KEYS = ["renderer", "template"];
var CANONICAL_TAGDEF_KEYS = {
migrator: "migrate",
"code-generator": "translate",
codeGenerator: "translate",
"node-factory": "parse",
nodeFactory: "parse",
transformer: "transform",
"parse-options": "parseOptions"
};
var VISITOR_TAGDEF_KEYS = ["parse", "migrate", "transform", "translate"];
var tagDiscoveryDirs = ["tags", "components"];
var taglibs = mergeTaglibs(
import_compiler.taglib.resolveOptionalTaglibs(import_translator4.optionalTaglibs).concat(import_translator4.taglibs),
import_translator3.taglibs
);
var transform = mergeVisitors(import_translator4.transform, import_translator3.transform);
var analyze = mergeVisitors(import_translator4.analyze, import_translator3.analyze);
var translate = patchTranslateProgram(
mergeVisitors(import_translator4.translate, import_translator3.translate)
);
function getRuntimeEntryFiles(output, optimize) {
return [
...(0, import_translator4.getRuntimeEntryFiles)(output, optimize),
...(0, import_translator3.getRuntimeEntryFiles)(output, optimize),
`marko/${optimize ? "dist" : "src"}/runtime/helpers/tags-compat/${output === "html" ? "html" : "dom"}${optimize ? "" : "-debug"}.mjs`
];
}
function patchTranslateProgram(visitor) {
const { Program } = visitor;
const kState = Symbol();
const entryBuilder = {
build(entryFile) {
const state = entryFile[kState];
if (!state) {
throw entryFile.path.buildCodeFrameError(
"Unable to build hydrate code, no files were visited before finalizing the build"
);
}
if (state.has5) {
if (state.has6) {
const filename = entryFile.opts.filename;
const baseName = `./${import_path2.default.basename(filename)}`;
const generatorOpts = {
...entryFile.opts.generatorOpts,
sourceMaps: false
};
const { resolveVirtualDependency } = entryFile.markoOpts;
const importHydrateProgram = (name, statements) => {
return import_compiler.types.importDeclaration(
[],
import_compiler.types.stringLiteral(
resolveVirtualDependency(filename, {
code: (0, import_generator.default)(import_compiler.types.program(statements), generatorOpts).code,
virtualPath: `${baseName}.hydrate-${name}.js`
})
)
);
};
return [
importHydrateProgram("5", import_translator4.internalEntryBuilder.build(entryFile)),
importHydrateProgram("6", import_translator3.internalEntryBuilder.build(entryFile))
];
} else {
return import_translator4.internalEntryBuilder.build(entryFile);
}
} else {
return import_translator3.internalEntryBuilder.build(entryFile);
}
},
visit(file, entryFile, visitChild) {
const state = entryFile[kState] ||= {
has5: false,
has6: false
};
if (isTagsAPI(file)) {
state.has6 = true;
import_translator3.internalEntryBuilder.visit(file, entryFile, visitChild);
} else {
state.has5 = true;
import_translator4.internalEntryBuilder.visit(file, entryFile, visitChild);
}
}
};
const enterProgram = getVisitorEnter(Program);
const exitProgram = getVisitorExit(Program);
visitor.Program = {
enter(program, state) {
if (program.hub.file.markoOpts.output !== "hydrate") {
return enterProgram?.call(this, program, state);
}
const entryFile = program.hub.file;
const visitedFiles = /* @__PURE__ */ new Set([
(0, import_babel_utils2.resolveRelativePath)(entryFile, entryFile.opts.filename)
]);
entryBuilder.visit(entryFile, entryFile, function visitChild(resolved) {
if (!visitedFiles.has(resolved)) {
visitedFiles.add(resolved);
const file = (0, import_babel_utils2.loadFileForImport)(entryFile, resolved);
if (file) {
entryBuilder.visit(
file,
entryFile,
(id) => visitChild(resolveRelativeToEntry(entryFile, file, id))
);
}
}
});
program.node.body = entryBuilder.build(entryFile);
program.skip();
},
exit: exitProgram
};
return visitor;
}
function resolveRelativeToEntry(entryFile, file, req) {
return file === entryFile ? (0, import_babel_utils2.resolveRelativePath)(file, req) : (0, import_babel_utils2.resolveRelativePath)(
entryFile,
req[0] === "." ? import_path2.default.join(file.opts.filename, "..", req) : req
);
}
function mergeVisitors(visitor5 = {}, visitor6 = {}) {
const allVisitorKeys = getSetOfAllKeys(visitor5, visitor6);
const mergedVisitors = {};
for (const visitorKey of allVisitorKeys) {
mergedVisitors[visitorKey] = mergedVisitor(
visitor5[visitorKey],
visitor6[visitorKey]
);
}
return mergedVisitors;
}
function mergedVisitor(visitor5 = {}, visitor6 = {}) {
const enter5 = getVisitorEnter(visitor5);
const exit5 = getVisitorExit(visitor5);
const enter6 = getVisitorEnter(visitor6);
const exit6 = getVisitorExit(visitor6);
const visitor = {
enter(path3, state) {
return (isTagsAPI() ? enter6 : enter5)?.call(this, path3, state);
},
exit(path3, state) {
return (isTagsAPI() ? exit6 : exit5)?.call(this, path3, state);
}
};
return exit5 || exit6 ? visitor : visitor.enter;
}
function mergeTaglibs(taglibs53, taglibs63) {
const taglib5Merged = taglibs53.reduce(
(mergedTaglib2, taglib2) => Object.assign(mergedTaglib2, taglib2[1]),
{}
);
const taglib6Merged = taglibs63.reduce(
(mergedTaglib2, taglib2) => Object.assign(mergedTaglib2, taglib2[1]),
{}
);
const allTaglibKeys = getSetOfAllKeys(taglib5Merged, taglib6Merged);
const mergedTaglib = {};
for (const taglibKey of allTaglibKeys) {
if (taglibKey.startsWith("<")) {
mergedTaglib[taglibKey] = mergeTagDef(
taglib5Merged[taglibKey],
taglib6Merged[taglibKey]
);
} else if (taglibKey === "migrate") {
mergedTaglib[taglibKey] = mergeVisitors(
normalizeTagDefVisitors(taglib5Merged[taglibKey]),
normalizeTagDefVisitors(taglib6Merged[taglibKey])
);
}
}
return [["@marko/translator-interop-class-tags", mergedTaglib]];
}
function mergeTagDef(tagDef5 = {}, tagDef6 = {}) {
const tagDef5Normalized = normalizeTagDef(tagDef5);
const tagDef6Normalized = normalizeTagDef(tagDef6);
const allTagDefKeys = getSetOfAllKeys(tagDef5Normalized, tagDef6Normalized);
const mergedTagDef = {};
for (const tagDefKey of allTagDefKeys) {
if (VISITOR_TAGDEF_KEYS.includes(tagDefKey)) {
mergedTagDef[tagDefKey] = mergedVisitor(
normalizeTagDefVisitor(tagDef5Normalized[tagDefKey]),
normalizeTagDefVisitor(tagDef6Normalized[tagDefKey])
);
} else {
mergedTagDef[tagDefKey] = tagDef5Normalized[tagDefKey] ?? tagDef6Normalized[tagDefKey];
if (UNMERGABLE_TAGDEF_KEYS.includes(tagDefKey) && tagDef5Normalized[tagDefKey] && tagDef6Normalized[tagDefKey]) {
throw new Error(`cannot merge "${tagDefKey}"`);
}
}
}
return mergedTagDef;
}
function normalizeTagDef(tagDef) {
const normalized = {};
for (const key in tagDef) {
normalized[CANONICAL_TAGDEF_KEYS[key] ?? key] = tagDef[key];
}
return normalized;
}
function getVisitorEnter(visit) {
if (typeof visit === "function") {
return visit;
}
return visit?.enter;
}
function getVisitorExit(visit) {
if (typeof visit === "function") {
return void 0;
}
return visit?.exit;
}
function getSetOfAllKeys(o1, o2) {
return new Set(Object.keys(o1).concat(Object.keys(o2)));
}
function normalizeTagDefVisitors(visitor) {
return visitor?.default ?? visitor;
}
function normalizeTagDefVisitor(visitor) {
return typeof visitor === "function" ? visitor : visitor?.default ?? visitor;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
analyze,
getRuntimeEntryFiles,
tagDiscoveryDirs,
taglibs,
transform,
translate
});