Skip to content

Commit

Permalink
Merge pull request #28972 from storybookjs/norbert/babel-dedup-core
Browse files Browse the repository at this point in the history
Core: De-duplicate babel use in core
  • Loading branch information
ndelangen authored Aug 27, 2024
2 parents 2b0cd99 + c8decc2 commit 1de6e6f
Show file tree
Hide file tree
Showing 42 changed files with 320 additions and 290 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,4 @@ code/.nx/cache
code/.vite-inspect
.nx/cache
!**/fixtures/**/yarn.lock
code/core/report
3 changes: 2 additions & 1 deletion code/.eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ ember-output
!.eslintrc-markdown.js
!.storybook
core/assets
core/src/core-server/utils/__search-files-tests__
core/src/core-server/utils/__search-files-tests__
core/report
3 changes: 2 additions & 1 deletion code/.prettierignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
*.mdx

/.nx/cache
/.nx/cache
core/report

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 16 additions & 1 deletion code/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,11 @@
"import": "./dist/cli/index.js",
"require": "./dist/cli/index.cjs"
},
"./babel": {
"types": "./dist/babel/index.d.ts",
"import": "./dist/babel/index.js",
"require": "./dist/babel/index.cjs"
},
"./cli/bin": {
"types": "./dist/cli/bin/index.d.ts",
"import": "./dist/cli/bin/index.js",
Expand Down Expand Up @@ -253,6 +258,9 @@
"cli": [
"./dist/cli/index.d.ts"
],
"babel": [
"./dist/babel/index.d.ts"
],
"cli/bin": [
"./dist/cli/bin/index.d.ts"
]
Expand Down Expand Up @@ -381,7 +389,6 @@
"open": "^8.4.0",
"picomatch": "^2.3.0",
"polished": "^4.2.2",
"prettier": "^3.2.5",
"pretty-hrtime": "^1.0.3",
"prompts": "^2.4.0",
"qs": "^6.10.0",
Expand Down Expand Up @@ -413,6 +420,14 @@
"util": "^0.12.4",
"watchpack": "^2.2.0"
},
"peerDependencies": {
"prettier": "^3.2.5"
},
"peerDependenciesMeta": {
"prettier": {
"optional": true
}
},
"publishConfig": {
"access": "public"
},
Expand Down
1 change: 1 addition & 0 deletions code/core/scripts/entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export const getEntries = (cwd: string) => {
define('src/manager/globals.ts', ['node'], true),
define('src/preview/globals.ts', ['node'], true),
define('src/cli/index.ts', ['node'], true),
define('src/babel/index.ts', ['node'], true),
define('src/cli/bin/index.ts', ['node'], true),
];
};
Expand Down
33 changes: 17 additions & 16 deletions code/core/scripts/prep.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
/* eslint-disable local-rules/no-uncategorized-errors */
import { watch } from 'node:fs';
import { mkdir, rm } from 'node:fs/promises';
import { mkdir, rm, writeFile } from 'node:fs/promises';
import { dirname, join } from 'node:path';

import { ensureDir } from 'fs-extra';

import {
chalk,
dedent,
Expand Down Expand Up @@ -307,25 +309,24 @@ async function run() {
});
} else {
await Promise.all(
compile.map(async (context) => {
compile.map(async (context, index) => {
const out = await context.rebuild();
await context.dispose();

/**
* I'm leaving this in place, because I want to start utilizing it in the future. I'm
* imagining a github action that shows the bundle analysis in the PR. I didn't have the
* project-scope to make that happen now, but I want expose this very rich useful data
* accessible, for the next person investigating bundle size issues.
*/
if (out.metafile) {
const { outputs } = out.metafile;
const keys = Object.keys(outputs);
const format = keys.every((key) => key.endsWith('.js')) ? 'esm' : 'cjs';
const outName =
keys.length === 1 ? dirname(keys[0]).replace('dist/', '') : `meta-${format}-${index}`;

// if (out.metafile) {
// await writeFile('report/meta.json', JSON.stringify(out.metafile, null, 2));
// await writeFile(
// 'report/meta.txt',
// await esbuild.analyzeMetafile(out.metafile, { color: false, verbose: false })
// );
// console.log(await esbuild.analyzeMetafile(out.metafile, { color: true }));
// }
await ensureDir('report');
await writeFile(`report/${outName}.json`, JSON.stringify(out.metafile, null, 2));
await writeFile(
`report/${outName}.txt`,
await esbuild.analyzeMetafile(out.metafile, { color: false, verbose: false })
);
}
})
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
import * as babelParser from '@babel/parser';
import * as parser from '@babel/parser';
import type { ParserOptions } from '@babel/parser';
import type * as t from '@babel/types';
import * as recast from 'recast';

function parseWithFlowOrTypescript(source: string, parserOptions: babelParser.ParserOptions) {
function parseWithFlowOrTypescript(source: string, parserOptions: parser.ParserOptions) {
const flowCommentPattern = /^\s*\/\/\s*@flow/;
const useFlowPlugin = flowCommentPattern.test(source);

const parserPlugins: babelParser.ParserOptions['plugins'] = useFlowPlugin
? ['flow']
: ['typescript'];
const parserPlugins: parser.ParserOptions['plugins'] = useFlowPlugin ? ['flow'] : ['typescript'];

// Merge the provided parserOptions with the custom parser plugins
const mergedParserOptions = {
...parserOptions,
plugins: [...(parserOptions.plugins ?? []), ...parserPlugins],
};

return babelParser.parse(source, mergedParserOptions);
return parser.parse(source, mergedParserOptions);
}

export const parserOptions: ParserOptions = {
Expand Down Expand Up @@ -52,5 +50,5 @@ export const babelPrint = (ast: ASTNode): string => {
};

export const babelParseExpression = (code: string) => {
return babelParser.parseExpression(code, parserOptions);
return parser.parseExpression(code, parserOptions);
};
41 changes: 41 additions & 0 deletions code/core/src/babel/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* This entry is to ensure we use a single version of Babel across the codebase. This is to prevent
* issues with multiple versions of Babel being used in the same project. It also prevents us from
* bundling babel multiple times in the final bundles.
*/
import { transformSync } from '@babel/core';
import * as core from '@babel/core';
// @ts-expect-error File is not yet exposed, see https://github.com/babel/babel/issues/11350#issuecomment-644118606
import { File } from '@babel/core';
import bg from '@babel/generator';
import * as parser from '@babel/parser';
import bt from '@babel/traverse';
import * as types from '@babel/types';
import * as recast from 'recast';

export * from './babelParse';

// @ts-expect-error (needed due to it's use of `exports.default`)
const traverse = (bt.default || bt) as typeof bt;
// @ts-expect-error (needed due to it's use of `exports.default`)
const generate = (bg.default || bg) as typeof bg;

const BabelFileClass = File as any;

export {
// main
core,
generate,
traverse,
types,
parser,
transformSync,
BabelFileClass,

// other
recast,
};

export type { BabelFile, NodePath } from '@babel/core';
export type { GeneratorOptions } from '@babel/generator';
export type { Options as RecastOptions } from 'recast';
85 changes: 20 additions & 65 deletions code/core/src/common/utils/formatter.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,9 @@
import semver from 'semver';
import { dedent } from 'ts-dedent';

type Prettier = typeof import('prettier');
type PrettierVersion = 3;

let prettierInstance: Prettier | undefined;
let prettierVersion: 3 | null = null;

const getPrettier = async (): Promise<
{ instance: undefined; version: null } | { instance: typeof import('prettier'); version: 3 }
> => {
if (!prettierInstance) {
try {
prettierInstance = (await import('prettier')) as unknown as Prettier | undefined;
prettierVersion = prettierInstance?.version
? (semver.major(prettierInstance.version) as PrettierVersion)
: null;

return {
version: prettierVersion,
instance: prettierInstance,
} as any;
} catch (err) {
return {
instance: undefined,
version: null,
};
}
}

return {
instance: prettierInstance,
version: prettierVersion,
} as any;
};
async function getPrettier() {
return import('prettier').catch((e) => ({
resolveConfig: async () => null,
format: (content: string) => content,
}));
}

/**
* Format the content of a file using prettier. If prettier is not available in the user's project,
Expand All @@ -42,49 +12,34 @@ const getPrettier = async (): Promise<
*/
export async function formatFileContent(filePath: string, content: string): Promise<string> {
try {
const prettier = await getPrettier();

switch (prettier.version) {
case 3:
const config = await prettier.instance.resolveConfig(filePath);
const { resolveConfig, format } = await getPrettier();
const config = await resolveConfig(filePath);

if (!config || Object.keys(config).length === 0) {
return await formatWithEditorConfig(filePath, content);
}
if (!config || Object.keys(config).length === 0) {
return await formatWithEditorConfig(filePath, content);
}

const result = await prettier.instance.format(content, {
...(config as any),
filepath: filePath,
});
const result = await format(content, {
...(config as any),
filepath: filePath,
});

return result;
case null:
case undefined:
return await formatWithEditorConfig(filePath, content);
default:
console.warn(dedent`
Your prettier version ${
(prettier as any).version
} is not supported to format files which were edited by Storybook.
Please raise an issue on the Storybook GitHub repository.
Falling back to EditorConfig settings, if available.
`);
return await formatWithEditorConfig(filePath, content);
}
return result;
} catch (error) {
return content;
}
}

async function formatWithEditorConfig(filePath: string, content: string) {
const prettier = await import('prettier');
const config = await prettier.resolveConfig(filePath, { editorconfig: true });
const { resolveConfig, format } = await getPrettier();

const config = await resolveConfig(filePath, { editorconfig: true });

if (!config || Object.keys(config).length === 0) {
return content;
}

return prettier.format(content, {
return format(content, {
...(config as any),
filepath: filePath,
});
Expand Down
22 changes: 9 additions & 13 deletions code/core/src/core-server/utils/parser/generic-parser.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { types } from '@babel/core';
import * as babelParser from '@babel/parser';
import { parser, types as t } from '@storybook/core/babel';

import type { Parser, ParserResult } from './types';

Expand All @@ -12,7 +11,7 @@ export class GenericParser implements Parser {
* @returns The exports of the file
*/
async parse(content: string): Promise<ParserResult> {
const ast = babelParser.parse(content, {
const ast = parser.parse(content, {
allowImportExportEverywhere: true,
allowAwaitOutsideFunction: true,
allowNewTargetOutsideFunction: true,
Expand Down Expand Up @@ -66,19 +65,16 @@ export class GenericParser implements Parser {
const exports: ParserResult['exports'] = [];

ast.program.body.forEach(function traverse(node) {
if (types.isExportNamedDeclaration(node)) {
if (t.isExportNamedDeclaration(node)) {
// Handles function declarations: `export function a() {}`
if (
types.isFunctionDeclaration(node.declaration) &&
types.isIdentifier(node.declaration.id)
) {
if (t.isFunctionDeclaration(node.declaration) && t.isIdentifier(node.declaration.id)) {
exports.push({
name: node.declaration.id.name,
default: false,
});
}
// Handles class declarations: `export class A {}`
if (types.isClassDeclaration(node.declaration) && types.isIdentifier(node.declaration.id)) {
if (t.isClassDeclaration(node.declaration) && t.isIdentifier(node.declaration.id)) {
exports.push({
name: node.declaration.id.name,
default: false,
Expand All @@ -87,26 +83,26 @@ export class GenericParser implements Parser {
// Handles export specifiers: `export { a }`
if (node.declaration === null && node.specifiers.length > 0) {
node.specifiers.forEach((specifier) => {
if (types.isExportSpecifier(specifier) && types.isIdentifier(specifier.exported)) {
if (t.isExportSpecifier(specifier) && t.isIdentifier(specifier.exported)) {
exports.push({
name: specifier.exported.name,
default: false,
});
}
});
}
if (types.isVariableDeclaration(node.declaration)) {
if (t.isVariableDeclaration(node.declaration)) {
node.declaration.declarations.forEach((declaration) => {
// Handle variable declarators: `export const a = 1;`
if (types.isVariableDeclarator(declaration) && types.isIdentifier(declaration.id)) {
if (t.isVariableDeclarator(declaration) && t.isIdentifier(declaration.id)) {
exports.push({
name: declaration.id.name,
default: false,
});
}
});
}
} else if (types.isExportDefaultDeclaration(node)) {
} else if (t.isExportDefaultDeclaration(node)) {
exports.push({
name: 'default',
default: true,
Expand Down
Loading

0 comments on commit 1de6e6f

Please sign in to comment.