vuejs / rollup-plugin-vue

Roll .vue files
https://vuejs.github.io/rollup-plugin-vue
MIT License
845 stars 148 forks source link

Use rollup to transform styles before applying scope to scoped styles #300

Open khalyomede opened 4 years ago

khalyomede commented 4 years ago

Version

5.0.0

Reproduction link

None (this is an issue in the build step).

Steps to reproduce

Compile an SFC that requires sass files from node modules.

What is expected?

To find the sass files correctly from node_modules.

What is actually happening?

Sass files are not found.


I use the plugin to compile an SFC that import a sass file from node_modules. Here is my rollup.config.js:

import vue from "rollup-plugin-vue";
import commonjs from "rollup-plugin-commonjs";
import nodeResolve from "rollup-plugin-node-resolve";
import babel from "rollup-plugin-babel";
import replace from "rollup-plugin-replace";

const { PRODUCTION } = process.env;

export default {
  input: "src/js/index.js",
  output: {
    format: "iife",
    file: "dist/js/index.js"
  },
  plugins: [
    babel({
      presets: ["@babel/preset-env"],
      runtimeHelpers: true,
      plugins: ["@babel/plugin-proposal-export-default-from"]
    }),
    commonjs(),
    nodeResolve(),
    vue(),
    replace({
      "process.env.NODE_ENV": JSON.stringify(PRODUCTION ? "production" : "development")
    })
  ]
};

And here is my SFC (producing the error):

<template lang="pug">
  mdc-button Test
</template>
<script>
import { MdcButton } from "material-components-web-vue";

export default {
  components: {
    MdcButton
  }
};
</script>
<style lang="scss">
@import "@material/button/mdc-button";
</style>

And here is the error:

$ rollup -c

src/js/index.js → dist/js/index.js...
[!] (plugin VuePlugin) Error: Error: Can't find stylesheet to import.
  src\js\page\Home.vue 14:9  root stylesheet
src\js\page\Home.vue
Error: Error: Can't find stylesheet to import.
  src\js\page\Home.vue 14:9  root stylesheet
    at Promise.all.descriptor.styles.map (C:\xampp\htdocs\test-bug\node_modules\rollup-plugin-vue\dist\rollup-plugin-vue.js:241:31)

Does vue template compiler tries to load using CSS3 @import instead of finding from node_modules?

I read those 2 issues:

But it seems not working either (as I do not have the exact code the others persons used I post my full config here and hope someone can answer this tricky question I am struggling with).

vvanpo commented 4 years ago

@znck can you shed some light on this issue, and any potential workarounds? I get the exact same Can't find stylesheet to import. error on all of my Sass imports within .vue files, as they all reference node_modules.

I also don't fully understand why the title was changed to include "before applying scope to scoped styles", as OP does not used scoped within his reproduction example (nor do I).

vvanpo commented 4 years ago

After a bit of digging I've learned that importing node_modules is not actually something sass supports out-of-the-box. I was using the ~@some/package syntax, having assumed that ~ was particular to sass. But it's actually particular to sass-loader, not sass. sass-loader uses it to forward imports to the Webpack resolver. So it looks like there isn't actually a bug in rollup-plugin-vue, here.

Have a read: https://github.com/webpack-contrib/sass-loader/blob/master/README.md#resolving-import-at-rules

For your example, @khalyomede, you should be able to pass sass a load path option, setting it to your node_modules. I think you would use the style.preprocessOptions option: https://github.com/vuejs/rollup-plugin-vue/blob/master/docs/options.md#stylepreprocessoptions. The format of sass options is documented here: https://sass-lang.com/documentation/js-api#includepaths. Unfortunately, this might not work if you're publishing a library, as setting a load path is not equivalent to using node's package resolution algorithm, so package hoisting could go throw a wrench in this. For a regular application, though, it should be fine.

For my case, since I have an existing application with a codebase full of the ~ syntax, and an existing Webpack configuration that I cannot remove, it looks like I'll need to write a custom importer and pass that to sass.

vvanpo commented 4 years ago

@khalyomede a quicker fix would be to run SASS_PATH=node_modules rollup -c, which I can confirm works.

gfera commented 4 years ago

I found a fix that worked great for me:

rollup.config.js


const PATH_SRC = path.resolve(PATH_ROOT, "src").replace(/\\/gi, "/");
const PATH_NODE_MODULES = path
  .resolve(PATH_ROOT, "node_modules")
  .replace(/\\/gi, "/"); // Errm.. I use windows 😬 

export default {
  plugins: [
    vue({
      css: true,
      data: { // This helps to inject variables in each <style> tag of every Vue SFC
        scss: () => `@import "@/styles/variables.scss";`, 
        sass: () => `@import "@/styles/variables.scss"`,
      },
      style: {
        preprocessOptions: {
          scss: {
            importer: [
              function (url, prev) {
                return {
                  file: url
                    .replace(/^~/, `${PATH_NODE_MODULES}/`)
                    .replace(/^@/, PATH_SRC), // ain't pretty, it can be easily improved
                };
              },
            ],
          },
        },
      },
    }),
  ],
};
saraha33 commented 3 years ago

Hi,

i have to admit i just quickly browsed over this issue and i hope i understood the problem right - however just a quick note on how i was able to import files from node modules - namely by altering the config for rollup-plugin-vue the following way:

vue({
      style: {
        preprocessOptions: {
          scss: {
            includePaths: ['node_modules'],
          }
        },
      }),

might this be what you are looking for?

Robin-Hoodie commented 3 years ago

I found a fix that worked great for me:

rollup.config.js


const PATH_SRC = path.resolve(PATH_ROOT, "src").replace(/\\/gi, "/");
const PATH_NODE_MODULES = path
  .resolve(PATH_ROOT, "node_modules")
  .replace(/\\/gi, "/"); // Errm.. I use windows 😬 

export default {
  plugins: [
    vue({
      css: true,
      data: { // This helps to inject variables in each <style> tag of every Vue SFC
        scss: () => `@import "@/styles/variables.scss";`, 
        sass: () => `@import "@/styles/variables.scss"`,
      },
      style: {
        preprocessOptions: {
          scss: {
            importer: [
              function (url, prev) {
                return {
                  file: url
                    .replace(/^~/, `${PATH_NODE_MODULES}/`)
                    .replace(/^@/, PATH_SRC), // ain't pretty, it can be easily improved
                };
              },
            ],
          },
        },
      },
    }),
  ],
};

Just tried this, but found out this does not work for CSS imports. If the import ends on ".css", it will simply not pass the file through the importer function as it contains a .css extension: https://sass-lang.com/documentation/at-rules/import#plain-css-imports.

I have slightly improved your importer function to take extensionless .css imports into account to mimic Sass's CSS imports

 url => {
  let replacedUrl;
  if (url.includes("~")) {
    replacedUrl = url.replace(/^~/, NODE_MODULES_PATH);
    // Extensionless import https://sass-lang.com/documentation/at-rules/import#importing-css
   // This does imply that non CSS files (`.scss` files for example), need an explicit extension
    if (!/\.\w+$/.test(replacedUrl)) {
      replacedUrl = `${replacedUrl}.css`;
    }
  }
  return {
    file: replacedUrl || url
  };
}

I am not sure if sass-loader works with extensionless imports. I hope it does, so I can replace my rollup config with a webpack config without making too many changes in the sourcecode. Note that I did not replace @ as I didn't really need that for my use case.

EDIT: It does seem to still be applying the scoped [data-xxx] attribute to the imported classes, which I presume @khalyomede was referring to in the title.

atflick commented 3 years ago

Posting in case it's helpful to other's in the future, but I used a combination of other responses above which provides a more simple approach then replacing aliases... only note is that documentation on this could probably be better. I did have to define data in 2 different places to get it to work. I also use node-sass-glob-importer for globbing my mixins/functions inside my library.scss file, so if using that make sure to add it and this above the config

const globImporter = require('node-sass-glob-importer');
      data: {
        scss: () => '@import "library";'
      },
      style: {
        preprocessOptions: {
          scss: {
            importer: globImporter(),
            includePaths: [
              path.resolve(__dirname, '../node_modules/breakpoint-sass/stylesheets/'),
              path.resolve(__dirname, '../en-style/src/scss'),
              path.resolve(__dirname, '../en-style/src'),
            ],
            data: '@import "library";'
          }
        }
      }
plummer-flex commented 2 years ago

Is there any documentation of what options are available in preprocessOptions? I can not get the data pattern to inject the variables import to work in any context.

atflick commented 2 years ago

@plummer-flex that's within the vue-rollup-plugin here (which isn't very useful) https://rollup-plugin-vue.vuejs.org/options.html#style-preprocessoptions

but I think that just takes options similar to the Vue CLI CSS loaderOptions https://cli.vuejs.org/guide/css.html#passing-options-to-pre-processor-loaders

Furthermore, I believe all the css loaders had to make slight changes to their options for webpack 5 so I think you want to be using the options pre-webpack 5. I think for sass-loader version 10 is still compatible with webpack 4.