@enact/dev-utils
Version:
A collection of development utilities for Enact apps.
216 lines (195 loc) • 8.35 kB
JavaScript
const fs = require('fs');
const path = require('path');
const resolve = require('resolve');
const browserslist = require('browserslist');
const pkgRoot = require('./package-root');
const defaultTargets = [
'>1%',
'last 2 versions',
'last 5 Chrome versions',
'last 5 Firefox versions',
'Firefox ESR',
'not ie < 12',
'not ie_mob < 12',
'not dead'
];
const pkg = pkgRoot();
let enact = pkg.meta.enact || {};
const capitalize = name => name[0].toUpperCase() + name.slice(1);
const valid = v => v || v === false;
// Gently parse a file returning undefined on thrown errors
const gentlyParse = file => {
try {
return JSON.parse(fs.readFileSync(file, {encoding: 'utf8'}));
} catch (e) {
return undefined;
}
};
// Resolve a filepath relative to a context and theme
const themeFile = (context, theme, file) => {
const checks = [`@enact/${theme}/${file}`, `${theme}/${file}`];
for (let i = 0; i < checks.length; i++) {
try {
return resolve.sync(checks[i], {basedir: context});
} catch (e) {
// do nothing
}
}
};
// Resolve a valid theme decorator relative filepath (eg. screentypes.json)
const decoFile = (dir, file) => {
return [
// Possible theme decorator locations
path.join('ThemeDecorator', file),
path.join(capitalize(path.basename(dir)) + 'Decorator', file)
].find(f => fs.existsSync(path.join(dir, f)));
};
// Recursively resolves theme configuration details
const themeConfig = (context, theme) => {
const pkgFile = themeFile(context, theme, 'package.json');
if (pkgFile) {
const meta = require(pkgFile);
const cfg = meta.enact || {};
cfg.name = meta.name;
cfg.path = path.dirname(pkgFile);
if (!cfg.screenTypes) cfg.screenTypes = decoFile(cfg.path, 'screenTypes.json');
if (!cfg.fontGenerator) cfg.fontGenerator = decoFile(cfg.path, 'fontGenerator.js');
if (cfg.theme) cfg.theme = themeConfig(cfg.path, cfg.theme);
return cfg;
}
};
// Computes the value of a config prop in a hierarchy of:
// 1. Environment variable level (ENACT_<prop>)
// 2. Local source level (within a package.json or local ThemeDecorator)
// 3. Extended theme level (explicitly or implicitly within extended theme)
const computed = (prop, app, theme) => {
// Environment variables take top priority
const envProp = 'ENACT_' + prop.toUpperCase();
if (valid(process.env[envProp])) {
if (/^([{[].*[}\]]|true|false)$/.test(process.env[envProp].trim())) {
try {
return JSON.parse(process.env[envProp]);
} catch (e) {
// do nothing
}
}
return process.env[envProp];
}
// Local source level values take secondary priority
if (valid(app[prop])) return app[prop];
const selfThemeFiles = {
screenTypes: decoFile(pkg.path, 'screenTypes.json'),
fontGenerator: decoFile(pkg.path, 'fontGenerator.js')
};
if (valid(selfThemeFiles[prop])) return selfThemeFiles[prop];
// Extended theme level values take tertiary priority
const pathProps = ['isomorphic', 'template', 'screenTypes', 'fontGenerator'];
const computeThemeProp = (p, cfg) => {
if (valid(cfg[p])) {
if (pathProps.includes(p)) return path.join(cfg.path, cfg[p]);
return cfg[p];
} else if (cfg.theme) {
return computeThemeProp(p, cfg.theme);
}
};
if (theme) return computeThemeProp(prop, theme);
};
const config = {
// Project base directory.
context: pkg.path,
// Project name.
name: pkg.meta.name,
// Parse Enact metadata and apply options onto the config.
applyEnactMeta: function (meta) {
enact = Object.assign(enact, meta);
// Parse the theme config tree for defaults
config.theme = themeConfig(pkg.path, process.env.ENACT_THEME || enact.theme || 'sandstone');
// Optional alternate entrypoint for isomorphic builds.
config.isomorphic = computed('isomorphic', enact, config.theme);
// Optional filepath to an alternate HTML template for html-webpack-plugin.
config.template = computed('template', enact, config.theme);
// Optional <title></title> value for HTML
config.title = computed('title', enact, config.theme);
// Optional flag whether to externalize the prerender startup js
config.externalStartup = computed('externalStartup', enact, config.theme);
// Optional object mapping of webpack path aliases to use when building
config.alias = computed('alias', enact, config.alias);
// Optional webpack node configuration value (see https://webpack.js.org/configuration/node/).
config.nodeBuiltins = computed('nodeBuiltins', enact, config.theme);
// Optional webpack resolve configuration value (see https://webpack.js.org/configuration/resolve/).
config.resolveFallback = computed('resolveFallback', enact, config.theme);
// Optional property to specify a version of NodeJS to target required polyfills.
// True or 'current' will use active version of Node, otherwise will use a specified version number.
config.node = computed('node', enact, config.theme);
// Optional window condition(s) that indicate deeplinking and invalidate HTML prerender.
config.deep = computed('deep', enact, config.theme);
// Proxy target to use within the http-proxy-middleware during serving.
config.proxy = computed('proxy', enact, config.theme) || pkg.meta.proxy;
// Public path URL at which the app is served or destined to be hosted.
config.publicUrl = computed('publicUrl', enact, config.theme) || pkg.meta.homepage;
// Optionally force all LESS/CSS to be handled modularly, instead of solely having
// the *.module.css and *.module.less files be processed in a modular context.
config.forceCSSModules = computed('forceCSSModules', enact, config.theme);
config.additionalModulePaths = computed('additionalModulePaths', enact, config.theme);
config.entry = computed('entry', enact, config.theme);
// Resolve array of screenType configurations. When not found, falls back to any theme preset or sandstone.
const screens = computed('screenTypes', enact, config.theme);
config.screenTypes =
(Array.isArray(screens) && screens) ||
(typeof screens === 'string' &&
((path.isAbsolute(screens) && gentlyParse(screens)) ||
gentlyParse(path.join(pkg.path, screens)) ||
gentlyParse(path.join(pkg.path, 'node_modules', screens)))) ||
[];
// Resolve the resolution independence settings from explicit settings or the resolved screenTypes definitions.
const riConfig = computed('ri', enact, config.theme);
config.ri = valid(riConfig)
? riConfig
: config.screenTypes.reduce((r, s) => (s.base && {baseSize: s.pxPerRem}) || r, undefined);
// Resolved filepath to fontGenerator. When not found, falls back to any theme preset or sandstone.
const fontGenerator = computed('fontGenerator', enact, config.theme);
config.fontGenerator =
fontGenerator &&
(path.isAbsolute(fontGenerator)
? fontGenerator
: [path.join(pkg.path, fontGenerator), path.join(pkg.path, 'node_modules', fontGenerator)].find(
fs.existsSync
));
// Override theme's accent LESS variable value if desired. Private option; may be removed in future.
// When used, creates a LESS variable override map, overriding '@moon-accent' and/or '@<theme>-accent'
// values with the specified override. This allows a simple way to alter Enact spotlight color.
if (enact.accent) {
config.accent = {'moon-accent': enact.accent};
for (let t = config.theme; t; t = t.theme) {
config.accent[t.name.replace('@enact/', '') + '-accent'] = enact.accent;
}
}
},
// Sets the browserslist default fallback set of browsers to the Enact default browser support list.
setEnactTargetsAsDefault: function () {
if (!browserslist.loadConfig({path: pkg.path})) process.env.BROWSERSLIST = defaultTargets.join(',');
}
};
Object.defineProperty(config, 'environment', {
configurable: false,
enumerable: true,
get: function () {
if (enact.environment) return enact.environment;
let targets = browserslist.loadConfig({path: pkg.path});
if (targets) {
if (typeof targets === 'string') targets = targets.split(/,\s*/);
targets = targets.map(b => b.toLowerCase());
if (targets.some(b => !b.startsWith('not') && b.includes('electron'))) {
return 'electron-renderer';
} else if (targets.every(b => !b.startsWith('not') && b.includes('node'))) {
return 'node';
} else {
return 'browserslist';
}
} else {
return 'web';
}
}
});
config.applyEnactMeta();
module.exports = config;