From ef7d3ac46553d1891bc1078dda9a876f80c62171 Mon Sep 17 00:00:00 2001 From: Adrien Kiren Date: Wed, 21 Oct 2020 14:51:30 +0300 Subject: [PATCH 1/3] fix: apply :global() to whole selector + add strict mode --- README.md | 1 + index.js | 84 +++++++++++++++++++++++++++++++++----------- package-lock.json | 2 +- test/remove.test.js | 13 +++++++ test/replace.test.js | 73 ++++++++++++++++++++++++++++++++++++-- 5 files changed, 150 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 3ca64db..5ca9134 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,7 @@ Pass an object of the following properties | `localIdentName` | `{String}` | `"[local]-[hash:base64:6]"` | A rule using any available token from [webpack interpolateName](https://github.com/webpack/loader-utils#interpolatename) | | `includePaths` | `{Array}` | `[]` (Any) | An array of paths to be processed | | `getLocalIdent` | `Function` | `undefined` | Generate the classname by specifying a function instead of using the built-in interpolation | +| `strict` | `Boolean` | `false` | When true, an exception is raised when a class is used while not being defined in `\nRed'; -const sourceShorthand = '\nRed'; +const style = ''; +const source = style + '\nRed'; +const sourceShorthand = style + '\nRed'; test('Generate CSS Modules from HTML attributes, Replace CSS className', async () => { const output = await compiler({ @@ -55,3 +56,71 @@ test('[Shorthand] Avoid generated class to end with a hyphen', async () => { }); expect(output).toBe('\nRed'); }); + +describe('combining multiple classes', () => { + const style = ''; + const source = style + '\nRed'; + + const expectedStyle = + ''; + const expectedOutput = expectedStyle + '\nRed'; + + test('Generate CSS Modules from HTML attributes, Replace CSS className', async () => { + const output = await compiler( + { + source, + }, + { + localIdentName: '[local]-123456', + } + ); + + expect(output).toBe(expectedOutput); + }); +}); + +describe('using dynamic classes', () => { + describe('when matched class is empty', () => { + // The parser will identify a class named '' + const source = + '' + 'Red'; + + test('throws an exception', async () => { + await expect(compiler({ source })).rejects.toThrow( + 'Invalid class name in file src/App.svelte.\nThis usually happens when using dynamic classes with svelte-preprocess-cssmodules.' + ); + }); + }); + + describe('when matched class could be a valid class but does not match any style definition', () => { + // The parser will identify a class named 'color' + const source = + '' + + 'Red'; + + it('in strict mode, it throw an exception', async () => { + await expect(compiler({ source }, { strict: true })).rejects.toThrow( + 'Classname "color" was not found in declared src/App.svelte Red' + ); + }); + }); + + describe('when matched class is an invalid class', () => { + // The parser will identify a class named 'color-' + const source = + '' + + 'Red'; + + it('throws an exception when resulting class is invalid', async () => { + await expect(compiler({ source })).rejects.toThrow('Classname "color-" in file src/App.svelte is not valid'); + }); + }); +}); From c7404a3206538322e0ea89e57115978b1c92a297 Mon Sep 17 00:00:00 2001 From: Michael Vurchio Date: Mon, 26 Oct 2020 22:54:05 +0900 Subject: [PATCH 2/3] Consider joint selector only (no parents/children) & media queries --- index.js | 43 +++++++++++++++---------------------------- 1 file changed, 15 insertions(+), 28 deletions(-) diff --git a/index.js b/index.js index a2af809..84357e0 100644 --- a/index.js +++ b/index.js @@ -16,6 +16,9 @@ const regex = { class: (className) => { return new RegExp(`\\.(${className})\\b(?![-_])`, 'gm'); }, + classSelector: (className) => { + return new RegExp(`\\S*\\.(${className})\\b(?![-_])\\S*`, 'gm'); + }, }; let moduleClasses = {}; @@ -125,7 +128,6 @@ const markup = async ({ content, filename }) => { }; }; -const GLOBALIZE_PLACEHOLDER = '__to_globalize__'; const style = async ({ content, filename }) => { let code = content; @@ -141,36 +143,21 @@ const style = async ({ content, filename }) => { for (const className in classes) { code = code.replace( - regex.class(className), - () => `.${GLOBALIZE_PLACEHOLDER}${classes[className]}` - ); - } + regex.classSelector(className), + (match) => { + const generatedClass = match.replace( + regex.class(className), + () => `.${classes[className]}` + ); - let codeOutput = ''; - let cssRules = code.split('}'); - - // Remove last element of the split. It should be the empty string that comes after the last '}'. - const lastChunk = cssRules.pop(); - - // We wrap all css selector containing a scoped CSS class in :global() svelte css statement - for (const cssRule of cssRules) { - let [selector, rule] = cssRule.split('{'); - if (selector.includes(GLOBALIZE_PLACEHOLDER)) { - const selectorTrimmed = selector.trim(); - selector = selector.replace( - selectorTrimmed, - `:global(${selectorTrimmed.replace( - new RegExp(GLOBALIZE_PLACEHOLDER, 'g'), - '' - )})` - ); - } - codeOutput += `${selector}{${rule}}`; + return generatedClass.indexOf(':global(') !== -1 + ? generatedClass + : `:global(${generatedClass})`; + } + ); } - codeOutput += lastChunk; - - return { code: codeOutput }; + return { code }; }; module.exports = (options) => { From 58845d55eafe3615688537095b85ac3e68e35aa5 Mon Sep 17 00:00:00 2001 From: Michael Vurchio Date: Mon, 26 Oct 2020 23:24:42 +0900 Subject: [PATCH 3/3] Add tests --- test/replace.test.js | 117 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/test/replace.test.js b/test/replace.test.js index 61dce73..6dae263 100644 --- a/test/replace.test.js +++ b/test/replace.test.js @@ -79,6 +79,123 @@ describe('combining multiple classes', () => { }); }); +describe('Classname is part of a selector', () => { + + test('CSS Modules class targetting children', async () => { + const source = + '\n' + + '
Red*
'; + + const expectedOutput = + '\n' + + '
Red*
'; + + const output = await compiler( + { + source, + }, + { + localIdentName: '[local]-123', + } + ); + + expect(output).toBe(expectedOutput); + }); + + test('CSS Modules class has a parent', async () => { + const source = + '\n' + + '
Red
'; + + const expectedOutput = + '\n' + + '
Red
'; + + const output = await compiler( + { + source, + }, + { + localIdentName: '[local]-123', + } + ); + + expect(output).toBe(expectedOutput); + }); + + test('CSS Modules class has a global parent', async () => { + const source = + '\n' + + '
Red
'; + + const expectedOutput = + '\n' + + '
Red
'; + + const output = await compiler( + { + source, + }, + { + localIdentName: '[local]-123', + } + ); + + expect(output).toBe(expectedOutput); + }); + + test('CSS Modules class is used within a media query', async () => { + const source = + '\n' + + '
Red
'; + + const expectedOutput = + '\n' + + '
Red
'; + + const output = await compiler( + { + source, + }, + { + localIdentName: '[local]-123', + } + ); + + expect(output).toBe(expectedOutput); + }); +}); + describe('using dynamic classes', () => { describe('when matched class is empty', () => { // The parser will identify a class named ''