CSS plugin

This commit is contained in:
Jake Archibald
2020-09-24 11:49:41 +01:00
parent f5d9023ff3
commit 02e4eaf4b5
9 changed files with 91 additions and 199 deletions

View File

@@ -19,7 +19,6 @@ import postcss from 'postcss';
import postCSSNested from 'postcss-nested';
import postCSSUrl from 'postcss-url';
import postCSSModules from 'postcss-modules';
import postCSSImport from 'postcss-import';
import postCSSSimpleVars from 'postcss-simple-vars';
import cssNano from 'cssnano';
import camelCase from 'lodash.camelcase';
@@ -28,9 +27,19 @@ import glob from 'glob';
const globP = promisify(glob);
const moduleSuffix = '.css';
const prefix = 'css-bundle:';
const sourcePrefix = 'css:';
const addPrefix = 'add-css:';
const assetRe = new RegExp('/fake/path/to/asset/([^/]+)/', 'g');
const appendCssModule = '\0appendCss';
const appendCssSource = `
export default function appendCss(css) {
const style = document.createElement('style');
style.textContent = css;
document.head.append(style);
}
`;
export default function (resolveFileUrl) {
/** @type {string[]} */
let emittedCSSIds;
@@ -39,61 +48,6 @@ export default function (resolveFileUrl) {
/** @type {Map<string, { module: string, css: string }>} */
let pathToResult;
async function loadBundledCSS(path, rollupContext) {
const parsedPath = parsePath(path);
if (!pathToResult.has(path)) {
throw Error(`Cannot find ${path} in pathToResult`);
}
const file = pathToResult.get(path).css;
const cssResult = await postcss([
postCSSImport({
path: ['./', 'static-build/'],
load(path) {
if (!pathToResult.has(path)) {
throw Error(`Cannot find ${path} in pathToResult`);
}
return pathToResult.get(path).css;
},
}),
postCSSUrl({
url: ({ relativePath, url }) => {
if (/^https?:\/\//.test(url)) return url;
const parsedPath = parsePath(relativePath);
const source = readFileSync(resolvePath(dirname(path), relativePath));
const fileId = rollupContext.emitFile({
type: 'asset',
name: parsedPath.base,
source,
});
const hash = createHash('md5');
hash.update(source);
const md5 = hash.digest('hex');
hashToId.set(md5, fileId);
return `/fake/path/to/asset/${md5}/`;
},
}),
cssNano,
]).process(file, {
from: path,
});
const fileId = rollupContext.emitFile({
type: 'asset',
source: cssResult.css,
name: parsedPath.base,
});
emittedCSSIds.push(fileId);
return [
`export default import.meta.ROLLUP_FILE_URL_${fileId}`,
`export const inline = ${JSON.stringify(cssResult.css)}`,
].join('\n');
}
return {
name: 'css',
async buildStart() {
@@ -120,8 +74,28 @@ export default function (resolveFileUrl) {
moduleJSON = json;
},
}),
postCSSUrl({
url: ({ relativePath, url }) => {
if (/^https?:\/\//.test(url)) return url;
const parsedPath = parsePath(relativePath);
const source = readFileSync(
resolvePath(dirname(path), relativePath),
);
const fileId = this.emitFile({
type: 'asset',
name: parsedPath.base,
source,
});
const hash = createHash('md5');
hash.update(source);
const md5 = hash.digest('hex');
hashToId.set(md5, fileId);
return `/fake/path/to/asset/${md5}/`;
},
}),
cssNano,
]).process(file, {
from: undefined,
from: path,
});
const cssClassExports = Object.entries(moduleJSON).map(
@@ -152,14 +126,45 @@ export default function (resolveFileUrl) {
);
},
async resolveId(id, importer) {
if (!id.startsWith(prefix)) return;
if (id === appendCssModule) return id;
const prefix = id.startsWith(sourcePrefix)
? sourcePrefix
: id.startsWith(addPrefix)
? addPrefix
: undefined;
if (!prefix) return;
const resolved = await this.resolve(id.slice(prefix.length), importer);
if (!resolved) throw Error(`Couldn't resolve ${id} from ${importer}`);
return prefix + resolved.id;
},
async load(id) {
if (id.startsWith(prefix)) {
return loadBundledCSS(id.slice(prefix.length), this);
if (id === appendCssModule) return appendCssSource;
if (id.startsWith(sourcePrefix)) {
const path = id.slice(sourcePrefix.length);
if (!pathToResult.has(path)) {
throw Error(`Cannot find ${path} in pathToResult`);
}
const cssStr = JSON.stringify(pathToResult.get(path).css).replace(
assetRe,
(match, hash) =>
`" + import.meta.ROLLUP_FILE_URL_${hashToId.get(hash)} + "`,
);
return `export default ${cssStr};`;
}
if (id.startsWith(addPrefix)) {
const path = id.slice(addPrefix.length);
return (
`import css from 'css:${path}';\n` +
`import appendCss from '${appendCssModule}';\n` +
`appendCss(css);\n`
);
}
if (id.endsWith(moduleSuffix)) {
if (!pathToResult.has(id)) {
@@ -169,27 +174,5 @@ export default function (resolveFileUrl) {
return pathToResult.get(id).module;
}
},
async generateBundle(options, bundle) {
const cssAssets = emittedCSSIds.map((id) => this.getFileName(id));
for (const cssAsset of cssAssets) {
bundle[cssAsset].source = bundle[cssAsset].source.replace(
assetRe,
(_, p1) =>
resolveFileUrl({
fileName: this.getFileName(hashToId.get(p1)),
}),
);
}
for (const item of Object.values(bundle)) {
if (item.type === 'asset') continue;
item.code = item.code.replace(assetRe, (match, p1) =>
resolveFileUrl({
fileName: this.getFileName(hashToId.get(p1)),
}),
);
}
},
};
}