Skip to content

Commit

Permalink
feat: add root TypeScript support
Browse files Browse the repository at this point in the history
  • Loading branch information
mrmckeb committed May 17, 2020
1 parent fa914a7 commit 0d7b7fe
Show file tree
Hide file tree
Showing 20 changed files with 169 additions and 80 deletions.
1 change: 0 additions & 1 deletion app/angular/src/server/framework-preset-angular.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ export function webpack(
},
resolve: {
...config.resolve,
extensions: ['.ts', '.tsx', ...config.resolve.extensions],
},
plugins: [
...config.plugins,
Expand Down
1 change: 1 addition & 0 deletions app/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"lodash": "^4.17.15",
"prop-types": "^15.7.2",
"react-dev-utils": "^10.0.0",
"react-docgen-typescript-loader": "^3.7.2",
"regenerator-runtime": "^0.13.3",
"semver": "^7.3.2",
"ts-dedent": "^1.1.1",
Expand Down
67 changes: 12 additions & 55 deletions app/react/src/server/framework-preset-react-docgen.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as preset from './framework-preset-react-docgen';
describe('framework-preset-react-docgen', () => {
const babelPluginReactDocgenPath = require.resolve('babel-plugin-react-docgen');

it('should return the config with the extra plugins when `plugins` is an array.', () => {
it('should return the config with the extra plugin', () => {
const babelConfig = {
babelrc: false,
presets: ['env', 'foo-preset'],
Expand All @@ -15,62 +15,19 @@ describe('framework-preset-react-docgen', () => {

expect(config).toEqual({
babelrc: false,
plugins: [
'foo-plugin',
[
babelPluginReactDocgenPath,
{
DOC_GEN_COLLECTION_NAME: 'STORYBOOK_REACT_CLASSES',
},
],
],
presets: ['env', 'foo-preset'],
});
});

it('should return the config with the extra plugins when `plugins` is not an array.', () => {
const babelConfig: TransformOptions = {
babelrc: false,
presets: ['env', 'foo-preset'],
plugins: ['bar-plugin'],
};

const config = preset.babel(babelConfig);

expect(config).toEqual({
babelrc: false,
plugins: [
'bar-plugin',
[
babelPluginReactDocgenPath,
{
DOC_GEN_COLLECTION_NAME: 'STORYBOOK_REACT_CLASSES',
},
],
],
presets: ['env', 'foo-preset'],
});
});

it('should return the config only with the extra plugins when `plugins` is not present.', () => {
const babelConfig = {
babelrc: false,
plugins: ['foo-plugin'],
presets: ['env', 'foo-preset'],
};

const config = preset.babel(babelConfig);

expect(config).toEqual({
babelrc: false,
plugins: [
[
babelPluginReactDocgenPath,
{
DOC_GEN_COLLECTION_NAME: 'STORYBOOK_REACT_CLASSES',
},
],
overrides: [
{
test: /\.(mjs|jsx?)$/,
plugins: [
babelPluginReactDocgenPath,
{
DOC_GEN_COLLECTION_NAME: 'STORYBOOK_REACT_CLASSES',
},
],
},
],
presets: ['env', 'foo-preset'],
});
});
});
45 changes: 33 additions & 12 deletions app/react/src/server/framework-preset-react-docgen.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,43 @@
import { TransformOptions } from '@babel/core';
import { Configuration } from 'webpack';

export function babel(config: TransformOptions) {
// Ensure plugins are defined or fallback to an array to avoid empty values.
const babelConfigPlugins = config.plugins || [];

const extraPlugins = [
[
require.resolve('babel-plugin-react-docgen'),
export function babel(
config: TransformOptions,
{ typescript: { docgen = 'react-docgen-typescript' } } = { typescript: {} }
) {
return {
...config,
overrides: [
{
DOC_GEN_COLLECTION_NAME: 'STORYBOOK_REACT_CLASSES',
test: docgen === 'react-docgen' ? /\.(mjs|tsx?|jsx?)$/ : /\.(mjs|jsx?)$/,
plugins: [
[
require.resolve('babel-plugin-react-docgen'),
{
DOC_GEN_COLLECTION_NAME: 'STORYBOOK_REACT_CLASSES',
},
],
],
},
],
];
};
}

// If `babelConfigPlugins` is not an `Array`, calling `concat` will inject it
// as a single value, if it is an `Array` it will spread.
export function webpackFinal(
config: Configuration,
{ typescript: { docgen = 'react-docgen-typescript' } } = { typescript: {} }
) {
if (docgen !== 'react-docgen-typescript') return config;
return {
...config,
plugins: [].concat(babelConfigPlugins, extraPlugins),
module: {
...config.module,
rules: [
...config.module.rules,
{
loader: require.resolve('react-docgen-typescript-loader'),
},
],
},
};
}
1 change: 1 addition & 0 deletions app/vue/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"css-loader": "*",
"react": "*",
"react-dom": "*",
"ts-loader": "^7.0.4",
"vue": "^2.6.8",
"vue-loader": "^15.7.0",
"vue-template-compiler": "^2.6.8"
Expand Down
12 changes: 12 additions & 0 deletions app/vue/src/server/framework-preset-vue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,18 @@ export function webpack(config: Configuration) {
loader: require.resolve('vue-loader'),
options: {},
},
{
test: /\.tsx?$/,
use: [
{
loader: require.resolve('ts-loader'),
options: {
transpileOnly: true,
appendTsSuffixTo: [/\.vue$/],
},
},
],
},
],
},
resolve: {
Expand Down
3 changes: 3 additions & 0 deletions examples/react-ts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Storybook TS example

This Storybook demonstrates support for TypeScript in Storybook without additional configuration.
7 changes: 7 additions & 0 deletions examples/react-ts/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = {
stories: ['./src/*.stories.*'],
typescript: {
check: true,
checkOptions: {},
},
};
19 changes: 19 additions & 0 deletions examples/react-ts/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "@storybook/example-react-ts",
"version": "6.0.0-beta.7",
"private": true,
"scripts": {
"build-storybook": "cross-env STORYBOOK_DISPLAY_WARNING=true DISPLAY_WARNING=true build-storybook -c ./",
"debug": "cross-env NODE_OPTIONS=--inspect-brk STORYBOOK_DISPLAY_WARNING=true DISPLAY_WARNING=true start-storybook -p 9011 -c ./ --no-dll",
"storybook": "cross-env STORYBOOK_DISPLAY_WARNING=true DISPLAY_WARNING=true start-storybook -p 9011 -c ./ --no-dll"
},
"dependencies": {
"@storybook/react": "6.0.0-beta.7",
"@types/react": "^16.9.35",
"@types/react-dom": "^16.9.8",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"typescript": "^3.9.2",
"webpack": "^4.43.0"
}
}
6 changes: 6 additions & 0 deletions examples/react-ts/src/button.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React from 'react';
import { Button } from './button';

export default { component: Button, title: 'Examples / Button' };

export const SimpleButton = () => <Button label="Click me" />;
7 changes: 7 additions & 0 deletions examples/react-ts/src/button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React, { FC, ButtonHTMLAttributes } from 'react';

export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
label: string;
}

export const Button = ({ label }: ButtonProps) => <button type="button">{label}</button>;
10 changes: 10 additions & 0 deletions examples/react-ts/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"baseUrl": ".",
"skipLibCheck": true,
"jsx": "preserve",
"esModuleInterop": true,
"strict": true
},
"include": ["src/*"]
}
3 changes: 2 additions & 1 deletion lib/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"@babel/plugin-transform-react-constant-elements": "^7.2.0",
"@babel/preset-env": "^7.9.6",
"@babel/preset-react": "^7.8.3",
"@babel/preset-typescript": "^7.8.3",
"@babel/preset-typescript": "^7.9.0",
"@babel/register": "^7.8.3",
"@storybook/addons": "6.0.0-beta.7",
"@storybook/api": "6.0.0-beta.7",
Expand Down Expand Up @@ -74,6 +74,7 @@
"file-system-cache": "^1.0.5",
"find-cache-dir": "^3.0.0",
"find-up": "^4.1.0",
"fork-ts-checker-webpack-plugin": "^4.1.4",
"fs-extra": "^9.0.0",
"glob": "^7.1.6",
"glob-base": "^0.3.0",
Expand Down
1 change: 1 addition & 0 deletions lib/core/src/server/common/babel.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export default () => {
require.resolve('@babel/preset-env'),
{ shippedProposals: true, useBuiltIns: 'usage', corejs: '3' },
],
require.resolve('@babel/preset-typescript'),
],
plugins: [
require.resolve('@babel/plugin-proposal-object-rest-spread'),
Expand Down
20 changes: 18 additions & 2 deletions lib/core/src/server/config.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import { logger } from '@storybook/node-logger';
import loadPresets from './presets';
import loadCustomPresets from './common/custom-presets';

async function getPreviewWebpackConfig(options, presets) {
const babelOptions = await presets.apply('babel', {}, options);
const entries = await presets.apply('entries', [], options);
const stories = await presets.apply('stories', [], options);
const typescriptOptions = await presets.apply('typescript', {}, options);

return presets.apply('webpack', {}, { ...options, babelOptions, entries, stories });
return presets.apply(
'webpack',
{},
{ ...options, babelOptions, entries, stories, typescriptOptions }
);
}

export default async (options) => {
Expand All @@ -20,7 +26,17 @@ export default async (options) => {
...overridePresets,
];

const presets = loadPresets(presetsConfig, restOptions);
// Remove `@storybook/preset-typescript` and add a warning if in use.
const filteredPresetConfig = presetsConfig.filter(
(preset) => !/@storybook[\\\\/]preset-typescript/.test(preset)
);
if (filteredPresetConfig.length < presetsConfig.length) {
logger.warn(
'Storybook now supports TypeScript natively. You can safely remove `@storybook/preset-typescript`.'
);
}

const presets = loadPresets(filteredPresetConfig, restOptions);

return getPreviewWebpackConfig({ ...restOptions, presets }, presets);
};
8 changes: 8 additions & 0 deletions lib/core/src/server/config/useBaseTsSupport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* Returns true if the framework can use the base TS config.
* @param {string} framework
*/
export const useBaseTsSupport = (framework) => {
// These packages both have their own TS implementation.
return !['vue', 'angular'].includes(framework);
};
1 change: 0 additions & 1 deletion lib/core/src/server/manager/babel-loader-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ export default () => ({
require.resolve('@babel/preset-env'),
{ shippedProposals: true, useBuiltIns: 'usage', corejs: '3' },
],
require.resolve('@babel/preset-typescript'),
require.resolve('@babel/preset-react'),
],
plugins: [
Expand Down
5 changes: 3 additions & 2 deletions lib/core/src/server/preview/babel-loader-preview.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { includePaths, excludePaths } from '../config/utils';
import { useBaseTsSupport } from '../config/useBaseTsSupport';

export default (options) => ({
test: /\.(mjs|jsx?)$/,
export default (options, framework) => ({
test: useBaseTsSupport(framework) ? /\.(mjs|tsx?|jsx?)$/ : /\.(mjs|jsx?)$/,
use: [
{
loader: 'babel-loader',
Expand Down
11 changes: 9 additions & 2 deletions lib/core/src/server/preview/iframe-webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import WatchMissingNodeModulesPlugin from 'react-dev-utils/WatchMissingNodeModul
import TerserWebpackPlugin from 'terser-webpack-plugin';
import VirtualModulePlugin from 'webpack-virtual-modules';
import PnpWebpackPlugin from 'pnp-webpack-plugin';
import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';

import resolveFrom from 'resolve-from';

Expand All @@ -16,6 +17,7 @@ import createBabelLoader from './babel-loader-preview';
import { nodeModulesPaths, loadEnv } from '../config/utils';
import { getPreviewHeadHtml, getPreviewBodyHtml } from '../utils/template';
import { toRequireContextString } from './to-require-context';
import { useBaseTsSupport } from '../config/useBaseTsSupport';

const reactPaths = {};
try {
Expand All @@ -36,10 +38,11 @@ export default async ({
configType,
framework,
presets,
typescriptOptions,
}) => {
const dlls = await presets.apply('webpackDlls', []);
const { raw, stringified } = loadEnv({ production: true });
const babelLoader = createBabelLoader(babelOptions);
const babelLoader = createBabelLoader(babelOptions, framework);
const isProd = configType === 'PRODUCTION';
const entryTemplate = await fse.readFile(path.join(__dirname, 'virtualModuleEntry.template.js'), {
encoding: 'utf8',
Expand Down Expand Up @@ -81,6 +84,9 @@ export default async ({
.replace("'{{stories}}'", stories.map(toRequireContextString).join(','));
}

const shouldCheckTs = useBaseTsSupport(framework) && typescriptOptions.check;
const tsCheckOptions = typescriptOptions.checkOptions || {};

return {
mode: isProd ? 'production' : 'development',
bail: isProd,
Expand Down Expand Up @@ -129,6 +135,7 @@ export default async ({
new CaseSensitivePathsPlugin(),
quiet ? null : new ProgressPlugin(),
new Dotenv({ silent: true }),
shouldCheckTs ? new ForkTsCheckerWebpackPlugin(tsCheckOptions) : null,
].filter(Boolean),
module: {
rules: [
Expand All @@ -144,7 +151,7 @@ export default async ({
],
},
resolve: {
extensions: ['.mjs', '.js', '.jsx', '.json', '.cjs'],
extensions: ['.mjs', '.js', '.jsx', '.ts', '.tsx', '.json', '.cjs'],
modules: ['node_modules'].concat(raw.NODE_PATH || []),
alias: {
'babel-runtime/core-js/object/assign': require.resolve('core-js/es/object/assign'),
Expand Down
Loading

0 comments on commit 0d7b7fe

Please sign in to comment.