marko
Version:
UI Components + streaming, async, high performance, HTML templating for Node.js and the browser.
612 lines (535 loc) • 21.1 kB
JavaScript
;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);
}