Skip to content

Commit

Permalink
jss: make classname deterministic (#30065)
Browse files Browse the repository at this point in the history
* jss: make classname deterministic

* filehash

* typo

* test should work on windows

* Use sha256

* lint

* relativize filepath to project so hashes are independent of machine

* review fixes

* fix tests
  • Loading branch information
samouri committed Sep 2, 2020
1 parent e358a51 commit 6469a3c
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 9 deletions.
34 changes: 30 additions & 4 deletions build-system/babel-plugins/babel-plugin-transform-jss/index.js
Expand Up @@ -36,17 +36,43 @@
* ```
*/

const crypto = require('crypto');
const {create} = require('jss');
const {default: preset} = require('jss-preset-default');
const {relative, join} = require('path');

module.exports = function ({types: t, template}) {
function isJssFile(filename) {
return filename.endsWith('.jss.js');
}

function compileJss(JSS) {
const jss = create();
jss.setup(preset());
const seen = new Set();
function compileJss(JSS, filename) {
const relativeFilepath = relative(join(__dirname, '../../..'), filename);
const filehash = crypto
.createHash('sha256')
.update(relativeFilepath)
.digest('hex')
.slice(0, 7);
const jss = create({
...preset(),
createGenerateId: () => {
return (rule) => {
const dashCaseKey = rule.key.replace(
/([A-Z])/g,
(c) => `-${c.toLowerCase()}`
);
const className = `${dashCaseKey}-${filehash}`;
if (seen.has(className)) {
throw new Error(
`Classnames must be unique across all files. Found a duplicate: ${className}`
);
}
seen.add(className);
return className;
};
},
});
return jss.createStyleSheet(JSS);
}

Expand All @@ -70,7 +96,7 @@ module.exports = function ({types: t, template}) {
`First argument to createUseStyles must be statically evaluatable.`
);
}
const sheet = compileJss(JSS);
const sheet = compileJss(JSS, filename);
if ('CSS' in sheet.classes) {
throw path.buildCodeFrameError(
'Cannot have class named CSS in your JSS object.'
Expand Down
@@ -1,4 +1,4 @@
var _classes = JSON.parse("{\"button\":\"button-0-2-1\",\"CSS\":\".button-0-2-1 {\\n font-size: 12px;\\n}\"}");
var _classes = JSON.parse("{\"button\":\"button-21aa4a8\",\"CSS\":\".button-21aa4a8 {\\n font-size: 12px;\\n}\"}");

/**
* Copyright 2020 The AMP HTML Authors. All Rights Reserved.
Expand Down
Expand Up @@ -16,4 +16,4 @@

import {createUseStyles} from 'react-jss';

export const useStyles = createUseStyles({button: {fontSize: 12}});
export const useStyles = createUseStyles({floatLeft: {float: 'left'}});
@@ -1,4 +1,4 @@
var _classes = JSON.parse("{\"button\":\"button-0-3-1\",\"CSS\":\".button-0-3-1 {\\n font-size: 12px;\\n}\"}");
var _classes = JSON.parse("{\"floatLeft\":\"float-left-a6c6677\",\"CSS\":\".float-left-a6c6677 {\\n float: left;\\n}\"}");

/**
* Copyright 2020 The AMP HTML Authors. All Rights Reserved.
Expand Down
Expand Up @@ -14,6 +14,25 @@
* limitations under the License.
*/

const babel = require('@babel/core');
const path = require('path');
const runner = require('@babel/helper-plugin-test-runner').default;

runner(__dirname);

// eslint-disable-next-line no-undef
test('throws when duplicate classname is found', () => {
const fileContents = `
import {createUseStyles} from 'react-jss';
export const useStyles = createUseStyles({button: {fontSize: 12}});
`;
const filename = 'test.jss.js';
const plugins = [path.join(__dirname, '..')];
const caller = {name: 'babel-jest'};

// Transforming the same file twice should yield the same classnames, resulting in an error
babel.transformSync(fileContents, {filename, plugins, caller});
expect(() =>
babel.transformSync(fileContents, {filename, plugins, caller})
).toThrow(/Classnames must be unique across all files/);
});
10 changes: 8 additions & 2 deletions extensions/amp-base-carousel/1.0/test/test-amp-base-carousel.js
Expand Up @@ -24,6 +24,7 @@ import {poll} from '../../../../testing/iframe';
import {setStyles} from '../../../../src/style';
import {toArray} from '../../../../src/types';
import {toggleExperiment} from '../../../../src/experiments';
import {useStyles} from '../base-carousel.jss';
import {whenCalled} from '../../../../testing/test-helper';

describes.realWin(
Expand All @@ -38,14 +39,17 @@ describes.realWin(
let win;
let element;

// eslint-disable-next-line react-hooks/rules-of-hooks
const styles = useStyles();

async function getSlidesFromShadow() {
await whenCalled(env.sandbox.spy(element, 'attachShadow'));
const shadow = element.shadowRoot;
await waitForChildPromise(shadow, (shadow) => {
return shadow.querySelectorAll('[class*=hideScrollbar]');
});
const slots = await shadow.querySelectorAll(
'[class*=hideScrollbar] slot'
`[class*=${styles.hideScrollbar}] slot`
);
return toArray(slots).reduce(
(acc, slot) => acc.concat(slot.assignedElements()),
Expand Down Expand Up @@ -167,7 +171,9 @@ describes.realWin(
win.document.body.appendChild(element);
await getSlidesFromShadow();

scroller = element.shadowRoot.querySelector('[class*=scrollContainer]');
scroller = element.shadowRoot.querySelector(
`[class*=${styles.scrollContainer}]`
);
});

function invocation(method, args = {}) {
Expand Down

0 comments on commit 6469a3c

Please sign in to comment.