Skip to content

Loading SCSS with the ?url flag causes the import to fail #2522

Description

@duckbrain

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.

Activity

  1. yyx990803 commented on Mar 16, 2021

    @yyx990803
    Member

    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.

  2. duckbrain commented on Mar 16, 2021

    @duckbrain
    Author

    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.

  3. mijamo commented on Apr 7, 2021

    @mijamo

    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.

  4. tarnishablec commented on Aug 2, 2021

    @tarnishablec

    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

  5. duckbrain commented on Aug 14, 2021

    @duckbrain
    Author

    I've started some hacking on it #4604 for a few hours, but the build tools are bringing my poor machine to its knees. (It's pretty old and lightweight.) Sorry for dropping my brain dump here.

    Here's what I've learned so far. It looks like most of the relevant (development) changes will be made in packages/vite/src/node/plugins/css.ts

    1. Using fd0a0d9 as an example, getting ?url to return. (I have a hacky solution with detecting the ?url, I didn't look too hard for the correct way to get that.); avoids the compile error.
    2. When directly accessing a css preprocessed file, vite behaves differently when a JS/TS/etc file imports it.
    • If a js file imports /src/style.scss, the dev server returns JS that appends the compiled CSS to the document
    • If no js file imports /src/style.scss, the dev server returns the raw source code (not compiled)
    • The css source file needs to be resaved for the dev server to update the result (it's cached across restarts)
    1. vite build doesn't produce an output for /src/style.scss even if it's imported with or without ?url
    2. vite build adds stylesheet links to the built HTML with the compiled css.

    Development Server

    Number 2 above, probably doesn't matter. the import behavior for JS is necessary for the intended behavior. I'm thinking if you import /src/style.scss it should return export default "/src/style.scss?compiled", then a new ?compiled query would return the results of compiling the source file.

    Build

    Based on behavior, I'm guessing there is some sort of registration when a CSS import is encountered in a script file. With my dumb patch, a /src/style.scss?url doesn't include the compiled contents of /src/style.scss in an output css file (as it shouldn't). I'm hoping it wouldn't be too difficult to register a new css file (the manifest already returns an array of css files per JavaScript file) and output the css linked similarly as a normal CSS file and inline the URL in. I haven't looked at the build portion too much.

  6. philippkuehn commented on Aug 18, 2021

    @philippkuehn

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

  7. Widom commented on Aug 30, 2021

    @Widom

    The behavior of referencing CSS and SCSS in Vue files is also different。

    when use CSS:

    === App.vue ==

    <template>
        <div class="div1">div1</div>
        <div class="div2">div2</div>
    </template>
    <style lang="scss" scoped>
    @import "./div1.css";
    @import "./style/div2.css";
    </style>
    

    === ./div1.css ===

    .div1{
        background-image: url(./assets/logo.png);
        width: 80px;
        height:80px;
    }
    

    === ./style/div2.css ==

    .div2{
        background-image: url("../assets/logo.png");
        width: 80px;
        height:80px;
    }
    

    Compilation results:

    .div1[data-v-243a7a0a]{
        background-image:url(./logo.03d6d6da.png);
        width:80px;
        height:80px
    }
    .div2[data-v-243a7a0a]{
        background-image:url(./logo.03d6d6da.png);
        width:80px;
        height:80px
    }
    

    The same code rename css to scss:

    Compilation results:

    .div1[data-v-12b736e0]{
        background-image:url(./logo.03d6d6da.png);
        width:80px;
        height:80px
    }
    .div2[data-v-12b736e0]{
        background-image:url(../assets/logo.png);
        width:80px;
        height:80px
    }
    

    "background-image:url(../assets/logo.png); " is error in div2.

  8. BARNZ commented on Oct 28, 2021

    @BARNZ

    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?

  9. xialvjun commented on Nov 18, 2021

    @xialvjun

    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'
    
  10. jacksteamdev commented on Mar 15, 2022

    @jacksteamdev

    Want to mention that the behavior for import styles from './styles.css?url' is inconsistent between production and development.

    During production, it returns a Data URL, while in development it returns the path to the file. That seems okay-ish, but neither reference the concatenated output CSS file (which doesn't exist in development).

  11. 6 remaining items

  12. sep2 commented on Jul 16, 2022

    @sep2

    My use case: tinymce editor requires css url to be present, I don't need postprocessing and other stuffs, just want to

    import contentCss from 'tinymce/skins/content/default/content.min.css?url'
    import contentUiCss from 'tinymce/skins/ui/oxide/content.min.css?url'

    Expect contentCss and contentUiCss to be the url of these files after build, I could use them:

    <Editor
        init={{
            content_css: [contentCss, contentUiCss],
        }}
    />

    But vite cannot handle this situation.

  13. chuanqisun commented on Jul 17, 2022

    @chuanqisun

    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.

  14. sneakylenny commented on Aug 24, 2022

    @sneakylenny

    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.

    It does map 1 to 1 in the manifest file like static assets, or at least appears to do so:

      "resources/css/app.css": {
        "file": "assets/app-99133e62.css",
        "src": "resources/css/app.css",
        "isEntry": true
      },
      "resources/scss/themes/light.scss": {
        "file": "assets/light-987e26d3.css",
        "src": "resources/scss/themes/light.scss",
        "isEntry": true
      },
      "resources/scss/themes/dark.scss": {
        "file": "assets/dark-618dbc8a.css",
        "src": "resources/scss/themes/dark.css"
      }

    Note that I do specify the these files as input in the vite.config.

    Looking at the manifest is mildly infuriating since we can clearly see that the scss is being compiled, but we are not able to use it dynamically because we need the url of the generated css. There has to be a way to return the url, right?

  15. moved this to Annoying in Nuxt 3on Sep 5, 2022
  16. mjwvb commented on Apr 25, 2023

    @mjwvb

    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)), []);
    };
  17. KieranP commented on Aug 15, 2023

    @KieranP

    We're having the same issue. We need to pass a URL to a CSS overrides file to a 3rd party library, which loads it internally. We used ?url which seemed to work fine on development, but once deployed, it returns a URL to the original SCSS file, which the browser won't accept. We need it to process the SCSS and return the URL of the resulting parsed file, but this doesn't currently seem possible with Vite.

  18. moved this from Discussing to Has plan in Team Boardon Nov 29, 2023
  19. sevillaarvin commented on Jan 4, 2024

    @sevillaarvin

    For nuxt 3, I wanted something like:

     <script setup>
    // alternative:
    // const styleHref = new URL("~/assets/styles/app.scss", import.meta.url).href
    import styleHref from "~/assets/styles/app.scss?url"
    useHead({
            link: [{
                    href: styleHref, rel: "stylesheet"
            }]
    })
     </script>

    However, since "?url" doesn't seem to work for scss how I wanted it to (compile scss -> css, then output the path as string), this is my workaround:

    <script setup>
    import lAppStyle from "~/assets/styles/app.scss?inline"
    useHead({
            style: [lAppStyle]
    })
    </script>
  20. moved this from Annoying to Done in Nuxt 3on Jan 12, 2024
  21. locked and limited conversation to collaborators on Jan 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions