vite icon indicating copy to clipboard operation
vite copied to clipboard

url with relative path in sass/scss is broken when main.js and assets are in subdirectory

Open andyexeter opened this issue 2 years ago • 12 comments

Describe the bug

Following on from my comment in #7651 - the linked PR (#10741) doesn't solve the issue when main.js and assets are stored in a top level assets directory, e.g.:

├── assets
│   ├── images
│   │   └── vite.svg
│   ├── main.js
│   └── styles
│       ├── pages
│       │   └── home.scss
│       └── style.scss
├── index.html
├── package.json
├── package-lock.json
├── public
│   └── vite.svg
└── README.md

You can see in the linked reproducer that url('../../images/vite.svg') does not resolve the image correctly, even though that is the correct relative path to the file from assets/styles/pages/home.scss. When the path is changed to url('../images/vite.svg') it resolves correctly.

Reproduction

https://github.com/andyexeter/vitejs-vite-2btrkm

Steps to reproduce

Run npm install followed by npm run dev

System Info

System:
    OS: Linux 5.15 Ubuntu 20.04.5 LTS (Focal Fossa)
    CPU: (12) x64 11th Gen Intel(R) Core(TM) i5-11600 @ 2.80GHz
    Memory: 6.97 GB / 15.47 GB
    Container: Yes
    Shell: 5.8 - /usr/bin/zsh
  Binaries:
    Node: 16.18.1 - /usr/bin/node
    Yarn: 1.22.17 - /usr/bin/yarn
    npm: 8.19.2 - /usr/bin/npm
  Browsers:
    Chrome: 107.0.5304.110
    Firefox: 107.0

Used Package Manager

npm

Logs

No response

Validations

andyexeter avatar Nov 21 '22 10:11 andyexeter

Thanks for creating this. The reason why this is happening is explained in #7651:

Current Vite's implementation

It is implemented by this rebaseUrls function. This function is called inside importer option which is passed to sass. But importer will only be called if it is not a relative import because of the resolve order.

Loads are resolved by trying, in order:

  • Loading a file from disk relative to the file in which the @use or @import appeared.
  • Each custom importer.
  • Loading a file relative to the current working directory.
  • Each load path in includePaths.
  • Each load path specified in the SASS_PATH environment variable, which should be semicolon-separated on Windows and colon-separated elsewhere.

Interface LegacyStringOptions importer

Which means if a file is resolved by relative path, rebaseUrls functions won't be called. The example is below.

/* src/foo.scss */
@import "./nested/bar.scss";
/* @import "/@/nested/bar.scss"; */ /* if a alias is used `rebaseUrls` will be called */

/* ---- */
/* src/nested/bar.scss */
.bar {
  background: url('./bar.png');
}

But I forgot about this while creating #10741.

sapphi-red avatar Nov 22 '22 13:11 sapphi-red

I believe I'm seeing the same symptom. I'm just not sure if the cause is the same. Here is my reproduction: https://github.com/roydukkey/moist/tree/vite/issue-11012

roydukkey avatar Dec 20 '22 20:12 roydukkey

I think I ran into the same issue:

created a new vite-vanilla-typescript project, added npm i bootstrap-icons and have these files:

├── src
│   ├── main.ts
│   └── style.scss
└── index.html

In main.ts I import the bootstrap-icons via import "./style.scss";
In style.scss I import the bootstrap-icons.scss via @use "../node_modules/bootstrap/scss/bootstrap"; And it fails with:

downloadable font: download failed (font-family: "bootstrap-icons" style:normal weight:400 stretch:100 src index:0): status=2147746065 source: http://localhost:5173/MyProject/fonts/bootstrap-icons.woff2?24e3eb84d0bcaf83d77f904c78ac1f47

But when using @use "../node_modules/bootstrap/scss/bootstrap.css"; it works as expected.

(It also fails with the same error when importing *.scss)

Came here after a long time of finding out what might be wrong while learning web dev with vite/scss from here: https://github.com/twbs/icons/issues/1381

(vite 4.1.0)

nonsensation avatar Feb 13 '23 23:02 nonsensation

I think I ran into the same issue:

created a new vite-vanilla-typescript project, added npm i bootstrap-icons and have these files:

├── src
│   ├── main.ts
│   └── style.scss
└── index.html

In main.ts I import the bootstrap-icons via import "./style.scss"; In style.scss I import the bootstrap-icons.scss via @use "../node_modules/bootstrap/scss/bootstrap"; And it fails with:

downloadable font: download failed (font-family: "bootstrap-icons" style:normal weight:400 stretch:100 src index:0): status=2147746065 source: http://localhost:5173/MyProject/fonts/bootstrap-icons.woff2?24e3eb84d0bcaf83d77f904c78ac1f47

But when using @use "../node_modules/bootstrap/scss/bootstrap.css"; it works as expected.

(It also fails with the same error when importing *.scss)

Came here after a long time of finding out what might be wrong while learning web dev with vite/scss from here: twbs/icons#1381

(vite 4.1.0)

I have faced similar problems when using the library from @arcgis. The relative path in the library css fails to be resolved if .scss is imported, but it works when changing back to .css.

Thanks for the information.

tlyau62 avatar Dec 06 '23 04:12 tlyau62

Is there any news about this bug or workaround? I'm facing this issue with the bootstrap-icons library. I'm using vite 4.4.11

oussama-he avatar Jan 03 '24 09:01 oussama-he

@oussama-he the workaround for bootstrap-icons is to unroll the variable interpolation before you load the bootstrap-icons.scss file. You should determine where the locations will be based on where the packages are installed, the values below work for me.

This works because $bootstrap-icons-font-fileis marked !default:

// NOTE: this is a workaround for the vite-sass issue of loading the woff files in bootstrap-icons.scss using variables
$bootstrap-icons-font-file: "~bootstrap-icons/font/fonts/bootstrap-icons";
@import "bootstrap-icons/font/bootstrap-icons.scss";

dherbst avatar Jan 04 '24 10:01 dherbst

@dherbst Small side question, how did you manage to get the tilde (~) to work in Vite? I am getting Unable to resolve @import "~normalize.css/normalize.css"``?

frederikbosch avatar Jan 10 '24 12:01 frederikbosch

@frederikbosch you add it to the alias section of the vite.config.js see https://vitejs.dev/config/shared-options.html#resolve-alias

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url)),
      '~bootstrap': fileURLToPath(new URL('./node_modules/bootstrap', import.meta.url)),
      '~bootstrap-icons': fileURLToPath(new URL('./node_modules/bootstrap-icons', import.meta.url)),
    }
  }
})

dherbst avatar Jan 10 '24 13:01 dherbst

Getting this as well, and it is the one thing that is absolutely blocking me from easily moving off of Create React App.

[Edit] Indeed declaring @fonts and @images alias paths works. ... I actually don't really have a problem with that approach. But on principle, this still has me a bit uneasy that it might break in the future on third-party CSS being imported.

rjgotten avatar Feb 02 '24 16:02 rjgotten

This is currently blocking us from moving from a custom webpack build on a larger legacy app to vite. We have a lot of scss files for fonts etc. that are imported in a variety of other scss files. Sadly this breaks right now as we get errors like these:

../../../assets/fonts/opensans/opensans-extrabold-webfont.svg referenced in /some/path/to/screen.scss didn't resolve at build time, it will remain unchanged to be resolved at runtime

Is there any feasible workaround for this that doesn't include changing all the relative paths?

eXaminator avatar Feb 28 '24 11:02 eXaminator

facing the same issue here. relative paths in scss files aren't handled correctly.

nckirik avatar Mar 10 '24 19:03 nckirik

Also encountering this problem. It does not seem to require that "main.js and assets are in subdirectory", as per title.

Given this directory structure

styles/
    main.scss
    base/
        _fonts.scss
        path/
            to/
                font.ttf
index.js

Simply using url('./path/to/font.ttf') from styles/base/_fonts.scss which is then @imported from styles/main.scss gives

./path/to/font.ttf referenced in <projectPath>/styles/main.scss didn't resolve at build time, it will remain unchanged to be resolved at runtime

So I find it quite strange that the documentation still states:

In addition, relative url() references inside imported Sass/Less files that are in different directories from the root file are also automatically rebased to ensure correctness.

unekinn avatar Apr 29 '24 14:04 unekinn

Trying to use resolve alias but it didn't work for me, found simple hack to force it

Basic importing sass files from @foo/my-ui package that relative import fonts, to rewrite paths using postcss-url

import url from 'postcss-url'

// and in config 
css: {
  postcss: {
    plugins: [
      // https://github.com/vitejs/vite/issues/11012
      url({
        url: asset => {
          if (asset.url.startsWith('./fonts/')) {
            return path.resolve(__dirname, '../node_modules/@foo/my-ui/lib', asset.url)
          }
          return undefined
        },
      }),
    ],
  },
},

this is pretty naive implementation as in ma case only needed for fonts.

piecyk avatar May 29 '24 10:05 piecyk

@dherbst I have tried your workaround but I can't get it work.

I still get this error: image

These are my config and scss files

const { resolve } = require('path');
const { fileURLToPath } = require('url')

module.exports = {
    root: resolve('src/static'),
    base: '/static/',
    server: {
        host: '0.0.0.0',
        port: 3000,
        open: false,
        watch: {
            usePolling: true,
            disableGlobbing: false,
        },
    },
    resolve: {
        extensions: ['.js', '.json'],
        alias: {
            '@': fileURLToPath(new URL('./src', import.meta.url)),
            '~bootstrap': fileURLToPath(new URL('./node_modules/bootstrap', import.meta.url)),
            '~bootstrap-icons': fileURLToPath(new URL('./node_modules/bootstrap-icons', import.meta.url)),
          }
    },
    build: {
        outDir: resolve('src/static/dist'),
        assetsDir: '',
        manifest: true,
        emptyOutDir: true,
        target: 'es2015',
        rollupOptions: {
            input: {
                main: resolve('src/static/js/main.js'),
            },
            output: {
                chunkFileNames: undefined,
            },
        },
    },
};

style.scss

@import url('https://fonts.googleapis.com/css2?family=Fira+Sans:wght@400;700&family=Baloo+Bhaijaan+2:wght@400;700&display=swap');
@import 'simplebar/dist/simplebar.min.css'; 

// NOTE: this is a workaround for the vite-sass issue of loading the woff files in bootstrap-icons.scss using variables
$bootstrap-icons-font-file: "~bootstrap-icons/font/fonts/bootstrap-icons";
@import "~bootstrap-icons/font/bootstrap-icons.scss";

main.js

import 'vite/modulepreload-polyfill';
import '../css/style.scss'
import Alpine from 'alpinejs'
import { Toast } from "bootstrap";
import htmx from 'htmx.org';
import _hyperscript from "hyperscript.org";
import 'simplebar';

Please can you tell me what is wrong?

Thank you in advance.

oussama-he avatar Jun 02 '24 12:06 oussama-he

@oussama-he

In the style.scss file remove the ~ from the @import line, but keep it in the variable line.

@import "bootstrap-icons/font/bootstrap-icons.scss";

See if that helps. Other than that, it looks like vite is not rewriting part of the url to the woff files, perhaps something is going wrong somewhere in the build steps? I'm not sure.

dherbst avatar Jun 02 '24 13:06 dherbst

@oussama-he Have you tried preserveSymlinks: true in the resolve configuration?

Additionally: not sure if fileURLToPath(new URL("./some/url", import.meta.url)) would do the right thing, esp. if you are using CommonJS require and module.exports rather than ES modules for your config file. You might consider trying path.resolve(__dirname, "./some/path") instead.

rjgotten avatar Jun 03 '24 12:06 rjgotten

Looks like vite will not rewrite paths inside files imported in sass, try moving import bootstrap-icons.scss to main.js before style.sass.

piecyk avatar Jun 03 '24 12:06 piecyk

Looks like vite will not rewrite paths inside files imported in sass, try moving import bootstrap-icons.scss to main.js before style.sass.

I have a case where I have a deeply imported sheet that pulls in web fonts from a @fonts alias and it does resolve that correctly.

rjgotten avatar Jun 03 '24 12:06 rjgotten

Nothing worked for me. I always get the same error.

http://localhost:8000/static/@fs/home/oussama/budget-app/node_modules/bootstrap-icons/font/fonts/bootstrap-icons.woff?24e3eb84d0bcaf83d77f904c78ac1f47 net::ERR_ABORTED 404 (Not Found)

oussama-he avatar Jun 03 '24 13:06 oussama-he

Looks like vite will not rewrite paths inside files imported in sass, try moving import bootstrap-icons.scss to main.js before style.sass.

I have a case where I have a deeply imported sheet that pulls in web fonts from a @fonts alias and it does resolve that correctly.

@rjgotten maybe it's connected from where you importing it, like if sass file is from node_modules

piecyk avatar Jun 03 '24 19:06 piecyk

In my case it is rewritting paths in both files, the main styles.scss where all the imports happen, and imported stylessheets like config/_fonts.scss.

The problem is that it is transforming the relative path incorrectly; It just uses the assetsDir build option in the path, not including the outDir parameter.

So, src: url('../fonts/Inter-Regular.woff2') format('woff2'); in my _fonts.scss:

  • Becomes http://wp-starter.test/assets/Inter-Regular-CKDp9E3C.woff2
  • Insted of http://wp-starter.test/themes/wp-starter/assets/Inter-Regular-CKDp9E3C.woff2

I've created another issue since I'm not sure it is the same one: https://github.com/vitejs/vite/issues/17605

mcamprecios avatar Jul 03 '24 08:07 mcamprecios