Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"vite" rewrites "named import" of typescript enums as "default import" and breaks app #17878

Open
7 tasks done
superboum opened this issue Aug 12, 2024 · 2 comments
Open
7 tasks done
Labels
feat: deps optimizer Esbuild Dependencies Optimization p3-minor-bug An edge case that only affects very specific usage (priority)

Comments

@superboum
Copy link

superboum commented Aug 12, 2024

Describe the bug

I try to use a named import (import { Status } from 'ts-enum/status.ts'), I expect the import to work (it works with yarn build), but vite rewrites it as a "default import" (cf below) which is wrong (cf error).

vite import rewrite:

import __vite__cjsImport0_tsEnum_status from "/node_modules/.vite/deps/ts-enum_status.js?v=af7bba1f"; const Status = __vite__cjsImport0_tsEnum_status["Status"];

browser error (from dev console, both Firefox and Chrome):

main.ts:1 Uncaught SyntaxError: The requested module '/node_modules/.vite/deps/ts-enum_status.js?v=d7be5188' does not provide an export named 'default' (at main.ts:1:1)

Issues that might be related: #17727 ; #11231 ; #14018 ; #11783 ; #15434 ; #17619 ; #16742 ; #16293 ; #15620

Reproduction

https://gist.github.com/superboum/1d59a67290a3224d83fbc5cb7f97fb03

Steps to reproduce

Create a simple typescript module:

mkdir ts-enum
cd ts-enum
yarn init --yes
echo "export enum Status { GREEN = 'GREEN' }" > status.ts
# :warning: The issue seems directly related to enums. If we replace the enum by a struct, the error does not occur:
# echo 'export let Status = { "GREEN": "GREEN" }' > status.ts
cd ..

Create a vite project with the typescript template:

yarn create vite vite-bug --template vanilla-ts
cd vite-bug

Replace yarn PnP module resolution with the traditional node-modules

yarn config set nodeLinker node-modules

Now add our dependency (problem also exists if the dependency is pulled from git or a npm registry):

yarn add ts-enum@file:./../ts-enum
yarn install

Write the most straightforward javascript that will trigger the bug:

echo "import { Status } from 'ts-enum/status'" > src/main.ts
echo 'console.log(Status.GREEN)' >> src/main.ts

Building works perfectly, you can try with:

yarn build
cd dist ; python -m http.server
# go to http://localhost:8000 ; open the dev console ; see no error, only a `GREEN` console log. It works!

But vite breaks our app:

yarn dev
# go to http://localhost:5173 ; open the dev console ; see an import error

System Info

  System:
    OS: Linux 6.6 NixOS 24.05 (Uakari) 24.05 (Uakari)
    CPU: (4) x64 Intel(R) Core(TM) i7-4500U CPU @ 1.80GHz
    Memory: 9.71 GB / 15.53 GB
    Container: Yes
    Shell: 5.2.26 - /nix/store/pg0zqlym7y3cd8631b3x6cghxcmjl8xa-bash-interactive-5.2p26/bin/bash
  Binaries:
    Node: 18.20.4 - /run/current-system/sw/bin/node
    Yarn: 4.2.2 - /nix/store/wvalqmxg0275mf7alwyl0x0fvjbgcb8j-yarn-berry-4.2.2/bin/yarn
    npm: 10.7.0 - /run/current-system/sw/bin/npm
  Browsers:
    Chromium: 127.0.6533.88
    Firefox: 129.0
  npmPackages:
    vite: ^5.4.0 => 5.4.0

Used Package Manager

yarn

Logs

vite and vite build do not produce any error.
But loading the website in a browser with vite will produce this error:

main.ts:1 Uncaught SyntaxError: The requested module '/node_modules/.vite/deps/ts-enum_status.js?v=d7be5188' does not provide an export named 'default' (at main.ts:1:1)

Still, the output of vite --debug:

Reveal output
[nix-shell:/tmp/vite-bug]$ yarn run vite --debug
  vite:config no config file found. +0ms
  vite:config using resolved config: {
  vite:config   root: '/tmp/vite-bug',
  vite:config   base: '/',
  vite:config   mode: 'development',
  vite:config   configFile: undefined,
  vite:config   logLevel: undefined,
  vite:config   clearScreen: undefined,
  vite:config   optimizeDeps: {
  vite:config     holdUntilCrawlEnd: true,
  vite:config     force: undefined,
  vite:config     esbuildOptions: { preserveSymlinks: false }
  vite:config   },
  vite:config   server: {
  vite:config     preTransformRequests: true,
  vite:config     host: undefined,
  vite:config     sourcemapIgnoreList: [Function: isInNodeModules$1],
  vite:config     middlewareMode: false,
  vite:config     fs: {
  vite:config       strict: true,
  vite:config       allow: [Array],
  vite:config       deny: [Array],
  vite:config       cachedChecks: undefined
  vite:config     }
  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     optimizeDeps: { force: undefined },
  vite:config     server: { host: undefined }
  vite:config   },
  vite:config   decodedBase: '/',
  vite:config   rawBase: '/',
  vite:config   resolve: {
  vite:config     mainFields: [ 'browser', 'module', 'jsnext:main', 'jsnext' ],
  vite:config     conditions: [],
  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     preserveSymlinks: false,
  vite:config     alias: [ [Object], [Object] ]
  vite:config   },
  vite:config   publicDir: '/tmp/vite-bug/public',
  vite:config   cacheDir: '/tmp/vite-bug/node_modules/.vite',
  vite:config   command: 'serve',
  vite:config   ssr: {
  vite:config     target: 'node',
  vite:config     optimizeDeps: { noDiscovery: true, esbuildOptions: [Object] }
  vite:config   },
  vite:config   isWorker: false,
  vite:config   mainConfig: null,
  vite:config   bundleChain: [],
  vite:config   isProduction: false,
  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: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: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:css-analysis',
  vite:config     'vite:import-analysis'
  vite:config   ],
  vite:config   css: { lightningcss: undefined },
  vite:config   esbuild: { jsxDev: true },
  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     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     commonjsOptions: { include: [Array], extensions: [Array] },
  vite:config     dynamicImportVarsOptions: { warnOnError: true, exclude: [Array] },
  vite:config     modulePreload: { polyfill: true },
  vite:config     cssMinify: true
  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     headers: undefined
  vite:config   },
  vite:config   envDir: '/tmp/vite-bug',
  vite:config   env: { BASE_URL: '/', MODE: 'development', DEV: true, PROD: false },
  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(1) {
  vite:config     'fnpd_/tmp/vite-bug' => {
  vite:config       dir: '/tmp/vite-bug',
  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   appType: 'spa',
  vite:config   experimental: { importGlobRestoreExtension: false, hmrPartialAccept: false },
  vite:config   getSortedPlugins: [Function: getSortedPlugins],
  vite:config   getSortedPluginHooks: [Function: getSortedPluginHooks]
  vite:config } +10ms
  vite:deps Hash is consistent. Skipping. Use --force to override. +0ms

  VITE v5.4.0  ready in 194 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  press h + enter to show help
  vite:html-fallback Rewriting GET / to /index.html +0ms
  vite:time 38.75ms /index.html +0ms
  vite:resolve 7.77ms /src/main.ts -> /tmp/vite-bug/src/main.ts +0ms
  vite:load 22.27ms [fs] /src/main.ts +0ms
  vite:resolve 0.56ms ts-enum/status -> /tmp/vite-bug/node_modules/.vite/deps/ts-enum_status.js?v=30357745 +75ms
  vite:import-analysis /node_modules/.vite/deps/ts-enum_status.js?v=30357745 needs interop +0ms
  vite:optimize-deps load /tmp/vite-bug/node_modules/.vite/deps/ts-enum_status.js +0ms
  vite:import-analysis 8.90ms [1 imports rewritten] src/main.ts +5ms
  vite:transform 55.90ms /src/main.ts +0ms
  vite:load 6.74ms [plugin] /node_modules/.vite/deps/ts-enum_status.js?v=30357745 +64ms
  vite:import-analysis 0.48ms [no imports] node_modules/.vite/deps/ts-enum_status.js?v=30357745 +7ms
  vite:transform 1.35ms /node_modules/.vite/deps/ts-enum_status.js?v=30357745 +7ms
  vite:resolve 0.98ms /@vite/client -> /tmp/vite-bug/node_modules/vite/dist/client/client.mjs +128ms
  vite:cache [memory] /src/main.ts +0ms
  vite:time 1.83ms /src/main.ts +215ms
  vite:load 6.60ms [fs] /@vite/client +125ms
  vite:resolve 0.45ms @vite/env -> /tmp/vite-bug/node_modules/vite/dist/client/env.mjs +15ms
  vite:import-analysis 7.85ms [1 imports rewritten] node_modules/vite/dist/client/client.mjs +133ms
  vite:transform 8.80ms /@vite/client +133ms
  vite:time 19.86ms /@vite/client +12ms
  vite:load 5.62ms [fs] /node_modules/vite/dist/client/env.mjs +15ms
  vite:import-analysis 0.10ms [no imports] node_modules/vite/dist/client/env.mjs +6ms
  vite:transform 0.49ms /node_modules/vite/dist/client/env.mjs +6ms
  vite:cache [memory] /node_modules/.vite/deps/ts-enum_status.js?v=30357745 +36ms
  vite:time 0.60ms /node_modules/.vite/deps/ts-enum_status.js?v=30357745 +23ms
  vite:cache [304] /node_modules/vite/dist/client/env.mjs +0ms
  vite:time 0.31ms /node_modules/vite/dist/client/env.mjs +60ms

Workaround

Excluding ts-enum (the dependency containing an enum) from optimization seems to fix the issue.
It worked for me with the following vite.config.js on the reproduction project:

import { defineConfig } from 'vite'

export default defineConfig({
    optimizeDeps: {
        exclude: [
	  'ts-enum',
	],
    },
})

And then the import is not broken anymore:

import { Status } from "/node_modules/ts-enum/status.ts?v=23ac7d0a";

Validations

@superboum superboum changed the title "vite preview" rewrites named import as default import and breaks app "vite" rewrites named import as default import and breaks app Aug 12, 2024
@superboum superboum changed the title "vite" rewrites named import as default import and breaks app "vite" rewrites "named import" of typescript enums as "default import" and breaks app Aug 12, 2024
@hi-ogawa
Copy link
Collaborator

hi-ogawa commented Aug 13, 2024

The issues you linked don't look relevant. Probably this is due to a false detection of cjs in optimizer and a similar issue I remember is this one:


After some digging, it looks like es-module-lexer somehow manages to "succeed" parsing the ts file, but it's returning hasModuleSyntax: false. If it were failing, then it'll probably get parsed properly as typescript and esm, but it didn't get that chance.

const entryContent = await fsp.readFile(filePath, 'utf-8')
try {
parseResult = parse(entryContent)
} catch {
const loader = esbuildOptions.loader?.[path.extname(filePath)] || 'jsx'
debug?.(
`Unable to parse: ${filePath}.\n Trying again with a ${loader} transform.`,
)
const transformed = await transformWithEsbuild(entryContent, filePath, {
loader,
})
parseResult = parse(transformed.code)
usedJsxLoader = true
}

I made a simpler reproduction here

@hi-ogawa hi-ogawa added feat: deps optimizer Esbuild Dependencies Optimization p3-minor-bug An edge case that only affects very specific usage (priority) and removed pending triage labels Aug 13, 2024
@sapphi-red
Copy link
Member

es-module-lexer requires the input to be a valid JS, so probably we should check the file extension before running parse.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feat: deps optimizer Esbuild Dependencies Optimization p3-minor-bug An edge case that only affects very specific usage (priority)
Projects
None yet
Development

No branches or pull requests

3 participants