@s-ui/bundler 9.78.0 → 9.79.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/tailwind-v4-integration.md +71 -0
- package/package.json +1 -1
- package/shared/module-rules-sass.js +20 -1
- package/shared/plain-css-split-chunks.js +25 -0
- package/webpack.config.client.dev.js +3 -28
- package/webpack.config.dev.js +17 -0
- package/webpack.config.lib.js +1 -1
- package/webpack.config.prod.js +4 -2
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Tailwind v4 integration in `@s-ui/bundler` — investigation & alternatives
|
|
2
|
+
|
|
3
|
+
## Context
|
|
4
|
+
|
|
5
|
+
- `@adv-mt/ui` (Milanuncios DS) ships Tailwind **v4** CSS via `@tailwindcss/postcss`.
|
|
6
|
+
- `@adv-re/ui` (Fotocasa DS) uses Tailwind **v3**.
|
|
7
|
+
- `@s-ui/bundler` (`9.78.0`) hard-pins `tailwindcss@3.4.14` and enables the plugin only when a `tailwind.config.js` file exists at the project root.
|
|
8
|
+
- Tailwind v4 has an incompatible API: CSS-first config (`@import "tailwindcss"`, `@source`, `@theme`), no JS config by default, plugin renamed to `@tailwindcss/postcss`, built-in vendor prefixing.
|
|
9
|
+
|
|
10
|
+
Relevant code: `packages/sui-bundler/shared/module-rules-sass.js`, `shared/index.js` (`isTailwindEnabled`), `webpack.config.dev.js`, `webpack.config.client.dev.js`.
|
|
11
|
+
|
|
12
|
+
Relevant external work:
|
|
13
|
+
- MA integration PR — `frontend-ma--web-app#7667`
|
|
14
|
+
- Strategy doc — `frontend-ma--web-app/docs/design-system-css-strategy.md`
|
|
15
|
+
- Bundler PR — [SUI-Components/sui#1988](https://github.com/SUI-Components/sui/pull/1988) (`plainCssPackages` config to bypass sass-loader for plain CSS packages)
|
|
16
|
+
|
|
17
|
+
## Alternatives
|
|
18
|
+
|
|
19
|
+
### A. Land #1988 (`plainCssPackages`) — patch release
|
|
20
|
+
|
|
21
|
+
Bypass sass + postcss for pre-built DS CSS. No Tailwind upgrade.
|
|
22
|
+
|
|
23
|
+
- Pros: zero risk for Fotocasa; unblocks MA today; small surface.
|
|
24
|
+
- Cons: doesn't enable app-build Tailwind (Option 2 in MA's strategy doc); MA keeps shipping the full pre-built `@adv-mt/ui/styles.css`.
|
|
25
|
+
|
|
26
|
+
### B. Peer-dep Tailwind — major (v10) — **recommended**
|
|
27
|
+
|
|
28
|
+
Remove `tailwindcss` from bundler deps. Consumer installs `tailwindcss@3` OR `tailwindcss@4` + `@tailwindcss/postcss`. Bundler detects which is installed and wires the correct postcss plugin.
|
|
29
|
+
|
|
30
|
+
- Pros: no forced migration; each team upgrades on its own schedule; future-proof; bundler owns no Tailwind bugs.
|
|
31
|
+
- Cons: more setup for consumers (one extra install); detection logic in bundler.
|
|
32
|
+
|
|
33
|
+
### C. Hard upgrade v3 → v4 — major (v10)
|
|
34
|
+
|
|
35
|
+
Replace `tailwindcss@3` with `@tailwindcss/postcss@4`. Drop `tailwind.config.js` detection. Fotocasa migrates via `npx @tailwindcss/upgrade`.
|
|
36
|
+
|
|
37
|
+
- Pros: clean, single path forward.
|
|
38
|
+
- Cons: forces Fotocasa work; release coordination.
|
|
39
|
+
|
|
40
|
+
### D. Dual-mode flag — major (v10)
|
|
41
|
+
|
|
42
|
+
Ship both `tailwindcss@3` and `@tailwindcss/postcss@4`. Detect entry (`tailwind.config.js` → v3, `tailwind.css`/CSS-first → v4) and pick plugin per project. Remove v3 in v11.
|
|
43
|
+
|
|
44
|
+
- Pros: smooth ramp; opt-in v4.
|
|
45
|
+
- Cons (maintenance is moderate, not trivial):
|
|
46
|
+
- Two Tailwind deps shipped — bigger install for every consumer, even single-version ones.
|
|
47
|
+
- Branching pipeline lives in `module-rules-sass.js` + 3 webpack configs.
|
|
48
|
+
- Two doc paths in README.
|
|
49
|
+
- Test matrix doubles — every bundler release verified against v3 and v4.
|
|
50
|
+
- Bug surface: plugin order, autoprefixer overlap with v4's built-in prefixing, sourcemaps differ.
|
|
51
|
+
- Sunset still requires Fotocasa migration in v11 — work is deferred, not avoided.
|
|
52
|
+
- Verdict: real cost is owning v3 bugs in sui-bundler for ~a year. Worse than B unless Fotocasa explicitly wants zero-effort upgrade.
|
|
53
|
+
|
|
54
|
+
### E. Skip bundler change; push DS to per-component CSS exports
|
|
55
|
+
|
|
56
|
+
Pair with #1988. DS publishes `@adv-mt/ui/button/styles.css` etc. Sidesteps v4-in-bundler entirely until both teams want app-build Tailwind.
|
|
57
|
+
|
|
58
|
+
- Pros: no bundler v10 needed.
|
|
59
|
+
- Cons: requires DS build changes; consumer tracks per-page imports; no automatic tree-shaking.
|
|
60
|
+
|
|
61
|
+
## Recommendation
|
|
62
|
+
|
|
63
|
+
1. **Now** — merge #1988, patch release. Unblocks MA today, zero risk to Fotocasa.
|
|
64
|
+
2. **Next major (v10)** — option **B** (peer-dep). Best ergonomics across teams; lets each move independently; gives MA the path to app-build Tailwind v4 without forcing Fotocasa.
|
|
65
|
+
3. **Fallback** if peer-dep complexity is too high → option **D**, drop v3 in v11.
|
|
66
|
+
|
|
67
|
+
## Open questions
|
|
68
|
+
|
|
69
|
+
- Does Fotocasa have appetite to migrate to v4 (with codemod) within v10's window? If yes, **C** becomes viable and simpler than B.
|
|
70
|
+
- Are there other sui-bundler consumers using Tailwind v3? Audit before committing to B/C/D.
|
|
71
|
+
- Does autoprefixer need to be removed/conditional under v4? v4 has built-in vendor prefixing.
|
package/package.json
CHANGED
|
@@ -2,8 +2,17 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
|
|
2
2
|
|
|
3
3
|
const {cleanList, config, when, isTailwindEnabled} = require('./index')
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
const plainCssPackages = config.plainCssPackages || []
|
|
6
|
+
|
|
7
|
+
const plainCssExclude = plainCssPackages.length
|
|
8
|
+
? new RegExp(
|
|
9
|
+
`node_modules[\\\\/](${plainCssPackages.map(p => p.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|')})[\\\\/]`
|
|
10
|
+
)
|
|
11
|
+
: null
|
|
12
|
+
|
|
13
|
+
const sassRule = {
|
|
6
14
|
test: /(\.css|\.scss)$/,
|
|
15
|
+
...(plainCssExclude ? {exclude: plainCssExclude} : {}),
|
|
7
16
|
use: cleanList([
|
|
8
17
|
MiniCssExtractPlugin.loader,
|
|
9
18
|
require.resolve('css-loader'),
|
|
@@ -29,3 +38,13 @@ module.exports = {
|
|
|
29
38
|
require.resolve('@s-ui/sass-loader')
|
|
30
39
|
])
|
|
31
40
|
}
|
|
41
|
+
|
|
42
|
+
const plainCssRule = plainCssExclude
|
|
43
|
+
? {
|
|
44
|
+
test: /\.css$/,
|
|
45
|
+
include: plainCssExclude,
|
|
46
|
+
use: [MiniCssExtractPlugin.loader, require.resolve('css-loader')]
|
|
47
|
+
}
|
|
48
|
+
: null
|
|
49
|
+
|
|
50
|
+
module.exports = plainCssRule ? [sassRule, plainCssRule] : sassRule
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const {config} = require('./index')
|
|
2
|
+
|
|
3
|
+
const plainCssPackages = config.plainCssPackages || []
|
|
4
|
+
|
|
5
|
+
exports.plainCssSplitChunks = () => {
|
|
6
|
+
if (!plainCssPackages.length) return {}
|
|
7
|
+
|
|
8
|
+
const testRegex = new RegExp(
|
|
9
|
+
`[\\\\/]node_modules[\\\\/](${plainCssPackages.map(p => p.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|')})[\\\\/]`
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
splitChunks: {
|
|
14
|
+
cacheGroups: {
|
|
15
|
+
plainCssInEntry: {
|
|
16
|
+
type: 'css/mini-extract',
|
|
17
|
+
test: testRegex,
|
|
18
|
+
name: 'AppStyles',
|
|
19
|
+
chunks: 'all',
|
|
20
|
+
enforce: true
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -9,9 +9,10 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
|
|
9
9
|
const HtmlWebpackInjectAttributesPlugin = require('html-webpack-inject-attributes-plugin')
|
|
10
10
|
const {withHydrationOverlayWebpack} = require('@builder.io/react-hydration-overlay/webpack')
|
|
11
11
|
|
|
12
|
-
const {envVars, MAIN_ENTRY_POINT, config, cleanList, when
|
|
12
|
+
const {envVars, MAIN_ENTRY_POINT, config, cleanList, when} = require('./shared/index.js')
|
|
13
13
|
const definePlugin = require('./shared/define.js')
|
|
14
14
|
const manifestLoaderRules = require('./shared/module-rules-manifest-loader.js')
|
|
15
|
+
const sassRules = require('./shared/module-rules-sass.js')
|
|
15
16
|
const createSVGSpritemapPlugin = require('./shared/svg-spritemap')
|
|
16
17
|
const {aliasFromConfig, defaultAlias} = require('./shared/resolve-alias.js')
|
|
17
18
|
const {supportLegacyBrowsers, cacheDirectory} = require('./shared/config.js')
|
|
@@ -96,33 +97,7 @@ const webpackConfig = {
|
|
|
96
97
|
module: {
|
|
97
98
|
rules: cleanList([
|
|
98
99
|
createCompilerRules({supportLegacyBrowsers, isDevelopment: true}),
|
|
99
|
-
|
|
100
|
-
test: /(\.css|\.scss)$/,
|
|
101
|
-
use: cleanList([
|
|
102
|
-
MiniCssExtractPlugin.loader,
|
|
103
|
-
require.resolve('css-loader'),
|
|
104
|
-
when(config['externals-manifest'], () => ({
|
|
105
|
-
loader: 'externals-manifest-loader',
|
|
106
|
-
options: {
|
|
107
|
-
manifestURL: config['externals-manifest']
|
|
108
|
-
}
|
|
109
|
-
})),
|
|
110
|
-
{
|
|
111
|
-
loader: require.resolve('postcss-loader'),
|
|
112
|
-
options: {
|
|
113
|
-
postcssOptions: {
|
|
114
|
-
plugins: [
|
|
115
|
-
...(isTailwindEnabled() ? [require('tailwindcss')()] : []),
|
|
116
|
-
require('autoprefixer')({
|
|
117
|
-
overrideBrowserslist: config.targets
|
|
118
|
-
})
|
|
119
|
-
]
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
},
|
|
123
|
-
require.resolve('@s-ui/sass-loader')
|
|
124
|
-
])
|
|
125
|
-
},
|
|
100
|
+
...(Array.isArray(sassRules) ? sassRules : [sassRules]),
|
|
126
101
|
when(config['externals-manifest'], () => manifestLoaderRules(config['externals-manifest']))
|
|
127
102
|
])
|
|
128
103
|
},
|
package/webpack.config.dev.js
CHANGED
|
@@ -14,6 +14,13 @@ const {supportLegacyBrowsers, cacheDirectory} = require('./shared/config.js')
|
|
|
14
14
|
const {resolveLoader} = require('./shared/resolve-loader.js')
|
|
15
15
|
const createCompilerRules = require('./shared/module-rules-compiler.js')
|
|
16
16
|
|
|
17
|
+
const plainCssPackages = config.plainCssPackages || []
|
|
18
|
+
const plainCssExclude = plainCssPackages.length
|
|
19
|
+
? new RegExp(
|
|
20
|
+
`node_modules[\\\\/](${plainCssPackages.map(p => p.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|')})[\\\\/]`
|
|
21
|
+
)
|
|
22
|
+
: null
|
|
23
|
+
|
|
17
24
|
const outputPath = path.join(process.cwd(), 'dist')
|
|
18
25
|
|
|
19
26
|
const {CI = false} = process.env
|
|
@@ -90,6 +97,7 @@ const webpackConfig = {
|
|
|
90
97
|
createCompilerRules({supportLegacyBrowsers, isDevelopment: true}),
|
|
91
98
|
{
|
|
92
99
|
test: /(\.css|\.scss)$/,
|
|
100
|
+
...(plainCssExclude ? {exclude: plainCssExclude} : {}),
|
|
93
101
|
use: cleanList([
|
|
94
102
|
require.resolve('style-loader'),
|
|
95
103
|
when(config['externals-manifest'], () => ({
|
|
@@ -115,6 +123,15 @@ const webpackConfig = {
|
|
|
115
123
|
require.resolve('@s-ui/sass-loader')
|
|
116
124
|
])
|
|
117
125
|
},
|
|
126
|
+
...(plainCssExclude
|
|
127
|
+
? [
|
|
128
|
+
{
|
|
129
|
+
test: /\.css$/,
|
|
130
|
+
include: plainCssExclude,
|
|
131
|
+
use: [require.resolve('style-loader'), require.resolve('css-loader')]
|
|
132
|
+
}
|
|
133
|
+
]
|
|
134
|
+
: []),
|
|
118
135
|
when(config['externals-manifest'], () => manifestLoaderRules(config['externals-manifest']))
|
|
119
136
|
])
|
|
120
137
|
},
|
package/webpack.config.lib.js
CHANGED
|
@@ -60,7 +60,7 @@ module.exports = ({chunkCss} = {}) => {
|
|
|
60
60
|
definePlugin()
|
|
61
61
|
]),
|
|
62
62
|
module: {
|
|
63
|
-
rules: [createCompilerRules({supportLegacyBrowsers}), sassRules]
|
|
63
|
+
rules: [createCompilerRules({supportLegacyBrowsers}), ...(Array.isArray(sassRules) ? sassRules : [sassRules])]
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
}
|
package/webpack.config.prod.js
CHANGED
|
@@ -10,6 +10,7 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
|
|
10
10
|
const InlineChunkHtmlPlugin = require('./shared/inline-chunk-html-plugin.js')
|
|
11
11
|
|
|
12
12
|
const {when, cleanList, envVars, MAIN_ENTRY_POINT, config} = require('./shared/index.js')
|
|
13
|
+
const {plainCssSplitChunks} = require('./shared/plain-css-split-chunks.js')
|
|
13
14
|
const {aliasFromConfig} = require('./shared/resolve-alias.js')
|
|
14
15
|
const {extractComments, sourceMap, supportLegacyBrowsers, cacheDirectory} = require('./shared/config.js')
|
|
15
16
|
const {resolveLoader} = require('./shared/resolve-loader.js')
|
|
@@ -65,7 +66,8 @@ const webpackConfig = {
|
|
|
65
66
|
checkWasmTypes: false,
|
|
66
67
|
minimize: true,
|
|
67
68
|
minimizer: [minifyJs({extractComments, sourceMap}), minifyCss()].filter(Boolean),
|
|
68
|
-
runtimeChunk: true
|
|
69
|
+
runtimeChunk: true,
|
|
70
|
+
...plainCssSplitChunks()
|
|
69
71
|
},
|
|
70
72
|
cache: {
|
|
71
73
|
type: 'filesystem',
|
|
@@ -106,7 +108,7 @@ const webpackConfig = {
|
|
|
106
108
|
module: {
|
|
107
109
|
rules: cleanList([
|
|
108
110
|
createCompilerRules({supportLegacyBrowsers}),
|
|
109
|
-
sassRules,
|
|
111
|
+
...(Array.isArray(sassRules) ? sassRules : [sassRules]),
|
|
110
112
|
when(config['externals-manifest'], () => manifestLoaderRules(config['externals-manifest']))
|
|
111
113
|
])
|
|
112
114
|
},
|