Add images to assets manifest
This commit is contained in:
@@ -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');
|
||||
@@ -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: {
|
||||
@@ -29,11 +29,8 @@ 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')));
|
||||
@@ -41,8 +38,6 @@ Object.keys(config.entry).forEach(id =>
|
||||
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 } });
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
@@ -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
assets/build/util/interpolateName.js
Normal file
50
assets/build/util/interpolateName.js
Normal file
@@ -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
assets/build/util/promisify.js
Normal file
21
assets/build/util/promisify.js
Normal file
@@ -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);
|
||||
});
|
||||
};
|
||||
@@ -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');
|
||||
@@ -75,7 +76,7 @@ const webpackConfig = {
|
||||
include: config.paths.assets,
|
||||
loaders: [
|
||||
`file?${qs.stringify({
|
||||
name: '[path][name].[ext]',
|
||||
name: `[path]${assetsFilenames}.[ext]`,
|
||||
})}`,
|
||||
],
|
||||
},
|
||||
@@ -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({
|
||||
|
||||
@@ -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,
|
||||
|
||||
143
assets/build/webpack.plugin.copyglobs.js
Normal file
143
assets/build/webpack.plugin.copyglobs.js
Normal file
@@ -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];
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user