Files
leadchat/vite.config.ts
Shivam Mishra 4c7a539f9d feat: use iife build for sdk (#10255)
Vite uses `Rollup` for bundling, when building the sdk, we effectively
run a separate vite config, with `Library Mode`. When migrating from
Webpack to Vite, I selected `umd` format, i.e. Universal Module
Definition, which works as `amd`, `cjs` and `iife` all in one. However a
lot of Chatwoot users ran into issues where UMD sdk.js won't work.
Especially so when used with Google Tag Manager.

As a hotfix we moved the format from `umd` to `cjs`. Here's the thing
CJS is supposed to be used for Node packages. But for some-reason it was
working on browsers too, its no surprising, since the output is a valid
JS and the code we wrote was written for the browser.

There's a catch though, when minifying, esbuild would use tokens like
`$` and `_`, since `CJS` build is not scoped, unlike a `UMD` file, or
(spoiler alert) `IIFE`. Any declarations would be global, and websites
using `jQuery` (uff, culture) and `underscore-js` would break. We pushed
another hotfix disabling the name replacement in `esbuild` unless we
test out `IIFE` builds (which is this PR)

This PR fixes this by using `IIFE` instead, it is always scoped in a
function, so it never binds things globally, unless specifically written
to do so (example. `window.$chatwoot`).

I've tested this SDK on Safari, Chrome and iOS Safari on paperlayer test
site, it seems to be working fine. The sdk build is also scoped
correctly.

---------

Co-authored-by: Pranav <pranav@chatwoot.com>
2024-10-10 12:14:36 -07:00

111 lines
3.5 KiB
TypeScript

/// <reference types="vitest" />
/**
What's going on with library mode?
Glad you asked, here's a quick rundown:
1. vite-plugin-ruby will automatically bring all the entrypoints like dashbord and widget as input to vite.
2. vite needs to be in library mode to build the SDK as a single file. (UMD) format and set `inlineDynamicImports` to true.
3. But when setting `inlineDynamicImports` to true, vite will not be able to handle mutliple entrypoints.
This puts us in a deadlock, now there are two ways around this, either add another separate build pipeline to
the app using vanilla rollup or rspack or something. The second option is to remove sdk building from the main pipeline
and build it separately using Vite itself, toggled by an ENV variable.
`BUILD_MODE=library bin/vite build` should build only the SDK and save it to `public/packs/js/sdk.js`
`bin/vite build` will build the rest of the app as usual. But exclude the SDK.
We need to edit the `asset:precompile` rake task to include the SDK in the precompile list.
*/
import { defineConfig } from 'vite';
import ruby from 'vite-plugin-ruby';
import path from 'path';
import vue from '@vitejs/plugin-vue';
const isLibraryMode = process.env.BUILD_MODE === 'library';
const isTestMode = process.env.TEST === 'true';
const vueOptions = {
template: {
compilerOptions: {
isCustomElement: tag => ['ninja-keys'].includes(tag),
},
},
};
let plugins = [ruby(), vue(vueOptions)];
if (isLibraryMode) {
plugins = [];
} else if (isTestMode) {
plugins = [vue(vueOptions)];
}
export default defineConfig({
plugins: plugins,
build: {
rollupOptions: {
output: {
// [NOTE] when not in library mode, no new keys will be addedd or overwritten
// setting dir: isLibraryMode ? 'public/packs' : undefined will not work
...(isLibraryMode
? {
dir: 'public/packs',
entryFileNames: chunkInfo => {
if (chunkInfo.name === 'sdk') {
return 'js/sdk.js';
}
return '[name].js';
},
}
: {}),
inlineDynamicImports: isLibraryMode, // Disable code-splitting for SDK
},
},
lib: isLibraryMode
? {
entry: path.resolve(__dirname, './app/javascript/entrypoints/sdk.js'),
formats: ['iife'], // IIFE format for single file
name: 'sdk',
}
: undefined,
},
resolve: {
alias: {
vue: 'vue/dist/vue.esm-bundler.js',
components: path.resolve('./app/javascript/dashboard/components'),
v3: path.resolve('./app/javascript/v3'),
dashboard: path.resolve('./app/javascript/dashboard'),
helpers: path.resolve('./app/javascript/shared/helpers'),
shared: path.resolve('./app/javascript/shared'),
survey: path.resolve('./app/javascript/survey'),
widget: path.resolve('./app/javascript/widget'),
assets: path.resolve('./app/javascript/dashboard/assets'),
},
},
test: {
environment: 'jsdom',
include: ['app/**/*.{test,spec}.?(c|m)[jt]s?(x)'],
coverage: {
reporter: ['lcov', 'text'],
include: ['app/**/*.js', 'app/**/*.vue'],
exclude: [
'app/**/*.@(spec|stories|routes).js',
'**/specs/**/*',
'**/i18n/**/*',
],
},
globals: true,
outputFile: 'coverage/sonar-report.xml',
server: {
deps: {
inline: ['tinykeys', '@material/mwc-icon'],
},
},
setupFiles: ['fake-indexeddb/auto', 'vitest.setup.js'],
mockReset: true,
clearMocks: true,
},
});