Skip to content
This repository has been archived by the owner on May 15, 2024. It is now read-only.

feat(devBundler): aggregate styles into dependencies and local overrides #557

Merged
merged 9 commits into from
Sep 5, 2023
1 change: 1 addition & 0 deletions jest.esm.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ module.exports = {
'!**/node_modules/**',
'!**/build/**',
'!packages/*/test-results/**',
'!packages/*/jest.esm.setup.js',
// Despite it not being in the root, coverage reports see this package
'!packages/one-app-locale-bundler/**',
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ describe('server styles dispatcher', () => {
serverStylesDispatcher({ bundleType: BUNDLE_TYPES.SERVER })
);
const onEnd = hooks.onEnd[0];
addStyle('body{background: white;}body > p{font-color: black;}');
addStyle('digestMock', 'body{background: white;}body > p{font-color: black;}');

/* onEnd test start */
// mock the bundle using mockFs
Expand All @@ -64,7 +64,10 @@ describe('server styles dispatcher', () => {
expect(actualBundleContent).toMatchInlineSnapshot(`
"const mock = \\"JavaScript Content\\";
;module.exports.ssrStyles = {
getFullSheet: () => \\"body{background: white;}body > p{font-color: black;}\\",
aggregatedStyles: [{\\"css\\":\\"body{background: white;}body > p{font-color: black;}\\",\\"digest\\":\\"digestMock\\"}],
getFullSheet: function getFullSheet() {
return this.aggregatedStyles.reduce((acc, { css }) => acc + css, '');
},
};"
`);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ describe('Esbuild plugin stylesLoader', () => {

expect(loader).toEqual('js');
expect(contents).toMatchInlineSnapshot(`
"const digest = 'c0c0c0be320475d1514fe8e0c023d2780b6e23c2adab14a438a0ee2ef98369ba';
"const digest = '5e9583e668d7632ccabf75f612a320b29f5f48cd7a7e86489c7b0f8f5fdcdbbe';
const css = \`body {
background: white;
}
Expand Down Expand Up @@ -152,7 +152,7 @@ body > p {
expect(sassCompile).toHaveBeenCalledTimes(0);
expect(loader).toEqual('js');
expect(contents).toMatchInlineSnapshot(`
"const digest = '83279c4025e8b1107c3f376acaaac5656a3b68d0066ab70f2ceeb3c065a5751f';
"const digest = '5e9583e668d7632ccabf75f612a320b29f5f48cd7a7e86489c7b0f8f5fdcdbbe';
const css = \`body {
background: white;
}
Expand Down Expand Up @@ -200,7 +200,7 @@ body > p {

expect(loader).toEqual('js');
expect(contents).toMatchInlineSnapshot(`
"const digest = 'c0c0c0be320475d1514fe8e0c023d2780b6e23c2adab14a438a0ee2ef98369ba';
"const digest = '11e1fda0219a10c2de0ad6b28c1c6519985965cbef3f5b8f8f119d16f1bafff3';
const css = \`body {
background: white;
}
Expand Down Expand Up @@ -239,7 +239,7 @@ body > p {
expect(sassCompile).toHaveBeenCalledTimes(0);
expect(loader).toEqual('js');
expect(contents).toMatchInlineSnapshot(`
"const digest = '83279c4025e8b1107c3f376acaaac5656a3b68d0066ab70f2ceeb3c065a5751f';
"const digest = '5e9583e668d7632ccabf75f612a320b29f5f48cd7a7e86489c7b0f8f5fdcdbbe';
const css = \`body {
background: white;
}
Expand Down Expand Up @@ -306,7 +306,7 @@ export { css, digest };"
expect(sassCompile).toHaveBeenCalledTimes(0);
expect(loader).toEqual('js');
expect(contents).toMatchInlineSnapshot(`
"const digest = '786f696ae19422021e0f17df7c6dd6eb43f92c9c101f7d0649b341165dda1b31';
"const digest = 'f85b3a3cf0c00eb3fd23e6d440b10077d7493cf7f127538acb994cade5bce451';
const css = \` ._root_1vf0l_1 {
background: white;
}
Expand Down Expand Up @@ -378,7 +378,7 @@ export { css, digest };"
expect(sassCompile).toHaveBeenCalledTimes(0);
expect(loader).toEqual('js');
expect(contents).toMatchInlineSnapshot(`
"const digest = '786f696ae19422021e0f17df7c6dd6eb43f92c9c101f7d0649b341165dda1b31';
"const digest = 'f85b3a3cf0c00eb3fd23e6d440b10077d7493cf7f127538acb994cade5bce451';
const css = \` ._root_1vf0l_1 {
background: white;
}
Expand Down Expand Up @@ -432,7 +432,7 @@ export { css, digest };"

expect(loader).toEqual('js');
expect(contents).toMatchInlineSnapshot(`
"const digest = 'c0c0c0be320475d1514fe8e0c023d2780b6e23c2adab14a438a0ee2ef98369ba';
"const digest = '5e9583e668d7632ccabf75f612a320b29f5f48cd7a7e86489c7b0f8f5fdcdbbe';
const css = \`body {
background: white;
}
Expand Down Expand Up @@ -481,7 +481,7 @@ body > p {
expect(sassCompile).toHaveBeenCalledTimes(0);
expect(loader).toEqual('js');
expect(contents).toMatchInlineSnapshot(`
"const digest = '83279c4025e8b1107c3f376acaaac5656a3b68d0066ab70f2ceeb3c065a5751f';
"const digest = '5e9583e668d7632ccabf75f612a320b29f5f48cd7a7e86489c7b0f8f5fdcdbbe';
const css = \`body {
background: white;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,50 @@
* permissions and limitations under the License.
*/

import { getAggregatedStyles, emptyAggregatedStyles, addStyle } from '../../../esbuild/utils/server-style-aggregator';
import {
getAggregatedStyles,
emptyAggregatedStyles,
addStyle,
} from '../../../esbuild/utils/server-style-aggregator';

describe('serverSideAggirgator utilities', () => {
describe('serverSideAggregatorStyles utilities', () => {
beforeEach(() => {
emptyAggregatedStyles();
jest.clearAllMocks();
});

describe('addStyle', () => {
it('should append given perameter to aggregatedStyles', () => {
addStyle('testing string');
expect(getAggregatedStyles()).toBe('testing string');
it('should append styles to the aggregatedStyles as an object with a digest and css key', () => {
addStyle('digestMock', 'cssMock', false);
expect(getAggregatedStyles()).toBe('[{"css":"cssMock","digest":"digestMock"}]');
});

it('should deduplicate styles added that share the same digest', () => {
addStyle('digestMock', 'cssMock', false);
addStyle('digestMock', 'cssMock', false);
addStyle('digestMock', 'cssMock', false);
addStyle('digestMock', 'cssMock', false);
expect(getAggregatedStyles()).toBe('[{"css":"cssMock","digest":"digestMock"}]');
});
});
describe('getAggrigatedStyles', () => {
it('should return string of any and all collected styles', () => {
expect(getAggregatedStyles()).toBe('');

describe('getAggregatedStyles', () => {
it('should return a stringified empty array when no styles have been added', () => {
expect(getAggregatedStyles()).toBe('[]');
});

it('should return a stringified array with dependency styles declared first and local styles declared last', () => {
addStyle('digestLocalMock', 'cssLocalMock', false);
addStyle('digestDepsMock', 'cssDepsMock', true);
expect(getAggregatedStyles()).toBe('[{"css":"cssDepsMock","digest":"digestDepsMock"},{"css":"cssLocalMock","digest":"digestLocalMock"}]');
});
});
describe('emptyAggrigatedStyles', () => {

describe('emptyAggregatedStyles', () => {
it('should return emptied string', () => {
addStyle('testing string');
addStyle('digestMock', 'cssMock', false);
expect(getAggregatedStyles()).toBe('[{"css":"cssMock","digest":"digestMock"}]');
emptyAggregatedStyles();
expect(getAggregatedStyles()).toBe('');
expect(getAggregatedStyles()).toBe('[]');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ const serverStylesDispatcher = ({ bundleType }) => ({
const initialContent = await fs.promises.readFile(fileName, 'utf8');
const outputContent = `${initialContent}
;module.exports.ssrStyles = {
getFullSheet: () => ${JSON.stringify(getAggregatedStyles())},
aggregatedStyles: ${getAggregatedStyles()},
getFullSheet: function getFullSheet() {
return this.aggregatedStyles.reduce((acc, { css }) => acc + css, '');
},
};`;
await fs.promises.writeFile(fileName, outputContent, 'utf8');
}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ const stylesLoader = (cssModulesOptions = {}, { bundleType } = {}) => ({
});

const hash = crypto.createHash('sha256');
hash.update(args.path);
hash.update(result.css);
const digest = hash.copy().digest('hex');

let injectedCode = '';
Expand All @@ -84,7 +84,8 @@ const stylesLoader = (cssModulesOptions = {}, { bundleType } = {}) => ({
})();`;
} else {
// For SSR, aggregate all styles, then inject them once at the end
addStyle(result.css);
const isDependencyFile = args.path.indexOf('/node_modules/') >= 0;
addStyle(digest, result.css, isDependencyFile);
}

// provide useful values to the importer of this file, most importantly, the classnames
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,36 @@
* permissions and limitations under the License.
*/

let aggregatedStyles = '';
let aggregatedStyles = {
deps: [],
local: [],
};

export function addStyle(style) {
aggregatedStyles += style;
const sheetDigests = new Set();

export function addStyle(digest, css, isDependencyFile) {
if (!sheetDigests.has(digest)) {
sheetDigests.add(digest);

aggregatedStyles[isDependencyFile ? 'deps' : 'local'].push({
css,
digest,
});
}
}

export const getAggregatedStyles = () => aggregatedStyles;
/**
* Returns aggregated styles object from all parsed CSS files with dependencies listed first
* @returns {string}
*/
export const getAggregatedStyles = () => JSON.stringify(
[...aggregatedStyles.deps, ...aggregatedStyles.local]
);

export function emptyAggregatedStyles() {
aggregatedStyles = '';
aggregatedStyles = {
deps: [],
local: [],
};
sheetDigests.clear();
}
26 changes: 26 additions & 0 deletions packages/one-app-dev-bundler/jest.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2023 American Express Travel Related Services Company, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

const jestConfig = require('../../jest.esm.config');
Matthew-Mallimo marked this conversation as resolved.
Show resolved Hide resolved

module.exports = {
...jestConfig,
collectCoverageFrom: [
...jestConfig.collectCoverageFrom,
'**/*.{mjs,js,jsx}',
],
roots: ['./']
};
19 changes: 19 additions & 0 deletions packages/one-app-dev-bundler/jest.esm.setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright 2023 American Express Travel Related Services Company, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

const jestSetup = require('../../jest.esm.setup');

module.exports = jestSetup;
2 changes: 1 addition & 1 deletion packages/one-app-dev-bundler/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"postcss-modules": "^3.2.2",
"read-pkg-up": "^9.0.0",
"regenerator-runtime": "^0.13.9",
"sass": "^1.54.8",
"sass": "1.65.1",
eddhurst marked this conversation as resolved.
Show resolved Hide resolved
"ssri": "^7.1.0",
"ws": "^8.4.0"
},
Expand Down
Loading