UNPKG

marko

Version:

UI Components + streaming, async, high performance, HTML templating for Node.js and the browser.

612 lines (535 loc) • 21.1 kB
"use strict";var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");exports.__esModule = true;exports.analyze = void 0;exports.getRuntimeEntryFiles = getRuntimeEntryFiles;exports.translate = exports.taglibs = exports.tagDiscoveryDirs = exports.preferAPI = exports.optionalTaglibs = exports.internalEntryBuilder = void 0;var _compiler = require("@marko/compiler"); var _babelUtils = require("@marko/compiler/babel-utils"); var _package = require("marko/package.json"); var _path = require("path"); var _cdata = _interopRequireDefault(require("./cdata")); var _class = _interopRequireDefault(require("./class")); var _comment = _interopRequireDefault(require("./comment")); var _declaration = _interopRequireDefault(require("./declaration")); var _documentType = _interopRequireDefault(require("./document-type")); var _placeholder = _interopRequireDefault(require("./placeholder")); var _scriptlet = _interopRequireDefault(require("./scriptlet")); var _tag = _interopRequireDefault(require("./tag")); var _text = _interopRequireDefault(require("./text")); var _addDependencies = _interopRequireWildcard(require("./util/add-dependencies"));exports.internalEntryBuilder = _addDependencies.entryBuilder; var _getComponentFiles = _interopRequireDefault(require("./util/get-component-files")); var _optimizeHtmlWrites = require("./util/optimize-html-writes"); var _optimizeVdomCreate = require("./util/optimize-vdom-create"); var _taglib = _interopRequireWildcard(require("./taglib"));exports.optionalTaglibs = _taglib.optionalTaglibs;exports.taglibs = _taglib.default;function _interopRequireWildcard(e, t) {if ("function" == typeof WeakMap) var r = new WeakMap(),n = new WeakMap();return (_interopRequireWildcard = function (e, t) {if (!t && e && e.__esModule) return e;var o,i,f = { __proto__: null, default: e };if (null === e || "object" != typeof e && "function" != typeof e) return f;if (o = t ? n : r) {if (o.has(e)) return o.get(e);o.set(e, f);}for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]);return f;})(e, t);}const tagDiscoveryDirs = exports.tagDiscoveryDirs = ["components"]; const preferAPI = exports.preferAPI = "class"; const analyze = exports.analyze = { Program: { enter(program) { // Pre populate metadata for component files. const meta = program.hub.file.metadata.marko; (0, _getComponentFiles.default)(program); if (!meta.hasComponent && !meta.hasComponentBrowser) { meta.hasComponent = program. get("body"). some((child) => child.isMarkoClass()); } }, exit(program) { const { file } = program.hub; const meta = file.metadata.marko; const componentFiles = (0, _getComponentFiles.default)(program); const { styleFile, packageFile, componentBrowserFile } = componentFiles; if (packageFile) { meta.deps.unshift(`package: ${packageFile}`); } if (styleFile) { meta.deps.unshift(styleFile); } if (!meta.widgetBind) { if (meta.hasComponentBrowser) { meta.component = componentBrowserFile; } else if ( meta.hasComponent || meta.hasStatefulTagParams || meta.hasFunctionEventHandlers) { meta.component = file.opts.filename; } else if (meta.hasStringEventHandlers) { meta.implicitSplitComponent = true; meta.component = componentFiles.componentBrowserFile = "marko/src/runtime/helpers/empty-component.js"; meta.hasComponentBrowser = true; } } meta.component = meta.component && (0, _babelUtils.resolveRelativePath)(file, meta.component); meta.deps = meta.deps.map((filename) => typeof filename === "string" ? (0, _babelUtils.resolveRelativePath)(file, filename) : filename ); } }, MarkoTag(tag) { const { file } = tag.hub; const tagDef = (0, _babelUtils.getTagDef)(tag); // Check if tag uses stateful tag params. const meta = tag.hub.file.metadata.marko; let relativePath; if (tagDef) { if (tagDef.html && !tagDef.template && !tagDef.renderer) { if (tagDef.htmlType === "custom-element") { if (tagDef.parseOptions && tagDef.parseOptions.import) { // TODO: the taglib should be updated to support this as a top level option. meta.deps.push( (0, _path.resolve)( tagDef.dir, (0, _path.resolve)(tagDef.dir, tagDef.parseOptions.import) ) ); } } } else if (_compiler.types.isStringLiteral(tag.node.name)) { relativePath = resolveRelativeTagEntry(file, tagDef); } if (tagDef.translator && tagDef.translator.path) { if (!meta.watchFiles.includes(tagDef.translator.path)) { meta.watchFiles.push(tagDef.translator.path); } } } if (!relativePath && _compiler.types.isStringLiteral(tag.node.name)) { const tagName = tag.node.name.value; const binding = /^[A-Z][a-zA-Z0-9_$]*$/.test(tagName) && tag.scope.getBinding(tagName); if (binding && binding.kind === "module" && binding.identifier.loc) { const importSource = binding.path.parent.source.value; relativePath = (0, _babelUtils.resolveTagImport)(tag, importSource) || importSource; tag.node.extra = tag.node.extra || {}; tag.node.extra.tagNameImported = relativePath; } } if (relativePath) { tag.node.extra = tag.node.extra || {}; tag.node.extra.relativePath = relativePath; if (!meta.tags.includes(relativePath)) { meta.tags.push(relativePath); } const childFile = (0, _babelUtils.loadFileForTag)(tag); if (childFile?.ast.program.extra?.featureType === "tags") { tag.node.extra.featureType = "tags"; (file.path.node.extra ??= {}).needsCompat = true; } } if (!(meta.hasFunctionEventHandlers || meta.hasStringEventHandlers)) { for (const attr of tag.node.attributes) { if ( _compiler.types.isMarkoAttribute(attr) && attr.arguments && /^on[-A-Z]/.test(attr.name)) { if ( attr.arguments.length >= 1 && attr.arguments[0].type === "StringLiteral") { meta.hasStringEventHandlers = true; } else { meta.hasFunctionEventHandlers = true; } break; } } } if ( meta.hasStatefulTagParams || (0, _babelUtils.isNativeTag)(tag) || (0, _babelUtils.isMacroTag)(tag) || !tag.get("body").get("params").length) { return; } if ((0, _babelUtils.isDynamicTag)(tag)) { meta.hasStatefulTagParams = true; return; } let curTag = tag; while ((0, _babelUtils.isAttributeTag)(curTag)) { curTag = (0, _babelUtils.findParentTag)(curTag); } const tagFile = (0, _babelUtils.loadFileForTag)(curTag); const childMeta = tagFile && tagFile.metadata.marko; meta.hasStatefulTagParams = childMeta && ( childMeta.hasStatefulTagParams || childMeta.hasFunctionEventHandlers || childMeta.hasComponent && !childMeta.hasComponentBrowser); }, ImportDeclaration: { exit(path) { const source = path.get("source"); const tagEntry = (0, _babelUtils.resolveTagImport)(source, source.node.value); if (tagEntry) { const meta = path.hub.file.metadata.marko; source.node.value = tagEntry; if (!meta.tags.includes(tagEntry)) { meta.tags.push(tagEntry); } } } } }; const translate = exports.translate = { MarkoDocumentType: _documentType.default, MarkoDeclaration: _declaration.default, MarkoCDATA: _cdata.default, MarkoTag: _tag.default, MarkoText: _text.default, MarkoPlaceholder: _placeholder.default, MarkoScriptlet: _scriptlet.default, MarkoClass: _class.default, MarkoComment: _comment.default, ReferencedIdentifier(path) { if (path.node.name === "component" && !path.scope.hasBinding("component")) { path.replaceWith(path.hub.file._componentInstanceIdentifier); } }, Program: { enter(path) { const { hub: { file } } = path; if (file.markoOpts.output === "hydrate") { (0, _addDependencies.default)(file, true); return; } else if ( file.markoOpts.resolveVirtualDependency && file.markoOpts.output !== "html") { (0, _addDependencies.default)(file, false); } if (file.metadata.marko.moduleCode) { path. replaceWith( _compiler.types.program( (0, _babelUtils.parseStatements)(file, file.metadata.marko.moduleCode), undefined, file.markoOpts.modules === "cjs" ? "script" : "module" ) )[0]. skip(); return; } file._componentDefIdentifier = path.scope.generateUidIdentifier("componentDef"); file._componentInstanceIdentifier = path.scope.generateUidIdentifier("component"); // Pre-Analyze tree (0, _optimizeVdomCreate.analyzeStaticVDOM)(path); // Move non static content into the renderBody. const [renderBlock] = path.pushContainer("body", _compiler.types.blockStatement([])); path. get("body"). filter(isRenderContent). forEach((childPath) => { renderBlock.pushContainer("body", childPath.node); childPath.remove(); }); file._renderBlock = renderBlock; path.scope.crawl(); }, exit(path) { const { hub: { file } } = path; const { markoOpts, _inlineComponentClass } = file; const includeMetaInSource = markoOpts.meta !== false; const meta = file.metadata.marko; const { componentFile, componentBrowserFile } = (0, _getComponentFiles.default)(path); const isHTML = markoOpts.output === "html"; const renderBlock = file._renderBlock; const componentClass = componentFile && (0, _babelUtils.importDefault)( file, (0, _babelUtils.resolveRelativePath)(file, componentFile), "marko_component" ) || _inlineComponentClass || _compiler.types.objectExpression([]); const componentIdentifier = path.scope.generateUidIdentifier("marko_component"); const componentTypeIdentifier = path.scope.generateUidIdentifier( "marko_componentType" ); const templateIdentifier = path.scope.generateUidIdentifier("marko_template"); const rendererIdentifier = (0, _babelUtils.importDefault)( file, "marko/src/runtime/components/renderer.js", "marko_renderer" ); const templateRendererMember = _compiler.types.memberExpression( templateIdentifier, _compiler.types.identifier("_") ); const templateMetaMember = _compiler.types.memberExpression( templateIdentifier, _compiler.types.identifier("meta") ); if (markoOpts.writeVersionComment) { path.addComment( "leading", ` Compiled using marko@${_package.version} - DO NOT EDIT`, true ); } const runtimeTemplateIdentifier = path.scope.generateUidIdentifier("t"); path.unshiftContainer( "body", [ path.node.extra?.needsCompat && _compiler.types.importDeclaration( [], _compiler.types.stringLiteral( `marko/${markoOpts.optimize ? "dist" : "src"}/runtime/helpers/tags-compat/${ markoOpts.output === "html" ? "html" : "dom"}${ markoOpts.optimize ? "" : "-debug"}.${markoOpts.modules === "esm" ? "mjs" : "js"}` ) ), _compiler.types.importDeclaration( [_compiler.types.importSpecifier(runtimeTemplateIdentifier, _compiler.types.identifier("t"))], _compiler.types.stringLiteral( `marko/${markoOpts.optimize ? "dist" : "src"}/runtime/${ isHTML ? "html" : "vdom"}/${ markoOpts.hot ? "hot-reload.js" : "index.js"}` ) ), _compiler.types.variableDeclaration("const", [ _compiler.types.variableDeclarator( componentTypeIdentifier, _compiler.types.stringLiteral(meta.id) ), _compiler.types.variableDeclarator( templateIdentifier, _compiler.types.callExpression(runtimeTemplateIdentifier, [ componentTypeIdentifier] ) )] ), includeMetaInSource && _compiler.types.expressionStatement( _compiler.types.assignmentExpression( "=", _compiler.types.memberExpression(templateIdentifier, _compiler.types.identifier("path")), _compiler.types.identifier("__filename") ) ), _compiler.types.exportDefaultDeclaration(templateIdentifier)]. filter(Boolean) ); path.pushContainer( "body", [ !isHTML && _compiler.types.expressionStatement( _compiler.types.callExpression( (0, _babelUtils.importNamed)( file, "marko/src/runtime/components/registry.js", "r", "marko_registerComponent" ), [ componentTypeIdentifier, _compiler.types.arrowFunctionExpression( [], componentBrowserFile && !meta.implicitSplitComponent ? (0, _babelUtils.importDefault)( file, (0, _babelUtils.resolveRelativePath)(file, componentBrowserFile), "marko_split_component" ) : templateIdentifier )] ) ), _compiler.types.variableDeclaration("const", [ _compiler.types.variableDeclarator(componentIdentifier, componentClass)] )]. filter(Boolean) ); const templateRenderOptionsProps = [ _compiler.types.objectProperty(_compiler.types.identifier("t"), componentTypeIdentifier)]; if (!meta.component) { templateRenderOptionsProps.push( _compiler.types.objectProperty(_compiler.types.identifier("i"), _compiler.types.booleanLiteral(true)) ); } if (componentBrowserFile) { templateRenderOptionsProps.push( _compiler.types.objectProperty(_compiler.types.identifier("s"), _compiler.types.booleanLiteral(true)) ); } if (!markoOpts.optimize) { templateRenderOptionsProps.push( _compiler.types.objectProperty(_compiler.types.identifier("d"), _compiler.types.booleanLiteral(true)) ); } let rendererAssignment = _compiler.types.assignmentExpression( "=", templateRendererMember, _compiler.types.callExpression(rendererIdentifier, [ _compiler.types.functionExpression( null, [ _compiler.types.identifier("input"), _compiler.types.identifier("out"), file._componentDefIdentifier, file._componentInstanceIdentifier, _compiler.types.identifier("state"), _compiler.types.identifier("$global")], renderBlock.node ), _compiler.types.objectExpression(templateRenderOptionsProps), componentIdentifier] ) ); if (!isHTML && componentBrowserFile && !meta.implicitSplitComponent) { rendererAssignment = _compiler.types.assignmentExpression( "=", _compiler.types.memberExpression( (0, _babelUtils.importDefault)( file, (0, _babelUtils.resolveRelativePath)(file, componentBrowserFile), "marko_split_component" ), _compiler.types.identifier("renderer") ), rendererAssignment ); } path.pushContainer("body", _compiler.types.expressionStatement(rendererAssignment)); if (meta.implicitSplitComponent && isHTML) { renderBlock.unshiftContainer( "body", _compiler.types.expressionStatement( _compiler.types.callExpression( (0, _babelUtils.importDefault)( file, "marko/src/runtime/helpers/skip-serialize.js", "marko_skip_serialize" ), [_compiler.types.identifier("input")] ) ) ); } renderBlock.remove(); if (!isHTML) { path.pushContainer( "body", _compiler.types.expressionStatement( _compiler.types.assignmentExpression( "=", _compiler.types.memberExpression(templateIdentifier, _compiler.types.identifier("Component")), _compiler.types.callExpression( (0, _babelUtils.importDefault)( file, "marko/src/runtime/components/defineComponent.js", "marko_defineComponent" ), [componentIdentifier, templateRendererMember] ) ) ) ); } if (includeMetaInSource) { const metaObject = _compiler.types.objectExpression([ _compiler.types.objectProperty(_compiler.types.identifier("id"), componentTypeIdentifier)] ); if (meta.component) { metaObject.properties.push( _compiler.types.objectProperty( _compiler.types.identifier("component"), _compiler.types.stringLiteral(meta.component) ) ); } if (meta.deps.length) { metaObject.properties.push( _compiler.types.objectProperty( _compiler.types.identifier("deps"), (0, _babelUtils.parseExpression)(file, JSON.stringify(meta.deps)) ) ); } if (meta.tags.length) { metaObject.properties.push( _compiler.types.objectProperty( _compiler.types.identifier("tags"), _compiler.types.arrayExpression(meta.tags.map((tag) => _compiler.types.stringLiteral(tag))) ) ); } path.pushContainer( "body", _compiler.types.expressionStatement( _compiler.types.assignmentExpression("=", templateMetaMember, metaObject) ) ); } (0, _optimizeHtmlWrites.optimizeHTMLWrites)(path); } } }; function getRuntimeEntryFiles(output, optimize) { const base = `marko/${optimize ? "dist" : "src"}/`; return [ `${base}runtime/components/index.js`, `${base}runtime/components/defineComponent.js`, `${base}runtime/components/renderer.js`, `${base}runtime/components/registry.js`, `${base}runtime/components/attach-detach.js`, `${base}runtime/helpers/assign.js`, `${base}runtime/helpers/class-value.js`, `${base}runtime/helpers/of-fallback.js`, `${base}runtime/helpers/dynamic-tag.js`, `${base}runtime/helpers/attr-tag.js`, `${base}runtime/helpers/merge.js`, `${base}runtime/helpers/render-tag.js`, `${base}runtime/helpers/style-value.js`, `${base}runtime/helpers/to-string.js`, `${base}runtime/helpers/empty-component.js`, `${base}core-tags/components/preserve-tag.js`, ...(output === "html" ? [ `${base}runtime/html/index.js`, `${base}runtime/html/hot-reload.js`, `${base}runtime/html/helpers/attr.js`, `${base}runtime/html/helpers/attrs.js`, `${base}runtime/html/helpers/class-attr.js`, `${base}runtime/html/helpers/data-marko.js`, `${base}runtime/html/helpers/escape-script-placeholder.js`, `${base}runtime/html/helpers/escape-style-placeholder.js`, `${base}runtime/html/helpers/escape-xml.js`, `${base}runtime/html/helpers/merge-attrs.js`, `${base}runtime/html/helpers/props-script.js`, `${base}runtime/html/helpers/style-attr.js`, `${base}core-tags/components/init-components-tag.js`, `${base}core-tags/components/preferred-script-location-tag.js`, `${base}core-tags/core/__flush_here_and_after__.js`, `${base}core-tags/core/await/renderer.js`, `${base}core-tags/core/await/reorderer-renderer.js`, `${base}runtime/helpers/skip-serialize.js`, `${base}runtime/helpers/tags-compat/html${optimize ? "" : "-debug"}.mjs`] : [ `${base}runtime/vdom/index.js`, `${base}runtime/vdom/hot-reload.js`, `${base}runtime/vdom/helpers/attrs.js`, `${base}runtime/vdom/helpers/const-element.js`, `${base}runtime/helpers/tags-compat/dom${optimize ? "" : "-debug"}.mjs`])]; } function isRenderContent({ node }) { return /^Marko/.test(node.type) && !node.static; } function resolveRelativeTagEntry(file, tagDef) { // TODO: support transform and other entries. const entry = tagDef.template || tagDef.renderer; return entry && (0, _babelUtils.resolveRelativePath)(file, entry); }