@rollup-extras/plugin-copy
Version:
Rollup plugin to copy assets during build.
172 lines (169 loc) • 6.95 kB
JavaScript
import fs from 'fs/promises';
import fs_ from 'fs';
import path from 'path';
import { glob } from 'glob';
import globParent from 'glob-parent';
import { getOptions } from '@rollup-extras/utils/options';
import logger from '@rollup-extras/utils/logger';
import statistics from '@rollup-extras/utils/statistics';
const factories = { targets, logger };
const listFilenames = 'list-filenames';
function index (options) {
const files = new Map();
const normalizedOptions = getOptions(options, {
pluginName: '@rollup-extras/plugin-copy',
copyOnce: true,
flatten: false,
verbose: false,
exactFileNames: true,
watch: true,
emitFiles: true,
outputPlugin: false,
emitOriginalFileName: 'absolute'
}, 'targets', factories);
const { pluginName, copyOnce, verbose, exactFileNames, targets, outputPlugin, flatten, emitFiles, logger, emitOriginalFileName } = normalizedOptions;
let { watch } = normalizedOptions;
const hookName = outputPlugin ? 'generateBundle' : emitFiles ? 'buildStart' : 'buildEnd';
if (!outputPlugin && !emitFiles && watch) {
watch = false;
logger('can\'t use watch with emitFiles = false and outputPlugin = false', 0 /* LogLevel.verbose */);
}
if (outputPlugin && watch) {
watch = false;
logger('can\'t use watch with outputPlugin = true', 0 /* LogLevel.verbose */);
}
return {
name: pluginName,
async [hookName]() {
const results = await Promise.all(targets
.flatMap(target => Array.isArray(target.src) ? target.src.map(itemSrc => ({
...target,
src: itemSrc
})) : target)
.map(target => glob(target.src, { ignore: target.exclude })
.then(result => ({
src: result,
dest: target.dest ? target.dest : '',
parent: globParent(target.src)
}))));
for (const result of results) {
for (const file of result.src) {
let fileDesc;
if (files.has(file)) {
fileDesc = files.get(file);
}
else {
fileDesc = {
dest: [],
copied: [],
timestamp: 0
};
files.set(file, fileDesc);
}
const dest = flatten ? normalizeSlash(result.dest) : path.join(result.dest, path.relative(result.parent, path.dirname(file)));
if (!fileDesc.dest.includes(dest)) {
fileDesc.dest.push(dest);
}
// don't forget to watch it
if (watch) {
this.addWatchFile(file);
}
}
}
const statisticsCollector = statistics(verbose === listFilenames, (result) => `copied ${typeof result == 'number' ? result + ' files' : result.join(', ')}`);
logger.start('copying files', verbose ? 1 /* LogLevel.info */ : 0 /* LogLevel.verbose */);
await Promise.all([...files].map(async ([fileName, fileDesc]) => {
let source;
try {
const fileStat = await fs.stat(fileName);
if (!fileStat.isFile()) {
return;
}
const timestamp = fileStat.mtime.getTime();
if (timestamp > fileDesc.timestamp) {
fileDesc.timestamp = timestamp;
fileDesc.copied = [];
}
if (emitFiles) {
source = await fs.readFile(fileName);
}
}
catch (e) {
const loglevel = e['code'] === 'ENOENT' ? undefined : 2 /* LogLevel.warn */;
logger(`error reading file ${fileName}`, loglevel, e);
return;
}
for (const dest of fileDesc.dest) {
if (copyOnce && fileDesc.copied.includes(dest)) {
continue;
}
const baseName = path.basename(fileName);
// path.join removes ./ from the beginning, that's needed for rollup name/fileName fields
const destFileName = path.join(dest, baseName);
try {
if (emitFiles) {
this.emitFile({
type: 'asset',
[exactFileNames ? 'fileName' : 'name']: destFileName,
source: source,
originalFileName: getOriginalFileName(fileName, emitOriginalFileName)
});
}
else {
await fs.mkdir(path.dirname(destFileName), { recursive: true });
await fs.copyFile(fileName, destFileName, fs_.constants.COPYFILE_FICLONE);
}
if (verbose === listFilenames) {
logger(`\t${fileName} → ${destFileName}`, 1 /* LogLevel.info */);
}
statisticsCollector(baseName);
fileDesc.copied.push(dest);
}
catch (e) {
logger(`error copying file ${fileName} → ${destFileName}`, 2 /* LogLevel.warn */, e);
}
}
}));
logger.finish(statisticsCollector());
}
};
}
function normalizeSlash(dir) {
if (dir.endsWith('/')) {
return `${dir.substring(0, dir.length - 1)}`;
}
return dir;
}
function targets(options, field) {
let targets = options[field];
if (targets == null) {
targets = [options];
}
if (Array.isArray(targets)) {
targets = targets.map((item) => {
if (item) {
if (typeof item === 'string') {
return { src: item };
}
if (typeof item === 'object' && 'src' in item) {
return item;
}
}
return undefined;
}).filter(Boolean);
}
return targets;
}
function getOriginalFileName(fileName, emitOriginalFileName) {
if (emitOriginalFileName === 'relative') {
return fileName;
}
if (emitOriginalFileName === 'absolute') {
return path.resolve(fileName);
}
if (typeof emitOriginalFileName === 'function') {
return emitOriginalFileName(fileName);
}
return undefined;
}
export { index as default };