vite: Loading SCSS with the `?url` flag causes the import to fail

Describe the bug

Similar to #2455, when importing SCSS into a JS file, with the ?url flag, the import will fail.

A related, but apparently different bug, is that if ?url is used with a CSS file, it evaluates to a string like export default "/src/style.css". This may deserve a separate issue.

Reproduction

This can be reproduced with:

  1. yarn create @vitejs/app --template vue vite-sample; cd
  2. yarn add sass
  3. Create src/style.scss
  4. Add an import to App.vue
    import data from "./style.scss?url"
    console.log(data) 
    

Error in console

[plugin:vite:css] expected "{".
  ╷
1 │ export default "/src/style.scss"
  │                                 ^
  ╵
  src/style.scss 1:33  root stylesheet
/home/jonathan/src/git.ec2software.com/jonathan/vite-sample/src/style.scss:1:33
    at Object._newRenderError (/home/jonathan/src/git.ec2software.com/jonathan/vite-sample/node_modules/sass/sass.dart.js:13537:19)
    at Object._wrapException (/home/jonathan/src/git.ec2software.com/jonathan/vite-sample/node_modules/sass/sass.dart.js:13374:16)
    at _render_closure1.call$2 (/home/jonathan/src/git.ec2software.com/jonathan/vite-sample/node_modules/sass/sass.dart.js:80373:21)
    at _RootZone.runBinary$3$3 (/home/jonathan/src/git.ec2software.com/jonathan/vite-sample/node_modules/sass/sass.dart.js:27269:18)
    at _FutureListener.handleError$1 (/home/jonathan/src/git.ec2software.com/jonathan/vite-sample/node_modules/sass/sass.dart.js:25797:19)
    at _Future__propagateToListeners_handleError.call$0 (/home/jonathan/src/git.ec2software.com/jonathan/vite-sample/node_modules/sass/sass.dart.js:26094:49)
    at Object._Future__propagateToListeners (/home/jonathan/src/git.ec2software.com/jonathan/vite-sample/node_modules/sass/sass.dart.js:4543:77)
    at _Future._completeError$2 (/home/jonathan/src/git.ec2software.com/jonathan/vite-sample/node_modules/sass/sass.dart.js:25927:9)
    at _AsyncAwaitCompleter.completeError$2 (/home/jonathan/src/git.ec2software.com/jonathan/vite-sample/node_modules/sass/sass.dart.js:25270:12)
    at Object._asyncRethrow (/home/jonathan/src/git.ec2software.com/jonathan/vite-sample/node_modules/sass/sass.dart.js:4292:17
Click outside or fix the code to dismiss.
You can also disable this overlay with hmr: { overlay: false } in vite.config.js.

System Info

  • vite version: 2.1.0
  • Operating System: Arch Linux
  • Node version: v15.10.0
  • Package manager (npm/yarn/pnpm) and version: yarn

Logs (Optional if provided reproduction)

  1. Run vite or vite build with the --debug flag.
  2. Provide the error log here.

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 28
  • Comments: 23 (4 by maintainers)

Most upvoted comments

Currently queries has these behaviors.

  • import cssContent from 'foo.css': bundles files into a single css file and obtains the content of it (also loads style)
  • import cssContent from 'foo.css?inline': bundles files into a single css file and obtains the content of it
  • import rawCssContent from 'foo.css?raw': raw css file (no bundle or transpile)
  • import worker from 'foo.js?worker': bundles files into a single worker file
  • import workerUrl from 'foo.js?worker&url': bundles multiple files into a single worker file and obtains the url of it (#7914)

I think making it like below will have a consistency with the queries above.

  • import cssUrl from 'foo.css?url': bundles files into a single css file and obtains the url of it
  • import rawCssUrl from 'foo.css?raw&url': obtains the url of raw css file (no bundle or transpile)

Also these (it’s a bit off-topic)

  • import jsUrl from 'foo.js?url': bundles files into a single js file and obtains the url of it (#6757)
  • import jsUrl from 'foo.js?raw&url': obtains the url of raw js file (no bundle or transpile)

My expectation would be to point the the resulting CSS after compilation, so I can have a link tag that I inject at runtime reference it, so in development it would be /src/style.scss (which the vite dev server compiles on request) and in production it would be dist/assets/style.b4186631.css or whatever is output.

If I understand, the problem of what to do is that the styles are concatenated, scoped to the JS, but I’m trying to reference a style directly, so it may be part of one or more bundles that’s associated with various scripts, not a single CSS bundle for that SCSS entrypiont.

The usecase I’m trying to solve is scoping my CSS inside of a shadow DOM element. Another domain is including a script from my site to get a component. I want to omit adding the CSS until I manually add it to the shadow node.

I don’t care if scss file has its compiled css’s url, but I think css file should get its url.

import cssUrl from 'someCssFile.css?url' : syntax like this should be supported. We need a way to let vite help us organize our files but we use those files in our way, like add link element after component A mounted and remove that link after A is destroyed.

however:

import compiledCssUrl from 'someScssFile.scss?url' : this has different meanings

I think we should write it like:

import scssUrl from 'someScssFile.scss?url'
import compiledCssUrl from 'someScssFile.scss?url?scss'

It would be super useful if we could use something like:

import conditionalStylesUrl from 'conditionalStyles.scss?url'

Ideally this would allow retrieving of a path to the resulting compiled css file which could then be dynamically inserted as a stylesheet conditionally based on some application logic. I’d love to know if there is already a way to do this?

The same error happens when using raw for scss files (import text from './src/style.scss?raw')

Same issue devs! Any updates on this?

Any progress yet? I am facing a similar scenario when dealing with shadowdom, need to get scss compilation result and inject a preload link into head

Same issue. My team needs to have fine control over the order/position at which css files are added to the DOM, hence we cannot rely on vite’s built-in css import handler and must manually create <link rel="stylesheet" href="...">. We need the capability to get the asset url from css files for this work.

The only work around we can think of is renaming the .css as .txt, import it as ?raw, and manually set the <style> tag inner html. This breaks our caching though.

My use case is when using CSS within a Web Component. So:

import css from '../styles/webcomponent.css?url';
console.log(`Static import: ${css}`); // Static import: export default "/styles/webcomponent.css"

// vs

const  css = new URL('../styles/webcomponent.css', import.meta.url).href;
console.log(`Import meta: ${css}`); // Import meta: http://localhost:3000/styles/webcomponent.css

Import meta works fine, but I guess the stylesheet isn’t processed? Correct me if I’m wrong. I’m not using some plugin yet (e.g.: autoprefixer), so I can’t say for sure.

Same problem for me basically. We want to load different SCSS files for different pages and use React Helmet for that and would like to point to the URL of the file dynamically instead of having Vite appending the style automatically to the page.

We have the same problem coming from CRA. Our use case is to import dynamic theme scss from a third party package based on a user setting. The css files are too big and too many to preload all of them, so we want to load them on demand. In CRA we imported the scss as url and appended it to the html head. Now in Vite that seems impossible as ?url doesn’t transform the sass css in build/preview mode.

I hope this will be fixed by e.g. ?inline&url, but for now I want to share our workaround. We ended up using dynamic import(), like import(‘@/stylesheets/themes/theme.light.scss’). This automatically bundles the sass as a separate css file in the assets folder and automatically links it in the html head on demand. The problem with this approach though is that the css files are appended but never removed. So here comes the “hacky” part: after import you need to toggle the dynamically linked css by using the media attribute. In dev mode this needs to be done on the appended <style …> tag, and in production mode on the <link …> tag. Downside of this approach is that all imported theme css files are attached to the DOM forever, but I think browsers are smart enough to allocate resources accordingly.

Our stripped-down react code is as follows:

// Globally configured themes
const themes = {
    'light': {
        id: 'light',
        import: () => import('@/stylesheets/themes/theme.light.scss'),
    },
    'dark': {
        id: 'dark',
        import: () => import('@/stylesheets/themes/theme.dark.scss'),
    },
};

// Theme DOM updater. If undefined, it will unmount all themes
const applyTheme = (themeId) => {
    // Dev mode uses <style> and production mode uses <link>
    const oldStyles = document.querySelectorAll(
        `style[data-vite-dev-id*="/theme."], link[href*="/theme."]`
    );
    oldStyles.forEach((oldStyle) => oldStyle.setAttribute('media', 'disabled'));

    if (themeId) {
        const newStyle = document.querySelector(
            `style[data-vite-dev-id*="/theme.${themeId}"], link[href*="/theme.${themeId}"]`
        );
        newStyle?.removeAttribute('media');
    }
};

// Component to automatically mount and apply current theme
const ThemeManager = ({ themeId }) => {
    const theme = themes[themeId]; // Get theme config with its import method

    const themeIdRef = useRef();

    // Dynamically import its css file and update the DOM
    useEffect(() => {
        themeIdRef.current = theme.id;
        theme.import().then(() => applyTheme(themeIdRef.current));
    }, [theme]);

    // On unmount disable all theme css
    useEffect(() => () => applyTheme((themeIdRef.current = undefined)), []);
};

small world, @sep2, that is exactly what blocked my team too. We have to put tinymce skin css files into the public folder with a manual copying step during in build. If you adopt the same workaround, make sure you add a version prefix/suffix somewhere in the public file path for immutable caching, or your user might get stuck with that version forever.

What do you expect to get by appending ?url to an SCSS file? Multiple CSS files are going to be concatenated into one file during build so it’s not a 1 to 1 mapping like static assets.