Open monokee opened 4 years ago
I don't think this idea should be abandoned. It would be relatively easy to pre-compile both html templates and component css with JSDOM.
By extracting the entire component css Stylesheet we're sacrificing the lazy loading aspect of components and are optimistically adding the entire css at page load.
Maybe we should not attach ALL components virtually at build time but instead focus on what a build step is actually supposed to optimize: initial page load. So we just pre-render the components which are active after initial page load without asynchronous interaction by an external agent. We're essentially pre-rendering an application shell while keeping everything else lazy and dynamic. I like that.
Priority for this should be pretty low because the lazy component instantiation mechanism in sekoia already makes our 1000+ components apps super responsive.
I've since written a Gulp task that extracts the style property from components and adds it into a separate style sheet:
function extractCSS(stream) {
const DEFINE_COMPONENT_REGEX = new RegExp('(defineComponent|createComponent|\\.extend)\\(\'(.|[\\n\\r])+?}\\)', 'g');
const COMPONENT_NAME_REGEX = new RegExp('(defineComponent|createComponent|\\.extend)\\(\'([\\w-]+?)\',\\s?{');
const COMPONENT_STYLE_REGEX = new RegExp('style:\\s?\\(`((.|[\\n\\r])+?)\`\\),?');
const STYLE_URL_REGEX = /url\((?!['"]?:)['"]?([^'")]*)['"]?\)/ // url is [1]
return stream.pipe(new Transform({
objectMode: true,
transform(file, enc, callback) {
if (!file.isNull() && file.isBuffer()) {
try {
let content = String(file.contents);
let componentStyles = '';
const componentDefinitions = content.match(DEFINE_COMPONENT_REGEX);
componentDefinitions.forEach(component => {
const styleGroups = component.match(COMPONENT_STYLE_REGEX);
if (styleGroups) {
// remove style from js components
if (styleGroups[0]) { // entire style definition style: (`:host {...}`)
content = content.replace(styleGroups[0], '');
}
// write transformed styles to string that will be put into a css file next
if (styleGroups[1]) { // inside css styles only: :host {...}
const name = component.match(COMPONENT_NAME_REGEX)[2];
// replace :host with component-name and extension
let transformedStyle = styleGroups[1].replaceAll(':host', `:is(${name}, [extends="${name}"])`);
// check if style contains urls
const url = transformedStyle.match(STYLE_URL_REGEX);
if (url && url[1]) {
if (url[1].startsWith('/')) {
console.warn('Did not transform Component CSS URL starting with hyphen: ' + url[1]);
} else if (!url[1].startsWith('data:') && !url[1].startsWith('http')) {
// add ../ to relative urls
transformedStyle = transformedStyle.replace(url[1], `../${url[1]}`);
}
}
componentStyles += transformedStyle;
}
}
});
file.contents = Buffer.from(content);
if (componentStyles.length) {
const { dir } = getNameAndDir(config.extractComponentCSS.index);
const { name } = getNameAndDir(config.extractComponentCSS.target);
src(config.extractComponentCSS.index).pipe(gap.appendText(`
/* --- Extracted from defineComponent --- */
${componentStyles}
`)).pipe(rename(name)).pipe(dest(dir)).pipe(new Transform({
objectMode: true,
transform(file2, enc2, callback2) {
// only doing this because I couldn't figure out how to execute callback1 after dest()
callback(null, file);
callback2(null, file2);
}
}));
} else {
callback(null, file);
}
} catch (e) {
throw new Error('CSS EXTRACT ERROR ' + e.message);
}
} else {
callback(null, file);
}
}
}));
}
CSS Engine
Consider compile time parsing -> bundling, auto-prefixing and minification. Remove the CSS template strings from cue javascript components entirely and write them into a distributable CSS package which can be autoprefixed and minified, further reducing initial page load times due to reduced bundle size and less cpu cycles spent on parsing/scoping/appending the CSS on a per-component level.
Should this be written as a gulp plugin or as a rollup plugin?