Skip to content

Commit

Permalink
feat: support asset module (#684)
Browse files Browse the repository at this point in the history
  • Loading branch information
SoonIter authored Jan 23, 2025
1 parent fc6cf37 commit 19dc930
Show file tree
Hide file tree
Showing 77 changed files with 1,313 additions and 247 deletions.
5 changes: 0 additions & 5 deletions examples/preact-component-bundle-false/rslib.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,6 @@ import { pluginSass } from '@rsbuild/plugin-sass';
import { defineConfig } from '@rslib/core';

export default defineConfig({
source: {
entry: {
index: ['./src/**'],
},
},
lib: [
{
bundle: false,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { FunctionComponent } from 'preact';
import logo from '../../assets/logo.svg';
import styles from './index.module.scss';

interface CounterButtonProps {
Expand All @@ -10,7 +11,12 @@ export const CounterButton: FunctionComponent<CounterButtonProps> = ({
onClick,
label,
}) => (
<button type="button" className={styles.button} onClick={onClick}>
<button
type="button"
className={`${styles.button} counter-button`}
onClick={onClick}
>
<img src={logo} alt="react" />
{label}
</button>
);
5 changes: 5 additions & 0 deletions examples/preact-component-bundle-false/src/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,8 @@ declare module '*.module.scss' {
const classes: { [key: string]: string };
export default classes;
}

declare module '*.svg' {
const url: string;
export default url;
}
7 changes: 7 additions & 0 deletions examples/preact-component-bundle-false/src/index.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
.counter-title {
width: 100px;
height: 100px;
background: no-repeat url('./assets/logo.svg');
background-size: cover;
}

.counter-text {
font-size: 50px;
}
6 changes: 0 additions & 6 deletions examples/react-component-bundle-false/rslib.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,6 @@ import { pluginSass } from '@rsbuild/plugin-sass';
import { defineConfig } from '@rslib/core';

export default defineConfig({
source: {
entry: {
index: ['./src/**'],
},
},
lib: [
{
format: 'esm',
Expand All @@ -32,7 +27,6 @@ export default defineConfig({
],
output: {
target: 'web',
assetPrefix: 'auto', // TODO: move this line to packages/core/src/asset/assetConfig.ts,
},
plugins: [pluginReact(), pluginSass()],
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type React from 'react';
import logo from '../../assets/logo.svg';
import styles from './index.module.scss';

interface CounterButtonProps {
Expand All @@ -10,7 +11,12 @@ export const CounterButton: React.FC<CounterButtonProps> = ({
onClick,
label,
}) => (
<button type="button" className={styles.button} onClick={onClick}>
<button
type="button"
className={`${styles.button} counter-button`}
onClick={onClick}
>
<img src={logo} alt="react" />
{label}
</button>
);
5 changes: 5 additions & 0 deletions examples/react-component-bundle-false/src/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,8 @@ declare module '*.module.scss' {
const classes: { [key: string]: string };
export default classes;
}

declare module '*.svg' {
const url: string;
export default url;
}
1 change: 0 additions & 1 deletion examples/react-component-bundle/rslib.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ export default defineConfig({
],
output: {
target: 'web',
assetPrefix: 'auto', // TODO: move this line to packages/core/src/asset/assetConfig.ts
},
plugins: [pluginReact(), pluginSass()],
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type React from 'react';
import logo from '../../assets/logo.svg';
import styles from './index.module.scss';

interface CounterButtonProps {
Expand All @@ -10,7 +11,12 @@ export const CounterButton: React.FC<CounterButtonProps> = ({
onClick,
label,
}) => (
<button type="button" className={styles.button} onClick={onClick}>
<button
type="button"
className={`${styles.button} counter-button`}
onClick={onClick}
>
<img src={logo} alt="react" />
{label}
</button>
);
5 changes: 5 additions & 0 deletions examples/react-component-bundle/src/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,8 @@ declare module '*.module.scss' {
const classes: { [key: string]: string };
export default classes;
}

declare module '*.svg' {
const url: string;
export default url;
}
68 changes: 68 additions & 0 deletions packages/core/src/asset/LibSvgrPatchPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { type Rspack, rspack } from '@rsbuild/core';
import { getUndoPath } from '../css/utils';

const pluginName = 'LIB_SVGR_PATCH_PLUGIN';

export const PUBLIC_PATH_PLACEHOLDER = '__RSLIB_SVGR_AUTO_PUBLIC_PATH__';

export class LibSvgrPatchPlugin implements Rspack.RspackPluginInstance {
readonly name: string = pluginName;
apply(compiler: Rspack.Compiler): void {
compiler.hooks.make.tap(this.name, (compilation) => {
compilation.hooks.processAssets.tap(this.name, (assets) => {
const isEsm = Boolean(compilation.options.output.module);
const chunkAsset = Object.keys(assets).filter((name) =>
/js$/.test(name),
);
for (const name of chunkAsset) {
compilation.updateAsset(name, (old) => {
const oldSource = old.source().toString();
const newSource = new rspack.sources.ReplaceSource(old);

const pattern = new RegExp(
`\\(?['"]${PUBLIC_PATH_PLACEHOLDER}(.*)['"]\\)?`,
'g',
);

const matches = [...oldSource.matchAll(pattern)];
const len = matches.length;
if (len === 0) {
return old;
}

const undoPath = getUndoPath(
name,
compilation.outputOptions.path!,
true,
);
for (let i = 0; i < len; i++) {
const match = matches[i]!;
const filename = match[1];
const requirePath = `${undoPath}${filename}`;
let replaced = '';
if (isEsm) {
replaced = `__rslib_svgr_url__${i}__`;
} else {
replaced = `require("${requirePath}")`;
}
newSource.replace(
match.index,
match.index + match[0].length - 1,
replaced,
);

if (isEsm) {
newSource.insert(
0,
`import __rslib_svgr_url__${i}__ from "${requirePath}";\n`,
);
}
}

return newSource;
});
}
});
});
}
}
150 changes: 146 additions & 4 deletions packages/core/src/asset/assetConfig.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,146 @@
import type { EnvironmentConfig } from '@rsbuild/core';
import type { EnvironmentConfig, RsbuildPlugin } from '@rsbuild/core';
import { CSS_EXTENSIONS_PATTERN } from '../constant';
import type { Format } from '../types';
import {
LibSvgrPatchPlugin,
PUBLIC_PATH_PLACEHOLDER,
} from './LibSvgrPatchPlugin';

const PLUGIN_NAME = 'rsbuild:lib-asset';

const RSBUILD_SVGR_PLUGIN_NAME = 'rsbuild:svgr';

/**
* Be compatible to css-extract importModule and experimentalLibPreserveExports
* when set experimentalLibPreserveExports to true, the css-loader result can not executed in node side, so clone the assets rule
* 1. js assets: original rule set issuer and experimentalLibPreserveExports: true
* 2. css assets: a copy of original rule
*/
const pluginLibAsset = ({ bundle }: { bundle: boolean }): RsbuildPlugin => ({
name: PLUGIN_NAME,
pre: [RSBUILD_SVGR_PLUGIN_NAME],
setup(api) {
api.modifyBundlerChain((config, { CHAIN_ID }) => {
// 1. modify svg rule first, svg is special because of svgr
const svgAssetRule = config.module.rules
.get(CHAIN_ID.RULE.SVG)
.oneOfs.get(CHAIN_ID.ONE_OF.SVG_ASSET);
const originalTypeOptions = svgAssetRule.get('type');
const originalParserOptions = svgAssetRule.get('parser');
const originalGeneratorOptions = svgAssetRule.get('generator');

const isUserSetPublicPath = config.output.get('publicPath') !== 'auto';

// if user sets publicPath, do not preserve asset import
const generatorOptions = isUserSetPublicPath
? originalGeneratorOptions
: {
...originalGeneratorOptions,
importMode: 'preserve',
};

const rule = config.module.rule(CHAIN_ID.RULE.SVG);

rule.oneOf(CHAIN_ID.ONE_OF.SVG_ASSET).generator(generatorOptions).issuer({
not: CSS_EXTENSIONS_PATTERN,
});

rule
.oneOf(`${CHAIN_ID.ONE_OF.SVG_ASSET}-for-css`)
.type(originalTypeOptions)
.parser(originalParserOptions)
.generator(originalGeneratorOptions)
.issuer(CSS_EXTENSIONS_PATTERN);

// 2. modify other assets rules
const ruleIds = [
CHAIN_ID.RULE.FONT,
CHAIN_ID.RULE.MEDIA,
CHAIN_ID.RULE.IMAGE,
CHAIN_ID.RULE.ADDITIONAL_ASSETS,
];
for (const ruleId of ruleIds) {
const oneOfId = `${ruleId}-asset`;
const assetRule = config.module.rules.get(ruleId);
if (!assetRule) {
continue;
}
const assetRuleOneOf = assetRule.oneOfs.get(oneOfId);

const originalTypeOptions = assetRuleOneOf.get('type');
const originalParserOptions = assetRuleOneOf.get('parser');
const originalGeneratorOptions = assetRuleOneOf.get('generator');

const generatorOptions = isUserSetPublicPath
? originalGeneratorOptions
: {
...originalGeneratorOptions,
importMode: 'preserve',
};

const rule = config.module.rule(ruleId);
rule.oneOf(oneOfId).generator(generatorOptions).issuer({
not: CSS_EXTENSIONS_PATTERN,
});

rule
.oneOf(`${oneOfId}-for-css`)
.type(originalTypeOptions)
.parser(originalParserOptions)
.generator(originalGeneratorOptions)
.issuer(CSS_EXTENSIONS_PATTERN);
}

// for svgr
// 1. remove __webpack_require__.p in svgr url-loader and file-loader
const isUsingSvgr = Boolean(
config.module
.rule(CHAIN_ID.RULE.SVG)
.oneOf(CHAIN_ID.RULE.SVG)
.uses.has(CHAIN_ID.USE.SVGR),
);
if (isUsingSvgr) {
const urlLoaderRule = config.module
.rule(CHAIN_ID.RULE.SVG)
.oneOf(CHAIN_ID.ONE_OF.SVG)
.use(CHAIN_ID.USE.URL);

const originalOptions = urlLoaderRule.get('options');

urlLoaderRule.options({
...originalOptions,
publicPath: (url: string) => `${PUBLIC_PATH_PLACEHOLDER}${url}`,
});
config.plugin(LibSvgrPatchPlugin.name).use(LibSvgrPatchPlugin, []);
}
// 2. in bundleless, only support transform the svg asset to mixedImport svgr file
// remove issuer to make every svg asset is transformed
if (!bundle) {
if (isUsingSvgr) {
const rule = config.module
.rule(CHAIN_ID.RULE.SVG)
.oneOf(CHAIN_ID.ONE_OF.SVG);
rule.issuer([]);
}
}

// css-asset
// preserve './' in css url
// in bundleless, we set this in libCssExtractLoader
// in bundle, we set this by https://github.com/web-infra-dev/rspack/pull/8946
if (bundle) {
config.plugins.get(CHAIN_ID.PLUGIN.MINI_CSS_EXTRACT)?.tap((options) => {
return [
{
...options[0],
enforceRelative: true,
},
];
});
}
});
},
});

// TODO: asset config document
export const composeAssetConfig = (
Expand All @@ -11,16 +152,17 @@ export const composeAssetConfig = (
return {
output: {
dataUriLimit: 0, // default: no inline asset
// assetPrefix: 'auto', // TODO: will turn on this with js support together in the future
assetPrefix: 'auto',
},
plugins: [pluginLibAsset({ bundle: true })],
};
}

return {
output: {
dataUriLimit: 0, // default: no inline asset
// assetPrefix: 'auto', // TODO: will turn on this with js support together in the future
assetPrefix: 'auto',
},
plugins: [pluginLibAsset({ bundle: false })],
};
}

Expand Down
Loading

0 comments on commit 19dc930

Please sign in to comment.