vercel / styled-jsx

Full CSS support for JSX without compromises
http://npmjs.com/styled-jsx
MIT License
7.72k stars 261 forks source link

Resolve urls inside separate css-files #598

Open germansokolov13 opened 5 years ago

germansokolov13 commented 5 years ago

Do you want to request a feature or report a bug?

Request a feature or request docs addition

Images in url() do not resolve if separate css-files setup is used.

https://github.com/zeit/styled-jsx#styles-in-regular-css-files Here it is documented how to use separate files for styles. However if we do that we can no longer insert images resolved by Webpack.

I tried to use css-loader but then the styles are rendered in such a way that it won't work at all.

Are there any plans or ideas how to handle this properly? Css inside components doesn't seem right but images have to be handled with Webpack. Normally there are some means to parse them out of url(). Without Webpack I can't use image loaders like those that encode images in base64. Without separate files I can't use IDE css helping tools. Something is off.

I ended up rolling my own Webpack loader to handle this but it seems like too much complexity.

giuseppeg commented 4 years ago

Do you know how other projects do this with loaders and such? Eg. if you have a sass project and build it with webpack (no styled-jsx). How do you solve this issue?

germansokolov13 commented 4 years ago

I think in majority of frontend projects that use Webpack css-loader is always used. This loader is the one responsible for resolving url()s in css-files. But css-loader does something else too while doing this. Its output is somehow incompatible with styled-jsx.

Most often other projects use a combination of style-loader and css-loader called at the very end of styles processing. I.e. first different kinds of loaders like sass and so on are called. css-loader is always the last but one being called. And style-loader is normally the last one.

But I do not know how to integrate it with styled-jsx

giuseppeg commented 4 years ago

Yes the styled-jsx loader turns CSS into JavaScript, so the url resolving should be done earlier. Have you tried to run your CSS through https://www.npmjs.com/package/resolve-url-loader before passing it to the styled-jsx's loader?

germansokolov13 commented 4 years ago

I tried now. If invoked before styledJsxLoader it seems to do nothing for some reason. Urls remain unchanged. If invoked after styledJsxLoader then error occurrs during build: Error: resolve-url-loader: CSS error <path to my file>: Unknown word.

I am not sure how this loader works. I used it without any options. Maybe it should configured somehow.

danielhusar commented 4 years ago

I was having the same issue, ended up creating my own custom loader:

function isAbsolutePath(url) {
  return /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(url)
}

module.exports = function (content) {
  if (this.cacheable) this.cacheable()
  this.addDependency(this.resourcePath)

  let index = 0
  let imports = ''
  const output = content.replace(/(?:url(?:\s+)?)\((?:'|")?(.*[^'"])(?:'|")?\)/gi, (...args) => {
    const url = args[1]
    if (isAbsolutePath(url)) {
      return args[0]
    }
    const name = `_temp_url_${index}`
    const nameAsVar = '${' + name + '}'
    imports = `${imports} import ${name} from '${url}';`
    index++
    return `url("${nameAsVar}")`
  })

  this.callback(null, `${imports} ${output}`)
}

Put this loader in between styled-jsx/webpack and options.defaultLoaders.babel (if you are using next.js) something like this:

config.module.rules.push({
  test: /\.scss$/,
  use: [
    options.defaultLoaders.babel,
    {
      loader: path.join(__dirname, './lib/styled-jsx-url-loader'),
    },
    {
      loader: require('styled-jsx/webpack').loader,
      options: {
        type: 'scoped',
      },
    },
  ],
})

It's not sure sophisticated, basically just one big regexp replace so there could be some false positives, but it does the job for me.

danielhusar commented 4 years ago

I made also a loader for that https://github.com/danielhusar/styled-jsx-url-loader