vitejs / vite

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

Importing a script file containing `import` with `?url` does not work with build #6757

Open sapphi-red opened 2 years ago

sapphi-red commented 2 years ago

Describe the bug

When the following conditions are met, it works with vite dev but it does not with vite preview.

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

penx commented 1 year ago

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

penx commented 1 year ago

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

difosfor commented 1 year ago

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!

difosfor commented 1 year ago

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;
    },
  };
}
dummdidumm commented 9 months ago

For people looking for a workaround, this worked for me: https://github.com/vitejs/vite/issues/15431#issuecomment-1870052169

yanbingbing commented 5 months ago

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.