diff --git a/.circleci/config.yml b/.circleci/config.yml index b0c0946ce514..2f62da99515e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -245,7 +245,7 @@ jobs: # Disable ESLint when running smoke tests to improve perf + As of CRA 4.0.3, CRA kitchen sinks are throwing # because of some ESLint warnings, related to: https://github.com/facebook/create-react-app/pull/10590 DISABLE_ESLINT_PLUGIN: 'true' - parallelism: 16 + parallelism: 4 steps: - git-shallow-clone/checkout_advanced: clone_options: '--depth 1 --verbose' diff --git a/code/addons/storyshots/storyshots-core/src/frameworks/configure.ts b/code/addons/storyshots/storyshots-core/src/frameworks/configure.ts index ad64cdd91d69..eef5a9767cc5 100644 --- a/code/addons/storyshots/storyshots-core/src/frameworks/configure.ts +++ b/code/addons/storyshots/storyshots-core/src/frameworks/configure.ts @@ -112,7 +112,7 @@ function configure( })); if (preview) { - // This is essentially the same code as lib/core/src/server/preview/virtualModuleEntry.template + // This is essentially the same code as lib/builder-webpack5/templates/virtualModuleEntry.template const { parameters, decorators, diff --git a/code/addons/storyshots/storyshots-core/stories/storyshot.async.test.js b/code/addons/storyshots/storyshots-core/stories/storyshot.async.test.js index d1afd7ccc6c9..a4f13e34ddc6 100644 --- a/code/addons/storyshots/storyshots-core/stories/storyshot.async.test.js +++ b/code/addons/storyshots/storyshots-core/stories/storyshot.async.test.js @@ -1,7 +1,7 @@ import path from 'path'; import { render, screen, waitFor } from '@testing-library/react'; import initStoryshots, { Stories2SnapsConverter } from '../src'; -import { TIMEOUT, EXPECTED_VALUE } from './exported_metadata/Async.stories.jsx'; +import { EXPECTED_VALUE } from './exported_metadata/Async.stories.jsx'; initStoryshots({ asyncJest: true, @@ -11,7 +11,7 @@ initStoryshots({ // When async is true we need to provide a test method that // calls done() when at the end of the test method - test: ({ story, context, done }) => { + test: async ({ story, context, done }) => { expect(done).toBeDefined(); // This is a storyOf Async (see ./required_with_context/Async.stories) @@ -26,16 +26,13 @@ initStoryshots({ // The Async component should not contain the expected value expect(screen.queryByText(EXPECTED_VALUE)).toBeFalsy(); - // wait until the "Async" component is updated - setTimeout(async () => { - await waitFor(() => { - expect(screen.getByText(EXPECTED_VALUE)).toBeInTheDocument(); - expect(container.firstChild).toMatchSpecificSnapshot(snapshotFilename); - }); + await waitFor(() => { + expect(screen.getByText(EXPECTED_VALUE)).toBeInTheDocument(); + expect(container.firstChild).toMatchSpecificSnapshot(snapshotFilename); + }); - // finally mark test as done - done(); - }, TIMEOUT); + // finally mark test as done + done(); } else { // If not async, mark the test as done done(); diff --git a/code/addons/storysource/package.json b/code/addons/storysource/package.json index 15a86e11eb0b..e710cf788e79 100644 --- a/code/addons/storysource/package.json +++ b/code/addons/storysource/package.json @@ -81,6 +81,9 @@ "optional": true } }, + "publishConfig": { + "access": "public" + }, "bundler": { "entries": [ "./src/index.ts", @@ -88,9 +91,6 @@ "./src/preset.ts" ] }, - "publishConfig": { - "access": "public" - }, "gitHead": "47386bd49d141ea70daac41ab3e4d52749fc5da9", "storybook": { "displayName": "Storysource", diff --git a/code/jest.config.js b/code/jest.config.js index b62abe214d68..46853dd1ed08 100644 --- a/code/jest.config.js +++ b/code/jest.config.js @@ -24,15 +24,7 @@ module.exports = { // 'babel-runtime/core-js/object/assign' 'core-js/library/fn/object/assign': 'core-js/es/object/assign', }, - projects: [ - '', - // '/app/angular', - // '/examples/svelte-kitchen-sink', - // '/examples/vue-kitchen-sink', - // This is explicitly commented out because having vue 2 & 3 in the - // dependency graph makes it impossible to run storyshots on both examples - // '/examples/vue-3-cli', - ], + projects: [''], roots: ['/addons', '/frameworks', '/lib', '/renderers'], transform: { '^.+\\.stories\\.[jt]sx?$': '@storybook/addon-storyshots/injectFileName', @@ -46,6 +38,7 @@ module.exports = { '/node_modules/', '/dist/', '/prebuilt/', + '/template/', 'addon-jest.test.js', '/frameworks/angular/*', '/examples/*/src/*.*', @@ -54,9 +47,10 @@ module.exports = { ], collectCoverage: false, collectCoverageFrom: [ - 'frameworks/**/*.{js,jsx,ts,tsx}', - 'lib/**/*.{js,jsx,ts,tsx}', - 'addons/**/*.{js,jsx,ts,tsx}', + 'frameworks/*/src/**/*.{js,jsx,ts,tsx}', + 'lib/*/src/**/*.{js,jsx,ts,tsx}', + 'renderers/*/src/**/*.{js,jsx,ts,tsx}', + 'addons/*/src/**/*.{js,jsx,ts,tsx}', ], coveragePathIgnorePatterns: [ '/node_modules/', @@ -64,6 +58,7 @@ module.exports = { '/dist/', '/prebuilt/', '/generators/', + '/template/', '/dll/', '/__mocks__ /', '/__mockdata__/', @@ -87,7 +82,12 @@ module.exports = { testEnvironment: 'jest-environment-jsdom-thirteen', setupFiles: ['raf/polyfill'], testURL: 'http://localhost', - modulePathIgnorePatterns: ['/dist/.*/__mocks__/', '/storybook-static/'], + modulePathIgnorePatterns: [ + // + '/dist/.*/__mocks__/', + '/storybook-static/', + '/template/', + ], moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx', 'json', 'node'], watchPlugins: ['jest-watch-typeahead/filename', 'jest-watch-typeahead/testname'], reporters: ['default', 'jest-junit'], diff --git a/code/lib/builder-manager/src/index.ts b/code/lib/builder-manager/src/index.ts index 10f4e5c5e209..547e4b55240a 100644 --- a/code/lib/builder-manager/src/index.ts +++ b/code/lib/builder-manager/src/index.ts @@ -1,5 +1,5 @@ import { dirname, join } from 'path'; -import { copy, writeFile, remove } from 'fs-extra'; +import fs from 'fs-extra'; import express from 'express'; import { logger } from '@storybook/node-logger'; @@ -111,7 +111,7 @@ const starter: StarterFunction = async function* starterGeneratorFn({ // make sure we clear output directory of addons dir before starting // this could cause caching issues where addons are loaded when they shouldn't const addonsDir = config.outdir; - await remove(addonsDir); + await fs.remove(addonsDir); yield; @@ -191,7 +191,7 @@ const builder: BuilderFunction = async function* builderGeneratorFn({ startTime, yield; - const managerFiles = copy(coreDirOrigin, coreDirTarget); + const managerFiles = fs.copy(coreDirOrigin, coreDirTarget); const { cssFiles, jsFiles } = await readOrderedFiles(addonsDir, compilation?.outputFiles); yield; @@ -211,7 +211,7 @@ const builder: BuilderFunction = async function* builderGeneratorFn({ startTime, await Promise.all([ // - writeFile(join(options.outputDir, 'index.html'), html), + fs.writeFile(join(options.outputDir, 'index.html'), html), managerFiles, ]); diff --git a/code/lib/builder-manager/src/utils/files.ts b/code/lib/builder-manager/src/utils/files.ts index 0b7fd8fedba0..8c1672608b1e 100644 --- a/code/lib/builder-manager/src/utils/files.ts +++ b/code/lib/builder-manager/src/utils/files.ts @@ -1,5 +1,5 @@ import { OutputFile } from 'esbuild'; -import { writeFile, ensureFile } from 'fs-extra'; +import fs from 'fs-extra'; import { join } from 'path'; import { Compilation } from '../types'; @@ -12,7 +12,8 @@ export async function readOrderedFiles( // convert deeply nested paths to a single level, also remove special characters const { location, url } = sanitizePath(file, addonsDir); - await ensureFile(location).then(() => writeFile(location, file.contents)); + await fs.ensureFile(location); + await fs.writeFile(location, file.contents); return url; }) || [] ); diff --git a/code/lib/builder-manager/src/utils/template.ts b/code/lib/builder-manager/src/utils/template.ts index b9d619ee701e..6917c1fdafc4 100644 --- a/code/lib/builder-manager/src/utils/template.ts +++ b/code/lib/builder-manager/src/utils/template.ts @@ -1,5 +1,5 @@ import path, { dirname, join } from 'path'; -import { readFile, pathExists } from 'fs-extra'; +import fs from 'fs-extra'; import { render } from 'ejs'; @@ -19,21 +19,21 @@ export const getTemplatePath = async (template: string) => { export const readTemplate = async (template: string) => { const path = await getTemplatePath(template); - return readFile(path, 'utf8'); + return fs.readFile(path, 'utf8'); }; export async function getManagerHeadTemplate( configDirPath: string, interpolations: Record ) { - const head = await pathExists(path.resolve(configDirPath, 'manager-head.html')).then< - Promise | false - >((exists) => { - if (exists) { - return readFile(path.resolve(configDirPath, 'manager-head.html'), 'utf8'); - } - return false; - }); + const head = await fs + .pathExists(path.resolve(configDirPath, 'manager-head.html')) + .then | false>((exists) => { + if (exists) { + return fs.readFile(path.resolve(configDirPath, 'manager-head.html'), 'utf8'); + } + return false; + }); if (!head) { return ''; @@ -76,6 +76,6 @@ export const renderHTML = async ( PREVIEW_URL: JSON.stringify(previewUrl, null, 2), // global preview URL SERVER_CHANNEL_URL: JSON.stringify(serverChannelUrl, null, 2), }, - head: customHeadRef ? await readFile(customHeadRef, 'utf8') : '', + head: customHeadRef ? await fs.readFile(customHeadRef, 'utf8') : '', }); }; diff --git a/code/lib/builder-webpack5/README.md b/code/lib/builder-webpack5/README.md index d4ee12a0c36d..a5ee5d258da9 100644 --- a/code/lib/builder-webpack5/README.md +++ b/code/lib/builder-webpack5/README.md @@ -2,8 +2,6 @@ Builder implemented with `webpack5` and `webpack5`-compatible loaders/plugins/config, used by `@storybook/core-server` to build the preview iframe. -`webpack4` is the default. To configure your Storybook to run `webpack5`, install `@storybook/manager-webpack5` and `@storybook/builder-webpack5` as dev dependencies then update your `.storybook/main.js` configuration. - ```js module.exports = { core: { diff --git a/code/lib/builder-webpack5/package.json b/code/lib/builder-webpack5/package.json index 97c2be9ee929..9aae0376ca0c 100644 --- a/code/lib/builder-webpack5/package.json +++ b/code/lib/builder-webpack5/package.json @@ -19,9 +19,28 @@ "url": "https://opencollective.com/storybook" }, "license": "MIT", - "main": "dist/cjs/index.js", - "module": "dist/esm/index.js", - "types": "dist/types/index.d.ts", + "exports": { + ".": { + "require": "./dist/index.js", + "import": "./dist/index.mjs", + "types": "./dist/index.d.ts" + }, + "./presets/custom-webpack-preset.js": { + "require": "./presets/custom-webpack-preset.js", + "import": "./presets/custom-webpack-preset.mjs", + "types": "./presets/custom-webpack-preset.d.ts" + }, + "./presets/preview-preset.js": { + "require": "./presets/preview-preset.js", + "import": "./presets/preview-preset.mjs", + "types": "./presets/preview-preset.d.ts" + }, + "./templates/virtualModuleModernEntry.js.handlebars": "./templates/virtualModuleModernEntry.js.handlebars", + "./package.json": "./package.json" + }, + "main": "dist/index.js", + "module": "dist/index.mjs", + "types": "dist/index.d.ts", "files": [ "dist/**/*", "templates/**/*", @@ -30,7 +49,7 @@ ], "scripts": { "check": "../../../scripts/node_modules/.bin/tsc --noEmit", - "prep": "node ../../../scripts/prepare.js" + "prep": "../../../scripts/prepare/bundle.ts" }, "dependencies": { "@babel/core": "^7.12.10", @@ -93,5 +112,13 @@ "publishConfig": { "access": "public" }, + "bundler": { + "entries": [ + "./src/index.ts", + "./src/presets/custom-webpack-preset.ts", + "./src/presets/preview-preset.ts" + ], + "platform": "node" + }, "gitHead": "47386bd49d141ea70daac41ab3e4d52749fc5da9" } diff --git a/code/lib/builder-webpack5/src/index.ts b/code/lib/builder-webpack5/src/index.ts index 8eb3e4b3e19b..b32e9870ace0 100644 --- a/code/lib/builder-webpack5/src/index.ts +++ b/code/lib/builder-webpack5/src/index.ts @@ -5,6 +5,7 @@ import { logger } from '@storybook/node-logger'; import { useProgressReporting } from '@storybook/core-common'; import type { Builder, Options } from '@storybook/core-common'; import { checkWebpackVersion } from '@storybook/core-webpack'; +import { join } from 'path'; export * from './types'; @@ -261,5 +262,5 @@ export const build = async (options: BuilderStartOptions) => { return result.value; }; -export const corePresets = [require.resolve('./presets/preview-preset.js')]; -export const overridePresets = [require.resolve('./presets/custom-webpack-preset.js')]; +export const corePresets = [join(__dirname, 'presets/preview-preset.js')]; +export const overridePresets = [join(__dirname, './presets/custom-webpack-preset.js')]; diff --git a/code/lib/builder-webpack5/src/preview/iframe-webpack.config.ts b/code/lib/builder-webpack5/src/preview/iframe-webpack.config.ts index 4e4bbc7077d7..e2f154e61800 100644 --- a/code/lib/builder-webpack5/src/preview/iframe-webpack.config.ts +++ b/code/lib/builder-webpack5/src/preview/iframe-webpack.config.ts @@ -122,7 +122,7 @@ export default async ( entries.push(frameworkInitEntry); const entryTemplate = await readTemplate( - path.join(__dirname, 'virtualModuleEntry.template.js') + path.join(__dirname, '..', '..', 'templates', 'virtualModuleEntry.template.js') ); previewAnnotations.forEach((previewAnnotationFilename: any) => { @@ -143,7 +143,7 @@ export default async ( }); if (stories.length > 0) { const storyTemplate = await readTemplate( - path.join(__dirname, 'virtualModuleStory.template.js') + path.join(__dirname, '..', '..', 'templates', 'virtualModuleStory.template.js') ); // NOTE: this file has a `.cjs` extension as it is a CJS file (from `dist/cjs`) and runs // in the user's webpack mode, which may be strict about the use of require/import. diff --git a/code/lib/builder-webpack5/src/preview/virtualModuleStory.template.js b/code/lib/builder-webpack5/src/preview/virtualModuleStory.template.js deleted file mode 100644 index 3a180df5db28..000000000000 --- a/code/lib/builder-webpack5/src/preview/virtualModuleStory.template.js +++ /dev/null @@ -1,4 +0,0 @@ -/* eslint-disable import/no-unresolved */ -import { configure } from '{{frameworkName}}'; - -configure(['{{stories}}'], module, false); diff --git a/code/lib/builder-webpack5/src/preview/virtualModuleEntry.template.js b/code/lib/builder-webpack5/templates/virtualModuleEntry.template.js similarity index 100% rename from code/lib/builder-webpack5/src/preview/virtualModuleEntry.template.js rename to code/lib/builder-webpack5/templates/virtualModuleEntry.template.js diff --git a/code/lib/builder-webpack5/templates/virtualModuleStory.template.js b/code/lib/builder-webpack5/templates/virtualModuleStory.template.js new file mode 100644 index 000000000000..43e631e4788b --- /dev/null +++ b/code/lib/builder-webpack5/templates/virtualModuleStory.template.js @@ -0,0 +1,3 @@ +const { configure } = require('{{frameworkName}}'); + +configure(['{{stories}}'], module, false); diff --git a/code/lib/builder-webpack5/tsconfig.json b/code/lib/builder-webpack5/tsconfig.json index 314889c338e2..de98a048bdd5 100644 --- a/code/lib/builder-webpack5/tsconfig.json +++ b/code/lib/builder-webpack5/tsconfig.json @@ -4,6 +4,6 @@ "strict": true, "skipLibCheck": true }, - "include": ["src/**/*", "typings.d.ts"], + "include": ["src/**/*", "typings.d.ts", "templates/virtualModuleEntry.template.js"], "exclude": ["src/**.test.ts"] } diff --git a/code/lib/core-common/src/index.ts b/code/lib/core-common/src/index.ts index 8c4df3940980..d0178dd2f3ba 100644 --- a/code/lib/core-common/src/index.ts +++ b/code/lib/core-common/src/index.ts @@ -2,33 +2,33 @@ export * from './presets'; +export * from './utils/cache'; export * from './utils/check-addon-order'; export * from './utils/envs'; +export * from './utils/findDistEsm'; +export * from './utils/get-framework-name'; +export * from './utils/get-storybook-configuration'; +export * from './utils/get-storybook-info'; +export * from './utils/get-storybook-refs'; +export * from './utils/glob-to-regexp'; export * from './utils/handlebars'; +export * from './utils/interpolate'; export * from './utils/interpret-files'; export * from './utils/interpret-require'; export * from './utils/load-custom-presets'; export * from './utils/load-main-config'; -export * from './utils/get-framework-name'; -export * from './utils/get-storybook-configuration'; -export * from './utils/get-storybook-info'; -export * from './utils/get-storybook-refs'; export * from './utils/load-manager-or-addons-file'; export * from './utils/load-preview-or-config-file'; export * from './utils/log-config'; +export * from './utils/normalize-stories'; export * from './utils/paths'; export * from './utils/progress-reporting'; +export * from './utils/readTemplate'; export * from './utils/resolve-path-in-sb-cache'; -export * from './utils/cache'; +export * from './utils/symlinks'; export * from './utils/template'; -export * from './utils/interpolate'; export * from './utils/validate-config'; export * from './utils/validate-configuration-files'; -export * from './utils/glob-to-regexp'; -export * from './utils/normalize-stories'; -export * from './utils/readTemplate'; -export * from './utils/findDistEsm'; -export * from './utils/symlinks'; export * from './types'; diff --git a/code/lib/core-server/src/core-presets.test.ts b/code/lib/core-server/src/core-presets.disabled-test.ts similarity index 92% rename from code/lib/core-server/src/core-presets.test.ts rename to code/lib/core-server/src/core-presets.disabled-test.ts index 5e5fcd1678d9..4c8f22320f2a 100644 --- a/code/lib/core-server/src/core-presets.test.ts +++ b/code/lib/core-server/src/core-presets.disabled-test.ts @@ -1,3 +1,4 @@ +/* eslint-disable import/no-extraneous-dependencies */ import 'jest-specific-snapshot'; import path from 'path'; import { mkdtemp as mkdtempCb } from 'fs'; @@ -26,23 +27,15 @@ const { packageJson } = readUpSync({ cwd: __dirname }); jest.setTimeout(10000); // FIXME: this doesn't work -const skipStoriesJsonPreset = [{ features: { buildStoriesJson: false, storyStoreV7: false } }]; -jest.mock('@storybook/builder-webpack5', () => { +jest.mock('webpack', () => { const value = jest.fn(() => false); - const actualBuilder = jest.requireActual('@storybook/builder-webpack5'); - // MUTATION! we couldn't mock webpack5, so we added a level of indirection instead - actualBuilder.executor.get = () => value; - actualBuilder.overridePresets = [...actualBuilder.overridePresets, skipStoriesJsonPreset]; - return actualBuilder; -}); + const actual = jest.requireActual('webpack'); -jest.mock('@storybook/builder-manager', () => { - const value = jest.fn(); - const actualBuilder = jest.requireActual('@storybook/builder-manager'); - // MUTATION! - actualBuilder.executor.get = () => value; - return actualBuilder; + Object.keys(actual).forEach((key) => { + value[key] = actual[key]; + }); + return value; }); jest.mock('@storybook/telemetry', () => ({ @@ -51,14 +44,15 @@ jest.mock('@storybook/telemetry', () => ({ })); jest.mock('fs-extra', () => ({ copy: jest.fn(() => undefined), - writeFile: jest.fn(() => undefined), - readFile: jest.fn((f) => ''), emptyDir: jest.fn(() => undefined), - ensureDir: jest.fn(() => undefined), - writeJSON: jest.fn(() => undefined), - remove: jest.fn(() => undefined), - readJSON: jest.fn(() => ({})), + ensureDir: jest.fn(() => true), + ensureFile: jest.fn(() => undefined), pathExists: jest.fn(() => true), + readFile: jest.fn((f) => ''), + readJSON: jest.fn(() => ({})), + remove: jest.fn(() => undefined), + writeFile: jest.fn(() => undefined), + writeJSON: jest.fn(() => undefined), })); jest.mock('./utils/StoryIndexGenerator', () => { diff --git a/code/lib/core-server/tsconfig.json b/code/lib/core-server/tsconfig.json index aac3458a077f..63768daf6d49 100644 --- a/code/lib/core-server/tsconfig.json +++ b/code/lib/core-server/tsconfig.json @@ -2,5 +2,5 @@ "extends": "../../tsconfig.json", "compilerOptions": {}, "include": ["src/**/*"], - "exclude": ["src/**/**.test.ts"] + "exclude": ["src/**/**.test.ts", "src/**/**.disabled-test.ts"] }