From 4c24846b2b381454df10207fd5bc5ccd7fce07f4 Mon Sep 17 00:00:00 2001 From: Zachary Cowan <44091329+zacowan@users.noreply.github.com> Date: Thu, 7 Mar 2024 07:37:47 -0500 Subject: [PATCH] fix(one-app-bundler): hash css module classes from files in node_modules (#613) Co-authored-by: Matthew Mallimo --- .../webpack/loaders/styles-loader.spec.js | 13 +- .../webpack/loaders/styles-loader.js | 6 +- .../esbuild/plugins/styles-loader.spec.js | 525 +++++++++++++----- .../esbuild/utils/load-styles.js | 11 +- .../loaders/__snapshots__/common.spec.js.snap | 13 + .../__tests__/webpack/loaders/common.spec.js | 16 + .../webpack/loaders/common.js | 15 +- 7 files changed, 444 insertions(+), 155 deletions(-) diff --git a/packages/one-app-bundler/__tests__/webpack/loaders/styles-loader.spec.js b/packages/one-app-bundler/__tests__/webpack/loaders/styles-loader.spec.js index d118998d..2b0a4b0b 100644 --- a/packages/one-app-bundler/__tests__/webpack/loaders/styles-loader.spec.js +++ b/packages/one-app-bundler/__tests__/webpack/loaders/styles-loader.spec.js @@ -35,20 +35,9 @@ describe('styles-loader', () => { getOptions: mockGetOptions, }); }); - it('should call the loadStyles util with the correct params, for non-node_modules', () => { + it('should call the loadStyles util from the dev bundler with the correct params', () => { expect(stylesLoader()).toBe('let mockJsContent = "helloMockContent"'); expect(loadStyles).toHaveBeenCalledTimes(1); expect(loadStyles).toHaveBeenCalledWith({ bundleType: BUNDLE_TYPES.BROWSER, cssModulesOptions: { generateScopedName: undefined }, path: 'style/path/mock.scss' }); }); - it('should call the loadStyles util with the correct params, for node_modules', () => { - stylesLoader = unboundStylesLoader.bind({ - resourcePath: 'node_modules/style/path/mock.scss', - getOptions: mockGetOptions, - }); - - expect(stylesLoader()).toBe('let mockJsContent = "helloMockContent"'); - - expect(loadStyles).toHaveBeenCalledTimes(1); - expect(loadStyles).toHaveBeenCalledWith({ bundleType: BUNDLE_TYPES.BROWSER, cssModulesOptions: { generateScopedName: '[local]' }, path: 'node_modules/style/path/mock.scss' }); - }); }); diff --git a/packages/one-app-bundler/webpack/loaders/styles-loader.js b/packages/one-app-bundler/webpack/loaders/styles-loader.js index 06ea9563..b1954695 100644 --- a/packages/one-app-bundler/webpack/loaders/styles-loader.js +++ b/packages/one-app-bundler/webpack/loaders/styles-loader.js @@ -15,10 +15,8 @@ import { loadStyles } from '@americanexpress/one-app-dev-bundler'; function stylesLoader() { - const options = { ...this.getOptions() }; - // use default for directly imported, dont scope for node_module - options.cssModulesOptions.generateScopedName = this.resourcePath.includes('node_modules') ? '[local]' : undefined; - return loadStyles({ path: this.resourcePath, ...options }); + // Use the same implementation as the dev bundler for consistency + return loadStyles({ path: this.resourcePath, ...this.getOptions() }); } export default stylesLoader; diff --git a/packages/one-app-dev-bundler/__tests__/esbuild/plugins/styles-loader.spec.js b/packages/one-app-dev-bundler/__tests__/esbuild/plugins/styles-loader.spec.js index c8228855..c70bc489 100644 --- a/packages/one-app-dev-bundler/__tests__/esbuild/plugins/styles-loader.spec.js +++ b/packages/one-app-dev-bundler/__tests__/esbuild/plugins/styles-loader.spec.js @@ -172,39 +172,76 @@ export default { }; export { css, digest };" `); }); - }); - it('should transform inputs to outputs for scss, in the server', async () => { - expect.assertions(4); + it('should transform inputs to outputs for scss, in the server', async () => { + expect.assertions(4); + + const mockFileName = 'index.scss'; + const mockFileContent = `body { + background: white; + } + body > p { + font-color: black; + }`; - const mockFileName = 'index.scss'; - const mockFileContent = `body { + const plugin = stylesLoader({}, { + bundleType: BUNDLE_TYPES.SERVER, + }); + const onLoadHook = runSetupAndGetLifeHooks(plugin).onLoad[0].hookFunction; + + const { contents, loader } = await runOnLoadHook( + onLoadHook, + { mockFileName, mockFileContent } + ); + + expect(sassCompile).toHaveBeenCalledTimes(1); + expect(sassCompile).toHaveBeenCalledWith(`mock/path/to/file/${mockFileName}`, { loadPaths: ['./node_modules'] }); + + expect(loader).toEqual('js'); + expect(contents).toMatchInlineSnapshot(` + "const digest = '11e1fda0219a10c2de0ad6b28c1c6519985965cbef3f5b8f8f119d16f1bafff3'; + const css = \`body { + background: white; + } + + body > p { + font-color: black; + }\`; + + + export default { }; + export { css, digest };" + `); + }); + + it('should transform inputs to outputs for css, in the server', async () => { + expect.assertions(3); + + const mockFileName = 'index.css'; + const mockFileContent = `body { background: white; } body > p { font-color: black; }`; - const plugin = stylesLoader({}, { - bundleType: BUNDLE_TYPES.SERVER, - }); - const onLoadHook = runSetupAndGetLifeHooks(plugin).onLoad[0].hookFunction; - - const { contents, loader } = await runOnLoadHook( - onLoadHook, - { mockFileName, mockFileContent } - ); + const plugin = stylesLoader({}, { + bundleType: BUNDLE_TYPES.SERVER, + }); + const onLoadHook = runSetupAndGetLifeHooks(plugin).onLoad[0].hookFunction; - expect(sassCompile).toHaveBeenCalledTimes(1); - expect(sassCompile).toHaveBeenCalledWith(`mock/path/to/file/${mockFileName}`, { loadPaths: ['./node_modules'] }); + const { contents, loader } = await runOnLoadHook( + onLoadHook, + { mockFileName, mockFileContent } + ); - expect(loader).toEqual('js'); - expect(contents).toMatchInlineSnapshot(` -"const digest = '11e1fda0219a10c2de0ad6b28c1c6519985965cbef3f5b8f8f119d16f1bafff3'; + expect(sassCompile).toHaveBeenCalledTimes(0); + expect(loader).toEqual('js'); + expect(contents).toMatchInlineSnapshot(` +"const digest = '5e9583e668d7632ccabf75f612a320b29f5f48cd7a7e86489c7b0f8f5fdcdbbe'; const css = \`body { background: white; } - body > p { font-color: black; }\`; @@ -213,61 +250,282 @@ body > p { export default { }; export { css, digest };" `); - }); - - it('should transform inputs to outputs for css, in the server', async () => { - expect.assertions(3); + }); - const mockFileName = 'index.css'; - const mockFileContent = `body { + describe('css classes', () => { + const mockFileContent = ` +.test-class { background: white; } -body > p { +.test-class .nested-class { font-color: black; }`; - const plugin = stylesLoader({}, { - bundleType: BUNDLE_TYPES.SERVER, - }); - const onLoadHook = runSetupAndGetLifeHooks(plugin).onLoad[0].hookFunction; + it('should hash the css classes for .scss files not in node_modules', async () => { + expect.assertions(4); - const { contents, loader } = await runOnLoadHook( - onLoadHook, - { mockFileName, mockFileContent } - ); + const mockFileName = 'index.scss'; + const plugin = stylesLoader({}, { + bundleType: BUNDLE_TYPES.BROWSER, + }); + const onLoadHook = runSetupAndGetLifeHooks(plugin).onLoad[0].hookFunction; - expect(sassCompile).toHaveBeenCalledTimes(0); - expect(loader).toEqual('js'); - expect(contents).toMatchInlineSnapshot(` -"const digest = '5e9583e668d7632ccabf75f612a320b29f5f48cd7a7e86489c7b0f8f5fdcdbbe'; -const css = \`body { + const { contents, loader } = await runOnLoadHook( + onLoadHook, + { mockFileName, mockFileContent } + ); + + expect(sassCompile).toHaveBeenCalledTimes(1); + expect(sassCompile).toHaveBeenCalledWith(`mock/path/to/file/${mockFileName}`, { loadPaths: ['./node_modules'] }); + + expect(loader).toEqual('js'); + expect(contents).toMatchInlineSnapshot(` +"const digest = 'e7f5b9ef03f087455b4d224d28209638e7d986130a45e94f793690dbac47dc39'; +const css = \`._test-class_1o1cd_1 { background: white; } -body > p { + +._test-class_1o1cd_1 ._nested-class_1o1cd_5 { + font-color: black; +}\`; +(function() { + if ( global.BROWSER && !document.getElementById(digest)) { + var el = document.createElement('style'); + el.id = digest; + el.textContent = css; + document.head.appendChild(el); + } +})(); +export const testClass = '_test-class_1o1cd_1'; +export const nestedClass = '_nested-class_1o1cd_5'; +export default { testClass, nestedClass }; +export { css, digest };" +`); + }); + + it('should hash the css classes for .css files not in node_modules', async () => { + expect.assertions(3); + + const mockFileName = 'index.css'; + const plugin = stylesLoader({}, { + bundleType: BUNDLE_TYPES.BROWSER, + }); + const onLoadHook = runSetupAndGetLifeHooks(plugin).onLoad[0].hookFunction; + + const { contents, loader } = await runOnLoadHook( + onLoadHook, + { mockFileName, mockFileContent } + ); + + expect(sassCompile).toHaveBeenCalledTimes(0); + + expect(loader).toEqual('js'); + expect(contents).toMatchInlineSnapshot(` +"const digest = 'ce462c133ed89a8b7ce350c3aef04277cf1b8b618dfad088d668c2ed0f5209a7'; +const css = \` +._test-class_ykkej_2 { + background: white; +} +._test-class_ykkej_2 ._nested-class_ykkej_5 { font-color: black; }\`; +(function() { + if ( global.BROWSER && !document.getElementById(digest)) { + var el = document.createElement('style'); + el.id = digest; + el.textContent = css; + document.head.appendChild(el); + } +})(); +export const testClass = '_test-class_ykkej_2'; +export const nestedClass = '_nested-class_ykkej_5'; +export default { testClass, nestedClass }; +export { css, digest };" +`); + }); + it('should hash the css classes for .module.scss files in node_modules', async () => { + expect.assertions(4); -export default { }; + const mockFileName = 'node_modules/index.module.scss'; + const plugin = stylesLoader({}, { + bundleType: BUNDLE_TYPES.BROWSER, + }); + const onLoadHook = runSetupAndGetLifeHooks(plugin).onLoad[0].hookFunction; + + const { contents, loader } = await runOnLoadHook( + onLoadHook, + { mockFileName, mockFileContent } + ); + + expect(sassCompile).toHaveBeenCalledTimes(1); + expect(sassCompile).toHaveBeenCalledWith(`mock/path/to/file/${mockFileName}`, { loadPaths: ['./node_modules'] }); + + expect(loader).toEqual('js'); + expect(contents).toMatchInlineSnapshot(` +"const digest = 'e7f5b9ef03f087455b4d224d28209638e7d986130a45e94f793690dbac47dc39'; +const css = \`._test-class_1o1cd_1 { + background: white; +} + +._test-class_1o1cd_1 ._nested-class_1o1cd_5 { + font-color: black; +}\`; +(function() { + if ( global.BROWSER && !document.getElementById(digest)) { + var el = document.createElement('style'); + el.id = digest; + el.textContent = css; + document.head.appendChild(el); + } +})(); +export const testClass = '_test-class_1o1cd_1'; +export const nestedClass = '_nested-class_1o1cd_5'; +export default { testClass, nestedClass }; export { css, digest };" `); - }); - }); + }); + + it('should hash the css classes for .module.css files in node_modules', async () => { + expect.assertions(3); + + const mockFileName = 'index.css'; + const plugin = stylesLoader({}, { + bundleType: BUNDLE_TYPES.BROWSER, + }); + const onLoadHook = runSetupAndGetLifeHooks(plugin).onLoad[0].hookFunction; + + const { contents, loader } = await runOnLoadHook( + onLoadHook, + { mockFileName, mockFileContent } + ); + + expect(sassCompile).toHaveBeenCalledTimes(0); + + expect(loader).toEqual('js'); + expect(contents).toMatchInlineSnapshot(` +"const digest = 'ce462c133ed89a8b7ce350c3aef04277cf1b8b618dfad088d668c2ed0f5209a7'; +const css = \` +._test-class_ykkej_2 { + background: white; +} +._test-class_ykkej_2 ._nested-class_ykkej_5 { + font-color: black; +}\`; +(function() { + if ( global.BROWSER && !document.getElementById(digest)) { + var el = document.createElement('style'); + el.id = digest; + el.textContent = css; + document.head.appendChild(el); + } +})(); +export const testClass = '_test-class_ykkej_2'; +export const nestedClass = '_nested-class_ykkej_5'; +export default { testClass, nestedClass }; +export { css, digest };" +`); + }); + + it('should not hash the css classes for .scss files in node_modules', async () => { + expect.assertions(4); - describe('PRODUCTION environment', () => { - mockNodeEnv('production'); + const mockFileName = 'node_modules/index.scss'; + const plugin = stylesLoader({}, { + bundleType: BUNDLE_TYPES.BROWSER, + }); + const onLoadHook = runSetupAndGetLifeHooks(plugin).onLoad[0].hookFunction; - it('should transform inputs to default outputs for purged css, browser', async () => { - glob.sync.mockReturnValue(['Test.jsx']); + const { contents, loader } = await runOnLoadHook( + onLoadHook, + { mockFileName, mockFileContent } + ); - expect.assertions(3); + expect(sassCompile).toHaveBeenCalledTimes(1); + expect(sassCompile).toHaveBeenCalledWith(`mock/path/to/file/${mockFileName}`, { loadPaths: ['./node_modules'] }); + + expect(loader).toEqual('js'); + expect(contents).toMatchInlineSnapshot(` +"const digest = 'ade268ae3555b997b1a7c7fd6c3c2f4b8f1b2e4f6a79011c4d01a82b1a6df8bd'; +const css = \`.test-class { + background: white; +} + +.test-class .nested-class { + font-color: black; +}\`; +(function() { + if ( global.BROWSER && !document.getElementById(digest)) { + var el = document.createElement('style'); + el.id = digest; + el.textContent = css; + document.head.appendChild(el); + } +})(); +export const testClass = 'test-class'; +export const nestedClass = 'nested-class'; +export default { testClass, nestedClass }; +export { css, digest };" +`); + }); - const plugin = stylesLoader({}, { - bundleType: BUNDLE_TYPES.BROWSER, + it('should not hash the css classes for .css files in node_modules', async () => { + expect.assertions(3); + + const mockFileName = 'node_modules/index.css'; + const plugin = stylesLoader({}, { + bundleType: BUNDLE_TYPES.BROWSER, + }); + const onLoadHook = runSetupAndGetLifeHooks(plugin).onLoad[0].hookFunction; + + const { contents, loader } = await runOnLoadHook( + onLoadHook, + { mockFileName, mockFileContent } + ); + + expect(sassCompile).toHaveBeenCalledTimes(0); + + expect(loader).toEqual('js'); + expect(contents).toMatchInlineSnapshot(` +"const digest = '46b2c9f3228391651835f4f0819eebe700536d488ddb9d523e826d2368427eb0'; +const css = \` +.test-class { + background: white; +} +.test-class .nested-class { + font-color: black; +}\`; +(function() { + if ( global.BROWSER && !document.getElementById(digest)) { + var el = document.createElement('style'); + el.id = digest; + el.textContent = css; + document.head.appendChild(el); + } +})(); +export const testClass = 'test-class'; +export const nestedClass = 'nested-class'; +export default { testClass, nestedClass }; +export { css, digest };" +`); + }); }); - const onLoadHook = runSetupAndGetLifeHooks(plugin).onLoad[0].hookFunction; - const additionalMockedFiles = { - 'Test.jsx': `\ + }); + + describe('PRODUCTION environment', () => { + mockNodeEnv('production'); + + it('should transform inputs to default outputs for purged css, browser', async () => { + glob.sync.mockReturnValue(['Test.jsx']); + + expect.assertions(3); + + const plugin = stylesLoader({}, { + bundleType: BUNDLE_TYPES.BROWSER, + }); + const onLoadHook = runSetupAndGetLifeHooks(plugin).onLoad[0].hookFunction; + const additionalMockedFiles = { + 'Test.jsx': `\ import styles from './index.module.css'; const Component = () => { @@ -279,15 +537,15 @@ export { css, digest };" } export default Component`, - }; - - const { - contents, loader, - } = await runOnLoadHook( - onLoadHook, - { - mockFileNAme: 'index.module.css', - mockFileContent: `\ + }; + + const { + contents, loader, + } = await runOnLoadHook( + onLoadHook, + { + mockFileNAme: 'index.module.css', + mockFileContent: `\ .root { background: white; } @@ -299,13 +557,13 @@ export { css, digest };" .second { font-color: black; }`, - }, - additionalMockedFiles - ); + }, + additionalMockedFiles + ); - expect(sassCompile).toHaveBeenCalledTimes(0); - expect(loader).toEqual('js'); - expect(contents).toMatchInlineSnapshot(` + expect(sassCompile).toHaveBeenCalledTimes(0); + expect(loader).toEqual('js'); + expect(contents).toMatchInlineSnapshot(` "const digest = 'f85b3a3cf0c00eb3fd23e6d440b10077d7493cf7f127538acb994cade5bce451'; const css = \` ._root_1vf0l_1 { background: white; @@ -327,19 +585,19 @@ export const second = '_second_1vf0l_9'; export default { root, second }; export { css, digest };" `); - }); + }); - it('should transform inputs to named outputs for purged css, browser', async () => { - glob.sync.mockReturnValue(['Test.jsx']); + it('should transform inputs to named outputs for purged css, browser', async () => { + glob.sync.mockReturnValue(['Test.jsx']); - expect.assertions(3); + expect.assertions(3); - const plugin = stylesLoader({}, { - bundleType: BUNDLE_TYPES.BROWSER, - }); - const onLoadHook = runSetupAndGetLifeHooks(plugin).onLoad[0].hookFunction; - const additionalMockedFiles = { - 'Test.jsx': `\ + const plugin = stylesLoader({}, { + bundleType: BUNDLE_TYPES.BROWSER, + }); + const onLoadHook = runSetupAndGetLifeHooks(plugin).onLoad[0].hookFunction; + const additionalMockedFiles = { + 'Test.jsx': `\ import { root, second } from './index.module.css'; const Component = () => { @@ -351,15 +609,15 @@ export { css, digest };" } export default Component`, - }; - - const { - contents, loader, - } = await runOnLoadHook( - onLoadHook, - { - mockFileNAme: 'index.module.css', - mockFileContent: `\ + }; + + const { + contents, loader, + } = await runOnLoadHook( + onLoadHook, + { + mockFileNAme: 'index.module.css', + mockFileContent: `\ .root { background: white; } @@ -371,13 +629,13 @@ export { css, digest };" .second { font-color: black; }`, - }, - additionalMockedFiles - ); + }, + additionalMockedFiles + ); - expect(sassCompile).toHaveBeenCalledTimes(0); - expect(loader).toEqual('js'); - expect(contents).toMatchInlineSnapshot(` + expect(sassCompile).toHaveBeenCalledTimes(0); + expect(loader).toEqual('js'); + expect(contents).toMatchInlineSnapshot(` "const digest = 'f85b3a3cf0c00eb3fd23e6d440b10077d7493cf7f127538acb994cade5bce451'; const css = \` ._root_1vf0l_1 { background: white; @@ -399,17 +657,17 @@ export const second = '_second_1vf0l_9'; export default { root, second }; export { css, digest };" `); - }); + }); - it('should transform inputs to outputs for scss, with purge disabled, in the browser', async () => { - expect.assertions(4); + it('should transform inputs to outputs for scss, with purge disabled, in the browser', async () => { + expect.assertions(4); - getModulesBundlerConfig.mockImplementation(() => ({ - disabled: true, - })); + getModulesBundlerConfig.mockImplementation(() => ({ + disabled: true, + })); - const mockFileName = 'index.scss'; - const mockFileContent = `body { + const mockFileName = 'index.scss'; + const mockFileContent = `body { background: white; & > p { @@ -417,21 +675,21 @@ export { css, digest };" } }`; - const plugin = stylesLoader({}, { - bundleType: BUNDLE_TYPES.BROWSER, - }); - const onLoadHook = runSetupAndGetLifeHooks(plugin).onLoad[0].hookFunction; + const plugin = stylesLoader({}, { + bundleType: BUNDLE_TYPES.BROWSER, + }); + const onLoadHook = runSetupAndGetLifeHooks(plugin).onLoad[0].hookFunction; - const { contents, loader } = await runOnLoadHook( - onLoadHook, - { mockFileName, mockFileContent } - ); + const { contents, loader } = await runOnLoadHook( + onLoadHook, + { mockFileName, mockFileContent } + ); - expect(sassCompile).toHaveBeenCalledTimes(1); - expect(sassCompile).toHaveBeenCalledWith(`mock/path/to/file/${mockFileName}`, { loadPaths: ['./node_modules'] }); + expect(sassCompile).toHaveBeenCalledTimes(1); + expect(sassCompile).toHaveBeenCalledWith(`mock/path/to/file/${mockFileName}`, { loadPaths: ['./node_modules'] }); - expect(loader).toEqual('js'); - expect(contents).toMatchInlineSnapshot(` + expect(loader).toEqual('js'); + expect(contents).toMatchInlineSnapshot(` "const digest = '5e9583e668d7632ccabf75f612a320b29f5f48cd7a7e86489c7b0f8f5fdcdbbe'; const css = \`body { background: white; @@ -451,36 +709,36 @@ body > p { export default { }; export { css, digest };" `); - }); + }); - it('should transform inputs to outputs for css, in the browser', async () => { - expect.assertions(3); + it('should transform inputs to outputs for css, with purge disabled, in the browser', async () => { + expect.assertions(3); - getModulesBundlerConfig.mockImplementation(() => ({ - disabled: true, - })); + getModulesBundlerConfig.mockImplementation(() => ({ + disabled: true, + })); - const mockFileName = 'index.css'; - const mockFileContent = `body { + const mockFileName = 'index.css'; + const mockFileContent = `body { background: white; } body > p { font-color: black; }`; - const plugin = stylesLoader({}, { - bundleType: BUNDLE_TYPES.BROWSER, - }); - const onLoadHook = runSetupAndGetLifeHooks(plugin).onLoad[0].hookFunction; + const plugin = stylesLoader({}, { + bundleType: BUNDLE_TYPES.BROWSER, + }); + const onLoadHook = runSetupAndGetLifeHooks(plugin).onLoad[0].hookFunction; - const { contents, loader } = await runOnLoadHook( - onLoadHook, - { mockFileName, mockFileContent } - ); + const { contents, loader } = await runOnLoadHook( + onLoadHook, + { mockFileName, mockFileContent } + ); - expect(sassCompile).toHaveBeenCalledTimes(0); - expect(loader).toEqual('js'); - expect(contents).toMatchInlineSnapshot(` + expect(sassCompile).toHaveBeenCalledTimes(0); + expect(loader).toEqual('js'); + expect(contents).toMatchInlineSnapshot(` "const digest = '5e9583e668d7632ccabf75f612a320b29f5f48cd7a7e86489c7b0f8f5fdcdbbe'; const css = \`body { background: white; @@ -500,6 +758,7 @@ body > p { export default { }; export { css, digest };" `); + }); }); }); }); diff --git a/packages/one-app-dev-bundler/esbuild/utils/load-styles.js b/packages/one-app-dev-bundler/esbuild/utils/load-styles.js index 3b47ee98..aeb5c7a9 100644 --- a/packages/one-app-dev-bundler/esbuild/utils/load-styles.js +++ b/packages/one-app-dev-bundler/esbuild/utils/load-styles.js @@ -24,6 +24,15 @@ import getModulesBundlerConfig from './get-modules-bundler-config.js'; import { BUNDLE_TYPES } from '../constants/enums.js'; import { addStyle } from './server-style-aggregator.js'; +const getGenerateScopedNameOption = (path) => { + if (!path.includes('node_modules') || path.endsWith('.module.css') || path.endsWith('.module.scss')) { + // use the default option (scoped) for non-node_module files or css modules within node_modules + return undefined; + } + // for standard css files within node_modules, do not scope the class names + return '[local]'; +}; + // This function can generically take css or scss content, // and 'load it', turning it into js. Meaning it can be called // from either esbuild or webpack based bundlers. @@ -34,7 +43,7 @@ const loadStyles = async ({ }) => { const { localsConvention = 'camelCaseOnly', - generateScopedName, + generateScopedName = getGenerateScopedNameOption(path), } = cssModulesOptions; let cssContent; diff --git a/packages/one-app-server-bundler/__tests__/webpack/loaders/__snapshots__/common.spec.js.snap b/packages/one-app-server-bundler/__tests__/webpack/loaders/__snapshots__/common.spec.js.snap index 787961d7..b7982695 100644 --- a/packages/one-app-server-bundler/__tests__/webpack/loaders/__snapshots__/common.spec.js.snap +++ b/packages/one-app-server-bundler/__tests__/webpack/loaders/__snapshots__/common.spec.js.snap @@ -22,6 +22,19 @@ Object { } `; +exports[`Common webpack loaders css-loader should return null from getLocalIndent if resourcePath ends with .module.css 1`] = ` +Object { + "loader": "css-loader", + "options": Object { + "importLoaders": 2, + "modules": Object { + "getLocalIdent": [Function], + "localIdentName": "[name]__[local]___[hash:base64:5]", + }, + }, +} +`; + exports[`Common webpack loaders css-loader should still return a good localIdentName when not given a chunk name 1`] = ` Object { "loader": "css-loader", diff --git a/packages/one-app-server-bundler/__tests__/webpack/loaders/common.spec.js b/packages/one-app-server-bundler/__tests__/webpack/loaders/common.spec.js index 11114f4e..1fa1a2c1 100644 --- a/packages/one-app-server-bundler/__tests__/webpack/loaders/common.spec.js +++ b/packages/one-app-server-bundler/__tests__/webpack/loaders/common.spec.js @@ -81,6 +81,22 @@ describe('Common webpack loaders', () => { loaderContext, localIdentName, localName, options ); + expect(result).toEqual(null); + expect(config).toMatchSnapshot(); + }); + it('should return null from getLocalIndent if resourcePath ends with .module.css', () => { + const config = cssLoader(); + const loaderContext = { + resourcePath: 'node_modules/some-library/some-library.module.css', + }; + const localIdentName = '[name]__[local]___[hash:base64:5]'; + const localName = 'horizontal'; + const options = { context: undefined, hashPrefix: '', regExp: null }; + + const result = config.options.modules.getLocalIdent( + loaderContext, localIdentName, localName, options + ); + expect(result).toEqual(null); expect(config).toMatchSnapshot(); }); diff --git a/packages/one-app-server-bundler/webpack/loaders/common.js b/packages/one-app-server-bundler/webpack/loaders/common.js index c5d566bc..ff6b5955 100644 --- a/packages/one-app-server-bundler/webpack/loaders/common.js +++ b/packages/one-app-server-bundler/webpack/loaders/common.js @@ -27,11 +27,16 @@ const cssLoader = ({ name = '', importLoaders = 2 } = {}) => ({ // The documentation can be found here: // https://github.com/webpack-contrib/css-loader#getlocalident - // The below function returns the classnames as is if the resourcePath includes node_modules - // if it doesn't it returns null allowing localIdentName to define the classname - getLocalIdent: (loaderContext, localIdentName, localName) => ( - loaderContext.resourcePath.includes('node_modules') ? localName : null - ), + // With the exception of non-module css files in node_modules, we want to use the default + // localIdentName (returning null). For non-module css files in node_modules though, we will + // return the localName of the class as-is (non-scoped). + getLocalIdent: (loaderContext, localIdentName, localName) => { + const { resourcePath } = loaderContext; + if (!resourcePath.includes('node_modules') || resourcePath.endsWith('.module.css') || resourcePath.endsWith('.module.scss')) { + return null; + } + return localName; + }, }, }, });