Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improving maintainability: TypeScript #195

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
ac7ea8f
Created `tsconfig.json` for Node.js 16
mrauhu Jan 5, 2022
59113e6
Created `tsconfig.json` in `packages/storybook-builder-vite` directory
mrauhu Jan 5, 2022
4089a74
Renamed `*.js` files to `*.ts` files in `packages/storybook-builder-v…
mrauhu Jan 6, 2022
b0c59b6
Converted from `require()` and `module.exports` to `import` and `export`
mrauhu Jan 5, 2022
f74da30
Temporary fix types for `.filter()` of stories in `svelte/csf-plugin.ts`
mrauhu Jan 5, 2022
1d08a3c
Provided empty object to `createCompiler()` function in `mdx-plugin.t…
mrauhu Jan 5, 2022
34ec205
Importing promisified version of the `glob` from `glob-promise` package
mrauhu Jan 5, 2022
c4e51e9
Removed wrong extension from import of `createViteServer()` function
mrauhu Jan 5, 2022
df8f27f
Ignoring the `dist` directory
mrauhu Jan 5, 2022
c7e1fe2
Using the `dist` directory: updated `main` field; created `types` fie…
mrauhu Jan 5, 2022
ab5418f
Created `start` and `prepublish` scripts in the root `package.json`
mrauhu Jan 5, 2022
ca28bb2
Updated `tsconfig.json`: emit declarations, enabled source maps (with…
mrauhu Jan 5, 2022
b215618
Start unused arguments with underscore in `pluginConfig()` function a…
mrauhu Jan 5, 2022
9edc9d9
Ran `prepublish` script manually in GitHub Workflows. Fix: https://gi…
mrauhu Jan 6, 2022
b6bcaba
Fix: updated resolved path of the `iframe.html`, because we're servin…
mrauhu Jan 6, 2022
9dad533
Fix: async import of exported `vueDocgen()` function from local file …
mrauhu Jan 6, 2022
e32a648
Added the `dist` directory to `.prettierignore`
mrauhu Jan 7, 2022
d364211
Installed packages: `@types/express` as dev dependency and `@storyboo…
mrauhu Jan 7, 2022
b0fc21d
Created declarations for `@storybook/addon-svelte-csf` files
mrauhu Jan 7, 2022
822cf02
Created types: `EnvsRaw` and `ExtendedOptions`
mrauhu Jan 7, 2022
4355601
Enabled `strict` flag in `packages/storybook-builder-vite/tsconfig.js…
mrauhu Jan 7, 2022
b35cb27
Added types for variables and functions. Created additional types.
mrauhu Jan 7, 2022
47e5f24
Fixed: the `config.build` property may be undefined
mrauhu Jan 7, 2022
ea5f950
Simplify JSDoc
mrauhu Jan 7, 2022
ed88537
Created TODO: Maybe convert `injectExportOrderPlugin` to function tha…
mrauhu Jan 7, 2022
020471c
Fixed rare case if the `envPrefix` is undefined in `stringifyProcessE…
mrauhu Jan 7, 2022
13151cb
Reorder imports in `build.ts` and `vite-server.ts`
mrauhu Jan 7, 2022
23a09e2
Added check for a case if the `StoriesEntry` is object in functions:
mrauhu Jan 7, 2022
1d1360e
Created `listStories()` function and removed duplicated code. TODO: M…
mrauhu Jan 7, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.github
.yarn
storybook-static
dist
1 change: 1 addition & 0 deletions .github/workflows/buildAndPublish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ jobs:
- name: install, build, and test
run: |
yarn install
yarn prepublish
yarn workspaces foreach -p run build-storybook
env:
CI: true
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/buildExamples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,8 @@ jobs:
- name: Install dependencies
run: yarn install

- name: Compile TypeScript
run: yarn prepublish

- name: Build examples
run: yarn workspaces foreach -p run build-storybook
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ node_modules
storybook-static
.idea

# TypeScript
dist

# Yarn stuff
/**/.yarn/*
Expand Down
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.yarn
**/*/storybook-static/*
*.yml
dist
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
"node": ">=16.0.0"
},
"scripts": {
"start": "cd packages/storybook-builder-vite && tsc -w",
"prepublish": "cd packages/storybook-builder-vite && tsc",
"lint": "yarn lint:prettier && yarn lint:eslint",
"lint:prettier": "prettier --write .",
"lint:eslint": "eslint \"packages/*/**/*.{ts,tsx,js,jsx,mjs,cjs}\" --fix",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
const path = require('path');
const { stringifyProcessEnvs, allowedEnvPrefix: envPrefix } = require('./envs');
const { pluginConfig } = require('./vite-config');
const { build: viteBuild } = require('vite');
import * as path from 'path';
import { build as viteBuild } from 'vite';
import { allowedEnvPrefix as envPrefix, stringifyProcessEnvs } from './envs';
import { pluginConfig } from './vite-config';

module.exports.build = async function build(options) {
import type { UserConfig } from 'vite';
import type { EnvsRaw, ExtendedOptions } from './types';

export async function build(options: ExtendedOptions) {
const { presets } = options;

const config = {
Expand All @@ -22,11 +25,11 @@ module.exports.build = async function build(options) {
},
},
plugins: await pluginConfig(options, 'build'),
};
} as UserConfig;

const finalConfig = await presets.apply('viteFinal', config, options);

const envsRaw = await presets.apply('env');
const envsRaw = await presets.apply<Promise<EnvsRaw>>('env');
// Stringify env variables after getting `envPrefix` from the final config
const envs = stringifyProcessEnvs(envsRaw, finalConfig.envPrefix);
// Update `define`
Expand All @@ -36,4 +39,4 @@ module.exports.build = async function build(options) {
};

await viteBuild(finalConfig);
};
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
const fs = require('fs');
const path = require('path');
const { transformIframeHtml } = require('./transform-iframe-html');
const { generateIframeScriptCode } = require('./codegen-iframe-script');
const { generateModernIframeScriptCode } = require('./codegen-modern-iframe-script');
const { generateImportFnScriptCode } = require('./codegen-importfn-script');
import * as fs from 'fs';
import * as path from 'path';
import { transformIframeHtml } from './transform-iframe-html';
import { generateIframeScriptCode } from './codegen-iframe-script';
import { generateModernIframeScriptCode } from './codegen-modern-iframe-script';
import { generateImportFnScriptCode } from './codegen-importfn-script';

module.exports.codeGeneratorPlugin = function codeGeneratorPlugin(options) {
import type { Plugin } from 'vite';
import type { ExtendedOptions } from './types';

export function codeGeneratorPlugin(options: ExtendedOptions): Plugin {
const virtualFileId = '/virtual:/@storybook/builder-vite/vite-app.js';
const virtualStoriesFile = '/virtual:/@storybook/builder-vite/storybook-stories.js';
const iframePath = path.resolve(__dirname, 'input', 'iframe.html');
let iframeId;
const iframePath = path.resolve(__dirname, '..', 'input', 'iframe.html');
let iframeId: string;

// noinspection JSUnusedGlobalSymbols
return {
Expand All @@ -18,7 +21,7 @@ module.exports.codeGeneratorPlugin = function codeGeneratorPlugin(options) {
configureServer(server) {
// invalidate the whole vite-app.js script on every file change.
// (this might be a little too aggressive?)
server.watcher.on('change', () => {
server.watcher.on('change', (_e) => {
const { moduleGraph } = server;
const appModule = moduleGraph.getModuleById(virtualFileId);
if (appModule) {
Expand All @@ -36,6 +39,9 @@ module.exports.codeGeneratorPlugin = function codeGeneratorPlugin(options) {
// to serve iframe.html. The reason is that Vite's dev server (at the time of writing)
// does not support virtual files as entry points.
if (command === 'build') {
if (!config.build) {
config.build = {};
}
config.build.rollupOptions = {
input: iframePath,
};
Expand Down Expand Up @@ -67,7 +73,7 @@ module.exports.codeGeneratorPlugin = function codeGeneratorPlugin(options) {
}

if (id === iframeId) {
return fs.readFileSync(path.resolve(__dirname, 'input', 'iframe.html'), 'utf-8');
return fs.readFileSync(path.resolve(__dirname, '..', 'input', 'iframe.html'), 'utf-8');
}
},
async transformIndexHtml(html, ctx) {
Expand All @@ -77,4 +83,4 @@ module.exports.codeGeneratorPlugin = function codeGeneratorPlugin(options) {
return transformIframeHtml(html, options);
},
};
};
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
const path = require('path');
const glob = require('glob-promise');
const { normalizePath } = require('vite');
const { loadPreviewOrConfigFile } = require('@storybook/core-common');
import { loadPreviewOrConfigFile } from '@storybook/core-common';
import { normalizePath } from 'vite';
import { listStories } from './list-stories';

import type { ExtendedOptions } from './types';

// This is somewhat of a hack; the problem is that previewEntries resolves to
// the CommonJS imports, probably because require.resolve in Node.js land leads
// to that. For Vite, we need the ESM modules.
function replaceCJStoESMPath(entryPath) {
function replaceCJStoESMPath(entryPath: string) {
return entryPath.replace('/cjs/', '/esm/');
}

module.exports.generateIframeScriptCode = async function generateIframeScriptCode(options) {
export async function generateIframeScriptCode(options: ExtendedOptions) {
const { presets, configDir, framework, frameworkPath } = options;
const previewEntries = (await presets.apply('previewEntries', [], options)).map(replaceCJStoESMPath);

Expand All @@ -22,16 +23,12 @@ module.exports.generateIframeScriptCode = async function generateIframeScriptCod
const presetEntries = await presets.apply('config', [], options);
const configEntries = [...presetEntries, previewOrConfigFile].filter(Boolean);

const storyEntries = (
await Promise.all(
(await presets.apply('stories')).map((g) => glob(path.isAbsolute(g) ? g : path.join(configDir, g)))
)
).reduce((carry, stories) => carry.concat(stories), []);
const storyEntries = await listStories(options);

const absoluteFilesToImport = (files, name) =>
const absoluteFilesToImport = (files: string[], name: string) =>
files.map((el, i) => `import ${name ? `* as ${name}_${i} from ` : ''}'/@fs/${normalizePath(el)}'`).join('\n');

const importArray = (name, length) =>
const importArray = (name: string, length: number) =>
`[${new Array(length)
.fill(0)
.map((_, i) => `${name}_${i}`)
Expand Down Expand Up @@ -109,4 +106,4 @@ module.exports.generateIframeScriptCode = async function generateIframeScriptCod
)}.filter(el => el.default), { hot: import.meta.hot }, false); // not sure if the import.meta.hot thing is correct
`.trim();
return code;
};
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
const glob = require('glob-promise');
const path = require('path');
const { normalizePath } = require('vite');
import * as path from 'path';
import { normalizePath } from 'vite';
import { listStories } from './list-stories';

import type { Options } from '@storybook/core-common';

/**
* This file is largely based on https://github.com/storybookjs/storybook/blob/d1195cbd0c61687f1720fefdb772e2f490a46584/lib/core-common/src/utils/to-importFn.ts
Expand All @@ -10,11 +12,8 @@ const { normalizePath } = require('vite');
* Paths get passed either with no leading './' - e.g. `src/Foo.stories.js`,
* or with a leading `../` (etc), e.g. `../src/Foo.stories.js`.
* We want to deal in importPaths relative to the working dir, so we normalize
*
* @param {string} relativePath
* @returns {string}
*/
function toImportPath(relativePath) {
function toImportPath(relativePath: string) {
return relativePath.startsWith('../') ? relativePath : `./${relativePath}`;
}

Expand All @@ -24,9 +23,8 @@ function toImportPath(relativePath) {
* to delay loading. It then creates a function, `importFn(path)`, which resolves a path to an import
* function and this is called by Storybook to fetch a story dynamically when needed.
* @param stories An array of absolute story paths.
* @returns {Promise<string>}
*/
async function toImportFn(stories) {
async function toImportFn(stories: string[]) {
const objectEntries = stories.map((file) => {
return ` '${toImportPath(normalizePath(path.relative(process.cwd(), file)))}': async () => import('/@fs/${file}')`;
});
Expand All @@ -42,16 +40,10 @@ async function toImportFn(stories) {
`;
}

module.exports.generateImportFnScriptCode = async function generateImportFnScriptCode(options) {
export async function generateImportFnScriptCode(options: Options) {
// First we need to get an array of stories and their absolute paths.
const stories = (
await Promise.all(
(
await options.presets.apply('stories', [], options)
).map((storyEntry) => glob(path.isAbsolute(storyEntry) ? storyEntry : path.join(options.configDir, storyEntry)))
)
).reduce((carry, stories) => carry.concat(stories), []);
const stories = await listStories(options);

// We can then call toImportFn to create a function that can be used to load each story dynamically.
return (await toImportFn(stories)).trim();
};
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
const { loadPreviewOrConfigFile } = require('@storybook/core-common');
const { normalizePath } = require('vite');
import { loadPreviewOrConfigFile } from '@storybook/core-common';
import { normalizePath } from 'vite';

module.exports.generateModernIframeScriptCode = async function generateModernIframeScriptCode(
options,
{ storiesFilename }
import type { ExtendedOptions } from './types';

export interface GenerateModernIframeScriptCodeOptions {
storiesFilename: string;
}

export async function generateModernIframeScriptCode(
options: ExtendedOptions,
{ storiesFilename }: GenerateModernIframeScriptCodeOptions
) {
const { presets, configDir } = options;

Expand Down Expand Up @@ -76,4 +82,4 @@ module.exports.generateModernIframeScriptCode = async function generateModernIfr
}
`.trim();
return code;
};
}
18 changes: 18 additions & 0 deletions packages/storybook-builder-vite/declarations/extract-stories.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* @see https://github.com/storybookjs/addon-svelte-csf/blob/f72b8f28dabbb99c92e12d0170d3c1db4397ee7c/src/parser/extract-stories.ts
*/
declare module '@storybook/addon-svelte-csf/dist/cjs/parser/extract-stories' {
interface StoryDef {
name: string;
template: boolean;
source: string;
hasArgs: boolean;
}

interface StoriesDef {
stories: Record<string, StoryDef>;
allocatedIds: string[];
}

function extractStories(component: string): { stories: StoriesDef; allocatedIds: string[] };
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* @see https://github.com/storybookjs/addon-svelte-csf/blob/f72b8f28dabbb99c92e12d0170d3c1db4397ee7c/src/parser/svelte-stories-loader.ts
* @see https://github.com/sveltejs/svelte/blob/deed340cf5d9c278f9a0605297ad6e4a3a1579d9/src/compiler/compile/utils/get_name_from_filename.ts
*/
declare module '@storybook/addon-svelte-csf/dist/cjs/parser/svelte-stories-loader' {
function getNameFromFilename(filename: string): string;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
const { stringifyEnvs } = require('@storybook/core-common');
import { stringifyEnvs } from '@storybook/core-common';

import type { EnvsRaw } from './types';
import type { UserConfig } from 'vite';

// Allowed env variables on the client
const allowedEnvVariables = [
Expand All @@ -13,23 +16,21 @@ const allowedEnvVariables = [
];

// Env variables starts with env prefix will be exposed to your client source code via `import.meta.env`
module.exports.allowedEnvPrefix = ['VITE_', 'STORYBOOK_'];
export const allowedEnvPrefix = ['VITE_', 'STORYBOOK_'];

/**
* Customized version of stringifyProcessEnvs from @storybook/core-common which
* uses import.meta.env instead of process.env and checks for allowed variables.
* @param {Object<string, string>} raw
* @param {string[]|string} envPrefix
*/
module.exports.stringifyProcessEnvs = function stringifyProcessEnvs(raw, envPrefix) {
const updatedRaw = {};
export function stringifyProcessEnvs(raw: EnvsRaw, envPrefix: UserConfig['envPrefix']) {
const updatedRaw: EnvsRaw = {};
const envs = Object.entries(raw).reduce(
(acc, [key, value]) => {
(acc: EnvsRaw, [key, value]) => {
// Only add allowed values OR values from array OR string started with allowed prefixes
if (
allowedEnvVariables.includes(key) ||
(Array.isArray(envPrefix) && !!envPrefix.find((prefix) => key.startsWith(prefix))) ||
key.startsWith(envPrefix)
(typeof envPrefix === 'string' && key.startsWith(envPrefix))
) {
acc[`import.meta.env.${key}`] = JSON.stringify(value);
updatedRaw[key] = value;
Expand All @@ -46,4 +47,4 @@ module.exports.stringifyProcessEnvs = function stringifyProcessEnvs(raw, envPref
envs['import.meta.env'] = JSON.stringify(stringifyEnvs(updatedRaw));

return envs;
};
}
Loading