vitejs/vite

Use the Sass Compiler API

nex3 opened this issue · 2 comments

nex3 commented

Description

We (the Sass team) have just released a proposal for a Compiler API in Sass that shares resources between different compilations (see also sass/sass#3296). This is particularly useful when using the sass-embedded package, which runs a very fast subprocess to compile Sass: within the lifetime of a single Compiler, the subprocess will remain open, eliminating all the overhead of starting it and shutting it down that currently causes sass-embedded to be slower than it could be.

For now, we're just seeking your feedback on the proposal. Once it lands, we hope you'll integrate it into Vite for substantially improved performance.

Suggested solution

Start a Compiler when Vite's build initializes and uses that compiler to compile all Sass files, so that Sass users using the embedded host experience better performance.

Alternative

Runs a separate sass.compile() call for each compilation, which has a large amount of overhead when using sass-embedded.

Additional context

No response

Validations

Thank you!

As the proposed API only supports the modern API, Vite needs to migrate to modern API first (#7116). Other than that, I guess the proposed API work for us.

Proposal -- do what sass-loader did (webpack-contrib/sass-loader#774). Create an 'api' flag on the options that lets uses opt-in to use the modern API, while still maintaining 100% backwards compatibility with others.

Could be as simple as this in the config:

    export default defineConfig({
      plugins: [react()],
      css :{
        preprocessorOptions : {
            scss: {
                api: "modern",
                // Use the new Node loaders.  Added as an example so that it performed functionality other than what is
                // available today.
                importers: [
                    new sass.NodePackageImporter() 
                    
                ]
            }        
        } 
      }
    })

Cutdown code will look something like this:

    // this would be the new input type... either one.
    type SassWorkerOptions = SassStylePreprocessorOptions & {additionalData: undefined} | 
                  Sass.StringOptions<"sync"> & {api:"modern"};

    if (options.api === "modern") {
        // eslint-disable-next-line no-restricted-globals
        const url = require('node:url');

        // reshape this.
        options.filename = url.pathToFileURL(options.filename);

        return new Promise((resolve, reject) => {
            try {
                let out = {
                    stats: {
                        includedFiles: [] as string[]
                    }
                };
                const res = sass.compileString(data, options as Sass.StringOptions<"sync">);
                out = Object.assign(out, res);

                // rebuild stats.includedFiles, since they're wanted
                // in some downstream process.
                out.stats = {
                    includedFiles: res.loadedUrls.map( (item) => {
                        return item.pathname
                    })
                }
                resolve(out);
            } catch (e) {
                reject(e);
            }
        });
    }