Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions e2e/cases/less/inline-query/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { build, dev, rspackOnlyTest } from '@e2e/helper';
import { expect } from '@playwright/test';

rspackOnlyTest(
'should allow to import inline Less 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();
},
);

rspackOnlyTest(
'should allow to import inline Less 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();
},
);
File renamed without changes.
5 changes: 5 additions & 0 deletions e2e/cases/less/inline-query/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import aInline from './a.less?inline';
import bInline from './b.module.less?inline';

window.aInline = aInline;
window.bInline = bInline;
5 changes: 5 additions & 0 deletions e2e/cases/less/raw-query/rsbuild.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { pluginLess } from '@rsbuild/plugin-less';

export default {
plugins: [pluginLess()],
};
5 changes: 5 additions & 0 deletions e2e/cases/less/raw-query/src/a.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.header {
&-class {
color: red;
}
}
5 changes: 5 additions & 0 deletions e2e/cases/less/raw-query/src/b.module.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.title {
&-class {
font-size: 14px;
}
}
File renamed without changes.
72 changes: 36 additions & 36 deletions e2e/cases/sass/inline-query/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,43 @@
import { readFileSync } from 'node:fs';
import path from 'node:path';
import { build, dev } from '@e2e/helper';
import { expect, test } from '@playwright/test';
import { build, dev, rspackOnlyTest } from '@e2e/helper';
import { expect } from '@playwright/test';

test('should allow to import inline Sass files in development mode', async ({
page,
}) => {
const rsbuild = await dev({
cwd: __dirname,
page,
});
rspackOnlyTest(
'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');
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);
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();
});
await rsbuild.close();
},
);

test('should allow to import inline Sass files in production mode', async ({
page,
}) => {
const rsbuild = await build({
cwd: __dirname,
page,
});
rspackOnlyTest(
'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}',
);
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();
});
await rsbuild.close();
},
);
2 changes: 2 additions & 0 deletions packages/core/src/configChain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ export const CHAIN_ID = {
LESS: 'less',
/** Rule for raw Less */
LESS_RAW: 'less-raw',
/** Rule for inline Less */
LESS_INLINE: 'less-inline',
/** Rule for Sass */
SASS: 'sass',
/** Rule for raw Sass */
Expand Down
4 changes: 4 additions & 0 deletions packages/core/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,10 @@ declare module '*.sass?inline' {
const content: string;
export default content;
}
declare module '*.less?inline' {
const content: string;
export default content;
}

/**
* Raw CSS
Expand Down
80 changes: 49 additions & 31 deletions packages/plugin-less/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,24 +135,29 @@ export const pluginLess = (
name: PLUGIN_LESS_NAME,

setup(api) {
const { include = /\.less$/ } = pluginOptions;

api.modifyBundlerChain(async (chain, { CHAIN_ID, environment }) => {
const { config } = environment;

const ruleId = findRuleId(chain, CHAIN_ID.RULE.LESS);
const test = pluginOptions.include ?? /\.less$/;
const rule = chain.module
.rule(ruleId)
.test(test)
.rule(findRuleId(chain, CHAIN_ID.RULE.LESS))
.test(include)
// exclude `import './foo.less?raw'` and `import './foo.less?inline'`
.resourceQuery({ not: /raw|inline/ })
.sideEffects(true)
.resolve.preferRelative(true)
.end()
// exclude `import './foo.less?raw'`
.resourceQuery({ not: /raw/ });
.end();

const inlineRule = chain.module
.rule(findRuleId(chain, CHAIN_ID.RULE.LESS_INLINE))
.test(include)
.resourceQuery(/inline/);

// Support for importing raw Less files
chain.module
.rule(CHAIN_ID.RULE.LESS_RAW)
.test(test)
.test(include)
.type('asset/source')
.resourceQuery(/raw/);

Expand All @@ -163,35 +168,48 @@ export const pluginLess = (
api.context.rootPath,
);

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') => void,
) => {
callback(rule, 'normal');
callback(inlineRule, 'inline');
};

const lessLoaderPath = path.join(
__dirname,
'../compiled/less-loader/index.js',
);

if (pluginOptions.exclude) {
rule.exclude.add(pluginOptions.exclude);
}
updateRules((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(CHAIN_ID.RULE.CSS);
rule.dependency(cssRule.get('dependency'));
// 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<Record<string, any>>({}, options);
for (const id of Object.keys(cssRule.uses.entries())) {
const loader = cssRule.uses.get(id);
const options = loader.get('options') ?? {};
const clonedOptions = deepmerge<Record<string, any>>({}, options);

if (id === CHAIN_ID.USE.CSS) {
// add less-loader
clonedOptions.importLoaders += 1;
}
if (id === CHAIN_ID.USE.CSS) {
// add less-loader
clonedOptions.importLoaders += 1;
}

rule.use(id).loader(loader.get('loader')).options(clonedOptions);
}
rule.use(id).loader(loader.get('loader')).options(clonedOptions);
}

rule
.use(CHAIN_ID.USE.LESS)
.loader(path.join(__dirname, '../compiled/less-loader/index.js'))
.options(options);
rule.use(CHAIN_ID.USE.LESS).loader(lessLoaderPath).options(options);
});
});
},
});
Loading
Loading