copy-webpack-plugin
Advanced tools
Comparing version
1053
dist/index.js
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.default = void 0; | ||
const path = require("path"); | ||
var _path = _interopRequireDefault(require("path")); | ||
const { | ||
validate | ||
} = require("schema-utils"); | ||
var _os = _interopRequireDefault(require("os")); | ||
const serialize = require("serialize-javascript"); | ||
var _crypto = _interopRequireDefault(require("crypto")); | ||
const normalizePath = require("normalize-path"); | ||
var _webpack = _interopRequireDefault(require("webpack")); | ||
const globParent = require("glob-parent"); | ||
var _schemaUtils = require("schema-utils"); | ||
const fastGlob = require("fast-glob"); // @ts-ignore | ||
var _pLimit = _interopRequireDefault(require("p-limit")); | ||
var _globby = _interopRequireDefault(require("globby")); | ||
const { | ||
version | ||
} = require("../package.json"); | ||
var _findCacheDir = _interopRequireDefault(require("find-cache-dir")); | ||
const schema = require("./options.json"); | ||
var _serializeJavascript = _interopRequireDefault(require("serialize-javascript")); | ||
const { | ||
readFile, | ||
stat, | ||
throttleAll | ||
} = require("./utils"); | ||
var _cacache = _interopRequireDefault(require("cacache")); | ||
const template = /\[\\*([\w:]+)\\*\]/i; | ||
/** @typedef {import("schema-utils/declarations/validate").Schema} Schema */ | ||
var _loaderUtils = _interopRequireDefault(require("loader-utils")); | ||
/** @typedef {import("webpack").Compiler} Compiler */ | ||
var _normalizePath = _interopRequireDefault(require("normalize-path")); | ||
/** @typedef {import("webpack").Compilation} Compilation */ | ||
var _globParent = _interopRequireDefault(require("glob-parent")); | ||
/** @typedef {import("webpack").WebpackError} WebpackError */ | ||
var _fastGlob = _interopRequireDefault(require("fast-glob")); | ||
/** @typedef {import("webpack").Asset} Asset */ | ||
var _package = require("../package.json"); | ||
/** @typedef {import("globby").Options} GlobbyOptions */ | ||
var _options = _interopRequireDefault(require("./options.json")); | ||
/** @typedef {import("globby").GlobEntry} GlobEntry */ | ||
var _promisify = require("./utils/promisify"); | ||
/** @typedef {ReturnType<Compilation["getLogger"]>} WebpackLogger */ | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
/** @typedef {ReturnType<Compilation["getCache"]>} CacheFacade */ | ||
// webpack 5 exposes the sources property to ensure the right version of webpack-sources is used | ||
const { | ||
RawSource | ||
} = // eslint-disable-next-line global-require | ||
_webpack.default.sources || require("webpack-sources"); | ||
/** @typedef {ReturnType<ReturnType<Compilation["getCache"]>["getLazyHashedEtag"]>} Etag */ | ||
const template = /(\[ext\])|(\[name\])|(\[path\])|(\[folder\])|(\[emoji(?::(\d+))?\])|(\[(?:([^:\]]+):)?(?:hash|contenthash)(?::([a-z]+\d*))?(?::(\d+))?\])|(\[\d+\])/; | ||
/** @typedef {ReturnType<Compilation["fileSystemInfo"]["mergeSnapshots"]>} Snapshot */ | ||
/** | ||
* @typedef {boolean} Force | ||
*/ | ||
/** | ||
* @typedef {Object} CopiedResult | ||
* @property {string} sourceFilename | ||
* @property {string} absoluteFilename | ||
* @property {string} filename | ||
* @property {Asset["source"]} source | ||
* @property {Force | undefined} force | ||
* @property {Record<string, any>} info | ||
*/ | ||
/** | ||
* @typedef {string} StringPattern | ||
*/ | ||
/** | ||
* @typedef {boolean} NoErrorOnMissing | ||
*/ | ||
/** | ||
* @typedef {string} Context | ||
*/ | ||
/** | ||
* @typedef {string} From | ||
*/ | ||
/** | ||
* @callback ToFunction | ||
* @param {{ context: string, absoluteFilename?: string }} pathData | ||
* @return {string | Promise<string>} | ||
*/ | ||
/** | ||
* @typedef {string | ToFunction} To | ||
*/ | ||
/** | ||
* @typedef {"dir" | "file" | "template"} ToType | ||
*/ | ||
/** | ||
* @callback TransformerFunction | ||
* @param {Buffer} input | ||
* @param {string} absoluteFilename | ||
* @returns {string | Buffer | Promise<string> | Promise<Buffer>} | ||
*/ | ||
/** | ||
* @typedef {{ keys: { [key: string]: any } } | { keys: ((defaultCacheKeys: { [key: string]: any }, absoluteFilename: string) => Promise<{ [key: string]: any }>) }} TransformerCacheObject | ||
*/ | ||
/** | ||
* @typedef {Object} TransformerObject | ||
* @property {TransformerFunction} transformer | ||
* @property {boolean | TransformerCacheObject} [cache] | ||
*/ | ||
/** | ||
* @typedef {TransformerFunction | TransformerObject} Transform | ||
*/ | ||
/** | ||
* @callback Filter | ||
* @param {string} filepath | ||
* @returns {boolean | Promise<boolean>} | ||
*/ | ||
/** | ||
* @callback TransformAllFunction | ||
* @param {{ data: Buffer, sourceFilename: string, absoluteFilename: string }[]} data | ||
* @returns {string | Buffer | Promise<string> | Promise<Buffer>} | ||
*/ | ||
/** | ||
* @typedef { Record<string, any> | ((item: { absoluteFilename: string, sourceFilename: string, filename: string, toType: ToType }) => Record<string, any>) } Info | ||
*/ | ||
/** | ||
* @typedef {Object} ObjectPattern | ||
* @property {From} from | ||
* @property {GlobbyOptions} [globOptions] | ||
* @property {Context} [context] | ||
* @property {To} [to] | ||
* @property {ToType} [toType] | ||
* @property {Info} [info] | ||
* @property {Filter} [filter] | ||
* @property {Transform} [transform] | ||
* @property {TransformAllFunction} [transformAll] | ||
* @property {Force} [force] | ||
* @property {number} [priority] | ||
* @property {NoErrorOnMissing} [noErrorOnMissing] | ||
*/ | ||
/** | ||
* @typedef {StringPattern | ObjectPattern} Pattern | ||
*/ | ||
/** | ||
* @typedef {Object} AdditionalOptions | ||
* @property {number} [concurrency] | ||
*/ | ||
/** | ||
* @typedef {Object} PluginOptions | ||
* @property {Pattern[]} patterns | ||
* @property {AdditionalOptions} [options] | ||
*/ | ||
class CopyPlugin { | ||
constructor(options = {}) { | ||
(0, _schemaUtils.validate)(_options.default, options, { | ||
/** | ||
* @param {PluginOptions} [options] | ||
*/ | ||
constructor(options = { | ||
patterns: [] | ||
}) { | ||
validate( | ||
/** @type {Schema} */ | ||
schema, options, { | ||
name: "Copy Plugin", | ||
baseDataPath: "options" | ||
}); | ||
/** | ||
* @private | ||
* @type {Pattern[]} | ||
*/ | ||
this.patterns = options.patterns; | ||
/** | ||
* @private | ||
* @type {AdditionalOptions} | ||
*/ | ||
this.options = options.options || {}; | ||
} | ||
/** | ||
* @private | ||
* @param {Compilation} compilation | ||
* @param {number} startTime | ||
* @param {string} dependency | ||
* @returns {Promise<Snapshot | undefined>} | ||
*/ | ||
static async createSnapshot(compilation, startTime, dependency) { | ||
if (!compilation.fileSystemInfo) { | ||
return; | ||
} // eslint-disable-next-line consistent-return | ||
// eslint-disable-next-line consistent-return | ||
return new Promise((resolve, reject) => { | ||
compilation.fileSystemInfo.createSnapshot(startTime, [dependency], // eslint-disable-next-line no-undefined | ||
compilation.fileSystemInfo.createSnapshot(startTime, [dependency], // @ts-ignore | ||
// eslint-disable-next-line no-undefined | ||
undefined, // eslint-disable-next-line no-undefined | ||
@@ -77,13 +212,18 @@ undefined, null, (error, snapshot) => { | ||
resolve(snapshot); | ||
resolve( | ||
/** @type {Snapshot} */ | ||
snapshot); | ||
}); | ||
}); | ||
} | ||
/** | ||
* @private | ||
* @param {Compilation} compilation | ||
* @param {Snapshot} snapshot | ||
* @returns {Promise<boolean | undefined>} | ||
*/ | ||
static async checkSnapshotValid(compilation, snapshot) { | ||
if (!compilation.fileSystemInfo) { | ||
return; | ||
} // eslint-disable-next-line consistent-return | ||
// eslint-disable-next-line consistent-return | ||
return new Promise((resolve, reject) => { | ||
@@ -100,21 +240,64 @@ compilation.fileSystemInfo.checkSnapshotValid(snapshot, (error, isValid) => { | ||
} | ||
/** | ||
* @private | ||
* @param {Compiler} compiler | ||
* @param {Compilation} compilation | ||
* @param {Buffer} source | ||
* @returns {string} | ||
*/ | ||
static async runPattern(compiler, compilation, logger, cache, inputPattern, index) { | ||
const pattern = typeof inputPattern === "string" ? { | ||
from: inputPattern | ||
} : { ...inputPattern | ||
static getContentHash(compiler, compilation, source) { | ||
const { | ||
outputOptions | ||
} = compilation; | ||
const { | ||
hashDigest, | ||
hashDigestLength, | ||
hashFunction, | ||
hashSalt | ||
} = outputOptions; | ||
const hash = compiler.webpack.util.createHash( | ||
/** @type {string} */ | ||
hashFunction); | ||
if (hashSalt) { | ||
hash.update(hashSalt); | ||
} | ||
hash.update(source); | ||
const fullContentHash = hash.digest(hashDigest); | ||
return fullContentHash.toString().slice(0, hashDigestLength); | ||
} | ||
/** | ||
* @private | ||
* @param {typeof import("globby").globby} globby | ||
* @param {Compiler} compiler | ||
* @param {Compilation} compilation | ||
* @param {WebpackLogger} logger | ||
* @param {CacheFacade} cache | ||
* @param {ObjectPattern & { context: string }} inputPattern | ||
* @param {number} index | ||
* @returns {Promise<Array<CopiedResult | undefined> | undefined>} | ||
*/ | ||
static async runPattern(globby, compiler, compilation, logger, cache, inputPattern, index) { | ||
const { | ||
RawSource | ||
} = compiler.webpack.sources; | ||
const pattern = { ...inputPattern | ||
}; | ||
pattern.fromOrigin = pattern.from; | ||
pattern.from = _path.default.normalize(pattern.from); | ||
pattern.compilerContext = compiler.context; | ||
pattern.context = _path.default.normalize(typeof pattern.context !== "undefined" ? !_path.default.isAbsolute(pattern.context) ? _path.default.join(pattern.compilerContext, pattern.context) : pattern.context : pattern.compilerContext); | ||
logger.log(`starting to process a pattern from '${pattern.from}' using '${pattern.context}' context`); | ||
const originalFrom = pattern.from; | ||
const normalizedOriginalFrom = path.normalize(originalFrom); | ||
logger.log(`starting to process a pattern from '${normalizedOriginalFrom}' using '${pattern.context}' context`); | ||
let absoluteFrom; | ||
if (_path.default.isAbsolute(pattern.from)) { | ||
pattern.absoluteFrom = pattern.from; | ||
if (path.isAbsolute(normalizedOriginalFrom)) { | ||
absoluteFrom = normalizedOriginalFrom; | ||
} else { | ||
pattern.absoluteFrom = _path.default.resolve(pattern.context, pattern.from); | ||
absoluteFrom = path.resolve(pattern.context, normalizedOriginalFrom); | ||
} | ||
logger.debug(`getting stats for '${pattern.absoluteFrom}'...`); | ||
logger.debug(`getting stats for '${absoluteFrom}'...`); | ||
const { | ||
@@ -126,20 +309,32 @@ inputFileSystem | ||
try { | ||
stats = await (0, _promisify.stat)(inputFileSystem, pattern.absoluteFrom); | ||
stats = await stat(inputFileSystem, absoluteFrom); | ||
} catch (error) {// Nothing | ||
} | ||
/** | ||
* @type {"file" | "dir" | "glob"} | ||
*/ | ||
let fromType; | ||
if (stats) { | ||
if (stats.isDirectory()) { | ||
pattern.fromType = "dir"; | ||
logger.debug(`determined '${pattern.absoluteFrom}' is a directory`); | ||
fromType = "dir"; | ||
logger.debug(`determined '${absoluteFrom}' is a directory`); | ||
} else if (stats.isFile()) { | ||
pattern.fromType = "file"; | ||
logger.debug(`determined '${pattern.absoluteFrom}' is a file`); | ||
fromType = "file"; | ||
logger.debug(`determined '${absoluteFrom}' is a file`); | ||
} else { | ||
logger.debug(`determined '${pattern.absoluteFrom}' is a glob`); | ||
// Fallback | ||
fromType = "glob"; | ||
logger.debug(`determined '${absoluteFrom}' is unknown`); | ||
} | ||
} // eslint-disable-next-line no-param-reassign | ||
} else { | ||
fromType = "glob"; | ||
logger.debug(`determined '${absoluteFrom}' is a glob`); | ||
} | ||
/** @type {GlobbyOptions & { objectMode: true }} */ | ||
pattern.globOptions = { ...{ | ||
const globOptions = { ...{ | ||
followSymbolicLinks: true | ||
@@ -152,218 +347,181 @@ }, | ||
} | ||
}; // TODO remove after drop webpack@4 | ||
}; // @ts-ignore | ||
if (compiler.webpack && inputFileSystem.lstat && inputFileSystem.stat && inputFileSystem.lstatSync && inputFileSystem.statSync && inputFileSystem.readdir && inputFileSystem.readdirSync) { | ||
pattern.globOptions.fs = inputFileSystem; | ||
} | ||
globOptions.fs = inputFileSystem; | ||
let glob; | ||
switch (pattern.fromType) { | ||
switch (fromType) { | ||
case "dir": | ||
compilation.contextDependencies.add(pattern.absoluteFrom); | ||
logger.debug(`added '${pattern.absoluteFrom}' as a context dependency`); | ||
/* eslint-disable no-param-reassign */ | ||
compilation.contextDependencies.add(absoluteFrom); | ||
logger.debug(`added '${absoluteFrom}' as a context dependency`); | ||
pattern.context = absoluteFrom; | ||
glob = path.posix.join(fastGlob.escapePath(normalizePath(path.resolve(absoluteFrom))), "**/*"); | ||
absoluteFrom = path.join(absoluteFrom, "**/*"); | ||
pattern.context = pattern.absoluteFrom; | ||
pattern.glob = _path.default.posix.join(_fastGlob.default.escapePath((0, _normalizePath.default)(_path.default.resolve(pattern.absoluteFrom))), "**/*"); | ||
pattern.absoluteFrom = _path.default.join(pattern.absoluteFrom, "**/*"); | ||
if (typeof pattern.globOptions.dot === "undefined") { | ||
pattern.globOptions.dot = true; | ||
if (typeof globOptions.dot === "undefined") { | ||
globOptions.dot = true; | ||
} | ||
/* eslint-enable no-param-reassign */ | ||
break; | ||
case "file": | ||
compilation.fileDependencies.add(pattern.absoluteFrom); | ||
logger.debug(`added '${pattern.absoluteFrom}' as a file dependency`); | ||
/* eslint-disable no-param-reassign */ | ||
compilation.fileDependencies.add(absoluteFrom); | ||
logger.debug(`added '${absoluteFrom}' as a file dependency`); | ||
pattern.context = path.dirname(absoluteFrom); | ||
glob = fastGlob.escapePath(normalizePath(path.resolve(absoluteFrom))); | ||
pattern.context = _path.default.dirname(pattern.absoluteFrom); | ||
pattern.glob = _fastGlob.default.escapePath((0, _normalizePath.default)(_path.default.resolve(pattern.absoluteFrom))); | ||
if (typeof pattern.globOptions.dot === "undefined") { | ||
pattern.globOptions.dot = true; | ||
if (typeof globOptions.dot === "undefined") { | ||
globOptions.dot = true; | ||
} | ||
/* eslint-enable no-param-reassign */ | ||
break; | ||
case "glob": | ||
default: | ||
{ | ||
const contextDependencies = _path.default.normalize((0, _globParent.default)(pattern.absoluteFrom)); | ||
const contextDependencies = path.normalize(globParent(absoluteFrom)); | ||
compilation.contextDependencies.add(contextDependencies); | ||
logger.debug(`added '${contextDependencies}' as a context dependency`); | ||
/* eslint-disable no-param-reassign */ | ||
pattern.fromType = "glob"; | ||
pattern.glob = _path.default.isAbsolute(pattern.fromOrigin) ? pattern.fromOrigin : _path.default.posix.join(_fastGlob.default.escapePath((0, _normalizePath.default)(_path.default.resolve(pattern.context))), pattern.fromOrigin); | ||
/* eslint-enable no-param-reassign */ | ||
glob = path.isAbsolute(originalFrom) ? originalFrom : path.posix.join(fastGlob.escapePath(normalizePath(path.resolve(pattern.context))), originalFrom); | ||
} | ||
} | ||
logger.log(`begin globbing '${pattern.glob}'...`); | ||
let paths; | ||
logger.log(`begin globbing '${glob}'...`); | ||
/** | ||
* @type {GlobEntry[]} | ||
*/ | ||
let globEntries; | ||
try { | ||
paths = await (0, _globby.default)(pattern.glob, pattern.globOptions); | ||
globEntries = await globby(glob, globOptions); | ||
} catch (error) { | ||
compilation.errors.push(error); | ||
compilation.errors.push( | ||
/** @type {WebpackError} */ | ||
error); | ||
return; | ||
} | ||
if (paths.length === 0) { | ||
if (globEntries.length === 0) { | ||
if (pattern.noErrorOnMissing) { | ||
logger.log(`finished to process a pattern from '${pattern.from}' using '${pattern.context}' context to '${pattern.to}'`); | ||
logger.log(`finished to process a pattern from '${normalizedOriginalFrom}' using '${pattern.context}' context to '${pattern.to}'`); | ||
return; | ||
} | ||
const missingError = new Error(`unable to locate '${pattern.glob}' glob`); | ||
compilation.errors.push(missingError); | ||
const missingError = new Error(`unable to locate '${glob}' glob`); | ||
compilation.errors.push( | ||
/** @type {WebpackError} */ | ||
missingError); | ||
return; | ||
} | ||
/** | ||
* @type {Array<CopiedResult | undefined>} | ||
*/ | ||
const filteredPaths = (await Promise.all(paths.map(async item => { | ||
// Exclude directories | ||
if (!item.dirent.isFile()) { | ||
return false; | ||
} | ||
if (pattern.filter) { | ||
let isFiltered; | ||
let copiedResult; | ||
try { | ||
isFiltered = await pattern.filter(item.path); | ||
} catch (error) { | ||
compilation.errors.push(error); | ||
return false; | ||
try { | ||
copiedResult = await Promise.all(globEntries.map( | ||
/** | ||
* @param {GlobEntry} globEntry | ||
* @returns {Promise<CopiedResult | undefined>} | ||
*/ | ||
async globEntry => { | ||
// Exclude directories | ||
if (!globEntry.dirent.isFile()) { | ||
return; | ||
} | ||
if (!isFiltered) { | ||
logger.log(`skip '${item.path}', because it was filtered`); | ||
} | ||
if (pattern.filter) { | ||
let isFiltered; | ||
return isFiltered ? item : false; | ||
} | ||
try { | ||
isFiltered = await pattern.filter(globEntry.path); | ||
} catch (error) { | ||
compilation.errors.push( | ||
/** @type {WebpackError} */ | ||
error); | ||
return; | ||
} | ||
return item; | ||
}))).filter(item => item); | ||
if (!isFiltered) { | ||
logger.log(`skip '${globEntry.path}', because it was filtered`); | ||
return; | ||
} | ||
} | ||
if (filteredPaths.length === 0) { | ||
// TODO should be error in the next major release | ||
logger.log(`finished to process a pattern from '${pattern.from}' using '${pattern.context}' context to '${pattern.to}'`); | ||
return; | ||
} | ||
const from = globEntry.path; | ||
logger.debug(`found '${from}'`); // `globby`/`fast-glob` return the relative path when the path contains special characters on windows | ||
const files = await Promise.all(filteredPaths.map(async item => { | ||
const from = item.path; | ||
logger.debug(`found '${from}'`); // `globby`/`fast-glob` return the relative path when the path contains special characters on windows | ||
const absoluteFilename = path.resolve(pattern.context, from); | ||
const to = typeof pattern.to === "function" ? await pattern.to({ | ||
context: pattern.context, | ||
absoluteFilename | ||
}) : path.normalize(typeof pattern.to !== "undefined" ? pattern.to : ""); | ||
const toType = pattern.toType ? pattern.toType : template.test(to) ? "template" : path.extname(to) === "" || to.slice(-1) === path.sep ? "dir" : "file"; | ||
logger.log(`'to' option '${to}' determinated as '${toType}'`); | ||
const relativeFrom = path.relative(pattern.context, absoluteFilename); | ||
let filename = toType === "dir" ? path.join(to, relativeFrom) : to; | ||
const absoluteFilename = _path.default.resolve(pattern.context, from); | ||
if (path.isAbsolute(filename)) { | ||
filename = path.relative( | ||
/** @type {string} */ | ||
compiler.options.output.path, filename); | ||
} | ||
pattern.to = typeof pattern.to !== "function" ? _path.default.normalize(typeof pattern.to !== "undefined" ? pattern.to : "") : await pattern.to({ | ||
context: pattern.context, | ||
absoluteFilename | ||
}); | ||
logger.log(`determined that '${from}' should write to '${filename}'`); | ||
const sourceFilename = normalizePath(path.relative(compiler.context, absoluteFilename)); // If this came from a glob or dir, add it to the file dependencies | ||
const isToDirectory = _path.default.extname(pattern.to) === "" || pattern.to.slice(-1) === _path.default.sep; | ||
if (fromType === "dir" || fromType === "glob") { | ||
compilation.fileDependencies.add(absoluteFilename); | ||
logger.debug(`added '${absoluteFilename}' as a file dependency`); | ||
} | ||
switch (true) { | ||
// if toType already exists | ||
case !!pattern.toType: | ||
break; | ||
let cacheEntry; | ||
logger.debug(`getting cache for '${absoluteFilename}'...`); | ||
case template.test(pattern.to): | ||
pattern.toType = "template"; | ||
break; | ||
try { | ||
cacheEntry = await cache.getPromise(`${sourceFilename}|${index}`, null); | ||
} catch (error) { | ||
compilation.errors.push( | ||
/** @type {WebpackError} */ | ||
error); | ||
return; | ||
} | ||
/** | ||
* @type {Asset["source"] | undefined} | ||
*/ | ||
case isToDirectory: | ||
pattern.toType = "dir"; | ||
break; | ||
default: | ||
pattern.toType = "file"; | ||
} | ||
let source; | ||
logger.log(`'to' option '${pattern.to}' determinated as '${pattern.toType}'`); | ||
const relativeFrom = pattern.flatten ? _path.default.basename(absoluteFilename) : _path.default.relative(pattern.context, absoluteFilename); | ||
let filename = pattern.toType === "dir" ? _path.default.join(pattern.to, relativeFrom) : pattern.to; | ||
if (cacheEntry) { | ||
logger.debug(`found cache for '${absoluteFilename}'...`); | ||
let isValidSnapshot; | ||
logger.debug(`checking snapshot on valid for '${absoluteFilename}'...`); | ||
if (_path.default.isAbsolute(filename)) { | ||
filename = _path.default.relative(compiler.options.output.path, filename); | ||
} | ||
logger.log(`determined that '${from}' should write to '${filename}'`); | ||
const sourceFilename = (0, _normalizePath.default)(_path.default.relative(pattern.compilerContext, absoluteFilename)); | ||
return { | ||
absoluteFilename, | ||
sourceFilename, | ||
filename | ||
}; | ||
})); | ||
let assets; | ||
try { | ||
assets = await Promise.all(files.map(async file => { | ||
const { | ||
absoluteFilename, | ||
sourceFilename, | ||
filename | ||
} = file; | ||
const result = { | ||
absoluteFilename, | ||
sourceFilename, | ||
filename, | ||
force: pattern.force, | ||
info: typeof pattern.info === "function" ? pattern.info(file) || {} : pattern.info || {} | ||
}; // If this came from a glob or dir, add it to the file dependencies | ||
if (pattern.fromType === "dir" || pattern.fromType === "glob") { | ||
compilation.fileDependencies.add(absoluteFilename); | ||
logger.debug(`added '${absoluteFilename}' as a file dependency`); | ||
} | ||
if (cache) { | ||
let cacheEntry; | ||
logger.debug(`getting cache for '${absoluteFilename}'...`); | ||
try { | ||
cacheEntry = await cache.getPromise(`${sourceFilename}|${index}`, null); | ||
isValidSnapshot = await CopyPlugin.checkSnapshotValid(compilation, cacheEntry.snapshot); | ||
} catch (error) { | ||
compilation.errors.push(error); | ||
compilation.errors.push( | ||
/** @type {WebpackError} */ | ||
error); | ||
return; | ||
} | ||
if (cacheEntry) { | ||
logger.debug(`found cache for '${absoluteFilename}'...`); | ||
let isValidSnapshot; | ||
logger.debug(`checking snapshot on valid for '${absoluteFilename}'...`); | ||
try { | ||
isValidSnapshot = await CopyPlugin.checkSnapshotValid(compilation, cacheEntry.snapshot); | ||
} catch (error) { | ||
compilation.errors.push(error); | ||
return; | ||
} | ||
if (isValidSnapshot) { | ||
logger.debug(`snapshot for '${absoluteFilename}' is valid`); | ||
result.source = cacheEntry.source; | ||
} else { | ||
logger.debug(`snapshot for '${absoluteFilename}' is invalid`); | ||
} | ||
if (isValidSnapshot) { | ||
logger.debug(`snapshot for '${absoluteFilename}' is valid`); | ||
({ | ||
source | ||
} = cacheEntry); | ||
} else { | ||
logger.debug(`missed cache for '${absoluteFilename}'`); | ||
logger.debug(`snapshot for '${absoluteFilename}' is invalid`); | ||
} | ||
} else { | ||
logger.debug(`missed cache for '${absoluteFilename}'`); | ||
} | ||
if (!result.source) { | ||
let startTime; | ||
if (cache) { | ||
startTime = Date.now(); | ||
} | ||
if (!source) { | ||
const startTime = Date.now(); | ||
logger.debug(`reading '${absoluteFilename}'...`); | ||
@@ -373,5 +531,7 @@ let data; | ||
try { | ||
data = await (0, _promisify.readFile)(inputFileSystem, absoluteFilename); | ||
data = await readFile(inputFileSystem, absoluteFilename); | ||
} catch (error) { | ||
compilation.errors.push(error); | ||
compilation.errors.push( | ||
/** @type {WebpackError} */ | ||
error); | ||
return; | ||
@@ -381,30 +541,75 @@ } | ||
logger.debug(`read '${absoluteFilename}'`); | ||
result.source = new RawSource(data); | ||
source = new RawSource(data); | ||
let snapshot; | ||
logger.debug(`creating snapshot for '${absoluteFilename}'...`); | ||
if (cache) { | ||
let snapshot; | ||
logger.debug(`creating snapshot for '${absoluteFilename}'...`); | ||
try { | ||
snapshot = await CopyPlugin.createSnapshot(compilation, startTime, absoluteFilename); | ||
} catch (error) { | ||
compilation.errors.push( | ||
/** @type {WebpackError} */ | ||
error); | ||
return; | ||
} | ||
if (snapshot) { | ||
logger.debug(`created snapshot for '${absoluteFilename}'`); | ||
logger.debug(`storing cache for '${absoluteFilename}'...`); | ||
try { | ||
snapshot = await CopyPlugin.createSnapshot(compilation, startTime, absoluteFilename); | ||
await cache.storePromise(`${sourceFilename}|${index}`, null, { | ||
source, | ||
snapshot | ||
}); | ||
} catch (error) { | ||
compilation.errors.push(error); | ||
compilation.errors.push( | ||
/** @type {WebpackError} */ | ||
error); | ||
return; | ||
} | ||
if (snapshot) { | ||
logger.debug(`created snapshot for '${absoluteFilename}'`); | ||
logger.debug(`storing cache for '${absoluteFilename}'...`); | ||
logger.debug(`stored cache for '${absoluteFilename}'`); | ||
} | ||
} | ||
try { | ||
await cache.storePromise(`${sourceFilename}|${index}`, null, { | ||
source: result.source, | ||
snapshot | ||
}); | ||
} catch (error) { | ||
compilation.errors.push(error); | ||
return; | ||
if (pattern.transform) { | ||
/** | ||
* @type {TransformerObject} | ||
*/ | ||
const transformObj = typeof pattern.transform === "function" ? { | ||
transformer: pattern.transform | ||
} : pattern.transform; | ||
if (transformObj.transformer) { | ||
logger.log(`transforming content for '${absoluteFilename}'...`); | ||
const buffer = source.buffer(); | ||
if (transformObj.cache) { | ||
// TODO: remove in the next major release | ||
const hasher = compiler.webpack && compiler.webpack.util && compiler.webpack.util.createHash ? compiler.webpack.util.createHash("xxhash64") : // eslint-disable-next-line global-require | ||
require("crypto").createHash("md4"); | ||
const defaultCacheKeys = { | ||
version, | ||
sourceFilename, | ||
transform: transformObj.transformer, | ||
contentHash: hasher.update(buffer).digest("hex"), | ||
index | ||
}; | ||
const cacheKeys = `transform|${serialize(typeof transformObj.cache === "boolean" ? defaultCacheKeys : typeof transformObj.cache.keys === "function" ? await transformObj.cache.keys(defaultCacheKeys, absoluteFilename) : { ...defaultCacheKeys, | ||
...transformObj.cache.keys | ||
})}`; | ||
logger.debug(`getting transformation cache for '${absoluteFilename}'...`); | ||
const cacheItem = cache.getItemCache(cacheKeys, cache.getLazyHashedEtag(source)); | ||
source = await cacheItem.getPromise(); | ||
logger.debug(source ? `found transformation cache for '${absoluteFilename}'` : `no transformation cache for '${absoluteFilename}'`); | ||
if (!source) { | ||
const transformed = await transformObj.transformer(buffer, absoluteFilename); | ||
source = new RawSource(transformed); | ||
logger.debug(`caching transformation for '${absoluteFilename}'...`); | ||
await cacheItem.storePromise(source); | ||
logger.debug(`cached transformation for '${absoluteFilename}'`); | ||
} | ||
logger.debug(`stored cache for '${absoluteFilename}'`); | ||
} else { | ||
source = new RawSource(await transformObj.transformer(buffer, absoluteFilename)); | ||
} | ||
@@ -414,135 +619,262 @@ } | ||
if (pattern.transform) { | ||
logger.log(`transforming content for '${absoluteFilename}'...`); | ||
const buffer = result.source.source(); | ||
let info = typeof pattern.info === "undefined" ? {} : typeof pattern.info === "function" ? pattern.info({ | ||
absoluteFilename, | ||
sourceFilename, | ||
filename, | ||
toType | ||
}) || {} : pattern.info || {}; | ||
if (pattern.cacheTransform) { | ||
const defaultCacheKeys = { | ||
version: _package.version, | ||
if (toType === "template") { | ||
logger.log(`interpolating template '${filename}' for '${sourceFilename}'...`); | ||
const contentHash = CopyPlugin.getContentHash(compiler, compilation, source.buffer()); | ||
const ext = path.extname(sourceFilename); | ||
const base = path.basename(sourceFilename); | ||
const name = base.slice(0, base.length - ext.length); | ||
const data = { | ||
filename: normalizePath(path.relative(pattern.context, absoluteFilename)), | ||
contentHash, | ||
chunk: { | ||
name, | ||
id: | ||
/** @type {string} */ | ||
sourceFilename, | ||
transform: pattern.transform, | ||
contentHash: _crypto.default.createHash("md4").update(buffer).digest("hex"), | ||
index | ||
}; | ||
const cacheKeys = `transform|${(0, _serializeJavascript.default)(typeof pattern.cacheTransform.keys === "function" ? await pattern.cacheTransform.keys(defaultCacheKeys, absoluteFilename) : { ...defaultCacheKeys, | ||
...pattern.cacheTransform.keys | ||
})}`; | ||
let cacheItem; | ||
let cacheDirectory; | ||
logger.debug(`getting transformation cache for '${absoluteFilename}'...`); // webpack@5 API | ||
hash: contentHash | ||
} | ||
}; | ||
const { | ||
path: interpolatedFilename, | ||
info: assetInfo | ||
} = compilation.getPathWithInfo(normalizePath(filename), data); | ||
info = { ...info, | ||
...assetInfo | ||
}; | ||
filename = interpolatedFilename; | ||
logger.log(`interpolated template '${filename}' for '${sourceFilename}'`); | ||
} else { | ||
filename = normalizePath(filename); | ||
} // eslint-disable-next-line consistent-return | ||
if (cache) { | ||
cacheItem = cache.getItemCache(cacheKeys, cache.getLazyHashedEtag(result.source)); | ||
result.source = await cacheItem.getPromise(); | ||
} else { | ||
cacheDirectory = pattern.cacheTransform.directory ? pattern.cacheTransform.directory : typeof pattern.cacheTransform === "string" ? pattern.cacheTransform : (0, _findCacheDir.default)({ | ||
name: "copy-webpack-plugin" | ||
}) || _os.default.tmpdir(); | ||
let cached; | ||
try { | ||
cached = await _cacache.default.get(cacheDirectory, cacheKeys); | ||
} catch (error) { | ||
logger.debug(`no transformation cache for '${absoluteFilename}'...`); | ||
} // eslint-disable-next-line no-undefined | ||
return { | ||
sourceFilename, | ||
absoluteFilename, | ||
filename, | ||
source, | ||
info, | ||
force: pattern.force | ||
}; | ||
})); | ||
} catch (error) { | ||
compilation.errors.push( | ||
/** @type {WebpackError} */ | ||
error); | ||
return; | ||
} | ||
if (copiedResult.length === 0) { | ||
if (pattern.noErrorOnMissing) { | ||
logger.log(`finished to process a pattern from '${normalizedOriginalFrom}' using '${pattern.context}' context to '${pattern.to}'`); | ||
return; | ||
} | ||
result.source = cached ? new RawSource(cached.data) : undefined; | ||
} | ||
const missingError = new Error(`unable to locate '${glob}' glob after filtering paths`); | ||
compilation.errors.push( | ||
/** @type {WebpackError} */ | ||
missingError); | ||
return; | ||
} | ||
logger.debug(result.source ? `found transformation cache for '${absoluteFilename}'` : `no transformation cache for '${absoluteFilename}'`); | ||
logger.log(`finished to process a pattern from '${normalizedOriginalFrom}' using '${pattern.context}' context`); // eslint-disable-next-line consistent-return | ||
if (!result.source) { | ||
const transformed = await pattern.transform(buffer, absoluteFilename); | ||
result.source = new RawSource(transformed); | ||
logger.debug(`caching transformation for '${absoluteFilename}'...`); // webpack@5 API | ||
return copiedResult; | ||
} | ||
/** | ||
* @param {Compiler} compiler | ||
*/ | ||
if (cache) { | ||
await cacheItem.storePromise(result.source); | ||
} else { | ||
try { | ||
await _cacache.default.put(cacheDirectory, cacheKeys, transformed); | ||
} catch (error) { | ||
compilation.errors.push(error); | ||
return; | ||
} | ||
} | ||
logger.debug(`cached transformation for '${absoluteFilename}'`); | ||
} | ||
} else { | ||
result.source = new RawSource(await pattern.transform(buffer, absoluteFilename)); | ||
apply(compiler) { | ||
const pluginName = this.constructor.name; | ||
compiler.hooks.thisCompilation.tap(pluginName, compilation => { | ||
const logger = compilation.getLogger("copy-webpack-plugin"); | ||
const cache = compilation.getCache("CopyWebpackPlugin"); | ||
/** | ||
* @type {typeof import("globby").globby} | ||
*/ | ||
let globby; | ||
compilation.hooks.processAssets.tapAsync({ | ||
name: "copy-webpack-plugin", | ||
stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL | ||
}, async (unusedAssets, callback) => { | ||
if (typeof globby === "undefined") { | ||
try { | ||
// @ts-ignore | ||
({ | ||
globby | ||
} = await import("globby")); | ||
} catch (error) { | ||
callback( | ||
/** @type {Error} */ | ||
error); | ||
return; | ||
} | ||
} | ||
if (pattern.toType === "template") { | ||
logger.log(`interpolating template '${filename}' for '${sourceFilename}'...`); // If it doesn't have an extension, remove it from the pattern | ||
// ie. [name].[ext] or [name][ext] both become [name] | ||
logger.log("starting to add additional assets..."); | ||
const copiedResultMap = new Map(); | ||
/** | ||
* @type {(() => Promise<void>)[]} | ||
*/ | ||
if (!_path.default.extname(absoluteFilename)) { | ||
// eslint-disable-next-line no-param-reassign | ||
result.filename = result.filename.replace(/\.?\[ext]/g, ""); | ||
} // eslint-disable-next-line no-param-reassign | ||
const scheduledTasks = []; | ||
this.patterns.map( | ||
/** | ||
* @param {Pattern} item | ||
* @param {number} index | ||
* @return {number} | ||
*/ | ||
(item, index) => scheduledTasks.push(async () => { | ||
/** | ||
* @type {ObjectPattern} | ||
*/ | ||
const normalizedPattern = typeof item === "string" ? { | ||
from: item | ||
} : { ...item | ||
}; | ||
const context = typeof normalizedPattern.context === "undefined" ? compiler.context : path.isAbsolute(normalizedPattern.context) ? normalizedPattern.context : path.join(compiler.context, normalizedPattern.context); | ||
normalizedPattern.context = context; | ||
/** | ||
* @type {Array<CopiedResult | undefined> | undefined} | ||
*/ | ||
let copiedResult; | ||
result.immutable = /\[(?:([^:\]]+):)?(?:hash|contenthash)(?::([a-z]+\d*))?(?::(\d+))?\]/gi.test(result.filename); // eslint-disable-next-line no-param-reassign | ||
try { | ||
copiedResult = await CopyPlugin.runPattern(globby, compiler, compilation, logger, cache, | ||
/** @type {ObjectPattern & { context: string }} */ | ||
normalizedPattern, index); | ||
} catch (error) { | ||
compilation.errors.push( | ||
/** @type {WebpackError} */ | ||
error); | ||
return; | ||
} | ||
result.filename = _loaderUtils.default.interpolateName({ | ||
resourcePath: absoluteFilename | ||
}, result.filename, { | ||
content: result.source.source(), | ||
context: pattern.context | ||
}); // Bug in `loader-utils`, package convert `\\` to `/`, need fix in loader-utils | ||
// eslint-disable-next-line no-param-reassign | ||
if (!copiedResult) { | ||
return; | ||
} | ||
/** | ||
* @type {Array<CopiedResult>} | ||
*/ | ||
result.filename = _path.default.normalize(result.filename); | ||
logger.log(`interpolated template '${filename}' for '${sourceFilename}'`); | ||
} | ||
if (pattern.transformPath) { | ||
logger.log(`transforming '${result.filename}' for '${absoluteFilename}'...`); // eslint-disable-next-line no-param-reassign | ||
let filteredCopiedResult = copiedResult.filter( | ||
/** | ||
* @param {CopiedResult | undefined} result | ||
* @returns {result is CopiedResult} | ||
*/ | ||
result => Boolean(result)); | ||
result.immutable = false; // eslint-disable-next-line no-param-reassign | ||
if (typeof normalizedPattern.transformAll !== "undefined") { | ||
if (typeof normalizedPattern.to === "undefined") { | ||
compilation.errors.push( | ||
/** @type {WebpackError} */ | ||
new Error(`Invalid "pattern.to" for the "pattern.from": "${normalizedPattern.from}" and "pattern.transformAll" function. The "to" option must be specified.`)); | ||
return; | ||
} | ||
result.filename = await pattern.transformPath(result.filename, absoluteFilename); | ||
logger.log(`transformed new '${result.filename}' for '${absoluteFilename}'...`); | ||
} // eslint-disable-next-line no-param-reassign | ||
filteredCopiedResult.sort((a, b) => a.absoluteFilename > b.absoluteFilename ? 1 : a.absoluteFilename < b.absoluteFilename ? -1 : 0); | ||
const mergedEtag = filteredCopiedResult.length === 1 ? cache.getLazyHashedEtag(filteredCopiedResult[0].source) : filteredCopiedResult.reduce( | ||
/** | ||
* @param {Etag} accumulator | ||
* @param {CopiedResult} asset | ||
* @param {number} i | ||
* @return {Etag} | ||
*/ | ||
// @ts-ignore | ||
(accumulator, asset, i) => { | ||
// eslint-disable-next-line no-param-reassign | ||
accumulator = cache.mergeEtags(i === 1 ? cache.getLazyHashedEtag( | ||
/** @type {CopiedResult}*/ | ||
accumulator.source) : accumulator, cache.getLazyHashedEtag(asset.source)); | ||
return accumulator; | ||
}); | ||
const cacheItem = cache.getItemCache(`transformAll|${serialize({ | ||
version, | ||
from: normalizedPattern.from, | ||
to: normalizedPattern.to, | ||
transformAll: normalizedPattern.transformAll | ||
})}`, mergedEtag); | ||
let transformedAsset = await cacheItem.getPromise(); | ||
if (!transformedAsset) { | ||
transformedAsset = { | ||
filename: normalizedPattern.to | ||
}; | ||
result.filename = (0, _normalizePath.default)(result.filename); // eslint-disable-next-line consistent-return | ||
try { | ||
transformedAsset.data = await normalizedPattern.transformAll(filteredCopiedResult.map(asset => { | ||
return { | ||
data: asset.source.buffer(), | ||
sourceFilename: asset.sourceFilename, | ||
absoluteFilename: asset.absoluteFilename | ||
}; | ||
})); | ||
} catch (error) { | ||
compilation.errors.push( | ||
/** @type {WebpackError} */ | ||
error); | ||
return; | ||
} | ||
return result; | ||
})); | ||
} catch (error) { | ||
compilation.errors.push(error); | ||
return; | ||
} | ||
const filename = typeof normalizedPattern.to === "function" ? await normalizedPattern.to({ | ||
context | ||
}) : normalizedPattern.to; | ||
logger.log(`finished to process a pattern from '${pattern.from}' using '${pattern.context}' context to '${pattern.to}'`); // eslint-disable-next-line consistent-return | ||
if (template.test(filename)) { | ||
const contentHash = CopyPlugin.getContentHash(compiler, compilation, transformedAsset.data); | ||
const { | ||
path: interpolatedFilename, | ||
info: assetInfo | ||
} = compilation.getPathWithInfo(normalizePath(filename), { | ||
contentHash, | ||
chunk: { | ||
id: "unknown-copied-asset", | ||
hash: contentHash | ||
} | ||
}); | ||
transformedAsset.filename = interpolatedFilename; | ||
transformedAsset.info = assetInfo; | ||
} | ||
return assets; | ||
} | ||
const { | ||
RawSource | ||
} = compiler.webpack.sources; | ||
transformedAsset.source = new RawSource(transformedAsset.data); | ||
transformedAsset.force = normalizedPattern.force; | ||
await cacheItem.storePromise(transformedAsset); | ||
} | ||
apply(compiler) { | ||
const pluginName = this.constructor.name; | ||
const limit = (0, _pLimit.default)(this.options.concurrency || 100); | ||
compiler.hooks.thisCompilation.tap(pluginName, compilation => { | ||
const logger = compilation.getLogger("copy-webpack-plugin"); | ||
const cache = compilation.getCache ? compilation.getCache("CopyWebpackPlugin") : // eslint-disable-next-line no-undefined | ||
undefined; | ||
compilation.hooks.additionalAssets.tapAsync("copy-webpack-plugin", async callback => { | ||
logger.log("starting to add additional assets..."); | ||
let assets; | ||
filteredCopiedResult = [transformedAsset]; | ||
} | ||
try { | ||
assets = await Promise.all(this.patterns.map((item, index) => limit(async () => CopyPlugin.runPattern(compiler, compilation, logger, cache, item, index)))); | ||
} catch (error) { | ||
compilation.errors.push(error); | ||
callback(); | ||
return; | ||
} // Avoid writing assets inside `p-limit`, because it creates concurrency. | ||
const priority = normalizedPattern.priority || 0; | ||
if (!copiedResultMap.has(priority)) { | ||
copiedResultMap.set(priority, []); | ||
} | ||
copiedResultMap.get(priority).push(...filteredCopiedResult); | ||
})); | ||
await throttleAll(this.options.concurrency || 100, scheduledTasks); | ||
const copiedResult = [...copiedResultMap.entries()].sort((a, b) => a[0] - b[0]); // Avoid writing assets inside `p-limit`, because it creates concurrency. | ||
// It could potentially lead to an error - 'Multiple assets emit different content to the same filename' | ||
assets.reduce((acc, val) => acc.concat(val), []).filter(Boolean).forEach(asset => { | ||
copiedResult.reduce((acc, val) => acc.concat(val[1]), []).filter(Boolean).forEach( | ||
/** | ||
* @param {CopiedResult} result | ||
* @returns {void} | ||
*/ | ||
result => { | ||
const { | ||
@@ -554,12 +886,3 @@ absoluteFilename, | ||
force | ||
} = asset; // For old version webpack 4 | ||
/* istanbul ignore if */ | ||
if (typeof compilation.emitAsset !== "function") { | ||
// eslint-disable-next-line no-param-reassign | ||
compilation.assets[filename] = source; | ||
return; | ||
} | ||
} = result; | ||
const existingAsset = compilation.getAsset(filename); | ||
@@ -573,10 +896,5 @@ | ||
}; | ||
if (asset.immutable) { | ||
info.immutable = true; | ||
} | ||
logger.log(`force updating '${filename}' from '${absoluteFilename}' to compilation assets, because it already exists...`); | ||
compilation.updateAsset(filename, source, { ...info, | ||
...asset.info | ||
...result.info | ||
}); | ||
@@ -595,10 +913,5 @@ logger.log(`force updated '${filename}' from '${absoluteFilename}' to compilation assets, because it already exists`); | ||
}; | ||
if (asset.immutable) { | ||
info.immutable = true; | ||
} | ||
logger.log(`writing '${filename}' from '${absoluteFilename}' to compilation assets...`); | ||
compilation.emitAsset(filename, source, { ...info, | ||
...asset.info | ||
...result.info | ||
}); | ||
@@ -616,4 +929,7 @@ logger.log(`written '${filename}' from '${absoluteFilename}' to compilation assets`); | ||
formatFlag | ||
}) => // eslint-disable-next-line no-undefined | ||
copied ? green(formatFlag("copied")) : undefined); | ||
}) => copied ? | ||
/** @type {Function} */ | ||
green( | ||
/** @type {Function} */ | ||
formatFlag("copied")) : ""); | ||
}); | ||
@@ -626,3 +942,2 @@ } | ||
var _default = CopyPlugin; | ||
exports.default = _default; | ||
module.exports = CopyPlugin; |
@@ -9,2 +9,4 @@ { | ||
"type": "string", | ||
"description": "Glob or path from where we copy files.", | ||
"link": "https://github.com/webpack-contrib/copy-webpack-plugin#from", | ||
"minLength": 1 | ||
@@ -20,19 +22,41 @@ }, | ||
} | ||
] | ||
], | ||
"description": "Output path.", | ||
"link": "https://github.com/webpack-contrib/copy-webpack-plugin#to" | ||
}, | ||
"context": { | ||
"type": "string" | ||
"type": "string", | ||
"description": "A path that determines how to interpret the 'from' path.", | ||
"link": "https://github.com/webpack-contrib/copy-webpack-plugin#context" | ||
}, | ||
"globOptions": { | ||
"type": "object" | ||
"type": "object", | ||
"description": "Allows to configute the glob pattern matching library used by the plugin.", | ||
"link": "https://github.com/webpack-contrib/copy-webpack-plugin#globoptions" | ||
}, | ||
"filter": { | ||
"instanceof": "Function" | ||
"instanceof": "Function", | ||
"description": "Allows to filter copied assets.", | ||
"link": "https://github.com/webpack-contrib/copy-webpack-plugin#filter" | ||
}, | ||
"transformAll": { | ||
"instanceof": "Function", | ||
"description": "Allows you to modify the contents of multiple files and save the result to one file.", | ||
"link": "https://github.com/webpack-contrib/copy-webpack-plugin#transformall" | ||
}, | ||
"toType": { | ||
"enum": ["dir", "file", "template"] | ||
"enum": ["dir", "file", "template"], | ||
"description": "Determinate what is to option - directory, file or template.", | ||
"link": "https://github.com/webpack-contrib/copy-webpack-plugin#totype" | ||
}, | ||
"force": { | ||
"type": "boolean" | ||
"type": "boolean", | ||
"description": "Overwrites files already in 'compilation.assets' (usually added by other plugins/loaders).", | ||
"link": "https://github.com/webpack-contrib/copy-webpack-plugin#force" | ||
}, | ||
"priority": { | ||
"type": "number", | ||
"description": "Allows to specify the priority of copying files with the same destination name.", | ||
"link": "https://github.com/webpack-contrib/copy-webpack-plugin#priority" | ||
}, | ||
"info": { | ||
@@ -46,34 +70,45 @@ "anyOf": [ | ||
} | ||
] | ||
], | ||
"description": "Allows to add assets info.", | ||
"link": "https://github.com/webpack-contrib/copy-webpack-plugin#info" | ||
}, | ||
"flatten": { | ||
"type": "boolean" | ||
}, | ||
"transform": { | ||
"instanceof": "Function" | ||
}, | ||
"cacheTransform": { | ||
"description": "Allows to modify the file contents.", | ||
"link": "https://github.com/webpack-contrib/copy-webpack-plugin#transform", | ||
"anyOf": [ | ||
{ | ||
"type": "boolean" | ||
"instanceof": "Function" | ||
}, | ||
{ | ||
"type": "string" | ||
}, | ||
{ | ||
"type": "object", | ||
"additionalProperties": false, | ||
"properties": { | ||
"directory": { | ||
"type": "string", | ||
"absolutePath": true | ||
"transformer": { | ||
"instanceof": "Function", | ||
"description": "Allows to modify the file contents.", | ||
"link": "https://github.com/webpack-contrib/copy-webpack-plugin#transformer" | ||
}, | ||
"keys": { | ||
"cache": { | ||
"description": "Enables/disables and configure caching.", | ||
"link": "https://github.com/webpack-contrib/copy-webpack-plugin#cache", | ||
"anyOf": [ | ||
{ | ||
"type": "object", | ||
"additionalProperties": true | ||
"type": "boolean" | ||
}, | ||
{ | ||
"instanceof": "Function" | ||
"type": "object", | ||
"additionalProperties": false, | ||
"properties": { | ||
"keys": { | ||
"anyOf": [ | ||
{ | ||
"type": "object", | ||
"additionalProperties": true | ||
}, | ||
{ | ||
"instanceof": "Function" | ||
} | ||
] | ||
} | ||
} | ||
} | ||
@@ -86,7 +121,6 @@ ] | ||
}, | ||
"transformPath": { | ||
"instanceof": "Function" | ||
}, | ||
"noErrorOnMissing": { | ||
"type": "boolean" | ||
"type": "boolean", | ||
"description": "Doesn't generate an error on missing file(s).", | ||
"link": "https://github.com/webpack-contrib/copy-webpack-plugin#noerroronmissing" | ||
} | ||
@@ -123,3 +157,5 @@ }, | ||
"concurrency": { | ||
"type": "number" | ||
"type": "number", | ||
"description": "Limits the number of simultaneous requests to fs.", | ||
"link": "https://github.com/webpack-contrib/copy-webpack-plugin#concurrency" | ||
} | ||
@@ -126,0 +162,0 @@ } |
{ | ||
"name": "copy-webpack-plugin", | ||
"version": "6.4.1", | ||
"version": "11.0.0", | ||
"description": "Copy files && directories with webpack", | ||
@@ -14,15 +14,19 @@ "license": "MIT", | ||
}, | ||
"main": "dist/cjs.js", | ||
"main": "dist/index.js", | ||
"types": "types/index.d.ts", | ||
"engines": { | ||
"node": ">= 10.13.0" | ||
"node": ">= 14.15.0" | ||
}, | ||
"scripts": { | ||
"start": "npm run build -- -w", | ||
"clean": "del-cli dist", | ||
"clean": "del-cli dist types", | ||
"prebuild": "npm run clean", | ||
"build": "cross-env NODE_ENV=production babel src -d dist --copy-files", | ||
"build:types": "tsc --declaration --emitDeclarationOnly --outDir types --rootDir src && prettier \"types/**/*.ts\" --write", | ||
"build:code": "cross-env NODE_ENV=production babel src -d dist --copy-files", | ||
"build": "npm-run-all -p \"build:**\"", | ||
"commitlint": "commitlint --from=master", | ||
"security": "npm audit", | ||
"security": "npm audit --production", | ||
"lint:prettier": "prettier --list-different .", | ||
"lint:js": "eslint --cache .", | ||
"lint:types": "tsc --pretty --noEmit", | ||
"lint": "npm-run-all -l -p \"lint:**\"", | ||
@@ -34,52 +38,50 @@ "test:only": "cross-env NODE_ENV=test jest", | ||
"test": "npm run test:coverage", | ||
"prepare": "npm run build", | ||
"release": "standard-version", | ||
"defaults": "webpack-defaults" | ||
"prepare": "husky install && npm run build", | ||
"release": "standard-version" | ||
}, | ||
"files": [ | ||
"dist" | ||
"dist", | ||
"types" | ||
], | ||
"peerDependencies": { | ||
"webpack": "^4.37.0 || ^5.0.0" | ||
"webpack": "^5.1.0" | ||
}, | ||
"dependencies": { | ||
"cacache": "^15.0.5", | ||
"fast-glob": "^3.2.4", | ||
"find-cache-dir": "^3.3.1", | ||
"glob-parent": "^5.1.1", | ||
"globby": "^11.0.1", | ||
"loader-utils": "^2.0.0", | ||
"fast-glob": "^3.2.11", | ||
"glob-parent": "^6.0.1", | ||
"globby": "^13.1.1", | ||
"normalize-path": "^3.0.0", | ||
"p-limit": "^3.0.2", | ||
"schema-utils": "^3.0.0", | ||
"serialize-javascript": "^5.0.1", | ||
"webpack-sources": "^1.4.3" | ||
"schema-utils": "^4.0.0", | ||
"serialize-javascript": "^6.0.0" | ||
}, | ||
"devDependencies": { | ||
"@babel/cli": "^7.12.1", | ||
"@babel/core": "^7.12.3", | ||
"@babel/preset-env": "^7.12.1", | ||
"@commitlint/cli": "^11.0.0", | ||
"@commitlint/config-conventional": "^11.0.0", | ||
"@webpack-contrib/defaults": "^6.3.0", | ||
"@babel/cli": "^7.17.10", | ||
"@babel/core": "^7.17.10", | ||
"@babel/eslint-parser": "^7.16.5", | ||
"@babel/preset-env": "^7.17.12", | ||
"@commitlint/cli": "^17.0.0", | ||
"@commitlint/config-conventional": "^17.0.0", | ||
"@types/glob-parent": "^5.1.1", | ||
"@types/normalize-path": "^3.0.0", | ||
"@types/serialize-javascript": "^5.0.2", | ||
"@webpack-contrib/eslint-config-webpack": "^3.0.0", | ||
"babel-jest": "^26.6.3", | ||
"chokidar": "^3.4.3", | ||
"cross-env": "^7.0.2", | ||
"babel-jest": "^28.1.0", | ||
"cross-env": "^7.0.3", | ||
"del": "^6.0.0", | ||
"del-cli": "^3.0.1", | ||
"eslint": "^7.13.0", | ||
"eslint-config-prettier": "^6.15.0", | ||
"eslint-plugin-import": "^2.22.1", | ||
"file-loader": "^6.1.1", | ||
"husky": "^4.3.0", | ||
"del-cli": "^4.0.1", | ||
"eslint": "^8.15.0", | ||
"eslint-config-prettier": "^8.5.0", | ||
"eslint-plugin-import": "^2.26.0", | ||
"file-loader": "^6.2.0", | ||
"husky": "^8.0.1", | ||
"is-gzip": "^2.0.0", | ||
"jest": "^26.6.3", | ||
"lint-staged": "^10.5.1", | ||
"memfs": "^3.2.0", | ||
"jest": "^28.1.0", | ||
"lint-staged": "^12.4.1", | ||
"memfs": "^3.4.1", | ||
"mkdirp": "^1.0.4", | ||
"npm-run-all": "^4.1.5", | ||
"prettier": "^2.1.2", | ||
"standard-version": "^9.0.0", | ||
"webpack": "^5.4.0" | ||
"prettier": "^2.6.2", | ||
"standard-version": "^9.3.1", | ||
"typescript": "^4.6.4", | ||
"webpack": "^5.72.1" | ||
}, | ||
@@ -86,0 +88,0 @@ "keywords": [ |
534
README.md
@@ -25,5 +25,17 @@ <div align="center"> | ||
```console | ||
$ npm install copy-webpack-plugin --save-dev | ||
npm install copy-webpack-plugin --save-dev | ||
``` | ||
or | ||
```console | ||
yarn add -D copy-webpack-plugin | ||
``` | ||
or | ||
```console | ||
pnpm add -D copy-webpack-plugin | ||
``` | ||
Then add the plugin to your `webpack` config. For example: | ||
@@ -48,3 +60,3 @@ | ||
> âšī¸ `webpack-copy-plugin` is not designed to copy files generated from the build process; rather, it is to copy files that already exist in the source tree, as part of the build process. | ||
> âšī¸ `copy-webpack-plugin` is not designed to copy files generated from the build process; rather, it is to copy files that already exist in the source tree, as part of the build process. | ||
@@ -57,2 +69,5 @@ > âšī¸ If you want `webpack-dev-server` to write files to the output directory during development, you can force it with the [`writeToDisk`](https://github.com/webpack/webpack-dev-middleware#writetodisk) option or the [`write-file-webpack-plugin`](https://github.com/gajus/write-file-webpack-plugin). | ||
- **[`patterns`](#patterns)** | ||
- **[`options`](#options-1)** | ||
The plugin's signature: | ||
@@ -70,3 +85,3 @@ | ||
{ from: "source", to: "dest" }, | ||
{ from: "other", to: "public" }, | ||
"path/to/source", // absolute or relative, files/directories/globs - see below for examples | ||
], | ||
@@ -81,26 +96,28 @@ options: { | ||
### Patterns | ||
### `Patterns` | ||
| Name | Type | Default | Description | | ||
| :-------------------------------------: | :-------------------------: | :---------------------------------------------: | :---------------------------------------------------------------------------------------------------- | | ||
| [`from`](#from) | `{String}` | `undefined` | Glob or path from where we Ņopy files. | | ||
| [`to`](#to) | `{String\|Function}` | `compiler.options.output` | Output path. | | ||
| [`context`](#context) | `{String}` | `options.context \|\| compiler.options.context` | A path that determines how to interpret the `from` path. | | ||
| [`globOptions`](#globoptions) | `{Object}` | `undefined` | [Options][glob-options] passed to the glob pattern matching library including `ignore` option. | | ||
| [`filter`](#filter) | `{Function}` | `undefined` | Allows to filter copied assets. | | ||
| [`toType`](#totype) | `{String}` | `undefined` | Determinate what is `to` option - directory, file or template. | | ||
| [`force`](#force) | `{Boolean}` | `false` | Overwrites files already in `compilation.assets` (usually added by other plugins/loaders). | | ||
| [`flatten`](#flatten) | `{Boolean}` | `false` | Removes all directory references and only copies file names. | | ||
| [`transform`](#transform) | `{Function}` | `undefined` | Allows to modify the file contents. | | ||
| [`cacheTransform`](#cacheTransform) | `{Boolean\|String\|Object}` | `false` | Enable `transform` caching. You can use `{ cache: { key: 'my-cache-key' } }` to invalidate the cache. | | ||
| [`transformPath`](#transformpath) | `{Function}` | `undefined` | Allows to modify the writing path. | | ||
| [`noErrorOnMissing`](#noerroronmissing) | `{Boolean}` | `false` | Doesn't generate an error on missing file(s). | | ||
| [`info`](#info) | `{Object\|Function}` | `undefined` | Allows to add assets info. | | ||
- [`from`](#from) | ||
- [`to`](#to) | ||
- [`context`](#context) | ||
- [`globOptions`](#globoptions) | ||
- [`filter`](#filter) | ||
- [`toType`](#totype) | ||
- [`force`](#force) | ||
- [`priority`](#priority) | ||
- [`transform`](#transform) | ||
- [`transformAll`](#transformAll) | ||
- [`noErrorOnMissing`](#noerroronmissing) | ||
- [`info`](#info) | ||
#### `from` | ||
Type: `String` | ||
Type: | ||
```ts | ||
type from = string; | ||
``` | ||
Default: `undefined` | ||
Glob or path from where we Ņopy files. | ||
Glob or path from where we copy files. | ||
Globs accept [fast-glob pattern-syntax](https://github.com/mrmlnc/fast-glob#pattern-syntax). | ||
@@ -183,6 +200,13 @@ Glob can only be a `string`. | ||
Type: `String|Function` | ||
Type: | ||
```ts | ||
type to = | ||
| string | ||
| ((pathData: { context: string; absoluteFilename?: string }) => string); | ||
``` | ||
Default: `compiler.options.output` | ||
##### String | ||
##### `string` | ||
@@ -212,3 +236,3 @@ Output path. | ||
from: "**/*", | ||
to: "[path][name].[contenthash].[ext]", | ||
to: "[path][name].[contenthash][ext]", | ||
}, | ||
@@ -221,3 +245,3 @@ ], | ||
##### Function | ||
##### `function` | ||
@@ -240,3 +264,3 @@ Allows to modify the writing path. | ||
to({ context, absoluteFilename }) { | ||
return "dest/newPath"; | ||
return "dest/newPath/[name][ext]"; | ||
}, | ||
@@ -259,5 +283,4 @@ }, | ||
from: "src/*.png", | ||
to: "dest/", | ||
to({ context, absoluteFilename }) { | ||
return Promise.resolve("dest/newPath"); | ||
return Promise.resolve("dest/newPath/[name][ext]"); | ||
}, | ||
@@ -273,3 +296,8 @@ }, | ||
Type: `String` | ||
Type: | ||
```ts | ||
type context = string; | ||
``` | ||
Default: `options.context|compiler.options.context` | ||
@@ -303,4 +331,2 @@ | ||
Also, `context` indicates how to interpret the search results. Further, he is considered in this role. | ||
To determine the structure from which the found resources will be copied to the destination folder, the `context` option is used. | ||
@@ -318,6 +344,11 @@ | ||
Type: `Object` | ||
Type: | ||
```ts | ||
type globOptions = import("globby").Options; | ||
``` | ||
Default: `undefined` | ||
Allows to configute the glob pattern matching library used by the plugin. [See the list of supported options][glob-options] | ||
Allows to configure the glob pattern matching library used by the plugin. [See the list of supported options][glob-options] | ||
To exclude files from the selection, you should use [globOptions.ignore option](https://github.com/mrmlnc/fast-glob#ignore) | ||
@@ -348,6 +379,11 @@ | ||
Type: `Function` | ||
Type: | ||
```ts | ||
type filter = (filepath: string) => boolean; | ||
``` | ||
Default: `undefined` | ||
> âšī¸ To ignore files by path please use the [`globOptions.ignore`]((#globoptions) option. | ||
> âšī¸ To ignore files by path please use the [`globOptions.ignore`](#globoptions) option. | ||
@@ -384,3 +420,8 @@ **webpack.config.js** | ||
Type: `String` | ||
Type: | ||
```ts | ||
type toType = "dir" | "file" | "template"; | ||
``` | ||
Default: `undefined` | ||
@@ -393,7 +434,7 @@ | ||
| Name | Type | Default | Description | | ||
| :--------------: | :--------: | :---------: | :------------------------------------------------------------------------------------------------- | | ||
| **`'dir'`** | `{String}` | `undefined` | If `to` has no extension or ends on `'/'` | | ||
| **`'file'`** | `{String}` | `undefined` | If `to` is not a directory and is not a template | | ||
| **`'template'`** | `{String}` | `undefined` | If `to` contains [a template pattern](https://github.com/webpack-contrib/file-loader#placeholders) | | ||
| Name | Type | Default | Description | | ||
| :---------------------------: | :------: | :---------: | :--------------------------------------------------------------------------------------------------- | | ||
| **[`'dir'`](#dir)** | `string` | `undefined` | If `to` has no extension or ends on `'/'` | | ||
| **[`'file'`](#file)** | `string` | `undefined` | If `to` is not a directory and is not a template | | ||
| **[`'template'`](#template)** | `string` | `undefined` | If `to` contains [a template pattern](https://webpack.js.org/configuration/output/#template-strings) | | ||
@@ -451,3 +492,3 @@ ##### `'dir'` | ||
from: "src/", | ||
to: "dest/[name].[hash].[ext]", | ||
to: "dest/[name].[contenthash][ext]", | ||
toType: "template", | ||
@@ -463,3 +504,8 @@ }, | ||
Type: `Boolean` | ||
Type: | ||
```ts | ||
type force = boolean; | ||
``` | ||
Default: `false` | ||
@@ -487,11 +533,16 @@ | ||
#### `flatten` | ||
#### `priority` | ||
Type: `Boolean` | ||
Default: `false` | ||
Type: | ||
Removes all directory references and only copies file names. | ||
```ts | ||
type priority = number; | ||
``` | ||
> â ī¸ If files have the same name, the result is non-deterministic. | ||
Default: `0` | ||
Allows to specify the priority of copying files with the same destination name. | ||
Files for patterns with higher priority will be copied later. | ||
To overwrite files, the [`force`](#force) option must be enabled. | ||
**webpack.config.js** | ||
@@ -504,7 +555,15 @@ | ||
patterns: [ | ||
// Copied second and will overwrite "dir_2/file.txt" | ||
{ | ||
from: "src/**/*", | ||
to: "dest/", | ||
flatten: true, | ||
from: "dir_1/file.txt", | ||
to: "newfile.txt", | ||
force: true, | ||
priority: 10, | ||
}, | ||
// Copied first | ||
{ | ||
from: "dir_2/file.txt", | ||
to: "newfile.txt", | ||
priority: 5, | ||
}, | ||
], | ||
@@ -518,3 +577,13 @@ }), | ||
Type: `Function` | ||
Type: | ||
```ts | ||
type transform = | ||
| { | ||
transformer: (input: string, absoluteFilename: string) => string | Buffer; | ||
cache?: boolean | TransformerCacheObject | undefined; | ||
} | ||
| ((input: string, absoluteFilename: string) => string | Buffer); | ||
``` | ||
Default: `undefined` | ||
@@ -524,2 +593,4 @@ | ||
##### `function` | ||
**webpack.config.js** | ||
@@ -547,2 +618,19 @@ | ||
##### `object` | ||
| Name | Default | Description | | ||
| :-------------------------------: | :---------: | :--------------------------------------------------------------------------------------------------------------- | | ||
| **[`transformer`](#transformer)** | `undefined` | Allows to modify the file contents. | | ||
| **[`cache`](#cache)** | `false` | Enable `transform` caching. You can use `transform: { cache: { key: 'my-cache-key' } }` to invalidate the cache. | | ||
###### `transformer` | ||
Type: | ||
```ts | ||
type transformer = (input: string, absoluteFilename: string) => string; | ||
``` | ||
Default: `undefined` | ||
**webpack.config.js** | ||
@@ -558,4 +646,8 @@ | ||
to: "dest/", | ||
transform(content, path) { | ||
return Promise.resolve(optimize(content)); | ||
// The `content` argument is a [`Buffer`](https://nodejs.org/api/buffer.html) object, it could be converted to a `String` to be processed using `content.toString()` | ||
// The `absoluteFrom` argument is a `String`, it is absolute path from where the file is being copied | ||
transform: { | ||
transformer(content, absoluteFrom) { | ||
return optimize(content); | ||
}, | ||
}, | ||
@@ -569,14 +661,2 @@ }, | ||
#### `cacheTransform` | ||
Type: `Boolean|String|Object` | ||
Default: `false` | ||
Enable/disable and configure caching. | ||
Default path to cache directory: `node_modules/.cache/copy-webpack-plugin`. | ||
##### `Boolean` | ||
Enables/Disable `transform` caching. | ||
**webpack.config.js** | ||
@@ -592,6 +672,7 @@ | ||
to: "dest/", | ||
transform(content, path) { | ||
return optimize(content); | ||
transform: { | ||
transformer(content, path) { | ||
return Promise.resolve(optimize(content)); | ||
}, | ||
}, | ||
cacheTransform: true, | ||
}, | ||
@@ -604,8 +685,40 @@ ], | ||
##### `String` | ||
###### `cache` | ||
Enables `transform` caching and setup cache directory. | ||
Type: | ||
```ts | ||
type cache = | ||
| boolean | ||
| { | ||
keys: { | ||
[key: string]: any; | ||
}; | ||
} | ||
| { | ||
keys: ( | ||
defaultCacheKeys: { | ||
[key: string]: any; | ||
}, | ||
absoluteFilename: string | ||
) => Promise<{ | ||
[key: string]: any; | ||
}>; | ||
} | ||
| undefined; | ||
``` | ||
Default: `false` | ||
**webpack.config.js** | ||
Enable/disable and configure caching. | ||
Default path to cache directory: `node_modules/.cache/copy-webpack-plugin`. | ||
###### `boolean` | ||
Enables/Disable `transform` caching. | ||
**webpack.config.js** | ||
```js | ||
@@ -619,7 +732,8 @@ module.exports = { | ||
to: "dest/", | ||
transform(content, path) { | ||
return optimize(content); | ||
transform: { | ||
transformer(content, path) { | ||
return optimize(content); | ||
}, | ||
cache: true, | ||
}, | ||
// Should be absolute | ||
cacheTransform: path.resolve(__dirname, "cache-directory"), | ||
}, | ||
@@ -632,3 +746,3 @@ ], | ||
##### `Object` | ||
##### `object` | ||
@@ -647,12 +761,14 @@ Enables `transform` caching and setup cache directory and invalidation keys. | ||
to: "dest/", | ||
transform(content, path) { | ||
return optimize(content); | ||
}, | ||
cacheTransform: { | ||
directory: path.resolve(__dirname, "cache-directory"), | ||
keys: { | ||
// May be useful for invalidating cache based on external values | ||
// For example, you can invalid cache based on `process.version` - { node: process.version } | ||
key: "value", | ||
transform: { | ||
transformer(content, path) { | ||
return optimize(content); | ||
}, | ||
cache: { | ||
directory: path.resolve(__dirname, "cache-directory"), | ||
keys: { | ||
// May be useful for invalidating cache based on external values | ||
// For example, you can invalid cache based on `process.version` - { node: process.version } | ||
key: "value", | ||
}, | ||
}, | ||
}, | ||
@@ -680,14 +796,16 @@ }, | ||
to: "dest/", | ||
transform(content, path) { | ||
return optimize(content); | ||
}, | ||
cacheTransform: { | ||
directory: path.resolve(__dirname, "cache-directory"), | ||
keys: (defaultCacheKeys, absoluteFrom) => { | ||
const keys = getCustomCacheInvalidationKeysSync(); | ||
transform: { | ||
transformer(content, path) { | ||
return optimize(content); | ||
}, | ||
cache: { | ||
directory: path.resolve(__dirname, "cache-directory"), | ||
keys: (defaultCacheKeys, absoluteFrom) => { | ||
const keys = getCustomCacheInvalidationKeysSync(); | ||
return { | ||
...defaultCacheKeys, | ||
keys, | ||
}; | ||
return { | ||
...defaultCacheKeys, | ||
keys, | ||
}; | ||
}, | ||
}, | ||
@@ -714,14 +832,16 @@ }, | ||
to: "dest/", | ||
transform(content, path) { | ||
return optimize(content); | ||
}, | ||
cacheTransform: { | ||
directory: path.resolve(__dirname, "cache-directory"), | ||
keys: async (defaultCacheKeys, absoluteFrom) => { | ||
const keys = await getCustomCacheInvalidationKeysAsync(); | ||
transform: { | ||
transformer(content, path) { | ||
return optimize(content); | ||
}, | ||
cache: { | ||
directory: path.resolve(__dirname, "cache-directory"), | ||
keys: async (defaultCacheKeys, absoluteFrom) => { | ||
const keys = await getCustomCacheInvalidationKeysAsync(); | ||
return { | ||
...defaultCacheKeys, | ||
keys, | ||
}; | ||
return { | ||
...defaultCacheKeys, | ||
keys, | ||
}; | ||
}, | ||
}, | ||
@@ -736,12 +856,21 @@ }, | ||
#### `transformPath` | ||
#### `transformAll` | ||
Type: `Function` | ||
Type: | ||
```ts | ||
type transformAll = ( | ||
data: { | ||
data: Buffer; | ||
sourceFilename: string; | ||
absoluteFilename: string; | ||
}[] | ||
) => any; | ||
``` | ||
Default: `undefined` | ||
Allows to modify the writing path. | ||
Allows you to modify the contents of multiple files and save the result to one file. | ||
> â ī¸ Don't return directly `\\` in `transformPath` (i.e `path\to\newFile`) option because on UNIX the backslash is a valid character inside a path component, i.e., it's not a separator. | ||
> On Windows, the forward slash and the backward slash are both separators. | ||
> Instead please use `/` or `path` methods. | ||
> âšī¸ The `to` option must be specified and point to a file. It is allowed to use only `[contenthash]` and `[fullhash]` template strings. | ||
@@ -756,26 +885,16 @@ **webpack.config.js** | ||
{ | ||
from: "src/*.png", | ||
to: "dest/", | ||
transformPath(targetPath, absolutePath) { | ||
return "newPath"; | ||
}, | ||
}, | ||
], | ||
}), | ||
], | ||
}; | ||
``` | ||
from: "src/**/*.txt", | ||
to: "dest/file.txt", | ||
// The `assets` argument is an assets array for the pattern.from ("src/**/*.txt") | ||
transformAll(assets) { | ||
const result = assets.reduce((accumulator, asset) => { | ||
// The asset content can be obtained from `asset.source` using `source` method. | ||
// The asset content is a [`Buffer`](https://nodejs.org/api/buffer.html) object, it could be converted to a `String` to be processed using `content.toString()` | ||
const content = asset.data; | ||
**webpack.config.js** | ||
accumulator = `${accumulator}${content}\n`; | ||
return accumulator; | ||
}, ""); | ||
```js | ||
module.exports = { | ||
plugins: [ | ||
new CopyPlugin({ | ||
patterns: [ | ||
{ | ||
from: "src/*.png", | ||
to: "dest/", | ||
transformPath(targetPath, absolutePath) { | ||
return Promise.resolve("newPath"); | ||
return result; | ||
}, | ||
@@ -791,6 +910,11 @@ }, | ||
Type: `Boolean` | ||
Type: | ||
```ts | ||
type noErrorOnMissing = boolean; | ||
``` | ||
Default: `false` | ||
Doesn't generate an error on missing file(s); | ||
Doesn't generate an error on missing file(s). | ||
@@ -814,3 +938,15 @@ ```js | ||
Type: `Object|Function<Object>` | ||
Type: | ||
```ts | ||
type info = | ||
| Record<string, any> | ||
| ((item: { | ||
absoluteFilename: string; | ||
sourceFilename: string; | ||
filename: string; | ||
toType: ToType; | ||
}) => Record<string, any>); | ||
``` | ||
Default: `undefined` | ||
@@ -860,8 +996,14 @@ | ||
| Name | Type | Default | Description | | ||
| :---------------------------: | :--------: | :-----: | :----------------------------------------------- | | ||
| [`concurrency`](#concurrency) | `{Number}` | `100` | Limits the number of simultaneous requests to fs | | ||
- [`concurrency`](#concurrency) | ||
#### `concurrency` | ||
type: | ||
```ts | ||
type concurrency = number; | ||
``` | ||
default: `100` | ||
limits the number of simultaneous requests to fs | ||
@@ -1078,2 +1220,112 @@ | ||
#### Flatten copy | ||
Removes all directory references and only copies file names. | ||
> â ī¸ If files have the same name, the result is non-deterministic. | ||
**webpack.config.js** | ||
```js | ||
module.exports = { | ||
plugins: [ | ||
new CopyPlugin({ | ||
patterns: [ | ||
{ | ||
from: "src/**/*", | ||
to: "[name][ext]", | ||
}, | ||
], | ||
}), | ||
], | ||
}; | ||
``` | ||
Result: | ||
```txt | ||
file-1.txt | ||
file-2.txt | ||
nested-file.txt | ||
``` | ||
#### Copy in new directory | ||
**webpack.config.js** | ||
```js | ||
module.exports = { | ||
plugins: [ | ||
new CopyPlugin({ | ||
patterns: [ | ||
{ | ||
// When copying files starting with a dot, must specify the toType option | ||
// toType: "file", | ||
to({ context, absoluteFilename }) { | ||
return `newdirectory/${path.relative(context, absoluteFilename)}`; | ||
}, | ||
from: "directory", | ||
}, | ||
], | ||
}), | ||
], | ||
}; | ||
``` | ||
Result: | ||
```txt | ||
"newdirectory/file-1.txt", | ||
"newdirectory/nestedfile.txt", | ||
"newdirectory/nested/deep-nested/deepnested.txt", | ||
"newdirectory/nested/nestedfile.txt", | ||
``` | ||
#### Skip running JavaScript files through a minimizer | ||
Useful if you need to simply copy `*.js` files to destination "as is" without evaluating and minimizing them using Terser. | ||
**webpack.config.js** | ||
```js | ||
module.exports = { | ||
plugins: [ | ||
new CopyPlugin({ | ||
patterns: [ | ||
"relative/path/to/file.ext", | ||
{ | ||
from: "**/*", | ||
// Terser skip this file for minimization | ||
info: { minimized: true }, | ||
}, | ||
], | ||
}), | ||
], | ||
}; | ||
``` | ||
##### `yarn workspaces` and `monorepos` | ||
When using `yarn workspaces` or` monorepos`, relative copy paths from node_modules can be broken due to the way packages are hoisting. | ||
To avoid this, should explicitly specify where to copy the files from using `require.resolve`. | ||
**webpack.config.js** | ||
```js | ||
module.exports = { | ||
plugins: [ | ||
new CopyPlugin({ | ||
patterns: [ | ||
{ | ||
from: `${path.dirname( | ||
require.resolve(`${moduleName}/package.json`) | ||
)}/target`, | ||
to: "target", | ||
}, | ||
], | ||
}), | ||
], | ||
}; | ||
``` | ||
## Contributing | ||
@@ -1080,0 +1332,0 @@ |
7
-41.67%1374
117.06%1318
23.64%77590
-1.37%29
11.54%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
Updated
Updated
Updated
Updated
Updated