Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose resolved viteConfig for 3rd party libraries #4329

Closed
JessicaSachs opened this issue Mar 15, 2022 · 7 comments
Closed

Expose resolved viteConfig for 3rd party libraries #4329

JessicaSachs opened this issue Mar 15, 2022 · 7 comments
Labels
feature / enhancement New feature or request

Comments

@JessicaSachs
Copy link

JessicaSachs commented Mar 15, 2022

Describe the problem

Tools like Vitest, Cypress, and Storybook want to support SvelteKit. These projects need to bundle components that users write in their SvelteKit apps and need a programmatic API to kick off static bundling or serving the app's dev server. To ensure that the code that's being tested and rendered behaves like it will in production, it's critical to use the user's resolved build configuration instead of making them "eject" and re-define all of the compilation options.

Describe the proposed solution

Meta build-tools like CRACO, CRA, Vue CLI, Next.js, and Nuxt.js all provide ways to access the underlying, "compiled" Webpack configurations used to build and bundle the application's source code. These APIs allow 3rd party libraries like Vitest and Cypress to ingest the user's build configuration and then compile their code in a different context than that of the SvelteKit (or Nuxt, etc) framework.

Getting the Config and Merging it in w/ Vite

Static API

import { getResolvedConfig } from '@sveltejs/kit'

Merging w/ vite

import { mergeConfig, createServer } from 'vite'
import { getResolvedConfig } from '@sveltejs/kit'

const viteConfigOverrides = {
  plugins: [
    Cypress(), // Mount all of the components as pages and add routing
    // or Vitest's mocking plugin, enabling ESM mocks
    // Mocks()
  ]
}

const finalConfig = mergeConfig(getResovledConfig(), viteConfigOverrides)
const server = await createServer(finalConfig) // or otherwise compile + ingest the build configuration

Alternatives considered

You could also re-export/otherwise wrap Vite's createServer and mergeConfig methods. This would make dependency management less of a pain, but I'm not asking for it because I think it's possible to meet the needs for Cy and Vitest without this API.

Dev Server API

import { createServer } from '@sveltejs/kit' // passes through to Vite

Importance

i cannot use SvelteKit without it

Additional Information

This is critical for Cypress to release official Svelte support for Component Testing Svelte apps. We could hardcode build configurations for users, but we don't feel comfortable doing that because they will diverge from the user's production setup and may allow for prod-only bugs to be released.

@bluwy bluwy added the feature / enhancement New feature or request label Mar 15, 2022
@dominikg
Copy link
Member

see #2921 and #3869

for vite config, config and configResolved hooks work if cypress plugin was added in svelte.config.js under kit.vite.plugins.

You can use env or mode to control whether the cypress plugin is active
"test:cypress": "cross-env CYPRESS=true sveltekit dev".

That's not to say this is ideal, but you can make it work today. See also https://github.com/nickbreaton/vitest-svelte-kit

@brillout: you recently mentioned that telefunc + sveltekit could happen soon. Is there a pattern you can share here?

@Rich-Harris
Copy link
Member

Can you elaborate on which bits of config you need access to? The problem with getResolvedConfig is the reason SvelteKit has a dependency on Vite rather than being just another Vite plugin (which would be our preference) — there is no single resolved config. Instead, svelte-kit build invokes vite.build multiple times with multiple configs.

Exposing programmatic control is totally doable though. Would that unblock you?

@benmccann
Copy link
Member

It'd be cool to consider this in conjunction with Vitest. There was quite a bit of discussion about that in the Vitest #svelte Discord channel on Jan 7, 2022: https://discord.com/channels/917386801235247114/918804871333945394/929824676014854174

The difficulty for Vitest is that SvelteKit can't do SSR with Vite's HTML plugin present, but Vitest relies on it to serve the Vitest UI. For Vitest, @dominikg had suggested splitting dev/plugin.js into two: one plugin containing update_manifest and one plugin containing the middleware. Splitting the plugin would allow all the $app code, etc. to be generated without running the SvelteKit dev server.

@JessicaSachs
Copy link
Author

JessicaSachs commented Mar 17, 2022

Thanks for the response, both.

The general theme is that Cypress's Component Testing architecture is a meta-website, ingesting the user's app and spitting out something that works the same way, but with different pages. We're trying to take the user's compilation options and create a new "website" where each spec file is its own entry point/page.

This is identical to Storybook btw.

Depending on the bundler, we do different things, but the TLDR is that the output of cypress open --component is a live devServer where visiting http://localhost:3333/__cypress/Task.cy.js serves your component spec. There's an optimization to that for CI=true cypress run --component which is where we've dist'd your files and we manually route http://localhost:3333/__cypress/Task.cy.js via an express server that serves a dist-cypress directory from somewhere on your machine. This avoids HMR, watchers, etc from being setup in CI.

Config needs:
I mainly need access to the plugins, environment variables, and anything else that effects the runtime of a particular file. The below code needs to work exactly like it would in prd.

// someFile.js
import coolWasmThing from './foo.wasm' // If the user has a plugin that handles wasm configured
import Todo from './Todo.svelte' // To grab the Svelte plugin that the SvelteKit uses under the hood

if (import.meta.MY_ENV_VAR === 'dev') {
  // change an API URL or something
  myRequestClient.baseUrl = 'http://localhost:3001'
}

there is no single resolved config. Instead, svelte-kit build invokes vite.build multiple times with multiple configs.

We also intend to run build to dist the assets of the user's site, after passing in some custom rollup entry points. A programmatic API for svelte-kit build would be ideal.

Exposing programmatic control is totally doable though. Would that unblock you?

Yes. So long as I have something that will allow me to give SvelteKit a new custom index file or let me intercept all requests by acting as middleware 😄

What I don't need: Anything that's related to making multiple pages

  1. Page-by-page configuration
  2. File-based routing (we do our own using transformIndexHtml in our Cypress Vite plugin and if we're not using the dev server (in CI) we have an express server that handles all routes)

What I do need: Anything that's related to correctness of the code being compiled.

  1. Ability to run a dev server that bundles each file matching a certain glob on the user's file system. Currently, this is done by writing each file path into the optimizeDeps.entries object in Vite and then dynamically requesting those files when necessary.
  2. Ability to programmatically change the index.html file and write my own to fetch the scripts bundled in Step 1. Ideally via the same hooks we already use for transformIndexHtml.
  3. Ability to dist a custom list of files matching a glob (so that I can serve them later with some express server).

@Rich-Harris
Copy link
Member

Yikes — we're talking about some fairly invasive changes to SvelteKit's internals. Candidly, it's unlikely to happen.

That's the bad news. The good news is I think you can solve this entirely in userland. By default, SvelteKit generates pages from src/routes, but it's just a config value. If you instead had this...

// svelte.config.js
export default {
  kit: {
    files: {
      routes: process.env.CYPRESS ? '.cypress-component-tests' : 'src/routes'
    }
  }
};

...and generated routes for each of the [Component].spec.js files found in src/routes or src/lib, then you could just run SvelteKit normally (in either dev or build mode).

You could even abstract this behind a function that read from the user's configuration, in case they had configured src/routes and src/lib to be something else:

// svelte.config.js
import { config } from 'cypress-svelte-whatever';

export default config({
  kit: {
    files: {
      routes: 'src/pages'
    }
  }
});

Does that seem workable?

@JessicaSachs
Copy link
Author

JessicaSachs commented Mar 18, 2022

Ahhh. I'm surprised the changes are invasive. This seems like a common problem for tools like Storybook, Cypress, and Vitest.

Our goal is to avoid users having to touch anything on their side. Our current API with all other frameworks in Cypress 10 is to use a single string in a cypress.config.js file.

e.g.

export default defineConfig({
  component: {
    framework: 'svelte'
  }
})

... and that's it.

I think based on what I'm hearing, you're suggesting two things:

  1. Use a svelte config file as an entry point (I'd probably ship our own which would pull in the user's at runtime). I don't mind this!
// node_modules/@cypress/sveltekit-dev-server/svelte.config.js

const userConfig = await findUp('svelte.config.js')
export default {
  // ...userConfig (do deep merging here)
  viteConfig: {
    plugins: [ Cypress() ],
    rollupConfig: {
      /* spec files here */
    },
    optimizeDeps: {
      entries: [ /* spec files here */ ]
    }
  }
}
  1. But then you're suggesting to start it how? Via the CLI? Via a module API? I'm unclear on this part.
// node_modules/@cypress/sveltekit-dev-server/index.js

import { startDev } from 'sveltekit'
const configFilePath = resolve('./svelte.config.js')

const server = startDev({ configFile: configFilePath })
// I need server.close from here so I can cleanly tear down the process,
// but if there's no module API I guess I can figure it out another way.

It sounds like in the worst case I can get comfortable executing from the CLI and tearing down the process from the outside-in.

@benmccann
Copy link
Member

Closing as solved by #5094. Feedback welcome in #5184

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature / enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

5 participants