Skip to content

Commit bc9921d

Browse files
committed
chore: update
1 parent 61e685b commit bc9921d

File tree

46 files changed

+750
-302
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+750
-302
lines changed
Lines changed: 7 additions & 0 deletions
Loading

examples/preact-component-bundle-false/src/components/CounterButton/index.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { FunctionComponent } from 'preact';
2+
import logo from '../../assets/logo.svg';
23
import styles from './index.module.scss';
34

45
interface CounterButtonProps {
@@ -10,7 +11,12 @@ export const CounterButton: FunctionComponent<CounterButtonProps> = ({
1011
onClick,
1112
label,
1213
}) => (
13-
<button type="button" className={styles.button} onClick={onClick}>
14+
<button
15+
type="button"
16+
className={`${styles.button} counter-button`}
17+
onClick={onClick}
18+
>
19+
<img src={logo} alt="react" />
1420
{label}
1521
</button>
1622
);

examples/preact-component-bundle-false/src/env.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,8 @@ declare module '*.module.scss' {
22
const classes: { [key: string]: string };
33
export default classes;
44
}
5+
6+
declare module '*.svg' {
7+
const url: string;
8+
export default url;
9+
}
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
}

examples/react-component-bundle-false/src/components/CounterButton/index.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type React from 'react';
2+
import logo from '../../assets/logo.svg';
23
import styles from './index.module.scss';
34

45
interface CounterButtonProps {
@@ -10,7 +11,12 @@ export const CounterButton: React.FC<CounterButtonProps> = ({
1011
onClick,
1112
label,
1213
}) => (
13-
<button type="button" className={styles.button} onClick={onClick}>
14+
<button
15+
type="button"
16+
className={`${styles.button} counter-button`}
17+
onClick={onClick}
18+
>
19+
<img src={logo} alt="react" />
1420
{label}
1521
</button>
1622
);

examples/react-component-bundle-false/src/env.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,8 @@ declare module '*.module.scss' {
22
const classes: { [key: string]: string };
33
export default classes;
44
}
5+
6+
declare module '*.svg' {
7+
const url: string;
8+
export default url;
9+
}

examples/react-component-bundle/src/components/CounterButton/index.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type React from 'react';
2+
import logo from '../../assets/logo.svg';
23
import styles from './index.module.scss';
34

45
interface CounterButtonProps {
@@ -10,7 +11,12 @@ export const CounterButton: React.FC<CounterButtonProps> = ({
1011
onClick,
1112
label,
1213
}) => (
13-
<button type="button" className={styles.button} onClick={onClick}>
14+
<button
15+
type="button"
16+
className={`${styles.button} counter-button`}
17+
onClick={onClick}
18+
>
19+
<img src={logo} alt="react" />
1420
{label}
1521
</button>
1622
);

examples/react-component-bundle/src/env.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,8 @@ declare module '*.module.scss' {
22
const classes: { [key: string]: string };
33
export default classes;
44
}
5+
6+
declare module '*.svg' {
7+
const url: string;
8+
export default url;
9+
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import assert from 'node:assert';
2+
import { type Rspack, rspack } from '@rsbuild/core';
3+
import { getUndoPath } from '../css/utils';
4+
5+
/**
6+
* these codes is written according to
7+
* https://github.com/web-infra-dev/rspack/blob/61f0cd2b4e313445a9d3329ca71240e99edfb352/crates/rspack_plugin_asset/src/lib.rs#L531
8+
*/
9+
10+
// 1. bundleless: single file
11+
const BUNDLELESS_ASSET_PATTERN: RegExp =
12+
/__webpack_require__\.p\s\+\s["'](.+)["']/g;
13+
const RSLIB_NAMESPACE_OBJECT = '__rslib_asset__';
14+
const esmSingleFileTemplate = (
15+
url: string,
16+
) => `import ${RSLIB_NAMESPACE_OBJECT} from '${url}';
17+
export default ${RSLIB_NAMESPACE_OBJECT};`;
18+
const cjsSingleFileTemplate = (url: string) =>
19+
`module.exports = require('${url}');`;
20+
21+
function extractAssetFilenames(content: string): string[] {
22+
return [...content.matchAll(BUNDLELESS_ASSET_PATTERN)]
23+
.map((i) => {
24+
return i?.[1];
25+
})
26+
.filter(Boolean) as string[];
27+
}
28+
29+
// 2. bundle: concatenated
30+
const CONCATENATED_PATTERN: RegExp =
31+
/(const|var)\s(\w+)\s=\s\(?__webpack_require__\.p\s\+\s["'](.+)["']\)?/g;
32+
const concatenatedEsmReplaceTemplate = (variableName: string, url: string) =>
33+
`import ${variableName} from '${url}';`;
34+
const concatenatedCjsReplaceTemplate = (
35+
declarationKind: string,
36+
variableName: string,
37+
url: string,
38+
) => `${declarationKind} ${variableName} = require('${url}');`;
39+
40+
// 3. bundle: not concatenated, in __webpack_require__.m
41+
const NOT_CONCATENATED_PATTERN: RegExp =
42+
/module\.exports = __webpack_require__\.p\s\+\s["'](.+)["']/g;
43+
const nonConcatenatedReplaceTemplate = (url: string) =>
44+
`module.exports = require('${url}');`;
45+
46+
const pluginName = 'LIB_ASSET_EXTRACT_PLUGIN';
47+
48+
type Options = {
49+
// bundle and isUsingSvgr options: just for perf, in bundleless we can replace the entire file directly
50+
bundle: boolean;
51+
isUsingSvgr: boolean;
52+
};
53+
54+
class LibAssetExtractPlugin implements Rspack.RspackPluginInstance {
55+
readonly name: string = pluginName;
56+
options: Options;
57+
constructor(options: Options) {
58+
this.options = options;
59+
}
60+
61+
apply(compiler: Rspack.Compiler): void {
62+
compiler.hooks.make.tap(pluginName, (compilation) => {
63+
compilation.hooks.processAssets.tap(pluginName, (assets) => {
64+
const chunkAsset = Object.keys(assets).filter((name) =>
65+
/js$/.test(name),
66+
);
67+
const isEsmFormat = compilation.options.output.module;
68+
const canEntireFileReplacedDirectly =
69+
!this.options.bundle && !this.options.isUsingSvgr;
70+
for (const name of chunkAsset) {
71+
const undoPath = getUndoPath(
72+
name,
73+
compilation.outputOptions.path!,
74+
true,
75+
);
76+
compilation.updateAsset(name, (old) => {
77+
const oldSource = old.source().toString();
78+
79+
// bundleless
80+
if (canEntireFileReplacedDirectly) {
81+
const assetFilenames = extractAssetFilenames(oldSource);
82+
// asset/resource
83+
if (assetFilenames.length === 0) {
84+
return old;
85+
}
86+
assert(
87+
assetFilenames.length === 1,
88+
`in bundleless mode, each asset file should only generate one js module, but generated ${assetFilenames}, ${oldSource}`,
89+
);
90+
const assetFilename = assetFilenames[0];
91+
let newSource = '';
92+
const url = `${undoPath}${assetFilename}`;
93+
94+
if (isEsmFormat) {
95+
newSource = esmSingleFileTemplate(url);
96+
} else {
97+
newSource = cjsSingleFileTemplate(url);
98+
}
99+
return new rspack.sources.RawSource(newSource);
100+
}
101+
102+
// bundle
103+
const newSource = new rspack.sources.ReplaceSource(old);
104+
function replace(
105+
pattern: RegExp,
106+
replacer: (match: RegExpMatchArray) => string,
107+
) {
108+
const matches = oldSource.matchAll(pattern);
109+
for (const match of matches) {
110+
const replaced = replacer(match);
111+
newSource.replace(
112+
match.index,
113+
match.index + match[0].length - 1,
114+
replaced,
115+
);
116+
}
117+
}
118+
replace(CONCATENATED_PATTERN, (match) => {
119+
const declarationKind = match[1];
120+
const variableName = match[2];
121+
const url = `${undoPath}${match[3]}`;
122+
return isEsmFormat
123+
? concatenatedEsmReplaceTemplate(variableName!, url)
124+
: concatenatedCjsReplaceTemplate(
125+
declarationKind!,
126+
variableName!,
127+
url,
128+
);
129+
});
130+
replace(NOT_CONCATENATED_PATTERN, (match) => {
131+
const url = `${undoPath}${match[1]}`;
132+
return nonConcatenatedReplaceTemplate(url);
133+
});
134+
return newSource;
135+
});
136+
}
137+
});
138+
});
139+
}
140+
}
141+
export { LibAssetExtractPlugin };

packages/core/src/asset/assetConfig.ts

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,36 @@
1-
import type { EnvironmentConfig } from '@rsbuild/core';
1+
import type { EnvironmentConfig, RsbuildPlugin } from '@rsbuild/core';
22
import type { Format } from '../types';
3+
import { LibAssetExtractPlugin } from './LibAssetExtractPlugin';
4+
5+
const PLUGIN_NAME = 'rsbuild:lib-asset-bundleless';
6+
7+
const RSBUILD_SVGR_PLUGIN_NAME = 'rsbuild:svgr';
8+
const pluginLibAsset = ({ bundle }: { bundle: boolean }): RsbuildPlugin => ({
9+
name: PLUGIN_NAME,
10+
pre: [RSBUILD_SVGR_PLUGIN_NAME],
11+
setup(api) {
12+
api.modifyBundlerChain((config, { CHAIN_ID }) => {
13+
// only support transform the svg asset to mixedImport svgr file
14+
// remove issuer to make every svg asset is transformed
15+
const isUsingSvgr = Boolean(
16+
config.module
17+
.rule(CHAIN_ID.RULE.SVG)
18+
.oneOf(CHAIN_ID.RULE.SVG)
19+
.uses.has(CHAIN_ID.USE.SVGR),
20+
);
21+
if (isUsingSvgr) {
22+
const rule = config.module
23+
.rule(CHAIN_ID.RULE.SVG)
24+
.oneOf(CHAIN_ID.RULE.SVG);
25+
rule.issuer([]);
26+
}
27+
28+
config
29+
.plugin(LibAssetExtractPlugin.name)
30+
.use(LibAssetExtractPlugin, [{ bundle, isUsingSvgr }]);
31+
});
32+
},
33+
});
334

435
// TODO: asset config document
536
export const composeAssetConfig = (
@@ -11,16 +42,23 @@ export const composeAssetConfig = (
1142
return {
1243
output: {
1344
dataUriLimit: 0, // default: no inline asset
14-
// assetPrefix: 'auto', // TODO: will turn on this with js support together in the future
45+
assetPrefix: 'auto', // TODO: will turn on this with js support together in the future
46+
},
47+
tools: {
48+
rspack: {
49+
plugins: [
50+
new LibAssetExtractPlugin({ bundle: true, isUsingSvgr: false }),
51+
],
52+
},
1553
},
1654
};
1755
}
18-
1956
return {
2057
output: {
2158
dataUriLimit: 0, // default: no inline asset
22-
// assetPrefix: 'auto', // TODO: will turn on this with js support together in the future
59+
assetPrefix: 'auto',
2360
},
61+
plugins: [pluginLibAsset({ bundle: false })],
2462
};
2563
}
2664

0 commit comments

Comments
 (0)