TL;DR
For Typescript applications that use Webpack, @import-meta-env/unplugin seems to disregard already generated sourcemaps, creates and returns its own, generated based on already transpiled code. This resets sourcemap for input file, thus leading to wrong source-code view in the browser.
Problem
I have a Typescript React app, locally running on Webpack devserver. For Typescript, I use ts-loader and I also have @import-meta-env/unplugin to handle env variable injection.
tsconfig.json:
{
"compilerOptions": {
"allowJs": false,
"jsx": "react-jsx",
"lib": ["dom", "dom.iterable", "esnext"],
"sourceMap": true,
"target": "es6",
"module": "esnext",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"declaration": false,
"allowUmdGlobalAccess": true,
"baseUrl": "./",
"paths": {
"@common/*": ["./src/common/*"],
"@order-management/*": ["./src/order-management/*"]
}
},
"include": ["./src/**/*"],
"exclude": ["node_modules", "dist"]
}
webpack.config.js:
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ESLintPlugin = require('eslint-webpack-plugin');
const StylelintPlugin = require('stylelint-webpack-plugin');
const { name, version } = require('./package.json');
const CopyPlugin = require('copy-webpack-plugin');
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
const ImportMetaEnvPlugin = require('@import-meta-env/unplugin');
module.exports = {
entry: './src/index.ts',
mode: 'development',
devtool: 'inline-source-map',
resolve: {
extensions: ['.tsx', '.ts', '.js'],
symlinks: false,
plugins: [new TsconfigPathsPlugin()],
},
module: {
rules: [
{ test: /\.(ts|js)x?$/, exclude: /node_modules/, use: 'ts-loader' },
{ test: /\.(?:ico|gif|png|jpg|jpeg)$/i, type: 'asset/resource' },
{ test: /\.(woff(2)?|eot|ttf|otf|svg|)$/, type: 'asset/inline' },
{
test: /\.css$/i,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
options: {
modules: {
auto: true,
localIdentName: '[path][name]__[local]--[hash:base64:5]',
},
},
},
],
},
],
},
output: {
path: path.resolve(__dirname, './dist'),
clean: true,
filename: './static/js/[name].[contenthash:8].js',
chunkFilename: './static/js/[name].[chunkhash].js',
assetModuleFilename: './static/media/[name].[hash][ext]',
},
optimization: {
splitChunks: {
cacheGroups: {
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
priority: 1,
name: 'libs',
},
},
},
},
plugins: [
new webpack.DefinePlugin({
SERVICE_NAMESPACE: JSON.stringify('coffeeshop'),
SERVICE_NAME: JSON.stringify(name),
SERVICE_VERSION: JSON.stringify(version),
}),
new CopyPlugin({
patterns: [
{
from: './public',
filter: (file) =>
!['index.html'].some((ignoredFile) => file.endsWith(ignoredFile)),
},
],
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, './public/index.html'),
}),
ImportMetaEnvPlugin.webpack({
env: './config/.env',
example: './config/.env.example',
}),
new ESLintPlugin({
context: './src',
extensions: ['.ts', '.tsx'],
configType: 'flat',
}),
new StylelintPlugin({ context: './src', extensions: ['.css'] }),
],
devServer: {
static: path.join(__dirname, 'public'),
open: false,
port: 3000,
headers: {
'Access-Control-Allow-Origin': '*',
},
historyApiFallback: true,
proxy: [
{
context: '/otel',
target: 'http://localhost:4318',
secure: false,
pathRewrite: { '^/otel': '' },
},
],
},
stats: 'verbose',
};
Debugging my app, in browser's devtools, I noticed that sourcemaps are wrong for all files that use import.meta.env.*. Further investigating this issue, I came to a conclusion that plugin @import-meta-env/unplugin is overriding sourcemaps. In my Webpack configuration I have 2 loaders which generate sourcemaps - ts-loader and @import-meta-env/unplugin. ts-loader is invoked first, transpiles the code and generates sourcemap. Then, transpiled code and generated sourcemap is passed to @import-meta-env/unplugin. It generates sourcemap for transpiled code and returns its own sourcemap just for that code (for example, ts-loader merges its own generated sourcemap and input sourcemap from other loaders).
Original source code (what I want to see in the browser):
import { createRoot } from 'react-dom/client';
import { App } from '@common/components';
import './global.css';
import { configureOpenTelemetry, getLogger } from '@common/utils';
import { OrderPage } from '@order-management/pages';
import { UserContextProvider } from '@common/context';
const environmentName = import.meta.env.ENVIRONMENT;
configureOpenTelemetry(
SERVICE_NAMESPACE,
SERVICE_NAME,
SERVICE_VERSION,
environmentName,
);
const logger = getLogger('application');
const container = document.getElementById('container');
if (!container) {
throw new Error('Container not found.');
}
const root = createRoot(container);
root.render(
<UserContextProvider>
<App>
<OrderPage />
</App>
</UserContextProvider>,
);
logger.logInfo('Application started...');
Actual view in the browser:
import { jsx as _jsx } from "react/jsx-runtime";
import { createRoot } from 'react-dom/client';
import { App } from '@common/components';
import './global.css';
import { configureOpenTelemetry, getLogger } from '@common/utils';
import { OrderPage } from '@order-management/pages';
import { UserContextProvider } from '@common/context';
const environmentName = import.meta.env.ENVIRONMENT;
configureOpenTelemetry(SERVICE_NAMESPACE, SERVICE_NAME, SERVICE_VERSION, environmentName);
const logger = getLogger('application');
const container = document.getElementById('container');
if (!container) {
throw new Error('Container not found.');
}
const root = createRoot(container);
root.render(_jsx(UserContextProvider, { children: _jsx(App, { children: _jsx(OrderPage, {}) }) }));
logger.logInfo('Application started...');
I tried removing sourcemap generation functionality from @import-meta-env/unplugin, thus forcing it to return input sourcemap (ts-loader's) and the problem was solved.
import-meta-env/packages/unplugin/src /transform-dev.ts:
return {
code: s.toString(),
//map: s.generateMap({ source: id, includeContent: true }),
};
versions:
@import-meta-env/unplugin: 0.6.2
ts-loader: 9.5.2
webpack: 5.99.5
typescript: 5.8.3
react: 19.1.0
node: v20.18.3
TL;DR
For Typescript applications that use Webpack,
@import-meta-env/unpluginseems to disregard already generated sourcemaps, creates and returns its own, generated based on already transpiled code. This resets sourcemap for input file, thus leading to wrong source-code view in the browser.Problem
I have a Typescript React app, locally running on Webpack devserver. For Typescript, I use
ts-loaderand I also have@import-meta-env/unpluginto handle env variable injection.tsconfig.json:webpack.config.js:Debugging my app, in browser's devtools, I noticed that sourcemaps are wrong for all files that use
import.meta.env.*. Further investigating this issue, I came to a conclusion that plugin@import-meta-env/unpluginis overriding sourcemaps. In my Webpack configuration I have 2 loaders which generate sourcemaps -ts-loaderand@import-meta-env/unplugin.ts-loaderis invoked first, transpiles the code and generates sourcemap. Then, transpiled code and generated sourcemap is passed to@import-meta-env/unplugin. It generates sourcemap for transpiled code and returns its own sourcemap just for that code (for example,ts-loadermerges its own generated sourcemap and input sourcemap from other loaders).Original source code (what I want to see in the browser):
Actual view in the browser:
I tried removing sourcemap generation functionality from
@import-meta-env/unplugin, thus forcing it to return input sourcemap (ts-loader's) and the problem was solved.import-meta-env/packages/unplugin/src /transform-dev.ts:versions:
@import-meta-env/unplugin: 0.6.2ts-loader: 9.5.2webpack: 5.99.5typescript: 5.8.3react: 19.1.0node: v20.18.3