From b402d6f7e21a353dab73ba1f5857b4525a465128 Mon Sep 17 00:00:00 2001 From: neverland Date: Mon, 24 Mar 2025 15:48:15 +0800 Subject: [PATCH 1/2] feat(plugin-sass): add support for inline query --- e2e/cases/css/inline-query/index.test.ts | 2 - e2e/cases/sass/inline-query/index.test.ts | 43 ++++ .../{raw => inline-query}/rsbuild.config.ts | 0 .../sass/{raw => inline-query}/src/a.scss | 0 .../{raw => inline-query}/src/b.module.scss | 0 e2e/cases/sass/inline-query/src/index.js | 5 + .../sass/{raw => raw-query}/index.test.ts | 0 e2e/cases/sass/raw-query/rsbuild.config.ts | 5 + e2e/cases/sass/raw-query/src/a.scss | 5 + e2e/cases/sass/raw-query/src/b.module.scss | 5 + .../sass/{raw => raw-query}/src/index.js | 0 packages/core/src/configChain.ts | 2 + packages/core/types.d.ts | 8 + packages/plugin-sass/src/index.ts | 119 ++++++----- .../tests/__snapshots__/index.test.ts.snap | 200 +++++++++++++++++- packages/plugin-sass/tests/index.test.ts | 4 +- 16 files changed, 340 insertions(+), 58 deletions(-) create mode 100644 e2e/cases/sass/inline-query/index.test.ts rename e2e/cases/sass/{raw => inline-query}/rsbuild.config.ts (100%) rename e2e/cases/sass/{raw => inline-query}/src/a.scss (100%) rename e2e/cases/sass/{raw => inline-query}/src/b.module.scss (100%) create mode 100644 e2e/cases/sass/inline-query/src/index.js rename e2e/cases/sass/{raw => raw-query}/index.test.ts (100%) create mode 100644 e2e/cases/sass/raw-query/rsbuild.config.ts create mode 100644 e2e/cases/sass/raw-query/src/a.scss create mode 100644 e2e/cases/sass/raw-query/src/b.module.scss rename e2e/cases/sass/{raw => raw-query}/src/index.js (100%) diff --git a/e2e/cases/css/inline-query/index.test.ts b/e2e/cases/css/inline-query/index.test.ts index 03d843df5d..363c1217e9 100644 --- a/e2e/cases/css/inline-query/index.test.ts +++ b/e2e/cases/css/inline-query/index.test.ts @@ -1,5 +1,3 @@ -import { readFileSync } from 'node:fs'; -import path from 'node:path'; import { build, dev, rspackOnlyTest } from '@e2e/helper'; import { expect } from '@playwright/test'; diff --git a/e2e/cases/sass/inline-query/index.test.ts b/e2e/cases/sass/inline-query/index.test.ts new file mode 100644 index 0000000000..8a2753a706 --- /dev/null +++ b/e2e/cases/sass/inline-query/index.test.ts @@ -0,0 +1,43 @@ +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 inline Sass files in development mode', async ({ + page, +}) => { + const rsbuild = await dev({ + cwd: __dirname, + page, + }); + + const aInline: string = await page.evaluate('window.aInline'); + const bInline: string = await page.evaluate('window.bInline'); + + expect( + aInline.includes('.header-class') && aInline.includes('color: red'), + ).toBe(true); + expect( + bInline.includes('.title-class') && bInline.includes('font-size: 14px'), + ).toBe(true); + + await rsbuild.close(); +}); + +test('should allow to import inline Sass files in production mode', async ({ + page, +}) => { + const rsbuild = await build({ + cwd: __dirname, + page, + }); + + expect(await page.evaluate('window.aInline')).toBe( + '.header-class{color:red}', + ); + expect(await page.evaluate('window.bInline')).toBe( + '.title-class{font-size:14px}', + ); + + await rsbuild.close(); +}); diff --git a/e2e/cases/sass/raw/rsbuild.config.ts b/e2e/cases/sass/inline-query/rsbuild.config.ts similarity index 100% rename from e2e/cases/sass/raw/rsbuild.config.ts rename to e2e/cases/sass/inline-query/rsbuild.config.ts diff --git a/e2e/cases/sass/raw/src/a.scss b/e2e/cases/sass/inline-query/src/a.scss similarity index 100% rename from e2e/cases/sass/raw/src/a.scss rename to e2e/cases/sass/inline-query/src/a.scss diff --git a/e2e/cases/sass/raw/src/b.module.scss b/e2e/cases/sass/inline-query/src/b.module.scss similarity index 100% rename from e2e/cases/sass/raw/src/b.module.scss rename to e2e/cases/sass/inline-query/src/b.module.scss diff --git a/e2e/cases/sass/inline-query/src/index.js b/e2e/cases/sass/inline-query/src/index.js new file mode 100644 index 0000000000..b8c7b9e527 --- /dev/null +++ b/e2e/cases/sass/inline-query/src/index.js @@ -0,0 +1,5 @@ +import aInline from './a.scss?inline'; +import bInline from './b.module.scss?inline'; + +window.aInline = aInline; +window.bInline = bInline; diff --git a/e2e/cases/sass/raw/index.test.ts b/e2e/cases/sass/raw-query/index.test.ts similarity index 100% rename from e2e/cases/sass/raw/index.test.ts rename to e2e/cases/sass/raw-query/index.test.ts diff --git a/e2e/cases/sass/raw-query/rsbuild.config.ts b/e2e/cases/sass/raw-query/rsbuild.config.ts new file mode 100644 index 0000000000..eac845109c --- /dev/null +++ b/e2e/cases/sass/raw-query/rsbuild.config.ts @@ -0,0 +1,5 @@ +import { pluginSass } from '@rsbuild/plugin-sass'; + +export default { + plugins: [pluginSass()], +}; diff --git a/e2e/cases/sass/raw-query/src/a.scss b/e2e/cases/sass/raw-query/src/a.scss new file mode 100644 index 0000000000..ce27061885 --- /dev/null +++ b/e2e/cases/sass/raw-query/src/a.scss @@ -0,0 +1,5 @@ +.header { + &-class { + color: red; + } +} diff --git a/e2e/cases/sass/raw-query/src/b.module.scss b/e2e/cases/sass/raw-query/src/b.module.scss new file mode 100644 index 0000000000..b640e3f1b5 --- /dev/null +++ b/e2e/cases/sass/raw-query/src/b.module.scss @@ -0,0 +1,5 @@ +.title { + &-class { + font-size: 14px; + } +} diff --git a/e2e/cases/sass/raw/src/index.js b/e2e/cases/sass/raw-query/src/index.js similarity index 100% rename from e2e/cases/sass/raw/src/index.js rename to e2e/cases/sass/raw-query/src/index.js diff --git a/packages/core/src/configChain.ts b/packages/core/src/configChain.ts index a28d838491..50ca5de33f 100644 --- a/packages/core/src/configChain.ts +++ b/packages/core/src/configChain.ts @@ -61,6 +61,8 @@ export const CHAIN_ID = { SASS: 'sass', /** Rule for raw Sass */ SASS_RAW: 'sass-raw', + /** Rule for inline Sass */ + SASS_INLINE: 'sass-inline', /** Rule for stylus */ STYLUS: 'stylus', /** Rule for raw stylus */ diff --git a/packages/core/types.d.ts b/packages/core/types.d.ts index 702d4dbd33..07661e0858 100644 --- a/packages/core/types.d.ts +++ b/packages/core/types.d.ts @@ -191,6 +191,14 @@ declare module '*.css?inline' { const content: string; export default content; } +declare module '*.scss?inline' { + const content: string; + export default content; +} +declare module '*.sass?inline' { + const content: string; + export default content; +} /** * Raw CSS diff --git a/packages/plugin-sass/src/index.ts b/packages/plugin-sass/src/index.ts index 8fe9d856ff..8f2ca91d32 100644 --- a/packages/plugin-sass/src/index.ts +++ b/packages/plugin-sass/src/index.ts @@ -98,7 +98,7 @@ export const pluginSass = ( name: PLUGIN_SASS_NAME, setup(api) { - const { rewriteUrls = true } = pluginOptions; + const { rewriteUrls = true, include = /\.s(?:a|c)ss$/ } = pluginOptions; api.onAfterCreateCompiler(({ compiler }) => { patchCompilerGlobalLocation(compiler); @@ -117,73 +117,92 @@ export const pluginSass = ( rewriteUrls ? true : isUseSourceMap, ); - const ruleId = findRuleId(chain, CHAIN_ID.RULE.SASS); - const test = pluginOptions.include ?? /\.s(?:a|c)ss$/; const rule = chain.module - .rule(ruleId) - .test(test) + .rule(findRuleId(chain, CHAIN_ID.RULE.SASS)) + .test(include) + // exclude `import './foo.scss?raw'` + .resourceQuery({ not: /raw|inline/ }) .sideEffects(true) .resolve.preferRelative(true) - .end() - // exclude `import './foo.scss?raw'` - .resourceQuery({ not: /raw/ }); + .end(); + + const inlineRule = chain.module + .rule(findRuleId(chain, CHAIN_ID.RULE.SASS_INLINE)) + .test(include) + .resourceQuery(/inline/); // Support for importing raw Sass files chain.module .rule(CHAIN_ID.RULE.SASS_RAW) - .test(test) + .test(include) .type('asset/source') .resourceQuery(/raw/); - for (const item of excludes) { - rule.exclude.add(item); - } + // Update the normal rule and the inline rule + const updateRules = ( + callback: ( + rule: RspackChain.Rule, + type: 'normal' | 'inline', + ) => Promise, + ) => + Promise.all([callback(rule, 'normal'), callback(inlineRule, 'inline')]); + + const sassLoaderPath = path.join( + __dirname, + '../compiled/sass-loader/index.js', + ); - if (pluginOptions.exclude) { - rule.exclude.add(pluginOptions.exclude); - } + const resolveUrlLoaderPath = path.join( + __dirname, + '../compiled/resolve-url-loader/index.js', + ); - // Copy the builtin CSS rules - const cssRule = chain.module.rules.get(CHAIN_ID.RULE.CSS); - rule.dependency(cssRule.get('dependency')); + const resolveUrlLoaderOptions = { + join: await getResolveUrlJoinFn(), + // 'resolve-url-loader' relies on 'adjust-sourcemap-loader', + // it has performance regression issues in some scenarios, + // so we need to disable the sourceMap option. + sourceMap: false, + }; + + await updateRules(async (rule, type) => { + for (const item of excludes) { + rule.exclude.add(item); + } + if (pluginOptions.exclude) { + rule.exclude.add(pluginOptions.exclude); + } + + // Copy the builtin CSS rules + const cssRule = chain.module.rules.get( + type === 'normal' ? CHAIN_ID.RULE.CSS : CHAIN_ID.RULE.CSS_INLINE, + ); + rule.dependency(cssRule.get('dependency')); - for (const id of Object.keys(cssRule.uses.entries())) { - const loader = cssRule.uses.get(id); - const options = loader.get('options') ?? {}; - const clonedOptions = deepmerge>({}, options); + for (const id of Object.keys(cssRule.uses.entries())) { + const loader = cssRule.uses.get(id); + const options = loader.get('options') ?? {}; + const clonedOptions = deepmerge>({}, options); - if (id === CHAIN_ID.USE.CSS) { - // add resolve-url-loader - if (rewriteUrls) { - clonedOptions.importLoaders += 1; + if (id === CHAIN_ID.USE.CSS) { + // add sass-loader and resolve-url-loader + clonedOptions.importLoaders += rewriteUrls ? 2 : 1; } - // add sass-loader - clonedOptions.importLoaders += 1; + + rule.use(id).loader(loader.get('loader')).options(clonedOptions); } - rule.use(id).loader(loader.get('loader')).options(clonedOptions); - } + // use `resolve-url-loader` to rewrite urls + if (rewriteUrls) { + rule + .use(CHAIN_ID.USE.RESOLVE_URL) + .loader(resolveUrlLoaderPath) + .options(resolveUrlLoaderOptions) + .end(); + } - // use `resolve-url-loader` to rewrite urls - if (rewriteUrls) - rule - .use(CHAIN_ID.USE.RESOLVE_URL) - .loader( - path.join(__dirname, '../compiled/resolve-url-loader/index.js'), - ) - .options({ - join: await getResolveUrlJoinFn(), - // 'resolve-url-loader' relies on 'adjust-sourcemap-loader', - // it has performance regression issues in some scenarios, - // so we need to disable the sourceMap option. - sourceMap: false, - }) - .end(); - - rule - .use(CHAIN_ID.USE.SASS) - .loader(path.join(__dirname, '../compiled/sass-loader/index.js')) - .options(options); + rule.use(CHAIN_ID.USE.SASS).loader(sassLoaderPath).options(options); + }); }); }, }); diff --git a/packages/plugin-sass/tests/__snapshots__/index.test.ts.snap b/packages/plugin-sass/tests/__snapshots__/index.test.ts.snap index 2a9ecef456..eaefe99350 100644 --- a/packages/plugin-sass/tests/__snapshots__/index.test.ts.snap +++ b/packages/plugin-sass/tests/__snapshots__/index.test.ts.snap @@ -10,7 +10,7 @@ exports[`plugin-sass > should add sass-loader 1`] = ` "preferRelative": true, }, "resourceQuery": { - "not": /raw/, + "not": /raw\\|inline/, }, "sideEffects": true, "test": /\\\\\\.s\\(\\?:a\\|c\\)ss\\$/, @@ -66,6 +66,53 @@ exports[`plugin-sass > should add sass-loader 1`] = ` }, ], }, + { + "resourceQuery": /inline/, + "test": /\\\\\\.s\\(\\?:a\\|c\\)ss\\$/, + "use": [ + { + "loader": "/packages/core/compiled/css-loader/index.js", + "options": { + "exportType": "string", + "importLoaders": 3, + "modules": false, + "sourceMap": false, + }, + }, + { + "loader": "builtin:lightningcss-loader", + "options": { + "targets": [ + "chrome >= 87", + "edge >= 88", + "firefox >= 78", + "safari >= 14", + ], + }, + }, + { + "loader": "/packages/plugin-sass/compiled/resolve-url-loader/index.js", + "options": { + "join": [Function], + "sourceMap": false, + }, + }, + { + "loader": "/packages/plugin-sass/compiled/sass-loader/index.js", + "options": { + "api": "modern-compiler", + "implementation": "/node_modules//sass-embedded/dist/lib/index.js", + "sassOptions": { + "quietDeps": true, + "silenceDeprecations": [ + "import", + ], + }, + "sourceMap": true, + }, + }, + ], + }, { "resourceQuery": /raw/, "test": /\\\\\\.s\\(\\?:a\\|c\\)ss\\$/, @@ -84,7 +131,7 @@ exports[`plugin-sass > should add sass-loader and css-loader when injectStyles 1 "preferRelative": true, }, "resourceQuery": { - "not": /raw/, + "not": /raw\\|inline/, }, "sideEffects": true, "test": /\\\\\\.s\\(\\?:a\\|c\\)ss\\$/, @@ -140,6 +187,53 @@ exports[`plugin-sass > should add sass-loader and css-loader when injectStyles 1 }, ], }, + { + "resourceQuery": /inline/, + "test": /\\\\\\.s\\(\\?:a\\|c\\)ss\\$/, + "use": [ + { + "loader": "/packages/core/compiled/css-loader/index.js", + "options": { + "exportType": "string", + "importLoaders": 3, + "modules": false, + "sourceMap": false, + }, + }, + { + "loader": "builtin:lightningcss-loader", + "options": { + "targets": [ + "chrome >= 87", + "edge >= 88", + "firefox >= 78", + "safari >= 14", + ], + }, + }, + { + "loader": "/packages/plugin-sass/compiled/resolve-url-loader/index.js", + "options": { + "join": [Function], + "sourceMap": false, + }, + }, + { + "loader": "/packages/plugin-sass/compiled/sass-loader/index.js", + "options": { + "api": "modern-compiler", + "implementation": "/node_modules//sass-embedded/dist/lib/index.js", + "sassOptions": { + "quietDeps": true, + "silenceDeprecations": [ + "import", + ], + }, + "sourceMap": true, + }, + }, + ], + }, { "resourceQuery": /raw/, "test": /\\\\\\.s\\(\\?:a\\|c\\)ss\\$/, @@ -161,7 +255,7 @@ exports[`plugin-sass > should add sass-loader with excludes 1`] = ` "preferRelative": true, }, "resourceQuery": { - "not": /raw/, + "not": /raw\\|inline/, }, "sideEffects": true, "test": /\\\\\\.s\\(\\?:a\\|c\\)ss\\$/, @@ -217,6 +311,56 @@ exports[`plugin-sass > should add sass-loader with excludes 1`] = ` }, ], }, + { + "exclude": [ + /node_modules/, + ], + "resourceQuery": /inline/, + "test": /\\\\\\.s\\(\\?:a\\|c\\)ss\\$/, + "use": [ + { + "loader": "/packages/core/compiled/css-loader/index.js", + "options": { + "exportType": "string", + "importLoaders": 3, + "modules": false, + "sourceMap": false, + }, + }, + { + "loader": "builtin:lightningcss-loader", + "options": { + "targets": [ + "chrome >= 87", + "edge >= 88", + "firefox >= 78", + "safari >= 14", + ], + }, + }, + { + "loader": "/packages/plugin-sass/compiled/resolve-url-loader/index.js", + "options": { + "join": [Function], + "sourceMap": false, + }, + }, + { + "loader": "/packages/plugin-sass/compiled/sass-loader/index.js", + "options": { + "api": "modern-compiler", + "implementation": "/node_modules//sass-embedded/dist/lib/index.js", + "sassOptions": { + "quietDeps": true, + "silenceDeprecations": [ + "import", + ], + }, + "sourceMap": true, + }, + }, + ], + }, { "resourceQuery": /raw/, "test": /\\\\\\.s\\(\\?:a\\|c\\)ss\\$/, @@ -235,7 +379,7 @@ exports[`plugin-sass > should allow to use legacy API and mute deprecation warni "preferRelative": true, }, "resourceQuery": { - "not": /raw/, + "not": /raw\\|inline/, }, "sideEffects": true, "test": /\\\\\\.s\\(\\?:a\\|c\\)ss\\$/, @@ -292,6 +436,54 @@ exports[`plugin-sass > should allow to use legacy API and mute deprecation warni }, ], }, + { + "resourceQuery": /inline/, + "test": /\\\\\\.s\\(\\?:a\\|c\\)ss\\$/, + "use": [ + { + "loader": "/packages/core/compiled/css-loader/index.js", + "options": { + "exportType": "string", + "importLoaders": 3, + "modules": false, + "sourceMap": false, + }, + }, + { + "loader": "builtin:lightningcss-loader", + "options": { + "targets": [ + "chrome >= 87", + "edge >= 88", + "firefox >= 78", + "safari >= 14", + ], + }, + }, + { + "loader": "/packages/plugin-sass/compiled/resolve-url-loader/index.js", + "options": { + "join": [Function], + "sourceMap": false, + }, + }, + { + "loader": "/packages/plugin-sass/compiled/sass-loader/index.js", + "options": { + "api": "legacy", + "implementation": "/node_modules//sass-embedded/dist/lib/index.js", + "sassOptions": { + "quietDeps": true, + "silenceDeprecations": [ + "import", + "legacy-js-api", + ], + }, + "sourceMap": true, + }, + }, + ], + }, { "resourceQuery": /raw/, "test": /\\\\\\.s\\(\\?:a\\|c\\)ss\\$/, diff --git a/packages/plugin-sass/tests/index.test.ts b/packages/plugin-sass/tests/index.test.ts index b09031676f..8d69b2cd55 100644 --- a/packages/plugin-sass/tests/index.test.ts +++ b/packages/plugin-sass/tests/index.test.ts @@ -77,7 +77,7 @@ 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(3); + expect(matchRules(bundlerConfigs[0], 'a.scss').length).toBe(2); + expect(matchRules(bundlerConfigs[0], 'b.scss').length).toBe(5); }); }); From e56698c148f14ca0cd9757235e90c77621458e75 Mon Sep 17 00:00:00 2001 From: neverland Date: Mon, 24 Mar 2025 16:01:56 +0800 Subject: [PATCH 2/2] fix --- pnpm-lock.yaml | 18 ++++++++++++++++-- website/package.json | 2 +- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2ce33371b9..b46d1afefa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1144,8 +1144,8 @@ importers: specifier: workspace:* version: link:../packages/core '@rsbuild/plugin-sass': - specifier: workspace:* - version: link:../packages/plugin-sass + specifier: ^1.2.2 + version: 1.2.2(@rsbuild/core@packages+core) '@rspress/plugin-client-redirects': specifier: ^2.0.0-alpha.5 version: 2.0.0-alpha.5(@rspress/runtime@2.0.0-alpha.5) @@ -2266,6 +2266,11 @@ packages: '@rsbuild/core': optional: true + '@rsbuild/plugin-sass@1.2.2': + resolution: {integrity: sha512-vznLfxxPXDyFSPYW7JWTYf/6SJMx5DEgKParNd5lXo7FRa1IKsQOrJdf6F3Rm+T7jKoAvnCVXjM2IkxBW2yJSA==} + peerDependencies: + '@rsbuild/core': 1.x + '@rsbuild/plugin-type-check@1.2.1': resolution: {integrity: sha512-PtbjeMqDQy8IiPDTuaj8ZmvR42b0AsRq6RUF6wxa8dDsOzD0Dl1GcvemVGCto+/Dh8frLUmnlWF+T8riBw5rtA==} peerDependencies: @@ -7879,6 +7884,15 @@ snapshots: optionalDependencies: '@rsbuild/core': link:packages/core + '@rsbuild/plugin-sass@1.2.2(@rsbuild/core@packages+core)': + dependencies: + '@rsbuild/core': link:packages/core + deepmerge: 4.3.1 + loader-utils: 2.0.4 + postcss: 8.5.3 + reduce-configs: 1.1.0 + sass-embedded: 1.86.0 + '@rsbuild/plugin-type-check@1.2.1(@rsbuild/core@packages+core)(@rspack/core@1.3.0-beta.0(@swc/helpers@0.5.15))(typescript@5.8.2)': dependencies: deepmerge: 4.3.1 diff --git a/website/package.json b/website/package.json index 527091ef20..6e906a0025 100644 --- a/website/package.json +++ b/website/package.json @@ -10,7 +10,7 @@ }, "devDependencies": { "@rsbuild/core": "workspace:*", - "@rsbuild/plugin-sass": "workspace:*", + "@rsbuild/plugin-sass": "^1.2.2", "@rspress/plugin-client-redirects": "^2.0.0-alpha.5", "@rspress/plugin-rss": "^2.0.0-alpha.5", "@rstack-dev/doc-ui": "1.7.3",