diff --git a/e2e/cases/css/sass-raw/index.test.ts b/e2e/cases/css/sass-raw/index.test.ts new file mode 100644 index 0000000000..eca866150a --- /dev/null +++ b/e2e/cases/css/sass-raw/index.test.ts @@ -0,0 +1,40 @@ +import { readFileSync } from 'node:fs'; +import path from 'node:path'; +import { build, dev } from '@e2e/helper'; +import { expect, test } from '@playwright/test'; + +test('should allow to import raw Sass files in development mode', async ({ + page, +}) => { + const rsbuild = await dev({ + cwd: __dirname, + page, + }); + + expect(await page.evaluate('window.aRaw')).toBe( + readFileSync(path.join(__dirname, 'src/a.scss'), 'utf-8'), + ); + expect(await page.evaluate('window.bRaw')).toBe( + readFileSync(path.join(__dirname, 'src/b.module.scss'), 'utf-8'), + ); + + await rsbuild.close(); +}); + +test('should allow to import raw Sass files in production mode', async ({ + page, +}) => { + const rsbuild = await build({ + cwd: __dirname, + page, + }); + + expect(await page.evaluate('window.aRaw')).toBe( + readFileSync(path.join(__dirname, 'src/a.scss'), 'utf-8'), + ); + expect(await page.evaluate('window.bRaw')).toBe( + readFileSync(path.join(__dirname, 'src/b.module.scss'), 'utf-8'), + ); + + await rsbuild.close(); +}); diff --git a/e2e/cases/css/sass-raw/rsbuild.config.ts b/e2e/cases/css/sass-raw/rsbuild.config.ts new file mode 100644 index 0000000000..eac845109c --- /dev/null +++ b/e2e/cases/css/sass-raw/rsbuild.config.ts @@ -0,0 +1,5 @@ +import { pluginSass } from '@rsbuild/plugin-sass'; + +export default { + plugins: [pluginSass()], +}; diff --git a/e2e/cases/css/sass-raw/src/a.scss b/e2e/cases/css/sass-raw/src/a.scss new file mode 100644 index 0000000000..ce27061885 --- /dev/null +++ b/e2e/cases/css/sass-raw/src/a.scss @@ -0,0 +1,5 @@ +.header { + &-class { + color: red; + } +} diff --git a/e2e/cases/css/sass-raw/src/b.module.scss b/e2e/cases/css/sass-raw/src/b.module.scss new file mode 100644 index 0000000000..b640e3f1b5 --- /dev/null +++ b/e2e/cases/css/sass-raw/src/b.module.scss @@ -0,0 +1,5 @@ +.title { + &-class { + font-size: 14px; + } +} diff --git a/e2e/cases/css/sass-raw/src/index.js b/e2e/cases/css/sass-raw/src/index.js new file mode 100644 index 0000000000..67e53dbcd0 --- /dev/null +++ b/e2e/cases/css/sass-raw/src/index.js @@ -0,0 +1,5 @@ +import aRaw from './a.scss?raw'; +import bRaw from './b.module.scss?raw'; + +window.aRaw = aRaw; +window.bRaw = bRaw; diff --git a/packages/core/src/configChain.ts b/packages/core/src/configChain.ts index eede8336c2..a289af5521 100644 --- a/packages/core/src/configChain.ts +++ b/packages/core/src/configChain.ts @@ -51,10 +51,12 @@ export const CHAIN_ID = { CSS: 'css', /** Rule for raw CSS */ CSS_RAW: 'css-raw', - /** Rule for less */ + /** Rule for Less */ LESS: 'less', - /** Rule for sass */ + /** Rule for Sass */ SASS: 'sass', + /** Rule for raw Sass */ + SASS_RAW: 'sass-raw', /** Rule for stylus */ STYLUS: 'stylus', /** Rule for svg */ diff --git a/packages/core/src/plugins/css.ts b/packages/core/src/plugins/css.ts index bedf34b734..a08f377352 100644 --- a/packages/core/src/plugins/css.ts +++ b/packages/core/src/plugins/css.ts @@ -265,7 +265,6 @@ export const pluginCss = (): RsbuildPlugin => ({ order: 'pre', handler: async (chain, { target, isProd, CHAIN_ID, environment }) => { const rule = chain.module.rule(CHAIN_ID.RULE.CSS); - const rawRule = chain.module.rule(CHAIN_ID.RULE.CSS_RAW); const { config } = environment; rule @@ -279,7 +278,11 @@ export const pluginCss = (): RsbuildPlugin => ({ .resourceQuery({ not: /raw/ }); // Support for importing raw CSS files - rawRule.test(CSS_REGEX).type('asset/source').resourceQuery(/raw/); + chain.module + .rule(CHAIN_ID.RULE.CSS_RAW) + .test(CSS_REGEX) + .type('asset/source') + .resourceQuery(/raw/); const emitCss = config.output.emitCss ?? target === 'web'; diff --git a/packages/plugin-sass/src/index.ts b/packages/plugin-sass/src/index.ts index 1207879202..e3303a608c 100644 --- a/packages/plugin-sass/src/index.ts +++ b/packages/plugin-sass/src/index.ts @@ -118,12 +118,22 @@ export const pluginSass = ( ); const ruleId = findRuleId(chain, CHAIN_ID.RULE.SASS); + const test = pluginOptions.include ?? /\.s(?:a|c)ss$/; const rule = chain.module .rule(ruleId) - .test(pluginOptions.include ?? /\.s(?:a|c)ss$/) + .test(test) .merge({ sideEffects: true }) .resolve.preferRelative(true) - .end(); + .end() + // exclude `import './foo.scss?raw'` + .resourceQuery({ not: /raw/ }); + + // Support for importing raw Sass files + chain.module + .rule(CHAIN_ID.RULE.SASS_RAW) + .test(test) + .type('asset/source') + .resourceQuery(/raw/); for (const item of excludes) { rule.exclude.add(item); diff --git a/packages/plugin-sass/tests/__snapshots__/index.test.ts.snap b/packages/plugin-sass/tests/__snapshots__/index.test.ts.snap index b6f8cabe0b..2a9ecef456 100644 --- a/packages/plugin-sass/tests/__snapshots__/index.test.ts.snap +++ b/packages/plugin-sass/tests/__snapshots__/index.test.ts.snap @@ -9,6 +9,9 @@ exports[`plugin-sass > should add sass-loader 1`] = ` "resolve": { "preferRelative": true, }, + "resourceQuery": { + "not": /raw/, + }, "sideEffects": true, "test": /\\\\\\.s\\(\\?:a\\|c\\)ss\\$/, "use": [ @@ -63,6 +66,11 @@ exports[`plugin-sass > should add sass-loader 1`] = ` }, ], }, + { + "resourceQuery": /raw/, + "test": /\\\\\\.s\\(\\?:a\\|c\\)ss\\$/, + "type": "asset/source", + }, ] `; @@ -75,6 +83,9 @@ exports[`plugin-sass > should add sass-loader and css-loader when injectStyles 1 "resolve": { "preferRelative": true, }, + "resourceQuery": { + "not": /raw/, + }, "sideEffects": true, "test": /\\\\\\.s\\(\\?:a\\|c\\)ss\\$/, "use": [ @@ -129,6 +140,11 @@ exports[`plugin-sass > should add sass-loader and css-loader when injectStyles 1 }, ], }, + { + "resourceQuery": /raw/, + "test": /\\\\\\.s\\(\\?:a\\|c\\)ss\\$/, + "type": "asset/source", + }, ] `; @@ -144,6 +160,9 @@ exports[`plugin-sass > should add sass-loader with excludes 1`] = ` "resolve": { "preferRelative": true, }, + "resourceQuery": { + "not": /raw/, + }, "sideEffects": true, "test": /\\\\\\.s\\(\\?:a\\|c\\)ss\\$/, "use": [ @@ -198,6 +217,11 @@ exports[`plugin-sass > should add sass-loader with excludes 1`] = ` }, ], }, + { + "resourceQuery": /raw/, + "test": /\\\\\\.s\\(\\?:a\\|c\\)ss\\$/, + "type": "asset/source", + }, ] `; @@ -210,6 +234,9 @@ exports[`plugin-sass > should allow to use legacy API and mute deprecation warni "resolve": { "preferRelative": true, }, + "resourceQuery": { + "not": /raw/, + }, "sideEffects": true, "test": /\\\\\\.s\\(\\?:a\\|c\\)ss\\$/, "use": [ @@ -265,5 +292,10 @@ exports[`plugin-sass > should allow to use legacy API and mute deprecation warni }, ], }, + { + "resourceQuery": /raw/, + "test": /\\\\\\.s\\(\\?:a\\|c\\)ss\\$/, + "type": "asset/source", + }, ] `; diff --git a/packages/plugin-sass/tests/index.test.ts b/packages/plugin-sass/tests/index.test.ts index 4ddda081a3..b09031676f 100644 --- a/packages/plugin-sass/tests/index.test.ts +++ b/packages/plugin-sass/tests/index.test.ts @@ -78,6 +78,6 @@ describe('plugin-sass', () => { const bundlerConfigs = await rsbuild.initConfigs(); expect(matchRules(bundlerConfigs[0], 'a.scss').length).toBe(1); - expect(matchRules(bundlerConfigs[0], 'b.scss').length).toBe(2); + expect(matchRules(bundlerConfigs[0], 'b.scss').length).toBe(3); }); });