Skip to content

Commit

Permalink
feat: add tailwindcss 4.0 (#422)
Browse files Browse the repository at this point in the history
* feat: ADD tailwindcss 4.0

* cleanup

* simplify app.css

* update vite.config.tjs

* import addDefault

* rgb to oklch

* 👌 FIX: linting (FTW)

* correct oklch (tmp only tailwind)

* back to all

* typography without any plugin ✅

* Update .changeset/empty-geese-draw.md

Co-authored-by: CokaKoala <[email protected]>

* Update .changeset/empty-geese-draw.md

---------

Co-authored-by: CokaKoala <[email protected]>
Co-authored-by: Manuel <[email protected]>
  • Loading branch information
3 people authored Feb 7, 2025
1 parent 41cc5f6 commit abe84f2
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 117 deletions.
5 changes: 5 additions & 0 deletions .changeset/empty-geese-draw.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'sv': patch
---

feat: update to `tailwindcss` v4.0.0
39 changes: 21 additions & 18 deletions packages/addons/_tests/tailwindcss/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,28 @@ test.concurrent.for(variants)('none - %s', async (variant, { page, ...ctx }) =>
ctx.onTestFinished(async () => await close());

const el = page.getByTestId('base');
await expect(el).toHaveCSS('background-color', 'rgb(71, 85, 105)');
await expect(el).toHaveCSS('border-color', 'rgb(249, 250, 251)');
await expect(el).toHaveCSS('background-color', 'oklch(0.446 0.043 257.281)');
await expect(el).toHaveCSS('border-color', 'oklch(0.985 0.002 247.839)');
await expect(el).toHaveCSS('border-width', '4px');
await expect(el).toHaveCSS('margin-top', '4px');
});

test.concurrent.for(variants)('typography - %s', async (variant, { page, ...ctx }) => {
const cwd = await ctx.run(variant, { tailwindcss: { plugins: ['typography'] } });

// ...add files
addFixture(cwd, variant);

const { close } = await prepareServer({ cwd, page });
// kill server process when we're done
ctx.onTestFinished(async () => await close());

const el = page.getByTestId('typography');
await expect(el).toHaveCSS('font-size', '18px');
await expect(el).toHaveCSS('line-height', '28px');
await expect(el).toHaveCSS('text-align', 'right');
await expect(el).toHaveCSS('text-decoration-line', 'line-through');
});
test.concurrent.for(variants)(
'typography without plugin - %s',
async (variant, { page, ...ctx }) => {
const cwd = await ctx.run(variant, { tailwindcss });

// ...add files
addFixture(cwd, variant);

const { close } = await prepareServer({ cwd, page });
// kill server process when we're done
ctx.onTestFinished(async () => await close());

const el = page.getByTestId('typography');
await expect(el).toHaveCSS('font-size', '18px');
await expect(el).toHaveCSS('line-height', '28px');
await expect(el).toHaveCSS('text-align', 'right');
await expect(el).toHaveCSS('text-decoration-line', 'line-through');
}
);
118 changes: 19 additions & 99 deletions packages/addons/tailwindcss/index.ts
Original file line number Diff line number Diff line change
@@ -1,130 +1,50 @@
import { defineAddon, defineAddonOptions } from '@sveltejs/cli-core';
import { defineAddon } from '@sveltejs/cli-core';
import { addImports } from '@sveltejs/cli-core/css';
import { array, common, exports, imports, object } from '@sveltejs/cli-core/js';
import { parseCss, parseScript, parseJson, parseSvelte } from '@sveltejs/cli-core/parsers';
import { array, functions, imports, object, exports } from '@sveltejs/cli-core/js';
import { parseCss, parseJson, parseScript, parseSvelte } from '@sveltejs/cli-core/parsers';
import { addSlot } from '@sveltejs/cli-core/html';

type Plugin = {
id: string;
package: string;
version: string;
identifier: string;
};

const plugins: Plugin[] = [
{
id: 'typography',
package: '@tailwindcss/typography',
version: '^0.5.16',
identifier: 'typography'
},
{
id: 'forms',
package: '@tailwindcss/forms',
version: '^0.5.10',
identifier: 'forms'
},
{
id: 'container-queries',
package: '@tailwindcss/container-queries',
version: '^0.1.1',
identifier: 'containerQueries'
}
];

const options = defineAddonOptions({
plugins: {
type: 'multiselect',
question: 'Which plugins would you like to add?',
options: plugins.map((p) => ({ value: p.id, label: p.id, hint: p.package })),
default: []
}
});

export default defineAddon({
id: 'tailwindcss',
alias: 'tailwind',
shortDescription: 'css framework',
homepage: 'https://tailwindcss.com',
options,
run: ({ sv, options, typescript, kit, dependencyVersion }) => {
options: {},
run: ({ sv, typescript, kit, dependencyVersion }) => {
const ext = typescript ? 'ts' : 'js';
const prettierInstalled = Boolean(dependencyVersion('prettier'));

sv.devDependency('tailwindcss', '^3.4.17');
sv.devDependency('autoprefixer', '^10.4.20');

if (prettierInstalled) sv.devDependency('prettier-plugin-tailwindcss', '^0.6.10');
sv.devDependency('tailwindcss', '^4.0.0');
sv.devDependency('@tailwindcss/vite', '^4.0.0');

for (const plugin of plugins) {
if (!options.plugins.includes(plugin.id)) continue;
if (prettierInstalled) sv.devDependency('prettier-plugin-tailwindcss', '^0.6.11');

sv.devDependency(plugin.package, plugin.version);
}

sv.file(`tailwind.config.${ext}`, (content) => {
// add the vite plugin
sv.file(`vite.config.${ext}`, (content) => {
const { ast, generateCode } = parseScript(content);
let root;
const rootExport = object.createEmpty();
if (typescript) {
imports.addNamed(ast, 'tailwindcss', { Config: 'Config' }, true);
root = common.satisfiesExpression(rootExport, 'Config');
}

const { astNode: exportDeclaration, value: node } = exports.defaultExport(
ast,
root ?? rootExport
);

const config = node.type === 'TSSatisfiesExpression' ? node.expression : node;
if (config.type !== 'ObjectExpression') {
throw new Error(`Unexpected tailwind config shape: ${config.type}`);
}

if (!typescript) {
common.addJsDocTypeComment(exportDeclaration, "import('tailwindcss').Config");
}

const contentArray = object.property(config, 'content', array.createEmpty());
array.push(contentArray, './src/**/*.{html,js,svelte,ts}');

const themeObject = object.property(config, 'theme', object.createEmpty());
object.property(themeObject, 'extend', object.createEmpty());

const pluginsArray = object.property(config, 'plugins', array.createEmpty());
const vitePluginName = 'tailwindcss';
imports.addDefault(ast, '@tailwindcss/vite', vitePluginName);

for (const plugin of plugins) {
if (!options.plugins.includes(plugin.id)) continue;
imports.addDefault(ast, plugin.package, plugin.identifier);
array.push(pluginsArray, { type: 'Identifier', name: plugin.identifier });
}
const { value: rootObject } = exports.defaultExport(ast, functions.call('defineConfig', []));
const param1 = functions.argumentByIndex(rootObject, 0, object.createEmpty());

return generateCode();
});

sv.file('postcss.config.js', (content) => {
const { ast, generateCode } = parseScript(content);
const { value: rootObject } = exports.defaultExport(ast, object.createEmpty());
const pluginsObject = object.property(rootObject, 'plugins', object.createEmpty());
const pluginsArray = object.property(param1, 'plugins', array.createEmpty());
const pluginFunctionCall = functions.call(vitePluginName, []);
array.push(pluginsArray, pluginFunctionCall);

object.property(pluginsObject, 'tailwindcss', object.createEmpty());
object.property(pluginsObject, 'autoprefixer', object.createEmpty());
return generateCode();
});

sv.file('src/app.css', (content) => {
const layerImports = ['base', 'components', 'utilities'].map(
(layer) => `tailwindcss/${layer}`
);
if (layerImports.every((i) => content.includes(i))) {
if (content.includes('tailwindcss')) {
return content;
}

const { ast, generateCode } = parseCss(content);
const originalFirst = ast.first;

const specifiers = layerImports.map((i) => `'${i}'`);
const nodes = addImports(ast, specifiers);
const nodes = addImports(ast, ["'tailwindcss'"]);

if (
originalFirst !== ast.first &&
Expand Down

0 comments on commit abe84f2

Please sign in to comment.