UNPKG

@marko/compiler

Version:
414 lines (361 loc) 12.6 kB
"use strict";var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");exports.__esModule = true;exports.default = void 0;var _traverse = _interopRequireDefault(require("@babel/traverse")); var _crypto = require("crypto"); var _path = _interopRequireDefault(require("path")); var t = _interopRequireWildcard(require("../babel-types")); var _diagnostics = require("../babel-utils/diagnostics"); var _getFile = require("../babel-utils/get-file"); var _tags = require("../babel-utils/tags"); var _taglib = require("../taglib"); var _config = _interopRequireDefault(require("../taglib/config")); var _buildCodeFrame = require("../util/build-code-frame"); var _mergeErrors = _interopRequireDefault(require("../util/merge-errors")); var _shouldOptimize = _interopRequireDefault(require("../util/should-optimize")); var _tryLoadTranslator = _interopRequireDefault(require("../util/try-load-translator")); var _file = require("./file"); var _parser = require("./parser"); var _migrate = require("./plugins/migrate"); var _transform = require("./plugins/transform");function _getRequireWildcardCache(e) {if ("function" != typeof WeakMap) return null;var r = new WeakMap(),t = new WeakMap();return (_getRequireWildcardCache = function (e) {return e ? t : r;})(e);}function _interopRequireWildcard(e, r) {if (!r && e && e.__esModule) return e;if (null === e || "object" != typeof e && "function" != typeof e) return { default: e };var t = _getRequireWildcardCache(r);if (t && t.has(e)) return t.get(e);var n = { __proto__: null },a = Object.defineProperty && Object.getOwnPropertyDescriptor;for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) {var i = a ? Object.getOwnPropertyDescriptor(e, u) : null;i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u];}return n.default = e, t && t.set(e, n), n;} const SOURCE_FILES = new WeakMap();var _default = (api, markoOpts) => { api.assertVersion(7); const translator = markoOpts.translator = (0, _tryLoadTranslator.default)( markoOpts.translator ); markoOpts.output = markoOpts.output || "html"; if (markoOpts.optimize === undefined) { api.cache.using(_shouldOptimize.default); markoOpts.optimize = (0, _shouldOptimize.default)(); } if (!translator || !translator.translate) { throw new Error( "@marko/compiler: translator must provide a translate visitor object" ); } if ( markoOpts.output === "hydrate" && typeof markoOpts.resolveVirtualDependency !== "function") { throw new Error( `@marko/compiler: the "resolveVirtualDependency" option must be supplied when output is "hydrate".` ); } let curOpts; return { name: "marko", manipulateOptions(opts) { // We need to allow these for now since we are not parsing the entire file // These types of syntax errors will be picked up by the bundler / runtime anyways. opts.parserOpts.allowAwaitOutsideFunction = opts.parserOpts.allowImportExportEverywhere = opts.parserOpts.allowReturnOutsideFunction = opts.parserOpts.allowSuperOutsideMethod = opts.parserOpts.allowUndeclaredExports = opts.parserOpts.allowNewTargetOutsideFunction = true; curOpts = opts; }, parserOverride(code) { const file = getMarkoFile(code, curOpts, markoOpts); const finalAst = t.cloneNode(file.ast, true); SOURCE_FILES.set(finalAst, file); return finalAst; }, pre(file) { const { buildError: prevBuildError } = file.hub; const { buildCodeFrameError: prevCodeFrameError } = file; const prevFS = _config.default.fs; const prevFile = (0, _getFile.getFileInternal)(); _config.default.fs = markoOpts.fileSystem; (0, _getFile.setFileInternal)(file); curOpts = undefined; try { const { ast, metadata } = file; const sourceFile = SOURCE_FILES.get(ast); metadata.marko = shallowClone(sourceFile.metadata.marko); if (isMarkoOutput(markoOpts.output)) { finalizeMeta(metadata.marko); return; } const taglibLookup = sourceFile.___taglibLookup; const rootTranslators = []; file.buildCodeFrameError = _file.MarkoFile.prototype.buildCodeFrameError; file.hub.buildError = file.buildCodeFrameError.bind(file); file.markoOpts = markoOpts; file.___taglibLookup = taglibLookup; file.___getMarkoFile = getMarkoFile; if (markoOpts.output !== "hydrate") { for (const id in taglibLookup.taglibsById) { addPlugin( metadata.marko, rootTranslators, taglibLookup.taglibsById[id].translator ); } } rootTranslators.push(translator.translate); file.___compileStage = "translate"; traverseAll(file, rootTranslators); finalizeMeta(metadata.marko); file.path.scope.crawl(); // Ensure all scopes are accurate for subsequent babel plugins } finally { _config.default.fs = prevFS; (0, _getFile.setFileInternal)(prevFile); file.buildCodeFrameError = prevCodeFrameError; file.hub.buildError = prevBuildError; file.markoOpts = file.___taglibLookup = file.___getMarkoFile = undefined; } }, visitor: markoOpts.stripTypes && isMarkoOutput(markoOpts.output) ? { MarkoClass(path) { // We replace the MarkoClass with a regular class declaration so babel can strip it's types. path.replaceWith( t.classDeclaration(t.identifier(""), null, path.node.body) ); }, ExportNamedDeclaration: { exit(path) { const { node } = path; // The babel typescript plugin will add an empty export declaration // if there are no other imports/exports in the file. // This is not needed for Marko file outputs since there is always // a default export. if (!(node.declaration || node.specifiers.length)) path.remove(); } } } : undefined }; };exports.default = _default; function getMarkoFile(code, fileOpts, markoOpts) { const { translator } = markoOpts; let compileCache = markoOpts.cache.get(translator); if (!compileCache) { markoOpts.cache.set(translator, compileCache = new Map()); } const { filename } = fileOpts; const isSource = markoOpts.output === "source"; const isMigrate = markoOpts.output === "migrate"; const canCache = !(isSource || isMigrate); const id = (0, _tags.getTemplateId)(markoOpts, filename); const contentHash = canCache && (0, _crypto.createHash)("MD5").update(code).digest("hex"); const cacheKey = canCache && (0, _crypto.createHash)("MD5").update(id).digest("hex"); let cached = canCache && compileCache.get(cacheKey); if (cached) { if (cached.contentHash !== contentHash) { // File content changed, invalidate the cache. cached = undefined; } else { for (const watchFile of cached.file.metadata.marko.watchFiles) { let mtime = Infinity; try { mtime = markoOpts.fileSystem.statSync(watchFile).mtime; } catch { // ignore } if (mtime > cached.time) { // Some dependency changed, invalidate the cache. cached = undefined; break; } } } } if (cached) { return cached.file; } const prevFs = _config.default.fs; const prevFile = (0, _getFile.getFileInternal)(); _config.default.fs = markoOpts.fileSystem; try { const taglibLookup = (0, _taglib.buildLookup)(_path.default.dirname(filename), translator); const file = (0, _getFile.setFileInternal)( new _file.MarkoFile(fileOpts, { code, ast: { type: "File", program: { type: "Program", sourceType: "module", body: [], directives: [], params: [t.identifier("input")] } } }) ); const meta = file.metadata.marko = { id, deps: [], tags: [], watchFiles: [], diagnostics: [] }; file.markoOpts = markoOpts; file.___taglibLookup = taglibLookup; file.___getMarkoFile = getMarkoFile; file.___compileStage = "parse"; (0, _parser.parseMarko)(file); if (isSource) { return file; } file.path.scope.crawl(); // Initialize bindings. const rootMigrators = []; for (const id in taglibLookup.taglibsById) { for (const migrator of taglibLookup.taglibsById[id].migrators) { addPlugin(meta, rootMigrators, migrator); } } rootMigrators.push(_migrate.visitor); file.___compileStage = "migrate"; traverseAll(file, rootMigrators); if (file.___hasParseErrors) { if (markoOpts.errorRecovery) { t.traverseFast(file.path.node, (node) => { if (node.type === "MarkoParseError") { (0, _diagnostics.diagnosticError)(file.path, { label: node.label, loc: node.errorLoc || node.loc }); } }); } else { let errors = []; t.traverseFast(file.path.node, (node) => { if (node.type === "MarkoParseError") { errors.push( (0, _buildCodeFrame.buildCodeFrameError)( file.opts.filename, file.code, node.errorLoc || node.loc, node.label ) ); } }); (0, _mergeErrors.default)(errors); } } if (isMigrate) { return file; } const rootTransformers = []; for (const id in taglibLookup.taglibsById) { for (const transformer of taglibLookup.taglibsById[id].transformers) { addPlugin(meta, rootTransformers, transformer); } } rootTransformers.push(_transform.visitor); if (translator.transform) { rootTransformers.push(translator.transform); } file.___compileStage = "transform"; traverseAll(file, rootTransformers); for (const taglibId in taglibLookup.taglibsById) { const { filePath } = taglibLookup.taglibsById[taglibId]; if ( filePath[filePath.length - 5] === "." && filePath.endsWith("marko.json")) { meta.watchFiles.push(filePath); } } compileCache.set(cacheKey, { time: Date.now(), file, contentHash }); if (translator.analyze) { try { file.___compileStage = "analyze"; traverseAll(file, translator.analyze); } catch (e) { compileCache.delete(cacheKey); throw e; } } return file; } finally { _config.default.fs = prevFs; (0, _getFile.setFileInternal)(prevFile); } } function shallowClone(data) { const clone = {}; for (const key in data) { const v = data[key]; if (v !== undefined) { if (v === null || typeof v !== "object") { clone[key] = v; } else { const Ctor = v.constructor; switch (Ctor) { case Array: clone[key] = [...v]; break; case Object: case null: clone[key] = { ...v }; break; case Map: case Set: case Date: case RegExp: clone[key] = new Ctor(v); break; default: throw new Error(`Unsupported metadata type of ${Ctor.name}`); } } } } return clone; } function mergeVisitors(all) { if (Array.isArray(all)) { if (all.length === 1) { all = all[0]; } else { return _traverse.default.visitors.merge(all); } } return _traverse.default.visitors.explode(all); } function traverseAll(file, visitors) { const program = file.path; (0, _traverse.default)( program.node, mergeVisitors(visitors), program.scope, program.state = {}, program, true ); } function addPlugin(meta, arr, plugin) { if (plugin) { const hook = plugin.hook.default || plugin.hook; if (plugin.path) { meta.watchFiles.push(plugin.path); } if (Array.isArray(hook)) { arr.push(...hook); } else { arr.push(hook); } } } function isMarkoOutput(output) { return output === "source" || output === "migrate"; } function finalizeMeta(meta) { meta.watchFiles = [...new Set(meta.watchFiles)]; if (meta.analyzedTags) { meta.analyzedTags = [...meta.analyzedTags]; } }