UNPKG

@marko/compiler

Version:
402 lines (336 loc) 10.6 kB
"use strict";exports.__esModule = true;exports.findAttributeTags = findAttributeTags;exports.findParentTag = findParentTag;exports.getArgOrSequence = getArgOrSequence;exports.getFullyResolvedTagName = getFullyResolvedTagName;exports.getMacroIdentifier = getMacroIdentifier;exports.getMacroIdentifierForName = getMacroIdentifierForName;exports.getTagDef = getTagDef;exports.getTagTemplate = getTagTemplate;exports.getTemplateId = getTemplateId;exports.hasMacro = hasMacro;exports.isAttributeTag = isAttributeTag;exports.isDynamicTag = isDynamicTag;exports.isLoopTag = isLoopTag;exports.isMacroTag = isMacroTag;exports.isNativeTag = isNativeTag;exports.isTransparentTag = isTransparentTag;exports.loadFileForImport = loadFileForImport;exports.loadFileForTag = loadFileForTag;exports.registerMacro = registerMacro;exports.resolveTagImport = resolveTagImport;var _compiler = require("@marko/compiler"); var _modules = _interopRequireDefault(require("@marko/compiler/modules")); var _path = require("path"); var _quickHash = require("../util/quick-hash"); var _diagnostics = require("./diagnostics"); var _imports = require("./imports"); var _taglib = require("./taglib");function _interopRequireDefault(e) {return e && e.__esModule ? e : { default: e };} const { cwd, root } = _modules.default; const MACRO_IDS_KEY = Symbol(); const MACRO_NAMES_KEY = "__marko_macro_names__"; // must be a string literal since it is used across compiler stages. const TRANSPARENT_TAGS = new Set([ "for", "while", "if", "else", "else-if", "_no-update"] ); function isNativeTag(path) { if (path.node._isDynamicString) { return true; } const tagDef = getTagDef(path); return ( tagDef && tagDef.html && ( tagDef.htmlType === "custom-element" || !tagDef.template && !tagDef.renderer)); } function isDynamicTag(path) { return !_compiler.types.isStringLiteral(path.node.name); } function isAttributeTag(path) { const { node: { name } } = path; return _compiler.types.isStringLiteral(name) && name.value[0] === "@"; } function isTransparentTag(path) { const { node: { name } } = path; return _compiler.types.isStringLiteral(name) && TRANSPARENT_TAGS.has(name.value); } function registerMacro(path, name) { const { file } = path.hub; const markoMeta = file.metadata.marko; const macroNames = markoMeta[MACRO_NAMES_KEY]; if (macroNames) { if (macroNames[name]) { (0, _diagnostics.diagnosticWarn)(path, { label: `A macro with the name "${name}" already exists.`, fix() { findParentTag(path).remove(); } }); } macroNames[name] = true; } else { markoMeta[MACRO_NAMES_KEY] = { [name]: true }; } } function hasMacro(path, name) { const macroNames = path.hub.file.metadata.marko[MACRO_NAMES_KEY]; return !!(macroNames && macroNames[name]); } function isMacroTag(path) { const { name } = path.node; return _compiler.types.isStringLiteral(name) && hasMacro(path, name.value); } function getMacroIdentifierForName(path, name) { const { file } = path.hub; if (file.___compileStage !== "translate") { throw new Error( "getMacroIdentifierForName can only be called during the translate phase of the compiler." ); } const markoMeta = file.metadata.marko; let macroIds = markoMeta[MACRO_IDS_KEY]; if (!macroIds) { macroIds = markoMeta[MACRO_IDS_KEY] = {}; for (const macroName in markoMeta[MACRO_NAMES_KEY]) { macroIds[macroName] = file.path.scope.generateUid(macroName); } } const id = macroIds[name]; if (!id) { throw new Error( "<macro> was added programmatically, but was not registered via the 'registerMacro' api in @marko/compiler/babel-utils." ); } return _compiler.types.identifier(id); } function getMacroIdentifier(path) { const { file } = path.hub; if (file.___compileStage !== "translate") { throw new Error( "getMacroIdentifier can only be called during the translate phase of the compiler." ); } if (!isMacroTag(path)) { throw path.buildCodeFrameError( "getMacroIdentifier called on non macro referencing tag." ); } return getMacroIdentifierForName(path, path.node.name.value); } function getTagTemplate(tag) { const { node, hub: { file } } = tag; if (node.extra?.tagNameImported) { return (0, _path.join)(file.opts.filename, node.extra.tagNameImported); } return getTagDef(tag)?.template; } function getTagDef(path) { const { node, hub: { file } } = path; if (node.tagDef === undefined) { if (isDynamicTag(path) || isMacroTag(path)) { node.tagDef = null; } else { node.tagDef = (0, _taglib.getTagDefForTagName)( file, isAttributeTag(path) ? getFullyResolvedTagName(path) : node.name.value ) || null; } } return node.tagDef; } function getFullyResolvedTagName(path) { const parts = []; let cur; do { cur = path.node.name.value; if (isAttributeTag(path)) { parts.push(cur.slice(1)); } else { parts.push(cur || "*"); break; } } while (path = findParentTag(path)); return parts.reverse().join(":"); } function findParentTag(path) { let cur = path.parentPath; while (cur.node) { if (cur.isMarkoTagBody()) { cur = cur.parentPath; continue; } if (!cur.isMarkoTag()) { cur = undefined; break; } if (isTransparentTag(cur)) { cur = cur.parentPath; continue; } return cur; } } function findAttributeTags(path, attributeTags = []) { const attrTags = path.node.body.attributeTags ? path.get("body").get("body") : path.get("attributeTags"); attrTags.forEach((child) => { if (isAttributeTag(child)) { attributeTags.push(child); } else if (isTransparentTag(child)) { findAttributeTags(child, attributeTags); } }); return attributeTags; } function getArgOrSequence(path) { const { node: { arguments: args } } = path; const len = args && args.length; if (len) { if (len > 1) { return _compiler.types.sequenceExpression(args); } else { return args[0]; } } } function isLoopTag(path) { if (!path.isMarkoTag()) { return false; } const tagName = path.node.name.value; return tagName === "while" || tagName === "for"; } function loadFileForTag(tag) { const { file } = tag.hub; if (tag.node.extra?.tagNameImported) { return loadFileForImport(file, tag.node.extra?.tagNameImported); } const def = getTagDef(tag); const filename = def && def.template; if (filename) { return resolveMarkoFile(file, filename); } } function loadFileForImport(file, request) { const relativeRequest = resolveTagImport(file.path, request); if (relativeRequest) { const filename = relativeRequest[0] === "." ? (0, _path.resolve)(file.opts.filename, "..", relativeRequest) : _modules.default.resolve(relativeRequest, (0, _path.dirname)(file.opts.filename)); return resolveMarkoFile(file, filename); } } function resolveMarkoFile(file, filename) { if (filename === file.opts.filename) { if (file.___compileStage === "analyze") { return file; } return file.___getMarkoFile(file.code, file.opts, file.markoOpts); } try { const childFile = file.___getMarkoFile( file.markoOpts.fileSystem.readFileSync(filename).toString("utf-8"), createNewFileOpts(file.opts, filename), file.markoOpts ); (file.metadata.marko.analyzedTags ||= new Set()).add(filename); return childFile; } catch (_) { // ignore }} const idCache = new WeakMap(); const templateIdHashOpts = { outputLength: 5 }; function getTemplateId(opts, request, child) { if (!child && opts.getTemplateId) return opts.getTemplateId(request); const id = (0, _path.relative)(root, request).replace(/[^a-zA-Z0-9_$./-]/g, "/"); if (opts.optimize) { const optimizeKnownTemplates = typeof opts === "object" && opts.optimizeKnownTemplates; const knownTemplatesSize = optimizeKnownTemplates?.length || 0; if (knownTemplatesSize) { let lookup = idCache.get(optimizeKnownTemplates); if (!lookup) { lookup = new Map(); idCache.set(optimizeKnownTemplates, lookup); for (let i = 0; i < knownTemplatesSize; i++) { lookup.set(optimizeKnownTemplates[i], { id: encodeTemplateId(i), children: new Map() }); } } let registered = lookup.get(request); if (registered) { if (child) { let childId = registered.children.get(child); if (childId === undefined) { childId = registered.children.size; registered.children.set(child, childId); } return registered.id + childId; } return registered.id; } } const hash = new _quickHash.Hash().update(id); if (child) { hash.update(child); } return encodeTemplateId(hash.digest()); } return id + (child ? `_${child}` : ""); } function resolveTagImport(path, request) { const { hub: { file } } = path; if (request[0] === "<") { const tagName = request.slice(1, -1); const tagDef = (0, _taglib.getTagDefForTagName)(file, tagName); const tagEntry = tagDef && (tagDef.renderer || tagDef.template); const relativePath = tagEntry && (0, _imports.resolveRelativePath)(file, tagEntry); if (!relativePath) { throw path.buildCodeFrameError( `Unable to find entry point for custom tag <${tagName}>.` ); } return relativePath; } if (request.endsWith(".marko")) { return (0, _imports.resolveRelativePath)(file, request); } } function createNewFileOpts(opts, filename) { const sourceFileName = (0, _path.basename)(filename); const sourceRoot = (0, _path.dirname)(filename); const filenameRelative = (0, _path.relative)(cwd, filename); return { ...opts, filename, sourceRoot, sourceFileName, filenameRelative, parserOpts: { ...opts.parserOpts, sourceFileName }, generatorOpts: { ...opts.generatorOpts, filename, sourceRoot, sourceFileName } }; } function encodeTemplateId(id) { const c = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_0123456789"; let n = id; let r = c[n % 53]; // Avoids chars that cannot start a property name and _ (reserved). n = Math.floor(n / 53); // ensure at most 7 characters. for (let i = 6; n > 0 && i--; n = Math.floor(n / 64)) { r += c[n & 63]; } return r; }