Skip to content

Commit 6067b85

Browse files
committed
feat(css): support bundleless css asset
1 parent 8a9f9e3 commit 6067b85

File tree

11 files changed

+200
-46
lines changed

11 files changed

+200
-46
lines changed

examples/react-component-bundle-false/rslib.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export default defineConfig({
3232
],
3333
output: {
3434
target: 'web',
35+
assetPrefix: 'auto' // TODO: move this line to packages/core/src/asset/assetConfig.ts
3536
},
3637
plugins: [pluginReact(), pluginSass()],
3738
});
Lines changed: 7 additions & 0 deletions
Loading
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
.button {
2-
background: yellow;
2+
background: url("../../assets/logo.svg");
33
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
.counter-title {
2+
width: 100px;
3+
height: 100px;
4+
background: no-repeat url('./assets/logo.svg');
5+
background-size: cover;
6+
}
7+
18
.counter-text {
29
font-size: 50px;
310
}

packages/core/src/asset/assetConfig.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { RsbuildConfig } from '@rsbuild/core';
22
import type { Format } from '../types';
33

4+
// TODO: asset config document
45
export const composeAssetConfig = (
56
bundle: boolean,
67
format: Format,
@@ -14,8 +15,13 @@ export const composeAssetConfig = (
1415
},
1516
};
1617
}
17-
// TODO: bundleless
18-
return {};
18+
19+
return {
20+
output: {
21+
dataUriLimit: 0, // default: no inline asset
22+
// assetPrefix: 'auto', // TODO: will turn on this with js support together in the future
23+
}
24+
};
1925
}
2026

2127
// mf and umd etc
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { rspack, type Rspack } from '@rsbuild/core';
2+
import { getUndoPath } from './utils';
3+
import { ABSOLUTE_PUBLIC_PATH, SINGLE_DOT_PATH_SEGMENT, AUTO_PUBLIC_PATH } from './libCssExtractLoader';
4+
5+
const pluginName = 'LIB_CSS_EXTRACT_PLUGIN';
6+
7+
type Options = {
8+
include: RegExp;
9+
};
10+
11+
class LibCssExtractPlugin implements Rspack.RspackPluginInstance {
12+
readonly name: string = pluginName;
13+
options: Options;
14+
constructor(options: Options) {
15+
this.options = options;
16+
}
17+
18+
apply(compiler: Rspack.Compiler): void {
19+
const include = this.options.include;
20+
compiler.hooks.thisCompilation.tap(pluginName, (compilation) => {
21+
compilation.hooks.chunkAsset.tap(pluginName, (_chunk, filename) => {
22+
const asset = compilation.getAsset(filename);
23+
console.log(asset);
24+
if (!asset) {
25+
return;
26+
}
27+
const needRemove = Boolean(asset.name.match(include));
28+
if (needRemove) {
29+
compilation.deleteAsset(filename);
30+
}
31+
});
32+
33+
});
34+
35+
/**
36+
* The following code is modified based on
37+
* https://github.com/webpack-contrib/mini-css-extract-plugin/blob/3effaa0319bad5cc1bf0ae760553bf7abcbc35a4/src/index.js#L1597
38+
*
39+
* replace publicPath placeholders of miniCssExtractLoader
40+
*/
41+
compiler.hooks.make.tap(pluginName, (compilation) => {
42+
compilation.hooks.processAssets.tap(pluginName, (assets) => {
43+
const chunkAsset = Object.keys(assets).filter((name) =>
44+
/\.css/.test(name),
45+
);
46+
for (const name of chunkAsset) {
47+
compilation.updateAsset(name, (old) => {
48+
const oldSource = old.source().toString();
49+
const replaceSource = new rspack.sources.ReplaceSource(old);
50+
51+
function replace(searchValue: string, replaceValue: string) {
52+
let start = oldSource.indexOf(searchValue);
53+
while(start !== -1) {
54+
console.log(start)
55+
replaceSource.replace(
56+
start,
57+
start + searchValue.length - 1,
58+
replaceValue,
59+
);
60+
start = oldSource.indexOf(searchValue, start + 1);
61+
}
62+
}
63+
64+
replace(ABSOLUTE_PUBLIC_PATH, '');
65+
replace(SINGLE_DOT_PATH_SEGMENT, '.');
66+
const undoPath = getUndoPath(name, compilation.outputOptions.path!, false);
67+
replace(AUTO_PUBLIC_PATH, undoPath)
68+
69+
return replaceSource;
70+
});
71+
}
72+
73+
});
74+
});
75+
}
76+
}
77+
export { LibCssExtractPlugin };

packages/core/src/css/RemoveCssExtractAssetPlugin.ts

Lines changed: 0 additions & 32 deletions
This file was deleted.

packages/core/src/css/cssConfig.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type {
66
RsbuildPlugin,
77
} from '@rsbuild/core';
88
import { CSS_EXTENSIONS_PATTERN } from '../constant';
9-
import { RemoveCssExtractAssetPlugin } from './RemoveCssExtractAssetPlugin';
9+
import { LibCssExtractPlugin } from './LibCssExtractPlugin';
1010
const require = createRequire(import.meta.url);
1111

1212
export const RSLIB_CSS_ENTRY_FLAG = '__rslib_css__';
@@ -139,8 +139,8 @@ const pluginLibCss = (rootDir: string): RsbuildPlugin => ({
139139
const cssExtract = CHAIN_ID.PLUGIN.MINI_CSS_EXTRACT;
140140
config.plugins.delete(cssExtract);
141141
config
142-
.plugin(RemoveCssExtractAssetPlugin.name)
143-
.use(RemoveCssExtractAssetPlugin, [
142+
.plugin(LibCssExtractPlugin.name)
143+
.use(LibCssExtractPlugin, [
144144
{
145145
include: new RegExp(`^${RSLIB_CSS_ENTRY_FLAG}`),
146146
},

packages/core/src/css/libCssExtractLoader.ts

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,18 @@
33
* https://github.com/web-infra-dev/rspack/blob/0a89e433a9f8596a7c6c4326542f168b5982d2da/packages/rspack/src/builtin-plugin/css-extract/loader.ts
44
* 1. remove hmr/webpack runtime
55
* 2. add `this.emitFile` to emit css files
6-
* 3. add `import './[name].css';`
6+
* 3. add `import './[name].css';` to js module
77
*/
88
import path, { extname } from 'node:path';
99
import type { Rspack } from '@rsbuild/core';
1010

11+
export const BASE_URI = "webpack://";
12+
export const MODULE_TYPE = "css/mini-extract";
13+
export const AUTO_PUBLIC_PATH = "__mini_css_extract_plugin_public_path_auto__";
14+
export const ABSOLUTE_PUBLIC_PATH: string = `${BASE_URI}/mini-css-extract-plugin/`;
15+
export const SINGLE_DOT_PATH_SEGMENT =
16+
"__mini_css_extract_plugin_single_dot_path_segment__";
17+
1118
interface DependencyDescription {
1219
identifier: string;
1320
content: string;
@@ -20,7 +27,13 @@ interface DependencyDescription {
2027
filepath: string;
2128
}
2229

30+
// https://github.com/web-infra-dev/rspack/blob/c0986d39b7d647682f10fcef5bbade39fd016eca/packages/rspack/src/config/types.ts#L10
31+
type Filename =
32+
| string
33+
| ((pathData: any, assetInfo?: any) => string);
34+
2335
export interface CssExtractRspackLoaderOptions {
36+
publicPath?: string | ((resourcePath: string, context: string) => string);
2437
emit?: boolean;
2538
esModule?: boolean;
2639
layer?: string;
@@ -29,7 +42,7 @@ export interface CssExtractRspackLoaderOptions {
2942
rootDir?: string;
3043
}
3144

32-
const PLUGIN_NAME = 'LIB_CSS_EXTRACT_LOADER';
45+
const LOADER_NAME = 'LIB_CSS_EXTRACT_LOADER';
3346

3447
function stringifyLocal(value: any) {
3548
return typeof value === 'function' ? value.toString() : JSON.stringify(value);
@@ -77,6 +90,35 @@ export const pitch: Rspack.LoaderDefinition['pitch'] = function (
7790
const filepath = this.resourcePath;
7891
const rootDir = options.rootDir ?? this.rootContext;
7992

93+
let { publicPath } = this._compilation!.outputOptions;
94+
95+
96+
if (typeof options.publicPath === "string") {
97+
// eslint-disable-next-line prefer-destructuring
98+
publicPath = options.publicPath;
99+
} else if (typeof options.publicPath === "function") {
100+
publicPath = options.publicPath(this.resourcePath, this.rootContext);
101+
}
102+
103+
if (publicPath === "auto") {
104+
publicPath = AUTO_PUBLIC_PATH;
105+
}
106+
107+
let publicPathForExtract: Filename | undefined;
108+
109+
if (typeof publicPath === "string") {
110+
const isAbsolutePublicPath = /^[a-zA-Z][a-zA-Z\d+\-.]*?:/.test(publicPath);
111+
112+
publicPathForExtract = isAbsolutePublicPath
113+
? publicPath
114+
: `${ABSOLUTE_PUBLIC_PATH}${publicPath.replace(
115+
/\./g,
116+
SINGLE_DOT_PATH_SEGMENT
117+
)}`;
118+
} else {
119+
publicPathForExtract = publicPath;
120+
}
121+
80122
const handleExports = (
81123
originalExports:
82124
| { default: Record<string, any>; __esModule: true }
@@ -196,7 +238,7 @@ export const pitch: Rspack.LoaderDefinition['pitch'] = function (
196238
return '';
197239
})();
198240

199-
let resultSource = `// extracted by ${PLUGIN_NAME}`;
241+
let resultSource = `// extracted by ${LOADER_NAME}`;
200242

201243
let importCssFiles = '';
202244

@@ -249,6 +291,8 @@ export const pitch: Rspack.LoaderDefinition['pitch'] = function (
249291
`${this.resourcePath}.webpack[javascript/auto]!=!!!${request}`,
250292
{
251293
layer: options.layer,
294+
publicPath: publicPathForExtract,
295+
baseUri: `${BASE_URI}/`,
252296
},
253297
(error, exports) => {
254298
if (error) {

packages/core/src/css/utils.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/**
2+
* This function is copied from
3+
* https://github.com/webpack-contrib/mini-css-extract-plugin/blob/3effaa0319bad5cc1bf0ae760553bf7abcbc35a4/src/utils.js#L169
4+
*/
5+
function getUndoPath(filename: string, outputPath: string, enforceRelative: boolean): string {
6+
let depth = -1;
7+
let append = "";
8+
9+
// eslint-disable-next-line no-param-reassign
10+
outputPath = outputPath.replace(/[\\/]$/, "");
11+
12+
for (const part of filename.split(/[/\\]+/)) {
13+
if (part === "..") {
14+
if (depth > -1) {
15+
// eslint-disable-next-line no-plusplus
16+
depth--;
17+
} else {
18+
const i = outputPath.lastIndexOf("/");
19+
const j = outputPath.lastIndexOf("\\");
20+
const pos = i < 0 ? j : j < 0 ? i : Math.max(i, j);
21+
22+
if (pos < 0) {
23+
return `${outputPath}/`;
24+
}
25+
26+
append = `${outputPath.slice(pos + 1)}/${append}`;
27+
28+
// eslint-disable-next-line no-param-reassign
29+
outputPath = outputPath.slice(0, pos);
30+
}
31+
} else if (part !== ".") {
32+
// eslint-disable-next-line no-plusplus
33+
depth++;
34+
}
35+
}
36+
37+
return depth > 0
38+
? `${"../".repeat(depth)}${append}`
39+
: enforceRelative
40+
? `./${append}`
41+
: append;
42+
}
43+
44+
export { getUndoPath }

0 commit comments

Comments
 (0)