Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(css): support css asset in bundleless mode and esm/cjs #582

Merged
merged 5 commits into from
Dec 19, 2024
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
1 change: 1 addition & 0 deletions examples/react-component-bundle-false/rslib.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export default defineConfig({
],
output: {
target: 'web',
assetPrefix: 'auto', // TODO: move this line to packages/core/src/asset/assetConfig.ts
},
plugins: [pluginReact(), pluginSass()],
});
7 changes: 7 additions & 0 deletions examples/react-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.
7 changes: 7 additions & 0 deletions examples/react-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;
}
1 change: 1 addition & 0 deletions examples/react-component-bundle-false/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const Counter: React.FC = () => {

return (
<div>
<h1 className="counter-title">React</h1>
<h2 className="counter-text">Counter: {count}</h2>
<CounterButton onClick={decrement} label="-" />
<CounterButton onClick={increment} label="+" />
Expand Down
10 changes: 8 additions & 2 deletions packages/core/src/asset/assetConfig.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { RsbuildConfig } from '@rsbuild/core';
import type { Format } from '../types';

// TODO: asset config document
export const composeAssetConfig = (
bundle: boolean,
format: Format,
Expand All @@ -14,8 +15,13 @@ export const composeAssetConfig = (
},
};
}
// TODO: bundleless
return {};

return {
output: {
dataUriLimit: 0, // default: no inline asset
// assetPrefix: 'auto', // TODO: will turn on this with js support together in the future
},
};
}

// mf and umd etc
Expand Down
81 changes: 81 additions & 0 deletions packages/core/src/css/LibCssExtractPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { type Rspack, rspack } from '@rsbuild/core';
import { RSLIB_CSS_ENTRY_FLAG } from './cssConfig';
import {
ABSOLUTE_PUBLIC_PATH,
AUTO_PUBLIC_PATH,
SINGLE_DOT_PATH_SEGMENT,
} from './libCssExtractLoader';
import { getUndoPath } from './utils';

const pluginName = 'LIB_CSS_EXTRACT_PLUGIN';

type Options = Record<string, unknown>;

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

apply(compiler: Rspack.Compiler): void {
// 1. mark and remove the normal css asset
// 2. preserve CSS Modules asset
compiler.hooks.thisCompilation.tap(pluginName, (compilation) => {
compilation.hooks.chunkAsset.tap(pluginName, (_chunk, filename) => {
const asset = compilation.getAsset(filename);
if (!asset) {
return;
}
const needRemove = Boolean(asset.name.match(RSLIB_CSS_ENTRY_FLAG));
if (needRemove) {
compilation.deleteAsset(filename);
}
});
});

/**
* The following code is modified based on
* https://github.com/webpack-contrib/mini-css-extract-plugin/blob/3effaa0319bad5cc1bf0ae760553bf7abcbc35a4/src/index.js#L1597
*
* replace publicPath placeholders of miniCssExtractLoader
*/
compiler.hooks.make.tap(pluginName, (compilation) => {
compilation.hooks.processAssets.tap(pluginName, (assets) => {
const chunkAsset = Object.keys(assets).filter((name) =>
/\.css/.test(name),
);
for (const name of chunkAsset) {
compilation.updateAsset(name, (old) => {
const oldSource = old.source().toString();
const replaceSource = new rspack.sources.ReplaceSource(old);

function replace(searchValue: string, replaceValue: string) {
let start = oldSource.indexOf(searchValue);
while (start !== -1) {
replaceSource.replace(
start,
start + searchValue.length - 1,
replaceValue,
);
start = oldSource.indexOf(searchValue, start + 1);
}
}

replace(ABSOLUTE_PUBLIC_PATH, '');
replace(SINGLE_DOT_PATH_SEGMENT, '.');
const undoPath = getUndoPath(
name,
compilation.outputOptions.path!,
false,
);
replace(AUTO_PUBLIC_PATH, undoPath);

return replaceSource;
});
}
});
});
}
}
export { LibCssExtractPlugin };
32 changes: 0 additions & 32 deletions packages/core/src/css/RemoveCssExtractAssetPlugin.ts

This file was deleted.

10 changes: 2 additions & 8 deletions packages/core/src/css/cssConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type {
RsbuildPlugin,
} from '@rsbuild/core';
import { CSS_EXTENSIONS_PATTERN } from '../constant';
import { RemoveCssExtractAssetPlugin } from './RemoveCssExtractAssetPlugin';
import { LibCssExtractPlugin } from './LibCssExtractPlugin';
const require = createRequire(import.meta.url);

export const RSLIB_CSS_ENTRY_FLAG = '__rslib_css__';
Expand Down Expand Up @@ -138,13 +138,7 @@ const pluginLibCss = (rootDir: string): RsbuildPlugin => ({
if (isUsingCssExtract) {
const cssExtract = CHAIN_ID.PLUGIN.MINI_CSS_EXTRACT;
config.plugins.delete(cssExtract);
config
.plugin(RemoveCssExtractAssetPlugin.name)
.use(RemoveCssExtractAssetPlugin, [
{
include: new RegExp(`^${RSLIB_CSS_ENTRY_FLAG}`),
},
]);
config.plugin(LibCssExtractPlugin.name).use(LibCssExtractPlugin);
}
});
},
Expand Down
47 changes: 44 additions & 3 deletions packages/core/src/css/libCssExtractLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,18 @@
* https://github.com/web-infra-dev/rspack/blob/0a89e433a9f8596a7c6c4326542f168b5982d2da/packages/rspack/src/builtin-plugin/css-extract/loader.ts
* 1. remove hmr/webpack runtime
* 2. add `this.emitFile` to emit css files
* 3. add `import './[name].css';`
* 3. add `import './[name].css';` to js module
*/
import path, { extname } from 'node:path';
import type { Rspack } from '@rsbuild/core';

export const BASE_URI = 'webpack://';
export const MODULE_TYPE = 'css/mini-extract';
export const AUTO_PUBLIC_PATH = '__mini_css_extract_plugin_public_path_auto__';
export const ABSOLUTE_PUBLIC_PATH: string = `${BASE_URI}/mini-css-extract-plugin/`;
export const SINGLE_DOT_PATH_SEGMENT =
'__mini_css_extract_plugin_single_dot_path_segment__';

interface DependencyDescription {
identifier: string;
content: string;
Expand All @@ -20,7 +27,11 @@ interface DependencyDescription {
filepath: string;
}

// https://github.com/web-infra-dev/rspack/blob/c0986d39b7d647682f10fcef5bbade39fd016eca/packages/rspack/src/config/types.ts#L10
type Filename = string | ((pathData: any, assetInfo?: any) => string);

export interface CssExtractRspackLoaderOptions {
publicPath?: string | ((resourcePath: string, context: string) => string);
emit?: boolean;
esModule?: boolean;
layer?: string;
Expand All @@ -29,7 +40,7 @@ export interface CssExtractRspackLoaderOptions {
rootDir?: string;
}

const PLUGIN_NAME = 'LIB_CSS_EXTRACT_LOADER';
const LOADER_NAME = 'LIB_CSS_EXTRACT_LOADER';

function stringifyLocal(value: any) {
return typeof value === 'function' ? value.toString() : JSON.stringify(value);
Expand Down Expand Up @@ -77,6 +88,34 @@ export const pitch: Rspack.LoaderDefinition['pitch'] = function (
const filepath = this.resourcePath;
const rootDir = options.rootDir ?? this.rootContext;

let { publicPath } = this._compilation!.outputOptions;

if (typeof options.publicPath === 'string') {
// eslint-disable-next-line prefer-destructuring
publicPath = options.publicPath;
} else if (typeof options.publicPath === 'function') {
publicPath = options.publicPath(this.resourcePath, this.rootContext);
}

if (publicPath === 'auto') {
publicPath = AUTO_PUBLIC_PATH;
}

let publicPathForExtract: Filename | undefined;

if (typeof publicPath === 'string') {
const isAbsolutePublicPath = /^[a-zA-Z][a-zA-Z\d+\-.]*?:/.test(publicPath);

publicPathForExtract = isAbsolutePublicPath
? publicPath
: `${ABSOLUTE_PUBLIC_PATH}${publicPath.replace(
/\./g,
SINGLE_DOT_PATH_SEGMENT,
)}`;
} else {
publicPathForExtract = publicPath;
}

const handleExports = (
originalExports:
| { default: Record<string, any>; __esModule: true }
Expand Down Expand Up @@ -196,7 +235,7 @@ export const pitch: Rspack.LoaderDefinition['pitch'] = function (
return '';
})();

let resultSource = `// extracted by ${PLUGIN_NAME}`;
let resultSource = `// extracted by ${LOADER_NAME}`;

let importCssFiles = '';

Expand Down Expand Up @@ -249,6 +288,8 @@ export const pitch: Rspack.LoaderDefinition['pitch'] = function (
`${this.resourcePath}.webpack[javascript/auto]!=!!!${request}`,
{
layer: options.layer,
publicPath: publicPathForExtract,
baseUri: `${BASE_URI}/`,
},
(error, exports) => {
if (error) {
Expand Down
45 changes: 45 additions & 0 deletions packages/core/src/css/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* This function is copied from
* https://github.com/webpack-contrib/mini-css-extract-plugin/blob/3effaa0319bad5cc1bf0ae760553bf7abcbc35a4/src/utils.js#L169
* linted by biome
*/
function getUndoPath(
filename: string,
outputPathArg: string,
enforceRelative: boolean,
): string {
let depth = -1;
let append = '';

let outputPath = outputPathArg.replace(/[\\/]$/, '');

for (const part of filename.split(/[/\\]+/)) {
if (part === '..') {
if (depth > -1) {
depth--;
} else {
const i = outputPath.lastIndexOf('/');
const j = outputPath.lastIndexOf('\\');
const pos = i < 0 ? j : j < 0 ? i : Math.max(i, j);

if (pos < 0) {
return `${outputPath}/`;
}

append = `${outputPath.slice(pos + 1)}/${append}`;

outputPath = outputPath.slice(0, pos);
}
} else if (part !== '.') {
depth++;
}
}

return depth > 0
? `${'../'.repeat(depth)}${append}`
: enforceRelative
? `./${append}`
: append;
}

export { getUndoPath };
25 changes: 13 additions & 12 deletions tests/e2e/react-component/index.pw.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,36 +58,37 @@ test('should render example "react-component-bundle" successfully', async ({
await rsbuild.close();
});

test('should render example "react-component-umd" successfully', async ({
test('should render example "react-component-bundle-false" successfully', async ({
page,
}) => {
const umdPath = path.resolve(
getCwdByExample('react-component-umd'),
'./dist/umd/index.js',
);
fs.mkdirSync(path.resolve(__dirname, './public/umd'), { recursive: true });
fs.copyFileSync(umdPath, path.resolve(__dirname, './public/umd/index.js'));

const rsbuild = await dev({
cwd: __dirname,
page,
environment: ['umd'],
environment: ['bundleFalse'],
});

await counterCompShouldWork(page);
await styleShouldWork(page);
await assetShouldWork(page);
await rsbuild.close();
});

test('should render example "react-component-bundle-false" successfully', async ({
test('should render example "react-component-umd" successfully', async ({
page,
}) => {
const umdPath = path.resolve(
getCwdByExample('react-component-umd'),
'./dist/umd/index.js',
);
fs.mkdirSync(path.resolve(__dirname, './public/umd'), { recursive: true });
fs.copyFileSync(umdPath, path.resolve(__dirname, './public/umd/index.js'));

const rsbuild = await dev({
cwd: __dirname,
page,
environment: ['bundleFalse'],
environment: ['umd'],
});

await counterCompShouldWork(page);
await styleShouldWork(page);
await rsbuild.close();
});
3 changes: 0 additions & 3 deletions tests/integration/asset/limit/rslib.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,6 @@ export default defineConfig({
distPath: {
root: './dist/esm/external-bundleless',
},
dataUriLimit: {
svg: 0,
},
},
}),
],
Expand Down
Loading
Loading