Anyone successfully integrated Svelte into Phoenix 1.6.x + esbuild?

@silverdr I’d love to hear about that, whenever you have time - at the moment I haven’t really tried stretching Svelte too much, so I haven’t encountered the edge cases I’m sure exist.

Now that I am now mostly done with migration to 1.6.x. I’ll have to return to this so I’ll get back to you too :wink:

Close to successful migration to 1.6.x. What I am also very happy about is that I found:

which allows the project to be both webpack and node.js cancer-free :wink:

But… now it’s time to get back to Svelte components, which are used in some places, and that most probably means that “Revenge Of Node_Modules” is coming…

Or maybe somebody thought out a way to get Svelte into Phoenix w/o node?

I understand it might be more tricky than Tailwind, but maybe?

1 Like

I wrote a short blog post about it:

1 Like

@yendrisrogelio I managed to get basic functionality working now. The issue, which your example repository does not tackle is Svelte components’ internal CSS styles. CSS for component’s markup works only as long as you put tailwind classes directly. IOW words this works:

<script>
  console.log("test");
</script>

<h1 class="text-red-500 text-5xl font-bold text-center">Tailwind CSS 4444</h1>

but this does not:

<script>
  console.log("test");
</script>

<style>
	h1 {
		@apply text-blue-700;
	}
</style>

<h1 class="text-5xl font-bold text-center">Tailwind CSS 4444</h1>

Even though it worked with the old build system.

Any chances for you to have a look at it and maybe come up with a solution?

Sorry for the late reply but I’ve been busy with work. The problem you have is the following, you are trying to apply postcss directives inside the styles of svelte. So that svelte can understand it you need to install svelte-preprocess to your dependencies cd assets && npm i svelte-preprocess, then you must modify your build.js file finally being like this…

const esbuild = require("esbuild")
const sveltePlugin = require("esbuild-svelte")
const sveltePreprocess = require("svelte-preprocess");

const args = process.argv.slice(2)
const watch = args.includes('--watch')
const deploy = args.includes('--deploy')

let opts = {
  entryPoints: ['js/app.js'],
  bundle: true,
  target: 'es2016',
  outdir: '../priv/static/assets',
  logLevel: 'info',
  plugins: [
    sveltePlugin({
        preprocess: sveltePreprocess({
			postcss: {
				plugins: [
					require("tailwindcss"), 
					require("autoprefixer"),
				],
			},
		}),
      })
  ]
}

if (watch) {
  opts = {
    ...opts,
    watch,
    sourcemap: 'inline'
  }
}

if (deploy) {
  opts = {
    ...opts,
    minify: true
  }
}

const promise = esbuild.build(opts)

if (watch) {
  promise.then(_result => {
    process.stdin.on('close', () => {
      process.exit(0)
    })

    process.stdin.resume()
  })
}

So your svelte component works perfectly with tailwindcss

<script>
  console.log("test");
</script>

<style>
	h1 {
		@apply text-blue-700;
	}
</style>

<h1 class="text-5xl font-bold text-center">Tailwind CSS 4444</h1>

Thank you very much for finding time to have a look and respond.

I see. And you are certainly right for this particular case. Yet I should have given a bit more context rather than simply use your demo for the example. What I have in the project is:

a) I already have everything needed to “compile” tailwind stuff, including all @apply directives (PostCSS). This comes from phoenixframework / tailwind,

b) esbuild-svelte buildscript build.js properly processes and extracts Svelte specific stylesheet files.

Therefore in the perfect world phoenixframework / tailwind should be able to compile them and bundle the results in one go, along with everything else it already does. Still I don’t seem to get it to work, at least in a non-smelly way. Any clues?

The problem is that Svelte module’s specific classes don’t make it to the final bundle at all. And that’s understandable because in default configuration both output to the same destination w/o knowing about each other. I can make tailwind output to a different file so that it doesn’t overwrite the one that comes from Svelte but I am having hard time getting the Svelte one processed. I can’t @import that into app.css because “@apply is not supported within nested at-rules like @import”. I can’t pass it as another input to tailwind because

and adding another watcher or so feels smelly.

Since it gets a bit of a different topic I started another thread for this

Right now I don’t have much time to try a solution, but maybe you could try another way, I don’t know if it’s good for you, but maybe it could work

It’s best to inject all of the above into a high-level component so they’re accessible on every page. You can inject them in the App.svelte file:

<script>
  console.log("test");
</script>

<style global lang="postcss">
  @tailwind base;
  @tailwind components;
  @tailwind utilities;
  
  h1 {
		@apply text-blue-700;
	}
</style>

<h1 class="text-5xl font-bold text-center">Tailwind CSS 4444</h1>

Then you would already have tailwind on all the nested components of the svelte to be used
and maybe this way you avoid svelte class conflicts. I don’t know but maybe that’s how it works for you


It works

1 Like

I’m the author of: GitHub - woutdp/live_svelte: Render Svelte directly into Phoenix LiveView with E2E reactivity.

I mostly focussed on Phoenix 1.7 but should be compatible with 1.6 I think.

It includes svelte-preprocess and tailwind with Phoenix’s built in tailwind setup. Also has SSR for Svelte components.

3 Likes

I eventually made it work well. Need to find some time to wrap the findings up and post. Although I started with 1.6, it continues to work with 1.7 with no changes. This means there’s a chance it works the other way around too :slight_smile: