js-lingui icon indicating copy to clipboard operation
js-lingui copied to clipboard

Official vite support?

Open Hideman85 opened this issue 2 years ago • 12 comments

I wonder if lingui could provide a official support of vite?

I have seen related topic https://github.com/lingui/js-lingui/issues/1016 and even https://github.com/skovhus/vite-lingui but I am facing an issue with vite/esbuild and lingui/babel-macros.

Uncaught Error: Module "path" has been externalized for browser compatibility and cannot be accessed in client code.
    get @lingui_macro.js:143
    pathJoinPosix2 @lingui_macro.js:153239
    node_modules 

At the end vite/esbuild/rollup work in ESM mode and will convert the macro into ESM renaming the import file and so not detected as a macro anymore.

I have found out that we can tell vite to exclude lingui macro from CommonJs to ESM process with the following:

optimizeDeps: {
  exclude: ['@lingui/macro']
},
build: {
  commonjsOptions: {
    exclude: [/@lingui\/macro/]
  }
},

// Error: Uncaught SyntaxError: import not found: Trans

I did not found a working solution in the other topic yet and I would appreciate some help here and even if possible an official support of vite :slightly_smiling_face:

In addition:

vite.config.js
import * as path from 'path';
import react from '@vitejs/plugin-react';
import { defineConfig } from 'vite';
import linguiLoader from './linguiVitePlugin';

// https://vitejs.dev/config/
export default defineConfig({
  define: {
    'process.env': process.env
  },
  server: {
    port: 3005,
  },
  preview: {
    port: 3006,
  },
  plugins: [
    react({
      babel: {
        configFile: true
      }
    }),
    linguiLoader({
      configPath: require.resolve('../../lingui.config.js')
    }),
  ],
  optimizeDeps: {
    exclude: ['@lingui/macro']
  },
  build: {
    commonjsOptions: {
      exclude: [/@lingui\/macro/]
    }
  },
  resolve: {
    alias: {
      '#app': path.resolve(__dirname, './src'),
    },
  },
});
linguiVitePlugin.js
const path = require('path')
const R = require('ramda')
const { getConfig } = require('@lingui/conf')
const { createCompiledCatalog, getCatalogs, getCatalogForFile } = require('@lingui/cli/api')

/**
 * Custom Vite Lingui loader plugin
 * Based on simple Vite svg plugin: https://github.com/jpkleemans/vite-svg-loader/blob/main/index.js
 * And webpack lingui loader: https://github.com/lingui/js-lingui/blob/main/packages/loader/src/webpackLoader.ts
 * @param options { configPath: string } need to specify the lingui config file
 */
module.exports = function linguiLoader (options) {
  let viteConfig = {}
  const poRegex = /\.po$/

  return {
    name: 'lingui-loader',
    enforce: 'pre',

    configResolved (config) {
      viteConfig = config
    },

    async load (id) {
      const isRootRef = viteConfig.command === 'build' && !id.startsWith(viteConfig.root)

      if (!id.match(poRegex) || isRootRef) {
        return
      }

      const config = getConfig({
        configPath: options.configPath,
        cwd: path.dirname(id),
      })

      const catalogRelativePath = path.relative(config.rootDir, id)

      const { locale, catalog } = getCatalogForFile(
        catalogRelativePath,
        getCatalogs(config)
      )
      const catalogs = catalog.readAll()
      const messages = R.mapObjIndexed(
        (_, key) =>
          catalog.getTranslation(catalogs, locale, key, {
            fallbackLocales: config.fallbackLocales,
            sourceLocale: config.sourceLocale,
          }),
        catalogs[locale]
      )

      // In production we don't want untranslated strings. It's better to use message
      // keys as a last resort.
      // In development, however, we want to catch missing strings with `missing` parameter
      // of I18nProvider (React) or setupI18n (core) and therefore we need to get
      // empty translations if missing.
      const strict = process.env.NODE_ENV !== 'production'
      return createCompiledCatalog(locale, messages, {
        strict,
        namespace: config.compileNamespace,
        pseudoLocale: config.pseudoLocale,
      })
    }
  }
}

module.exports.default = module.exports

Hideman85 avatar May 24 '22 08:05 Hideman85

Any idea? I'm still facing this blocker right now and cannot use lingui in the app yet :slightly_frowning_face:

Hideman85 avatar May 31 '22 10:05 Hideman85

Can you reproduce the issue in https://github.com/skovhus/vite-lingui? If you create a PR against that repository I'm fine with having a look.

skovhus avatar Jun 22 '22 18:06 skovhus

Also related to https://github.com/lingui/js-lingui/issues/1224

skovhus avatar Jun 22 '22 18:06 skovhus

Thanks, it is a pleasure to see some activity there, I'm gonna try your repo. I just look at the vite config and I notice the following @vitejs/plugin-react is already using babel, adding vite-plugin-babel-macros does not run babel twice?

Hideman85 avatar Jun 23 '22 07:06 Hideman85

I just look at the vite config and I notice the following @vitejs/plugin-react is already using babel, adding vite-plugin-babel-macros does not run babel twice?

It probably does, and it would be great to avoid that. But note that we still saw huge performance wins migrating from Webpack to Vite for our application.

skovhus avatar Jun 23 '22 07:06 skovhus

Thanks, it is a pleasure to see some activity there, I'm gonna try your repo. I just look at the vite config and I notice the following @vitejs/plugin-react is already using babel, adding vite-plugin-babel-macros does not run babel twice?

Technically, you don't need vite-plugin-babel-macros and any .babelrc, you just need to add macros to the @vite/plugin-react:

export default defineConfig({
  plugins: [
    react({
      babel: {
        plugins: ['macros'],
      },
    }),
  ],
})

That will run on dev and prod, and just once :)

Taking your repo as example @skovhus, there is the benchmark:

Before

Benchmark 1: rm -rf dist node_modules/.vite && yarn vite build
  Time (mean ± σ):      3.051 s ±  0.401 s    [User: 3.133 s, System: 0.371 s]
  Range (min … max):    2.460 s …  3.659 s    10 runs

After

Benchmark 1: rm -rf dist node_modules/.vite && yarn vite build
  Time (mean ± σ):      2.455 s ±  0.299 s    [User: 2.745 s, System: 0.317 s]
  Range (min … max):    2.234 s …  3.263 s    10 runs

semoal avatar Jun 23 '22 08:06 semoal

First it works removing the vite-babel-plugin and saying to react plugin to use babel config file. Second all my errors are due to the fact that my config is in monorepo project and vite is at top-level with a modified root to packages/app/src if you change the root it looks like everything break with babel or other stuff.

I tried to move the index.html at top-level and then import the app everything with the root unchanged and now looks to works. I need more investigation but it looks to be the issue. I would have never expect that and modifying your project step by step to shape like ours was the right path to understand this and thank you for that :slightly_smiling_face:

Hideman85 avatar Jun 23 '22 08:06 Hideman85

@semoal awesome! I completely overlooked the option of not using vite-plugin-babel-macros. With your suggestion we now go from a production build taking 80s to 60s.

I'll just updated the example repository. 👏

skovhus avatar Jun 23 '22 08:06 skovhus

So now everything work nice in our app too, in order to summarize my issues:

  • Keep vite root to the root folder even in monorepo architecture because some plugins does break if you move under packages/app
  • Remove any @babel/preset-env in your babel config it is basically not supported with vite (I need to look later an alternative, maybe vite-legacy)

In addition the vite plugin to dynamically compile the catalog is also working fine maybe it could be worth to have a vite documentation page that specify the need of enabling macros in the react babel options and some could use a vite plugin to compile on the fly catalogs. I would love to see an official support there I guess it can be helpful to lot of people specially with the growing vite community.

Hideman85 avatar Jun 23 '22 09:06 Hideman85

Thanks for documenting this, I'm sure it will be helpful for someone else.

Keep vite root to the root folder even in monorepo architecture because some plugins does break if you move under packages/app

Note that I raised this issue here: https://github.com/vitejs/vite/issues/7680

skovhus avatar Jun 23 '22 09:06 skovhus

I wrote a plugin for vite and more, which can compiles messages on the fly! It contains a demo for vite integration in the playground catalogs, Hope this helps.

a1ooha avatar Jul 14 '22 15:07 a1ooha

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Sep 16 '22 00:09 stale[bot]