diff --git a/change/@fluentui-react-storybook-addon-export-to-sandbox-055ab233-3768-4a82-b636-fbd44c7644a0.json b/change/@fluentui-react-storybook-addon-export-to-sandbox-055ab233-3768-4a82-b636-fbd44c7644a0.json
new file mode 100644
index 00000000000000..0ea869fa36204d
--- /dev/null
+++ b/change/@fluentui-react-storybook-addon-export-to-sandbox-055ab233-3768-4a82-b636-fbd44c7644a0.json
@@ -0,0 +1,7 @@
+{
+ "type": "patch",
+ "comment": "feat: run the story source transform before transpilation and remove the custom Babel loader",
+ "packageName": "@fluentui/react-storybook-addon-export-to-sandbox",
+ "email": "paulmardling@microsoft.com",
+ "dependentChangeType": "patch"
+}
diff --git a/packages/react-components/react-storybook-addon-export-to-sandbox/__tests__/fixtures/Example.stories.tsx b/packages/react-components/react-storybook-addon-export-to-sandbox/__tests__/fixtures/Example.stories.tsx
new file mode 100644
index 00000000000000..9694879edc62c5
--- /dev/null
+++ b/packages/react-components/react-storybook-addon-export-to-sandbox/__tests__/fixtures/Example.stories.tsx
@@ -0,0 +1 @@
+export const Example = (props: { label: string }) => ;
diff --git a/packages/react-components/react-storybook-addon-export-to-sandbox/__tests__/integration/webpack.cjs b/packages/react-components/react-storybook-addon-export-to-sandbox/__tests__/integration/webpack.cjs
new file mode 100644
index 00000000000000..d3f1ad45f8972c
--- /dev/null
+++ b/packages/react-components/react-storybook-addon-export-to-sandbox/__tests__/integration/webpack.cjs
@@ -0,0 +1,85 @@
+const assert = require('node:assert');
+const fs = require('node:fs');
+const os = require('node:os');
+const path = require('node:path');
+const webpackCompiler = require('webpack');
+
+const { webpack } = require('../../lib-commonjs/webpack.js');
+
+async function main() {
+ const outputPath = fs.mkdtempSync(path.join(os.tmpdir(), 'export-to-sandbox-webpack-'));
+ const config = webpack(
+ {
+ mode: 'development',
+ devtool: 'source-map',
+ context: path.join(__dirname, '../fixtures'),
+ entry: './Example.stories.tsx',
+ output: { path: outputPath, filename: 'bundle.js' },
+ module: {
+ rules: [
+ {
+ test: /\.tsx?$/,
+ use: {
+ loader: require.resolve('swc-loader'),
+ options: {
+ jsc: {
+ parser: { syntax: 'typescript', tsx: true },
+ target: 'es2019',
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ {
+ presetsList: [
+ {
+ name: 'node_modules/@fluentui/react-storybook-addon-export-to-sandbox/lib/preset.js',
+ preset: {},
+ options: { importMappings: {} },
+ },
+ ],
+ },
+ );
+
+ try {
+ const stats = await compile(config);
+ if (stats.hasErrors()) {
+ throw new Error(stats.toString({ all: false, errors: true, errorDetails: true }));
+ }
+
+ const bundle = fs.readFileSync(path.join(outputPath, 'bundle.js'), 'utf8');
+ assert.match(bundle, /Example\.parameters\.fullSource/);
+ assert.match(bundle, /label: string/);
+
+ const sourceMap = JSON.parse(fs.readFileSync(path.join(outputPath, 'bundle.js.map'), 'utf8'));
+ assert(sourceMap.sources.some(source => source.endsWith('Example.stories.tsx')));
+ assert(sourceMap.mappings.length > 0);
+ } finally {
+ fs.rmSync(outputPath, { recursive: true, force: true });
+ }
+}
+
+function compile(config) {
+ const compiler = webpackCompiler(config);
+
+ return new Promise((resolve, reject) => {
+ compiler.run((error, stats) => {
+ compiler.close(closeError => {
+ if (error || closeError) {
+ reject(error || closeError);
+ } else if (!stats) {
+ reject(new Error('Webpack compilation did not return stats.'));
+ } else {
+ resolve(stats);
+ }
+ });
+ });
+ });
+}
+
+main().catch(error => {
+ console.error(error);
+ process.exitCode = 1;
+});
diff --git a/packages/react-components/react-storybook-addon-export-to-sandbox/project.json b/packages/react-components/react-storybook-addon-export-to-sandbox/project.json
index 161368d74702ec..5d9a3f6f77b5a1 100644
--- a/packages/react-components/react-storybook-addon-export-to-sandbox/project.json
+++ b/packages/react-components/react-storybook-addon-export-to-sandbox/project.json
@@ -16,6 +16,14 @@
}
]
}
+ },
+ "test-integration": {
+ "executor": "nx:run-commands",
+ "dependsOn": ["build"],
+ "options": {
+ "cwd": "{projectRoot}",
+ "command": "node __tests__/integration/webpack.cjs"
+ }
}
}
}
diff --git a/packages/react-components/react-storybook-addon-export-to-sandbox/src/custom-babel-loader.ts b/packages/react-components/react-storybook-addon-export-to-sandbox/src/custom-babel-loader.ts
deleted file mode 100644
index 5a4789359c8d43..00000000000000
--- a/packages/react-components/react-storybook-addon-export-to-sandbox/src/custom-babel-loader.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-const babelLoader = require('babel-loader');
-
-/**
- * Custom loader wraps the original babel-loader and fixes the incorrect `inputSourceMap` that
- * is passed from a loader that uses the `sourceMap` option.
- */
-module.exports = function (source: string, inputSourceMap?: object | boolean | string) {
- return babelLoader.call(
- this,
- source,
- typeof inputSourceMap === 'string' ? JSON.parse(inputSourceMap) : inputSourceMap,
- );
-};
diff --git a/packages/react-components/react-storybook-addon-export-to-sandbox/src/webpack.spec.ts b/packages/react-components/react-storybook-addon-export-to-sandbox/src/webpack.spec.ts
index 07327204e7246c..bfc9fc0e4dbf0a 100644
--- a/packages/react-components/react-storybook-addon-export-to-sandbox/src/webpack.spec.ts
+++ b/packages/react-components/react-storybook-addon-export-to-sandbox/src/webpack.spec.ts
@@ -14,11 +14,12 @@ describe(`webpack`, () => {
expect(actual.module?.rules).toEqual([
{
- enforce: 'post',
+ enforce: 'pre',
test: /\.stories\.(jsx?$|tsx?$)/,
use: {
- loader: expect.stringContaining('custom-babel-loader'),
+ loader: expect.stringContaining('babel-loader'),
options: {
+ parserOpts: { plugins: ['typescript', 'jsx'] },
plugins: [
[
expect.stringContaining('babel-preset-storybook-full-source'),
@@ -52,12 +53,13 @@ describe(`webpack`, () => {
expect(actual.module?.rules).toEqual([
{
- enforce: 'post',
+ enforce: 'pre',
test: /\.stories\.tsx?/,
include: /foo-stories/,
use: {
- loader: expect.stringContaining('custom-babel-loader'),
+ loader: expect.stringContaining('babel-loader'),
options: {
+ parserOpts: { plugins: ['typescript', 'jsx'] },
plugins: [
[
expect.stringContaining('babel-preset-storybook-full-source'),
@@ -90,11 +92,12 @@ describe(`webpack`, () => {
expect(actual.module?.rules).toEqual([
{
- enforce: 'post',
+ enforce: 'pre',
test: /\.stories\.(jsx?$|tsx?$)/,
use: {
- loader: expect.stringContaining('custom-babel-loader'),
+ loader: expect.stringContaining('babel-loader'),
options: {
+ parserOpts: { plugins: ['typescript', 'jsx'] },
plugins: [
[
expect.stringContaining('babel-preset-storybook-full-source'),
diff --git a/packages/react-components/react-storybook-addon-export-to-sandbox/src/webpack.ts b/packages/react-components/react-storybook-addon-export-to-sandbox/src/webpack.ts
index ee906f1b6171ec..8ebe39c87ae43c 100644
--- a/packages/react-components/react-storybook-addon-export-to-sandbox/src/webpack.ts
+++ b/packages/react-components/react-storybook-addon-export-to-sandbox/src/webpack.ts
@@ -37,17 +37,14 @@ function createBabelLoaderRule(config: Required): import('webpack'
test: /\.stories\.(jsx?$|tsx?$)/,
...webpackRule,
/**
- * why the usage of 'post' ? - we need to run this loader after all storybook webpack rules/loaders have been executed.
- * while we can use Array.prototype.unshift to "override" the indexes this approach is more declarative without additional hacks.
+ * Run before transpilers so this loader receives the original story source and does not depend on
+ * the format of sourcemaps emitted by subsequent loaders.
*/
- enforce: 'post',
+ enforce: 'pre',
use: {
- /**
- * Custom babel loader wraps the original babel-loader and fixes the incorrect `inputSourceMap` parameter
- * that is passed to babel-loader.
- **/
- loader: require.resolve('./custom-babel-loader'),
+ loader: require.resolve('babel-loader'),
options: babelLoaderOptionsUpdater({
+ parserOpts: { plugins: ['typescript', 'jsx'] },
plugins: [plugin],
}),
},