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

esbuild-dev-server #21034

Open
vospascal opened this issue Apr 11, 2022 · 11 comments
Open

esbuild-dev-server #21034

vospascal opened this issue Apr 11, 2022 · 11 comments
Labels
E2E Issue related to end-to-end testing type: feature New feature that does not currently exist

Comments

@vospascal
Copy link

What would you like?

i would love if we could have a esbuild-dev-server

Why is this needed?

wy not its fast

Other

No response

@davidmunechika davidmunechika added the type: feature New feature that does not currently exist label Apr 12, 2022
@thednp
Copy link

thednp commented Apr 18, 2022

...with code-coverage support would be fantastic :)

@fochlac
Copy link
Contributor

fochlac commented Apr 24, 2023

I've set up a basic esbuild dev-server, feel free to check it out/use it/adapt it if you want:
https://github.com/fochlac/cypress-devserver-esbuild
Could also be the basis for a PR towards this repo

@nagash77 nagash77 added E2E Issue related to end-to-end testing and removed stage: backlog labels Apr 24, 2023
@lmiller1990
Copy link
Contributor

lmiller1990 commented Apr 24, 2023

Nice job figuring out how to implement this @fochlac! We really should document the public API and events in more detail.

@vospascal Vite (and our Vite Dev Server) uses esbuild - that gets you most of the way there. Could you just use that?

What would you expect in an esbuild dev server you don't already get from Vite?

Just to clarify, you want this for Component Testing (which uses a dev server) as opposed to E2E (uses preprocessor architecture, for which many cypress-esbuild-preprocessors already exist).

@fochlac
Copy link
Contributor

fochlac commented Apr 24, 2023

While using vite might be a solution for new projects, but for existing projects that use only esbuild with a complex setup it might be helpful to have pure esbuild-solution available, so a single configuration can be used for both (we have a very large project with a few custom plugins).

I'm just trying to write down my learnings from this and adapt the dev-server documentation as more detailed docs would have been really helpful when approaching this, see cypress-io/cypress-documentation#5211.

@lmiller1990
Copy link
Contributor

@fochlac thanks for the docs PR - I will give it a review.

I think the first step for esbuild in Cypress is to have a good third party dev server - third parties can move faster and try things when compared to our core offer, which has to go through a more thorough process. If it gains enough traction, we can definitely assess adding this as part of core in the future.

@fochlac
Copy link
Contributor

fochlac commented Apr 25, 2023

I agree, and I will maintain the esbuild-dev-server for now. I guess the most important step is actually having a decent documentation, since with the right knowledge it's actually fairly easy to build a custom dev-server yourself. Especially since you do not need to consider varied use-cases (i.e. esm-bundles with seperate css file vs css in js solutions like styled elements), which make it much harder to develop a generic solution.

Perhaps it might be more helpful to further simplify the API for creating custom dev-servers in the long term. The most complex part is the communication between the index.html and the file-server. Something along the lines of would be much easier to use:

const { defineConfig, createCustomDevServer } = require("cypress");
const { readFile } = require("fs/promises");
const { startBuildToolInWatchMode, customJsMapper, customCssMapper, getSupportFilePath, getOutputFolder } = require('./my-stuff')

module.exports = defineConfig({
    component: {
        devServer: createCustomDevServer(async ({ onBuildComplete, specs, supportFile, serveStatic }) => {
            let stop = startBuildToolInWatchMode(specs, supportFile, onBuildComplete)
            serveStatic(getOutputFolder())
            return {
                loadTest: async (relativeTestPath, { injectHtml, loadBundle, loadSupportFile }) => {
                    const testPath = customJsMapper(relativeTestPath)
                    const cssPath = customCssMapper(relativeTestPath)

                    const testBundle = readFile(testPath, {encoding: 'utf8'})
                    const testCss = readFile(cssPath, {encoding: 'utf8'})
                    
                    loadSupportFile(await readFile(getSupportFilePath(), {encoding: 'utf8'}))
                    loadBundle(await testBundle)
                    injectHtml(`<style>${await testCss}</style>`, 'head')
                },
                onSpecChange: (newSpecs) => {
                    stop()
                    stop = startBuildToolInWatchMode(newSpecs, onBuildComplete)
                },
                devServerPort: 0
            }
        })
    }
})

Would make a custom implementation much more straightforward. A fully implemented esbuild dev server would look like this:

const { defineConfig, createCustomDevServer } = require("cypress");
const { context } = require("esbuild");
const { readFile } = require("fs/promises");

module.exports = defineConfig({
    component: {
        devServer: createCustomDevServer(async ({ onBuildComplete, onBuildStart, specs, supportFilePath, serveStatic }) => {
            const createEsbuildConfig = (specs) => ({
                entryPoints: [
                    ...specs.map(spec => spec.absolute),
                    supportFilePath
                ],
                outdir: './dist',
                outBase: './',
                plugins: [{
                    name: 'watch',
                    setup(build) {
                        build.onStart(onBuildStart)
                        build.onEnd(onBuildComplete)
                    }
                }]
            })
            serveStatic('./dist')
            let ctx = await context(createEsbuildConfig(specs))
            ctx.watch()

            return {
                loadTest: async (relativeTestPath, { loadBundle, loadSupportFile }) => {
                    const testPath = path.resolve('./dist', relativeTestPath)
                    const supportPath = path.resolve('./dist', supportFilePath)
                    const testBundle = readFile(testPath, {encoding: 'utf8'})
                    const supportBundle = readFile(supportPath, {encoding: 'utf8'})
                    
                    loadSupportFile(await supportBundle)
                    loadBundle(await testBundle)
                },
                onSpecChange: async (newSpecs) => {
                    ctx.dispose()
                    ctx = await context(createEsbuildConfig(newSpecs))
                    ctx.watch()
                },
                devServerPort: 0
            }
        })
    }
})

@fochlac
Copy link
Contributor

fochlac commented Apr 25, 2023

Here we go:
https://github.com/fochlac/cypress-ct-custom-devserver

@vospascal
Copy link
Author

vospascal commented Apr 25, 2023

Think current vite solution is good enough at least for me only thing I struggle with it combining vitest and vite coverages to one big one other then that is perfect I mean I can merge them that works fine with a custom script but the lines don’t add up correctly one should compliment the other

@lmiller1990
Copy link
Contributor

lmiller1990 commented Apr 26, 2023

I'm not sure on the combining coverage issue - it should be just a matter of getting all the tools to work together, istanbul/nyc is always a pain to wrangle for some reason...

@fochlac your API looks 🔥 - main reason we never had one was when we developed the original one (webpack) we didn't really know what it would look like - now we've got a good idea of how things should look, we could definitely revisit the API, but this seems like a big breaking change, so it wouldn't be something we can take lightly.

For now, I agree the best approach is good documentation. You could potentially implement your API in useland and wire it up to our event based API.

There's also some other edge cases that would need solving if we want esbuild-dev-server to be part of the core offering, namely ESM with cy.stub and cy.spy. There is #22355 that explains this a bit more. We have an experimental plugin for Vite to facilitate ESM stubbing #26536. I'm guessing you will also have the same issue with esbuild-dev-server - ES modules are sealed by default, so sinon cannot modify them (which is how cy.stub and cy.spy work).

@fochlac
Copy link
Contributor

fochlac commented Apr 26, 2023

There's also some other edge cases that would need solving if we want esbuild-dev-server to be part of the core offering, namely ESM with cy.stub and cy.spy. There is #22355 that explains this a bit more. We have an experimental plugin for Vite to facilitate ESM stubbing #26536. I'm guessing you will also have the same issue with esbuild-dev-server - ES modules are sealed by default, so sinon cannot modify them (which is how cy.stub and cy.spy work).

Hmm, I guess that can be a problem. It would be quite straightforward to build a esbuild-plugin that works in the same way as your vite-plugin, since we could reuse it mostly 1:1. However, such plugins are usually the choke-point regarding speed for esbuild (propably the same for vite). Once your plugin is ready for production I might have a another look and port it to the esbuild-dev-server. Since I set up the index.html slightly different and you include client code I will propably have to couple that functionality more tightly with the dev-server.
Luckily we wont need it in my company, since we don't mock modules and only use cy.spy() to create mock-Functions to be passed into components as prop.

You could potentially implement your API in useland and wire it up to our event based API.

I created a lib that implements that api. It uses the existing api and just sets up a generic (express)-server and takes care of the communication with the ui. No need to reverse engineer the event based api for now. ^^

@lmiller1990
Copy link
Contributor

That was quick ⚡

I will leave this issue open as a feature request, let's see how much traction it gets. Thanks for the docs and implementation - I may give it a try on my next project 💯

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
E2E Issue related to end-to-end testing type: feature New feature that does not currently exist
Projects
None yet
Development

No branches or pull requests

6 participants