sveltejs / vite-plugin-svelte

Svelte plugin for http://vitejs.dev/
MIT License
864 stars 105 forks source link

Unable to reference global @custom-media rules in svelte component #814

Closed mattpilott closed 11 months ago

mattpilott commented 11 months ago

Describe the bug

If my app.css defines a @custom-media declaration and i use that in app.css everything works as expected. If i try to use that same custom media variable in a svelte component i get this error:

[SyntaxError: Custom media query --from-mobile is not defined]

If i add the @custom-media rule to the svelte component then it works but this of course defeats the purpose of defining custom-media rules.

Im not using a preprocessor, just vite with the lightningcss option

Reproduction URL

https://github.com/mattpilott/repro-custom-media

Reproduction

npm i npm run dev

You can see the h1 is blue but should be red from 640px and up

Logs

vite:config bundled config file loaded in 200.73ms +0ms
  vite:vite-plugin-svelte adding bare svelte packages to optimizeDeps.include: svelte/animate, svelte/easing, svelte/internal, svelte/motion, svelte/store, svelte/transition, svelte, svelte/internal/disclose-version  +0ms
  vite:vite-plugin-svelte extra config for dependencies generated by vitefu {
  optimizeDeps: { include: [], exclude: [] },
  ssr: { noExternal: [], external: [] }
} +1ms
  vite:vite-plugin-svelte post-processed extra config for dependencies {
  optimizeDeps: { include: [], exclude: [] },
  ssr: { noExternal: [], external: [] }
} +1ms
  vite:vite-plugin-svelte enabling "experimental.hmrPartialAccept" in vite config +0ms
  vite:vite-plugin-svelte additional vite config {
  resolve: {
    dedupe: [
      'svelte/animate',
      'svelte/easing',
      'svelte/internal',
      'svelte/motion',
      'svelte/ssr',
      'svelte/store',
      'svelte/transition',
      'svelte',
      'svelte/internal/disclose-version',
      'svelte-hmr/runtime/hot-api-esm.js',
      'svelte-hmr/runtime/proxy-adapter-dom.js',
      'svelte-hmr'
    ],
    conditions: [ 'svelte' ]
  },
  optimizeDeps: {
    include: [
      'svelte/animate',
      'svelte/easing',
      'svelte/internal',
      'svelte/motion',
      'svelte/store',
      'svelte/transition',
      'svelte',
      'svelte/internal/disclose-version'
    ],
    exclude: [ 'svelte-hmr' ],
    extensions: [ '.svelte' ],
    esbuildOptions: { plugins: [Array] }
  },
  ssr: { external: [], noExternal: [ 'svelte', /^svelte\// ] },
  experimental: { hmrPartialAccept: true }
} +0ms
  vite:vite-plugin-svelte resolved options {
  hot: { injectCss: false, partialAccept: true },
  compilerOptions: { css: 'external', dev: true, hydratable: true },
  extensions: [ '.svelte' ],
  emitCss: true,
  prebundleSvelteLibraries: true,
  configFile: false,
  preprocess: { script: [Function: script], markup: [Function: markup] },
  onwarn: undefined,
  root: '/Users/Matt/Sites/repro-custom-media',
  isBuild: false,
  isServe: true,
  isDebug: true,
  isProduction: false,
  stats: VitePluginSvelteStats {}
} +17ms

  vite:config using resolved config: {
  vite:config   plugins: [
  vite:config     'vite:optimized-deps',
  vite:config     'vite:watch-package-data',
  vite:config     'vite:pre-alias',
  vite:config     'alias',
  vite:config     'vite-plugin-svelte',
  vite:config     'vite-plugin-svelte-inspector',
  vite:config     'vite:modulepreload-polyfill',
  vite:config     'vite:resolve',
  vite:config     'vite:html-inline-proxy',
  vite:config     'vite:css',
  vite:config     'vite:esbuild',
  vite:config     'vite:json',
  vite:config     'vite:wasm-helper',
  vite:config     'vite:worker',
  vite:config     'vite:asset',
  vite:config     'vite-plugin-sveltekit-setup',
  vite:config     'vite-plugin-sveltekit-virtual-modules',
  vite:config     'vite-plugin-sveltekit-guard',
  vite:config     'vite-plugin-sveltekit-compile',
  vite:config     'vite:wasm-fallback',
  vite:config     'vite:define',
  vite:config     'vite:css-post',
  vite:config     'vite:worker-import-meta-url',
  vite:config     'vite:asset-import-meta-url',
  vite:config     'vite:dynamic-import-vars',
  vite:config     'vite:import-glob',
  vite:config     'vite:client-inject',
  vite:config     'vite:import-analysis'
  vite:config   ],
  vite:config   build: {
  vite:config     target: [ 'es2020', 'edge88', 'firefox78', 'chrome87', 'safari14' ],
  vite:config     cssTarget: [ 'es2020', 'edge88', 'firefox78', 'chrome87', 'safari14' ],
  vite:config     outDir: 'dist',
  vite:config     assetsDir: 'assets',
  vite:config     assetsInlineLimit: 4096,
  vite:config     cssCodeSplit: true,
  vite:config     sourcemap: false,
  vite:config     rollupOptions: {
  vite:config       input: '/Users/Matt/Sites/repro-custom-media/node_modules/.pnpm/@sveltejs+kit@1.27.6_svelte@4.2.7_vite@5.0.2/node_modules/@sveltejs/kit/src/runtime/client/start.js'
  vite:config     },
  vite:config     minify: 'esbuild',
  vite:config     terserOptions: {},
  vite:config     write: true,
  vite:config     emptyOutDir: null,
  vite:config     copyPublicDir: true,
  vite:config     manifest: false,
  vite:config     lib: false,
  vite:config     ssr: false,
  vite:config     ssrManifest: false,
  vite:config     ssrEmitAssets: false,
  vite:config     reportCompressedSize: true,
  vite:config     chunkSizeWarningLimit: 500,
  vite:config     watch: null,
  vite:config     cssMinify: 'lightningcss',
  vite:config     commonjsOptions: { include: [Array], extensions: [Array] },
  vite:config     dynamicImportVarsOptions: { warnOnError: true, exclude: [Array] },
  vite:config     modulePreload: { polyfill: true }
  vite:config   },
  vite:config   css: {
  vite:config     transformer: 'lightningcss',
  vite:config     lightningcss: { drafts: [Object], targets: [Object] }
  vite:config   },
  vite:config   optimizeDeps: {
  vite:config     disabled: 'build',
  vite:config     force: undefined,
  vite:config     include: [
  vite:config       'svelte/animate',
  vite:config       'svelte/easing',
  vite:config       'svelte/internal',
  vite:config       'svelte/motion',
  vite:config       'svelte/store',
  vite:config       'svelte/transition',
  vite:config       'svelte',
  vite:config       'svelte/internal/disclose-version'
  vite:config     ],
  vite:config     exclude: [ 'svelte-hmr', '@sveltejs/kit', '$app', '$env' ],
  vite:config     extensions: [ '.svelte' ],
  vite:config     esbuildOptions: { preserveSymlinks: false, plugins: [Array] }
  vite:config   },
  vite:config   server: {
  vite:config     preTransformRequests: true,
  vite:config     cors: { preflightContinue: true },
  vite:config     fs: { strict: true, allow: [Array], deny: [Array] },
  vite:config     sourcemapIgnoreList: [Function: sourcemapIgnoreList],
  vite:config     watch: { ignored: [Array] },
  vite:config     middlewareMode: false
  vite:config   },
  vite:config   resolve: {
  vite:config     mainFields: [ 'svelte', 'module', 'jsnext:main', 'jsnext' ],
  vite:config     conditions: [ 'svelte' ],
  vite:config     extensions: [
  vite:config       '.mjs',  '.js',
  vite:config       '.mts',  '.ts',
  vite:config       '.jsx',  '.tsx',
  vite:config       '.json'
  vite:config     ],
  vite:config     dedupe: [
  vite:config       'svelte/animate',
  vite:config       'svelte/easing',
  vite:config       'svelte/internal',
  vite:config       'svelte/motion',
  vite:config       'svelte/ssr',
  vite:config       'svelte/store',
  vite:config       'svelte/transition',
  vite:config       'svelte',
  vite:config       'svelte/internal/disclose-version',
  vite:config       'svelte-hmr/runtime/hot-api-esm.js',
  vite:config       'svelte-hmr/runtime/proxy-adapter-dom.js',
  vite:config       'svelte-hmr'
  vite:config     ],
  vite:config     preserveSymlinks: false,
  vite:config     alias: [ [Object], [Object], [Object], [Object], [Object] ]
  vite:config   },
  vite:config   ssr: {
  vite:config     target: 'node',
  vite:config     external: [ 'cookie', 'set-cookie-parser' ],
  vite:config     noExternal: [ 'svelte', /^svelte\//, 'esm-env', '@sveltejs/kit' ],
  vite:config     optimizeDeps: { disabled: true, esbuildOptions: [Object] }
  vite:config   },
  vite:config   experimental: { importGlobRestoreExtension: false, hmrPartialAccept: true },
  vite:config   root: '/Users/Matt/Sites/repro-custom-media',
  vite:config   preview: {
  vite:config     port: undefined,
  vite:config     strictPort: undefined,
  vite:config     host: undefined,
  vite:config     https: undefined,
  vite:config     open: undefined,
  vite:config     proxy: undefined,
  vite:config     cors: { preflightContinue: true },
  vite:config     headers: undefined
  vite:config   },
  vite:config   define: {
  vite:config     __SVELTEKIT_APP_VERSION_POLL_INTERVAL__: '0',
  vite:config     __SVELTEKIT_DEV__: 'true',
  vite:config     __SVELTEKIT_EMBEDDED__: 'false'
  vite:config   },
  vite:config   appType: 'custom',
  vite:config   base: '/',
  vite:config   publicDir: '/Users/Matt/Sites/repro-custom-media/static',
  vite:config   configFile: '/Users/Matt/Sites/repro-custom-media/vite.config.js',
  vite:config   configFileDependencies: [ '/Users/Matt/Sites/repro-custom-media/vite.config.js' ],
  vite:config   inlineConfig: {
  vite:config     root: undefined,
  vite:config     base: undefined,
  vite:config     mode: undefined,
  vite:config     configFile: undefined,
  vite:config     logLevel: undefined,
  vite:config     clearScreen: undefined,
  vite:config     optimizeDeps: { force: undefined },
  vite:config     server: {}
  vite:config   },
  vite:config   rawBase: '/',
  vite:config   cacheDir: '/Users/Matt/Sites/repro-custom-media/node_modules/.vite',
  vite:config   command: 'serve',
  vite:config   mode: 'development',
  vite:config   isWorker: false,
  vite:config   mainConfig: null,
  vite:config   isProduction: false,
  vite:config   esbuild: { jsxDev: true },
  vite:config   envDir: '/Users/Matt/Sites/repro-custom-media',
  vite:config   env: { BASE_URL: '/', MODE: 'development', DEV: true, PROD: false },
  vite:config   assetsInclude: [Function: assetsInclude],
  vite:config   logger: {
  vite:config     hasWarned: true,
  vite:config     info: [Function: info],
  vite:config     warn: [Function: warn],
  vite:config     warnOnce: [Function: warnOnce],
  vite:config     error: [Function: error],
  vite:config     clearScreen: [Function: clearScreen],
  vite:config     hasErrorLogged: [Function: hasErrorLogged]
  vite:config   },
  vite:config   packageCache: Map(1) {
  vite:config     'fnpd_/Users/Matt/Sites/repro-custom-media' => {
  vite:config       dir: '/Users/Matt/Sites/repro-custom-media',
  vite:config       data: [Object],
  vite:config       hasSideEffects: [Function: hasSideEffects],
  vite:config       webResolvedImports: {},
  vite:config       nodeResolvedImports: {},
  vite:config       setResolvedCache: [Function: setResolvedCache],
  vite:config       getResolvedCache: [Function: getResolvedCache]
  vite:config     },
  vite:config     set: [Function (anonymous)]
  vite:config   },
  vite:config   createResolver: [Function: createResolver],
  vite:config   worker: { format: 'iife', plugins: '() => plugins', rollupOptions: {} },
  vite:config   getSortedPlugins: [Function: getSortedPlugins],
  vite:config   getSortedPluginHooks: [Function: getSortedPluginHooks]
  vite:config } +25ms
  vite:resolve 0.99ms /node_modules/.pnpm/@sveltejs+kit@1.27.6_svelte@4.2.7_vite@5.0.2/node_modules/@sveltejs/kit/src/runtime/control.js -> /Users/Matt/Sites/repro-custom-media/node_modules/.pnpm/@sveltejs+kit@1.27.6_svelte@4.2.7_vite@5.0.2/node_modules/@sveltejs/kit/src/runtime/control.js +0ms
  vite:load 5.32ms [fs] /node_modules/.pnpm/@sveltejs+kit@1.27.6_svelte@4.2.7_vite@5.0.2/node_modules/@sveltejs/kit/src/runtime/control.js +0ms
  vite:import-analysis 0.57ms [no imports] node_modules/.pnpm/@sveltejs+kit@1.27.6_svelte@4.2.7_vite@5.0.2/node_modules/@sveltejs/kit/src/runtime/control.js +0ms
  vite:transform 2.14ms /node_modules/.pnpm/@sveltejs+kit@1.27.6_svelte@4.2.7_vite@5.0.2/node_modules/@sveltejs/kit/src/runtime/control.js +0ms
  vite:deps Hash is consistent. Skipping. Use --force to override. +0ms

System Info

System:
    OS: macOS 14.2
    CPU: (16) arm64 Apple M3 Max
    Memory: 4.16 GB / 48.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 20.10.0 - /opt/homebrew/bin/node
    npm: 10.2.4 - /opt/homebrew/bin/npm
    pnpm: 8.11.0 - /opt/homebrew/bin/pnpm
    bun: 1.0.14 - ~/.bun/bin/bun
  Browsers:
    Safari: 17.2
  npmPackages:
    @sveltejs/adapter-auto: ^2.0.0 => 2.1.1 
    @sveltejs/kit: ^1.27.4 => 1.27.6 
    svelte: ^4.2.7 => 4.2.7 
    vite: ^5.0.2 => 5.0.2
dominikg commented 11 months ago

note: sveltekit does not support vite5 yet. For a minimal reproduction please use npm create vite@latest --template svelte

I'm not familiar with how lightningcss works with custom media queries, but they have to be available to it when svelte components are preprocessed. app.css is processed independently, so there would have to be a way to let lightningcss know which values exist.

You can't pass through unprocessed custom media queries yet as svelte itself doesn't understand the syntax but needs to be able to parse the style block to apply its scoped styling.

To avoid duplication in .svelte files you could look into adding a svelte css preprocessor that prepends a banner with the custom media values used in the style.

// svelte.config.js
export default {
  preprocess:[{name:'custom-media-banner', style(){
    // if css contains custom-media
    // prepend content of custom-media.css with magic-string
    // return modified css, map and dependencies:[custom-media.css]
  }}]
}

some docs for preprocessors here: https://svelte.dev/docs/svelte-compiler#preprocess

dominikg commented 11 months ago

see this comment too https://github.com/parcel-bundler/lightningcss/issues/402#issuecomment-1397281555

it could work in build mode when lightningcss bundler is used, but vite-dev is unbundled, so it would still be a problem then. There's not much vite-plugin-svelte can do about it, it would have to come from vite itself. But even there its a hard problem because editing app.css to update a custom-media value would have to invalidate all modules that used that value. But lightningcss itself wouldn't have the ability to track that usage and trigger the invalidations.

I suggest you try to reproduce this dev issue without svelte involved npm create vite@latest --template vanilla create some css files where one defines custom-media, others use it and then see if vite dev is able to update accordingly. File an issue with that reproduction at vite repo.

dominikg commented 11 months ago

related vite issue https://github.com/vitejs/vite/issues/11029

ArnaudBarre commented 11 months ago

Yeah because of how Vite works in dev there is not much we can do here (and I'm not even sure build will works). But what you can do because Vite runs lightning css bundling for each css file, is that you can import the custom queries on every file that needs it, and then it gets inlined.

Quick example:

/* custom-medias.css */
@custom-media --from-mobile (min-width: 40em);
/* pages/page1.css */
@import "../custom-medias.css";

h1 {
  @media (--from-mobile) {
    color: red;
  }
}
/* pages/page2.css */
@import "../custom-medias.css";

h2 {
  @media (--from-mobile) {
    color: blue;
  }
}

Will output:

/* pages/page1.css */
@media (min-width: 40em) {
  h1 {
    color: red;
  }
}
/* pages/page2.css */
@media (min-width: 40em) {
  h2 {
    color: #00f;
  }
}
dominikg commented 11 months ago

you can also combine the @import with the banner preprocessor i sketched above, which means you don't have to remember to add the import manually. This allows you to use them in your svelte styles and the dependency on custom-medias.css should be tracked automatically.

dominikg commented 11 months ago

as mentioned above there's nothing v-p-s itself can do about this, it's how vite works. While other bundlers (notably parcel/lightningcss itself) work differently, we can't change this, so i'll close this one as "wontfix". Please do reach out if the suggestions above don't work for you. There's also the svelte and vite discords with active helping communities if you want a more direct chat.