Skip to content

Commit

Permalink
Merge pull request #27171 from storybookjs/valentin/fix-webpack5-sour…
Browse files Browse the repository at this point in the history
…cemaps

Builder: Fixes sourcemaps for Webpack5 and Vite builder
  • Loading branch information
valentinpalkovic authored May 24, 2024
2 parents 122a83f + a9f1e1d commit 8ae46ab
Show file tree
Hide file tree
Showing 12 changed files with 140 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ export async function externalGlobalsPlugin(externals: Record<string, string>) {
code: src.toString(),
map: src.generateMap({
source: id,
includeContent: true,
hires: true,
}),
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,7 @@ export default async function loader(
);
}

const generatedMap = magicString.generateMap({ hires: true });

return callback(null, magicString.toString(), generatedMap, meta);
return callback(null, magicString.toString(), map, meta);
} catch (err) {
return callback(null, source, map, meta);
}
Expand Down
1 change: 0 additions & 1 deletion code/frameworks/nextjs/src/swc/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ export const configureSWCLoader = async (
test: /\.((c|m)?(j|t)sx?)$/,
include: [getProjectRoot()],
exclude: [/(node_modules)/, ...Object.keys(virtualModules)],
enforce: 'post',
use: {
// we use our own patch because we need to remove tracing from the original code
// which is not possible otherwise
Expand Down
16 changes: 5 additions & 11 deletions code/frameworks/nextjs/src/swc/next-swc-loader-patch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,10 @@ async function loaderTransform(this: any, parentTrace: any, source?: string, inp
const programmaticOptions = {
...swcOptions,
filename,
inputSourceMap: inputSourceMap ? JSON.stringify(inputSourceMap) : undefined,
inputSourceMap:
inputSourceMap && typeof inputSourceMap === 'object'
? JSON.stringify(inputSourceMap)
: undefined,

// Set the default sourcemap behavior based on Webpack's mapping flag,
sourceMaps: this.sourceMap,
Expand Down Expand Up @@ -166,20 +169,11 @@ export function pitch(this: any) {
}, callback);
}

function sanitizeSourceMap(rawSourceMap: any): any {
const { sourcesContent, ...sourceMap } = rawSourceMap ?? {};

// JSON parse/stringify trick required for swc to accept the SourceMap
return JSON.parse(JSON.stringify(sourceMap));
}

export default function swcLoader(this: any, inputSource: string, inputSourceMap: any) {
const loaderSpan = mockCurrentTraceSpan.traceChild('next-swc-loader');
const callback = this.async();
loaderSpan
.traceAsyncFn(() =>
loaderTransform.call(this, loaderSpan, inputSource, sanitizeSourceMap(inputSourceMap))
)
.traceAsyncFn(() => loaderTransform.call(this, loaderSpan, inputSource, inputSourceMap))
.then(
([transformedSource, outputSourceMap]: any) => {
callback(null, transformedSource, outputSourceMap || inputSourceMap);
Expand Down
2 changes: 1 addition & 1 deletion code/frameworks/react-vite/src/plugins/react-docgen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export async function reactDocgen({

return {
code: s.toString(),
map: s.generateMap(),
map: s.generateMap({ hires: true, source: id }),
};
} catch (e: any) {
// Ignore the error when react-docgen cannot find a react component
Expand Down
9 changes: 8 additions & 1 deletion code/lib/csf-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@
"require": "./dist/index.js",
"import": "./dist/index.mjs"
},
"./dist/webpack-loader": {
"types": "./dist/webpack-loader.d.ts",
"node": "./dist/webpack-loader.js",
"require": "./dist/webpack-loader.js",
"import": "./dist/webpack-loader.mjs"
},
"./package.json": "./package.json"
},
"main": "dist/index.js",
Expand Down Expand Up @@ -55,7 +61,8 @@
},
"bundler": {
"entries": [
"./src/index.ts"
"./src/index.ts",
"./src/webpack-loader.ts"
],
"externals": [
"webpack",
Expand Down
1 change: 1 addition & 0 deletions code/lib/csf-plugin/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const STORIES_REGEX = /(?<!node_modules.*)\.(story|stories)\.[tj]sx?$/;
54 changes: 27 additions & 27 deletions code/lib/csf-plugin/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,39 @@
import { createUnplugin } from 'unplugin';
import fs from 'fs/promises';
import { loadCsf, enrichCsf, formatCsf } from '@storybook/csf-tools';
import type { EnrichCsfOptions } from '@storybook/csf-tools';
import { rollupBasedPlugin } from './rollup-based-plugin';
import { STORIES_REGEX } from './constants';

export type CsfPluginOptions = EnrichCsfOptions;

// Ignore node_modules
const STORIES_REGEX = /(?<!node_modules.*)\.(story|stories)\.[tj]sx?$/;

const logger = console;

export const unplugin = createUnplugin<CsfPluginOptions>((options) => {
return {
name: 'unplugin-csf',
transformInclude(id) {
return STORIES_REGEX.test(id);
rollup: {
...rollupBasedPlugin(options),
},
vite: {
enforce: 'pre',
...rollupBasedPlugin(options),
},
webpack(compiler) {
compiler.options.module.rules.unshift({
test: STORIES_REGEX,
enforce: 'post',
use: {
options,
loader: require.resolve('@storybook/csf-plugin/dist/webpack-loader'),
},
});
},
async transform(code, id) {
const sourceCode = await fs.readFile(id, 'utf-8');
try {
const makeTitle = (userTitle: string) => userTitle || 'default';
const csf = loadCsf(code, { makeTitle }).parse();
const csfSource = loadCsf(sourceCode, {
makeTitle,
}).parse();
enrichCsf(csf, csfSource, options);
return formatCsf(csf, { sourceMaps: true });
} catch (err: any) {
// This can be called on legacy storiesOf files, so just ignore
// those errors. But warn about other errors.
if (!err.message?.startsWith('CSF:')) {
logger.warn(err.message);
}
return code;
}
rspack(compiler) {
compiler.options.module.rules.unshift({
test: STORIES_REGEX,
enforce: 'post',
use: {
options,
loader: require.resolve('@storybook/csf-plugin/dist/webpack-loader'),
},
});
},
};
});
Expand Down
37 changes: 37 additions & 0 deletions code/lib/csf-plugin/src/rollup-based-plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type { EnrichCsfOptions } from '@storybook/csf-tools';
import { enrichCsf, formatCsf, loadCsf } from '@storybook/csf-tools';
import type { RollupPlugin } from 'unplugin';
import fs from 'fs/promises';
import { STORIES_REGEX } from './constants';

const logger = console;

export function rollupBasedPlugin(options: EnrichCsfOptions): Partial<RollupPlugin<any>> {
return {
name: 'plugin-csf',
async transform(code, id) {
if (!STORIES_REGEX.test(id)) {
return;
}

const sourceCode = await fs.readFile(id, 'utf-8');
try {
const makeTitle = (userTitle: string) => userTitle || 'default';
const csf = loadCsf(code, { makeTitle }).parse();
const csfSource = loadCsf(sourceCode, {
makeTitle,
}).parse();
enrichCsf(csf, csfSource, options);
const inputSourceMap = this.getCombinedSourcemap();
return formatCsf(csf, { sourceMaps: true, inputSourceMap }, code);
} catch (err: any) {
// This can be called on legacy storiesOf files, so just ignore
// those errors. But warn about other errors.
if (!err.message?.startsWith('CSF:')) {
logger.warn(err.message);
}
return code;
}
},
};
}
40 changes: 40 additions & 0 deletions code/lib/csf-plugin/src/webpack-loader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import fs from 'fs/promises';
import type { EnrichCsfOptions } from '@storybook/csf-tools';
import { loadCsf, formatCsf, enrichCsf } from '@storybook/csf-tools';

interface LoaderContext {
async: () => (err: Error | null, result?: string, map?: any) => void;
getOptions: () => EnrichCsfOptions;
resourcePath: string;
}

async function loader(this: LoaderContext, content: string, map: any) {
const callback = this.async();
const options = this.getOptions();
const id = this.resourcePath;

const sourceCode = await fs.readFile(id, 'utf-8');

try {
const makeTitle = (userTitle: string) => userTitle || 'default';
const csf = loadCsf(content, { makeTitle }).parse();
const csfSource = loadCsf(sourceCode, { makeTitle }).parse();
enrichCsf(csf, csfSource, options);
const formattedCsf = formatCsf(csf, { sourceMaps: true, inputSourceMap: map }, content);

if (typeof formattedCsf === 'string') {
return callback(null, formattedCsf, map);
}

callback(null, formattedCsf.code, formattedCsf.map);
} catch (err: any) {
// This can be called on legacy storiesOf files, so just ignore
// those errors. But warn about other errors.
if (!err.message?.startsWith('CSF:')) {
console.warn(err.message);
}
callback(null, content, map);
}
}

export default loader;
12 changes: 8 additions & 4 deletions code/lib/csf-tools/src/CsfFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -583,15 +583,19 @@ export const loadCsf = (code: string, options: CsfOptions) => {
interface FormatOptions {
sourceMaps?: boolean;
preserveStyle?: boolean;
inputSourceMap?: any;
}

export const formatCsf = (csf: CsfFile, options: FormatOptions = { sourceMaps: false }) => {
const result = generate.default(csf._ast, options);
export const formatCsf = (
csf: CsfFile,
options: FormatOptions = { sourceMaps: false },
code?: string
) => {
const result = generate.default(csf._ast, options, code);
if (options.sourceMaps) {
return result;
}
const { code } = result;
return code;
return result.code;
};

/**
Expand Down
18 changes: 12 additions & 6 deletions code/presets/react-webpack/src/loaders/react-docgen-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ let matchPath: TsconfigPaths.MatchPath | undefined;

export default async function reactDocgenLoader(
this: LoaderContext<{ debug: boolean }>,
source: string
source: string,
map: any
) {
const callback = this.async();
// get options
Expand Down Expand Up @@ -115,11 +116,16 @@ export default async function reactDocgenLoader(
}
});

const map = magicString.generateMap({
includeContent: true,
source: this.resourcePath,
});
callback(null, magicString.toString(), map);
callback(
null,
magicString.toString(),
map ??
magicString.generateMap({
hires: true,
source: this.resourcePath,
includeContent: true,
})
);
} catch (error: any) {
if (error.code === ERROR_CODES.MISSING_DEFINITION) {
callback(null, source);
Expand Down

0 comments on commit 8ae46ab

Please sign in to comment.