Skip to content

Commit

Permalink
Add images to assets manifest
Browse files Browse the repository at this point in the history
  • Loading branch information
QWp6t committed Nov 7, 2016
1 parent 0d38ab8 commit c49793c
Show file tree
Hide file tree
Showing 9 changed files with 270 additions and 79 deletions.
15 changes: 5 additions & 10 deletions assets/build/config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const path = require('path');
const argv = require('minimist')(process.argv.slice(2));
const glob = require('glob-all');
const uniq = require('lodash/uniq');

const mergeWithConcat = require('./util/mergeWithConcat');
const userConfig = require('../config');
Expand All @@ -11,7 +11,7 @@ const rootPath = (userConfig.paths && userConfig.paths.root)
: process.cwd();

const config = mergeWithConcat({
copy: ['images/**/*'],
copy: 'images/**/*',
proxyUrl: 'http://localhost:3000',
cacheBusting: '[name]_[hash]',
paths: {
Expand All @@ -29,20 +29,15 @@ const config = mergeWithConcat({
watch: [],
}, userConfig);

const files = glob.sync(config.copy, {
cwd: config.paths.assets,
mark: true,
}).filter(file => !((file.slice(-1) === '/') || (!file.indexOf('*') === -1)))
.map(file => path.join(config.paths.assets, file));
config.watch.push(config.copy);
config.watch = uniq(config.watch);

Object.keys(config.entry).forEach(id =>
config.entry[id].unshift(path.join(__dirname, 'public-path.js')));

module.exports = mergeWithConcat(config, {
env: Object.assign({ production: isProduction, development: !isProduction }, argv.env),
publicPath: `${config.publicPath}/${path.basename(config.paths.dist)}/`,
manifest: {},
});

if (files.length) {
module.exports = mergeWithConcat(module.exports, { entry: { files } });
}
7 changes: 4 additions & 3 deletions assets/build/util/assetsPluginProcessOutput.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

const path = require('path');

/**
Expand All @@ -7,12 +8,12 @@ const path = require('path');
* @return {String} JSON
*/
module.exports = (assets) => {
const results = {};
const manifest = {};
Object.keys(assets).forEach((name) => {
Object.keys(assets[name]).forEach((ext) => {
const filename = `${path.dirname(assets[name][ext])}/${path.basename(`${name}.${ext}`)}`;
results[filename] = assets[name][ext];
manifest[filename] = assets[name][ext];
});
});
return JSON.stringify(results);
return manifest;
};
50 changes: 50 additions & 0 deletions assets/build/util/interpolateName.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
'use strict'; // eslint-disable-line

const path = require('path');
const utils = require('loader-utils');

/**
* Generate output name from output pattern
*
* @link https://github.com/kevlened/copy-webpack-plugin/blob/323b1d74ef35ed2221637d8028b1bef854deb523/src/writeFile.js#L31-L65
* @param {string} pattern
* @param {string} relativeFrom
* @param {binary} content
* @return {string}
*/
module.exports = (pattern, relativeFrom, content) => {
let webpackTo = pattern;
let resourcePath = relativeFrom;

/* A hack so .dotted files don't get parsed as extensions */
const basename = path.basename(resourcePath);
let dotRemoved = false;
if (basename[0] === '.') {
dotRemoved = true;
resourcePath = path.join(path.dirname(resourcePath), basename.slice(1));
}

/**
* If it doesn't have an extension, remove it from the pattern
* ie. [name].[ext] or [name][ext] both become [name]
*/
if (!path.extname(resourcePath)) {
webpackTo = webpackTo.replace(/\.?\[ext]/g, '');
}

/**
* A hack because loaderUtils.interpolateName doesn't
* find the right path if no directory is defined
* ie. [path] applied to 'file.txt' would return 'file'
*/
if (resourcePath.indexOf('/') < 0) {
resourcePath = `/${resourcePath}`;
}

webpackTo = utils.interpolateName({ resourcePath }, webpackTo, { content });

if (dotRemoved) {
webpackTo = path.join(path.dirname(webpackTo), `.${path.basename(webpackTo)}`);
}
return webpackTo;
};
21 changes: 21 additions & 0 deletions assets/build/util/promisify.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Node-style asynchronous function.
*
* @callback nodeAsyncCallback
* @param {string|null} err
* @param {*} data
*/
/**
* Promisify node-style asynchronous functions
*
* @param {nodeAsyncCallback} fn - Function with node-style callback
* @param {this} [scope] - Scope to which the function should be bound. Default: fn
* @returns {Promise} - An instance of Promise
*/
module.exports = (fn, scope) => function callback() {
const args = [].slice.call(arguments);
return new Promise((resolve, reject) => {
args.push((err, data) => (err === null ? resolve(data) : reject(err)));
return fn.apply(scope || fn, args);
});
};
33 changes: 14 additions & 19 deletions assets/build/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const ExtractTextPlugin = require('extract-text-webpack-plugin');
const ImageminPlugin = require('imagemin-webpack-plugin').default;
const imageminMozjpeg = require('imagemin-mozjpeg');

const CopyGlobsPlugin = require('./webpack.plugin.copyglobs');
const mergeWithConcat = require('./util/mergeWithConcat');
const addHotMiddleware = require('./util/addHotMiddleware');
const webpackConfigProduction = require('./webpack.config.production');
Expand Down Expand Up @@ -75,7 +76,7 @@ const webpackConfig = {
include: config.paths.assets,
loaders: [
`file?${qs.stringify({
name: '[path][name].[ext]',
name: `[path]${assetsFilenames}.[ext]`,
})}`,
],
},
Expand Down Expand Up @@ -118,25 +119,19 @@ const webpackConfig = {
},
plugins: [
new CleanPlugin([config.paths.dist], config.paths.root),
new CopyGlobsPlugin({
// It would be nice to switch to copy-webpack-plugin, but unfortunately it doesn't
// provide a reliable way of tracking the before/after file names
pattern: config.copy,
output: `[path]${assetsFilenames}.[ext]`,
manifest: config.manifest,
}),
new ImageminPlugin({
optipng: {
optimizationLevel: 7,
},
gifsicle: {
optimizationLevel: 3,
},
pngquant: {
quality: '65-90',
speed: 4,
},
svgo: {
removeUnknownsAndDefaults: false,
cleanupIDs: false,
},
jpegtran: null,
plugins: [imageminMozjpeg({
quality: 75,
})],
optipng: { optimizationLevel: 7 },
gifsicle: { optimizationLevel: 3 },
pngquant: { quality: '65-90', speed: 4 },
svgo: { removeUnknownsAndDefaults: false, cleanupIDs: false },
plugins: [imageminMozjpeg({ quality: 75 })],
disable: (config.enabled.watcher),
}),
new ExtractTextPlugin({
Expand Down
4 changes: 3 additions & 1 deletion assets/build/webpack.config.production.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ module.exports = {
path: config.paths.dist,
filename: 'assets.json',
fullPath: false,
processOutput,
processOutput(assets) {
return JSON.stringify(Object.assign(processOutput(assets), config.manifest));
},
}),
new OptimizeCssAssetsPlugin({
cssProcessor: cssnano,
Expand Down
143 changes: 143 additions & 0 deletions assets/build/webpack.plugin.copyglobs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
'use strict'; // eslint-disable-line

const fs = require('fs');
const path = require('path');
const glob = require('glob');
const utils = require('loader-utils');
const includes = require('lodash/includes');

const interpolateName = require('./util/interpolateName');
const promisify = require('./util/promisify');

const fixPath = v => v.replace(/\\/g, '/');
const errorMsg = msg => `\x1b[31m${msg}\x1b[0m`;

const GLOB_CWD_AUTO = null;

const globAsync = promisify(glob);
const statAsync = promisify(fs.stat);
const readFileAsync = promisify(fs.readFile);

class PatternUndefinedError extends Error {
constructor() {
super(errorMsg('[copy-globs] You must provide glob pattern.'));
}
}

class ArgsArrayError extends TypeError {
constructor() {
super(errorMsg(
'[copy-globs] pattern cannot be an array.\n' +
'For multiple folders, use something like:\n\n' +
' +(images|fonts)/**/*\n\n' +
'See also: https://github.com/isaacs/node-glob#glob-primer\n'
));
}
}

/**
* Throws an error if pattern is an array or undefined
*
* @param pattern
*/
const testPattern = (pattern) => {
if (pattern === undefined) {
throw new PatternUndefinedError();
}
if (Array.isArray(pattern)) {
throw new ArgsArrayError();
}
};

const normalizeArguments = (input) => {
testPattern(input);
const options = {};
if (typeof input === 'string') {
options.pattern = input;
} else {
testPattern(input.pattern);
return input;
}
return options;
};

module.exports = class {
constructor(o) {
const options = normalizeArguments(o);
this.pattern = options.pattern;
this.disable = options.disable;
this.output = options.output || '[path][name].[ext]';
this.globOptions = Object.assign(options.globOptions || {}, { cwd: GLOB_CWD_AUTO });
this.globOptions.nodir = true;
this.manifest = options.manifest || {};
this.files = [];
}
apply(compiler) {
if (this.disable) {
return;
}
this.compiler = compiler;
this.resolveWorkingDirectory();
compiler.plugin('emit', this.emitHandler.bind(this));
compiler.plugin('after-emit', this.afterEmitHandler.bind(this));
}
emitHandler(compilation, callback) {
this.compilation = compilation;
globAsync(this.pattern, this.globOptions)
.then(
paths => Promise.all(paths.map(this.processAsset.bind(this))),
err => compilation.errors.push(err)
)
.then(() => {
Object.keys(this.files).forEach((absoluteFrom) => {
const file = this.files[absoluteFrom];
this.manifest[file.relativeFrom] = file.webpackTo;
this.compilation.assets[file.webpackTo] = {
size: () => file.stat.size,
source: () => file.content,
};
});
})
.then(callback);
}
afterEmitHandler(compilation, callback) {
Object.keys(this.files)
.filter(absoluteFrom => !includes(compilation.fileDependencies, absoluteFrom))
.forEach(absoluteFrom => compilation.fileDependencies.push(absoluteFrom));
callback();
}
resolveWorkingDirectory() {
if (this.globOptions.cwd === GLOB_CWD_AUTO) {
this.globOptions.cwd = this.compiler.options.context;
}
this.context = this.globOptions.cwd || this.compiler.options.context;
}
processAsset(relativeFrom) {
if (this.compilation.assets[relativeFrom]) {
return Promise.resolve();
}
const absoluteFrom = path.resolve(this.context, relativeFrom);
return statAsync(absoluteFrom)
.then(stat => this.buildFileObject(relativeFrom, absoluteFrom, stat))
.then(this.addAsset.bind(this));
}
buildFileObject(relativeFrom, absoluteFrom, stat) {
return readFileAsync(absoluteFrom)
.then((content) => {
const hash = utils.getHashDigest(content);
const webpackTo = fixPath(interpolateName(this.output, relativeFrom, content));
return { relativeFrom, absoluteFrom, stat, content, hash, webpackTo };
});
}
addAsset(file) {
const asset = this.getAsset(file.absoluteFrom);
if (asset && asset.hash === file.hash) {
return null;
}
this.files[file.absoluteFrom] = file;
return file;
}
getAsset(absoluteFrom) {
return this.files[absoluteFrom];
}
};
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"body-parser": "^1.15.2",
"browser-sync": "^2.17.5",
"buble": "^0.14.2",
"buble-loader": "^0.3.1",
"buble-loader": "^0.3.2",
"clean-webpack-plugin": "^0.1.13",
"css-loader": "^0.25.0",
"cssnano": "^3.8.0",
Expand All @@ -46,14 +46,15 @@
"eslint-plugin-react": "^6.5.0",
"extract-text-webpack-plugin": "^2.0.0-beta.4",
"file-loader": "^0.9.0",
"glob-all": "^3.1.0",
"glob": "^7.1.1",
"imagemin-mozjpeg": "^6.0.0",
"imagemin-webpack-plugin": "^1.2.1",
"imports-loader": "^0.6.5",
"loader-utils": "^0.2.16",
"lodash": "^4.16.6",
"minimist": "^1.2.0",
"monkey-hot-loader": "github:rmarscher/monkey-hot-loader#webpack2-import",
"node-sass": "^3.11.0",
"node-sass": "^3.11.2",
"optimize-css-assets-webpack-plugin": "^1.3.0",
"postcss": "^5.2.5",
"postcss-loader": "^1.1.0",
Expand Down
Loading

0 comments on commit c49793c

Please sign in to comment.