mirror of
https://github.com/marko-js/marko.git
synced 2025-12-08 19:26:05 +00:00
442 lines
9.8 KiB
JavaScript
442 lines
9.8 KiB
JavaScript
// TODO: Should deprecate and move into marko/cli
|
|
|
|
var fs = require("fs");
|
|
var nodePath = require("path");
|
|
var cwd = process.cwd();
|
|
var appModulePath = require("app-module-path");
|
|
var args = require("argly")
|
|
.createParser({
|
|
"--help": {
|
|
type: "boolean",
|
|
description: "Show this help message",
|
|
},
|
|
"--files --file -f *": {
|
|
type: "string[]",
|
|
description: "A set of directories or files to compile",
|
|
},
|
|
"--ignore -i": {
|
|
type: "string[]",
|
|
description: 'An ignore rule (default: --ignore "/node_modules" ".*")',
|
|
},
|
|
"--clean -c": {
|
|
type: "boolean",
|
|
description: "Clean all of the *.marko.js files",
|
|
},
|
|
"--force": {
|
|
type: "boolean",
|
|
description: "Force template recompilation even if unchanged",
|
|
},
|
|
"--paths -p": {
|
|
type: "string[]",
|
|
description:
|
|
"Additional directories to add to the Node.js module search path",
|
|
},
|
|
"--quiet -q": {
|
|
type: "boolean",
|
|
description: "Only print warnings and errors",
|
|
},
|
|
"--migrate -m": {
|
|
type: "boolean",
|
|
description:
|
|
"Run any migrations that exist for the provided template and write changes to disk",
|
|
},
|
|
"--strip-types -t": {
|
|
type: "boolean",
|
|
description: "Strip all type information from the compiled template",
|
|
},
|
|
"--browser -b": {
|
|
type: "boolean",
|
|
description: "Browser output",
|
|
},
|
|
"--source-maps -s": {
|
|
type: "string",
|
|
description:
|
|
"Output a sourcemap beside the compiled file. (use --source-maps inline for an inline source map)",
|
|
},
|
|
"--version -v": {
|
|
type: "boolean",
|
|
description: "Print markoc and marko compiler versions to the console",
|
|
},
|
|
})
|
|
.usage("Usage: $0 <pattern> [options]")
|
|
.example("Compile a single template", "$0 template.marko")
|
|
.example("Compile all templates in the current directory", "$0 .")
|
|
.example("Compile multiple templates", "$0 template.marko src/ foo/")
|
|
.example(
|
|
"Delete all *.marko.js files in the current directory",
|
|
"$0 . --clean",
|
|
)
|
|
.validate(function (result) {
|
|
if (result.help) {
|
|
this.printUsage();
|
|
process.exit(0);
|
|
} else if (result.version) {
|
|
console.log("markoc@" + markocPkgVersion);
|
|
|
|
if (markoPkgVersion) {
|
|
console.log("marko@" + markoPkgVersion);
|
|
}
|
|
|
|
process.exit(0);
|
|
} else if (!result.files || result.files.length === 0) {
|
|
this.printUsage();
|
|
process.exit(1);
|
|
}
|
|
})
|
|
.onError(function (err) {
|
|
this.printUsage();
|
|
|
|
if (err) {
|
|
console.log();
|
|
console.log(err);
|
|
}
|
|
|
|
process.exit(1);
|
|
})
|
|
.parse();
|
|
var Minimatch = require("minimatch").Minimatch;
|
|
var resolveFrom = require("resolve-from").silent;
|
|
|
|
// Try to use the Marko compiler installed with the project
|
|
var markoCompilerPath = resolveFrom(process.cwd(), "marko/compiler");
|
|
const markocPkgVersion = require("../package.json").version;
|
|
|
|
var markoPkgPath = resolveFrom(process.cwd(), "marko/package.json");
|
|
var markoPkgVersion = markoPkgPath && require(markoPkgPath).version;
|
|
|
|
var markoCompiler = markoCompilerPath
|
|
? require(markoCompilerPath)
|
|
: require("../compiler");
|
|
|
|
var mmOptions = {
|
|
matchBase: true,
|
|
dot: true,
|
|
flipNegate: true,
|
|
};
|
|
|
|
function relPath(path) {
|
|
if (path.startsWith(cwd)) {
|
|
return path.substring(cwd.length + 1);
|
|
}
|
|
}
|
|
|
|
var output = "html";
|
|
|
|
var isForBrowser = false;
|
|
|
|
if (args.browser) {
|
|
output = "dom";
|
|
isForBrowser = true;
|
|
} else if (args.migrate) {
|
|
output = "migrate";
|
|
}
|
|
|
|
var compileOptions = {
|
|
output: output,
|
|
browser: isForBrowser,
|
|
sourceOnly: false,
|
|
stripTypes: args.stripTypes,
|
|
sourceMaps: args.sourceMaps || false,
|
|
compilerType: "markoc",
|
|
compilerVersion: markoPkgVersion || markocPkgVersion,
|
|
};
|
|
|
|
var force = args.force;
|
|
if (force) {
|
|
markoCompiler.defaultOptions.checkUpToDate = false;
|
|
}
|
|
|
|
var paths = args.paths;
|
|
if (paths && paths.length) {
|
|
paths.forEach(function (path) {
|
|
appModulePath.addPath(nodePath.resolve(cwd, path));
|
|
});
|
|
}
|
|
|
|
var ignoreRules = args.ignore;
|
|
|
|
if (!ignoreRules) {
|
|
ignoreRules = ["/node_modules", ".*"];
|
|
}
|
|
|
|
ignoreRules = ignoreRules.filter(function (s) {
|
|
s = s.trim();
|
|
return s && !s.match(/^#/);
|
|
});
|
|
|
|
ignoreRules = ignoreRules.map(function (pattern) {
|
|
return new Minimatch(pattern, mmOptions);
|
|
});
|
|
|
|
function isIgnored(path, dir, stat) {
|
|
if (path.startsWith(dir)) {
|
|
path = path.substring(dir.length);
|
|
}
|
|
|
|
path = path.replace(/\\/g, "/");
|
|
|
|
var ignore = false;
|
|
var ignoreRulesLength = ignoreRules.length;
|
|
for (var i = 0; i < ignoreRulesLength; i++) {
|
|
var rule = ignoreRules[i];
|
|
|
|
var match = rule.match(path);
|
|
|
|
if (!match && stat && stat.isDirectory()) {
|
|
try {
|
|
stat = fs.statSync(path);
|
|
} catch (e) {
|
|
/* ignore error */
|
|
}
|
|
|
|
if (stat && stat.isDirectory()) {
|
|
match = rule.match(path + "/");
|
|
}
|
|
}
|
|
|
|
if (match) {
|
|
if (rule.negate) {
|
|
ignore = false;
|
|
} else {
|
|
ignore = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ignore;
|
|
}
|
|
|
|
function walk(files, options, done) {
|
|
if (!files || files.length === 0) {
|
|
done("No files provided");
|
|
}
|
|
|
|
var pending = 0;
|
|
|
|
if (!Array.isArray(files)) {
|
|
files = [files];
|
|
}
|
|
|
|
var fileCallback = options.file;
|
|
var context = {
|
|
errors: [],
|
|
beginAsync: function () {
|
|
pending++;
|
|
},
|
|
endAsync: function (err) {
|
|
if (err) {
|
|
this.errors.push(err);
|
|
}
|
|
|
|
pending--;
|
|
|
|
if (pending === 0) {
|
|
if (this.errors.length) {
|
|
done(this.errors);
|
|
} else {
|
|
done(null);
|
|
}
|
|
}
|
|
},
|
|
};
|
|
|
|
function doWalk(dir) {
|
|
context.beginAsync();
|
|
fs.readdir(dir, function (err, list) {
|
|
if (err) {
|
|
return context.endAsync(err);
|
|
}
|
|
|
|
if (list.length) {
|
|
list.forEach(function (basename) {
|
|
var file = nodePath.join(dir, basename);
|
|
|
|
context.beginAsync();
|
|
fs.stat(file, function (err, stat) {
|
|
if (err) {
|
|
return context.endAsync(err);
|
|
}
|
|
|
|
if (!isIgnored(file, dir, stat)) {
|
|
if (stat && stat.isDirectory()) {
|
|
doWalk(file);
|
|
} else {
|
|
fileCallback(file, context);
|
|
}
|
|
}
|
|
|
|
context.endAsync();
|
|
});
|
|
});
|
|
}
|
|
|
|
context.endAsync();
|
|
});
|
|
}
|
|
|
|
for (var i = 0; i < files.length; i++) {
|
|
var file = nodePath.resolve(cwd, files[i]);
|
|
|
|
var stat = fs.statSync(file);
|
|
|
|
if (stat.isDirectory()) {
|
|
doWalk(file);
|
|
} else {
|
|
fileCallback(file, context);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (args.clean) {
|
|
var deleteCount = 0;
|
|
|
|
walk(
|
|
args.files,
|
|
{
|
|
file: function (file, context) {
|
|
var basename = nodePath.basename(file);
|
|
|
|
if (
|
|
basename.endsWith(".marko.js") ||
|
|
basename.endsWith(".marko.html") ||
|
|
basename.endsWith(".marko.xml.js")
|
|
) {
|
|
context.beginAsync();
|
|
fs.unlink(file, function (err) {
|
|
if (err) {
|
|
return context.endAsync(err);
|
|
}
|
|
deleteCount++;
|
|
console.log("Deleted: " + file);
|
|
context.endAsync();
|
|
});
|
|
}
|
|
},
|
|
},
|
|
function () {
|
|
if (deleteCount === 0) {
|
|
console.log("No *.marko.js files were found. Already clean.");
|
|
} else {
|
|
console.log("Deleted " + deleteCount + " file(s)");
|
|
}
|
|
},
|
|
);
|
|
} else {
|
|
var found = {};
|
|
var compileCount = 0;
|
|
var failed = [];
|
|
|
|
var compile = function (path, context) {
|
|
if (found[path]) {
|
|
return;
|
|
}
|
|
|
|
found[path] = true;
|
|
var outPath = args.migrate ? path : path + ".js";
|
|
|
|
if (!args.quiet)
|
|
console.log(
|
|
"Compiling:\n Input: " +
|
|
relPath(path) +
|
|
"\n Output: " +
|
|
relPath(outPath) +
|
|
"\n",
|
|
);
|
|
|
|
context.beginAsync();
|
|
|
|
markoCompiler.compileFile(path, compileOptions, function (err, result) {
|
|
if (err) {
|
|
failed.push(
|
|
'Failed to compile "' +
|
|
relPath(path) +
|
|
'". Error: ' +
|
|
(err.stack || err),
|
|
);
|
|
context.endAsync(err);
|
|
return;
|
|
}
|
|
|
|
var src = result.code;
|
|
context.beginAsync();
|
|
fs.writeFile(outPath, src, "utf8", function (err) {
|
|
if (err) {
|
|
failed.push(
|
|
'Failed to write "' + path + '". Error: ' + (err.stack || err),
|
|
);
|
|
context.endAsync(err);
|
|
return;
|
|
}
|
|
|
|
if (result.map) {
|
|
fs.writeFile(
|
|
outPath + ".map",
|
|
JSON.stringify(result.map),
|
|
"utf-8",
|
|
function (err) {
|
|
if (err) {
|
|
failed.push(
|
|
'Failed to write sourcemap"' +
|
|
path +
|
|
'". Error: ' +
|
|
(err.stack || err),
|
|
);
|
|
context.endAsync(err);
|
|
return;
|
|
}
|
|
|
|
compileCount++;
|
|
context.endAsync();
|
|
},
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
compileCount++;
|
|
context.endAsync();
|
|
});
|
|
|
|
context.endAsync();
|
|
});
|
|
};
|
|
|
|
if (args.files && args.files.length) {
|
|
walk(
|
|
args.files,
|
|
{
|
|
file: function (file, context) {
|
|
var basename = nodePath.basename(file);
|
|
|
|
if (
|
|
basename.endsWith(".marko") ||
|
|
basename.endsWith(".marko.html") ||
|
|
basename.endsWith(".marko.xml")
|
|
) {
|
|
compile(file, context);
|
|
}
|
|
},
|
|
},
|
|
function (err) {
|
|
if (err) {
|
|
if (failed.length) {
|
|
console.error(
|
|
"The following errors occurred:\n- " + failed.join("\n- "),
|
|
);
|
|
} else {
|
|
console.error(err);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (compileCount === 0) {
|
|
console.log("No templates found");
|
|
} else {
|
|
console.log("Compiled " + compileCount + " templates(s)");
|
|
}
|
|
},
|
|
);
|
|
}
|
|
}
|