159 lines
4.4 KiB
JavaScript
159 lines
4.4 KiB
JavaScript
|
import debugUtil from "debug";
|
||
|
|
||
|
const debug = debugUtil("Eleventy:Bundle");
|
||
|
|
||
|
/* This class defers any `bundleGet` calls to a post-build transform step,
|
||
|
* to allow `getBundle` to be called before all of the `css` additions have been processed
|
||
|
*/
|
||
|
class OutOfOrderRender {
|
||
|
static SPLIT_REGEX = /(\/\*__EleventyBundle:[^:]*:[^:]*:[^:]*:EleventyBundle__\*\/)/;
|
||
|
static SEPARATOR = ":";
|
||
|
|
||
|
constructor(content) {
|
||
|
this.content = content;
|
||
|
this.managers = {};
|
||
|
}
|
||
|
|
||
|
// type if `get` (return string) or `file` (bundle writes to file, returns file url)
|
||
|
static getAssetKey(type, name, bucket) {
|
||
|
if(Array.isArray(bucket)) {
|
||
|
bucket = bucket.join(",");
|
||
|
} else if(typeof bucket === "string") {
|
||
|
} else {
|
||
|
bucket = "";
|
||
|
}
|
||
|
return `/*__EleventyBundle:${type}:${name}:${bucket || "default"}:EleventyBundle__*/`
|
||
|
}
|
||
|
|
||
|
static parseAssetKey(str) {
|
||
|
if(str.startsWith("/*__EleventyBundle:")) {
|
||
|
let [prefix, type, name, bucket, suffix] = str.split(OutOfOrderRender.SEPARATOR);
|
||
|
return { type, name, bucket };
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
setAssetManager(name, assetManager) {
|
||
|
this.managers[name] = assetManager;
|
||
|
}
|
||
|
|
||
|
setOutputDirectory(dir) {
|
||
|
this.outputDirectory = dir;
|
||
|
}
|
||
|
|
||
|
normalizeMatch(match) {
|
||
|
let ret = OutOfOrderRender.parseAssetKey(match)
|
||
|
return ret || match;
|
||
|
}
|
||
|
|
||
|
findAll() {
|
||
|
let matches = this.content.split(OutOfOrderRender.SPLIT_REGEX);
|
||
|
let ret = [];
|
||
|
for(let match of matches) {
|
||
|
ret.push(this.normalizeMatch(match));
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
setWriteToFileSystem(isWrite) {
|
||
|
this.writeToFileSystem = isWrite;
|
||
|
}
|
||
|
|
||
|
getAllBucketsForPage(pageData) {
|
||
|
let availableBucketsForPage = new Set();
|
||
|
for(let name in this.managers) {
|
||
|
for(let bucket of this.managers[name].getBucketsForPage(pageData)) {
|
||
|
availableBucketsForPage.add(`${name}::${bucket}`);
|
||
|
}
|
||
|
}
|
||
|
return availableBucketsForPage;
|
||
|
}
|
||
|
|
||
|
getManager(name) {
|
||
|
if(!this.managers[name]) {
|
||
|
throw new Error(`No asset manager found for ${name}. Known names: ${Object.keys(this.managers)}`);
|
||
|
}
|
||
|
return this.managers[name];
|
||
|
}
|
||
|
|
||
|
async replaceAll(pageData, stage = 0) {
|
||
|
let matches = this.findAll();
|
||
|
let availableBucketsForPage = this.getAllBucketsForPage(pageData);
|
||
|
let usedBucketsOnPage = new Set();
|
||
|
let bucketsOutputStringCount = {};
|
||
|
let bucketsFileCount = {};
|
||
|
|
||
|
for(let match of matches) {
|
||
|
if(typeof match === "string") {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// type is `file` or `get`
|
||
|
let {type, name, bucket} = match;
|
||
|
let key = `${name}::${bucket}`;
|
||
|
if(!usedBucketsOnPage.has(key)) {
|
||
|
usedBucketsOnPage.add(key);
|
||
|
}
|
||
|
|
||
|
if(type === "get") {
|
||
|
if(!bucketsOutputStringCount[key]) {
|
||
|
bucketsOutputStringCount[key] = 0;
|
||
|
}
|
||
|
bucketsOutputStringCount[key]++;
|
||
|
} else if(type === "file") {
|
||
|
if(!bucketsFileCount[key]) {
|
||
|
bucketsFileCount[key] = 0;
|
||
|
}
|
||
|
bucketsFileCount[key]++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Hoist code in non-default buckets that are output multiple times
|
||
|
// Only hoist if 2+ `get` OR 1+ `get` and 1+ `file`
|
||
|
for(let bucketInfo in bucketsOutputStringCount) {
|
||
|
let stringOutputCount = bucketsOutputStringCount[bucketInfo];
|
||
|
if(stringOutputCount > 1 || stringOutputCount === 1 && bucketsFileCount[bucketInfo] > 0) {
|
||
|
let [name, bucketName] = bucketInfo.split("::");
|
||
|
this.getManager(name).hoistBucket(pageData, bucketName);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
let content = await Promise.all(matches.map(match => {
|
||
|
if(typeof match === "string") {
|
||
|
return match;
|
||
|
}
|
||
|
|
||
|
let {type, name, bucket} = match;
|
||
|
let manager = this.getManager(name);
|
||
|
|
||
|
// Quit early if in stage 0, run delayed replacements if in stage 1+
|
||
|
if(typeof manager.isDelayed === "function" && manager.isDelayed() && stage === 0) {
|
||
|
return OutOfOrderRender.getAssetKey(type, name, bucket);
|
||
|
}
|
||
|
|
||
|
if(type === "get") {
|
||
|
// returns promise
|
||
|
return manager.getForPage(pageData, bucket);
|
||
|
} else if(type === "file") {
|
||
|
// returns promise
|
||
|
return manager.writeBundle(pageData, bucket, {
|
||
|
output: this.outputDirectory,
|
||
|
write: this.writeToFileSystem,
|
||
|
});
|
||
|
}
|
||
|
return "";
|
||
|
}));
|
||
|
|
||
|
for(let bucketInfo of availableBucketsForPage) {
|
||
|
if(!usedBucketsOnPage.has(bucketInfo)) {
|
||
|
let [name, bucketName] = bucketInfo.split("::");
|
||
|
debug(`WARNING! \`${pageData.inputPath}\` has unbundled \`${name}\` assets (in the '${bucketName}' bucket) that were not written to or used on the page. You might want to add a call to \`getBundle('${name}', '${bucketName}')\` to your content! Learn more: https://github.com/11ty/eleventy-plugin-bundle#asset-bucketing`);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return content.join("");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
export { OutOfOrderRender };
|