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

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

Closed
3 tasks done
duckbrain opened this issue Mar 15, 2021 · 23 comments · Fixed by #15259
Closed
3 tasks done

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

duckbrain opened this issue Mar 15, 2021 · 23 comments · Fixed by #15259
Labels
feat: css p3-significant High priority enhancement (priority)

Comments

@duckbrain
Copy link

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.
@yyx990803
Copy link
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.

@duckbrain
Copy link
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.

@mijamo
Copy link

mijamo commented Apr 7, 2021

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.

@tarnishablec
Copy link

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

@duckbrain
Copy link
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.

@philippkuehn
Copy link

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

@Widom
Copy link

Widom commented Aug 30, 2021

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.

@BARNZ
Copy link

BARNZ commented Oct 28, 2021

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?

@xialvjun
Copy link

xialvjun commented Nov 18, 2021

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'

@ghost
Copy link

ghost commented Dec 21, 2021

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.

@jacksteamdev
Copy link

jacksteamdev commented Mar 15, 2022

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).

@jacksteamdev
Copy link

jacksteamdev commented Mar 17, 2022

Since the implementation details of CSS files differ between production and development, it seems like the file URL approach is too low-level.

We need something that can allow us to manipulate where Vite applies styles during both production and development, like a config 😕 or lifestyle hooks 😱

@sapphi-red
Copy link
Member

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 (feat: worker support query url #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)

@iMrDJAi
Copy link
Contributor

iMrDJAi commented Apr 27, 2022

Same issue devs! Any updates on this?

@chuanqisun
Copy link

chuanqisun commented May 11, 2022

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.

@jacksteamdev
Copy link

jacksteamdev commented Jun 10, 2022

@patak-dev What if we could add or remove style roots through an API? The implementation details of the API would differ between production and development, and OFC without using the API things would behave as they do now.

Something like the following might do the trick:

// main.js (the entry file for index.html)

// typeof styleRootSet === Set<HTMLElement>
import { styleRootSet } from '@vite/client'

// runs before style tags are added to dom
export function setup() {
  // setup details...
  const shadowDOM = container.attachShadow?.({mode: 'closed'})
  styleRootSet.delete(document.head) // CSS no longer goes here
  styleRootSet.add(shadowDOM) // CSS now goes here
  // finish setup...
}

// runs after style tags are loaded
export function mount() {
  // mount your app, e.g.:
  document.body.appendChild(container)
  ReactDOM.render(<Content/>, root);
}

The idea is that if an entry file exports two functions called setup and mount, then they can modify the root elements where Vite updates/adds stylesheets. Other files could modify styleRootSet programmatically by importing @vite/client.

I realize this might have performance implications for production and/or that something like this can be done other ways, but at least maybe we can get the conversation moving in a productive direction.

@iMrDJAi
Copy link
Contributor

iMrDJAi commented Jun 13, 2022

It would be helpful to have a way to force style to load first before anything else. This would prevent the flash of unstyled elements on page reload during development mode. It will improve the development experience.

import './index.scss' takes a while to load:

image

Using ?url is a good workaround:

import styleUrl from './index.scss?url'

//...

const documentHtml = escapeInject`<!DOCTYPE html>
  <html lang="en">
    <head>
      <meta charset="UTF-8" />
      <link rel="icon" href="${logoUrl}" />
      ${dangerouslySkipEscape(`<link rel="stylesheet" href="${styleUrl}" />`)}
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <meta name="description" content="${desc}" />
      <title>${title}</title>
    </head>
    <body>
      <div id="page-view">${dangerouslySkipEscape(pageHtml)}</div>
    </body>
  </html>`

image

However it doesn't work in production since the URL becomes a base64 encoded data URI string :/.

@sep2
Copy link

sep2 commented Jul 16, 2022

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.

@chuanqisun
Copy link

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.

@sneakylenny
Copy link

sneakylenny commented Aug 24, 2022

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?

@mjwvb
Copy link

mjwvb commented Apr 25, 2023

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)), []);
};

@KieranP
Copy link

KieranP commented Aug 15, 2023

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.

@sevillaarvin
Copy link

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>

@github-project-automation github-project-automation bot moved this from Annoying to Done in Nuxt 3 Jan 12, 2024
@github-actions github-actions bot locked and limited conversation to collaborators Jan 27, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
feat: css p3-significant High priority enhancement (priority)
Projects
Archived in project