Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add possibility to exclude certain parts from the build #13

Merged
merged 4 commits into from
Dec 6, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions app/styles/ember-uikit.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
@import 'uikit/src/scss/variables-theme';
@import 'uikit/src/scss/mixins-theme';
@import 'uikit/src/scss/uikit-theme';
@import 'ember-uikit/variables-theme';
@import 'ember-uikit/mixins-theme';
@import 'ember-uikit/uikit-theme';
217 changes: 206 additions & 11 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,223 @@

const Funnel = require('broccoli-funnel');
const Merge = require('broccoli-merge-trees');
const path = require('path');

const DEFAULT_OPTIONS = {
importUIkitCSS: true,
importUIkitJS: true,
importUIkitIcons: true,
importUIkitAssets: true,

useIcons: true,

whitelist: [],
blacklist: []
};

const COMPONENT_DEPENDENCIES = {
'uk-switcher': ['uk-tab', 'uk-subnav']
};

module.exports = {
name: 'ember-uikit',

/**
* For ember-cli < 2.7 findHost doesnt exist so we backport from that version
* for earlier version of ember-cli.
* https://github.com/ember-cli/ember-cli/blame/16e4492c9ebf3348eb0f31df17215810674dbdf6/lib/models/addon.js#L533
*/
findHost() {
let fn =
this._findHost ||
function() {
let current = this;
let app;
do {
app = current.app || app;
} while (current.parent.parent && (current = current.parent));
return app;
};

return fn.call(this);
},

treeForPublic(tree) {
let uikitImages = new Funnel('node_modules/uikit/src/images', {
destDir: '/assets/images/'
});
let uikitAssets =
this.uikitOptions.importUIkitAssets &&
Funnel(this._getAssetsPath(), {
destDir: '/assets/images/components'
});

let uikitIcons =
this.uikitOptions.useIcons &&
this.uikitOptions.importUIkitIcons &&
new Funnel(this._getIconsPath(), {
destDir: '/assets/images/icons'
});

return new Merge([uikitAssets, uikitIcons, tree].filter(Boolean));
},

treeForStyles(tree) {
let uikitStyles =
this._hasSass() &&
this.uikitOptions.importUIkitCSS &&
new Funnel(this._getStylesPath(), {
destDir: 'ember-uikit'
});

return new Merge([uikitImages, tree].filter(Boolean));
return new Merge([uikitStyles, tree].filter(Boolean));
},

included(app) {
included() {
this._super.included.apply(this, arguments);

app.options.sassOptions = app.options.sassOptions || {};
app.options.sassOptions.includePaths =
app.options.sassOptions.includePaths || [];
this.app = this.findHost();

let options = Object.assign(
Object.assign({}, DEFAULT_OPTIONS),
this.app.options['ember-uikit']
);

this.uikitOptions = options;

if (!this.uikitOptions.useIcons) {
this.uikitOptions.blacklist.push('uk-icon');
}

if (!this._hasSass() && this.uikitOptions.importUIkitCSS) {
// use compiled css version of uikit
this.app.import(path.join(this._getStylesPath(), 'uikit.css'));
}

if (this.uikitOptions.importUIkitJS) {
this.app.import(path.join(this._getDistPath(), 'js', 'uikit.js'));

if (this.uikitOptions.useIcons) {
this.app.import(path.join(this._getDistPath(), 'js', 'uikit-icons.js'));
}
}
},

_generateWhitelist(whitelist) {
let list = [];

if (!whitelist) {
return list;
}

function _addToWhitelist(item) {
if (list.indexOf(item) === -1) {
list.push(item);

if (COMPONENT_DEPENDENCIES[item]) {
COMPONENT_DEPENDENCIES[item].forEach(_addToWhitelist);
}
}
}

whitelist.forEach(_addToWhitelist);
return list;
},

treeForAddon(tree) {
return this._super.treeForAddon.call(this, this._filterComponents(tree));
},

treeForAddonTemplates(tree) {
return this._super.treeForAddonTemplates.call(
this,
this._filterComponents(tree)
);
},

/**
* Treeshaking stolen from ember-bootstrap all credits to @kaliber5
*/
_filterComponents(tree) {
let whitelist = this._generateWhitelist(this.uikitOptions.whitelist);
let blacklist = this.uikitOptions.blacklist || [];

// exit early if no opts defined
if (whitelist.length === 0 && blacklist.length === 0) {
return tree;
}

return new Funnel(tree, {
exclude: [name => this._excludeComponent(name, whitelist, blacklist)]
});
},

_excludeComponent(name, whitelist, blacklist) {
let regex = /^(templates\/)?components\//;
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 isWhitelisted = whitelist.indexOf(baseName) !== -1;
let isBlacklisted = blacklist.indexOf(baseName) !== -1;

if (whitelist.length === 0 && blacklist.length === 0) {
return false;
}

if (whitelist.length && blacklist.length === 0) {
return !isWhitelisted;
}

return isBlacklisted;
},

_hasSass() {
return !!this.app.project.findAddonByName('ember-cli-sass');
},

_getNodeModulesPath() {
return path.relative(process.cwd(), this.app.project.nodeModulesPath);
},

_getDistPath() {
return path.join(this._getNodeModulesPath(), 'uikit', 'dist');
},

_getIconsPath() {
return path.join(
this._getNodeModulesPath(),
'uikit',
'src',
'images',
'icons'
);
},

_getAssetsPath() {
return path.join(
this._getNodeModulesPath(),
'uikit',
'src',
'images',
'components'
);
},

_getStylesPath() {
let uikitPath = path.join(this._getNodeModulesPath(), 'uikit');

app.options.sassOptions.includePaths.push('node_modules');
if (this._hasSass()) {
return path.join(uikitPath, 'src', 'scss');
}

app.import('node_modules/uikit/dist/js/uikit.js');
app.import('node_modules/uikit/dist/js/uikit-icons.js');
return path.join(uikitPath, 'dist', 'css');
}
};
3 changes: 3 additions & 0 deletions tests/dummy/app/snippets/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
rules: { 'no-undef': 'off' }
};
12 changes: 12 additions & 0 deletions tests/dummy/app/snippets/configuration.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// ember-cli-build.js

module.exports = function(defaults) {
let app = new EmberApp(defaults, {
'ember-uikit': {
useIcons: false,
whitelist: ['uk-button', 'uk-card']
}
});

return app.toTree();
};
2 changes: 1 addition & 1 deletion tests/dummy/app/styles/app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@ $base-heading-font-weight: 300;
left: 0;
top: 50%;
transform: translateX(calc(-100% - 5px));
}
}
70 changes: 70 additions & 0 deletions tests/dummy/app/templates/index.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,76 @@
include to <code>app/styles/app.scss</code>.
</p>

<h3>Configuration</h3>

<table class="uk-table uk-table-divider">
<thead>
<tr>
<th>Option</th>
<th>Type</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>importUIkitCSS</code></td>
<td><code>Boolean</code></td>
<td><code>false</code></td>
<td>Whether to import the CSS of UIkit</td>
</tr>
<tr>
<td><code>importUIkitJS</code></td>
<td><code>Boolean</code></td>
<td><code>false</code></td>
<td>Whether to import the JS of UIkit</td>
</tr>
<tr>
<td><code>importUIkitAssets</code></td>
<td><code>Boolean</code></td>
<td><code>false</code></td>
<td>Whether to import the assets of UIkit</td>
</tr>
<tr>
<td><code>importUIkitIcons</code></td>
<td><code>Boolean</code></td>
<td><code>false</code></td>
<td>Whether to import the icons of UIkit</td>
</tr>
<tr>
<td><code>useIcons</code></td>
<td><code>Boolean</code></td>
<td><code>true</code></td>
<td>Whether to use UIkits icons</td>
</tr>
<tr>
<td><code>whitelist</code></td>
<td><code>String[]</code></td>
<td><code>[]</code></td>
<td>
A list of included components. Only components in this list will be
included in your build. You should never use <code>whitelist</code>
and <code>blacklist</code>!
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you say that the two lists should not be used at the same time, i'd expect an error to be thrown in that case. I think right now the blacklist takes precedence.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That should definitely throw an error!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or at least a warning

</td>
</tr>
<tr>
<td><code>blacklist</code></td>
<td><code>String[]</code></td>
<td><code>[]</code></td>
<td>
A list of excluded components. Only components not in this list will
be included in your build
</td>
</tr>
</tbody>
</table>

<p>
Those options can be configured in your <code>ember-cli-build.js</code> file:
</p>

{{code-snippet name='configuration.js'}}

<h3>Contribution</h3>

<p>
Expand Down