@stencil/core
Version:
A Compiler for Web Components and Progressive Web Apps
620 lines (594 loc) • 20.4 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
var utils = require('../utils');
function getConfigFilePath(process, sys, configArg) {
if (configArg) {
if (!sys.path.isAbsolute(configArg)) {
// passed in a custom stencil config location
// but it's relative, so prefix the cwd
return utils.normalizePath(sys.path.join(process.cwd(), configArg));
}
// config path already an absolute path, we're good here
return utils.normalizePath(configArg);
}
// nothing was passed in, use the current working directory
return utils.normalizePath(process.cwd());
}
function parseFlags(process) {
const flags = {
task: null,
args: [],
knownArgs: [],
unknownArgs: null
};
// cmd line has more priority over npm scripts cmd
flags.args = process.argv.slice(2);
if (flags.args.length > 0 && flags.args[0] && !flags.args[0].startsWith('-')) {
flags.task = flags.args[0];
}
parseArgs(flags, flags.args, flags.knownArgs);
const npmScriptCmdArgs = getNpmScriptArgs(process);
parseArgs(flags, npmScriptCmdArgs, flags.knownArgs);
npmScriptCmdArgs.forEach(npmArg => {
if (!flags.args.includes(npmArg)) {
flags.args.push(npmArg);
}
});
if (flags.task != null) {
const i = flags.args.indexOf(flags.task);
if (i > -1) {
flags.args.splice(i, 1);
}
}
flags.unknownArgs = flags.args.filter((arg) => {
return !flags.knownArgs.includes(arg);
});
return flags;
}
function parseArgs(flags, args, knownArgs) {
ARG_OPTS.boolean.forEach(booleanName => {
const alias = ARG_OPTS.alias[booleanName];
const flagKey = configCase(booleanName);
if (typeof flags[flagKey] !== 'boolean') {
flags[flagKey] = null;
}
args.forEach(cmdArg => {
if (cmdArg === `--${booleanName}`) {
flags[flagKey] = true;
knownArgs.push(cmdArg);
}
else if (cmdArg === `--${flagKey}`) {
flags[flagKey] = true;
knownArgs.push(cmdArg);
}
else if (cmdArg === `--no-${booleanName}`) {
flags[flagKey] = false;
knownArgs.push(cmdArg);
}
else if (cmdArg === `--no${utils.dashToPascalCase(booleanName)}`) {
flags[flagKey] = false;
knownArgs.push(cmdArg);
}
else if (alias && cmdArg === `-${alias}`) {
flags[flagKey] = true;
knownArgs.push(cmdArg);
}
});
});
ARG_OPTS.string.forEach(stringName => {
const alias = ARG_OPTS.alias[stringName];
const flagKey = configCase(stringName);
if (typeof flags[flagKey] !== 'string') {
flags[flagKey] = null;
}
for (let i = 0; i < args.length; i++) {
const cmdArg = args[i];
if (cmdArg.startsWith(`--${stringName}=`)) {
const values = cmdArg.split('=');
values.shift();
flags[flagKey] = values.join('=');
knownArgs.push(cmdArg);
}
else if (cmdArg === `--${stringName}`) {
flags[flagKey] = args[i + 1];
knownArgs.push(cmdArg);
knownArgs.push(args[i + 1]);
}
else if (cmdArg === `--${flagKey}`) {
flags[flagKey] = args[i + 1];
knownArgs.push(cmdArg);
knownArgs.push(args[i + 1]);
}
else if (cmdArg.startsWith(`--${flagKey}=`)) {
const values = cmdArg.split('=');
values.shift();
flags[flagKey] = values.join('=');
knownArgs.push(cmdArg);
}
else if (alias) {
if (cmdArg.startsWith(`-${alias}=`)) {
const values = cmdArg.split('=');
values.shift();
flags[flagKey] = values.join('=');
knownArgs.push(cmdArg);
}
else if (cmdArg === `-${alias}`) {
flags[flagKey] = args[i + 1];
knownArgs.push(args[i + 1]);
}
}
}
});
ARG_OPTS.number.forEach(numberName => {
const alias = ARG_OPTS.alias[numberName];
const flagKey = configCase(numberName);
if (typeof flags[flagKey] !== 'number') {
flags[flagKey] = null;
}
for (let i = 0; i < args.length; i++) {
const cmdArg = args[i];
if (cmdArg.startsWith(`--${numberName}=`)) {
const values = cmdArg.split('=');
values.shift();
flags[flagKey] = parseInt(values.join(''), 10);
knownArgs.push(cmdArg);
}
else if (cmdArg === `--${numberName}`) {
flags[flagKey] = parseInt(args[i + 1], 10);
knownArgs.push(args[i + 1]);
}
else if (cmdArg.startsWith(`--${flagKey}=`)) {
const values = cmdArg.split('=');
values.shift();
flags[flagKey] = parseInt(values.join(''), 10);
knownArgs.push(cmdArg);
}
else if (cmdArg === `--${flagKey}`) {
flags[flagKey] = parseInt(args[i + 1], 10);
knownArgs.push(args[i + 1]);
}
else if (alias) {
if (cmdArg.startsWith(`-${alias}=`)) {
const values = cmdArg.split('=');
values.shift();
flags[flagKey] = parseInt(values.join(''), 10);
knownArgs.push(cmdArg);
}
else if (cmdArg === `-${alias}`) {
flags[flagKey] = parseInt(args[i + 1], 10);
knownArgs.push(args[i + 1]);
}
}
}
});
return flags;
}
function configCase(prop) {
prop = utils.dashToPascalCase(prop);
return prop.charAt(0).toLowerCase() + prop.substr(1);
}
const ARG_OPTS = {
boolean: [
'build',
'cache',
'check-version',
'ci',
'compare',
'debug',
'dev',
'docs',
'e2e',
'es5',
'esm',
'verbose',
'headless',
'devtools',
'help',
'log',
'open',
'prerender',
'prerender-external',
'prod',
'profile',
'service-worker',
'screenshot',
'serve',
'skip-node-check',
'spec',
'stats',
'update-screenshot',
'version',
'watch'
],
number: [
'max-workers',
'port'
],
string: [
'address',
'config',
'docs-json',
'emulate',
'log-level',
'root',
'screenshot-connector'
],
alias: {
'config': 'c',
'help': 'h',
'port': 'p',
'version': 'v'
}
};
function getNpmScriptArgs(process) {
// process.env.npm_config_argv
// {"remain":["4444"],"cooked":["run","serve","--port","4444"],"original":["run","serve","--port","4444"]}
let args = [];
try {
if (process.env) {
const npmConfigArgs = process.env.npm_config_argv;
if (npmConfigArgs) {
args = JSON.parse(npmConfigArgs).original;
if (args[0] === 'run') {
args = args.slice(2);
}
}
}
}
catch (e) { }
return args;
}
/*
* exit
* https://github.com/cowboy/node-exit
*
* Copyright (c) 2013 "Cowboy" Ben Alman
* Licensed under the MIT license.
*/
var exit = function exit(exitCode, streams) {
if (!streams) { streams = [process.stdout, process.stderr]; }
var drainCount = 0;
// Actually exit if all streams are drained.
function tryToExit() {
if (drainCount === streams.length) {
process.exit(exitCode);
}
}
streams.forEach(function(stream) {
// Count drained streams now, but monitor non-drained streams.
if (stream.bufferSize === 0) {
drainCount++;
} else {
stream.write('', 'utf-8', function() {
drainCount++;
tryToExit();
});
}
// Prevent further writing.
stream.write = function() {};
});
// If all streams were already drained, exit now.
tryToExit();
// In Windows, when run as a Node.js child process, a script utilizing
// this library might just exit with a 0 exit code, regardless. This code,
// despite the fact that it looks a bit crazy, appears to fix that.
process.on('exit', function() {
process.exit(exitCode);
});
};
function taskVersion(config) {
console.log(config.sys.compiler.version);
}
async function taskCheckVersion(config) {
try {
const currentVersion = config.sys.compiler.version;
const latestVersion = await config.sys.getLatestCompilerVersion(config.logger, true);
if (config.sys.semver.lt(currentVersion, latestVersion)) {
printUpdateMessage(config.logger, currentVersion, latestVersion);
}
else {
console.log(`${config.logger.cyan(config.sys.compiler.name)} version ${config.logger.green(config.sys.compiler.version)} is the latest version`);
}
}
catch (e) {
config.logger.error(`unable to load latest compiler version: ${e}`);
exit(1);
}
}
async function validateCompilerVersion(sys, logger, latestVersionPromise) {
const latestVersion = await latestVersionPromise;
if (latestVersion == null) {
return;
}
const currentVersion = sys.compiler.version;
if (sys.semver.lt(currentVersion, latestVersion)) {
printUpdateMessage(logger, currentVersion, latestVersion);
}
}
function printUpdateMessage(logger, currentVersion, latestVersion) {
const msg = [
`Update available: ${currentVersion} ${ARROW} ${latestVersion}`,
`To get the latest, please run:`,
NPM_INSTALL
];
const lineLength = msg[0].length;
const o = [];
let top = BOX_TOP_LEFT;
while (top.length <= lineLength + (PADDING * 2)) {
top += BOX_HORIZONTAL;
}
top += BOX_TOP_RIGHT;
o.push(top);
msg.forEach(m => {
let line = BOX_VERTICAL;
for (let i = 0; i < PADDING; i++) {
line += ` `;
}
line += m;
while (line.length <= lineLength + (PADDING * 2)) {
line += ` `;
}
line += BOX_VERTICAL;
o.push(line);
});
let bottom = BOX_BOTTOM_LEFT;
while (bottom.length <= lineLength + (PADDING * 2)) {
bottom += BOX_HORIZONTAL;
}
bottom += BOX_BOTTOM_RIGHT;
o.push(bottom);
let output = `\n${INDENT}${o.join(`\n${INDENT}`)}\n`;
output = output.replace(currentVersion, logger.red(currentVersion));
output = output.replace(latestVersion, logger.green(latestVersion));
output = output.replace(NPM_INSTALL, logger.cyan(NPM_INSTALL));
console.log(output);
}
const NPM_INSTALL = `npm install @stencil/core`;
const ARROW = `→`;
const BOX_TOP_LEFT = `╭`;
const BOX_TOP_RIGHT = `╮`;
const BOX_BOTTOM_LEFT = `╰`;
const BOX_BOTTOM_RIGHT = `╯`;
const BOX_VERTICAL = `│`;
const BOX_HORIZONTAL = `─`;
const PADDING = 2;
const INDENT = ` `;
async function taskBuild(process, config, flags) {
const { Compiler } = require('../compiler/index.js');
const compiler = new Compiler(config);
if (!compiler.isValid) {
exit(1);
}
let devServerStart = null;
if (shouldStartDevServer(config, flags)) {
try {
devServerStart = compiler.startDevServer();
}
catch (e) {
config.logger.error(e);
exit(1);
}
}
let latestVersionPromise;
if (config.devMode && config.watch) {
latestVersionPromise = config.sys.getLatestCompilerVersion(config.logger, false);
}
let devServer = null;
if (devServerStart) {
devServer = await devServerStart;
}
const results = await compiler.build();
if (!config.watch) {
if (devServer != null) {
await devServer.close();
devServer = null;
}
if (results != null && Array.isArray(results.diagnostics)) {
const hasError = results.diagnostics.some(d => d.level === 'error' || d.type === 'runtime');
if (hasError) {
config.sys.destroy();
exit(1);
}
}
}
if (config.watch || devServerStart) {
process.once('SIGINT', () => {
config.sys.destroy();
if (devServer != null) {
devServer.close();
}
});
}
if (latestVersionPromise != null) {
await validateCompilerVersion(config.sys, config.logger, latestVersionPromise);
}
return results;
}
function shouldStartDevServer(config, flags) {
return (config.devServer && (flags.serve || flags.prerender));
}
function taskDocs(config) {
const { Compiler } = require('../compiler/index.js');
const compiler = new Compiler(config);
if (!compiler.isValid) {
exit(1);
}
return compiler.docs();
}
function taskHelp(process, logger) {
const p = logger.dim((process.platform === 'win32') ? '>' : '$');
console.log(`
${logger.bold('Build:')} ${logger.dim('Build components for development or production.')}
${p} ${logger.green('stencil build [--dev] [--watch] [--prerender] [--debug]')}
${logger.cyan('--dev')} ${logger.dim('.............')} Development build
${logger.cyan('--watch')} ${logger.dim('...........')} Rebuild when files update
${logger.cyan('--serve')} ${logger.dim('...........')} Start the dev-server
${logger.cyan('--prerender')} ${logger.dim('.......')} Prerender the application
${logger.cyan('--docs')} ${logger.dim('............')} Generate component readme.md docs
${logger.cyan('--config')} ${logger.dim('..........')} Set stencil config file
${logger.cyan('--stats')} ${logger.dim('...........')} Write stencil-stats.json file
${logger.cyan('--log')} ${logger.dim('.............')} Write stencil-build.log file
${logger.cyan('--debug')} ${logger.dim('...........')} Set the log level to debug
${logger.bold('Test:')} ${logger.dim('Run unit and end-to-end tests.')}
${p} ${logger.green('stencil test [--spec] [--e2e]')}
${logger.cyan('--spec')} ${logger.dim('............')} Run unit tests with Jest
${logger.cyan('--e2e')} ${logger.dim('.............')} Run e2e tests with Puppeteer
${logger.bold('Examples:')}
${p} ${logger.green('stencil build --dev --watch --serve')}
${p} ${logger.green('stencil build --prerender')}
${p} ${logger.green('stencil test --spec --e2e')}
`);
}
async function taskServe(process, config, flags) {
const { Compiler } = require('../compiler/index.js');
config.suppressLogs = true;
const compiler = new Compiler(config);
if (!compiler.isValid) {
exit(1);
}
config.flags.serve = true;
config.devServer.openBrowser = flags.open;
config.devServer.reloadStrategy = null;
config.devServer.initialLoadUrl = '/';
config.devServer.websocket = false;
config.maxConcurrentWorkers = 1;
config.devServer.root = process.cwd();
if (typeof flags.root === 'string') {
if (!config.sys.path.isAbsolute(config.flags.root)) {
config.devServer.root = config.sys.path.relative(process.cwd(), flags.root);
}
}
config.devServer.root = utils.normalizePath(config.devServer.root);
const devServer = await compiler.startDevServer();
if (devServer) {
config.logger.info(`dev server: ${devServer.browserUrl}`);
}
process.once('SIGINT', () => {
config.sys.destroy();
devServer && devServer.close();
exit(0);
});
}
async function taskTest(config) {
try {
const { Testing } = require('../testing/index.js');
const testing = new Testing(config);
if (!testing.isValid) {
exit(1);
}
// always ensure we have jest modules installed
const ensureModuleIds = [
'@types/jest',
'jest',
'jest-cli'
];
if (config.flags.e2e) {
// if it's an e2e test, also make sure we're got
// puppeteer modules installed and if browserExecutablePath is provided don't download Chromium use only puppeteer-core instead
const puppeteer = config.testing.browserExecutablePath ? 'puppeteer-core' : 'puppeteer';
ensureModuleIds.push('@types/puppeteer', puppeteer);
if (config.flags.screenshot) {
// ensure we've got pixelmatch for screenshots
config.logger.warn(config.logger.yellow(`EXPERIMENTAL: screenshot visual diff testing is currently under heavy development and has not reached a stable status. However, any assistance testing would be appreciated.`));
}
}
// ensure we've got the required modules installed
// jest and puppeteer are quite large, so this
// is an experiment to lazy install these
// modules only when you need them
await config.sys.lazyRequire.ensure(config.logger, config.rootDir, ensureModuleIds);
const passed = await testing.runTests();
await testing.destroy();
if (!passed) {
exit(1);
}
}
catch (e) {
config.logger.error(e);
exit(1);
}
}
async function runTask(process, config, flags) {
if (flags.help || flags.task === `help`) {
taskHelp(process, config.logger);
}
else if (flags.version) {
taskVersion(config);
}
else if (flags.checkVersion) {
await taskCheckVersion(config);
}
else {
switch (flags.task) {
case 'build':
await taskBuild(process, config, flags);
break;
case 'docs':
await taskDocs(config);
break;
case 'serve':
await taskServe(process, config, flags);
break;
case 'test':
await taskTest(config);
break;
default:
config.logger.error(`Invalid stencil command, please see the options below:`);
taskHelp(process, config.logger);
exit(1);
}
}
}
async function run(process, sys, logger) {
process.on(`unhandledRejection`, (e) => {
if (!utils.shouldIgnoreError(e)) {
let msg = 'unhandledRejection';
if (e != null) {
if (e.stack) {
msg += ': ' + e.stack;
}
else if (e.message) {
msg += ': ' + e.message;
}
else {
msg += ': ' + e;
}
}
logger.error(msg);
}
});
process.title = `Stencil`;
const flags = parseFlags(process);
// load the config file
let config;
try {
const configPath = getConfigFilePath(process, sys, flags.config);
// if --config is provided we need to check if it exists
if (flags.config && !sys.fs.existsSync(configPath)) {
throw new Error(`Stencil configuration file cannot be found at: "${flags.config}"`);
}
config = sys.loadConfigFile(configPath, process);
config.sys = (config.sys || sys);
config.logger = (config.logger || logger);
}
catch (e) {
logger.error(e);
exit(1);
}
try {
if (typeof config.logLevel === 'string') {
config.logger.level = config.logLevel;
}
config.flags = flags;
process.title = `Stencil: ${config.namespace}`;
await runTask(process, config, flags);
}
catch (e) {
if (!utils.shouldIgnoreError(e)) {
config.logger.error(`uncaught cli error: ${e}${config.logger.level === 'debug' ? e.stack : ''}`);
exit(1);
}
}
}
exports.parseFlags = parseFlags;
exports.run = run;