diff --git a/.changeset/empty-geese-draw.md b/.changeset/empty-geese-draw.md new file mode 100644 index 00000000..893102b7 --- /dev/null +++ b/.changeset/empty-geese-draw.md @@ -0,0 +1,5 @@ +--- +'sv': patch +--- + +feat: update to `tailwindcss` v4.0.0 diff --git a/packages/addons/_tests/tailwindcss/test.ts b/packages/addons/_tests/tailwindcss/test.ts index 1ede1055..cd692cfa 100644 --- a/packages/addons/_tests/tailwindcss/test.ts +++ b/packages/addons/_tests/tailwindcss/test.ts @@ -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'); + } +); diff --git a/packages/addons/tailwindcss/index.ts b/packages/addons/tailwindcss/index.ts index 80c581b1..914e2005 100644 --- a/packages/addons/tailwindcss/index.ts +++ b/packages/addons/tailwindcss/index.ts @@ -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 &&