Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 | 2x 2x 2x 2x 2x 2x 2x 48x 48x 48x 61x 61x 48x 48x 2x 44x 44x 44x 44x 44x 44x 62x 14x 48x 48x 61x 5x 56x 56x 56x 56x 52x 52x 52x 52x 1x 1x 51x 11x 11x 11x 10x 10x 11x 40x 1x 40x 40x 4x 4x 48x 8x 40x 40x | import path from "path";
import mime from "mime-types";
import { createFilter } from "@rollup/pluginutils";
import { InputPluginOption, TransformResult } from "rollup";
import MagicString from "magic-string";
import { computeContentHash } from "../crypto";
// Regex to find URLs in CSS/SASS
const CSS_URL_REGEX = /url\(['"]?([^'")]+\.[^.)'"]+)['"]?\)/g;
interface CssUrlOptions {
include?: string | string[];
exclude?: string | string[];
shouldExtract?: (
filePath: string,
fileContent: Uint8Array,
mimeType: string,
) => boolean;
getExtractNameAndUrl?: (
url: string,
filePath: string,
mimeType: string,
contentHash: string,
) => { name: string; url: string };
warnSize?: number;
}
// helper so we can await inside String.replace
const replaceAsync = async (
str: string,
re: RegExp,
fn: (...args: string[]) => Promise<string>,
): Promise<string> => {
const parts: string[] = [];
let last = 0;
for (const m of str.matchAll(re)) {
parts.push(str.slice(last, m.index!), await fn(...m));
last = m.index! + m[0].length;
}
parts.push(str.slice(last));
return parts.join("");
};
export function cssUrl(options: CssUrlOptions = {}): InputPluginOption {
const filter = createFilter(
options.include || ["**/*.scss", "**/*.sass", "**/*.css"],
options.exclude,
);
const shouldExtract =
options.shouldExtract || ((_, content) => content.length > 14 * 1024);
const getExtractNameAndUrl =
options.getExtractNameAndUrl || ((url) => ({ name: url, url }));
const warnSize = options.warnSize || 100 * 1024;
const emittedFiles = new Set();
return {
name: "css-url",
transform: async function (
code: string,
id: string,
): Promise<TransformResult> {
if (!filter(id)) {
return null;
}
// Collect resolved asset file paths so the source manifest plugin
// (and any other downstream consumer) can discover them via
// this.getModuleInfo(id).meta[‘css-url’].assets.
const resolvedAssets: string[] = [];
const resultCode = await replaceAsync(
code,
CSS_URL_REGEX,
async (match, url) => {
// Don’t touch http(s) or already-inlined urls
if (/^(?:data:|https?:|\/\/)/i.test(url)) {
return match;
}
try {
// Resolve path relative to the current file
const currentDir = path.resolve(path.dirname(id));
const filePath = path.join(currentDir, url);
const fileContent = await this.fs.readFile(filePath);
this.addWatchFile(filePath);
resolvedAssets.push(filePath);
// Get MIME type
const mimeType = mime.lookup(filePath);
if (!mimeType) {
this.warn(`Could not determine MIME type for file ${url}`);
return match;
}
if (shouldExtract(filePath, fileContent, mimeType)) {
const contentHash = await computeContentHash(fileContent);
const { name, url: extractedUrl } = getExtractNameAndUrl(
url,
filePath,
mimeType,
contentHash,
);
if (!emittedFiles.has(name)) {
emittedFiles.add(name);
this.emitFile({
type: "asset",
fileName: name,
source: fileContent,
});
}
return `url('${extractedUrl}')`;
} else {
// Warn if file is large
if (fileContent.length > warnSize) {
this.warn(
`Inlining file ${url} with large size ${fileContent.length}`,
);
}
// inline as base64 data URL
const base64 = Buffer.from(fileContent).toString("base64");
return `url('data:${mimeType};base64,${base64}')`;
}
} catch (err) {
this.warn(`Error processing file ${url}: ${err}`);
return match;
}
},
);
if (resultCode === code) {
return null;
}
const magicString = new MagicString(resultCode);
return {
code: magicString.toString(),
map: magicString.generateMap({
includeContent: true,
hires: true,
}),
meta: {
"css-url": { assets: resolvedAssets },
},
};
},
};
}
|