Skip to content

Commit

Permalink
feat: Add support for handling assets directly in reptar.config.js.
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Themes are removed. You must now configure assets in
your reptar.config.js.

This requires removing config.path.themes, config.theme, and optionally
adding config.assets.
  • Loading branch information
hswolff committed Mar 26, 2017
1 parent 7c5a670 commit aa26d08
Show file tree
Hide file tree
Showing 40 changed files with 316 additions and 625 deletions.
43 changes: 43 additions & 0 deletions lib/assets/browserify.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import browserify from 'browserify';
import babelify from 'babelify';
import uglifyify from 'uglifyify';

export default {
calculateDestination(destination) {
return destination;
},

write(file) {
const { path: filePath } = file;

const bundle = browserify();
bundle.add(filePath);

bundle.transform(
babelify.configure({
presets: [
['env', {
targets: {
browsers: ['last 2 versions'],
},
uglify: true,
}],
],
})
);

bundle.transform(
uglifyify
);

return new Promise((resolve, reject) => {
bundle.bundle((err, buffer) => {
if (err) {
reject(err);
} else {
resolve(buffer.toString('utf8'));
}
});
});
},
};
49 changes: 49 additions & 0 deletions lib/assets/less.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import path from 'path';
import fs from 'fs-extra';
import less from 'less';
import LessPluginAutoPrefix from 'less-plugin-autoprefix';
import LessPluginCleanCSS from 'less-plugin-clean-css';
import LessPluginNpmImport from 'less-plugin-npm-import';

export default {
calculateDestination(destination) {
return destination.replace(
/\.less$/,
'.css'
);
},

write(file) {
const { path: filePath } = file;

// Derive source path from the input source file.
const sourcePath = path.dirname(filePath);

const lessPlugins = [
new LessPluginNpmImport({
basedir: sourcePath,
}),
new LessPluginAutoPrefix({
browsers: ['last 2 versions'],
}),
new LessPluginCleanCSS(),
];

const rawAsset = fs.readFileSync(filePath, 'utf8');

return new Promise((resolve, reject) => {
less.render(rawAsset, {
// Specify search paths for @import directives.
paths: [sourcePath],
plugins: lessPlugins,
}, (e, output) => {
if (e) {
reject(e);
return;
}

resolve(output.css);
});
});
},
};
29 changes: 14 additions & 15 deletions lib/theme/processor/sass.js → lib/assets/sass.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import sass from 'node-sass';
import ProcessorBase from '../processor-base';

export default class Sass extends ProcessorBase {
_getFile() {
export default {
calculateDestination(destination) {
return destination.replace(
/\.s[ac]ss$/,
'.css'
);
},

write(file) {
const { path: filePath } = file;

return new Promise((resolve, reject) => {
sass.render({
file: this.assetSource,
file: filePath,
}, (e, result) => {
if (e) {
reject(e);
Expand All @@ -15,14 +23,5 @@ export default class Sass extends ProcessorBase {
resolve(result.css);
});
});
}

_getDestination() {
const destination = this.assetDestination.replace(
/\.s[ac]ss$/,
'.css'
);

return destination;
}
}
},
};
28 changes: 24 additions & 4 deletions lib/config/config-example.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ module.exports = {
source: './',
destination: './_site',
templates: './_templates',
themes: './_themes',
data: './_data',
},
// Individual File configuration.
Expand Down Expand Up @@ -89,9 +88,30 @@ module.exports = {
permalink: { index: '/tag/:metadata/', page: '/tag/:metadata/:page/' },
},
},
// Current theme.
// What theme your Reptar site should use when building your site.
theme: 'thread',
assets: [
{
test: 'less',
use: 'less',
},
{
test: 'js',
use: 'browserify',
},
{
test: /\.s[ac]ss$/,
use: {
calculateDestination(destination) {
return destination.replace(
/\.s[ac]ss$/,
'.css'
);
},
write(filePath) {
return filePath;
},
},
},
],
// If we should remove the compile destination folder before writing.
clean_destination: false,
// Slug options.
Expand Down
25 changes: 22 additions & 3 deletions lib/config/config-schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ const middlewareOrLifecycleSchema = Joi.alternatives().try(
Joi.array().items(Joi.string(), Joi.func())
).default([]);

const regExpSchema = Joi.object().type(RegExp);

export default Joi.object({
site: Joi.object().default(),
path: Joi.object({
Expand All @@ -15,8 +17,6 @@ export default Joi.object({
.default('./_site'),
templates: Joi.string()
.default('./_templates'),
themes: Joi.string()
.default('./_themes'),
data: Joi.string()
.default('./_data'),
}).default(),
Expand Down Expand Up @@ -53,7 +53,26 @@ export default Joi.object({
page: Joi.string().default('/page/:page/'),
}),
}).without('path', 'metadata')),
theme: Joi.string().default('default'),
assets: Joi.array().items(
Joi.object({
test: Joi.alternatives().try(
Joi.string(),
Joi.func(),
regExpSchema
),
use: Joi.alternatives().try(
Joi.string(),
Joi.object({
calculateDestination: Joi.func().required(),
write: Joi.func().required(),
})
),
})
).default([
{ test: 'less', use: 'less' },
{ test: 'js', use: 'browserify' },
{ test: /\.s[ac]ss$/, use: 'sass' },
]),
clean_destination: Joi.boolean().default(false),
slug: Joi.object({
lower: Joi.boolean().default(true),
Expand Down
45 changes: 42 additions & 3 deletions lib/config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,21 @@ import resolve from 'resolve';
import Constants from '../constants';
import log from '../log';
import schema from './config-schema';
import less from '../assets/less';
import sass from '../assets/sass';
import browserify from '../assets/browserify';

const assetProcessors = {
browserify,
less,
sass,
};

function requireLocalModule(moduleName, { basedir } = {}) {
const modulePath = resolve.sync(moduleName, { basedir });
// eslint-disable-next-line global-require, import/no-dynamic-require
return require(modulePath);
}

/**
* Look for a {@link Constants.ConfigFilename} file in this directory or any
Expand Down Expand Up @@ -122,9 +137,7 @@ export default class Config {
// For a given value if it is a string resolve it as if it's an NPM module.
const resolveMiddlewareModules = (moduleVal) => {
if (_.isString(moduleVal)) {
const modulePath = resolve.sync(moduleVal, { basedir: this.root });
// eslint-disable-next-line global-require, import/no-dynamic-require
return require(modulePath);
return requireLocalModule(moduleVal, { basedir: this.root });
}
return moduleVal;
};
Expand All @@ -137,6 +150,32 @@ export default class Config {
const newVal = _.flatten(Array.of(val));
this._raw.lifecycle[key] = newVal.map(resolveMiddlewareModules);
});

// Convert every config.asset.test value to be a function.
this._raw.assets = this._raw.assets.map((asset) => {
let testVal = asset.test;

if (_.isString(testVal)) {
testVal = new RegExp(testVal);
}

if (!_.isFunction(testVal)) {
const regExp = testVal;
testVal = filePath => filePath.match(regExp) !== null;
}

let useVal = asset.use;
if (_.isString(useVal)) {
useVal = assetProcessors[useVal] ?
assetProcessors[useVal] :
requireLocalModule(useVal, { basedir: this.root });
}

return {
test: testVal,
use: useVal,
};
});
}

/**
Expand Down
1 change: 0 additions & 1 deletion lib/constants.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@

const Constants = {
ConfigFilename: 'reptar.config.js',
ThemeFilename: '_theme.yml',

/**
* Key used on the config.path object to dictate where to find the site source
Expand Down
21 changes: 20 additions & 1 deletion lib/file.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@ export default class File {

// If File doesn't have frontmatter then return early.
if (!hasFrontmatter) {
const assetConfig = this._config.get('assets').find(
({ test }) => test(this.path)
);

if (assetConfig) {
this.assetProcessor = assetConfig.use;
}

this.skipProcessing = true;
this._calculateDestination();
return;
Expand Down Expand Up @@ -230,8 +238,11 @@ export default class File {
);
}

this.destination = Url.makeUrlFileSystemSafe(destinationUrl);
if (this.assetProcessor) {
destinationUrl = this.assetProcessor.calculateDestination(destinationUrl);
}

this.destination = Url.makeUrlFileSystemSafe(destinationUrl);
this.data.url = Url.makePretty(this.destination);
}

Expand Down Expand Up @@ -288,6 +299,14 @@ export default class File {
this.destination
);

if (this.assetProcessor) {
const content = await this.assetProcessor.write(this);
await Promise.fromCallback((cb) => {
fs.outputFile(destinationPath, content, 'utf8', cb);
});
return;
}

// If this File is a static asset then we don't process it at all, and just
// copy it to its destination path.
// This typically applies to images and other similar files.
Expand Down
Loading

0 comments on commit aa26d08

Please sign in to comment.