ember-bootstrap
Version:
Bootstrap components for Ember.js
341 lines (280 loc) • 10.9 kB
JavaScript
;
const path = require('path');
const mergeTrees = require('broccoli-merge-trees');
const Funnel = require('broccoli-funnel');
const BroccoliDebug = require('broccoli-debug');
const SilentError = require('silent-error'); // From ember-cli
const VersionChecker = require('ember-cli-version-checker');
const resolve = require('resolve');
const defaultOptions = {
importBootstrapCSS: true,
insertEmberWormholeElementToDom: true,
bootstrapVersion: 5,
};
const supportedPreprocessors = ['sass'];
const componentDependencies = {
'bs-button-group': ['bs-button'],
'bs-accordion': ['bs-collapse'],
'bs-dropdown': ['bs-button', 'bs-link-to'],
'bs-form': ['bs-button'],
'bs-modal-simple': ['bs-modal'],
'bs-modal': ['bs-button'],
'bs-nav': ['bs-link-to', 'bs-dropdown'],
'bs-navbar': ['bs-nav', 'bs-button', 'bs-link-to', 'bs-collapse'],
'bs-popover': ['bs-contextual-help'],
'bs-tab': ['bs-nav'],
'bs-tooltip': ['bs-contextual-help'],
};
const minimumBS4Version = '4.0.0-beta';
const minimumBS5Version = '5.0.0';
module.exports = {
name: require('./package').name,
init() {
this._super.init.apply(this, arguments);
this.debugTree = BroccoliDebug.buildDebugCallback(
`ember-bootstrap:${this.name}`,
);
},
includedCommands() {
return {
'bootstrap:info': require('./lib/commands/info'),
};
},
included() {
this._super.included.apply(this, arguments);
let app = this._findHost(this);
this.app = app;
let options = Object.assign(
{},
defaultOptions,
app.options['ember-bootstrap'],
);
if (options.bootstrapVersion === 3) {
throw new SilentError(
'Support for Bootstrap 3 has been removed as of ember-bootstrap v5! Consider migrating to Bootstrap v4 or v5, or downgrade ember-bootstrap to v4!',
);
}
if (process.env.BOOTSTRAPVERSION) {
// override bootstrapVersion config when environment variable is set
options.bootstrapVersion = parseInt(process.env.BOOTSTRAPVERSION);
}
this.bootstrapOptions = options;
this.validateDependencies();
this.preprocessor = this.findPreprocessor();
// static Bootstrap CSS is mapped to vendor tree, independent of BS version, so import from there
let vendorPath = path.join('vendor', 'ember-bootstrap');
if (!this.hasPreprocessor()) {
// / Import css from bootstrap
if (options.importBootstrapCSS) {
this.warn(`\
Importing Bootstrap CSS through Ember Bootstrap is deprecated. Applications should import Bootstrap's CSS explicitly.
Please find information how to do so in the Ember guides: https://guides.emberjs.com/release/addons-and-dependencies/#toc_css.
Additionally set importBootstrapCSS configuration of Ember Bootstrap in your app's ember-cli-build.js to false. Please find more information about Ember Bootstrap's configuration here: https://www.ember-bootstrap.com/getting-started/setup
Until: 7.0.0
`);
this.needsBootstrapStyles = true;
this.import(path.join(vendorPath, 'bootstrap.css'));
this.import(path.join(vendorPath, 'bootstrap.css.map'), {
destDir: 'assets',
});
}
}
// import custom addon CSS
this.import(path.join(vendorPath, `bs${options.bootstrapVersion}.css`));
// setup config for @embroider/macros
this.options['@embroider/macros'].setOwnConfig.isBS4 =
this.getBootstrapVersion() === 4;
this.options['@embroider/macros'].setOwnConfig.isBS5 =
this.getBootstrapVersion() === 5;
this.options['@embroider/macros'].setOwnConfig.isNotBS5 =
this.getBootstrapVersion() !== 5;
this.options['@embroider/macros'].setOwnConfig.version =
require('./package.json').version;
},
options: {
'@embroider/macros': {
setOwnConfig: {},
},
'ember-cli-babel': { enableTypeScriptTransform: true },
},
validateDependencies() {
let checker = new VersionChecker(this.project);
let dep = checker.for('bootstrap');
if (this.getBootstrapVersion() === 4 && !dep.gte(minimumBS4Version)) {
this.warn(
`For Bootstrap 4 support this version of ember-bootstrap requires at least Bootstrap ${minimumBS4Version}, but you have ${dep.version}. Please run \`ember generate ember-bootstrap\` to update your dependencies!`,
);
}
if (this.getBootstrapVersion() === 5 && !dep.gte(minimumBS5Version)) {
this.warn(
`For Bootstrap 5 support this version of ember-bootstrap requires at least Bootstrap ${minimumBS5Version}, but you have ${dep.version}. Please run \`ember generate ember-bootstrap\` to update your dependencies!`,
);
}
},
findPreprocessor() {
return supportedPreprocessors.find(
(name) => !!this.app.project.findAddonByName(`ember-cli-${name}`),
);
},
getBootstrapStylesPath() {
switch (this.preprocessor) {
case 'sass':
return this.resolvePackagePath('bootstrap/scss');
default:
return this.resolvePackagePath('bootstrap/dist/css');
}
},
resolvePackagePath(pkgPath) {
let parts = pkgPath.split('/');
let pkg = parts[0];
let result = path.dirname(
resolve.sync(`${pkg}/package.json`, { basedir: this.app.project.root }),
);
// add sub folders to path
if (parts.length > 1) {
let args = parts.map((part, i) => (i === 0 ? result : part));
result = path.join.apply(path, args);
}
return result;
},
hasPreprocessor() {
return !!this.preprocessor;
},
treeForStyles() {
if (this.hasPreprocessor()) {
return new Funnel(this.getBootstrapStylesPath(), {
destDir: 'ember-bootstrap',
});
}
},
treeForVendor(tree) {
let trees = [tree];
if (!this.hasPreprocessor() && this.needsBootstrapStyles) {
trees.push(
new Funnel(this.getBootstrapStylesPath(), {
destDir: 'ember-bootstrap',
}),
);
}
return mergeTrees(trees, { overwrite: true });
},
getBootstrapVersion() {
return parseInt(this.bootstrapOptions.bootstrapVersion);
},
treeForApp(tree) {
tree = this.filterComponents(tree);
return this._super.treeForApp.call(this, tree);
},
treeForAddon(tree) {
tree = this.filterComponents(tree);
tree = this.debugTree(tree, 'addon-tree:tree-shaken');
return this._super.treeForAddon.call(this, tree);
},
/*
This method is depreciated by ember-cli when building other apps
but it is still called when building ember-bootstrap
https://github.com/kaliber5/ember-bootstrap/pull/883
*/
treeForAddonTemplates(tree) {
tree = this.debugTree(tree, 'addon-templates-tree:input');
tree = this.filterComponents(tree);
tree = this.debugTree(tree, 'addon-templates-tree:tree-shaken');
return this._super.treeForAddonTemplates.call(this, tree);
},
contentFor(type, config) {
if (
type === 'body-footer' &&
config.environment !== 'test' &&
this.bootstrapOptions.insertEmberWormholeElementToDom !== false
) {
this.warn(`\
Inserting the element used by Ember Bootstrap as wormhole target for modals and tooltips is deprecated.
Please add a <div id="ember-bootstrap-wormhole"></div> to either the \`app/index.html\` or \`app/templates/application.hbs\` and set Ember Bootstrap's \`insertEmberWormholeElementToDom\` configuration option to \`false\`.
Please find more details about Ember Bootstrap's configuration here: https://www.ember-bootstrap.com/getting-started/setup.
Until: 7.0.0`);
return '<div id="ember-bootstrap-wormhole"></div>';
}
},
warn(message) {
this.ui.writeWarnLine(message);
},
filterComponents(tree) {
if (this.bootstrapOptions.whitelist) {
throw new SilentError(
"Ember Bootstrap's `whitelist` option been renamed to `include`. Please update your ember-cli-build.js accordingly.",
);
}
if (this.bootstrapOptions.blacklist) {
throw new SilentError(
"Ember Bootstrap's `blacklist` option has been renamed to `exclude`. Please update your ember-cli-build.js accordingly.",
);
}
let includeList = this.generateIncludeList(this.bootstrapOptions.include);
let excludeList = this.bootstrapOptions.exclude || [];
// Emit deprecation warning if include or exclude options are used
if (this.bootstrapOptions.include) {
this.warn(`\
Ember Bootstrap's \`include\` option is deprecated and will be removed in v7.0.0.
Manual tree-shaking via the \`include\` and \`exclude\` options is no longer supported when migrating to v2 addons.
Please migrate to Embroider, which supports automatic tree-shaking out of the box. For more information, visit: https://ember-cli.com/embroider
To enable static component invocation detection in Embroider, set the \`staticComponents\` or \`staticInvocables\` option in your build configuration.
`);
}
if (this.bootstrapOptions.exclude) {
this.warn(`\
Ember Bootstrap's \`exclude\` option is deprecated and will be removed in v7.0.0.
Manual tree-shaking via the \`include\` and \`exclude\` options is no longer supported when migrating to v2 addons.
Please migrate to Embroider, which supports automatic tree-shaking out of the box. For more information, visit: https://ember-cli.com/embroider
To enable static component invocation detection in Embroider, set the \`staticComponents\` or \`staticInvocables\` option in your build configuration.
`);
}
// exit early if no opts defined
if (includeList.length === 0 && excludeList.length === 0) {
return tree;
}
return new Funnel(tree, {
exclude: [
(name) => this.excludeComponent(name, includeList, excludeList),
],
});
},
excludeComponent(name, includeList, excludeList) {
let regex = /^(templates\/)?components\/(base\/)?/;
let isComponent = regex.test(name);
if (!isComponent) {
return false;
}
let baseName = name.replace(regex, '');
let firstSeparator = baseName.indexOf('/');
if (firstSeparator !== -1) {
baseName = baseName.substring(0, firstSeparator);
} else {
baseName = baseName.substring(0, baseName.lastIndexOf('.'));
}
let isIncluded = includeList.indexOf(baseName) !== -1;
let isExcluded = excludeList.indexOf(baseName) !== -1;
if (includeList.length === 0 && excludeList.length === 0) {
return false;
}
if (includeList.length && excludeList.length === 0) {
return !isIncluded;
}
return isExcluded;
},
generateIncludeList(includeList) {
let list = [];
if (!includeList) {
return list;
}
function _addToIncludeList(item) {
if (list.indexOf(item) === -1) {
list.push(item);
if (componentDependencies[item]) {
componentDependencies[item].forEach(_addToIncludeList);
}
}
}
includeList.forEach(_addToIncludeList);
return list;
},
};