UNPKG

@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
"use strict"; 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 });