diff --git a/.pnpm-patches/@wordpress__build@0.14.0.patch b/.pnpm-patches/@wordpress__build@0.14.0.patch
new file mode 100644
index 000000000000..c710b5576a19
--- /dev/null
+++ b/.pnpm-patches/@wordpress__build@0.14.0.patch
@@ -0,0 +1,635 @@
+Bridge to upstream Gutenberg PRs https://github.com/WordPress/gutenberg/pull/78822
+and https://github.com/WordPress/gutenberg/pull/77465. This patch is exactly those two
+PRs applied to `@wordpress/build@0.14.0`, with no other changes (0.14.0 already has the
+`createNodeRequire` import the 0.13.0 patch had to add).
+
+#78822 reads each script module's ID from `package.json#name`, externalizes
+internal-package imports by exact name, and discovers script-module packages outside
+`./packages/` via convention (any `dependencies` entry whose own `package.json` declares
+`wpScriptModuleExports`). #77465 scopes the generated PHP `wp_deregister_script_module()`
+calls to `@wordpress/*` IDs, so shared non-core packages registered by more than one
+wp-build plugin get Core's idempotent first-wins semantics instead of last-plugin-wins.
+
+Convention discovery reads each shared package's `package.json` through its `exports`
+field, so a discovered package must expose `./package.json` (e.g.
+`@automattic/number-formatters` adds `"./package.json": "./package.json"`). Packages
+that do not are skipped and bundled inline.
+
+When the upstream PRs merge and Jetpack bumps `@wordpress/build` to a version that
+includes them, delete this file and remove its entry from `pnpm-workspace.yaml`.
+
+To rebase this patch to a new `@wordpress/build` version, without a Gutenberg checkout:
+
+ VERSION=0.16.0
+ WORK=$(mktemp -d) && cd "$WORK"
+ curl -sL "https://registry.npmjs.org/@wordpress/build/-/build-$VERSION.tgz" | tar xz
+ mv package pristine && cp -R pristine patched
+ for PR in 78822 77465; do
+ curl -sL "https://patch-diff.githubusercontent.com/raw/WordPress/gutenberg/pull/$PR.diff" \
+ | awk '/^diff --git/{f=($4 ~ /^b\/packages\/wp-build\//)} f' \
+ | sed 's|/packages/wp-build/|/|g' \
+ | (cd patched && patch -p1 || true)
+ done
+ find patched \( -name '*.orig' -o -name '*.rej' \) -delete
+ # Versions <= 0.13.0 predate this import; add it if absent.
+ grep -q "createRequire as createNodeRequire" patched/lib/build.mjs || \
+ perl -0pi -e "s/(import \{ createHash \} from 'node:crypto';\n)/\$1import { createRequire as createNodeRequire } from 'node:module';\n/" patched/lib/build.mjs
+ git diff --no-index --no-color pristine patched \
+ | sed -E -e 's|^diff --git a/pristine/|diff --git a/|' \
+ -e 's| b/patched/| b/|' \
+ -e 's|^--- a/pristine/|--- a/|' \
+ -e 's|^\+\+\+ b/patched/|+++ b/|' \
+ > "$OLDPWD/.pnpm-patches/@wordpress__build@$VERSION.patch"
+
+Then prepend this header, update pnpm-workspace.yaml, delete the old patch file, and run
+`pnpm install` + `pnpm --filter @automattic/jetpack-premium-analytics build` to validate.
+`@automattic/number-formatters` must keep `./package.json` in its `exports` for
+convention discovery to pick it up.
+
+diff --git a/CHANGELOG.md b/CHANGELOG.md
+index a7e9f3899a..c48aaa093a 100644
+--- a/CHANGELOG.md
++++ b/CHANGELOG.md
+@@ -4,6 +4,11 @@
+
+ ## 0.14.0 (2026-05-14)
+
++### Enhancements
++
++- Use each package's own `name` field as its script-module ID and externalize internal-package imports by exact name. Decouples script-module identity from `wpPlugin.packageNamespace`, so the npm name survives end-to-end (npm name === import specifier === script-module ID). No-op for Core; enables consumers whose owned npm scope differs from `packageNamespace` to keep a single identifier across npm, IDE, and the WordPress runtime.
++- Discover script-module packages outside `./packages/` via convention. Any entry in the plugin's `dependencies` whose `package.json` declares `wpScriptModuleExports` is registered as a script module, bundled, and externalized under its own npm name. No new config; local packages still take precedence on name collision.
++
+ ### Bug Fixes
+
+ - Register generated CSS module styles with `@wordpress/style-runtime` so they can be injected into registered documents, such as editor iframes ([#77965](https://github.com/WordPress/gutenberg/pull/77965)).
+diff --git a/lib/build.mjs b/lib/build.mjs
+index 39e746630b..b3fd659cde 100755
+--- a/lib/build.mjs
++++ b/lib/build.mjs
+@@ -4,6 +4,7 @@
+ * External dependencies
+ */
+ import { readFile, writeFile, copyFile, mkdir, unlink } from 'fs/promises';
++import { existsSync } from 'fs';
+ import path from 'path';
+ import { createHash } from 'node:crypto';
+ import { createRequire as createNodeRequire } from 'node:module';
+@@ -95,20 +96,6 @@ const TEST_FILE_PATTERNS = [
+ /\.(native|ios|android)\.(js|ts|tsx)$/,
+ ];
+
+-/**
+- * Get all package names from the packages directory.
+- *
+- * @return {string[]} Array of package names.
+- */
+-function getAllPackages() {
+- return glob
+- .sync( normalizePath( path.join( PACKAGES_DIR, '*', 'package.json' ) ) )
+- .map( ( packageJsonPath ) =>
+- path.basename( path.dirname( packageJsonPath ) )
+- );
+-}
+-
+-const PACKAGES = getAllPackages();
+ const ROOT_PACKAGE_JSON = getPackageInfoFromFile(
+ path.join( ROOT_DIR, 'package.json' )
+ );
+@@ -119,6 +106,116 @@ const HANDLE_PREFIX = WP_PLUGIN_CONFIG.handlePrefix || PACKAGE_NAMESPACE;
+ const EXTERNAL_NAMESPACES = WP_PLUGIN_CONFIG.externalNamespaces || {};
+ const PAGES = WP_PLUGIN_CONFIG.pages || [];
+
++/**
++ * A discovered package in the registry.
++ *
++ * @typedef {Object} PackageEntry
++ * @property {string} dir Absolute path to the package directory.
++ * @property {import('./package-utils.mjs').PackageJson} packageJson Parsed package.json contents.
++ * @property {boolean} external True when the package is pre-built outside this plugin (e.g. a workspace dep). External packages are bundled and externalized but not transpiled; local packages from `./packages/` are also transpiled from source.
++ */
++
++/**
++ * Build the registry of script-module packages this plugin builds.
++ *
++ * Two convention-driven discovery sources:
++ *
++ * 1. Local packages: every `./packages/
/package.json`.
++ * 2. Convention deps: every entry in the plugin's `dependencies` whose own
++ * `package.json` declares `wpScriptModuleExports`. Lets a plugin pull in
++ * shared script-module packages from outside `./packages/` (workspace
++ * siblings, npm-installed siblings) without any extra wp-build config.
++ * Local packages win first-match for any given name.
++ *
++ * @return {Map} Registry keyed by short identifier:
++ * directory name for local packages, full npm name for convention deps.
++ */
++function getAllPackages() {
++ const registry = new Map();
++
++ // 1. Local packages from ./packages/*
++ const localPaths = glob.sync(
++ normalizePath( path.join( PACKAGES_DIR, '*', 'package.json' ) )
++ );
++ for ( const pkgJsonPath of localPaths ) {
++ const dir = path.dirname( pkgJsonPath );
++ const key = path.basename( dir );
++ registry.set( key, {
++ dir,
++ packageJson: getPackageInfoFromFile( pkgJsonPath ),
++ external: false,
++ } );
++ }
++
++ // 2. Convention deps with wpScriptModuleExports
++ const deps = Object.keys( ROOT_PACKAGE_JSON.dependencies || {} );
++ const localNames = new Set(
++ Array.from( registry.values() ).map(
++ ( entry ) => entry.packageJson.name
++ )
++ );
++ const localRequire = createNodeRequire(
++ path.join( ROOT_DIR, 'package.json' )
++ );
++ for ( const depName of deps ) {
++ // First-match-wins: a local package with the same `name` already
++ // claimed this slot, skip the dep.
++ if ( localNames.has( depName ) || registry.has( depName ) ) {
++ continue;
++ }
++
++ // Resolve the dep's package.json. Some packages don't expose it in
++ // their `exports`, so fall back to a direct node_modules lookup.
++ let pkgJsonPath;
++ try {
++ pkgJsonPath = localRequire.resolve( `${ depName }/package.json` );
++ } catch {
++ const direct = path.join(
++ ROOT_DIR,
++ 'node_modules',
++ depName,
++ 'package.json'
++ );
++ if ( ! existsSync( direct ) ) {
++ continue;
++ }
++ pkgJsonPath = direct;
++ }
++
++ const depPackageJson = getPackageInfoFromFile( pkgJsonPath );
++ if ( ! depPackageJson.wpScriptModuleExports ) {
++ continue;
++ }
++
++ if ( depPackageJson.wpScript ) {
++ console.warn(
++ `wp-build: ${ depName } declares wpScript; ignored (external dependencies contribute script modules only).`
++ );
++ }
++
++ registry.set( depName, {
++ dir: path.dirname( pkgJsonPath ),
++ packageJson: depPackageJson,
++ external: true,
++ } );
++ }
++
++ return registry;
++}
++
++const PACKAGES = getAllPackages();
++
++// Set of every discovered package's `name` field. Used by the externals
++// plugin to externalize internal-package imports by exact name, regardless
++// of `packageNamespace`. Decouples script-module identity from a config
++// string so a package's own `name` survives end-to-end (npm name === import
++// specifier === script-module ID).
++const INTERNAL_PACKAGE_NAMES = new Set(
++ Array.from( PACKAGES.values() )
++ .map( ( entry ) => entry.packageJson.name )
++ .filter( Boolean )
++);
++
+ /**
+ * Interprets a configuration value as a boolean, where `"true"` and `"1"`
+ * are considered true while all other values are false.
+@@ -159,7 +256,8 @@ const wordpressExternalsPlugin = createWordpressExternalsPlugin(
+ PACKAGE_NAMESPACE,
+ SCRIPT_GLOBAL,
+ EXTERNAL_NAMESPACES,
+- HANDLE_PREFIX
++ HANDLE_PREFIX,
++ INTERNAL_PACKAGE_NAMES
+ );
+
+ const styleRuntimeRequire = createNodeRequire( import.meta.url );
+@@ -522,23 +620,25 @@ function resolveEntryPoint( packageDir, packageJson ) {
+ */
+ async function bundlePackage( packageName, options = {} ) {
+ const {
+- sourceDir = PACKAGES_DIR,
+ handlePrefix = HANDLE_PREFIX,
+ scriptGlobal = SCRIPT_GLOBAL,
+ packageNamespace = PACKAGE_NAMESPACE,
+ } = options;
+
++ const entry = PACKAGES.get( packageName );
++ const packageDir = entry.dir;
++ const packageJson = entry.packageJson;
++
+ const builtModules = [];
+ const builtScripts = [];
+ const builtStyles = [];
+- const packageDir = path.join( sourceDir, packageName );
+- const packageJson = getPackageInfoFromFile(
+- path.join( sourceDir, packageName, 'package.json' )
+- );
+
+ const builds = [];
+
+- if ( packageJson.wpScript ) {
++ // External (convention-discovered) packages contribute script modules only.
++ const buildAsScript = !! packageJson.wpScript && ! entry.external;
++
++ if ( buildAsScript ) {
+ const entryPoint = resolveEntryPoint( packageDir, packageJson );
+ const outputDir = path.join( BUILD_DIR, 'scripts', packageName );
+ const target = browserslistToEsbuild();
+@@ -700,10 +800,16 @@ async function bundlePackage( packageName, options = {} ) {
+ );
+ }
+
++ // The script-module ID is the package's own `name` field. The
++ // PHP registry, the asset manifest, and `wp_register_script_module`
++ // all treat the ID as an opaque string, so this lets the npm name
++ // survive end-to-end without being rewritten by build configuration.
++ // Falls back to the legacy `@/` shape
++ // only when `name` is missing (e.g. an unnamed local package).
++ const packageId =
++ packageJson.name || `@${ packageNamespace }/${ packageName }`;
+ const scriptModuleId =
+- exportName === '.'
+- ? `@${ packageNamespace }/${ packageName }`
+- : `@${ packageNamespace }/${ packageName }/${ fileName }`;
++ exportName === '.' ? packageId : `${ packageId }/${ fileName }`;
+
+ builtModules.push( {
+ id: scriptModuleId,
+@@ -715,7 +821,7 @@ async function bundlePackage( packageName, options = {} ) {
+ }
+
+ let hasMainStyle = false;
+- if ( packageJson.wpScript ) {
++ if ( buildAsScript ) {
+ const buildStyleDir = path.join( packageDir, 'build-style' );
+ const outputDir = path.join( BUILD_DIR, 'styles', packageName );
+
+@@ -909,7 +1015,7 @@ async function inferStyleDependencies( scriptDependencies, packageName ) {
+
+ const styleDeps = [];
+ // Get the resolve directory for context-aware package resolution
+- const resolveDir = path.join( PACKAGES_DIR, packageName );
++ const resolveDir = PACKAGES.get( packageName )?.dir || PACKAGES_DIR;
+
+ for ( const scriptHandle of scriptDependencies ) {
+ // Skip non-package dependencies (like 'react', 'lodash', etc.)
+@@ -1270,17 +1376,17 @@ async function generatePagesPhp( pageData, replacements ) {
+ */
+ async function transpilePackage( packageName ) {
+ const startTime = Date.now();
+- const packageDir = path.join( PACKAGES_DIR, packageName );
+- const packageJson = getPackageInfoFromFile(
+- path.join( PACKAGES_DIR, packageName, 'package.json' )
+- );
++ const entry = PACKAGES.get( packageName );
+
+- if ( ! packageJson ) {
++ if ( ! entry ) {
+ throw new Error(
+ `Could not find package.json for package: ${ packageName }`
+ );
+ }
+
++ const packageDir = entry.dir;
++ const packageJson = entry.packageJson;
++
+ const srcFiles = await glob( `src/**/*.${ SOURCE_EXTENSIONS }`, {
+ cwd: packageDir,
+ ignore: IGNORE_PATTERNS,
+@@ -1489,10 +1595,9 @@ async function transpilePackage( packageName ) {
+ * @return {Promise} Build time in milliseconds, or null if no styles.
+ */
+ async function compileStyles( packageName ) {
+- const packageDir = path.join( PACKAGES_DIR, packageName );
+- const packageJson = getPackageInfoFromFile(
+- path.join( PACKAGES_DIR, packageName, 'package.json' )
+- );
++ const entry = PACKAGES.get( packageName );
++ const packageDir = entry.dir;
++ const packageJson = entry.packageJson;
+
+ // Get SCSS entry point patterns from package.json, default to root-level only
+ const scssEntryPointPatterns = packageJson.wpStyleEntryPoints || [
+@@ -1600,12 +1705,20 @@ function isPackageSourceFile( filename ) {
+ return false;
+ }
+
+- return PACKAGES.some( ( packageName ) => {
++ for ( const entry of PACKAGES.values() ) {
++ // External packages are not transpiled from source, so their files
++ // do not trigger rebuilds via this path.
++ if ( entry.external ) {
++ continue;
++ }
+ const packagePath = normalizePath(
+- path.join( 'packages', packageName )
++ path.relative( ROOT_DIR, entry.dir )
+ );
+- return relativePath.startsWith( packagePath + '/' );
+- } );
++ if ( relativePath.startsWith( packagePath + '/' ) ) {
++ return true;
++ }
++ }
++ return false;
+ }
+
+ /**
+@@ -1619,9 +1732,12 @@ function getPackageName( filename ) {
+ path.relative( process.cwd(), filename )
+ );
+
+- for ( const packageName of PACKAGES ) {
++ for ( const [ packageName, entry ] of PACKAGES ) {
++ if ( entry.external ) {
++ continue;
++ }
+ const packagePath = normalizePath(
+- path.join( 'packages', packageName )
++ path.relative( ROOT_DIR, entry.dir )
+ );
+ if ( relativePath.startsWith( packagePath + '/' ) ) {
+ return packageName;
+@@ -2024,17 +2140,14 @@ async function buildAll( baseUrlExpression ) {
+
+ const startTime = Date.now();
+
+- // Build maps: short name ↔ full name ↔ package.json from package.json files
++ // Build maps: short name ↔ full name ↔ package.json from the registry.
+ const shortToFull = new Map();
+ const fullToShort = new Map();
+ const fullToPackageJson = new Map();
+- for ( const pkg of PACKAGES ) {
+- const packageJson = getPackageInfoFromFile(
+- path.join( PACKAGES_DIR, pkg, 'package.json' )
+- );
+- shortToFull.set( pkg, packageJson.name );
+- fullToShort.set( packageJson.name, pkg );
+- fullToPackageJson.set( packageJson.name, packageJson );
++ for ( const [ pkg, entry ] of PACKAGES ) {
++ shortToFull.set( pkg, entry.packageJson.name );
++ fullToShort.set( entry.packageJson.name, pkg );
++ fullToPackageJson.set( entry.packageJson.name, entry.packageJson );
+ }
+
+ const levels = groupByDepth( fullToPackageJson );
+@@ -2045,6 +2158,15 @@ async function buildAll( baseUrlExpression ) {
+ await Promise.all(
+ level.map( async ( fullName ) => {
+ const packageName = fullToShort.get( fullName );
++ const entry = PACKAGES.get( packageName );
++
++ // External packages are pre-built outside this plugin
++ // (e.g. a workspace dep). Skip transpilation; they are
++ // bundled and externalized in Phase 2.
++ if ( entry.external ) {
++ return;
++ }
++
+ const buildTime = await transpilePackage( packageName );
+ console.log(
+ ` ✔ Transpiled ${ packageName } (${ buildTime }ms)`
+@@ -2058,7 +2180,7 @@ async function buildAll( baseUrlExpression ) {
+ const scripts = [];
+ const styles = [];
+ await Promise.all(
+- PACKAGES.map( async ( packageName ) => {
++ Array.from( PACKAGES.keys() ).map( async ( packageName ) => {
+ const startBundleTime = Date.now();
+ const ret = await bundlePackage( packageName );
+ const buildTime = Date.now() - startBundleTime;
+@@ -2203,17 +2325,14 @@ async function watchMode() {
+ let isRebuilding = false;
+ const needsRebuild = new Set();
+
+- // Build maps: short name ↔ full name ↔ package.json from package.json files (once)
++ // Build maps: short name ↔ full name ↔ package.json from the registry (once)
+ const shortToFull = new Map();
+ const fullToShort = new Map();
+ const fullToPackageJson = new Map();
+- for ( const pkg of PACKAGES ) {
+- const packageJson = getPackageInfoFromFile(
+- path.join( PACKAGES_DIR, pkg, 'package.json' )
+- );
+- shortToFull.set( pkg, packageJson.name );
+- fullToShort.set( packageJson.name, pkg );
+- fullToPackageJson.set( packageJson.name, packageJson );
++ for ( const [ pkg, entry ] of PACKAGES ) {
++ shortToFull.set( pkg, entry.packageJson.name );
++ fullToShort.set( entry.packageJson.name, pkg );
++ fullToPackageJson.set( entry.packageJson.name, entry.packageJson );
+ }
+
+ // Get all routes and widgets for dependency tracking
+@@ -2246,8 +2365,14 @@ async function watchMode() {
+ async function rebuildPackage( packageName ) {
+ try {
+ const startTime = Date.now();
++ const entry = PACKAGES.get( packageName );
+
+- await transpilePackage( packageName );
++ // External packages are pre-built outside this plugin; only
++ // rebundle them when their declared script-module entry is
++ // regenerated. Skip transpilation.
++ if ( ! entry?.external ) {
++ await transpilePackage( packageName );
++ }
+ await bundlePackage( packageName );
+
+ const buildTime = Date.now() - startTime;
+@@ -2346,9 +2471,9 @@ async function watchMode() {
+ await processNextRebuild();
+ }
+
+- const watchPaths = PACKAGES.map( ( packageName ) =>
+- path.join( PACKAGES_DIR, packageName, 'src' )
+- );
++ const watchPaths = Array.from( PACKAGES.values() )
++ .filter( ( entry ) => ! entry.external )
++ .map( ( entry ) => path.join( entry.dir, 'src' ) );
+
+ const watcher = chokidar.watch( watchPaths, {
+ ignored: [
+diff --git a/lib/wordpress-externals-plugin.mjs b/lib/wordpress-externals-plugin.mjs
+index 2b7e01a66f..a07a310067 100644
+--- a/lib/wordpress-externals-plugin.mjs
++++ b/lib/wordpress-externals-plugin.mjs
+@@ -46,17 +46,19 @@ async function generateContentHash(
+ * This plugin handles WordPress package externals and vendor libraries,
+ * treating them as external dependencies available via global variables.
+ *
+- * @param {string} packageNamespace Custom package namespace (e.g., 'wordpress', 'my-plugin').
+- * @param {string|false} scriptGlobal Global variable name (e.g., 'wp', 'myPlugin') or false to disable globals.
+- * @param {Object} externalNamespaces Additional namespaces to externalize (e.g., { 'woo': { global: 'woo', handlePrefix: 'woocommerce' } }).
+- * @param {string} handlePrefix Handle prefix for main package (e.g., 'wp', 'mp'). Defaults to packageNamespace.
++ * @param {string} packageNamespace Custom package namespace (e.g., 'wordpress', 'my-plugin').
++ * @param {string|false} scriptGlobal Global variable name (e.g., 'wp', 'myPlugin') or false to disable globals.
++ * @param {Object} externalNamespaces Additional namespaces to externalize (e.g., { 'woo': { global: 'woo', handlePrefix: 'woocommerce' } }).
++ * @param {string} handlePrefix Handle prefix for main package (e.g., 'wp', 'mp'). Defaults to packageNamespace.
++ * @param {Set} [internalPackageNames] `name` fields of every internal package (local + convention-discovered). Imports matching any of these are externalized by exact name, regardless of `packageNamespace`.
+ * @return {Function} Function that creates the esbuild plugin instance.
+ */
+ export function createWordpressExternalsPlugin(
+ packageNamespace,
+ scriptGlobal,
+ externalNamespaces = {},
+- handlePrefix
++ handlePrefix,
++ internalPackageNames = new Set()
+ ) {
+ /**
+ * WordPress externals plugin for esbuild.
+@@ -199,6 +201,83 @@ export function createWordpressExternalsPlugin(
+ );
+ }
+
++ // Externalize internal packages by exact name. Must precede the
++ // namespace pattern below so internal names win over the wildcard.
++ if ( internalPackageNames.size > 0 ) {
++ // Longest-first: avoids `@org/block` shadowing `@org/block-editor`.
++ const namesSorted = Array.from( internalPackageNames ).sort(
++ ( a, b ) => b.length - a.length
++ );
++ const escapedNames = namesSorted.map( ( n ) =>
++ n.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' )
++ );
++ const internalNamesFilter = new RegExp(
++ `^(?:${ escapedNames.join( '|' ) })(?:/|$)`
++ );
++
++ build.onResolve(
++ { filter: internalNamesFilter },
++ /** @param {import('esbuild').OnResolveArgs} args */
++ ( args ) => {
++ const head = args.path.startsWith( '@' )
++ ? args.path.split( '/', 2 ).join( '/' )
++ : args.path.split( '/', 1 )[ 0 ];
++
++ if ( ! internalPackageNames.has( head ) ) {
++ return undefined;
++ }
++
++ const subpath =
++ args.path.length > head.length
++ ? args.path.slice( head.length + 1 )
++ : null;
++
++ const packageJson = getPackageInfo(
++ head,
++ args.resolveDir
++ );
++
++ if ( ! packageJson ) {
++ return undefined;
++ }
++
++ const isScriptModule = isScriptModuleImport(
++ packageJson,
++ subpath
++ );
++ const isScript = !! packageJson.wpScript;
++
++ // Dual packages: IIFE yields to the namespace handler.
++ let externalize = isScriptModule;
++ if ( isScriptModule && isScript ) {
++ externalize = buildFormat === 'esm';
++ }
++ if ( ! externalize ) {
++ return undefined;
++ }
++
++ const kind =
++ args.kind === 'dynamic-import'
++ ? 'dynamic'
++ : 'static';
++
++ if ( kind === 'static' ) {
++ moduleDependencies.set( args.path, 'static' );
++ } else if (
++ ! moduleDependencies.has( args.path )
++ ) {
++ moduleDependencies.set( args.path, 'dynamic' );
++ }
++
++ return {
++ path: args.path,
++ external: true,
++ sideEffects: !! packageJson.sideEffects,
++ };
++ }
++ );
++ }
++
+ // Handle package namespace externals (wordpress and custom)
+ for ( const externalConfig of packageExternals ) {
+ build.onResolve(
+diff --git a/templates/module-registration.php.template b/templates/module-registration.php.template
+index 49ca9ab333..6940832a9f 100644
+--- a/templates/module-registration.php.template
++++ b/templates/module-registration.php.template
+@@ -46,8 +46,12 @@ function {{PREFIX}}_register_script_modules() {
+ $asset = file_exists( $asset_path ) ? require $asset_path : array();
+
+ // Deregister first to override any previously registered version
+- // (e.g., Core's default modules when running as a plugin).
+- wp_deregister_script_module( $module['id'] );
++ // of Core's default script modules. Scoped to `@wordpress/*` — the
++ // only namespace Core registers by default — so plugin-local and
++ // shared packages preserve Core's idempotent "first-wins" semantics.
++ if ( str_starts_with( $module['id'], '@wordpress/' ) ) {
++ wp_deregister_script_module( $module['id'] );
++ }
+
+ wp_register_script_module(
+ $module['id'],
+diff --git a/templates/routes-registration.php.template b/templates/routes-registration.php.template
+index 7ab43f1eb1..3aa6918c11 100644
+--- a/templates/routes-registration.php.template
++++ b/templates/routes-registration.php.template
+@@ -54,8 +54,11 @@ function {{PREFIX}}_register_page_routes( $page_routes, $register_function_name
+ $content_handle = '{{HANDLE_PREFIX}}/routes/' . $route['name'] . '/content';
+ $extension = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '.js' : '.min.js';
+ // Deregister first to override any previously registered version
+- // (e.g., Core's default modules when running as a plugin).
+- wp_deregister_script_module( $content_handle );
++ // of Core's default script modules. Scoped to `@wordpress/*` — the
++ // only namespace Core registers by default.
++ if ( str_starts_with( $content_handle, '@wordpress/' ) ) {
++ wp_deregister_script_module( $content_handle );
++ }
+ wp_register_script_module(
+ $content_handle,
+ $build_constants['build_url'] . 'routes/' . $route['name'] . '/content' . $extension,
+@@ -73,8 +76,11 @@ function {{PREFIX}}_register_page_routes( $page_routes, $register_function_name
+ $route_handle = '{{HANDLE_PREFIX}}/routes/' . $route['name'] . '/route';
+ $extension = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '.js' : '.min.js';
+ // Deregister first to override any previously registered version
+- // (e.g., Core's default modules when running as a plugin).
+- wp_deregister_script_module( $route_handle );
++ // of Core's default script modules. Scoped to `@wordpress/*` — the
++ // only namespace Core registers by default.
++ if ( str_starts_with( $route_handle, '@wordpress/' ) ) {
++ wp_deregister_script_module( $route_handle );
++ }
+ wp_register_script_module(
+ $route_handle,
+ $build_constants['build_url'] . 'routes/' . $route['name'] . '/route' . $extension,
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 83651e740eb5..539abde232ab 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -7,6 +7,9 @@ settings:
pnpmfileChecksum: sha256-KrBHPLv8FlPoa5OUGbvotku6TLmuhZiq22jdIe7yBHc=
patchedDependencies:
+ '@wordpress/build@0.14.0':
+ hash: 6e9b16f14409f6ca17944490bb6a232332496721dd138a9595856308c214200b
+ path: .pnpm-patches/@wordpress__build@0.14.0.patch
react-autosize-textarea:
hash: 5c09e1dee59caaaba3871f9d722f93e56b41169db486b059597e8f8c788aa464
path: .pnpm-patches/react-autosize-textarea.patch
@@ -2125,7 +2128,7 @@ importers:
version: 6.46.0
'@wordpress/build':
specifier: 0.14.0
- version: 0.14.0(@babel/core@7.29.0)(@wordpress/route@0.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@wordpress/theme@0.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(browserslist@4.28.2)
+ version: 0.14.0(patch_hash=6e9b16f14409f6ca17944490bb6a232332496721dd138a9595856308c214200b)(@babel/core@7.29.0)(@wordpress/route@0.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@wordpress/theme@0.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(browserslist@4.28.2)
browserslist:
specifier: ^4.24.0
version: 4.28.2
@@ -2740,7 +2743,7 @@ importers:
version: 6.46.0
'@wordpress/build':
specifier: 0.14.0
- version: 0.14.0(@babel/core@7.29.0)(@wordpress/boot@0.13.0(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@wordpress/route@0.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@wordpress/theme@0.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(browserslist@4.28.2)
+ version: 0.14.0(patch_hash=6e9b16f14409f6ca17944490bb6a232332496721dd138a9595856308c214200b)(@babel/core@7.29.0)(@wordpress/boot@0.13.0(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@wordpress/route@0.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@wordpress/theme@0.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(browserslist@4.28.2)
'@wordpress/date':
specifier: 5.46.0
version: 5.46.0
@@ -3448,7 +3451,7 @@ importers:
version: 6.46.0
'@wordpress/build':
specifier: 0.14.0
- version: 0.14.0(@babel/core@7.29.0)(@wordpress/route@0.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@wordpress/theme@0.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(browserslist@4.28.2)
+ version: 0.14.0(patch_hash=6e9b16f14409f6ca17944490bb6a232332496721dd138a9595856308c214200b)(@babel/core@7.29.0)(@wordpress/route@0.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@wordpress/theme@0.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(browserslist@4.28.2)
browserslist:
specifier: ^4.24.0
version: 4.28.2
@@ -3789,7 +3792,7 @@ importers:
version: 6.46.0
'@wordpress/build':
specifier: 0.14.0
- version: 0.14.0(@babel/core@7.29.0)(@wordpress/route@0.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@wordpress/theme@0.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(browserslist@4.28.2)
+ version: 0.14.0(patch_hash=6e9b16f14409f6ca17944490bb6a232332496721dd138a9595856308c214200b)(@babel/core@7.29.0)(@wordpress/route@0.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@wordpress/theme@0.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(browserslist@4.28.2)
'@wordpress/theme':
specifier: 0.13.0
version: 0.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -3826,12 +3829,18 @@ importers:
projects/packages/premium-analytics:
dependencies:
+ '@automattic/number-formatters':
+ specifier: workspace:*
+ version: link:../../js-packages/number-formatters
'@wordpress/boot':
specifier: 0.13.0
version: 0.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@wordpress/data':
specifier: 10.46.0
version: 10.46.0(react@18.3.1)
+ '@wordpress/element':
+ specifier: ^6.22.0
+ version: 6.46.0
'@wordpress/i18n':
specifier: ^6.9.0
version: 6.19.0
@@ -3853,7 +3862,7 @@ importers:
version: 7.29.0
'@wordpress/build':
specifier: 0.14.0
- version: 0.14.0(@babel/core@7.29.0)(@wordpress/boot@0.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@wordpress/route@0.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(browserslist@4.28.2)
+ version: 0.14.0(patch_hash=6e9b16f14409f6ca17944490bb6a232332496721dd138a9595856308c214200b)(@babel/core@7.29.0)(@wordpress/boot@0.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@wordpress/route@0.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(browserslist@4.28.2)
browserslist:
specifier: 4.28.2
version: 4.28.2
@@ -4062,7 +4071,7 @@ importers:
version: 6.46.0
'@wordpress/build':
specifier: 0.14.0
- version: 0.14.0(@babel/core@7.29.0)(@wordpress/route@0.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@wordpress/theme@0.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(browserslist@4.28.2)
+ version: 0.14.0(patch_hash=6e9b16f14409f6ca17944490bb6a232332496721dd138a9595856308c214200b)(@babel/core@7.29.0)(@wordpress/route@0.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@wordpress/theme@0.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(browserslist@4.28.2)
autoprefixer:
specifier: 10.4.20
version: 10.4.20(postcss@8.5.14)
@@ -4289,7 +4298,7 @@ importers:
version: 6.46.0
'@wordpress/build':
specifier: 0.14.0
- version: 0.14.0(@babel/core@7.29.0)(@wordpress/boot@0.13.0(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@wordpress/route@0.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@wordpress/theme@0.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(browserslist@4.28.2)
+ version: 0.14.0(patch_hash=6e9b16f14409f6ca17944490bb6a232332496721dd138a9595856308c214200b)(@babel/core@7.29.0)(@wordpress/boot@0.13.0(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@wordpress/route@0.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@wordpress/theme@0.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(browserslist@4.28.2)
browserslist:
specifier: ^4.24.0
version: 4.28.2
@@ -4712,7 +4721,7 @@ importers:
version: 6.46.0
'@wordpress/build':
specifier: 0.14.0
- version: 0.14.0(@babel/core@7.29.0)(@wordpress/route@0.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@wordpress/theme@0.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(browserslist@4.28.2)
+ version: 0.14.0(patch_hash=6e9b16f14409f6ca17944490bb6a232332496721dd138a9595856308c214200b)(@babel/core@7.29.0)(@wordpress/route@0.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@wordpress/theme@0.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(browserslist@4.28.2)
autoprefixer:
specifier: 10.4.20
version: 10.4.20(postcss@8.5.14)
@@ -4785,7 +4794,7 @@ importers:
version: 6.46.0
'@wordpress/build':
specifier: 0.14.0
- version: 0.14.0(@babel/core@7.29.0)(@wordpress/boot@0.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@wordpress/private-apis@1.46.0)(@wordpress/route@0.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@wordpress/theme@0.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(browserslist@4.28.2)
+ version: 0.14.0(patch_hash=6e9b16f14409f6ca17944490bb6a232332496721dd138a9595856308c214200b)(@babel/core@7.29.0)(@wordpress/boot@0.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@wordpress/private-apis@1.46.0)(@wordpress/route@0.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@wordpress/theme@0.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(browserslist@4.28.2)
'@wordpress/icons':
specifier: ^13.0.0
version: 13.1.0(react@18.3.1)
@@ -23915,7 +23924,7 @@ snapshots:
'@wordpress/browserslist-config@6.46.0': {}
- '@wordpress/build@0.14.0(@babel/core@7.29.0)(@wordpress/boot@0.13.0(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@wordpress/route@0.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@wordpress/theme@0.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(browserslist@4.28.2)':
+ '@wordpress/build@0.14.0(patch_hash=6e9b16f14409f6ca17944490bb6a232332496721dd138a9595856308c214200b)(@babel/core@7.29.0)(@wordpress/boot@0.13.0(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@wordpress/route@0.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@wordpress/theme@0.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(browserslist@4.28.2)':
dependencies:
'@emotion/babel-plugin': 11.13.5
'@wordpress/style-runtime': 0.2.0
@@ -23942,7 +23951,7 @@ snapshots:
- browserslist
- supports-color
- '@wordpress/build@0.14.0(@babel/core@7.29.0)(@wordpress/boot@0.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@wordpress/private-apis@1.46.0)(@wordpress/route@0.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@wordpress/theme@0.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(browserslist@4.28.2)':
+ '@wordpress/build@0.14.0(patch_hash=6e9b16f14409f6ca17944490bb6a232332496721dd138a9595856308c214200b)(@babel/core@7.29.0)(@wordpress/boot@0.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@wordpress/private-apis@1.46.0)(@wordpress/route@0.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@wordpress/theme@0.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(browserslist@4.28.2)':
dependencies:
'@emotion/babel-plugin': 11.13.5
'@wordpress/style-runtime': 0.2.0
@@ -23970,7 +23979,7 @@ snapshots:
- browserslist
- supports-color
- '@wordpress/build@0.14.0(@babel/core@7.29.0)(@wordpress/boot@0.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@wordpress/route@0.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(browserslist@4.28.2)':
+ '@wordpress/build@0.14.0(patch_hash=6e9b16f14409f6ca17944490bb6a232332496721dd138a9595856308c214200b)(@babel/core@7.29.0)(@wordpress/boot@0.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@wordpress/route@0.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(browserslist@4.28.2)':
dependencies:
'@emotion/babel-plugin': 11.13.5
'@wordpress/style-runtime': 0.2.0
@@ -23996,7 +24005,7 @@ snapshots:
- browserslist
- supports-color
- '@wordpress/build@0.14.0(@babel/core@7.29.0)(@wordpress/route@0.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@wordpress/theme@0.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(browserslist@4.28.2)':
+ '@wordpress/build@0.14.0(patch_hash=6e9b16f14409f6ca17944490bb6a232332496721dd138a9595856308c214200b)(@babel/core@7.29.0)(@wordpress/route@0.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@wordpress/theme@0.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(browserslist@4.28.2)':
dependencies:
'@emotion/babel-plugin': 11.13.5
'@wordpress/style-runtime': 0.2.0
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 473b6616f833..f114b8c6164d 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -73,6 +73,12 @@ ignoredOptionalDependencies:
# Dependencies needing patching.
patchedDependencies:
+ # Decouple script-module identity from `wpPlugin.packageNamespace`, discover
+ # script-module packages outside `./packages/` via convention, and scope the
+ # deregister-before-register to `@wordpress/*`.
+ # Upstream PRs: https://github.com/WordPress/gutenberg/pull/78822 + /pull/77465
+ '@wordpress/build@0.14.0': .pnpm-patches/@wordpress__build@0.14.0.patch
+
# Vite/esbuild doesn't like the `__esModule` + `exports["default"]` pattern, it winds up double-wrapping the default.
# See also https://github.com/WordPress/gutenberg/issues/39619
react-autosize-textarea: .pnpm-patches/react-autosize-textarea.patch
diff --git a/projects/js-packages/number-formatters/changelog/update-wp-build-package-sources b/projects/js-packages/number-formatters/changelog/update-wp-build-package-sources
new file mode 100644
index 000000000000..547c72ce6cef
--- /dev/null
+++ b/projects/js-packages/number-formatters/changelog/update-wp-build-package-sources
@@ -0,0 +1,4 @@
+Significance: patch
+Type: changed
+
+Add wpScriptModuleExports and expose ./package.json in exports so wp-build convention discovery can register this as a shared script module.
diff --git a/projects/js-packages/number-formatters/package.json b/projects/js-packages/number-formatters/package.json
index d46d2b494e2e..6cb3ad32313d 100644
--- a/projects/js-packages/number-formatters/package.json
+++ b/projects/js-packages/number-formatters/package.json
@@ -19,11 +19,13 @@
"types": "./dist/types/index.d.ts",
"import": "./dist/esm/index.js",
"require": "./dist/cjs/index.cjs"
- }
+ },
+ "./package.json": "./package.json"
},
"main": "./dist/cjs/index.cjs",
"module": "./dist/esm/index.js",
"types": "./dist/types/index.d.ts",
+ "wpScriptModuleExports": ".",
"scripts": {
"build": "pnpm run clean && pnpm run build:ts",
"build:ts": "duel --dirs",
diff --git a/projects/packages/premium-analytics/changelog/update-wp-build-package-sources b/projects/packages/premium-analytics/changelog/update-wp-build-package-sources
new file mode 100644
index 000000000000..f690d6bc7e56
--- /dev/null
+++ b/projects/packages/premium-analytics/changelog/update-wp-build-package-sources
@@ -0,0 +1,4 @@
+Significance: patch
+Type: changed
+
+Patch `@wordpress/build` to read script-module IDs from `package.json#name`, discover script-module packages outside `./packages/` via convention, and scope the generated `wp_deregister_script_module()` calls to `@wordpress/*` so shared modules fall through to Core's first-wins. Pulls in `@automattic/number-formatters` as the first cross-directory shared script module, and renames the local `init` package to `@automattic/jetpack-premium-analytics-init` so its npm name, import specifier, and script-module ID all match.
diff --git a/projects/packages/premium-analytics/package.json b/projects/packages/premium-analytics/package.json
index c6ed3093c829..a0c3419f3bd0 100644
--- a/projects/packages/premium-analytics/package.json
+++ b/projects/packages/premium-analytics/package.json
@@ -16,7 +16,7 @@
{
"id": "jetpack-premium-analytics",
"init": [
- "@jetpack-premium-analytics/init"
+ "@automattic/jetpack-premium-analytics-init"
]
}
],
@@ -28,8 +28,10 @@
}
},
"dependencies": {
+ "@automattic/number-formatters": "workspace:*",
"@wordpress/boot": "0.13.0",
"@wordpress/data": "10.46.0",
+ "@wordpress/element": "^6.22.0",
"@wordpress/i18n": "^6.9.0",
"@wordpress/icons": "^13.0.0",
"@wordpress/route": "0.12.0",
diff --git a/projects/packages/premium-analytics/packages/init/package.json b/projects/packages/premium-analytics/packages/init/package.json
index 6ee2ce1e4470..70de0aea9d5b 100644
--- a/projects/packages/premium-analytics/packages/init/package.json
+++ b/projects/packages/premium-analytics/packages/init/package.json
@@ -1,6 +1,6 @@
{
"private": true,
- "name": "_@jetpack-premium-analytics/init",
+ "name": "@automattic/jetpack-premium-analytics-init",
"version": "0.1.0",
"type": "module",
"wpScript": true,
diff --git a/projects/packages/premium-analytics/routes/dashboard/package.json b/projects/packages/premium-analytics/routes/dashboard/package.json
index e71390452278..6fc64ed66e91 100644
--- a/projects/packages/premium-analytics/routes/dashboard/package.json
+++ b/projects/packages/premium-analytics/routes/dashboard/package.json
@@ -6,6 +6,9 @@
"page": "jetpack-premium-analytics"
},
"dependencies": {
+ "@automattic/number-formatters": "workspace:*",
+ "@types/react": "18.3.28",
+ "@wordpress/element": "^6.22.0",
"@wordpress/i18n": "^6.9.0"
}
}
diff --git a/projects/packages/premium-analytics/routes/dashboard/stage.tsx b/projects/packages/premium-analytics/routes/dashboard/stage.tsx
index 034a332bc4eb..450172717251 100644
--- a/projects/packages/premium-analytics/routes/dashboard/stage.tsx
+++ b/projects/packages/premium-analytics/routes/dashboard/stage.tsx
@@ -1,10 +1,34 @@
+import { formatNumber } from '@automattic/number-formatters';
+import { lazy, Suspense } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
-export const stage = () => {
+const samplePageViews = 1234567;
+const sampleUniqueVisitors = 98432;
+
+const UniqueVisitors = lazy( () =>
+ import( '@automattic/number-formatters' ).then( ( { formatNumberCompact } ) => ( {
+ default: ( { value }: { value: number } ) => { formatNumberCompact( value ) },
+ } ) )
+);
+
+const Stage = () => {
return (
{ __( 'Analytics', 'jetpack-premium-analytics' ) }
-
{ __( 'Welcome to the Analytics dashboard.', 'jetpack-premium-analytics' ) }
+
+
+ { __( 'Unique visitors (dynamic):', 'jetpack-premium-analytics' ) }{ ' ' }
+ … }>
+
+
+
+
+
+ { __( 'Total page views (static):', 'jetpack-premium-analytics' ) }{ ' ' }
+ { formatNumber( samplePageViews ) }
+
);
};
+
+export { Stage as stage };