Skip to content

Commit

Permalink
chore: update
Browse files Browse the repository at this point in the history
  • Loading branch information
SoonIter committed Jan 9, 2025
1 parent 61e685b commit bc9921d
Show file tree
Hide file tree
Showing 46 changed files with 750 additions and 302 deletions.
7 changes: 7 additions & 0 deletions examples/preact-component-bundle-false/src/assets/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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;
}
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;
}
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;
}
141 changes: 141 additions & 0 deletions packages/core/src/asset/LibAssetExtractPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import assert from 'node:assert';
import { type Rspack, rspack } from '@rsbuild/core';
import { getUndoPath } from '../css/utils';

/**
* these codes is written according to
* https://github.com/web-infra-dev/rspack/blob/61f0cd2b4e313445a9d3329ca71240e99edfb352/crates/rspack_plugin_asset/src/lib.rs#L531
*/

// 1. bundleless: single file
const BUNDLELESS_ASSET_PATTERN: RegExp =
/__webpack_require__\.p\s\+\s["'](.+)["']/g;
const RSLIB_NAMESPACE_OBJECT = '__rslib_asset__';
const esmSingleFileTemplate = (
url: string,
) => `import ${RSLIB_NAMESPACE_OBJECT} from '${url}';
export default ${RSLIB_NAMESPACE_OBJECT};`;
const cjsSingleFileTemplate = (url: string) =>
`module.exports = require('${url}');`;

function extractAssetFilenames(content: string): string[] {
return [...content.matchAll(BUNDLELESS_ASSET_PATTERN)]
.map((i) => {
return i?.[1];
})
.filter(Boolean) as string[];
}

// 2. bundle: concatenated
const CONCATENATED_PATTERN: RegExp =
/(const|var)\s(\w+)\s=\s\(?__webpack_require__\.p\s\+\s["'](.+)["']\)?/g;
const concatenatedEsmReplaceTemplate = (variableName: string, url: string) =>
`import ${variableName} from '${url}';`;
const concatenatedCjsReplaceTemplate = (
declarationKind: string,
variableName: string,
url: string,
) => `${declarationKind} ${variableName} = require('${url}');`;

// 3. bundle: not concatenated, in __webpack_require__.m
const NOT_CONCATENATED_PATTERN: RegExp =
/module\.exports = __webpack_require__\.p\s\+\s["'](.+)["']/g;
const nonConcatenatedReplaceTemplate = (url: string) =>
`module.exports = require('${url}');`;

const pluginName = 'LIB_ASSET_EXTRACT_PLUGIN';

type Options = {
// bundle and isUsingSvgr options: just for perf, in bundleless we can replace the entire file directly
bundle: boolean;
isUsingSvgr: boolean;
};

class LibAssetExtractPlugin implements Rspack.RspackPluginInstance {
readonly name: string = pluginName;
options: Options;
constructor(options: Options) {
this.options = options;
}

apply(compiler: Rspack.Compiler): void {
compiler.hooks.make.tap(pluginName, (compilation) => {
compilation.hooks.processAssets.tap(pluginName, (assets) => {
const chunkAsset = Object.keys(assets).filter((name) =>
/js$/.test(name),
);
const isEsmFormat = compilation.options.output.module;
const canEntireFileReplacedDirectly =
!this.options.bundle && !this.options.isUsingSvgr;
for (const name of chunkAsset) {
const undoPath = getUndoPath(
name,
compilation.outputOptions.path!,
true,
);
compilation.updateAsset(name, (old) => {
const oldSource = old.source().toString();

// bundleless
if (canEntireFileReplacedDirectly) {
const assetFilenames = extractAssetFilenames(oldSource);
// asset/resource
if (assetFilenames.length === 0) {
return old;
}
assert(
assetFilenames.length === 1,
`in bundleless mode, each asset file should only generate one js module, but generated ${assetFilenames}, ${oldSource}`,
);
const assetFilename = assetFilenames[0];
let newSource = '';
const url = `${undoPath}${assetFilename}`;

if (isEsmFormat) {
newSource = esmSingleFileTemplate(url);
} else {
newSource = cjsSingleFileTemplate(url);
}
return new rspack.sources.RawSource(newSource);
}

// bundle
const newSource = new rspack.sources.ReplaceSource(old);
function replace(
pattern: RegExp,
replacer: (match: RegExpMatchArray) => string,
) {
const matches = oldSource.matchAll(pattern);
for (const match of matches) {
const replaced = replacer(match);
newSource.replace(
match.index,
match.index + match[0].length - 1,
replaced,
);
}
}
replace(CONCATENATED_PATTERN, (match) => {
const declarationKind = match[1];
const variableName = match[2];
const url = `${undoPath}${match[3]}`;
return isEsmFormat
? concatenatedEsmReplaceTemplate(variableName!, url)
: concatenatedCjsReplaceTemplate(
declarationKind!,
variableName!,
url,
);
});
replace(NOT_CONCATENATED_PATTERN, (match) => {
const url = `${undoPath}${match[1]}`;
return nonConcatenatedReplaceTemplate(url);
});
return newSource;
});
}
});
});
}
}
export { LibAssetExtractPlugin };
46 changes: 42 additions & 4 deletions packages/core/src/asset/assetConfig.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,36 @@
import type { EnvironmentConfig } from '@rsbuild/core';
import type { EnvironmentConfig, RsbuildPlugin } from '@rsbuild/core';
import type { Format } from '../types';
import { LibAssetExtractPlugin } from './LibAssetExtractPlugin';

const PLUGIN_NAME = 'rsbuild:lib-asset-bundleless';

const RSBUILD_SVGR_PLUGIN_NAME = 'rsbuild:svgr';
const pluginLibAsset = ({ bundle }: { bundle: boolean }): RsbuildPlugin => ({
name: PLUGIN_NAME,
pre: [RSBUILD_SVGR_PLUGIN_NAME],
setup(api) {
api.modifyBundlerChain((config, { CHAIN_ID }) => {
// only support transform the svg asset to mixedImport svgr file
// remove issuer to make every svg asset is transformed
const isUsingSvgr = Boolean(
config.module
.rule(CHAIN_ID.RULE.SVG)
.oneOf(CHAIN_ID.RULE.SVG)
.uses.has(CHAIN_ID.USE.SVGR),
);
if (isUsingSvgr) {
const rule = config.module
.rule(CHAIN_ID.RULE.SVG)
.oneOf(CHAIN_ID.RULE.SVG);
rule.issuer([]);
}

config
.plugin(LibAssetExtractPlugin.name)
.use(LibAssetExtractPlugin, [{ bundle, isUsingSvgr }]);
});
},
});

// TODO: asset config document
export const composeAssetConfig = (
Expand All @@ -11,16 +42,23 @@ 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', // TODO: will turn on this with js support together in the future
},
tools: {
rspack: {
plugins: [
new LibAssetExtractPlugin({ bundle: true, isUsingSvgr: false }),
],
},
},
};
}

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
14 changes: 9 additions & 5 deletions packages/core/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { composeAssetConfig } from './asset/assetConfig';
import {
DEFAULT_CONFIG_EXTENSIONS,
DEFAULT_CONFIG_NAME,
ENTRY_EXTENSIONS_PATTERN,
DTS_EXTENSIONS_PATTERN,
JS_EXTENSIONS_PATTERN,
RSLIB_ENTRY_QUERY,
SWC_HELPERS,
Expand Down Expand Up @@ -975,9 +975,9 @@ const composeEntryConfig = async (
});

// Filter the glob resolved entry files based on the allowed extensions
const resolvedEntryFiles = globEntryFiles.filter((file) =>
ENTRY_EXTENSIONS_PATTERN.test(file),
);
const resolvedEntryFiles = globEntryFiles.filter((i) => {
return !DTS_EXTENSIONS_PATTERN.test(i);
});

if (resolvedEntryFiles.length === 0) {
throw new Error(`Cannot find ${resolvedEntryFiles}`);
Expand Down Expand Up @@ -1124,6 +1124,7 @@ const composeBundlelessExternalConfig = (
styleRedirectPath,
styleRedirectExtension,
redirectPath,
issuer,
);

if (cssExternal !== false) {
Expand Down Expand Up @@ -1154,7 +1155,10 @@ const composeBundlelessExternalConfig = (
);
} else {
// If it does not match jsExtensionsPattern, we should do nothing, eg: ./foo.png
return callback();
resolvedRequest = resolvedRequest.replace(
/\.[^.]+$/,
jsExtension,
);
}
} else {
resolvedRequest = `${resolvedRequest}${jsExtension}`;
Expand Down
11 changes: 4 additions & 7 deletions packages/core/src/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export const SHEBANG_REGEX: RegExp = /#!.*[\s\n\r]*$/;
export const REACT_DIRECTIVE_REGEX: RegExp =
/^['"]use (client|server)['"](;?)[\s\n\r]*$/;

const DTS_EXTENSIONS: string[] = ['d.ts', 'd.mts', 'd.cts'];

const JS_EXTENSIONS: string[] = [
'js',
'mjs',
Expand All @@ -33,11 +35,6 @@ const JS_EXTENSIONS: string[] = [

const CSS_EXTENSIONS: string[] = ['css', 'sass', 'scss', 'less'] as const;

const ENTRY_EXTENSIONS: string[] = [
...JS_EXTENSIONS,
...CSS_EXTENSIONS,
] as const;

export const JS_EXTENSIONS_PATTERN: RegExp = new RegExp(
`\\.(${JS_EXTENSIONS.join('|')})$`,
);
Expand All @@ -46,6 +43,6 @@ export const CSS_EXTENSIONS_PATTERN: RegExp = new RegExp(
`\\.(${CSS_EXTENSIONS.join('|')})$`,
);

export const ENTRY_EXTENSIONS_PATTERN: RegExp = new RegExp(
`\\.(${ENTRY_EXTENSIONS.join('|')})$`,
export const DTS_EXTENSIONS_PATTERN: RegExp = new RegExp(
`\\.(${DTS_EXTENSIONS.join('|')})$`,
);
Loading

0 comments on commit bc9921d

Please sign in to comment.