diff --git a/.prettierignore b/.prettierignore index 66b89eb96..672b28987 100644 --- a/.prettierignore +++ b/.prettierignore @@ -7,4 +7,7 @@ doc_build **/*.js **/*.ts **/*.jsx -**/*.tsx \ No newline at end of file +**/*.tsx + +# ignore pnpm-lock +pnpm-lock.yaml diff --git a/.vscode/settings.json b/.vscode/settings.json index cdc6c969a..d85bfe508 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -38,5 +38,14 @@ "[typescriptreact]": { "editor.defaultFormatter": "biomejs.biome" }, + "[css]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[sass]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[less]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, "typescript.tsdk": "node_modules/typescript/lib" } diff --git a/biome.json b/biome.json index 3bdc8c582..1e776d755 100644 --- a/biome.json +++ b/biome.json @@ -34,6 +34,11 @@ "enabled": false } }, + "css": { + "formatter": { + "enabled": false + } + }, "linter": { "enabled": true, "ignore": ["./tests/integration/**/*/src/*"], diff --git a/examples/react-component/README.md b/examples/react-component-bundle-false/README.md similarity index 100% rename from examples/react-component/README.md rename to examples/react-component-bundle-false/README.md diff --git a/examples/react-component-bundle-false/package.json b/examples/react-component-bundle-false/package.json new file mode 100644 index 000000000..dc4e7afad --- /dev/null +++ b/examples/react-component-bundle-false/package.json @@ -0,0 +1,20 @@ +{ + "name": "@examples/react-component-bundle-false", + "private": true, + "main": "./dist/cjs/index.js", + "module": "./dist/esm/index.mjs", + "types": "./dist/cjs/index.d.ts", + "scripts": { + "build": "rslib build" + }, + "devDependencies": { + "@rsbuild/plugin-react": "^1.0.2", + "@rsbuild/plugin-sass": "^1.0.1", + "@rslib/core": "workspace:*", + "@types/react": "^18.3.5", + "react": "^18.3.1" + }, + "peerDependencies": { + "react": "*" + } +} diff --git a/examples/react-component-bundle-false/rslib.config.ts b/examples/react-component-bundle-false/rslib.config.ts new file mode 100644 index 000000000..ab676d646 --- /dev/null +++ b/examples/react-component-bundle-false/rslib.config.ts @@ -0,0 +1,39 @@ +import { pluginReact } from '@rsbuild/plugin-react'; +import { pluginSass } from '@rsbuild/plugin-sass'; +import { type LibConfig, defineConfig } from '@rslib/core'; + +const shared: LibConfig = { + bundle: false, + dts: { + bundle: false, + }, +}; + +export default defineConfig({ + source: { + entry: { + index: ['./src/**', '!./src/env.d.ts'], + }, + }, + lib: [ + { + ...shared, + format: 'esm', + output: { + distPath: { + root: './dist/esm', + }, + }, + }, + { + ...shared, + format: 'cjs', + output: { + distPath: { + root: './dist/cjs', + }, + }, + }, + ], + plugins: [pluginReact(), pluginSass()], +}); diff --git a/examples/react-component-bundle-false/src/components/CounterButton/index.module.scss b/examples/react-component-bundle-false/src/components/CounterButton/index.module.scss new file mode 100644 index 000000000..19301eef2 --- /dev/null +++ b/examples/react-component-bundle-false/src/components/CounterButton/index.module.scss @@ -0,0 +1,3 @@ +.button { + background: yellow; +} diff --git a/examples/react-component/src/CounterButton.tsx b/examples/react-component-bundle-false/src/components/CounterButton/index.tsx similarity index 63% rename from examples/react-component/src/CounterButton.tsx rename to examples/react-component-bundle-false/src/components/CounterButton/index.tsx index 7075a25b4..c528a2769 100644 --- a/examples/react-component/src/CounterButton.tsx +++ b/examples/react-component-bundle-false/src/components/CounterButton/index.tsx @@ -1,3 +1,4 @@ +import styles from './index.module.scss'; interface CounterButtonProps { onClick: () => void; label: string; @@ -7,7 +8,7 @@ export const CounterButton: React.FC = ({ onClick, label, }) => ( - ); diff --git a/examples/react-component-bundle-false/src/env.d.ts b/examples/react-component-bundle-false/src/env.d.ts new file mode 100644 index 000000000..0506fbcb4 --- /dev/null +++ b/examples/react-component-bundle-false/src/env.d.ts @@ -0,0 +1,4 @@ +declare module '*.module.scss' { + const classes: { [key: string]: string }; + export default classes; +} diff --git a/examples/react-component-bundle-false/src/index.scss b/examples/react-component-bundle-false/src/index.scss new file mode 100644 index 000000000..2e506a0ac --- /dev/null +++ b/examples/react-component-bundle-false/src/index.scss @@ -0,0 +1,3 @@ +.counter-text { + font-size: 50px; +} diff --git a/examples/react-component/src/index.tsx b/examples/react-component-bundle-false/src/index.tsx similarity index 66% rename from examples/react-component/src/index.tsx rename to examples/react-component-bundle-false/src/index.tsx index 8edca0258..816f1310c 100644 --- a/examples/react-component/src/index.tsx +++ b/examples/react-component-bundle-false/src/index.tsx @@ -1,12 +1,13 @@ -import { CounterButton } from './CounterButton'; +import { CounterButton } from './components/CounterButton/index'; import { useCounter } from './useCounter'; +import './index.scss'; export const Counter: React.FC = () => { const { count, increment, decrement } = useCounter(); return (
-

Counter: {count}

+

Counter: {count}

diff --git a/examples/react-component/src/useCounter.tsx b/examples/react-component-bundle-false/src/useCounter.tsx similarity index 100% rename from examples/react-component/src/useCounter.tsx rename to examples/react-component-bundle-false/src/useCounter.tsx diff --git a/examples/react-component-bundle-false/tsconfig.json b/examples/react-component-bundle-false/tsconfig.json new file mode 100644 index 000000000..78ba7070a --- /dev/null +++ b/examples/react-component-bundle-false/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "allowJs": true, + "baseUrl": ".", + "declaration": true, + "emitDeclarationOnly": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "jsx": "react-jsx", + "lib": ["DOM", "ESNext"], + "moduleResolution": "node", + "resolveJsonModule": true, + "rootDir": "src", + "skipLibCheck": true, + "strict": true + }, + "exclude": ["**/node_modules"], + "include": ["src"] +} diff --git a/examples/react-component-bundle/README.md b/examples/react-component-bundle/README.md new file mode 100644 index 000000000..0bd27a52c --- /dev/null +++ b/examples/react-component-bundle/README.md @@ -0,0 +1,3 @@ +# @examples/react-component + +This example demonstrates how to use Rslib to build a simple React component. diff --git a/examples/react-component/package.json b/examples/react-component-bundle/package.json similarity index 81% rename from examples/react-component/package.json rename to examples/react-component-bundle/package.json index a4ea25237..9cde7b8fd 100644 --- a/examples/react-component/package.json +++ b/examples/react-component-bundle/package.json @@ -1,5 +1,5 @@ { - "name": "@examples/react-component", + "name": "@examples/react-component-bundle", "private": true, "main": "./dist/cjs/index.js", "module": "./dist/esm/index.mjs", @@ -9,6 +9,7 @@ }, "devDependencies": { "@rsbuild/plugin-react": "1.0.3", + "@rsbuild/plugin-sass": "^1.0.1", "@rslib/core": "workspace:*", "@types/react": "^18.3.11", "react": "^18.3.1" diff --git a/examples/react-component/rslib.config.ts b/examples/react-component-bundle/rslib.config.ts similarity index 72% rename from examples/react-component/rslib.config.ts rename to examples/react-component-bundle/rslib.config.ts index 0808d6504..e7cfc9898 100644 --- a/examples/react-component/rslib.config.ts +++ b/examples/react-component-bundle/rslib.config.ts @@ -1,4 +1,5 @@ import { pluginReact } from '@rsbuild/plugin-react'; +import { pluginSass } from '@rsbuild/plugin-sass'; import { defineConfig } from '@rslib/core'; const shared = { @@ -15,6 +16,8 @@ export default defineConfig({ output: { distPath: { root: './dist/esm', + css: '.', + cssAsync: '.', }, }, }, @@ -24,9 +27,11 @@ export default defineConfig({ output: { distPath: { root: './dist/cjs', + css: '.', + cssAsync: '.', }, }, }, ], - plugins: [pluginReact()], + plugins: [pluginReact(), pluginSass()], }); diff --git a/examples/react-component-bundle/src/components/CounterButton/index.module.scss b/examples/react-component-bundle/src/components/CounterButton/index.module.scss new file mode 100644 index 000000000..19301eef2 --- /dev/null +++ b/examples/react-component-bundle/src/components/CounterButton/index.module.scss @@ -0,0 +1,3 @@ +.button { + background: yellow; +} diff --git a/examples/react-component-bundle/src/components/CounterButton/index.tsx b/examples/react-component-bundle/src/components/CounterButton/index.tsx new file mode 100644 index 000000000..c528a2769 --- /dev/null +++ b/examples/react-component-bundle/src/components/CounterButton/index.tsx @@ -0,0 +1,14 @@ +import styles from './index.module.scss'; +interface CounterButtonProps { + onClick: () => void; + label: string; +} + +export const CounterButton: React.FC = ({ + onClick, + label, +}) => ( + +); diff --git a/examples/react-component-bundle/src/env.d.ts b/examples/react-component-bundle/src/env.d.ts new file mode 100644 index 000000000..0506fbcb4 --- /dev/null +++ b/examples/react-component-bundle/src/env.d.ts @@ -0,0 +1,4 @@ +declare module '*.module.scss' { + const classes: { [key: string]: string }; + export default classes; +} diff --git a/examples/react-component-bundle/src/index.scss b/examples/react-component-bundle/src/index.scss new file mode 100644 index 000000000..2e506a0ac --- /dev/null +++ b/examples/react-component-bundle/src/index.scss @@ -0,0 +1,3 @@ +.counter-text { + font-size: 50px; +} diff --git a/examples/react-component-bundle/src/index.tsx b/examples/react-component-bundle/src/index.tsx new file mode 100644 index 000000000..816f1310c --- /dev/null +++ b/examples/react-component-bundle/src/index.tsx @@ -0,0 +1,15 @@ +import { CounterButton } from './components/CounterButton/index'; +import { useCounter } from './useCounter'; +import './index.scss'; + +export const Counter: React.FC = () => { + const { count, increment, decrement } = useCounter(); + + return ( +
+

Counter: {count}

+ + +
+ ); +}; diff --git a/examples/react-component-bundle/src/useCounter.tsx b/examples/react-component-bundle/src/useCounter.tsx new file mode 100644 index 000000000..885dbdfe0 --- /dev/null +++ b/examples/react-component-bundle/src/useCounter.tsx @@ -0,0 +1,10 @@ +import { useState } from 'react'; + +export const useCounter = (initialValue = 0) => { + const [count, setCount] = useState(initialValue); + + const increment = () => setCount((prev) => prev + 1); + const decrement = () => setCount((prev) => prev - 1); + + return { count, increment, decrement }; +}; diff --git a/examples/react-component-bundle/tsconfig.json b/examples/react-component-bundle/tsconfig.json new file mode 100644 index 000000000..78ba7070a --- /dev/null +++ b/examples/react-component-bundle/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "allowJs": true, + "baseUrl": ".", + "declaration": true, + "emitDeclarationOnly": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "jsx": "react-jsx", + "lib": ["DOM", "ESNext"], + "moduleResolution": "node", + "resolveJsonModule": true, + "rootDir": "src", + "skipLibCheck": true, + "strict": true + }, + "exclude": ["**/node_modules"], + "include": ["src"] +} diff --git a/examples/react-component/tsconfig.json b/examples/react-component/tsconfig.json deleted file mode 100644 index 19bcd30dd..000000000 --- a/examples/react-component/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "compilerOptions": { - "jsx": "react-jsx", - "strict": true, - "skipLibCheck": true - }, - "include": ["src/**/*"] -} diff --git a/packages/core/package.json b/packages/core/package.json index 716c39724..682ad8670 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -42,6 +42,7 @@ }, "devDependencies": { "@rslib/tsconfig": "workspace:*", + "@rspack/core": "1.0.8", "@types/fs-extra": "^11.0.4", "commander": "^12.1.0", "fast-glob": "^3.3.2", diff --git a/packages/core/rslib.config.ts b/packages/core/rslib.config.ts index 5d5658483..c4daef171 100644 --- a/packages/core/rslib.config.ts +++ b/packages/core/rslib.config.ts @@ -12,6 +12,10 @@ export default defineConfig({ }, ], source: { + entry: { + index: './src/index.ts', + libCssExtractLoader: './src/css/libCssExtractLoader.ts', + }, define: { RSLIB_VERSION: JSON.stringify(require('./package.json').version), }, diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index 1d3f355fd..d3855029c 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -17,6 +17,13 @@ import { JS_EXTENSIONS_PATTERN, SWC_HELPERS, } from './constant'; +import { + type CssLoaderOptionsAuto, + RSLIB_CSS_ENTRY_FLAG, + composeCssConfig, + cssExternalHandler, + isCssGlobalFile, +} from './css/cssConfig'; import { pluginCjsShim } from './plugins/cjsShim'; import type { AutoExternal, @@ -24,6 +31,7 @@ import type { Format, LibConfig, PkgJson, + Redirect, RsbuildConfigOutputTarget, RslibConfig, RslibConfigAsyncFn, @@ -627,16 +635,20 @@ const composeEntryConfig = async ( entries: NonNullable['entry'], bundle: LibConfig['bundle'], root: string, -): Promise => { + cssModulesAuto: CssLoaderOptionsAuto, +): Promise<{ entryConfig: RsbuildConfig; lcp: string | null }> => { if (!entries) { - return {}; + return { entryConfig: {}, lcp: null }; } if (bundle !== false) { return { - source: { - entry: entries, + entryConfig: { + source: { + entry: entries, + }, }, + lcp: null, }; } @@ -680,27 +692,54 @@ const composeEntryConfig = async ( // Using the longest common path of all non-declaration input files by default. const outBase = lcp === null ? root : lcp; - for (const file of resolvedEntryFiles) { + function getEntryName(file: string) { const { dir, name } = path.parse(path.relative(outBase, file)); // Entry filename contains nested path to preserve source directory structure. const entryFileName = path.join(dir, name); - resolvedEntries[entryFileName] = file; + + // 1. we mark the global css files (which will generate empty js chunk in cssExtract), and deleteAsset in RemoveCssExtractAssetPlugin + // 2. avoid the same name e.g: `index.ts` and `index.css` + if (isCssGlobalFile(file, cssModulesAuto)) { + return `${RSLIB_CSS_ENTRY_FLAG}/${entryFileName}`; + } + + return entryFileName; + } + + for (const file of resolvedEntryFiles) { + const entryName = getEntryName(file); + if (resolvedEntries[entryName]) { + logger.warn( + `duplicate entry: ${entryName}, this may lead to the incorrect output, please rename the file`, + ); + } + resolvedEntries[entryName] = file; } } - return { + const lcp = await calcLongestCommonPath(Object.values(resolvedEntries)); + const entryConfig: RsbuildConfig = { source: { entry: resolvedEntries, }, }; + + return { + entryConfig, + lcp, + }; }; const composeBundleConfig = ( jsExtension: string, + redirect: Redirect, + cssModulesAuto: CssLoaderOptionsAuto, bundle = true, ): RsbuildConfig => { if (bundle) return {}; + const isStyleRedirect = redirect.style ?? true; + return { output: { externals: [ @@ -714,9 +753,24 @@ const composeBundleConfig = ( // If data.request already have an extension, we replace it with new extension // This may result in a change in semantics, // user should use copy to keep origin file or use another separate entry to deal this - let request = data.request; + let request: string = data.request; + + const cssExternal = cssExternalHandler( + request, + callback, + jsExtension, + cssModulesAuto, + isStyleRedirect, + ); + + if (cssExternal !== false) { + return cssExternal; + } + if (request[0] === '.') { - if (extname(request)) { + const ext = extname(request); + + if (ext) { if (JS_EXTENSIONS_PATTERN.test(request)) { request = request.replace(/\.[^.]+$/, jsExtension); } else { @@ -850,6 +904,7 @@ async function composeLibRsbuildConfig(config: LibConfig, configPath: string) { rootPath, config.source?.tsconfigPath, ); + const cssModulesAuto = config.output?.cssModules?.auto ?? true; const { format, @@ -858,6 +913,7 @@ async function composeLibRsbuildConfig(config: LibConfig, configPath: string) { autoExtension = true, autoExternal = true, externalHelpers = false, + redirect = {}, } = config; const formatConfig = composeFormatConfig(format!); const externalHelpersConfig = composeExternalHelpersConfig( @@ -873,7 +929,12 @@ async function composeLibRsbuildConfig(config: LibConfig, configPath: string) { jsExtension, dtsExtension, } = composeAutoExtensionConfig(config, autoExtension, pkgJson); - const bundleConfig = composeBundleConfig(jsExtension, config.bundle); + const bundleConfig = composeBundleConfig( + jsExtension, + redirect, + cssModulesAuto, + config.bundle, + ); const targetConfig = composeTargetConfig(config.output?.target); const syntaxConfig = composeSyntaxConfig( config?.syntax, @@ -884,11 +945,13 @@ async function composeLibRsbuildConfig(config: LibConfig, configPath: string) { pkgJson, userExternals: config.output?.externals, }); - const entryConfig = await composeEntryConfig( + const { entryConfig, lcp } = await composeEntryConfig( config.source?.entry, config.bundle, dirname(configPath), + cssModulesAuto, ); + const cssConfig = composeCssConfig(lcp, config.bundle); const dtsConfig = await composeDtsConfig(config, dtsExtension); const externalsWarnConfig = composeExternalsWarnConfig( format!, @@ -914,6 +977,7 @@ async function composeLibRsbuildConfig(config: LibConfig, configPath: string) { bundleConfig, targetConfig, entryConfig, + cssConfig, minifyConfig, dtsConfig, bannerFooterConfig, @@ -976,6 +1040,7 @@ export async function composeCreateRsbuildConfig( 'format', 'autoExtension', 'autoExternal', + 'redirect', 'syntax', 'externalHelpers', 'banner', diff --git a/packages/core/src/constant.ts b/packages/core/src/constant.ts index 1197d265c..8a87a577e 100644 --- a/packages/core/src/constant.ts +++ b/packages/core/src/constant.ts @@ -42,6 +42,10 @@ export const JS_EXTENSIONS_PATTERN: RegExp = new RegExp( `\\.(${JS_EXTENSIONS.join('|')})$`, ); +export const CSS_EXTENSIONS_PATTERN: RegExp = new RegExp( + `\\.(${CSS_EXTENSIONS.join('|')})$`, +); + export const ENTRY_EXTENSIONS_PATTERN: RegExp = new RegExp( `\\.(${ENTRY_EXTENSIONS.join('|')})$`, ); diff --git a/packages/core/src/css/RemoveCssExtractAssetPlugin.ts b/packages/core/src/css/RemoveCssExtractAssetPlugin.ts new file mode 100644 index 000000000..5b57a24f9 --- /dev/null +++ b/packages/core/src/css/RemoveCssExtractAssetPlugin.ts @@ -0,0 +1,31 @@ +import type { Compiler, RspackPluginInstance } from '@rspack/core'; + +const pluginName = 'REMOVE_CSS_EXTRACT_ASSET_PLUGIN'; + +type Options = { + include: RegExp; +}; +class RemoveCssExtractAssetPlugin implements RspackPluginInstance { + readonly name: string = pluginName; + options: Options; + constructor(options: Options) { + this.options = options; + } + + apply(compiler: Compiler): void { + const include = this.options.include; + 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(include)); + if (needRemove) { + compilation.deleteAsset(filename); + } + }); + }); + } +} +export { RemoveCssExtractAssetPlugin }; diff --git a/packages/core/src/css/cssConfig.ts b/packages/core/src/css/cssConfig.ts new file mode 100644 index 000000000..9ec6f7fa6 --- /dev/null +++ b/packages/core/src/css/cssConfig.ts @@ -0,0 +1,171 @@ +import { createRequire } from 'node:module'; +import path from 'node:path'; +import type { + CSSLoaderOptions, + RsbuildConfig, + RsbuildPlugin, +} from '@rsbuild/core'; +import { CSS_EXTENSIONS_PATTERN } from '../constant'; +import { RemoveCssExtractAssetPlugin } from './RemoveCssExtractAssetPlugin'; +const require = createRequire(import.meta.url); + +export const RSLIB_CSS_ENTRY_FLAG = '__rslib_css__'; + +// https://rsbuild.dev/config/output/css-modules#cssmodulesauto +export type CssLoaderOptionsAuto = CSSLoaderOptions['modules'] extends infer T + ? T extends { auto?: any } + ? T['auto'] + : never + : never; + +export function isCssFile(filepath: string): boolean { + return CSS_EXTENSIONS_PATTERN.test(filepath); +} + +const CSS_MODULE_REG = /\.module\.\w+$/i; + +/** + * This function is modified based on + * https://github.com/web-infra-dev/rspack/blob/7b80a45a1c58de7bc506dbb107fad6fda37d2a1f/packages/rspack/src/loader-runner/index.ts#L903 + */ +const PATH_QUERY_FRAGMENT_REGEXP = + /^((?:\u200b.|[^?#\u200b])*)(\?(?:\u200b.|[^#\u200b])*)?(#.*)?$/; +export function parsePathQueryFragment(str: string): { + path: string; + query: string; + fragment: string; +} { + const match = PATH_QUERY_FRAGMENT_REGEXP.exec(str); + return { + path: match?.[1]?.replace(/\u200b(.)/g, '$1') || '', + query: match?.[2] ? match[2].replace(/\u200b(.)/g, '$1') : '', + fragment: match?.[3] || '', + }; +} + +export function isCssModulesFile( + filepath: string, + auto: CssLoaderOptionsAuto, +): boolean { + const filename = path.basename(filepath); + if (auto === true) { + return CSS_MODULE_REG.test(filename); + } + + if (auto instanceof RegExp) { + return auto.test(filepath); + } + + if (typeof auto === 'function') { + const { path, query, fragment } = parsePathQueryFragment(filepath); + // this is a mock for loader + return auto(path, query, fragment); + } + + return false; +} + +export function isCssGlobalFile( + filepath: string, + auto: CssLoaderOptionsAuto, +): boolean { + const isCss = isCssFile(filepath); + if (!isCss) { + return false; + } + const isCssModules = isCssModulesFile(filepath, auto); + return !isCssModules; +} + +type ExternalCallback = (arg0?: null, arg1?: string) => void; + +export function cssExternalHandler( + request: string, + callback: ExternalCallback, + jsExtension: string, + auto: CssLoaderOptionsAuto, + isStyleRedirect: boolean, +): void | false { + const isCssModulesRequest = isCssModulesFile(request, auto); + + // cssExtract would execute the file handled by css-loader, so we cannot external the "helper import" from css-loader + // do not external @rsbuild/core/compiled/css-loader/noSourceMaps.js, sourceMaps.js, api.mjs etc. + if (/compiled\/css-loader\//.test(request)) { + return callback(); + } + + // 1. css modules: import './CounterButton.module.scss' -> import './CounterButton.module.mjs' + // 2. css global: import './CounterButton.scss' -> import './CounterButton.css' + if (request[0] === '.' && isCssFile(request)) { + // preserve import './CounterButton.module.scss' + if (!isStyleRedirect) { + return callback(null, request); + } + if (isCssModulesRequest) { + return callback(null, request.replace(/\.[^.]+$/, jsExtension)); + } + return callback(null, request.replace(/\.[^.]+$/, '.css')); + } + + return false; +} + +const pluginName = 'rsbuild:lib-css'; + +const pluginLibCss = (rootDir: string): RsbuildPlugin => ({ + name: pluginName, + setup(api) { + api.modifyBundlerChain((config, { CHAIN_ID }) => { + let isUsingCssExtract = false; + for (const ruleId of [ + CHAIN_ID.RULE.CSS, + CHAIN_ID.RULE.SASS, + CHAIN_ID.RULE.LESS, + CHAIN_ID.RULE.STYLUS, + ]) { + const rule = config.module.rule(ruleId); + if (rule.uses.has(CHAIN_ID.USE.MINI_CSS_EXTRACT)) { + isUsingCssExtract = true; + rule + .use(CHAIN_ID.USE.MINI_CSS_EXTRACT) + .loader(require.resolve('./libCssExtractLoader.js')) + .options({ + rootDir, + }); + } + } + + 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}`), + }, + ]); + } + }); + }, +}); + +export const composeCssConfig = ( + rootDir: string | null, + bundle = true, +): RsbuildConfig => { + if (bundle || rootDir === null) { + return {}; + } + + return { + plugins: [pluginLibCss(rootDir)], + tools: { + cssLoader: { + // Otherwise, external variables will be executed by css-extract and cause an error. + // e.g: `@import url('./a.css');` + import: false, + }, + }, + }; +}; diff --git a/packages/core/src/css/libCssExtractLoader.ts b/packages/core/src/css/libCssExtractLoader.ts new file mode 100644 index 000000000..352ebdc3c --- /dev/null +++ b/packages/core/src/css/libCssExtractLoader.ts @@ -0,0 +1,261 @@ +/** + * The following code is modified based on + * 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';` + */ +import path, { extname } from 'node:path'; +import type { LoaderDefinition } from '@rspack/core'; + +interface DependencyDescription { + identifier: string; + content: string; + context: string; + media?: string; + supports?: string; + layer?: string; + sourceMap?: string; + identifierIndex: number; + filepath: string; +} + +export interface CssExtractRspackLoaderOptions { + emit?: boolean; + esModule?: boolean; + layer?: string; + defaultExport?: boolean; + + rootDir?: string; +} + +const PLUGIN_NAME = 'LIB_CSS_EXTRACT_LOADER'; + +function stringifyLocal(value: any) { + return typeof value === 'function' ? value.toString() : JSON.stringify(value); +} + +const loader: LoaderDefinition = function loader(content) { + if ( + this._compiler?.options?.experiments?.css && + this._module && + (this._module.type === 'css' || + this._module.type === 'css/auto' || + this._module.type === 'css/global' || + this._module.type === 'css/module') + ) { + return content; + } + return; +}; + +export const pitch: LoaderDefinition['pitch'] = function (request, _, _data) { + if ( + this._compiler?.options?.experiments?.css && + this._module && + (this._module.type === 'css' || + this._module.type === 'css/auto' || + this._module.type === 'css/global' || + this._module.type === 'css/module') + ) { + const e = new Error( + `use type 'css' and \`CssExtractRspackPlugin\` together, please set \`experiments.css\` to \`false\` or set \`{ type: "javascript/auto" }\` for rules with \`CssExtractRspackPlugin\` in your rspack config (now \`CssExtractRspackPlugin\` does nothing).`, + ); + e.stack = undefined; + this.emitWarning(e); + + return; + } + + const options = this.getOptions() as CssExtractRspackLoaderOptions; + const emit = typeof options.emit !== 'undefined' ? options.emit : true; + const callback = this.async(); + const filepath = this.resourcePath; + const rootDir = options.rootDir ?? this.rootContext; + + const handleExports = ( + originalExports: + | { default: Record; __esModule: true } + | Record, + ) => { + let locals: Record | undefined; + let namedExport: boolean; + + const esModule = + typeof options.esModule !== 'undefined' ? options.esModule : true; + let dependencies: DependencyDescription[] = []; + + try { + // eslint-disable-next-line no-underscore-dangle + const exports = originalExports.__esModule + ? originalExports.default + : originalExports; + + namedExport = + // eslint-disable-next-line no-underscore-dangle + originalExports.__esModule && + (!originalExports.default || !('locals' in originalExports.default)); + + if (namedExport) { + for (const key of Object.keys(originalExports)) { + if (key !== 'default') { + if (!locals) { + locals = {}; + } + + locals[key] = (originalExports as Record)[key]!; + } + } + } else { + locals = exports?.locals; + } + + if (Array.isArray(exports) && emit) { + const identifierCountMap = new Map(); + + dependencies = exports + .map(([id, content, media, sourceMap, supports, layer]) => { + const identifier = id; + const context = this.rootContext; + + const count = identifierCountMap.get(identifier) || 0; + + identifierCountMap.set(identifier, count + 1); + + return { + identifier, + context, + content, + media, + supports, + layer, + identifierIndex: count, + sourceMap: sourceMap + ? JSON.stringify(sourceMap) + : // eslint-disable-next-line no-undefined + undefined, + filepath, + }; + }) + .filter((item) => item !== null) as DependencyDescription[]; + } + } catch (e) { + callback(e as Error); + + return; + } + + const result = (function makeResult() { + if (locals) { + if (namedExport) { + const identifiers = Array.from( + (function* generateIdentifiers() { + let identifierId = 0; + + for (const key of Object.keys(locals)) { + identifierId += 1; + + yield [`_${identifierId.toString(16)}`, key]; + } + })(), + ); + + const localsString = identifiers + .map( + ([id, key]) => + `\nvar ${id} = ${stringifyLocal(locals![key as string])};`, + ) + .join(''); + const exportsString = `export { ${identifiers + .map(([id, key]) => `${id} as ${JSON.stringify(key)}`) + .join(', ')} }`; + + const defaultExport = + typeof options.defaultExport !== 'undefined' + ? options.defaultExport + : false; + + return defaultExport + ? `${localsString}\n${exportsString}\nexport default { ${identifiers + .map(([id, key]) => `${JSON.stringify(key)}: ${id}`) + .join(', ')} }\n` + : `${localsString}\n${exportsString}\n`; + } + + return `\n${ + esModule ? 'export default' : 'module.exports = ' + } ${JSON.stringify(locals)};`; + } + if (esModule) { + return '\nexport {};'; + } + return ''; + })(); + + let resultSource = `// extracted by ${PLUGIN_NAME}`; + + let importCssFiles = ''; + + function getRelativePath(from: string, to: string) { + let relativePath = path.relative(from, to); + + if ( + !relativePath.startsWith('./') && + !relativePath.startsWith('../') && + !path.isAbsolute(relativePath) + ) { + relativePath = `./${relativePath}`; + } + + return relativePath; + } + + const m = new Map(); + + for (const { content, filepath } of dependencies) { + let distFilepath = getRelativePath(rootDir, filepath); + const ext = extname(distFilepath); + if (ext !== 'css') { + distFilepath = distFilepath.replace(ext, '.css'); + } + distFilepath = distFilepath.replace(/\.module\.css/, '_module.css'); + + const cssFilename = path.basename(distFilepath); + if (content.trim()) { + m.get(distFilepath) + ? m.set(distFilepath, `${m.get(distFilepath) + content}\n`) + : m.set(distFilepath, `${content}\n`); + + importCssFiles += '\n'; + importCssFiles += `import "./${cssFilename}"`; + } + } + for (const [distFilepath, content] of m.entries()) { + this.emitFile(distFilepath, content); + } + + resultSource += importCssFiles; + + resultSource += result; + + callback(null, resultSource, undefined); + }; + + this.importModule( + `${this.resourcePath}.webpack[javascript/auto]!=!!!${request}`, + { + layer: options.layer, + }, + (error, exports) => { + if (error) { + callback(error); + + return; + } + + handleExports(exports); + }, + ); +}; + +export default loader; diff --git a/packages/core/src/types/config/index.ts b/packages/core/src/types/config/index.ts index 24c452e8e..2992aa673 100644 --- a/packages/core/src/types/config/index.ts +++ b/packages/core/src/types/config/index.ts @@ -48,11 +48,20 @@ export type BannerAndFooter = { dts?: string; }; +export type Redirect = { + // TODO: support other redirects + // alias?: boolean; + style?: boolean; + // asset?: boolean; + // autoExtension?: boolean; +}; + export interface LibConfig extends RsbuildConfig { bundle?: boolean; format?: Format; autoExtension?: boolean; autoExternal?: AutoExternal; + redirect?: Redirect; /** Support esX and browserslist query */ syntax?: Syntax; externalHelpers?: boolean; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b8b37552a..a40344daa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -72,11 +72,14 @@ importers: specifier: ^5.6.2 version: 5.6.2 - examples/react-component: + examples/react-component-bundle: devDependencies: '@rsbuild/plugin-react': specifier: 1.0.3 version: 1.0.3(@rsbuild/core@1.0.10) + '@rsbuild/plugin-sass': + specifier: ^1.0.1 + version: 1.0.2(@rsbuild/core@1.0.10) '@rslib/core': specifier: workspace:* version: link:../../packages/core @@ -87,6 +90,24 @@ importers: specifier: ^18.3.1 version: 18.3.1 + examples/react-component-bundle-false: + devDependencies: + '@rsbuild/plugin-react': + specifier: ^1.0.2 + version: 1.0.3(@rsbuild/core@1.0.10) + '@rsbuild/plugin-sass': + specifier: ^1.0.1 + version: 1.0.2(@rsbuild/core@1.0.10) + '@rslib/core': + specifier: workspace:* + version: link:../../packages/core + '@types/react': + specifier: ^18.3.5 + version: 18.3.11 + react: + specifier: ^18.3.1 + version: 18.3.1 + packages/core: dependencies: '@microsoft/api-extractor': @@ -102,6 +123,9 @@ importers: '@rslib/tsconfig': specifier: workspace:* version: link:../../scripts/tsconfig + '@rspack/core': + specifier: npm:@rspack/core-canary@1.0.9-canary-0cad17d5-20240929151646 + version: '@rspack/core-canary@1.0.9-canary-0cad17d5-20240929151646(@swc/helpers@0.5.13)' '@types/fs-extra': specifier: ^11.0.4 version: 11.0.4 @@ -193,9 +217,6 @@ importers: tests: dependencies: - '@examples/react-component': - specifier: workspace:* - version: link:../examples/react-component react: specifier: ^18.3.1 version: 18.3.1 @@ -212,8 +233,14 @@ importers: '@rsbuild/core': specifier: 1.0.7 version: 1.0.7 + '@rsbuild/plugin-less': + specifier: ^1.0.1 + version: 1.0.1(@rsbuild/core@1.0.7) '@rsbuild/plugin-react': - specifier: 1.0.2 + specifier: ^1.0.2 + version: 1.0.3(@rsbuild/core@1.0.7) + '@rsbuild/plugin-sass': + specifier: ^1.0.1 version: 1.0.2(@rsbuild/core@1.0.7) '@rslib/core': specifier: workspace:* @@ -249,7 +276,14 @@ importers: specifier: workspace:* version: link:scripts - tests/e2e/react-component: {} + tests/e2e/react-component: + dependencies: + '@examples/react-component-bundle': + specifier: workspace:* + version: link:../../../examples/react-component-bundle + '@examples/react-component-bundle-false': + specifier: workspace:* + version: link:../../../examples/react-component-bundle-false tests/integration/alias: {} @@ -349,10 +383,10 @@ importers: version: 18.3.1 devDependencies: '@rsbuild/plugin-react': - specifier: 1.0.2 - version: 1.0.2(@rsbuild/core@1.0.10) + specifier: ^1.0.2 + version: 1.0.3(@rsbuild/core@1.0.10) '@rsbuild/plugin-svgr': - specifier: 1.0.2 + specifier: ^1.0.2 version: 1.0.2(@rsbuild/core@1.0.10)(typescript@5.6.2) tests/integration/cli: {} @@ -455,6 +489,48 @@ importers: tests/integration/sourcemap/inline: {} + tests/integration/style/css-modules/bundle: {} + + tests/integration/style/css-modules/bundle-false: {} + + tests/integration/style/css-modules/bundle-false-auto: {} + + tests/integration/style/css/bundle: {} + + tests/integration/style/css/bundle-false: {} + + tests/integration/style/less/bundle: {} + + tests/integration/style/less/bundle-false: {} + + tests/integration/style/less/bundle-import: {} + + tests/integration/style/lightningcss/bundle: {} + + tests/integration/style/lightningcss/bundle-false: {} + + tests/integration/style/postcss/bundle: + devDependencies: + postcss-alias: + specifier: 2.0.0 + version: 2.0.0 + + tests/integration/style/postcss/bundle-false: + devDependencies: + postcss-alias: + specifier: 2.0.0 + version: 2.0.0 + + tests/integration/style/sass/bundle: {} + + tests/integration/style/sass/bundle-false: {} + + tests/integration/style/tailwindcss/bundle: + devDependencies: + tailwindcss: + specifier: ^3.4.11 + version: 3.4.13 + tests/integration/syntax/config: {} tests/integration/syntax/default: {} @@ -517,6 +593,10 @@ importers: packages: + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + '@ampproject/remapping@2.3.0': resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} @@ -872,6 +952,10 @@ packages: cpu: [x64] os: [win32] + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + '@jest/schemas@29.6.3': resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -1043,6 +1127,10 @@ packages: cpu: [x64] os: [win32] + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + '@pkgr/core@0.1.1': resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} @@ -1724,10 +1812,20 @@ packages: resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} engines: {node: '>=10'} + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -1838,6 +1936,10 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} + camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + camelcase@6.3.0: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} @@ -1976,6 +2078,10 @@ packages: commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + commander@6.2.1: resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} engines: {node: '>= 6'} @@ -2059,6 +2165,11 @@ packages: resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} engines: {node: '>= 6'} + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + csso@5.0.5: resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} @@ -2146,6 +2257,9 @@ packages: devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + diff-sequences@29.6.3: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2158,6 +2272,9 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + dom-helpers@5.2.1: resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} @@ -2195,6 +2312,9 @@ packages: resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} engines: {node: '>=12'} + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + edit-json-file@1.8.0: resolution: {integrity: sha512-IBOpbe2aQufNl5oZ4jsr2AmNVUy5bO7jS5hk0cCyWhOLdH59Xv41B3XQObE/JB89Ae5qDY9hVsq13/hgGhFBZg==} @@ -2210,6 +2330,9 @@ packages: emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + emojis-list@3.0.0: resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==} engines: {node: '>= 4'} @@ -2413,6 +2536,10 @@ packages: debug: optional: true + foreground-child@3.3.0: + resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} + engines: {node: '>=14'} + form-data@4.0.0: resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} engines: {node: '>= 6'} @@ -2515,9 +2642,17 @@ packages: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + glob-to-regexp@0.4.1: resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} @@ -2831,6 +2966,9 @@ packages: iterate-object@1.3.4: resolution: {integrity: sha512-4dG1D1x/7g8PwHS9aK6QV5V94+ZvyP4+d19qDv43EzImmrndysIl4prmJ1hWWIGCqrZHyaHBm6BSEWHOLnpoNw==} + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + jest-diff@29.7.0: resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2843,6 +2981,10 @@ packages: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} + jiti@1.21.6: + resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} + hasBin: true + jju@1.4.0: resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} @@ -2896,6 +3038,14 @@ packages: leac@0.6.0: resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==} + lilconfig@2.1.0: + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} + engines: {node: '>=10'} + + lilconfig@3.1.2: + resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==} + engines: {node: '>=14'} + lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -2955,6 +3105,9 @@ packages: lowlight@1.20.0: resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==} + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@4.1.5: resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} @@ -3194,9 +3347,17 @@ packages: resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} engines: {node: '>=16 || 14 >=14.17'} + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} @@ -3207,6 +3368,9 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + nano-staged@0.8.0: resolution: {integrity: sha512-QSEqPGTCJbkHU2yLvfY6huqYPjdBrOaTMKatO1F8nCSrkQGXeKwtCiCnsdxnuMhbg3DTVywKaeWLGCE5oJpq0g==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -3278,6 +3442,10 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} + object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + object-inspect@1.13.2: resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} engines: {node: '>= 0.4'} @@ -3344,6 +3512,9 @@ packages: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + package-manager-detector@0.2.0: resolution: {integrity: sha512-E385OSk9qDcXhcM9LNSe4sdhx8a9mAPrZ4sMLW+tmxl5ZuGtPUcdFu+MPP2jbgiWAZ6Pfe5soGFMd+0Db5Vrog==} @@ -3389,6 +3560,10 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + path-serializer@0.0.6: resolution: {integrity: sha512-kBhnac4AnaKORPpadUFKsxdTOMDF+vNuYyZcmbmeWnjogEUIjdOgOMpKxdb8acv6i8ldVthtDV35EOXIt1DbJw==} @@ -3419,10 +3594,18 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + pify@4.0.1: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} + pirates@4.0.6: + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + engines: {node: '>= 6'} + playwright-core@1.47.2: resolution: {integrity: sha512-3JvMfF+9LJfe16l7AbSmU555PaTl2tPyQsVInqm3id16pdDfvZ8TTZ/pyzmkbDrZTQefyzU7AIHlZqQnxpqHVQ==} engines: {node: '>=18'} @@ -3433,6 +3616,50 @@ packages: engines: {node: '>=18'} hasBin: true + postcss-alias@2.0.0: + resolution: {integrity: sha512-SzsjZdGaqVRql33315QLrhnNpNvvH2skp0hXpjzz+nYmaSwXSUjk4JADJRi7zUkwYyCXj41fy1go5MR3S9TDyg==} + + postcss-import@15.1.0: + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: ^8.0.0 + + postcss-js@4.0.1: + resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + + postcss-load-config@4.0.2: + resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} + engines: {node: '>= 14'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + + postcss-nested@6.2.0: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@6.0.23: + resolution: {integrity: sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==} + engines: {node: '>=4.0.0'} + postcss@8.4.47: resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==} engines: {node: ^10 || ^12 || >=14} @@ -3584,6 +3811,9 @@ packages: resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} + read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + read-yaml-file@1.1.0: resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} engines: {node: '>=6'} @@ -4022,6 +4252,10 @@ packages: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + string-width@7.2.0: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} @@ -4059,6 +4293,11 @@ packages: style-to-object@0.4.4: resolution: {integrity: sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==} + sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -4091,6 +4330,11 @@ packages: resolution: {integrity: sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA==} engines: {node: '>=10.0.0'} + tailwindcss@3.4.13: + resolution: {integrity: sha512-KqjHOJKogOUt5Bs752ykCeiwvi0fKVkr5oqsFNt/8px/tA8scFPIlkygsf6jXrfCqGHz7VflA6+yytWuM+XhFw==} + engines: {node: '>=14.0.0'} + hasBin: true + tapable@2.2.1: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} @@ -4124,6 +4368,13 @@ packages: engines: {node: '>=10'} hasBin: true + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + thingies@1.21.0: resolution: {integrity: sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==} engines: {node: '>=10.18'} @@ -4183,6 +4434,9 @@ packages: trough@2.2.0: resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + tsconfck@3.1.3: resolution: {integrity: sha512-ulNZP1SVpRDesxeMLON/LtWM8HIgAJEIVpVVhBM6gsmvQ8+Rh+ZG7FWGvHh7Ah3pRABwVJWklWCr/BTZSv0xnQ==} engines: {node: ^18 || >=20} @@ -4439,6 +4693,10 @@ packages: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -4463,6 +4721,11 @@ packages: resolution: {integrity: sha512-ULGbghCLsN8Hs8vfExlqrJIe8Hl2TUjD7/zsIGMP8U+dgRXEsDXk4yydxeZJgdGiimP1XB7zhmhOB4/HyfqOyQ==} hasBin: true + yaml@2.5.1: + resolution: {integrity: sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==} + engines: {node: '>= 14'} + hasBin: true + yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -4480,6 +4743,8 @@ packages: snapshots: + '@alloc/quick-lru@5.2.0': {} + '@ampproject/remapping@2.3.0': dependencies: '@jridgewell/gen-mapping': 0.3.5 @@ -4904,6 +5169,15 @@ snapshots: '@esbuild/win32-x64@0.21.5': optional: true + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + '@jest/schemas@29.6.3': dependencies: '@sinclair/typebox': 0.27.8 @@ -5111,6 +5385,9 @@ snapshots: '@nx/nx-win32-x64-msvc@20.0.0': optional: true + '@pkgjs/parseargs@0.11.0': + optional: true + '@pkgr/core@0.1.1': {} '@playwright/test@1.47.2': @@ -5192,21 +5469,27 @@ snapshots: deepmerge: 4.3.1 reduce-configs: 1.0.0 + '@rsbuild/plugin-less@1.0.1(@rsbuild/core@1.0.7)': + dependencies: + '@rsbuild/core': 1.0.7 + deepmerge: 4.3.1 + reduce-configs: 1.0.0 + '@rsbuild/plugin-react@1.0.2(@rsbuild/core@1.0.10)': dependencies: '@rsbuild/core': 1.0.10 '@rspack/plugin-react-refresh': 1.0.0(react-refresh@0.14.2) react-refresh: 0.14.2 - '@rsbuild/plugin-react@1.0.2(@rsbuild/core@1.0.7)': + '@rsbuild/plugin-react@1.0.3(@rsbuild/core@1.0.10)': dependencies: - '@rsbuild/core': 1.0.7 + '@rsbuild/core': 1.0.10 '@rspack/plugin-react-refresh': 1.0.0(react-refresh@0.14.2) react-refresh: 0.14.2 - '@rsbuild/plugin-react@1.0.3(@rsbuild/core@1.0.10)': + '@rsbuild/plugin-react@1.0.3(@rsbuild/core@1.0.7)': dependencies: - '@rsbuild/core': 1.0.10 + '@rsbuild/core': 1.0.7 '@rspack/plugin-react-refresh': 1.0.0(react-refresh@0.14.2) react-refresh: 0.14.2 @@ -5219,6 +5502,15 @@ snapshots: reduce-configs: 1.0.0 sass-embedded: 1.79.4 + '@rsbuild/plugin-sass@1.0.2(@rsbuild/core@1.0.7)': + dependencies: + '@rsbuild/core': 1.0.7 + deepmerge: 4.3.1 + loader-utils: 2.0.4 + postcss: 8.4.47 + reduce-configs: 1.0.0 + sass-embedded: 1.79.4 + '@rsbuild/plugin-svgr@1.0.2(@rsbuild/core@1.0.10)(typescript@5.6.2)': dependencies: '@rsbuild/core': 1.0.10 @@ -5897,11 +6189,17 @@ snapshots: ansi-styles@5.2.0: {} + ansi-styles@6.2.1: {} + + any-promise@1.3.0: {} + anymatch@3.1.3: dependencies: normalize-path: 3.0.0 picomatch: 2.3.1 + arg@5.0.2: {} + argparse@1.0.10: dependencies: sprintf-js: 1.0.3 @@ -6016,6 +6314,8 @@ snapshots: callsites@3.1.0: {} + camelcase-css@2.0.1: {} + camelcase@6.3.0: {} caniuse-lite@1.0.30001663: {} @@ -6147,6 +6447,8 @@ snapshots: commander@2.20.3: {} + commander@4.1.1: {} + commander@6.2.1: {} commander@7.2.0: {} @@ -6224,6 +6526,8 @@ snapshots: css-what@6.1.0: {} + cssesc@3.0.0: {} + csso@5.0.5: dependencies: css-tree: 2.2.1 @@ -6282,6 +6586,8 @@ snapshots: dependencies: dequal: 2.0.3 + didyoumean@1.2.2: {} + diff-sequences@29.6.3: {} diff@5.2.0: {} @@ -6290,6 +6596,8 @@ snapshots: dependencies: path-type: 4.0.0 + dlv@1.1.3: {} + dom-helpers@5.2.1: dependencies: '@babel/runtime': 7.24.8 @@ -6340,6 +6648,8 @@ snapshots: dotenv@16.4.5: {} + eastasianwidth@0.2.0: {} + edit-json-file@1.8.0: dependencies: find-value: 1.0.12 @@ -6356,6 +6666,8 @@ snapshots: emoji-regex@8.0.0: {} + emoji-regex@9.2.2: {} + emojis-list@3.0.0: {} encodeurl@1.0.2: {} @@ -6609,6 +6921,11 @@ snapshots: follow-redirects@1.15.6: {} + foreground-child@3.3.0: + dependencies: + cross-spawn: 7.0.3 + signal-exit: 4.1.0 + form-data@4.0.0: dependencies: asynckit: 0.4.0 @@ -6692,8 +7009,21 @@ snapshots: dependencies: is-glob: 4.0.3 + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + glob-to-regexp@0.4.1: {} + glob@10.4.5: + dependencies: + foreground-child: 3.3.0 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + globals@11.12.0: {} globby@11.1.0: @@ -7055,6 +7385,12 @@ snapshots: iterate-object@1.3.4: {} + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + jest-diff@29.7.0: dependencies: chalk: 4.1.2 @@ -7070,6 +7406,8 @@ snapshots: merge-stream: 2.0.0 supports-color: 8.1.1 + jiti@1.21.6: {} + jju@1.4.0: {} js-tokens@4.0.0: {} @@ -7111,6 +7449,10 @@ snapshots: leac@0.6.0: {} + lilconfig@2.1.0: {} + + lilconfig@3.1.2: {} + lines-and-columns@1.2.4: {} lines-and-columns@2.0.3: {} @@ -7168,6 +7510,8 @@ snapshots: fault: 1.0.4 highlight.js: 10.7.3 + lru-cache@10.4.3: {} + lru-cache@4.1.5: dependencies: pseudomap: 1.0.2 @@ -7653,14 +7997,26 @@ snapshots: dependencies: brace-expansion: 2.0.1 + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.1 + minimist@1.2.8: {} + minipass@7.1.2: {} + mri@1.2.0: {} ms@2.0.0: {} ms@2.1.3: {} + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + nano-staged@0.8.0: dependencies: picocolors: 1.1.0 @@ -7754,6 +8110,8 @@ snapshots: object-assign@4.1.1: {} + object-hash@3.0.0: {} + object-inspect@1.13.2: {} on-finished@2.4.1: @@ -7829,6 +8187,8 @@ snapshots: p-try@2.2.0: {} + package-json-from-dist@1.0.1: {} + package-manager-detector@0.2.0: {} parent-module@1.0.1: @@ -7883,6 +8243,11 @@ snapshots: path-parse@1.0.7: {} + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + path-serializer@0.0.6: {} path-to-regexp@0.1.10: {} @@ -7905,8 +8270,12 @@ snapshots: picomatch@2.3.1: {} + pify@2.3.0: {} + pify@4.0.1: {} + pirates@4.0.6: {} + playwright-core@1.47.2: {} playwright@1.47.2: @@ -7915,6 +8284,47 @@ snapshots: optionalDependencies: fsevents: 2.3.2 + postcss-alias@2.0.0: + dependencies: + postcss: 6.0.23 + + postcss-import@15.1.0(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.8 + + postcss-js@4.0.1(postcss@8.4.47): + dependencies: + camelcase-css: 2.0.1 + postcss: 8.4.47 + + postcss-load-config@4.0.2(postcss@8.4.47): + dependencies: + lilconfig: 3.1.2 + yaml: 2.5.1 + optionalDependencies: + postcss: 8.4.47 + + postcss-nested@6.2.0(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + postcss-selector-parser: 6.1.2 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-value-parser@4.2.0: {} + + postcss@6.0.23: + dependencies: + chalk: 2.4.2 + source-map: 0.6.1 + supports-color: 5.5.0 + postcss@8.4.47: dependencies: nanoid: 3.3.7 @@ -8075,6 +8485,10 @@ snapshots: dependencies: loose-envify: 1.4.0 + read-cache@1.0.0: + dependencies: + pify: 2.3.0 + read-yaml-file@1.1.0: dependencies: graceful-fs: 4.2.11 @@ -8552,6 +8966,12 @@ snapshots: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + string-width@7.2.0: dependencies: emoji-regex: 10.4.0 @@ -8587,6 +9007,16 @@ snapshots: dependencies: inline-style-parser: 0.1.1 + sucrase@3.35.0: + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + commander: 4.1.1 + glob: 10.4.5 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.6 + ts-interface-checker: 0.1.13 + supports-color@5.5.0: dependencies: has-flag: 3.0.0 @@ -8626,6 +9056,33 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 + tailwindcss@3.4.13: + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.2 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.6 + lilconfig: 2.1.0 + micromatch: 4.0.7 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.1.0 + postcss: 8.4.47 + postcss-import: 15.1.0(postcss@8.4.47) + postcss-js: 4.0.1(postcss@8.4.47) + postcss-load-config: 4.0.2(postcss@8.4.47) + postcss-nested: 6.2.0(postcss@8.4.47) + postcss-selector-parser: 6.1.2 + resolve: 1.22.8 + sucrase: 3.35.0 + transitivePeerDependencies: + - ts-node + tapable@2.2.1: {} tar-stream@2.2.0: @@ -8654,6 +9111,14 @@ snapshots: commander: 2.20.3 source-map-support: 0.5.21 + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + thingies@1.21.0(tslib@2.6.3): dependencies: tslib: 2.6.3 @@ -8692,6 +9157,8 @@ snapshots: trough@2.2.0: {} + ts-interface-checker@0.1.13: {} + tsconfck@3.1.3(typescript@5.6.2): optionalDependencies: typescript: 5.6.2 @@ -8972,6 +9439,12 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + wrappy@1.0.2: {} xtend@4.0.2: {} @@ -8989,6 +9462,8 @@ snapshots: commander: 6.2.1 js-yaml: 3.14.1 + yaml@2.5.1: {} + yargs-parser@21.1.1: {} yargs@17.7.2: diff --git a/tests/benchmark/index.bench.ts b/tests/benchmark/index.bench.ts index 4b0892498..ebd1743ff 100644 --- a/tests/benchmark/index.bench.ts +++ b/tests/benchmark/index.bench.ts @@ -11,9 +11,17 @@ describe('run rslib in examples', () => { { time: 5 }, ); bench( - 'examples/react-component', + 'examples/react-component-bundle', async () => { - const cwd = getCwdByExample('react-component'); + const cwd = getCwdByExample('react-component-bundle'); + await rslibBuild(cwd); + }, + { time: 5 }, + ); + bench( + 'examples/react-component-bundle-false', + async () => { + const cwd = getCwdByExample('react-component-bundle-false'); await rslibBuild(cwd); }, { time: 5 }, diff --git a/tests/e2e/react-component/index.pw.test.ts b/tests/e2e/react-component/index.pw.test.ts index fc01e1eb3..f842d0535 100644 --- a/tests/e2e/react-component/index.pw.test.ts +++ b/tests/e2e/react-component/index.pw.test.ts @@ -1,14 +1,7 @@ -import { expect, test } from '@playwright/test'; +import { type Page, expect, test } from '@playwright/test'; import { dev } from 'test-helper/rsbuild'; -test('should render example "react-component" successfully', async ({ - page, -}) => { - const rsbuild = await dev({ - cwd: __dirname, - page, - }); - +async function counterCompShouldWork(page: Page) { const h2El = page.locator('h2'); await expect(h2El).toHaveText('Counter: 0'); @@ -21,6 +14,59 @@ test('should render example "react-component" successfully', async ({ await expect(h2El).toHaveText('Counter: 1'); subtractEl?.click(); await expect(h2El).toHaveText('Counter: 0'); +} + +async function styleShouldWork(page: Page) { + const h2El = page.locator('h2'); + expect(h2El).toHaveCSS('font-size', '50px'); + + const buttonEl = page.locator('#root button'); + const [subtractEl, addEl] = await buttonEl.all(); + subtractEl && + expect(subtractEl).toHaveCSS('background-color', 'rgb(255, 255, 0)'); + addEl && expect(addEl).toHaveCSS('background-color', 'rgb(255, 255, 0)'); +} + +test('should render example "react-component-bundle" successfully', async ({ + page, +}) => { + const rsbuild = await dev({ + cwd: __dirname, + page, + rsbuildConfig: { + source: { + entry: { + index: './src/bundle.tsx', + }, + }, + }, + }); + + await counterCompShouldWork(page); + + await styleShouldWork(page); + + await rsbuild.close(); +}); + +test('should render example "react-component-bundle-false" successfully', async ({ + page, +}) => { + const rsbuild = await dev({ + cwd: __dirname, + page, + rsbuildConfig: { + source: { + entry: { + index: './src/bundleFalse.tsx', + }, + }, + }, + }); + + await counterCompShouldWork(page); + + await styleShouldWork(page); await rsbuild.close(); }); diff --git a/tests/e2e/react-component/package.json b/tests/e2e/react-component/package.json index 534d548d2..7a870befc 100644 --- a/tests/e2e/react-component/package.json +++ b/tests/e2e/react-component/package.json @@ -1,5 +1,9 @@ { "name": "react-component-e2e", "version": "1.0.0", - "private": true + "private": true, + "dependencies": { + "@examples/react-component-bundle": "workspace:*", + "@examples/react-component-bundle-false": "workspace:*" + } } diff --git a/tests/e2e/react-component/src/App.tsx b/tests/e2e/react-component/src/App.tsx deleted file mode 100644 index 96c164b16..000000000 --- a/tests/e2e/react-component/src/App.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { Counter } from '@examples/react-component'; - -const App = () => ( -
- -
-); - -export default App; diff --git a/tests/e2e/react-component/src/bundle.tsx b/tests/e2e/react-component/src/bundle.tsx new file mode 100644 index 000000000..3338a64c2 --- /dev/null +++ b/tests/e2e/react-component/src/bundle.tsx @@ -0,0 +1,17 @@ +import { Counter } from '@examples/react-component-bundle'; +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import '@examples/react-component-bundle/dist/esm/index.css'; + +const App = () => ( +
+ +
+); + +const root = ReactDOM.createRoot(document.getElementById('root')!); +root.render( + + + , +); diff --git a/tests/e2e/react-component/src/index.tsx b/tests/e2e/react-component/src/bundleFalse.tsx similarity index 62% rename from tests/e2e/react-component/src/index.tsx rename to tests/e2e/react-component/src/bundleFalse.tsx index 2b875af73..4eca0bb7c 100644 --- a/tests/e2e/react-component/src/index.tsx +++ b/tests/e2e/react-component/src/bundleFalse.tsx @@ -1,6 +1,12 @@ +import { Counter } from '@examples/react-component-bundle-false'; import React from 'react'; import ReactDOM from 'react-dom/client'; -import App from './App'; + +const App = () => ( +
+ +
+); const root = ReactDOM.createRoot(document.getElementById('root')!); root.render( diff --git a/tests/integration/bundle-false/svgr/package.json b/tests/integration/bundle-false/svgr/package.json index cd0230e42..589ceb011 100644 --- a/tests/integration/bundle-false/svgr/package.json +++ b/tests/integration/bundle-false/svgr/package.json @@ -7,7 +7,7 @@ "react": "^18.3.1" }, "devDependencies": { - "@rsbuild/plugin-react": "1.0.2", - "@rsbuild/plugin-svgr": "1.0.2" + "@rsbuild/plugin-react": "^1.0.2", + "@rsbuild/plugin-svgr": "^1.0.2" } } diff --git a/tests/integration/style/css-modules/__fixtures__/basic/src/button/index.module.scss b/tests/integration/style/css-modules/__fixtures__/basic/src/button/index.module.scss new file mode 100644 index 000000000..20f5fa0c2 --- /dev/null +++ b/tests/integration/style/css-modules/__fixtures__/basic/src/button/index.module.scss @@ -0,0 +1,3 @@ +.content-wrapper { + background-color: #fff; +} diff --git a/tests/integration/style/css-modules/__fixtures__/basic/src/button/index.tsx b/tests/integration/style/css-modules/__fixtures__/basic/src/button/index.tsx new file mode 100644 index 000000000..e497b87f7 --- /dev/null +++ b/tests/integration/style/css-modules/__fixtures__/basic/src/button/index.tsx @@ -0,0 +1,3 @@ +import styles from './index.module.scss'; + +export default styles; diff --git a/tests/integration/style/css-modules/__fixtures__/basic/src/env.d.ts b/tests/integration/style/css-modules/__fixtures__/basic/src/env.d.ts new file mode 100644 index 000000000..b0ac762b0 --- /dev/null +++ b/tests/integration/style/css-modules/__fixtures__/basic/src/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/tests/integration/style/css-modules/__fixtures__/basic/src/index.ts b/tests/integration/style/css-modules/__fixtures__/basic/src/index.ts new file mode 100644 index 000000000..d6cc2e49f --- /dev/null +++ b/tests/integration/style/css-modules/__fixtures__/basic/src/index.ts @@ -0,0 +1,4 @@ +import button from './button/index'; +import './reset.scss'; + +export { button }; diff --git a/tests/integration/style/css-modules/__fixtures__/basic/src/reset.scss b/tests/integration/style/css-modules/__fixtures__/basic/src/reset.scss new file mode 100644 index 000000000..3e57dbe91 --- /dev/null +++ b/tests/integration/style/css-modules/__fixtures__/basic/src/reset.scss @@ -0,0 +1,3 @@ +.outer { + color: red; +} diff --git a/tests/integration/style/css-modules/__fixtures__/basic/tsconfig.json b/tests/integration/style/css-modules/__fixtures__/basic/tsconfig.json new file mode 100644 index 000000000..888d3e460 --- /dev/null +++ b/tests/integration/style/css-modules/__fixtures__/basic/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "@rslib/tsconfig/base", + "compilerOptions": { + "baseUrl": "./" + }, + "include": ["src"] +} diff --git a/tests/integration/style/css-modules/bundle-false-auto/package.json b/tests/integration/style/css-modules/bundle-false-auto/package.json new file mode 100644 index 000000000..8a3ba8a84 --- /dev/null +++ b/tests/integration/style/css-modules/bundle-false-auto/package.json @@ -0,0 +1,6 @@ +{ + "name": "css-modules-bundle-false-auto-test", + "version": "1.0.0", + "private": true, + "type": "module" +} diff --git a/tests/integration/style/css-modules/bundle-false-auto/rslib.config.ts b/tests/integration/style/css-modules/bundle-false-auto/rslib.config.ts new file mode 100644 index 000000000..bf921ff48 --- /dev/null +++ b/tests/integration/style/css-modules/bundle-false-auto/rslib.config.ts @@ -0,0 +1,27 @@ +import { pluginSass } from '@rsbuild/plugin-sass'; +import { defineConfig } from '@rslib/core'; +import { generateBundleCjsConfig, generateBundleEsmConfig } from 'test-helper'; + +export default defineConfig({ + lib: [ + generateBundleEsmConfig({ bundle: false }), + generateBundleCjsConfig({ bundle: false }), + ], + source: { + entry: { + index: ['../__fixtures__/basic/src/**', '!../__fixtures__/**/*.d.ts'], + }, + }, + plugins: [ + pluginSass({ + sassLoaderOptions: { + additionalData: '$base-color: #c6538c;', + }, + }), + ], + output: { + cssModules: { + auto: /\.scss/, + }, + }, +}); diff --git a/tests/integration/style/css-modules/bundle-false/package.json b/tests/integration/style/css-modules/bundle-false/package.json new file mode 100644 index 000000000..88ba6c946 --- /dev/null +++ b/tests/integration/style/css-modules/bundle-false/package.json @@ -0,0 +1,6 @@ +{ + "name": "css-modules-bundle-false-test", + "version": "1.0.0", + "private": true, + "type": "module" +} diff --git a/tests/integration/style/css-modules/bundle-false/rslib.config.ts b/tests/integration/style/css-modules/bundle-false/rslib.config.ts new file mode 100644 index 000000000..2ea1e042b --- /dev/null +++ b/tests/integration/style/css-modules/bundle-false/rslib.config.ts @@ -0,0 +1,22 @@ +import { pluginSass } from '@rsbuild/plugin-sass'; +import { defineConfig } from '@rslib/core'; +import { generateBundleCjsConfig, generateBundleEsmConfig } from 'test-helper'; + +export default defineConfig({ + lib: [ + generateBundleEsmConfig({ bundle: false }), + generateBundleCjsConfig({ bundle: false }), + ], + source: { + entry: { + index: ['../__fixtures__/basic/src/**'], + }, + }, + plugins: [ + pluginSass({ + sassLoaderOptions: { + additionalData: '$base-color: #c6538c;', + }, + }), + ], +}); diff --git a/tests/integration/style/css-modules/bundle/package.json b/tests/integration/style/css-modules/bundle/package.json new file mode 100644 index 000000000..9808c1801 --- /dev/null +++ b/tests/integration/style/css-modules/bundle/package.json @@ -0,0 +1,6 @@ +{ + "name": "css-modules-bundle-test", + "version": "1.0.0", + "private": true, + "type": "module" +} diff --git a/tests/integration/style/css-modules/bundle/rslib.config.ts b/tests/integration/style/css-modules/bundle/rslib.config.ts new file mode 100644 index 000000000..a18391047 --- /dev/null +++ b/tests/integration/style/css-modules/bundle/rslib.config.ts @@ -0,0 +1,19 @@ +import { pluginSass } from '@rsbuild/plugin-sass'; +import { defineConfig } from '@rslib/core'; +import { generateBundleCjsConfig, generateBundleEsmConfig } from 'test-helper'; + +export default defineConfig({ + lib: [generateBundleEsmConfig(), generateBundleCjsConfig()], + source: { + entry: { + index: ['../__fixtures__/basic/src/index.ts'], + }, + }, + plugins: [ + pluginSass({ + sassLoaderOptions: { + additionalData: '$base-color: #c6538c;', + }, + }), + ], +}); diff --git a/tests/integration/style/css-modules/index.test.ts b/tests/integration/style/css-modules/index.test.ts new file mode 100644 index 000000000..2a88a4d8b --- /dev/null +++ b/tests/integration/style/css-modules/index.test.ts @@ -0,0 +1,83 @@ +import { join } from 'node:path'; +import { buildAndGetResults } from 'test-helper'; +import { expectFileContainContent } from 'test-helper/vitest'; +import { expect, test } from 'vitest'; + +test('should extract css-modules successfully in bundle', async () => { + const fixturePath = join(__dirname, 'bundle'); + const { contents } = await buildAndGetResults(fixturePath, 'css'); + + const esmFiles = Object.keys(contents.esm); + expect(esmFiles).toMatchInlineSnapshot(` + [ + "/tests/integration/style/css-modules/bundle/dist/esm/static/css/index.css", + ] + `); + + const cjsFiles = Object.keys(contents.cjs); + expect(cjsFiles).toMatchInlineSnapshot(` + [ + "/tests/integration/style/css-modules/bundle/dist/cjs/static/css/index.css", + ] + `); +}); + +test('should extract css-modules successfully in bundle-false', async () => { + const fixturePath = join(__dirname, 'bundle-false'); + const { contents } = await buildAndGetResults(fixturePath, 'css'); + const { contents: jsContents } = await buildAndGetResults(fixturePath, 'js'); + + const esmFiles = Object.keys(contents.esm); + expect(esmFiles).toMatchInlineSnapshot(` + [ + "/tests/integration/style/css-modules/bundle-false/dist/esm/button/index_module.css", + "/tests/integration/style/css-modules/bundle-false/dist/esm/reset.css", + ] + `); + expectFileContainContent( + jsContents.esm, + 'button/index.module.js', + 'import "./index_module.css"', + ); + + const cjsFiles = Object.keys(contents.cjs); + expect(cjsFiles).toMatchInlineSnapshot(` + [ + "/tests/integration/style/css-modules/bundle-false/dist/cjs/button/index_module.css", + "/tests/integration/style/css-modules/bundle-false/dist/cjs/reset.css", + ] + `); + expectFileContainContent( + jsContents.cjs, + 'button/index.module.cjs', + 'require("./index_module.css")', + ); +}); + +test('should extract css-modules successfully in bundle-false with output.cssModules.auto config', async () => { + const fixturePath = join(__dirname, 'bundle-false-auto'); + const { contents } = await buildAndGetResults(fixturePath, 'css'); + const { contents: jsContents } = await buildAndGetResults(fixturePath, 'js'); + + const esmFiles = Object.keys(contents.esm); + expect(esmFiles).toMatchInlineSnapshot(` + [ + "/tests/integration/style/css-modules/bundle-false-auto/dist/esm/button/index_module.css", + "/tests/integration/style/css-modules/bundle-false-auto/dist/esm/reset.css", + ] + `); + expectFileContainContent(jsContents.esm, 'reset.js', 'import "./reset.css"'); + + const cjsFiles = Object.keys(contents.cjs); + expect(cjsFiles).toMatchInlineSnapshot(` + [ + "/tests/integration/style/css-modules/bundle-false-auto/dist/cjs/button/index_module.css", + "/tests/integration/style/css-modules/bundle-false-auto/dist/cjs/reset.css", + ] + `); + expectFileContainContent( + jsContents.cjs, + 'reset.cjs', + 'require("./reset.css")', + ); +}); diff --git a/tests/integration/style/css/__fixtures__/basic/src/import.css b/tests/integration/style/css/__fixtures__/basic/src/import.css new file mode 100644 index 000000000..3827c0489 --- /dev/null +++ b/tests/integration/style/css/__fixtures__/basic/src/import.css @@ -0,0 +1,8 @@ +@import 'https://cdnjs.cloudflare.com/ajax/libs/modern-normalize/1.1.0/modern-normalize.css'; +@import url('https://cdnjs.cloudflare.com/ajax/libs/modern-normalize/1.0.0/modern-normalize.css'); +@import url('lib1.css'); +@import 'lib2.css'; + +.import { + background-image: url('https://cdnjs.cloudflare.com/ajax/libs/modern-normalize/1.1.0/modern-normalize.css'); +} diff --git a/tests/integration/style/css/__fixtures__/basic/src/lib1.css b/tests/integration/style/css/__fixtures__/basic/src/lib1.css new file mode 100644 index 000000000..7e86aa1fd --- /dev/null +++ b/tests/integration/style/css/__fixtures__/basic/src/lib1.css @@ -0,0 +1,3 @@ +.lib1 { + color: red; +} diff --git a/tests/integration/style/css/__fixtures__/basic/src/lib2.css b/tests/integration/style/css/__fixtures__/basic/src/lib2.css new file mode 100644 index 000000000..b109d5735 --- /dev/null +++ b/tests/integration/style/css/__fixtures__/basic/src/lib2.css @@ -0,0 +1,3 @@ +.lib2 { + color: green; +} diff --git a/tests/integration/style/css/__fixtures__/basic/tsconfig.json b/tests/integration/style/css/__fixtures__/basic/tsconfig.json new file mode 100644 index 000000000..888d3e460 --- /dev/null +++ b/tests/integration/style/css/__fixtures__/basic/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "@rslib/tsconfig/base", + "compilerOptions": { + "baseUrl": "./" + }, + "include": ["src"] +} diff --git a/tests/integration/style/css/bundle-false/package.json b/tests/integration/style/css/bundle-false/package.json new file mode 100644 index 000000000..174eb3a59 --- /dev/null +++ b/tests/integration/style/css/bundle-false/package.json @@ -0,0 +1,6 @@ +{ + "name": "css-bundle-false-test", + "version": "1.0.0", + "private": true, + "type": "module" +} diff --git a/tests/integration/style/css/bundle-false/rslib.config.ts b/tests/integration/style/css/bundle-false/rslib.config.ts new file mode 100644 index 000000000..73218ed2f --- /dev/null +++ b/tests/integration/style/css/bundle-false/rslib.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from '@rslib/core'; +import { generateBundleCjsConfig, generateBundleEsmConfig } from 'test-helper'; + +export default defineConfig({ + lib: [ + generateBundleEsmConfig({ bundle: false }), + generateBundleCjsConfig({ bundle: false }), + ], + source: { + entry: { + index: ['../__fixtures__/basic/src/**/*.css'], + }, + }, +}); diff --git a/tests/integration/style/css/bundle/package.json b/tests/integration/style/css/bundle/package.json new file mode 100644 index 000000000..c948a1784 --- /dev/null +++ b/tests/integration/style/css/bundle/package.json @@ -0,0 +1,6 @@ +{ + "name": "css-bundle-test", + "version": "1.0.0", + "private": true, + "type": "module" +} diff --git a/tests/integration/style/css/bundle/rslib.config.ts b/tests/integration/style/css/bundle/rslib.config.ts new file mode 100644 index 000000000..5a9a0c3d3 --- /dev/null +++ b/tests/integration/style/css/bundle/rslib.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from '@rslib/core'; +import { generateBundleCjsConfig, generateBundleEsmConfig } from 'test-helper'; + +export default defineConfig({ + lib: [generateBundleEsmConfig(), generateBundleCjsConfig()], + source: { + entry: { + index: '../__fixtures__/basic/src/import.css', + }, + }, +}); diff --git a/tests/integration/style/css/index.test.ts b/tests/integration/style/css/index.test.ts new file mode 100644 index 000000000..b81d5673c --- /dev/null +++ b/tests/integration/style/css/index.test.ts @@ -0,0 +1,43 @@ +import { join } from 'node:path'; +import { buildAndGetResults } from 'test-helper'; +import { expect, test } from 'vitest'; + +test('should extract css successfully in bundle', async () => { + const fixturePath = join(__dirname, 'bundle'); + const { contents } = await buildAndGetResults(fixturePath, 'css'); + const esmFiles = Object.keys(contents.esm); + expect(esmFiles).toMatchInlineSnapshot(` + [ + "/tests/integration/style/css/bundle/dist/esm/static/css/index.css", + ] + `); + + const cjsFiles = Object.keys(contents.cjs); + expect(cjsFiles).toMatchInlineSnapshot(` + [ + "/tests/integration/style/css/bundle/dist/cjs/static/css/index.css", + ] + `); +}); + +test('should extract css successfully in bundle-false', async () => { + const fixturePath = join(__dirname, 'bundle-false'); + const { contents } = await buildAndGetResults(fixturePath, 'css'); + const esmFiles = Object.keys(contents.esm); + expect(esmFiles).toMatchInlineSnapshot(` + [ + "/tests/integration/style/css/bundle-false/dist/esm/import.css", + "/tests/integration/style/css/bundle-false/dist/esm/lib1.css", + "/tests/integration/style/css/bundle-false/dist/esm/lib2.css", + ] + `); + + const cjsFiles = Object.keys(contents.cjs); + expect(cjsFiles).toMatchInlineSnapshot(` + [ + "/tests/integration/style/css/bundle-false/dist/cjs/import.css", + "/tests/integration/style/css/bundle-false/dist/cjs/lib1.css", + "/tests/integration/style/css/bundle-false/dist/cjs/lib2.css", + ] + `); +}); diff --git a/tests/integration/style/less/__fixtures__/.gitignore b/tests/integration/style/less/__fixtures__/.gitignore new file mode 100644 index 000000000..a16062e4c --- /dev/null +++ b/tests/integration/style/less/__fixtures__/.gitignore @@ -0,0 +1,2 @@ +!node_modules +node_modules/.* diff --git a/tests/integration/style/less/__fixtures__/basic/src/assets/normal.ttf b/tests/integration/style/less/__fixtures__/basic/src/assets/normal.ttf new file mode 100644 index 000000000..a440acf63 Binary files /dev/null and b/tests/integration/style/less/__fixtures__/basic/src/assets/normal.ttf differ diff --git a/tests/integration/style/less/__fixtures__/basic/src/index.less b/tests/integration/style/less/__fixtures__/basic/src/index.less new file mode 100644 index 000000000..1f3a5efd6 --- /dev/null +++ b/tests/integration/style/less/__fixtures__/basic/src/index.less @@ -0,0 +1,15 @@ +@import './nest/nest.less'; +@import '~/alias.css'; + +@url: './assets/normal.ttf'; + +.url-variable { + font-family: url(@url); +} + +@border-dark: rgba(@base-color, 0.88); + +.alert { + border: 1px solid @border-dark; + margin: 10px / 2; +} diff --git a/tests/integration/style/less/__fixtures__/basic/src/nest/alias.css b/tests/integration/style/less/__fixtures__/basic/src/nest/alias.css new file mode 100644 index 000000000..3d9a2b2a4 --- /dev/null +++ b/tests/integration/style/less/__fixtures__/basic/src/nest/alias.css @@ -0,0 +1,3 @@ +p { + color: red; +} diff --git a/tests/integration/style/less/__fixtures__/basic/src/nest/nest.less b/tests/integration/style/less/__fixtures__/basic/src/nest/nest.less new file mode 100644 index 000000000..6e57a0107 --- /dev/null +++ b/tests/integration/style/less/__fixtures__/basic/src/nest/nest.less @@ -0,0 +1,3 @@ +.div { + font-family: url('../assets/normal.ttf'); +} diff --git a/tests/integration/style/less/__fixtures__/import/node_modules/lib1/index.css b/tests/integration/style/less/__fixtures__/import/node_modules/lib1/index.css new file mode 100644 index 000000000..ac8182f41 --- /dev/null +++ b/tests/integration/style/less/__fixtures__/import/node_modules/lib1/index.css @@ -0,0 +1,3 @@ +.lib1 { + font-size: 18px; +} diff --git a/tests/integration/style/less/__fixtures__/import/node_modules/lib1/package.json b/tests/integration/style/less/__fixtures__/import/node_modules/lib1/package.json new file mode 100644 index 000000000..8a203241a --- /dev/null +++ b/tests/integration/style/less/__fixtures__/import/node_modules/lib1/package.json @@ -0,0 +1,11 @@ +{ + "name": "lib1", + "version": "1.0.0", + "description": "", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/tests/integration/style/less/__fixtures__/import/src/extension/index.less b/tests/integration/style/less/__fixtures__/import/src/extension/index.less new file mode 100644 index 000000000..4702e9722 --- /dev/null +++ b/tests/integration/style/less/__fixtures__/import/src/extension/index.less @@ -0,0 +1,4 @@ +@import './util'; +body { + background: blue; +} diff --git a/tests/integration/style/less/__fixtures__/import/src/extension/util.less b/tests/integration/style/less/__fixtures__/import/src/extension/util.less new file mode 100644 index 000000000..538fa56f4 --- /dev/null +++ b/tests/integration/style/less/__fixtures__/import/src/extension/util.less @@ -0,0 +1,3 @@ +div { + color: red; +} diff --git a/tests/integration/style/less/__fixtures__/import/src/foundation/code.less b/tests/integration/style/less/__fixtures__/import/src/foundation/code.less new file mode 100644 index 000000000..362e22dd1 --- /dev/null +++ b/tests/integration/style/less/__fixtures__/import/src/foundation/code.less @@ -0,0 +1,4 @@ +code { + padding: 0.25em; + line-height: 0; +} diff --git a/tests/integration/style/less/__fixtures__/import/src/index.less b/tests/integration/style/less/__fixtures__/import/src/index.less new file mode 100644 index 000000000..36b4d7ac8 --- /dev/null +++ b/tests/integration/style/less/__fixtures__/import/src/index.less @@ -0,0 +1,4 @@ +@import url('foundation/code'); +@import '~lib1/index.css'; +// should prefer less than js +@import 'prefer'; diff --git a/tests/integration/style/less/__fixtures__/import/src/index.ts b/tests/integration/style/less/__fixtures__/import/src/index.ts new file mode 100644 index 000000000..ff0fe0734 --- /dev/null +++ b/tests/integration/style/less/__fixtures__/import/src/index.ts @@ -0,0 +1,2 @@ +import './index.less'; +import './extension/index.less'; diff --git a/tests/integration/style/less/__fixtures__/import/src/prefer/index.js b/tests/integration/style/less/__fixtures__/import/src/prefer/index.js new file mode 100644 index 000000000..6be02374d --- /dev/null +++ b/tests/integration/style/less/__fixtures__/import/src/prefer/index.js @@ -0,0 +1 @@ +console.log('hello world'); diff --git a/tests/integration/style/less/__fixtures__/import/src/prefer/index.less b/tests/integration/style/less/__fixtures__/import/src/prefer/index.less new file mode 100644 index 000000000..32cb6f2aa --- /dev/null +++ b/tests/integration/style/less/__fixtures__/import/src/prefer/index.less @@ -0,0 +1,3 @@ +.prefer { + color: darkcyan; +} diff --git a/tests/integration/style/less/bundle-false/package.json b/tests/integration/style/less/bundle-false/package.json new file mode 100644 index 000000000..93d061a64 --- /dev/null +++ b/tests/integration/style/less/bundle-false/package.json @@ -0,0 +1,6 @@ +{ + "name": "less-bundle-false-test", + "version": "1.0.0", + "private": true, + "type": "module" +} diff --git a/tests/integration/style/less/bundle-false/rslib.config.ts b/tests/integration/style/less/bundle-false/rslib.config.ts new file mode 100644 index 000000000..ca9dcb75f --- /dev/null +++ b/tests/integration/style/less/bundle-false/rslib.config.ts @@ -0,0 +1,35 @@ +import { pluginLess } from '@rsbuild/plugin-less'; +import { defineConfig } from '@rslib/core'; +import { generateBundleCjsConfig, generateBundleEsmConfig } from 'test-helper'; + +export default defineConfig({ + lib: [ + generateBundleEsmConfig({ bundle: false }), + generateBundleCjsConfig({ bundle: false }), + ], + source: { + entry: { + index: ['../__fixtures__/basic/src/**', '!../__fixtures__/**/*.d.ts'], + }, + alias: { + '~': require('node:path').resolve( + __dirname, + '../__fixtures__/basic/src/nest', + ), + }, + }, + plugins: [ + pluginLess({ + lessLoaderOptions: { + lessOptions: { + math: 'always', + }, + additionalData: '@base-color: #c6538c;', + }, + }), + ], + output: { + // TODO: support asset + // dataUriLimit: 0 + }, +}); diff --git a/tests/integration/style/less/bundle-import/package.json b/tests/integration/style/less/bundle-import/package.json new file mode 100644 index 000000000..ace691fe3 --- /dev/null +++ b/tests/integration/style/less/bundle-import/package.json @@ -0,0 +1,6 @@ +{ + "name": "less-bundle-import-test", + "version": "1.0.0", + "private": true, + "type": "module" +} diff --git a/tests/integration/style/less/bundle-import/rslib.config.ts b/tests/integration/style/less/bundle-import/rslib.config.ts new file mode 100644 index 000000000..ac559ed66 --- /dev/null +++ b/tests/integration/style/less/bundle-import/rslib.config.ts @@ -0,0 +1,13 @@ +import { pluginLess } from '@rsbuild/plugin-less'; +import { defineConfig } from '@rslib/core'; +import { generateBundleCjsConfig, generateBundleEsmConfig } from 'test-helper'; + +export default defineConfig({ + lib: [generateBundleEsmConfig(), generateBundleCjsConfig()], + source: { + entry: { + index: ['../__fixtures__/import/src/index.ts'], + }, + }, + plugins: [pluginLess()], +}); diff --git a/tests/integration/style/less/bundle/package.json b/tests/integration/style/less/bundle/package.json new file mode 100644 index 000000000..469b9e44c --- /dev/null +++ b/tests/integration/style/less/bundle/package.json @@ -0,0 +1,6 @@ +{ + "name": "less-bundle-test", + "version": "1.0.0", + "private": true, + "type": "module" +} diff --git a/tests/integration/style/less/bundle/rslib.config.ts b/tests/integration/style/less/bundle/rslib.config.ts new file mode 100644 index 000000000..18bcbe877 --- /dev/null +++ b/tests/integration/style/less/bundle/rslib.config.ts @@ -0,0 +1,28 @@ +import { pluginLess } from '@rsbuild/plugin-less'; +import { defineConfig } from '@rslib/core'; +import { generateBundleCjsConfig, generateBundleEsmConfig } from 'test-helper'; + +export default defineConfig({ + lib: [generateBundleEsmConfig(), generateBundleCjsConfig()], + source: { + entry: { + index: ['../__fixtures__/basic/src/index.less'], + }, + alias: { + '~': require('node:path').resolve( + __dirname, + '../__fixtures__/basic/src/nest', + ), + }, + }, + plugins: [ + pluginLess({ + lessLoaderOptions: { + lessOptions: { + math: 'always', + }, + additionalData: '@base-color: #c6538c;', + }, + }), + ], +}); diff --git a/tests/integration/style/less/index.test.ts b/tests/integration/style/less/index.test.ts new file mode 100644 index 000000000..0f80250a5 --- /dev/null +++ b/tests/integration/style/less/index.test.ts @@ -0,0 +1,67 @@ +import { join } from 'node:path'; +import { buildAndGetResults } from 'test-helper'; +import { expectFileContainContent } from 'test-helper/vitest'; +import { expect, test } from 'vitest'; + +test('should extract with pluginLess successfully in bundle-false', async () => { + const fixturePath = join(__dirname, 'bundle-false'); + const { contents } = await buildAndGetResults(fixturePath, 'css'); + + const esmFiles = Object.keys(contents.esm); + expect(esmFiles).toMatchInlineSnapshot(` + [ + "/tests/integration/style/less/bundle-false/dist/esm/index.css", + "/tests/integration/style/less/bundle-false/dist/esm/nest/alias.css", + "/tests/integration/style/less/bundle-false/dist/esm/nest/nest.css", + ] + `); + + const cjsFiles = Object.keys(contents.cjs); + expect(cjsFiles).toMatchInlineSnapshot(` + [ + "/tests/integration/style/less/bundle-false/dist/cjs/index.css", + "/tests/integration/style/less/bundle-false/dist/cjs/nest/alias.css", + "/tests/integration/style/less/bundle-false/dist/cjs/nest/nest.css", + ] + `); +}); + +test('should extract css with pluginLess successfully in bundle', async () => { + const fixturePath = join(__dirname, 'bundle'); + const { contents } = await buildAndGetResults(fixturePath, 'css'); + + const esmFiles = Object.keys(contents.esm); + expect(esmFiles).toMatchInlineSnapshot(` + [ + "/tests/integration/style/less/bundle/dist/esm/static/css/index.css", + ] + `); + + const cjsFiles = Object.keys(contents.cjs); + expect(cjsFiles).toMatchInlineSnapshot(` + [ + "/tests/integration/style/less/bundle/dist/cjs/static/css/index.css", + ] + `); +}); + +test('should extract css with pluginLess successfully in import case', async () => { + const fixturePath = join(__dirname, 'bundle-import'); + const { contents } = await buildAndGetResults(fixturePath, 'css'); + + const esmFiles = Object.keys(contents.esm); + expect(esmFiles).toMatchInlineSnapshot(` + [ + "/tests/integration/style/less/bundle-import/dist/esm/static/css/index.css", + ] + `); + expectFileContainContent(contents.esm, 'index.css', '.lib1 {'); + + const cjsFiles = Object.keys(contents.cjs); + expect(cjsFiles).toMatchInlineSnapshot(` + [ + "/tests/integration/style/less/bundle-import/dist/cjs/static/css/index.css", + ] + `); + expectFileContainContent(contents.cjs, 'index.css', '.lib1 {'); +}); diff --git a/tests/integration/style/lightningcss/__fixtures__/basic/src/index.css b/tests/integration/style/lightningcss/__fixtures__/basic/src/index.css new file mode 100644 index 000000000..2a671e559 --- /dev/null +++ b/tests/integration/style/lightningcss/__fixtures__/basic/src/index.css @@ -0,0 +1 @@ +@import 'prefix.css'; diff --git a/tests/integration/style/lightningcss/__fixtures__/basic/src/prefix.css b/tests/integration/style/lightningcss/__fixtures__/basic/src/prefix.css new file mode 100644 index 000000000..f2b42a90b --- /dev/null +++ b/tests/integration/style/lightningcss/__fixtures__/basic/src/prefix.css @@ -0,0 +1,7 @@ +@media (min-resolution: 2dppx) { + .item { + transition: all 0.5s; + user-select: none; + background: linear-gradient(to bottom, white, black); + } +} diff --git a/tests/integration/style/lightningcss/bundle-false/.browserslistrc b/tests/integration/style/lightningcss/bundle-false/.browserslistrc new file mode 100644 index 000000000..4efa1bba0 --- /dev/null +++ b/tests/integration/style/lightningcss/bundle-false/.browserslistrc @@ -0,0 +1,3 @@ +Edge >= 12 +iOS >= 8 +Android >= 4.0 diff --git a/tests/integration/style/lightningcss/bundle-false/package.json b/tests/integration/style/lightningcss/bundle-false/package.json new file mode 100644 index 000000000..89266c31d --- /dev/null +++ b/tests/integration/style/lightningcss/bundle-false/package.json @@ -0,0 +1,4 @@ +{ + "name": "lightningcss-bundle-false-test", + "private": true +} diff --git a/tests/integration/style/lightningcss/bundle-false/rslib.config.ts b/tests/integration/style/lightningcss/bundle-false/rslib.config.ts new file mode 100644 index 000000000..4c0b2192f --- /dev/null +++ b/tests/integration/style/lightningcss/bundle-false/rslib.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from '@rslib/core'; +import { generateBundleCjsConfig, generateBundleEsmConfig } from 'test-helper'; + +export default defineConfig({ + lib: [ + generateBundleEsmConfig({ bundle: false }), + generateBundleCjsConfig({ bundle: false }), + ], + source: { + entry: { + index: ['../__fixtures__/basic/src/*.css'], + }, + }, +}); diff --git a/tests/integration/style/lightningcss/bundle/.browserslistrc b/tests/integration/style/lightningcss/bundle/.browserslistrc new file mode 100644 index 000000000..4efa1bba0 --- /dev/null +++ b/tests/integration/style/lightningcss/bundle/.browserslistrc @@ -0,0 +1,3 @@ +Edge >= 12 +iOS >= 8 +Android >= 4.0 diff --git a/tests/integration/style/lightningcss/bundle/package.json b/tests/integration/style/lightningcss/bundle/package.json new file mode 100644 index 000000000..3ef976b7b --- /dev/null +++ b/tests/integration/style/lightningcss/bundle/package.json @@ -0,0 +1,4 @@ +{ + "name": "lightningcss-bundle-test", + "private": true +} diff --git a/tests/integration/style/lightningcss/bundle/rslib.config.ts b/tests/integration/style/lightningcss/bundle/rslib.config.ts new file mode 100644 index 000000000..1dad8ed1c --- /dev/null +++ b/tests/integration/style/lightningcss/bundle/rslib.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from '@rslib/core'; +import { generateBundleCjsConfig, generateBundleEsmConfig } from 'test-helper'; + +export default defineConfig({ + lib: [generateBundleEsmConfig(), generateBundleCjsConfig()], + source: { + entry: { + index: ['../__fixtures__/basic/src/index.css'], + }, + }, +}); diff --git a/tests/integration/style/lightningcss/index.test.ts b/tests/integration/style/lightningcss/index.test.ts new file mode 100644 index 000000000..147281665 --- /dev/null +++ b/tests/integration/style/lightningcss/index.test.ts @@ -0,0 +1,62 @@ +import { join } from 'node:path'; +import { buildAndGetResults } from 'test-helper'; +import { expectFileContainContent } from 'test-helper/vitest'; +import { expect, test } from 'vitest'; + +test('should extract css with lightningcss-loader successfully in bundle', async () => { + const fixturePath = join(__dirname, 'bundle'); + const { contents } = await buildAndGetResults(fixturePath, 'css'); + const esmFiles = Object.keys(contents.esm); + expect(esmFiles).toMatchInlineSnapshot(` + [ + "/tests/integration/style/lightningcss/bundle/dist/esm/static/css/index.css", + ] + `); + expectFileContainContent( + contents.esm, + 'index.css', + '-webkit-user-select: none;', + ); + + const cjsFiles = Object.keys(contents.cjs); + expect(cjsFiles).toMatchInlineSnapshot(` + [ + "/tests/integration/style/lightningcss/bundle/dist/cjs/static/css/index.css", + ] + `); + expectFileContainContent( + contents.cjs, + 'index.css', + '-webkit-user-select: none;', + ); +}); + +test('should extract css with lightningcss-loader successfully in bundle-false', async () => { + const fixturePath = join(__dirname, 'bundle-false'); + const { contents } = await buildAndGetResults(fixturePath, 'css'); + const esmFiles = Object.keys(contents.esm); + expect(esmFiles).toMatchInlineSnapshot(` + [ + "/tests/integration/style/lightningcss/bundle-false/dist/esm/index.css", + "/tests/integration/style/lightningcss/bundle-false/dist/esm/prefix.css", + ] + `); + expectFileContainContent( + contents.esm, + 'prefix.css', + '-webkit-user-select: none;', + ); + + const cjsFiles = Object.keys(contents.cjs); + expect(cjsFiles).toMatchInlineSnapshot(` + [ + "/tests/integration/style/lightningcss/bundle-false/dist/cjs/index.css", + "/tests/integration/style/lightningcss/bundle-false/dist/cjs/prefix.css", + ] + `); + expectFileContainContent( + contents.cjs, + 'prefix.css', + '-webkit-user-select: none;', + ); +}); diff --git a/tests/integration/style/postcss/__fixtures__/basic/src/alias.css b/tests/integration/style/postcss/__fixtures__/basic/src/alias.css new file mode 100644 index 000000000..4608ad5cb --- /dev/null +++ b/tests/integration/style/postcss/__fixtures__/basic/src/alias.css @@ -0,0 +1,9 @@ +@alias { + fs: font-size; + bg: background; +} + +.aliased { + fs: 16px; + bg: white; +} diff --git a/tests/integration/style/postcss/__fixtures__/basic/src/index.css b/tests/integration/style/postcss/__fixtures__/basic/src/index.css new file mode 100644 index 000000000..1d41cc135 --- /dev/null +++ b/tests/integration/style/postcss/__fixtures__/basic/src/index.css @@ -0,0 +1 @@ +@import 'alias.css'; diff --git a/tests/integration/style/postcss/bundle-false/package.json b/tests/integration/style/postcss/bundle-false/package.json new file mode 100644 index 000000000..91869e902 --- /dev/null +++ b/tests/integration/style/postcss/bundle-false/package.json @@ -0,0 +1,7 @@ +{ + "name": "postcss-bundle-false-test", + "private": true, + "devDependencies": { + "postcss-alias": "2.0.0" + } +} diff --git a/tests/integration/style/postcss/bundle-false/rslib.config.ts b/tests/integration/style/postcss/bundle-false/rslib.config.ts new file mode 100644 index 000000000..4e23ba808 --- /dev/null +++ b/tests/integration/style/postcss/bundle-false/rslib.config.ts @@ -0,0 +1,22 @@ +import { defineConfig } from '@rslib/core'; +import { generateBundleCjsConfig, generateBundleEsmConfig } from 'test-helper'; + +export default defineConfig({ + lib: [ + generateBundleEsmConfig({ bundle: false }), + generateBundleCjsConfig({ bundle: false }), + ], + source: { + entry: { + index: ['../__fixtures__/basic/src/**/*.css'], + }, + }, + tools: { + lightningcssLoader: false, + postcss: { + postcssOptions: { + plugins: [require('postcss-alias')], + }, + }, + }, +}); diff --git a/tests/integration/style/postcss/bundle/package.json b/tests/integration/style/postcss/bundle/package.json new file mode 100644 index 000000000..65d16a538 --- /dev/null +++ b/tests/integration/style/postcss/bundle/package.json @@ -0,0 +1,7 @@ +{ + "name": "postcss-bundle-test", + "private": true, + "devDependencies": { + "postcss-alias": "2.0.0" + } +} diff --git a/tests/integration/style/postcss/bundle/rslib.config.ts b/tests/integration/style/postcss/bundle/rslib.config.ts new file mode 100644 index 000000000..bba694410 --- /dev/null +++ b/tests/integration/style/postcss/bundle/rslib.config.ts @@ -0,0 +1,19 @@ +import { defineConfig } from '@rslib/core'; +import { generateBundleCjsConfig, generateBundleEsmConfig } from 'test-helper'; + +export default defineConfig({ + lib: [generateBundleEsmConfig(), generateBundleCjsConfig()], + source: { + entry: { + index: ['../__fixtures__/basic/src/index.css'], + }, + }, + tools: { + lightningcssLoader: false, + postcss: { + postcssOptions: { + plugins: [require('postcss-alias')], + }, + }, + }, +}); diff --git a/tests/integration/style/postcss/index.test.ts b/tests/integration/style/postcss/index.test.ts new file mode 100644 index 000000000..00e9443c3 --- /dev/null +++ b/tests/integration/style/postcss/index.test.ts @@ -0,0 +1,48 @@ +import { join } from 'node:path'; +import { buildAndGetResults } from 'test-helper'; +import { expectFileContainContent } from 'test-helper/vitest'; +import { expect, test } from 'vitest'; + +test('should extract css with postcss-loader successfully in bundle', async () => { + const fixturePath = join(__dirname, 'bundle'); + const { contents } = await buildAndGetResults(fixturePath, 'css'); + + const esmFiles = Object.keys(contents.esm); + expect(esmFiles).toMatchInlineSnapshot(` + [ + "/tests/integration/style/postcss/bundle/dist/esm/static/css/index.css", + ] + `); + expectFileContainContent(contents.esm, 'index.css', 'font-size: 16px;'); + + const cjsFiles = Object.keys(contents.cjs); + expect(cjsFiles).toMatchInlineSnapshot(` + [ + "/tests/integration/style/postcss/bundle/dist/cjs/static/css/index.css", + ] + `); + expectFileContainContent(contents.cjs, 'index.css', 'font-size: 16px;'); +}); + +test('should extract css with postcss-loader successfully in bundle-false', async () => { + const fixturePath = join(__dirname, 'bundle-false'); + const { contents } = await buildAndGetResults(fixturePath, 'css'); + + const esmFiles = Object.keys(contents.esm); + expect(esmFiles).toMatchInlineSnapshot(` + [ + "/tests/integration/style/postcss/bundle-false/dist/esm/alias.css", + "/tests/integration/style/postcss/bundle-false/dist/esm/index.css", + ] + `); + expectFileContainContent(contents.esm, 'alias.css', 'font-size: 16px;'); + + const cjsFiles = Object.keys(contents.cjs); + expect(cjsFiles).toMatchInlineSnapshot(` + [ + "/tests/integration/style/postcss/bundle-false/dist/cjs/alias.css", + "/tests/integration/style/postcss/bundle-false/dist/cjs/index.css", + ] + `); + expectFileContainContent(contents.cjs, 'alias.css', 'font-size: 16px;'); +}); diff --git a/tests/integration/style/redirect-style-false/index.test.ts b/tests/integration/style/redirect-style-false/index.test.ts new file mode 100644 index 000000000..b4268607e --- /dev/null +++ b/tests/integration/style/redirect-style-false/index.test.ts @@ -0,0 +1,27 @@ +import { buildAndGetResults } from 'test-helper'; +import { expectFileContainContent } from 'test-helper/vitest'; +import { expect, test } from 'vitest'; + +test('should extract css successfully when using redirect.style = false', async () => { + const fixturePath = __dirname; + const { contents } = await buildAndGetResults(fixturePath, 'js'); + const esmFiles = Object.keys(contents.esm); + expect(esmFiles).toMatchInlineSnapshot(` + [ + "/tests/integration/style/redirect-style-false/dist/esm/index.js", + ] + `); + expectFileContainContent(contents.esm, 'index.js', 'import "./index.less";'); + + const cjsFiles = Object.keys(contents.cjs); + expect(cjsFiles).toMatchInlineSnapshot(` + [ + "/tests/integration/style/redirect-style-false/dist/cjs/index.cjs", + ] + `); + expectFileContainContent( + contents.cjs, + 'index.cjs', + 'require("./index.less")', + ); +}); diff --git a/tests/integration/style/redirect-style-false/package.json b/tests/integration/style/redirect-style-false/package.json new file mode 100644 index 000000000..788a8e737 --- /dev/null +++ b/tests/integration/style/redirect-style-false/package.json @@ -0,0 +1,6 @@ +{ + "name": "css-bundle-false-redirect-style-false-test", + "version": "1.0.0", + "private": true, + "type": "module" +} diff --git a/tests/integration/style/redirect-style-false/rslib.config.ts b/tests/integration/style/redirect-style-false/rslib.config.ts new file mode 100644 index 000000000..b2796fb33 --- /dev/null +++ b/tests/integration/style/redirect-style-false/rslib.config.ts @@ -0,0 +1,27 @@ +import { defineConfig } from '@rslib/core'; +import { generateBundleCjsConfig, generateBundleEsmConfig } from 'test-helper'; + +export default defineConfig({ + lib: [ + generateBundleEsmConfig({ + bundle: false, + redirect: { + style: false, + }, + }), + generateBundleCjsConfig({ + bundle: false, + redirect: { + style: false, + }, + }), + ], + source: { + entry: { + index: ['./src/index.ts'], + }, + }, + output: { + copy: [{ from: './src/index.less' }, { from: './src/style.module.less' }], + }, +}); diff --git a/tests/integration/style/redirect-style-false/src/index.less b/tests/integration/style/redirect-style-false/src/index.less new file mode 100644 index 000000000..303b7b9b8 --- /dev/null +++ b/tests/integration/style/redirect-style-false/src/index.less @@ -0,0 +1,3 @@ +.text-blue { + color: blue; +} diff --git a/tests/integration/style/redirect-style-false/src/index.ts b/tests/integration/style/redirect-style-false/src/index.ts new file mode 100644 index 000000000..c86b44947 --- /dev/null +++ b/tests/integration/style/redirect-style-false/src/index.ts @@ -0,0 +1,4 @@ +import './index.less'; +import styles from './style.module.less'; + +styles; diff --git a/tests/integration/style/redirect-style-false/src/style.module.less b/tests/integration/style/redirect-style-false/src/style.module.less new file mode 100644 index 000000000..303b7b9b8 --- /dev/null +++ b/tests/integration/style/redirect-style-false/src/style.module.less @@ -0,0 +1,3 @@ +.text-blue { + color: blue; +} diff --git a/tests/integration/style/sass/__fixtures__/.gitignore b/tests/integration/style/sass/__fixtures__/.gitignore new file mode 100644 index 000000000..a16062e4c --- /dev/null +++ b/tests/integration/style/sass/__fixtures__/.gitignore @@ -0,0 +1,2 @@ +!node_modules +node_modules/.* diff --git a/tests/integration/style/sass/__fixtures__/node_modules/lib1/index.css b/tests/integration/style/sass/__fixtures__/node_modules/lib1/index.css new file mode 100644 index 000000000..ac8182f41 --- /dev/null +++ b/tests/integration/style/sass/__fixtures__/node_modules/lib1/index.css @@ -0,0 +1,3 @@ +.lib1 { + font-size: 18px; +} diff --git a/tests/integration/style/sass/__fixtures__/node_modules/lib1/package.json b/tests/integration/style/sass/__fixtures__/node_modules/lib1/package.json new file mode 100644 index 000000000..160aecfde --- /dev/null +++ b/tests/integration/style/sass/__fixtures__/node_modules/lib1/package.json @@ -0,0 +1,5 @@ +{ + "name": "lib1", + "version": "1.0.0", + "description": "" +} diff --git a/tests/integration/style/sass/__fixtures__/src/foundation/_code.scss b/tests/integration/style/sass/__fixtures__/src/foundation/_code.scss new file mode 100644 index 000000000..362e22dd1 --- /dev/null +++ b/tests/integration/style/sass/__fixtures__/src/foundation/_code.scss @@ -0,0 +1,4 @@ +code { + padding: 0.25em; + line-height: 0; +} diff --git a/tests/integration/style/sass/__fixtures__/src/foundation/_lists.scss b/tests/integration/style/sass/__fixtures__/src/foundation/_lists.scss new file mode 100644 index 000000000..6945c97fb --- /dev/null +++ b/tests/integration/style/sass/__fixtures__/src/foundation/_lists.scss @@ -0,0 +1,11 @@ +ul, +ol { + text-align: left; + + & & { + padding: { + bottom: 0; + left: 0; + } + } +} diff --git a/tests/integration/style/sass/__fixtures__/src/foundation/index.scss b/tests/integration/style/sass/__fixtures__/src/foundation/index.scss new file mode 100644 index 000000000..274a0d5b8 --- /dev/null +++ b/tests/integration/style/sass/__fixtures__/src/foundation/index.scss @@ -0,0 +1,3 @@ +body { + // background: url(./logo.svg); +} diff --git a/tests/integration/style/sass/__fixtures__/src/foundation/logo.svg b/tests/integration/style/sass/__fixtures__/src/foundation/logo.svg new file mode 100644 index 000000000..6b60c1042 --- /dev/null +++ b/tests/integration/style/sass/__fixtures__/src/foundation/logo.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/tests/integration/style/sass/__fixtures__/src/index.scss b/tests/integration/style/sass/__fixtures__/src/index.scss new file mode 100644 index 000000000..7c777ffc6 --- /dev/null +++ b/tests/integration/style/sass/__fixtures__/src/index.scss @@ -0,0 +1,16 @@ +@import 'foundation/code', 'foundation/lists'; +// TODO: Error: Sass variables aren't allowed in plain CSS. +// @import '~lib1/index.css'; +@import './foundation/index.scss'; + +// TODO: asset support +// $url: './foundation/logo.svg'; +$border-dark: rgba($base-color, 0.88); + +// .url-variable { +// background: url($url); +// } + +.alert { + border: 1px solid $border-dark; +} diff --git a/tests/integration/style/sass/bundle-false/package.json b/tests/integration/style/sass/bundle-false/package.json new file mode 100644 index 000000000..8e4628aca --- /dev/null +++ b/tests/integration/style/sass/bundle-false/package.json @@ -0,0 +1,5 @@ +{ + "name": "sass-bundle-false-test", + "version": "1.0.0", + "private": true +} diff --git a/tests/integration/style/sass/bundle-false/rslib.config.ts b/tests/integration/style/sass/bundle-false/rslib.config.ts new file mode 100644 index 000000000..b07f07bf9 --- /dev/null +++ b/tests/integration/style/sass/bundle-false/rslib.config.ts @@ -0,0 +1,31 @@ +import { pluginSass } from '@rsbuild/plugin-sass'; +import { defineConfig } from '@rslib/core'; +import { generateBundleCjsConfig, generateBundleEsmConfig } from 'test-helper'; + +export default defineConfig({ + lib: [ + generateBundleEsmConfig({ bundle: false }), + generateBundleCjsConfig({ bundle: false }), + ], + source: { + entry: { + index: [ + '../__fixtures__/src/**/*.scss', + // TODO: assets support + // '../__fixtures__/foundation/logo.svg' + ], + }, + }, + plugins: [ + pluginSass({ + sassLoaderOptions: { + additionalData: '$base-color: #c6538c;', + }, + }), + ], + // output: { + // dataUriLimit: { + // svg: 0 + // } + // } +}); diff --git a/tests/integration/style/sass/bundle/package.json b/tests/integration/style/sass/bundle/package.json new file mode 100644 index 000000000..73bcec5eb --- /dev/null +++ b/tests/integration/style/sass/bundle/package.json @@ -0,0 +1,5 @@ +{ + "name": "sass-bundle-test", + "version": "1.0.0", + "private": true +} diff --git a/tests/integration/style/sass/bundle/rslib.config.ts b/tests/integration/style/sass/bundle/rslib.config.ts new file mode 100644 index 000000000..4d18c4f25 --- /dev/null +++ b/tests/integration/style/sass/bundle/rslib.config.ts @@ -0,0 +1,24 @@ +import { pluginSass } from '@rsbuild/plugin-sass'; +import { defineConfig } from '@rslib/core'; +import { generateBundleCjsConfig, generateBundleEsmConfig } from 'test-helper'; + +export default defineConfig({ + lib: [generateBundleEsmConfig(), generateBundleCjsConfig()], + source: { + entry: { + index: ['../__fixtures__/src/index.scss'], + }, + }, + plugins: [ + pluginSass({ + sassLoaderOptions: { + additionalData: '$base-color: #c6538c;', + }, + }), + ], + // output: { + // dataUriLimit: { + // svg: 0 + // } + // } +}); diff --git a/tests/integration/style/sass/index.test.ts b/tests/integration/style/sass/index.test.ts new file mode 100644 index 000000000..207fc6b27 --- /dev/null +++ b/tests/integration/style/sass/index.test.ts @@ -0,0 +1,44 @@ +import { join } from 'node:path'; +import { buildAndGetResults } from 'test-helper'; +import { expect, test } from 'vitest'; + +test('should extract css with pluginSass in bundle', async () => { + const fixturePath = join(__dirname, 'bundle'); + const { contents } = await buildAndGetResults(fixturePath, 'css'); + const esmFiles = Object.keys(contents.esm); + expect(esmFiles).toMatchInlineSnapshot(` + [ + "/tests/integration/style/sass/bundle/dist/esm/static/css/index.css", + ] + `); + + const cjsFiles = Object.keys(contents.cjs); + expect(cjsFiles).toMatchInlineSnapshot(` + [ + "/tests/integration/style/sass/bundle/dist/cjs/static/css/index.css", + ] + `); +}); + +test('should extract css with pluginSass in bundle-false', async () => { + const fixturePath = join(__dirname, 'bundle-false'); + const { contents } = await buildAndGetResults(fixturePath, 'css'); + const esmFiles = Object.keys(contents.esm); + + expect(esmFiles).toMatchInlineSnapshot(` + [ + "/tests/integration/style/sass/bundle-false/dist/esm/foundation/_code.css", + "/tests/integration/style/sass/bundle-false/dist/esm/foundation/_lists.css", + "/tests/integration/style/sass/bundle-false/dist/esm/index.css", + ] + `); + + const cjsFiles = Object.keys(contents.cjs); + expect(cjsFiles).toMatchInlineSnapshot(` + [ + "/tests/integration/style/sass/bundle-false/dist/cjs/foundation/_code.css", + "/tests/integration/style/sass/bundle-false/dist/cjs/foundation/_lists.css", + "/tests/integration/style/sass/bundle-false/dist/cjs/index.css", + ] + `); +}); diff --git a/tests/integration/style/style-inject/__fixtures__/basic/src/import.css b/tests/integration/style/style-inject/__fixtures__/basic/src/import.css new file mode 100644 index 000000000..3827c0489 --- /dev/null +++ b/tests/integration/style/style-inject/__fixtures__/basic/src/import.css @@ -0,0 +1,8 @@ +@import 'https://cdnjs.cloudflare.com/ajax/libs/modern-normalize/1.1.0/modern-normalize.css'; +@import url('https://cdnjs.cloudflare.com/ajax/libs/modern-normalize/1.0.0/modern-normalize.css'); +@import url('lib1.css'); +@import 'lib2.css'; + +.import { + background-image: url('https://cdnjs.cloudflare.com/ajax/libs/modern-normalize/1.1.0/modern-normalize.css'); +} diff --git a/tests/integration/style/style-inject/__fixtures__/basic/src/index.ts b/tests/integration/style/style-inject/__fixtures__/basic/src/index.ts new file mode 100644 index 000000000..7ef92bd7e --- /dev/null +++ b/tests/integration/style/style-inject/__fixtures__/basic/src/index.ts @@ -0,0 +1 @@ +import './import.css'; diff --git a/tests/integration/style/style-inject/__fixtures__/basic/src/lib1.css b/tests/integration/style/style-inject/__fixtures__/basic/src/lib1.css new file mode 100644 index 000000000..7e86aa1fd --- /dev/null +++ b/tests/integration/style/style-inject/__fixtures__/basic/src/lib1.css @@ -0,0 +1,3 @@ +.lib1 { + color: red; +} diff --git a/tests/integration/style/style-inject/__fixtures__/basic/src/lib2.css b/tests/integration/style/style-inject/__fixtures__/basic/src/lib2.css new file mode 100644 index 000000000..b109d5735 --- /dev/null +++ b/tests/integration/style/style-inject/__fixtures__/basic/src/lib2.css @@ -0,0 +1,3 @@ +.lib2 { + color: green; +} diff --git a/tests/integration/style/style-inject/__fixtures__/basic/tsconfig.json b/tests/integration/style/style-inject/__fixtures__/basic/tsconfig.json new file mode 100644 index 000000000..888d3e460 --- /dev/null +++ b/tests/integration/style/style-inject/__fixtures__/basic/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "@rslib/tsconfig/base", + "compilerOptions": { + "baseUrl": "./" + }, + "include": ["src"] +} diff --git a/tests/integration/style/style-inject/bundle/package.json b/tests/integration/style/style-inject/bundle/package.json new file mode 100644 index 000000000..a56d7c062 --- /dev/null +++ b/tests/integration/style/style-inject/bundle/package.json @@ -0,0 +1,6 @@ +{ + "name": "style-inject-bundle-test", + "version": "1.0.0", + "private": true, + "type": "module" +} diff --git a/tests/integration/style/style-inject/bundle/rslib.config.ts b/tests/integration/style/style-inject/bundle/rslib.config.ts new file mode 100644 index 000000000..b649cf8a8 --- /dev/null +++ b/tests/integration/style/style-inject/bundle/rslib.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from '@rslib/core'; +import { generateBundleCjsConfig, generateBundleEsmConfig } from 'test-helper'; + +export default defineConfig({ + lib: [generateBundleEsmConfig(), generateBundleCjsConfig()], + source: { + entry: { + index: '../__fixtures__/basic/src/index.ts', + }, + }, + output: { + injectStyles: true, + }, +}); diff --git a/tests/integration/style/style-inject/index.test.ts b/tests/integration/style/style-inject/index.test.ts new file mode 100644 index 000000000..535a2d296 --- /dev/null +++ b/tests/integration/style/style-inject/index.test.ts @@ -0,0 +1,13 @@ +import { join } from 'node:path'; +import { buildAndGetResults } from 'test-helper'; +import { expect, test } from 'vitest'; + +test('should extract css successfully with `output.styleInject = true` in bundle', async () => { + const fixturePath = join(__dirname, 'bundle'); + const { contents } = await buildAndGetResults(fixturePath, 'css'); + const esmFiles = Object.keys(contents.esm ?? {}); + expect(esmFiles).toMatchInlineSnapshot('[]'); + + const cjsFiles = Object.keys(contents.cjs ?? {}); + expect(cjsFiles).toMatchInlineSnapshot('[]'); +}); diff --git a/tests/integration/style/tailwindcss/bundle/package.json b/tests/integration/style/tailwindcss/bundle/package.json new file mode 100644 index 000000000..4d4afd6c9 --- /dev/null +++ b/tests/integration/style/tailwindcss/bundle/package.json @@ -0,0 +1,7 @@ +{ + "name": "tailwindcss-bundle-test", + "private": true, + "devDependencies": { + "tailwindcss": "^3.4.11" + } +} diff --git a/tests/integration/style/tailwindcss/bundle/postcss.config.ts b/tests/integration/style/tailwindcss/bundle/postcss.config.ts new file mode 100644 index 000000000..ee31a19ce --- /dev/null +++ b/tests/integration/style/tailwindcss/bundle/postcss.config.ts @@ -0,0 +1,9 @@ +const path = require('node:path'); + +export default { + plugins: { + tailwindcss: { + config: path.join(__dirname, './tailwind.config.cjs'), + }, + }, +}; diff --git a/tests/integration/style/tailwindcss/bundle/rslib.config.ts b/tests/integration/style/tailwindcss/bundle/rslib.config.ts new file mode 100644 index 000000000..a9b4c1f8c --- /dev/null +++ b/tests/integration/style/tailwindcss/bundle/rslib.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from '@rslib/core'; +import { generateBundleCjsConfig, generateBundleEsmConfig } from 'test-helper'; + +export default defineConfig({ + lib: [generateBundleEsmConfig(), generateBundleCjsConfig()], + source: { + entry: { + index: ['./src/index.ts'], + }, + }, + tools: { + lightningcssLoader: false, + }, +}); diff --git a/tests/integration/style/tailwindcss/bundle/src/index.css b/tests/integration/style/tailwindcss/bundle/src/index.css new file mode 100644 index 000000000..65dd5f63a --- /dev/null +++ b/tests/integration/style/tailwindcss/bundle/src/index.css @@ -0,0 +1 @@ +@tailwind utilities; diff --git a/tests/integration/style/tailwindcss/bundle/src/index.ts b/tests/integration/style/tailwindcss/bundle/src/index.ts new file mode 100644 index 000000000..7f4b8326e --- /dev/null +++ b/tests/integration/style/tailwindcss/bundle/src/index.ts @@ -0,0 +1,7 @@ +import './index.css'; + +const div = document.createElement('div'); + +div.classList.add('text-3xl', 'font-bold', 'underline'); + +export { div }; diff --git a/tests/integration/style/tailwindcss/bundle/tailwind.config.cjs b/tests/integration/style/tailwindcss/bundle/tailwind.config.cjs new file mode 100644 index 000000000..e03a0c3e0 --- /dev/null +++ b/tests/integration/style/tailwindcss/bundle/tailwind.config.cjs @@ -0,0 +1,10 @@ +const path = require('node:path'); + +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [path.join(__dirname, './src/**/*.{html,js,ts,jsx,tsx}')], + theme: { + extend: {}, + }, + plugins: [], +}; diff --git a/tests/integration/style/tailwindcss/index.test.ts b/tests/integration/style/tailwindcss/index.test.ts new file mode 100644 index 000000000..9b04b32b7 --- /dev/null +++ b/tests/integration/style/tailwindcss/index.test.ts @@ -0,0 +1,32 @@ +import { join } from 'node:path'; +import { buildAndGetResults } from 'test-helper'; +import { expectFileContainContent } from 'test-helper/vitest'; +import { expect, test } from 'vitest'; + +test('should extract css when using tailwindcss successfully in bundle', async () => { + const fixturePath = join(__dirname, 'bundle'); + const { contents } = await buildAndGetResults(fixturePath, 'css'); + const esmFiles = Object.keys(contents.esm); + expect(esmFiles).toMatchInlineSnapshot(` + [ + "/tests/integration/style/tailwindcss/bundle/dist/esm/static/css/index.css", + ] + `); + expectFileContainContent(contents.esm, 'index.css', [ + '.text-3xl {', + '.font-bold {', + '.underline {', + ]); + + const cjsFiles = Object.keys(contents.cjs); + expect(cjsFiles).toMatchInlineSnapshot(` + [ + "/tests/integration/style/tailwindcss/bundle/dist/cjs/static/css/index.css", + ] + `); + expectFileContainContent(contents.cjs, 'index.css', [ + '.text-3xl {', + '.font-bold {', + '.underline {', + ]); +}); diff --git a/tests/package.json b/tests/package.json index beec47b2e..6363ae238 100644 --- a/tests/package.json +++ b/tests/package.json @@ -7,7 +7,6 @@ "test:e2e": "playwright test --pass-with-no-tests" }, "dependencies": { - "@examples/react-component": "workspace:*", "react": "^18.3.1", "react-dom": "^18.3.1" }, @@ -15,7 +14,9 @@ "@codspeed/vitest-plugin": "^3.1.1", "@playwright/test": "1.47.2", "@rsbuild/core": "1.0.7", - "@rsbuild/plugin-react": "1.0.2", + "@rsbuild/plugin-less": "^1.0.1", + "@rsbuild/plugin-react": "^1.0.2", + "@rsbuild/plugin-sass": "^1.0.1", "@rslib/core": "workspace:*", "@rslib/tsconfig": "workspace:*", "@types/fs-extra": "^11.0.4", diff --git a/tests/scripts/shared.ts b/tests/scripts/shared.ts index d8d9067fb..71e4b5d07 100644 --- a/tests/scripts/shared.ts +++ b/tests/scripts/shared.ts @@ -1,3 +1,4 @@ +import assert from 'node:assert'; import fs from 'node:fs'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -241,3 +242,14 @@ export function generateFileTree(dir: string) { return fileTree; } + +export function getFileBySuffix( + files: Record, + suffix: string, +): string { + const fileName = Object.keys(files).find((file) => file.endsWith(suffix)); + assert(fileName); + const content = files[fileName]; + assert(content); + return content; +} diff --git a/tests/scripts/vitest.ts b/tests/scripts/vitest.ts new file mode 100644 index 000000000..a4480cffe --- /dev/null +++ b/tests/scripts/vitest.ts @@ -0,0 +1,17 @@ +// This file can only be imported by vitest test files +import { expect } from 'vitest'; +import { getFileBySuffix } from './shared'; + +export function expectFileContainContent( + files: Record, + suffix: string, + content: string | string[], +) { + const fileContent = getFileBySuffix(files, suffix); + + const contents = Array.isArray(content) ? content : [content]; + + for (const text of contents) { + expect(fileContent).toContain(text); + } +} diff --git a/tests/tsconfig.json b/tests/tsconfig.json index f3e545a11..9d8bd3139 100644 --- a/tests/tsconfig.json +++ b/tests/tsconfig.json @@ -5,7 +5,8 @@ "composite": true }, "include": [ - "e2e/cases/**/*.ts", + "e2e/**/*.ts", + "integration/**/*.ts", "benchmark/**/*.ts", "playwright.config.ts", "scripts"