Files
metabase/rspack.iframe-sdk-embed.config.js
Oleh K a522f86267 Update supported browsers doc and pin browserslist (#80477)
* GDGT-3128: Pin browserslist to the versions we actually support

The documented floor (Chrome 70, Firefox 68, Edge 17, Safari 11) had drifted
far from reality. Auditing the shipped bundles turned up features that break
well above it:

  - subgrid (Chrome 117), unguarded in two CSS modules
  - :has() (Firefox 121), 248 occurrences; the postcss polyfill is disabled
  - color-mix() (Chrome 111), including the ui/colors base palette
  - toSorted/findLast (Safari 16.4) and URL.canParse (Chrome 120, Safari 17)

There are no polyfills to fall back on: core-js is not a dependency, so
preset-env injects nothing.

browserslist said "defaults", which resolves to Chrome 109 among others --
below our own floor -- so the build was targeting browsers the code breaks
on. Pin it to the audited versions instead.

The rspack swc-loaders hardcoded env.targets: ["defaults"], bypassing
package.json entirely. Dropping targets lets swc fall back to the
browserslist config, matching babel-loader and postcss.

Verified against the real postcss pipeline over all 826 CSS files. Prefix
output only improves: -webkit-backdrop-filter is now emitted (Safari needs it
through 17.6, and it was previously missing), and -webkit-background-clip is
dropped, which is safe because Chrome 120 is exactly where the unprefixed
form lands. The -moz-column-*/-o-* prefixes that go away were for Firefox
versions far below 121.

* GDGT-3128: Use the :global() function form in CSS modules

Pinning browserslist dropped `op_mini all` from the targets, and Opera Mini
was the only thing keeping postcss-preset-env on its expanded nesting
desugaring. With it gone, preset-env emits the spec-correct `:is()` form:

    .editor :global .cm-panels     ->  :is(.editor :global) .cm-panels

That pulls `:global` inside `:is()`, where its positional "everything after
this is global" semantics no longer apply. css-loader then scoped the classes
it was meant to leave alone:

    :local(.editor) .cm-panels     ->  :is(:local(.editor)) :local(.cm-panels)

CodeMirror renders real `.cm-editor`/`.cm-panels`/`.cm-content` elements, so
every rule in those blocks silently stopped applying -- 20 scoped selectors and
0 global in CodeMirror.module.css alone. That took out three e2e specs:
cc-typing-suggestion, native-reproductions and transforms.

The `:global()` function form binds global-ness to a selector node instead of a
position, so no amount of selector rearrangement can separate it from the class.
Verified to hold three levels deep, including under `:is()` wrapping.

Selector output is otherwise unchanged: compiling each file under the old and
new configs and comparing the post-CSS-modules selector sets gives an exact
match, except for a `::-moz-placeholder` that the browserslist pin drops on its
own (Firefox 121 has it unprefixed).

The stylelint rule catches the bare form. It also flags a top-level `:global {}`
block, which happens to work today only because a lone `:global` is a single
compound and escapes the `:is()` wrap -- one more nesting level and it breaks
the same way.

* GDGT-3128: Raise the Safari floor to 18.2 for auxclick

`auxclick` lands in Safari and iOS Safari 18.2 -- every version through 18.1
reports `n` in caniuse. There are 12 unguarded `onAuxClick` handlers in app
source (19 counting tests), so 17 was never a floor we actually met.

Chrome (55), Firefox (53) and Edge (79) are all far below their current floors,
so only Safari moves.

Side effect: `-webkit-backdrop-filter` is no longer emitted. Safari needed it
only through 17.6, so past 18.2 autoprefixer correctly drops it. The same bump
also covers scrollbar-gutter/width/color, which all wanted exactly Safari 18.2.
2026-09-29 10:59:14 +02:00

105 lines
2.8 KiB
JavaScript

/* eslint-disable import/no-commonjs */
/* eslint-disable no-undef */
const path = require("path");
const {
CopyJsFromTmpDirectoryPlugin,
} = require("./frontend/build/shared/rspack/copy-js-from-tmp-directory-plugin");
const {
COMPRESSION_CONFIG,
} = require("./frontend/build/shared/rspack/compression");
const {
SIDE_EFFECT_FREE_RULE,
} = require("./frontend/build/shared/rspack/side-effect-free-modules");
const SRC_PATH = __dirname + "/frontend/src/metabase";
const ENTERPRISE_SRC_PATH =
__dirname + "/enterprise/frontend/src/metabase-enterprise";
const SCRIPT_TAG_PATH = path.resolve(
SRC_PATH,
"embedding/embedding-iframe-sdk/embed.ts",
);
const BUILD_PATH = __dirname + "/resources/frontend_client";
const EMBEDDING_SRC_PATH = __dirname + "/enterprise/frontend/src/embedding";
const SDK_BUNDLE_SRC_PATH = __dirname + "/frontend/src/embedding-sdk-bundle";
const SDK_SHARED_SRC_PATH = __dirname + "/frontend/src/embedding-sdk-shared";
const OUT_FILE_NAME = "embed.js";
const OUT_TEMP_PATH = path.resolve(BUILD_PATH, "tmp-embed-js");
const DEV_PORT = process.env.MB_FRONTEND_DEV_PORT || 8080;
const resolveEnterprisePathOrNoop = (path) =>
process.env.MB_EDITION === "ee"
? ENTERPRISE_SRC_PATH + path
: SRC_PATH + "/utils/noop";
module.exports = {
name: "iframe_sdk_embed_v1",
entry: SCRIPT_TAG_PATH,
output: {
// we must use a different directory than the main rspack config,
// otherwise the path conflicts and the output bundle will not appear.
path: OUT_TEMP_PATH,
filename: OUT_FILE_NAME,
library: {
name: ["metabase", "embed"],
type: "umd",
},
globalObject: "this",
publicPath: `http://localhost:${DEV_PORT}/app`,
},
devServer: { hot: false },
module: {
rules: [
SIDE_EFFECT_FREE_RULE,
{
test: /\.(ts|js)$/,
use: [
{
loader: "builtin:swc-loader",
options: {
jsc: {
loose: true,
transform: {},
parser: { syntax: "typescript" },
},
sourceMaps: false,
minify: false,
env: {},
},
},
],
type: "javascript/auto",
},
],
},
optimization: { splitChunks: false, runtimeChunk: false },
devtool: false,
plugins: [
...COMPRESSION_CONFIG,
CopyJsFromTmpDirectoryPlugin({
tmpPath: OUT_TEMP_PATH,
outputPath: path.join(BUILD_PATH, "app/"),
cleanupInDevMode: true,
}),
],
resolve: {
extensions: [".js", ".ts"],
alias: {
metabase: SRC_PATH,
embedding: EMBEDDING_SRC_PATH,
"embedding-sdk-bundle": SDK_BUNDLE_SRC_PATH,
"embedding-sdk-shared": SDK_SHARED_SRC_PATH,
"sdk-iframe-embedding-script-ee-plugins": resolveEnterprisePathOrNoop(
"/sdk-iframe-embedding-script-plugins",
),
},
},
};