vitejs / vite

Next generation frontend tooling. It's fast!
http://vitejs.dev
MIT License
67.18k stars 6.04k forks source link

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

Open andyexeter opened 1 year ago

andyexeter commented 1 year ago

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

sapphi-red commented 1 year ago

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.

roydukkey commented 1 year ago

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

nonsensation commented 1 year ago

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)

tlyau62 commented 9 months ago

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.

oussama-he commented 8 months ago

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

dherbst commented 8 months ago

@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";
frederikbosch commented 8 months ago

@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"``?

dherbst commented 8 months ago

@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)),
    }
  }
})
rjgotten commented 7 months ago

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.

eXaminator commented 6 months ago

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?

nckirik commented 6 months ago

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

unekinn commented 4 months ago

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.

piecyk commented 3 months ago

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.

oussama-he commented 3 months ago

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

dherbst commented 3 months ago

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

rjgotten commented 3 months ago

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

piecyk commented 3 months ago

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

rjgotten commented 3 months ago

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.

oussama-he commented 3 months ago

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)
piecyk commented 3 months ago

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

mcamprecios commented 2 months ago

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:

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