Skip to content

Commit

Permalink
feature/issue 1257 CSS optimization workflow parity (#1258)
Browse files Browse the repository at this point in the history
* initial implementation

* WIP updating test cases

* refresh PostCSS plugin README docs

* document optimization in development behavior

* restore test case with exception

* tests passing

* gate font plugin from only serving files

* optimize supported one-off externalized assets

* restore disabled test cases

* fixup comments
  • Loading branch information
thescientist13 committed Aug 2, 2024
1 parent 5084cdc commit 12c9793
Show file tree
Hide file tree
Showing 21 changed files with 119 additions and 77 deletions.
61 changes: 51 additions & 10 deletions packages/cli/src/config/rollup.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* eslint-disable complexity */
/* eslint-disable complexity, max-depth */
import fs from 'fs';
import path from 'path';
import { checkResourceExists, normalizePathnameForWindows } from '../lib/resource-utils.js';
Expand Down Expand Up @@ -487,7 +487,10 @@ function greenwoodSyncImportAttributes(compilation) {

unbundledAssetsRefMapper[emitConfig.name] = {
importers: [...unbundledAssetsRefMapper[emitConfig.name].importers, bundle],
importRefs: [...unbundledAssetsRefMapper[emitConfig.name].importRefs, importRef]
importRefs: [...unbundledAssetsRefMapper[emitConfig.name].importRefs, importRef],
preBundled,
source,
sourceURL
};
}
}
Expand All @@ -499,20 +502,58 @@ function greenwoodSyncImportAttributes(compilation) {
// we use write bundle here to handle import.meta.ROLLUP_ASSET_URL_${ref} linking
// since it seems that Rollup will not do it after the bundling hook
// https://github.com/rollup/rollup/blob/v3.29.4/docs/plugin-development/index.md#generatebundle
writeBundle(options, bundles) {
async writeBundle(options, bundles) {
const resourcePlugins = compilation.config.plugins.filter((plugin) => {
return plugin.type === 'resource';
}).map((plugin) => {
return plugin.provider(compilation);
});

for (const asset in unbundledAssetsRefMapper) {
for (const bundle in bundles) {
const { fileName } = bundles[bundle];
const hash = fileName.split('.')[fileName.split('.').length - 2];
const ext = fileName.split('.').pop();

if (fileName.replace(`.${hash}`, '') === asset) {
unbundledAssetsRefMapper[asset].importers.forEach((importer, idx) => {
let contents = fs.readFileSync(new URL(`./${importer}`, compilation.context.outputDir), 'utf-8');
if (externalizedResources.includes(ext)) {
const hash = fileName.split('.')[fileName.split('.').length - 2];

contents = contents.replace(unbundledAssetsRefMapper[asset].importRefs[idx], fileName);
if (fileName.replace(`.${hash}`, '') === asset) {
unbundledAssetsRefMapper[asset].importers.forEach((importer, idx) => {
let contents = fs.readFileSync(new URL(`./${importer}`, compilation.context.outputDir), 'utf-8');

fs.writeFileSync(new URL(`./${importer}`, compilation.context.outputDir), contents);
});
contents = contents.replace(unbundledAssetsRefMapper[asset].importRefs[idx], fileName);

fs.writeFileSync(new URL(`./${importer}`, compilation.context.outputDir), contents);
});

// have to apply Greenwood's optimizing here instead of in generateBundle
// since we can't do async work inside a sync AST operation
if (!asset.preBundled) {
const assetUrl = unbundledAssetsRefMapper[asset].sourceURL;
const request = new Request(assetUrl, { headers: { 'Content-Type': 'text/css' } });
let response = new Response(unbundledAssetsRefMapper[asset].source);

for (const plugin of resourcePlugins) {
if (plugin.shouldPreIntercept && await plugin.shouldPreIntercept(assetUrl, request, response.clone())) {
response = await plugin.preIntercept(assetUrl, request, response.clone());
}
}

for (const plugin of resourcePlugins) {
if (plugin.shouldIntercept && await plugin.shouldIntercept(assetUrl, request, response.clone())) {
response = await plugin.intercept(assetUrl, request, response.clone());
}
}

for (const plugin of resourcePlugins) {
if (plugin.shouldOptimize && await plugin.shouldOptimize(assetUrl, response.clone())) {
response = await plugin.optimize(assetUrl, response.clone());
}
}

fs.writeFileSync(new URL(`./${fileName}`, compilation.context.outputDir), await response.text());
}
}
}
}
}
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const resourcePlugins = config.plugins
.filter(plugin => plugin.name !== 'plugin-node-modules:resource' && plugin.name !== 'plugin-user-workspace')
.map(plugin => plugin.provider({
context: {
outputDir: new URL(`file://${process.cwd()}/public`),
projectDirectory: new URL(`file://${process.cwd()}/`),
scratchDir: new URL(`file://${process.cwd()}/.greenwood/`)
},
Expand Down
40 changes: 13 additions & 27 deletions packages/cli/src/plugins/resource/plugin-standard-css.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,23 +49,26 @@ function bundleCss(body, url, compilation) {
barePath = barePath.replace('/', '');
}

const locationUrl = barePath.startsWith('node_modules')
const locationUrl = barePath.indexOf('node_modules/') >= 0
? new URL(`./${barePath}`, projectDirectory)
: new URL(`./${barePath}`, userWorkspace);

if (fs.existsSync(locationUrl)) {
const isDev = process.env.__GWD_COMMAND__ === 'develop'; // eslint-disable-line no-underscore-dangle
const hash = hashString(fs.readFileSync(locationUrl, 'utf-8'));
const ext = barePath.split('.').pop();
const hashedRoot = barePath.replace(`.${ext}`, `.${hash}.${ext}`);
const hashedRoot = isDev ? barePath : barePath.replace(`.${ext}`, `.${hash}.${ext}`);

fs.mkdirSync(new URL(`./${path.dirname(barePath)}/`, outputDir), {
recursive: true
});
if (!isDev) {
fs.mkdirSync(new URL(`./${path.dirname(hashedRoot)}/`, outputDir), {
recursive: true
});

fs.promises.copyFile(
locationUrl,
new URL(`./${hashedRoot}`, outputDir)
);
fs.promises.copyFile(
locationUrl,
new URL(`./${hashedRoot}`, outputDir)
);
}

optimizedCss += `url('${basePath}${hashedRoot}')`;
} else {
Expand Down Expand Up @@ -311,7 +314,7 @@ class StandardCssResource extends ResourceInterface {
}

async intercept(url, request, response) {
let body = await response.text();
let body = bundleCss(await response.text(), url, this.compilation);
let headers = {};

if (request.headers.get('Accept')?.indexOf('text/javascript') >= 0 && !url.searchParams.has('type')) {
Expand All @@ -323,23 +326,6 @@ class StandardCssResource extends ResourceInterface {

return new Response(body, { headers });
}

async shouldOptimize(url, response) {
const { protocol, pathname, searchParams } = url;
const isValidCss = pathname.split('.').pop() === this.extensions[0]
&& protocol === 'file:'
&& response.headers.get('Content-Type').indexOf(this.contentType) >= 0
&& searchParams.get('type') !== 'css';

return this.compilation.config.optimization !== 'none' && isValidCss;
}

async optimize(url, response) {
const body = await response.text();
const optimizedBody = bundleCss(body, url, this.compilation);

return new Response(optimizedBody);
}
}

const greenwoodPluginStandardCss = {
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/src/plugins/resource/plugin-standard-font.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ class StandardFontResource extends ResourceInterface {
}

async shouldServe(url) {
return this.extensions.indexOf(url.pathname.split('.').pop()) >= 0;
const { pathname, protocol } = url;

return this.extensions.indexOf(pathname.split('.').pop()) >= 0 && protocol === 'file:';
}

async serve(url) {
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/plugins/resource/plugin-standard-html.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ class StandardHtmlResource extends ResourceInterface {
}

async shouldOptimize(url, response) {
return response.headers.get('Content-Type').indexOf(this.contentType) >= 0;
return response.headers.get('Content-Type')?.indexOf(this.contentType) >= 0;
}

async optimize(url, response) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ describe('Develop Greenwood With: ', function() {
});

it('should return the correct response body', function(done) {
expect(body).to.contain('color: blue;');
expect(body).to.contain('*{color:blue}');
done();
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -639,7 +639,7 @@ describe('Develop Greenwood With: ', function() {
});

it('should return the correct response body', function(done) {
expect(body).to.contain('color: blue;');
expect(body).to.contain('*{color:blue}');
done();
});
});
Expand Down Expand Up @@ -983,7 +983,7 @@ describe('Develop Greenwood With: ', function() {
});

it('should correctly return CSS from the developers local files', function(done) {
expect(body).to.contain('/* Set the global variables for everything. Change these to use your own fonts/colours. */');
expect(body).to.contain(':root{--sans-font:-apple-system');
done();
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ describe('Develop Greenwood With: ', function() {
});

it('should correctly return CSS from the developers local files', function(done) {
expect(body).to.equal(':root {\n --color-primary: #135;\n}');
expect(body).to.equal(':root{--color-primary:#135}');

done();
});
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/test/cases/develop.spa/develop.spa.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ describe('Develop Greenwood With: ', function() {
});

it('should return the expected body contents', function(done) {
expect(body.replace(/\n/g, '').indexOf('* { color: red;}')).to.equal(0);
expect(body.replace(/\n/g, '').indexOf('*{color:red}')).to.equal(0);
done();
});
});
Expand Down Expand Up @@ -203,7 +203,7 @@ describe('Develop Greenwood With: ', function() {
});

it('should return the expected body contents', function(done) {
expect(body.indexOf('/* Set the global variables for everything. Change these to use your own fonts/colours. */')).to.equal(0);
expect(body.indexOf(':root{--sans-font:-apple-system')).to.equal(0);
done();
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ describe('Build Greenwood With: ', function() {
const styleContents = fs.readFileSync(styles[0], 'utf-8');

expect(styles.length).to.equal(1);
expect(styleContents).to.contain(':host {\n color: red;\n}');
expect(styleContents).to.equal(':host{color:red}');
});
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ describe('Build Greenwood With: ', function() {
});
});

// TODO not sure why this was disabled, but should enable this test case
xdescribe('Custom Format Dynamic Contact Page', function() {
let aboutPage;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ describe('Build Greenwood With: ', function() {
const styleContents = fs.readFileSync(styles[0], 'utf-8');

expect(styles.length).to.equal(1);
expect(styleContents).to.contain(':host {\n text-align: center;');
expect(styleContents).to.contain(':host{text-align:center;margin-bottom:40px;}');
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ describe('Develop Greenwood With: ', function() {
});

it('should return the correct response body', function(done) {
expect(body).to.equal(':host {\n color: red;\n}');
expect(body).to.equal(':host{color:red}');
done();
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ describe('Serve Greenwood With: ', function() {
it('should have the expected output from importing hero.css as a Constructable Stylesheet', function() {
const scriptContents = fs.readFileSync(scripts[0], 'utf-8');

expect(scriptContents).to.contain('const sheet = new CSSStyleSheet();sheet.replaceSync(`:host { color: red; }`);');
expect(scriptContents).to.contain('const sheet = new CSSStyleSheet();sheet.replaceSync(`:host{color:red}`);');
});

it('should have the expected output from importing hero.json', function() {
Expand Down Expand Up @@ -147,7 +147,7 @@ describe('Serve Greenwood With: ', function() {
it('should have the expected output from importing hero.css as a Constructable Stylesheet', function() {
const scriptContents = fs.readFileSync(scripts[0], 'utf-8');

expect(scriptContents).to.contain('const sheet = new CSSStyleSheet();sheet.replaceSync(`:host { color: red; }`);');
expect(scriptContents).to.contain('const sheet = new CSSStyleSheet();sheet.replaceSync(`:host{color:red}`);');
});

it('should have the expected output from importing hero.json', function() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ describe('Develop Greenwood With: ', function() {
});

it('should correctly return CSS from the developers local files', function(done) {
expect(body).to.equal(':root {\n --color-primary: #135;\n --color-secondary: #74b238;\n --font-family: \'Optima\', sans-serif;\n}');
expect(body).to.equal(':root{--color-primary:#135;--color-secondary:#74b238;--font-family:\'Optima\', sans-serif;}');

done();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ describe('Build Greenwood With: ', function() {
it('should have the expected output from importing styles.css in main.js', function() {
const contents = fs.readFileSync(scripts[0], 'utf-8');

expect(contents).to.contain('import from styles.css: p { color: red; }');
expect(contents).to.contain('import from styles.css: p{color:red}');
});
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,15 +86,17 @@ describe('Develop Greenwood With: ', function() {
// https://github.com/ProjectEvergreen/greenwood/issues/766
// https://unpkg.com/browse/bootstrap@4.6.1/dist/css/bootstrap.css
// https://unpkg.com/browse/font-awesome@4.7.0/css/font-awesome.css
it('should return an ECMASCript module', function() {
// TODO looks like this use case is "broken" within csstree
// https://github.com/csstree/csstree/issues/179
xit('should return an ECMASCript module', function() {
expect(data.replace('\n', '').replace(/ /g, '').trim())
.to.equal('constraw=`*{background-image:url("/assets/background.jpg");font-family:\'Arial\'}.blockquote-footer::before{content:"\\\\2014\\\\00A0";}.fa-chevron-right:before{content:"\\\\f054";}`;exportdefaultraw;'); // eslint-disable-line max-len
.to.equal('constraw=`*{background-image:url(\'/assets/background.jpg\');font-family:\'Arial\';}.blockquote-footer::before{content:"\\\\2014\\\\00A0";}.fa-chevron-right:before{content:"\\\\f054";}`;exportdefaultraw;'); // eslint-disable-line max-len
});
});

// https://github.com/ProjectEvergreen/greenwood/pull/747
// https://unpkg.com/browse/@material/mwc-button@0.22.1/styles.css.js
xdescribe('Develop command for .css.js files behaviors (CSS in disguise)', function() {
describe('Develop command for .css.js files behaviors (CSS in disguise)', function() {
let response = {};
let data;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,8 @@ describe('Build Greenwood With: ', function() {
it('should have the expected output from importing styles.css in index.html', function() {
const styles = dom.window.document.querySelectorAll('style');

// TODO minify CSS-in-JS?
expect(styles.length).to.equal(1);
expect(styles[0].textContent).to.contain('.footer { width: 90%; margin: 0 auto; padding: 0; text-align: center; }');
expect(styles[0].textContent).to.contain('.footer{width:90%;margin:0 auto;padding:0;text-align:center;}');
});
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ describe('Serve Greenwood With: ', function() {
const styleTag = productsPageDom.window.document.querySelectorAll('body > style');

expect(styleTag.length).to.equal(1);
expect(styleTag[0].textContent.replace(/ /g, '').replace(/\n/, '')).contain('h1{color:red;}');
expect(styleTag[0].textContent.replace(/ /g, '').replace(/\n/, '')).contain('h1{color:red}');
done();
});

Expand All @@ -104,7 +104,7 @@ describe('Serve Greenwood With: ', function() {

expect(cardComponents.length).to.equal(2);
Array.from(cardComponents).forEach((card) => {
expect(card.innerHTML).contain('display: flex;');
expect(card.innerHTML).contain('display:flex;');
});
done();
});
Expand Down Expand Up @@ -137,7 +137,7 @@ describe('Serve Greenwood With: ', function() {

expect(cardComponents.length).to.equal(2);
Array.from(cardComponents).forEach((card) => {
expect(card.innerHTML).contain('display: flex;');
expect(card.innerHTML).contain('display:flex;');
});
done();
});
Expand Down
Loading

0 comments on commit 12c9793

Please sign in to comment.