Skip to content

Commit

Permalink
Fixes #1677.
Browse files Browse the repository at this point in the history
  • Loading branch information
zachleat committed Mar 6, 2021
1 parent 1993035 commit c3c9d7b
Show file tree
Hide file tree
Showing 9 changed files with 247 additions and 126 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
"gray-matter": "^4.0.2",
"hamljs": "^0.6.2",
"handlebars": "^4.7.6",
"is-glob": "^4.0.1",
"liquidjs": "^9.16.1",
"lodash": "^4.17.20",
"luxon": "^1.25.0",
Expand Down
21 changes: 14 additions & 7 deletions src/Eleventy.js
Original file line number Diff line number Diff line change
Expand Up @@ -525,10 +525,13 @@ Arguments:
incrementalFile,
this.eleventyFiles.getIncludesDir()
);
let isLayout = TemplatePath.startsWithSubPath(
incrementalFile,
this.eleventyFiles.getLayoutsDir()
);

let isLayout = false;
let layoutsDir = this.eleventyFiles.getLayoutsDir();
if (layoutsDir) {
isLayout = TemplatePath.startsWithSubPath(incrementalFile, layoutsDir);
}

let isJSDependency = this.watchTargets.isJavaScriptDependency(
incrementalFile
);
Expand All @@ -537,8 +540,9 @@ Arguments:
}
}

let writeResult = await this.write();
let hasError = !!writeResult.error;
await this.write();
// let writeResult = await this.write();
// let hasError = !!writeResult.error;

this.writer.resetIncrementalFile();

Expand Down Expand Up @@ -680,7 +684,10 @@ Arguments:
{
ignored: ignores,
ignoreInitial: true,
// also interesting: awaitWriteFinish
awaitWriteFinish: {
stabilityThreshold: 150,
pollInterval: 25,
},
},
configOptions
);
Expand Down
119 changes: 82 additions & 37 deletions src/TemplatePassthrough.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const fs = require("fs");
const isGlob = require("is-glob");
const copy = require("recursive-copy");
const TemplatePath = require("./TemplatePath");
const debug = require("debug")("Eleventy:TemplatePassthrough");
Expand All @@ -9,6 +11,8 @@ class TemplatePassthroughError extends EleventyBaseError {}

class TemplatePassthrough {
constructor(path, outputDir, inputDir) {
this.rawPath = path;

// inputPath is relative to the root of your project and not your Eleventy input directory.
this.inputPath = path.inputPath;
// inputDir is only used when stripping from output path in `getOutputPath`
Expand All @@ -20,6 +24,10 @@ class TemplatePassthrough {
this.isDryRun = false;
}

getPath() {
return this.rawPath;
}

getOutputPath(inputFileFromGlob) {
const { inputDir, outputDir, outputPath, inputPath } = this;

Expand Down Expand Up @@ -60,73 +68,110 @@ class TemplatePassthrough {
const files = TemplatePath.addLeadingDotSlashArray(
await fastglob(glob, {
caseSensitiveMatch: false,
dot: true
dot: true,
})
);
bench.after();
return files;
}

/* Types:
* 1. via glob, individual files found
* 2. directory, triggers an event for each file
* 3. individual file
*/
async copy(src, dest, copyOptions) {
if (
TemplatePath.stripLeadingDotSlash(dest).includes(
!TemplatePath.stripLeadingDotSlash(dest).includes(
TemplatePath.stripLeadingDotSlash(this.outputDir)
)
) {
let fileCopyCount = 0;
// copy() returns a promise
return copy(src, dest, copyOptions)
.on(copy.events.COPY_FILE_START, function(copyOp) {
// Access to individual files at `copyOp.src`
debug("Copying individual file %o", copyOp.src);
aggregateBench.get("Passthrough Copy File").before();
})
.on(copy.events.COPY_FILE_COMPLETE, function() {
fileCopyCount++;
aggregateBench.get("Passthrough Copy File").after();
})
.then(() => {
return fileCopyCount;
});
return Promise.reject(
new TemplatePassthroughError(
"Destination is not in the site output directory. Check your passthrough paths."
)
);
}
return Promise.reject(
new TemplatePassthroughError(
"Destination is not in the site output directory. Check your passthrough paths."
)
);

let fileCopyCount = 0;
let map = {};

// returns a promise
return copy(src, dest, copyOptions)
.on(copy.events.COPY_FILE_START, function (copyOp) {
// Access to individual files at `copyOp.src`
debug("Copying individual file %o", copyOp.src);
map[copyOp.src] = copyOp.dest;
aggregateBench.get("Passthrough Copy File").before();
})
.on(copy.events.COPY_FILE_COMPLETE, function () {
fileCopyCount++;
aggregateBench.get("Passthrough Copy File").after();
})
.then(() => {
return {
count: fileCopyCount,
map,
};
});
}

async write() {
if (this.isDryRun) {
return Promise.resolve({
count: 0,
map: {},
});
}

const copyOptions = {
overwrite: true,
dot: true,
junk: false,
results: false
results: false,
};
let promises = [];

if (!this.isDryRun) {
debug("Copying %o", this.inputPath);

// If directory or file, recursive copy
if (await TemplatePath.exists(this.inputPath)) {
// IMPORTANT: this returns a promise, does not await for promise to finish
return this.copy(this.inputPath, this.getOutputPath(), copyOptions);
}
debug("Copying %o", this.inputPath);

if (!isGlob(this.inputPath) && fs.existsSync(this.inputPath)) {
// IMPORTANT: this returns a promise, does not await for promise to finish
promises.push(
this.copy(this.inputPath, this.getOutputPath(), copyOptions)
);
} else {
// If not directory or file, attempt to get globs
const files = await this.getFiles(this.inputPath);
let files = await this.getFiles(this.inputPath);

const promises = files.map(inputFile =>
this.copy(inputFile, this.getOutputPath(inputFile), copyOptions)
);
promises = files.map((inputFile) => {
let target = this.getOutputPath(inputFile);
return this.copy(inputFile, target, copyOptions);
});
}

return Promise.all(promises).catch(err => {
// IMPORTANT: this returns an array of promises, does not await for promise to finish
return Promise.all(promises)
.then((results) => {
// collate the count and input/output map results from the array.
let count = 0;
let map = {};

for (let result of results) {
count += result.count;
Object.assign(map, result.map);
}

return {
count,
map,
};
})
.catch((err) => {
throw new TemplatePassthroughError(
`Error copying passthrough files: ${err.message}`,
err
);
});
}
}
}

Expand Down
104 changes: 66 additions & 38 deletions src/TemplatePassthroughManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const EleventyBaseError = require("./EleventyBaseError");
const TemplatePassthrough = require("./TemplatePassthrough");
const TemplatePath = require("./TemplatePath");
const debug = require("debug")("Eleventy:TemplatePassthroughManager");
const debugDev = require("debug")("Dev:Eleventy:TemplatePassthroughManager");

class TemplatePassthroughManagerConfigError extends EleventyBaseError {}
class TemplatePassthroughManagerCopyError extends EleventyBaseError {}
Expand All @@ -21,6 +22,7 @@ class TemplatePassthroughManager {

reset() {
this.count = 0;
this.conflictMap = {};
this.incrementalFile = null;
debug("Resetting counts to 0");
}
Expand Down Expand Up @@ -93,34 +95,50 @@ class TemplatePassthroughManager {
return this.count;
}

async copyPath(path) {
let pass = new TemplatePassthrough(path, this.outputDir, this.inputDir);
getTemplatePassthroughForPath(path) {
return new TemplatePassthrough(path, this.outputDir, this.inputDir);
}

if (this.incrementalFile && path.inputPath !== this.incrementalFile) {
pass.setDryRun(true);
} else {
pass.setDryRun(this.isDryRun);
async copyPassthrough(pass) {
if (!(pass instanceof TemplatePassthrough)) {
throw new TemplatePassthroughManagerCopyError(
"copyPassthrough expects an instance of TemplatePassthrough"
);
}

let path = pass.getPath();
pass.setDryRun(this.isDryRun);

return pass
.write()
.then((fileCopyCount) => {
.then(({ count, map }) => {
for (let src in map) {
let dest = map[src];
if (this.conflictMap[dest]) {
throw new TemplatePassthroughManagerCopyError(
`Multiple passthrough copy files are trying to write to the same output file (${dest}). ${src} and ${this.conflictMap[dest]}`
);
}

debugDev(
"Adding %o to passthrough copy conflict map, from %o",
dest,
src
);
this.conflictMap[dest] = src;
}

if (pass.isDryRun) {
// We don’t count the skipped files as we need to iterate over them
debug(
"Skipped %o (either from --dryrun or --incremental)",
path.inputPath
);
} else {
if (Array.isArray(fileCopyCount)) {
// globs
for (let count of fileCopyCount) {
this.count += count;
}
} else {
this.count += fileCopyCount;
if (count) {
this.count += count;
}
debug("Copied %o (%d files)", path.inputPath, fileCopyCount || 0);
debug("Copied %o (%d files)", path.inputPath, count || 0);
}
})
.catch(function (e) {
Expand Down Expand Up @@ -149,40 +167,50 @@ class TemplatePassthroughManager {
return false;
}

// Performance note: these can actually take a fair bit of time, but aren’t a
// bottleneck to eleventy. The copies are performed asynchronously and don’t affect eleventy
// write times in a significant way.
async copyAll(paths) {
getAllNormalizedPaths(paths) {
if (
this.incrementalFile &&
this.isPassthroughCopyFile(paths, this.incrementalFile)
) {
return this.copyPath(this._normalizePaths(this.incrementalFile)).then(
() => {
debug(
`TemplatePassthrough --incremental copy finished. Current count: ${this.count}`
);
}
);
return [this._normalizePaths(this.incrementalFile)];
}

let promises = [];
debug("TemplatePassthrough copy started.");
for (let path of this.getConfigPaths()) {
let normalizedPaths = [];

let pathsFromConfigurationFile = this.getConfigPaths();
for (let path of pathsFromConfigurationFile) {
debug("TemplatePassthrough copying from config: %o", path);
promises.push(this.copyPath(path));
normalizedPaths.push(path);
}

let passthroughPaths = this.getNonTemplatePaths(paths);
for (let path of passthroughPaths) {
let normalizedPath = this._normalizePaths(path);
debug(
`TemplatePassthrough copying from non-matching file extension: ${normalizedPath.inputPath}`
);
promises.push(this.copyPath(normalizedPath));
if (paths && paths.length) {
let passthroughPaths = this.getNonTemplatePaths(paths);
for (let path of passthroughPaths) {
let normalizedPath = this._normalizePaths(path);
debug(
`TemplatePassthrough copying from non-matching file extension: ${normalizedPath.inputPath}`
);
normalizedPaths.push(normalizedPath);
}
}

return normalizedPaths;
}

// Performance note: these can actually take a fair bit of time, but aren’t a
// bottleneck to eleventy. The copies are performed asynchronously and don’t affect eleventy
// write times in a significant way.
async copyAll(paths) {
debug("TemplatePassthrough copy started.");
let normalizedPaths = this.getAllNormalizedPaths(paths);
let passthroughs = [];
for (let path of normalizedPaths) {
passthroughs.push(this.getTemplatePassthroughForPath(path));
}

return Promise.all(promises).then(() => {
return Promise.all(
passthroughs.map((pass) => this.copyPassthrough(pass))
).then(() => {
debug(`TemplatePassthrough copy finished. Current count: ${this.count}`);
});
}
Expand Down
Loading

0 comments on commit c3c9d7b

Please sign in to comment.