vite
                                
                                 vite copied to clipboard
                                
                                    vite copied to clipboard
                            
                            
                            
                        Importing a script file containing `import` with `?url` does not work with build
Describe the bug
When the following conditions are met, it works with vite dev but it does not with vite preview.
- a script (foo.js) contains aimport
- foo.jsis imported by- import 'foo.js?url'
When running vite preview, the error below happened.
Uncaught (in promise) TypeError: Failed to resolve module specifier "./foo". Invalid relative url or base scheme isn't hierarchical.
It seems like files imported with ?url are not bundled because ./foo does not exist in dist.
Reproduction
https://stackblitz.com/edit/vitejs-vite-cdjop5?file=main.js
System Info
System:
    OS: Windows 10 10.0.19042
    CPU: (16) x64 AMD Ryzen 7 3700X 8-Core Processor
    Memory: 9.63 GB / 31.93 GB
  Binaries:
    Node: 16.13.2 - C:\Program Files\nodejs\node.EXE
    Yarn: 1.22.17 - C:\Program Files\nodejs\yarn.CMD
    npm: 7.24.2 - C:\Program Files\nodejs\npm.CMD
  Browsers:
    Edge: Spartan (44.19041.1266.0), Chromium (97.0.1072.76)
    Internet Explorer: 11.0.19041.1202
  npmPackages:
    vite: ^2.7.13 => 2.7.13
Used Package Manager
npm
Logs
vite:config no config file found. +0ms
  vite:config using resolved config: {
  vite:config   root: '/home/projects/vitejs-vite-cdjop5',
  vite:config   base: '/',
  vite:config   mode: 'production',
  vite:config   configFile: undefined,
  vite:config   logLevel: undefined,
  vite:config   clearScreen: undefined,
  vite:config   build: {
  vite:config     target: [ 'es2019', 'edge88', 'firefox78', 'chrome87', 'safari13.1' ],
  vite:config     polyfillModulePreload: true,
  vite:config     outDir: '/home/projects/vitejs-vite-cdjop5/dist',
  vite:config     assetsDir: 'assets',
  vite:config     assetsInlineLimit: 4096,
  vite:config     cssCodeSplit: true,
  vite:config     cssTarget: [ 'es2019', 'edge88', 'firefox78', 'chrome87', 'safari13.1' ],
  vite:config     sourcemap: false,
  vite:config     rollupOptions: { input: '/home/projects/vitejs-vite-cdjop5/index.html' },
  vite:config     minify: 'esbuild',
  vite:config     terserOptions: {},
  vite:config     write: true,
  vite:config     emptyOutDir: null,
  vite:config     manifest: false,
  vite:config     lib: false,
  vite:config     ssr: false,
  vite:config     ssrManifest: false,
  vite:config     reportCompressedSize: true,
  vite:config     chunkSizeWarningLimit: 500,
  vite:config     watch: null,
  vite:config     commonjsOptions: { include: [Array], extensions: [Array] },
  vite:config     dynamicImportVarsOptions: { warnOnError: true, exclude: [Array] }
  vite:config   },
  vite:config   configFileDependencies: [],
  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     build: {}
  vite:config   },
  vite:config   resolve: { dedupe: undefined, alias: [ [Object], [Object] ] },
  vite:config   publicDir: '/home/projects/vitejs-vite-cdjop5/public',
  vite:config   cacheDir: '/home/projects/vitejs-vite-cdjop5/node_modules/.vite',
  vite:config   command: 'build',
  vite:config   isProduction: true,
  vite:config   plugins: [
  vite:config     'alias',
  vite:config     'vite:modulepreload-polyfill',
  vite:config     'vite:resolve',
  vite:config     'vite:html-inline-script-proxy',
  vite:config     'vite:css',
  vite:config     'vite:esbuild',
  vite:config     'vite:json',
  vite:config     'vite:wasm',
  vite:config     'vite:worker',
  vite:config     'vite:asset',
  vite:config     'vite:define',
  vite:config     'vite:css-post',
  vite:config     'vite:watch-package-data',
  vite:config     'vite:build-html',
  vite:config     'commonjs',
  vite:config     'vite:data-uri',
  vite:config     'rollup-plugin-dynamic-import-variables',
  vite:config     'vite:asset-import-meta-url',
  vite:config     'vite:build-import-analysis',
  vite:config     'vite:esbuild-transpile',
  vite:config     'vite:reporter',
  vite:config     'vite:load-fallback'
  vite:config   ],
  vite:config   server: {
  vite:config     preTransformRequests: true,
  vite:config     fs: { strict: true, allow: [Array], deny: [Array] }
  vite:config   },
  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: undefined
  vite:config   },
  vite:config   env: { BASE_URL: '/', MODE: 'production', DEV: false, PROD: true },
  vite:config   assetsInclude: [Function: assetsInclude],
  vite:config   logger: {
  vite:config     hasWarned: false,
  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(0) { set: [Function (anonymous)] },
  vite:config   createResolver: [Function: createResolver],
  vite:config   optimizeDeps: {
  vite:config     esbuildOptions: { keepNames: undefined, preserveSymlinks: undefined }
  vite:config   }
  vite:config } +5ms
vite v2.7.13 building for production...
✓ 4 modules transformed.
dist/index.html                 0.38 KiB
dist/assets/index.c5184935.js   1.17 KiB / gzip: 0.73 KiB
Validations
- [X] Follow our Code of Conduct
- [X] Read the Contributing Guidelines.
- [X] Read the docs.
- [X] Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
- [X] Make sure this is a Vite issue and not a framework-specific issue. For example, if it's a Vue SFC related bug, it should likely be reported to https://github.com/vuejs/core instead.
- [X] Check that this is a concrete bug. For Q&A open a GitHub Discussion or join our Discord Chat Server.
- [X] The provided reproduction is a minimal reproducible example of the bug.
Related to this issue, https://github.com/vitejs/vite/issues/9606 was closed with the reasoning:
Worklets are an experimental feature
Would it be possible to clarify what is meant by this? I don't think worklets are considered experimental in the spec and have very wide browser support. Does it mean Vite's support for them is experimental?
Also I'd be interested to know if there are any workarounds for TypeScript worklets with dependencies? I can't place "the processor JS code inside the public directory" as suggested in #9606 as they need to be transpiled and have their dependencies bundled.
Related: https://github.com/vitejs/vite/issues/6979
Ok figured it out, I can do
import MyWorkletProcessorUrl from "./MyWorkletProcessor?worker&url";
...
await context.audioWorklet.addModule(MyWorkletProcessorUrl);
https://github.com/vitejs/vite/blob/main/docs/guide/features.md#import-with-query-suffixes
This does not work when you build a lib and have to inline the worklet into the bundle, e.g. using: ?worker&url&inline. That results in the inlined code not being compiled as expected and with an mp2t mime type in case you use typescript as also mentioned in some other issues like #11823.
AudioWorklet has been supported since 2018 by Chrome and since early 2021 by Safari so I'm also surprised to find Vite still considers this to be an experimental API.
So far I have not found any way to get this working besides using another build tool or phase to prepare a JS bundle file for vite to consume which seems painfully cumbersome to me. Rewriting the worklet code into JS that can be imported and used raw, doesn't seem practical either. Could this be resolved using a plugin? So far I've only found vite-plugin-worker, but it's deprecated.
Shall I create a new issue for adding support for importing with ?worklet&inline or ?audioworklet&inline like we can already with ?worker&inline?
Or could somebody please suggest a better workaround for me here? Thanks!
I'm currently working around this using the quickly hacked together plugin below in case anybody else is interested. Unfortunately emoji characters within my script don't get decoded to utf8 as expected on the other side; I'm still trying to figure out how to fix that.
function tsBundleUrlPlugin(): PluginOption {
  let viteConfig: UserConfig;
  return {
    name: 'vite-plugin-ts-bundle-url',
    apply: 'build',
    enforce: 'post',
    config(config) {
      viteConfig = config;
    },
    async transform(_code, id) {
      if (!id.endsWith('.ts?url')) {
        return;
      }
      const quietLogger = createLogger();
      quietLogger.info = () => undefined;
      const output = await build({
        ...viteConfig,
        configFile: false,
        clearScreen: false,
        customLogger: quietLogger,
        build: {
          ...viteConfig.build,
          lib: {
            entry: id.replace('?url', ''),
            name: '_',
            formats: ['iife'],
          },
          write: false,
        },
      });
      if (!(output instanceof Array)) {
        throw new Error('Expected output to be Array');
      }
      const iife = output[0].output[0].code;
      const encoded = Buffer.from(iife, 'utf8').toString('base64');
      const transformed = `export default "data:text/javascript;base64,${encoded}";`;
      // TODO: Fix this so emoji etc. get properly decoded from within audio worklet module added using this url
      const relative = relativePath('.', id);
      console.log(
        `TypeScript bundle url: ${relative} (${transformed.length} bytes)`,
      );
      // eslint-disable-next-line consistent-return -- We need to return a string here and undefined above
      return transformed;
    },
  };
}
For people looking for a workaround, this worked for me: https://github.com/vitejs/vite/issues/15431#issuecomment-1870052169
Ok figured it out, I can do
import MyWorkletProcessorUrl from "./MyWorkletProcessor?worker&url"; ... await context.audioWorklet.addModule(MyWorkletProcessorUrl);https://github.com/vitejs/vite/blob/main/docs/guide/features.md#import-with-query-suffixes
This can temporarily solve my problem, but I don't think it's a good solution because, when I'm in development mode, '?url' can work, but once it's built, it doesn't meet expectations. '?worker&url' seems like a hacky way to use it.