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 byimport '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.