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

CLI: Add support for Nuxt to project init #26884

Merged
merged 10 commits into from
Jun 24, 2024
3 changes: 2 additions & 1 deletion code/lib/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@
],
"scripts": {
"check": "node --loader ../../../scripts/node_modules/esbuild-register/loader.js -r ../../../scripts/node_modules/esbuild-register/register.js ../../../scripts/prepare/check.ts",
"prep": "node --loader ../../../scripts/node_modules/esbuild-register/loader.js -r ../../../scripts/node_modules/esbuild-register/register.js ../../../scripts/prepare/bundle.ts"
"prep": "node --loader ../../../scripts/node_modules/esbuild-register/loader.js -r ../../../scripts/node_modules/esbuild-register/register.js ../../../scripts/prepare/bundle.ts",
"sb": "node ./bin/index.js"
},
"dependencies": {
"@babel/core": "^7.24.4",
Expand Down
32 changes: 22 additions & 10 deletions code/lib/cli/src/detect.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,28 @@ const MOCK_FRAMEWORK_FILES: {
},
},
},
{
name: ProjectType.NUXT,
files: {
'package.json': {
dependencies: {
nuxt: '^3.11.2',
},
},
},
},
{
name: ProjectType.NUXT,
files: {
'package.json': {
dependencies: {
// Nuxt projects may have Vue 3 as an explicit dependency
nuxt: '^3.11.2',
vue: '^3.0.0',
},
},
},
},
{
name: ProjectType.VUE3,
files: {
Expand Down Expand Up @@ -433,16 +455,6 @@ describe('Detect', () => {
expect(result).toBe(ProjectType.UNDETECTED);
});

// TODO(blaine): Remove once Nuxt3 is supported
it(`UNSUPPORTED for Nuxt framework above version 3.0.0`, () => {
const result = detectFrameworkPreset({
dependencies: {
nuxt: '3.0.0',
},
});
expect(result).toBe(ProjectType.UNSUPPORTED);
});

// TODO: The mocking in this test causes tests after it to fail
it('REACT_SCRIPTS for custom react scripts config', () => {
const forkedReactScriptsConfig = {
Expand Down
15 changes: 14 additions & 1 deletion code/lib/cli/src/detect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,11 @@ export async function detectBuilder(packageManager: JsPackageManager, projectTyp
}

// REWORK
if (webpackConfig || (dependencies.webpack && dependencies.vite !== undefined)) {
if (
webpackConfig ||
((dependencies.webpack || dependencies['@nuxt/webpack-builder']) &&
dependencies.vite !== undefined)
) {
commandLog('Detected webpack project. Setting builder to webpack')();
return CoreBuilder.Webpack5;
}
Expand All @@ -133,6 +137,8 @@ export async function detectBuilder(packageManager: JsPackageManager, projectTyp
case ProjectType.NEXTJS:
case ProjectType.EMBER:
return CoreBuilder.Webpack5;
case ProjectType.NUXT:
return CoreBuilder.Vite;
default:
const { builder } = await prompts(
{
Expand Down Expand Up @@ -202,6 +208,13 @@ export async function detectLanguage(packageManager: JsPackageManager) {
} else if (semver.lt(typescriptVersion, '3.8.0')) {
logger.warn('Detected TypeScript < 3.8, populating with JavaScript examples');
}
} else {
// No direct dependency on TypeScript, but could be a transitive dependency
// This is eg the case for Nuxt projects, which support a recent version of TypeScript
// Check for tsconfig.json (https://www.typescriptlang.org/docs/handbook/tsconfig-json.html)
if (fs.existsSync('tsconfig.json')) {
language = SupportedLanguage.TYPESCRIPT_4_9;
}
}

return language;
Expand Down
32 changes: 32 additions & 0 deletions code/lib/cli/src/generators/NUXT/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { baseGenerator } from '../baseGenerator';
import type { Generator } from '../types';

const generator: Generator = async (packageManager, npmOptions, options) => {
await baseGenerator(
packageManager,
npmOptions,
options,
'nuxt',
{
extraPackages: async ({ builder }) => {
return ['@nuxtjs/storybook'];
},
installStorybookPackage: false,
kasperpeulen marked this conversation as resolved.
Show resolved Hide resolved
installFrameworkPackages: false,
componentsDestinationPath: './components',
extraMain: {
stories: ['../components/**/*.mdx', '../components/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
},
},
'nuxt'
);
// Add nuxtjs/storybook to nuxt.config.js
await packageManager.runPackageCommand('nuxi', [
'module',
'add',
'@nuxtjs/storybook',
'--skipInstall',
]);
};

export default generator;
28 changes: 19 additions & 9 deletions code/lib/cli/src/generators/baseGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,16 @@ const defaultOptions: FrameworkOptions = {
staticDir: undefined,
addScripts: true,
addMainFile: true,
addPreviewFile: true,
kasperpeulen marked this conversation as resolved.
Show resolved Hide resolved
addComponents: true,
webpackCompiler: () => undefined,
extraMain: undefined,
framework: undefined,
extensions: undefined,
componentsDestinationPath: undefined,
storybookConfigFolder: '.storybook',
installStorybookPackage: true,
installFrameworkPackages: true,
};

const getBuilderDetails = (builder: string) => {
Expand Down Expand Up @@ -202,12 +205,15 @@ export async function baseGenerator(
staticDir,
addScripts,
addMainFile,
addPreviewFile,
addComponents,
extraMain,
extensions,
storybookConfigFolder,
componentsDestinationPath,
webpackCompiler,
installStorybookPackage,
installFrameworkPackages,
} = {
...defaultOptions,
...options,
Expand Down Expand Up @@ -279,9 +285,9 @@ export async function baseGenerator(
: extraPackages;

const allPackages = [
'storybook',
installStorybookPackage ? 'storybook' : undefined,
getExternalFramework(rendererId) ? undefined : `@storybook/${rendererId}`,
...frameworkPackages,
...(installFrameworkPackages ? frameworkPackages : []),
Comment on lines +288 to +290
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kasperpeulen I will need to roll-back this optional installation of the storybook package to ensure this #28310 works.

I'm going to just proceed, but I'd like to evaluate with you on what to do about this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tobiasdiez said that it works a bit different in the nuxt world

The setup with nuxt is a bit different. In the nuxt world, you normally install a so-called module. The task of the module is to interface with whatever external library is used under the hood, as well as shipping the library (i.e. it is declared as a dependency).

For example, the tailwind module for nuxt (https://github.com/nuxt-modules/tailwindcss) declares tailwindcss as a dependency (https://github.com/nuxt-modules/tailwindcss/blob/main/package.json#L56). Similarly, in the storybook module we already pull-in storybook as the dependency: https://github.com/nuxt-modules/storybook/blob/30cb7036f1bfc5bc50b11b6b7c3857bcf23886b6/package.json#L81 Thus users don't need to add the storybook packages themselves in their package.json.

Not sure how that would affect that in 8.2 storybook is required as a peer dep though. I guess we should test it to find out.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't be a problem for us. The nuxt module can pull-in whatever top-level storybook dependency is appropriate. In fact, we do this already for the storybook package: https://github.com/nuxt-modules/storybook/blob/d1bd15a990851bbcee53822b09acd046a657fd22/packages/nuxt-module/package.json#L50

@ndelangen what issues do you expect?

...addonPackages,
...(extraPackagesToInstall || []),
].filter(Boolean);
Expand Down Expand Up @@ -323,7 +329,9 @@ export async function baseGenerator(
addDependenciesSpinner.succeed();
}

await fse.ensureDir(`./${storybookConfigFolder}`);
if (addMainFile || addPreviewFile) {
await fse.ensureDir(`./${storybookConfigFolder}`);
}

if (addMainFile) {
const prefixes = shouldApplyRequireWrapperOnPackageNames
Expand Down Expand Up @@ -371,12 +379,14 @@ export async function baseGenerator(
});
}

await configurePreview({
frameworkPreviewParts,
storybookConfigFolder: storybookConfigFolder as string,
language,
rendererId,
});
if (addPreviewFile) {
await configurePreview({
frameworkPreviewParts,
storybookConfigFolder: storybookConfigFolder as string,
language,
rendererId,
});
}

if (addScripts) {
await packageManager.addStorybookCommandInScripts({
Expand Down
3 changes: 3 additions & 0 deletions code/lib/cli/src/generators/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,16 @@ export interface FrameworkOptions {
staticDir?: string;
addScripts?: boolean;
addMainFile?: boolean;
addPreviewFile?: boolean;
addComponents?: boolean;
webpackCompiler?: ({ builder }: { builder: Builder }) => 'babel' | 'swc' | undefined;
extraMain?: any;
extensions?: string[];
framework?: Record<string, any>;
storybookConfigFolder?: string;
componentsDestinationPath?: string;
installStorybookPackage?: boolean;
installFrameworkPackages?: boolean;
}

export type Generator<T = void> = (
Expand Down
1 change: 1 addition & 0 deletions code/lib/cli/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ export const frameworkToDefaultBuilder: Record<SupportedFrameworks, CoreBuilder>
'html-vite': CoreBuilder.Vite,
'html-webpack5': CoreBuilder.Webpack5,
nextjs: CoreBuilder.Webpack5,
nuxt: CoreBuilder.Vite,
'preact-vite': CoreBuilder.Vite,
'preact-webpack5': CoreBuilder.Webpack5,
qwik: CoreBuilder.Vite,
Expand Down
6 changes: 6 additions & 0 deletions code/lib/cli/src/initiate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import reactNativeGenerator from './generators/REACT_NATIVE';
import reactScriptsGenerator from './generators/REACT_SCRIPTS';
import nextjsGenerator from './generators/NEXTJS';
import vue3Generator from './generators/VUE3';
import nuxtGenerator from './generators/NUXT';
import webpackReactGenerator from './generators/WEBPACK_REACT';
import htmlGenerator from './generators/HTML';
import webComponentsGenerator from './generators/WEB-COMPONENTS';
Expand Down Expand Up @@ -109,6 +110,11 @@ const installStorybook = async <Project extends ProjectType>(
commandLog('Adding Storybook support to your "Vue 3" app')
);

case ProjectType.NUXT:
return nuxtGenerator(packageManager, npmOptions, generatorOptions).then(
commandLog('Adding Storybook support to your "Nuxt" app')
);

case ProjectType.ANGULAR:
commandLog('Adding Storybook support to your "Angular" app');
return angularGenerator(packageManager, npmOptions, generatorOptions, options);
Expand Down
14 changes: 10 additions & 4 deletions code/lib/cli/src/project_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export type ExternalFramework = {
export const externalFrameworks: ExternalFramework[] = [
{ name: 'qwik', packageName: 'storybook-framework-qwik' },
{ name: 'solid', frameworks: ['storybook-solidjs-vite'], renderer: 'storybook-solidjs' },
{ name: 'nuxt', packageName: '@storybook-vue/nuxt' },
];

/**
Expand Down Expand Up @@ -52,6 +53,7 @@ export enum ProjectType {
WEBPACK_REACT = 'WEBPACK_REACT',
NEXTJS = 'NEXTJS',
VUE3 = 'VUE3',
NUXT = 'NUXT',
ANGULAR = 'ANGULAR',
EMBER = 'EMBER',
WEB_COMPONENTS = 'WEB_COMPONENTS',
Expand Down Expand Up @@ -117,6 +119,13 @@ export type TemplateConfiguration = {
* therefore WEBPACK_REACT has to come first, as it's more specific.
*/
export const supportedTemplates: TemplateConfiguration[] = [
{
preset: ProjectType.NUXT,
dependencies: ['nuxt'],
matcherFunction: ({ dependencies }) => {
return dependencies?.every(Boolean) ?? true;
},
},
{
preset: ProjectType.VUE3,
dependencies: {
Expand Down Expand Up @@ -238,10 +247,7 @@ export const supportedTemplates: TemplateConfiguration[] = [
// users an "Unsupported framework" message
export const unsupportedTemplate: TemplateConfiguration = {
preset: ProjectType.UNSUPPORTED,
dependencies: {
// TODO(blaine): Remove when we support Nuxt 3
nuxt: (versionRange) => eqMajor(versionRange, 3),
},
dependencies: {},
matcherFunction: ({ dependencies }) => {
return dependencies?.some(Boolean) ?? false;
},
Expand Down
1 change: 1 addition & 0 deletions code/lib/core-common/src/utils/framework-to-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const frameworkToRenderer: Record<
sveltekit: 'svelte',
'vue3-vite': 'vue3',
'vue3-webpack5': 'vue3',
nuxt: 'vue3',
'web-components-vite': 'web-components',
'web-components-webpack5': 'web-components',
// renderers
Expand Down
2 changes: 1 addition & 1 deletion code/lib/types/scripts/generate-available-frameworks.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import path from 'node:path';
import prettier from 'prettier';
import dedent from 'ts-dedent';

const thirdPartyFrameworks = ['qwik', 'solid'];
const thirdPartyFrameworks = ['qwik', 'solid', 'nuxt'];

const run = async () => {
const frameworks = await readdir(path.join(__dirname, '..', '..', '..', 'frameworks'));
Expand Down
3 changes: 2 additions & 1 deletion code/lib/types/src/modules/frameworks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ export type SupportedFrameworks =
| 'web-components-vite'
| 'web-components-webpack5'
| 'qwik'
| 'solid';
| 'solid'
| 'nuxt';
3 changes: 2 additions & 1 deletion code/lib/types/src/modules/renderers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ export type SupportedRenderers =
| 'html'
| 'web-components'
| 'server'
| 'solid';
| 'solid'
| 'nuxt';