marko
Version:
UI Components + streaming, async, high performance, HTML templating for Node.js and the browser.
372 lines (326 loc) • 11.3 kB
JavaScript
;var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");exports.__esModule = true;exports.entryBuilder = exports.default = void 0;var _compiler = require("@marko/compiler");
var _babelUtils = require("@marko/compiler/babel-utils");
var _modules = _interopRequireDefault(require("@marko/compiler/modules"));
var _magicString = _interopRequireDefault(require("magic-string"));
var _path = _interopRequireDefault(require("path"));
const kEntryState = Symbol();
const lassoDepPrefix = "package: ";var _default =
(entryFile, isHydrate) => {
const program = entryFile.path;
const programNode = program.node;
if (!isHydrate) {
const body = [];
addBrowserImports(new Set(), undefined, body, entryFile, entryFile);
if (body.length) {
programNode.body = body.concat(programNode.body);
}
return;
}
const visitedFiles = new Set([
(0, _babelUtils.resolveRelativePath)(entryFile, entryFile.opts.filename)]
);
entryBuilder.visit(entryFile, entryFile, function visitChild(resolved) {
if (!visitedFiles.has(resolved)) {
visitedFiles.add(resolved);
const file = (0, _babelUtils.loadFileForImport)(entryFile, resolved);
if (file) {
entryBuilder.visit(file, entryFile, (id) =>
visitChild(resolveRelativeToEntry(entryFile, file, id))
);
}
}
});
programNode.body = entryBuilder.build(entryFile);
program.skip();
};exports.default = _default;
const entryBuilder = exports.entryBuilder = {
build(entryFile, exportInit) {
const state = entryFile[kEntryState];
if (!state) {
throw entryFile.path.buildCodeFrameError(
"Unable to build hydrate code, no files were visited before finalizing the build"
);
}
const { markoOpts } = entryFile;
const entryMarkoMeta = entryFile.metadata.marko;
const { body } = state;
let didExportInit = false;
entryMarkoMeta.watchFiles = [...state.watchFiles];
entryMarkoMeta.deps = [...state.lassoDeps];
if (state.hasComponents) {
const initId = _compiler.types.identifier("init");
const markoComponentsImport = importPath(
(0, _babelUtils.resolveRelativePath)(entryFile, "marko/src/runtime/components/index.js")
);
if (state.splitComponentIndex) {
markoComponentsImport.specifiers.push(
_compiler.types.importSpecifier(_compiler.types.identifier("register"), _compiler.types.identifier("register"))
);
}
body.unshift(markoComponentsImport);
if (markoOpts.hydrateInit || exportInit) {
const initExpression = _compiler.types.callExpression(
initId,
markoOpts.runtimeId ? [_compiler.types.stringLiteral(markoOpts.runtimeId)] : []
);
markoComponentsImport.specifiers.push(
_compiler.types.importSpecifier(initId, initId)
);
body.push(
exportInit ?
_compiler.types.exportDefaultDeclaration(
_compiler.types.arrowFunctionExpression([], initExpression)
) :
_compiler.types.expressionStatement(initExpression)
);
}
} else if (exportInit) {
body.push(
_compiler.types.exportDefaultDeclaration(
_compiler.types.arrowFunctionExpression([], _compiler.types.blockStatement([]))
)
);
}
return body;
},
visit(file, entryFile, visitChild) {
const fileMeta = file.metadata.marko;
const fileName = file.opts.filename;
const state = entryFile[kEntryState] ||= {
shouldIncludeImport: toTestFn(file.markoOpts.hydrateIncludeImports),
watchFiles: new Set(),
imports: new Set(),
lassoDeps: new Set(),
hasComponents: false,
splitComponentIndex: 0,
body: []
};
const { watchFiles, imports, lassoDeps, body } = state;
if (fileMeta.component) {
state.hasComponents = true;
if (_path.default.basename(fileMeta.component) === _path.default.basename(fileName)) {
// Stateful component.
addImport(imports, body, (0, _babelUtils.resolveRelativePath)(entryFile, fileName));
return;
}
}
watchFiles.add(fileName);
for (const watchFile of fileMeta.watchFiles) {
watchFiles.add(watchFile);
}
addBrowserImports(imports, lassoDeps, body, file, entryFile);
for (const child of file.path.node.body) {
if (_compiler.types.isImportDeclaration(child)) {
const { value } = child.source;
if (state.shouldIncludeImport(value)) {
addImport(
imports,
body,
resolveRelativeToEntry(entryFile, file, value)
);
}
} else if (
_compiler.types.isMarkoScriptlet(child) &&
child.static &&
child.target !== "server")
{
for (const stmt of child.body) {
if (_compiler.types.isImportDeclaration(stmt)) {
const { value } = stmt.source;
if (child.target === "client" || state.shouldIncludeImport(value)) {
addImport(
imports,
body,
resolveRelativeToEntry(entryFile, file, value)
);
}
}
}
}
}
for (const tag of fileMeta.tags) {
if (tag.endsWith(".marko")) {
visitChild(tag);
} else if (/^@lasso\/marko-taglib\//.test(tag)) {
state.hasComponents = true;
} else {
const importedTemplates = tryGetTemplateImports(file, tag);
if (importedTemplates) {
for (const templateFile of importedTemplates) {
visitChild(templateFile);
}
}
}
}
if (fileMeta.component) {
// Split component
const splitComponentId = _compiler.types.identifier(
`component_${state.splitComponentIndex++}`
);
const splitComponentImport = importPath(
resolveRelativeToEntry(entryFile, file, fileMeta.component)
);
splitComponentImport.specifiers.push(
_compiler.types.importDefaultSpecifier(splitComponentId)
);
body.push(
splitComponentImport,
_compiler.types.expressionStatement(
_compiler.types.callExpression(_compiler.types.identifier("register"), [
_compiler.types.stringLiteral(fileMeta.id),
splitComponentId]
)
)
);
}
}
};
function addBrowserImports(seenImports, lassoDeps, body, file, entryFile) {
const { filename, sourceMaps } = file.opts;
let s;
for (let dep of file.metadata.marko.deps) {
if (typeof dep !== "string") {
const { virtualPath } = dep;
let { code } = dep;
let map;
if (sourceMaps && dep.startPos !== undefined) {
s = s || new _magicString.default(file.code, { filename });
map = s.snip(dep.startPos, dep.endPos).generateMap({
source: filename,
includeContent: true
});
if (sourceMaps === "inline" || sourceMaps === "both") {
code += dep.style ?
`\n/*# sourceMappingURL=${map.toUrl()}*/` :
`\n//# sourceMappingURL=${map.toUrl()}`;
if (sourceMaps === "inline") {
map = undefined;
}
}
}
dep = file.markoOpts.resolveVirtualDependency(filename, {
map,
code,
virtualPath
});
if (!dep) {
continue;
}
} else if (isLassoDep(dep)) {
if (lassoDeps) {
lassoDeps.add(
resolveLassoManifestDepRelativeToEntry(entryFile, file, dep)
);
}
continue;
}
addImport(seenImports, body, resolveRelativeToEntry(entryFile, file, dep));
}
}
function addImport(seenImports, body, resolved) {
if (seenImports.has(resolved)) return;
seenImports.add(resolved);
body.push(importPath(resolved));
}
function resolveRelativeToEntry(entryFile, file, req) {
return file === entryFile ?
(0, _babelUtils.resolveRelativePath)(file, req) :
(0, _babelUtils.resolveRelativePath)(
entryFile,
req[0] === "." ? _path.default.join(file.opts.filename, "..", req) : req
);
}
function importPath(path) {
return _compiler.types.importDeclaration([], _compiler.types.stringLiteral(path));
}
function tryGetTemplateImports(file, rendererRelativePath) {
const resolvedRendererPath = tryResolveFrom(
rendererRelativePath,
file.opts.filename
);
let templateImports;
if (resolvedRendererPath) {
if (resolvedRendererPath.endsWith(".marko")) {
addTemplateImport(resolvedRendererPath);
} else if (resolvedRendererPath.endsWith(".js")) {
try {
for (const statement of (0, _babelUtils.parseStatements)(
file,
file.markoOpts.fileSystem.readFileSync(resolvedRendererPath, "utf-8")
)) {
if (statement.type === "ImportDeclaration") {
addTemplateImport(statement.source.value);
} else {
_compiler.types.traverseFast(statement, (node) => {
if (
node.type === "CallExpression" && (
node.callee.name === "require" ||
node.callee.type === "MemberExpression" &&
node.callee.object.type === "Identifier" &&
node.callee.object.name === "require" &&
node.callee.property.type === "Identifier" &&
node.callee.property.name === "resolve") &&
node.arguments.length === 1 &&
node.arguments[0].type === "StringLiteral")
{
addTemplateImport(node.arguments[0].value);
}
});
}
}
} catch {
// Ignore
}}
}
return templateImports;
function addTemplateImport(request) {
if (request.endsWith(".marko")) {
const resolvedTemplatePath = tryResolveFrom(
request,
resolvedRendererPath
);
if (resolvedTemplatePath) {
if (templateImports) {
templateImports.push(resolvedTemplatePath);
} else {
templateImports = [resolvedTemplatePath];
}
}
}
}
}
function tryResolveFrom(request, from) {
return request[0] === "." ?
_path.default.resolve(from, "..", request) :
/^(?:[/\\]|[a-zA-Z]:)/.test(request) ?
request :
_modules.default.tryResolve(request, _path.default.dirname(from));
}
function toTestFn(val) {
if (typeof val === "function") {
return val;
}
return val.test.bind(val);
}
function isLassoDep(dep) {
return dep.startsWith(lassoDepPrefix);
}
/**
* Lasso manifest deps in `lasso-marko` do not support node module resolution.
* eg `package: @ebay/ebayui-core/browser.json` will not resolve.
*
* This resolution returns a direct posix relative path.
*/
function resolveLassoManifestDepRelativeToEntry(entryFile, targetFile, dep) {
const entryFileName = entryFile.opts.filename;
const targetFileName = targetFile.opts.filename;
if (entryFileName === targetFileName) return dep;
const resolved = toPosix(
_path.default.relative(
_path.default.dirname(entryFileName),
_path.default.join(targetFileName, "..", dep.slice(lassoDepPrefix.length))
)
);
return lassoDepPrefix + (resolved[0] === "." ? resolved : `./${resolved}`);
}
const toPosix = _path.default.sep === "/" ? (v) => v : (v) => v.replace(/\\/g, "/");