feat(bundling): vite generators (#13158)

This commit is contained in:
Katerina Skroumpelou 2022-11-21 20:18:40 +02:00 committed by GitHub
parent 91f4635e0c
commit f394608658
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
86 changed files with 3600 additions and 203 deletions

View File

@ -234,9 +234,16 @@
"type": "boolean",
"default": false,
"hidden": true
},
"bundler": {
"description": "The bundler to use.",
"enum": ["vite", "webpack"],
"x-prompt": "Which bundler do you want to use?",
"default": "webpack"
}
},
"required": [],
"examplesFile": "## Examples\n\n{% tabs %}\n{% tab label=\"Simple Application\" %}\n\nCreate an application named `my-app`:\n\n```bash\nnx g @nrwl/react:application my-app\n```\n\n{% /tab %}\n\n{% tab label=\"Application using Vite as bundler\" %}\n\nCreate an application named `my-app`:\n\n```bash\nnx g @nrwl/react:app my-app --bundler=vite\n```\n\n{% /tab %}\n\n{% tab label=\"Specify directory and style extension\" %}\n\nCreate an application named `my-app` in the `my-dir` directory and use `scss` for styles:\n\n```bash\nnx g @nrwl/react:app my-app --directory=my-dir --style=scss\n```\n\n{% /tab %}\n\n{% tab label=\"Add tags\" %}\n\nAdd tags to the application (used for linting).\n\n```bash\nnx g @nrwl/react:app my-app --tags=scope:admin,type:ui\n```\n\n{% /tab %}\n{% /tabs %}\n",
"presets": []
},
"aliases": ["app"],
@ -1056,6 +1063,12 @@
"devServerPort": {
"type": "number",
"description": "The port for the dev server of the remote app."
},
"bundler": {
"description": "The bundler to use.",
"enum": ["vite", "webpack"],
"x-prompt": "Which bundler do you want to use?",
"default": "webpack"
}
},
"required": ["name"],
@ -1217,6 +1230,12 @@
"devServerPort": {
"type": "number",
"description": "The port for the dev server of the remote app."
},
"bundler": {
"description": "The bundler to use.",
"enum": ["vite", "webpack"],
"x-prompt": "Which bundler do you want to use?",
"default": "webpack"
}
},
"required": ["name"],

File diff suppressed because one or more lines are too long

View File

@ -29,7 +29,7 @@
"bundler": {
"type": "string",
"description": "The bundler to use.",
"enum": ["webpack", "none"],
"enum": ["webpack", "none", "vite"],
"default": "webpack"
},
"unitTestRunner": {
@ -119,8 +119,9 @@
"bundler": {
"type": "string",
"description": "The bundler to use.",
"enum": ["webpack", "none"],
"default": "webpack"
"enum": ["webpack", "none", "vite"],
"default": "webpack",
"x-prompt": "Which bundler do you want to use?"
},
"linter": {
"description": "The tool to use for running lint checks.",
@ -160,6 +161,7 @@
}
},
"required": [],
"examplesFile": "## Examples\n\n{% tabs %}\n{% tab label=\"Simple Application\" %}\n\nCreate an application named `my-app`:\n\n```bash\nnx g @nrwl/web:application my-app\n```\n\n{% /tab %}\n\n{% tab label=\"Application using Vite as bundler\" %}\n\nCreate an application named `my-app`:\n\n```bash\nnx g @nrwl/web:app my-app --bundler=vite\n```\n\n{% /tab %}\n\n{% tab label=\"Specify directory\" %}\n\nCreate an application named `my-app` in the `my-dir` directory:\n\n```bash\nnx g @nrwl/web:app my-app --directory=my-dir\n```\n\n{% /tab %}\n\n{% tab label=\"Add tags\" %}\n\nAdd tags to the application (used for linting).\n\n```bash\nnx g @nrwl/web:app my-app --tags=scope:admin,type:ui\n```\n\n{% /tab %}\n{% /tabs %}\n",
"presets": []
},
"aliases": ["app"],

View File

@ -371,7 +371,7 @@
"path": "generated/packages/vite.json",
"schemas": {
"executors": ["dev-server", "build", "test"],
"generators": ["init"]
"generators": ["init", "configuration"]
}
},
{

View File

@ -59,7 +59,29 @@ nx g @nrwl/vite:init
You will notice that the executor will ask you of the framework you are planning to use. This is just to make sure that the right dependencies are installed. You can always install manually any other dependencies you need.
{% /callout %}
## Using Vite.js in your applications
## Generate an application using Vite
You can generate a React or a Web application that uses Vite.js. The `@nrwl/react:app` and `@nrwl/web:app` generators accept the `bundler` option, where you can pass `vite`. This will generate a new application configured to use Vite.js, and it will also install all the necessary dependencies.
To generate a React application using Vite.js, run the following:
```bash
nx g @nrwl/react:app my-app --bundler=vite
```
To generate a Web application using Vite.js, run the following:
```bash
nx g @nrwl/web:app my-app --bundler=vite
```
## Modify an existing React or Web application to use Vite.js
You can use the `@nrwl/vite:configuration` generator to change your React or Web application to use Vite.js. This generator will modify your application's configuration to use Vite.js, and it will also install all the necessary dependencies.
You can read more about this generator on the [`@nrwl/vite:configuration`](/packages/vite/generators/configuration) generator page.
## Set up your apps to use Vite.js manually
You can use the `@nrwl/vite:dev-server` and the `@nrwl/vite:build` executors to serve and build your applications using Vite.js. To do this, you need to make a few adjustments to your application.

View File

@ -16,7 +16,9 @@ describe('React Cypress Component Tests', () => {
beforeAll(() => {
projectName = newProject({ name: uniq('cy-react') });
runCLI(`generate @nrwl/react:app ${appName} --no-interactive`);
runCLI(
`generate @nrwl/react:app ${appName} --bundler=webpack --no-interactive`
);
runCLI(
`generate @nrwl/react:component fancy-cmp --project=${appName} --no-interactive`
);

View File

@ -25,7 +25,9 @@ describe('React Module Federation', () => {
const remote2 = uniq('remote2');
const remote3 = uniq('remote3');
runCLI(`generate @nrwl/react:host ${shell} --style=css --no-interactive`);
runCLI(
`generate @nrwl/react:host ${shell} --bundler=webpack --style=css --no-interactive`
);
runCLI(
`generate @nrwl/react:remote ${remote1} --style=css --host=${shell} --no-interactive`
);

View File

@ -28,7 +28,9 @@ describe('React Applications', () => {
const libName = uniq('lib');
const libWithNoComponents = uniq('lib');
runCLI(`generate @nrwl/react:app ${appName} --style=css --no-interactive`);
runCLI(
`generate @nrwl/react:app ${appName} --style=css --bundler=webpack --no-interactive`
);
runCLI(`generate @nrwl/react:lib ${libName} --style=css --no-interactive`);
runCLI(
`generate @nrwl/react:lib ${libWithNoComponents} --no-interactive --no-component`
@ -63,7 +65,9 @@ describe('React Applications', () => {
it('should generate app with legacy-ie support', async () => {
const appName = uniq('app');
runCLI(`generate @nrwl/react:app ${appName} --style=css --no-interactive`);
runCLI(
`generate @nrwl/react:app ${appName} --style=css --bundler=webpack --no-interactive`
);
// changing browser support of this application
updateFile(`apps/${appName}/.browserslistrc`, `IE 11`);
@ -90,7 +94,9 @@ describe('React Applications', () => {
const appName = uniq('app');
const libName = uniq('lib');
runCLI(`generate @nrwl/react:app ${appName} --no-interactive --js`);
runCLI(
`generate @nrwl/react:app ${appName} --bundler=webpack --no-interactive --js`
);
runCLI(`generate @nrwl/react:lib ${libName} --no-interactive --js`);
const mainPath = `apps/${appName}/src/main.js`;
@ -173,7 +179,7 @@ describe('React Applications: --style option', () => {
`('should support global and css modules', ({ style }) => {
const appName = uniq('app');
runCLI(
`generate @nrwl/react:app ${appName} --style=${style} --no-interactive`
`generate @nrwl/react:app ${appName} --style=${style} --bundler=webpack --no-interactive`
);
// make sure stylePreprocessorOptions works
@ -210,7 +216,9 @@ describe('React Applications: additional packages', () => {
it('should generate app with routing', async () => {
const appName = uniq('app');
runCLI(`generate @nrwl/react:app ${appName} --routing --no-interactive`);
runCLI(
`generate @nrwl/react:app ${appName} --routing --bundler=webpack --no-interactive`
);
runCLI(`build ${appName} --outputHashing none`);
@ -226,7 +234,7 @@ describe('React Applications: additional packages', () => {
const appName = uniq('app');
const libName = uniq('lib');
runCLI(`g @nrwl/react:app ${appName} --no-interactive`);
runCLI(`g @nrwl/react:app ${appName} --bundler=webpack --no-interactive`);
runCLI(`g @nrwl/react:redux lemon --project=${appName}`);
runCLI(`g @nrwl/react:lib ${libName} --no-interactive`);
runCLI(`g @nrwl/react:redux orange --project=${libName}`);
@ -252,7 +260,7 @@ describe('React Applications and Libs with PostCSS', () => {
const appName = uniq('app');
const libName = uniq('lib');
runCLI(`g @nrwl/react:app ${appName} --no-interactive`);
runCLI(`g @nrwl/react:app ${appName} --bundler=webpack --no-interactive`);
runCLI(`g @nrwl/react:lib ${libName} --no-interactive`);
const mainPath = `apps/${appName}/src/main.tsx`;

View File

@ -19,13 +19,14 @@ const myApp = uniq('my-app');
describe('Vite Plugin', () => {
let proj: string;
beforeEach(() => {
proj = newProject();
runCLI(`generate @nrwl/react:app ${myApp}`);
runCLI(`generate @nrwl/vite:init`);
updateFile(
`apps/${myApp}/index.html`,
`
describe('set up new project manually', () => {
beforeEach(() => {
proj = newProject();
runCLI(`generate @nrwl/react:app ${myApp}`);
runCLI(`generate @nrwl/vite:init`);
updateFile(
`apps/${myApp}/index.html`,
`
<!DOCTYPE html>
<html lang="en">
<head>
@ -38,15 +39,15 @@ describe('Vite Plugin', () => {
</head>
<body>
<div id="root"></div>
<script type="module" src="src/main.tsx"></script>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
`
);
);
createFile(
`apps/${myApp}/vite.config.ts`,
`
createFile(
`apps/${myApp}/vite.config.ts`,
`
/// <reference types="vitest" />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
@ -65,11 +66,11 @@ describe('Vite Plugin', () => {
environment: 'jsdom',
}
});`
);
);
updateFile(
`apps/${myApp}/tsconfig.json`,
`
updateFile(
`apps/${myApp}/tsconfig.json`,
`
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
@ -102,56 +103,130 @@ describe('Vite Plugin', () => {
]
}
`
);
);
updateProjectConfig(myApp, (config) => {
config.targets.build.executor = '@nrwl/vite:build';
config.targets.serve.executor = '@nrwl/vite:dev-server';
config.targets.test.executor = '@nrwl/vite:test';
updateProjectConfig(myApp, (config) => {
config.targets.build.executor = '@nrwl/vite:build';
config.targets.serve.executor = '@nrwl/vite:dev-server';
config.targets.test.executor = '@nrwl/vite:test';
config.targets.build.options = {
outputPath: `dist/apps/${myApp}`,
};
config.targets.build.options = {
outputPath: `dist/apps/${myApp}`,
};
config.targets.serve.options = {
buildTarget: `${myApp}:build`,
};
config.targets.serve.options = {
buildTarget: `${myApp}:build`,
};
config.targets.serve.options = {
config: `apps/${myApp}/vite.config.ts`,
};
config.targets.serve.options = {
config: `apps/${myApp}/vite.config.ts`,
};
return config;
return config;
});
});
afterEach(() => cleanupProject());
it('should build applications', async () => {
runCLI(`build ${myApp}`);
expect(readFile(`dist/apps/${myApp}/index.html`)).toBeDefined();
rmDist();
}, 200000);
it('should serve applications in dev mode', async () => {
const port = 4212;
const p = await runCommandUntil(
`run ${myApp}:serve --port=${port}`,
(output) => {
return output.includes('Local:');
}
);
try {
await promisifiedTreeKill(p.pid, 'SIGKILL');
await killPorts(port);
} catch {
// ignore
}
}, 200000);
it('should test applications', async () => {
const result = await runCLIAsync(`test ${myApp}`);
expect(result.combinedOutput).toContain(
`Successfully ran target test for project ${myApp}`
);
});
});
afterEach(() => cleanupProject());
it('should build applications', async () => {
runCLI(`build ${myApp}`);
expect(readFile(`dist/apps/${myApp}/index.html`)).toBeDefined();
rmDist();
}, 200000);
describe('set up new React project with --bundler=vite option', () => {
beforeEach(() => {
proj = newProject();
runCLI(`generate @nrwl/react:app ${myApp} --bundler=vite`);
});
afterEach(() => cleanupProject());
it('should build applications', async () => {
runCLI(`build ${myApp}`);
expect(readFile(`dist/apps/${myApp}/index.html`)).toBeDefined();
rmDist();
}, 200000);
it('should serve applications in dev mode', async () => {
const port = 4212;
const p = await runCommandUntil(
`run ${myApp}:serve --port=${port}`,
(output) => {
return output.includes('Local:');
it('should serve applications in dev mode', async () => {
const port = 4212;
const p = await runCommandUntil(
`run ${myApp}:serve --port=${port}`,
(output) => {
return output.includes('Local:');
}
);
try {
await promisifiedTreeKill(p.pid, 'SIGKILL');
await killPorts(port);
} catch {
// ignore
}
);
try {
await promisifiedTreeKill(p.pid, 'SIGKILL');
await killPorts(port);
} catch {
// ignore
}
}, 200000);
}, 200000);
it('should test applications', async () => {
const result = await runCLIAsync(`test ${myApp}`);
expect(result.combinedOutput).toContain(
`Successfully ran target test for project ${myApp}`
);
it('should test applications', async () => {
const result = await runCLIAsync(`test ${myApp}`);
expect(result.combinedOutput).toContain(
`Successfully ran target test for project ${myApp}`
);
});
});
describe('convert React webpack project to vite using the vite:configuration generator', () => {
beforeEach(() => {
proj = newProject();
runCLI(`generate @nrwl/react:app ${myApp} --bundler=webpack`);
runCLI(`generate @nrwl/vite:configuration ${myApp}`);
});
afterEach(() => cleanupProject());
it('should build applications', async () => {
runCLI(`build ${myApp}`);
expect(readFile(`dist/apps/${myApp}/index.html`)).toBeDefined();
rmDist();
}, 200000);
it('should serve applications in dev mode', async () => {
const port = 4212;
const p = await runCommandUntil(
`run ${myApp}:serve --port=${port}`,
(output) => {
return output.includes('Local:');
}
);
try {
await promisifiedTreeKill(p.pid, 'SIGKILL');
await killPorts(port);
} catch {
// ignore
}
}, 200000);
it('should test applications', async () => {
const result = await runCLIAsync(`test ${myApp}`);
expect(result.combinedOutput).toContain(
`Successfully ran target test for project ${myApp}`
);
});
});
});

View File

@ -0,0 +1,94 @@
import {
checkFilesDoNotExist,
checkFilesExist,
cleanupProject,
createFile,
isNotWindows,
killPorts,
newProject,
removeFile,
runCLI,
runCLIAsync,
runCypressTests,
uniq,
} from '@nrwl/e2e/utils';
describe('Web Components Applications with bundler set as vite', () => {
beforeEach(() => newProject());
afterEach(() => cleanupProject());
it('should be able to generate a web app', async () => {
const appName = uniq('app');
runCLI(`generate @nrwl/web:app ${appName} --bundler=vite --no-interactive`);
const lintResults = runCLI(`lint ${appName}`);
expect(lintResults).toContain('All files pass linting.');
runCLI(`build ${appName}`);
checkFilesExist(`dist/apps/${appName}/index.html`);
const testResults = await runCLIAsync(`test ${appName}`);
expect(testResults.combinedOutput).toContain(
'Test Suites: 1 passed, 1 total'
);
const lintE2eResults = runCLI(`lint ${appName}-e2e`);
expect(lintE2eResults).toContain('All files pass linting.');
if (isNotWindows() && runCypressTests()) {
const e2eResults = runCLI(`e2e ${appName}-e2e --no-watch`);
expect(e2eResults).toContain('All specs passed!');
expect(await killPorts()).toBeTruthy();
}
}, 500000);
it('should be able to generate a web app with standaloneConfig', async () => {
const appName = uniq('app');
runCLI(
`generate @nrwl/web:app ${appName} --bundler=vite --no-interactive --standalone-config`
);
checkFilesExist(`apps/${appName}/project.json`);
const lintResults = runCLI(`lint ${appName}`);
expect(lintResults).toContain('All files pass linting.');
}, 120000);
it('should remove previous output before building', async () => {
const appName = uniq('app');
const libName = uniq('lib');
runCLI(`generate @nrwl/web:app ${appName} --bundler=vite --no-interactive`);
runCLI(
`generate @nrwl/react:lib ${libName} --buildable --no-interactive --compiler swc`
);
createFile(`dist/apps/${appName}/_should_remove.txt`);
createFile(`dist/libs/${libName}/_should_remove.txt`);
createFile(`dist/apps/_should_not_remove.txt`);
checkFilesExist(
`dist/apps/${appName}/_should_remove.txt`,
`dist/apps/_should_not_remove.txt`
);
runCLI(`build ${appName}`);
runCLI(`build ${libName}`);
checkFilesDoNotExist(
`dist/apps/${appName}/_should_remove.txt`,
`dist/libs/${libName}/_should_remove.txt`
);
checkFilesExist(`dist/apps/_should_not_remove.txt`);
}, 120000);
it('should support workspaces w/o workspace config file', async () => {
removeFile('workspace.json');
const myapp = uniq('myapp');
runCLI(`generate @nrwl/web:app ${myapp} --bundler=vite --directory=myDir`);
runCLI(`build my-dir-${myapp}`);
expect(() =>
checkFilesDoNotExist('workspace.json', 'angular.json')
).not.toThrow();
}, 1000000);
});

View File

@ -23,7 +23,9 @@ describe('Web Components Applications', () => {
it('should be able to generate a web app', async () => {
const appName = uniq('app');
runCLI(`generate @nrwl/web:app ${appName} --no-interactive`);
runCLI(
`generate @nrwl/web:app ${appName} --bundler=webpack --no-interactive`
);
const lintResults = runCLI(`lint ${appName}`);
expect(lintResults).toContain('All files pass linting.');
@ -61,7 +63,7 @@ describe('Web Components Applications', () => {
it('should be able to generate a web app with standaloneConfig', async () => {
const appName = uniq('app');
runCLI(
`generate @nrwl/web:app ${appName} --no-interactive --standalone-config`
`generate @nrwl/web:app ${appName} --bundler=webpack --no-interactive --standalone-config`
);
checkFilesExist(`apps/${appName}/project.json`);
@ -74,7 +76,9 @@ describe('Web Components Applications', () => {
const appName = uniq('app');
const libName = uniq('lib');
runCLI(`generate @nrwl/web:app ${appName} --no-interactive --compiler swc`);
runCLI(
`generate @nrwl/web:app ${appName} --bundler=webpack --no-interactive --compiler swc`
);
runCLI(
`generate @nrwl/react:lib ${libName} --buildable --no-interactive --compiler swc`
);
@ -112,7 +116,9 @@ describe('Web Components Applications', () => {
it('should do another build if differential loading is needed', async () => {
const appName = uniq('app');
runCLI(`generate @nrwl/web:app ${appName} --no-interactive`);
runCLI(
`generate @nrwl/web:app ${appName} --bundler=webpack --no-interactive`
);
updateFile(`apps/${appName}/browserslist`, `IE 9-11`);
@ -126,7 +132,9 @@ describe('Web Components Applications', () => {
it('should emit decorator metadata when it is enabled in tsconfig', async () => {
const appName = uniq('app');
runCLI(`generate @nrwl/web:app ${appName} --no-interactive`);
runCLI(
`generate @nrwl/web:app ${appName} --bundler=webpack --no-interactive`
);
updateFile(`apps/${appName}/src/app/app.element.ts`, (content) => {
const newContent = `${content}
@ -180,7 +188,9 @@ describe('Web Components Applications', () => {
it('should support workspaces w/o workspace config file', async () => {
removeFile('workspace.json');
const myapp = uniq('myapp');
runCLI(`generate @nrwl/web:app ${myapp} --directory=myDir`);
runCLI(
`generate @nrwl/web:app ${myapp} --bundler=webpack --directory=myDir`
);
runCLI(`build my-dir-${myapp}`);
expect(() =>
@ -190,7 +200,9 @@ describe('Web Components Applications', () => {
it('should support custom webpackConfig option', async () => {
const appName = uniq('app');
runCLI(`generate @nrwl/web:app ${appName} --no-interactive`);
runCLI(
`generate @nrwl/web:app ${appName} --bundler=webpack --no-interactive`
);
updateProjectConfig(appName, (config) => {
config.targets.build.options.webpackConfig = `apps/${appName}/webpack.config.js`;
@ -284,7 +296,9 @@ describe('CLI - Environment Variables', () => {
const nxSharedEnv = process.env.NX_SHARED_ENV;
`;
runCLI(`generate @nrwl/web:app ${appName} --no-interactive`);
runCLI(
`generate @nrwl/web:app ${appName} --bundler=webpack --no-interactive`
);
const content = readFile(main);
@ -307,7 +321,9 @@ describe('CLI - Environment Variables', () => {
const main2 = `apps/${appName2}/src/main.ts`;
const newCode2 = `const envVars = [process.env.NODE_ENV, process.env.NX_BUILD, process.env.NX_API, process.env.NX_WS_BASE, process.env.NX_WS_ENV_LOCAL, process.env.NX_WS_LOCAL_ENV, process.env.NX_APP_BASE, process.env.NX_APP_ENV_LOCAL, process.env.NX_APP_LOCAL_ENV, process.env.NX_SHARED_ENV];`;
runCLI(`generate @nrwl/web:app ${appName2} --no-interactive`);
runCLI(
`generate @nrwl/web:app ${appName2} --bundler=webpack --no-interactive`
);
const content2 = readFile(main2);
@ -336,7 +352,9 @@ describe('Build Options', () => {
const appName = uniq('app');
runCLI(`generate @nrwl/web:app ${appName} --no-interactive`);
runCLI(
`generate @nrwl/web:app ${appName} --bundler=webpack --no-interactive`
);
const srcPath = `apps/${appName}/src`;
const fooCss = `${srcPath}/foo.css`;
@ -413,7 +431,9 @@ describe('index.html interpolation', () => {
test('should interpolate environment variables', () => {
const appName = uniq('app');
runCLI(`generate @nrwl/web:app ${appName} --no-interactive`);
runCLI(
`generate @nrwl/web:app ${appName} --bundler=webpack --no-interactive`
);
const srcPath = `apps/${appName}/src`;
const indexPath = `${srcPath}/index.html`;

View File

@ -0,0 +1,43 @@
## Examples
{% tabs %}
{% tab label="Simple Application" %}
Create an application named `my-app`:
```bash
nx g @nrwl/react:application my-app
```
{% /tab %}
{% tab label="Application using Vite as bundler" %}
Create an application named `my-app`:
```bash
nx g @nrwl/react:app my-app --bundler=vite
```
{% /tab %}
{% tab label="Specify directory and style extension" %}
Create an application named `my-app` in the `my-dir` directory and use `scss` for styles:
```bash
nx g @nrwl/react:app my-app --directory=my-dir --style=scss
```
{% /tab %}
{% tab label="Add tags" %}
Add tags to the application (used for linting).
```bash
nx g @nrwl/react:app my-app --tags=scope:admin,type:ui
```
{% /tab %}
{% /tabs %}

View File

@ -39,6 +39,7 @@
"@nrwl/js": "file:../js",
"@nrwl/linter": "file:../linter",
"@nrwl/storybook": "file:../storybook",
"@nrwl/vite": "file:../vite",
"@nrwl/web": "file:../web",
"@nrwl/webpack": "file:../webpack",
"@nrwl/workspace": "file:../workspace",

View File

@ -328,7 +328,11 @@ describe('app', () => {
});
it('should setup the nrwl web build builder', async () => {
await applicationGenerator(appTree, { ...schema, name: 'my-app' });
await applicationGenerator(appTree, {
...schema,
name: 'my-app',
bundler: 'webpack',
});
const workspaceJson = getProjects(appTree);
const targetConfig = workspaceJson.get('my-app').targets;
@ -363,8 +367,28 @@ describe('app', () => {
});
});
it('should setup the nrwl vite builder if bundler is vite', async () => {
await applicationGenerator(appTree, {
...schema,
name: 'my-app',
bundler: 'vite',
});
const workspaceJson = getProjects(appTree);
const targetConfig = workspaceJson.get('my-app').targets;
expect(targetConfig.build.executor).toEqual('@nrwl/vite:build');
expect(targetConfig.build.outputs).toEqual(['{options.outputPath}']);
expect(targetConfig.build.options).toEqual({
outputPath: 'dist/apps/my-app',
});
});
it('should setup the nrwl web dev server builder', async () => {
await applicationGenerator(appTree, { ...schema, name: 'my-app' });
await applicationGenerator(appTree, {
...schema,
name: 'my-app',
bundler: 'webpack',
});
const workspaceJson = getProjects(appTree);
const targetConfig = workspaceJson.get('my-app').targets;
@ -379,6 +403,25 @@ describe('app', () => {
});
});
it('should setup the nrwl vite dev server builder if bundler is vite', async () => {
await applicationGenerator(appTree, {
...schema,
name: 'my-app',
bundler: 'vite',
});
const workspaceJson = getProjects(appTree);
const targetConfig = workspaceJson.get('my-app').targets;
expect(targetConfig.serve.executor).toEqual('@nrwl/vite:dev-server');
expect(targetConfig.serve.options).toEqual({
buildTarget: 'my-app:build',
});
expect(targetConfig.serve.configurations.production).toEqual({
buildTarget: 'my-app:build:production',
hmr: false,
});
});
it('should setup the eslint builder', async () => {
await applicationGenerator(appTree, { ...schema, name: 'my-app' });
@ -584,7 +627,11 @@ describe('app', () => {
});
it('should exclude styles from workspace.json', async () => {
await applicationGenerator(appTree, { ...schema, style: 'none' });
await applicationGenerator(appTree, {
...schema,
style: 'none',
bundler: 'webpack',
});
const workspaceJson = getProjects(appTree);
@ -592,6 +639,20 @@ describe('app', () => {
[]
);
});
it('should not break if bundler is vite', async () => {
await applicationGenerator(appTree, {
...schema,
style: 'none',
bundler: 'vite',
});
const workspaceJson = getProjects(appTree);
expect(
workspaceJson.get('my-app').targets.build.options.styles
).toBeUndefined();
});
});
describe('--style styled-components', () => {
@ -658,6 +719,7 @@ describe('app', () => {
await applicationGenerator(appTree, {
...schema,
style: '@emotion/styled',
bundler: 'webpack',
});
const workspaceJson = getProjects(appTree);
@ -667,6 +729,20 @@ describe('app', () => {
);
});
it('should not break if bundler is vite', async () => {
await applicationGenerator(appTree, {
...schema,
style: '@emotion/styled',
bundler: 'vite',
});
const workspaceJson = getProjects(appTree);
expect(
workspaceJson.get('my-app').targets.build.options.styles
).toBeUndefined();
});
it('should add dependencies to package.json', async () => {
await applicationGenerator(appTree, {
...schema,
@ -735,9 +811,10 @@ describe('app', () => {
});
});
it('should adds custom webpack config', async () => {
it('should add custom webpack config', async () => {
await applicationGenerator(appTree, {
...schema,
bundler: 'webpack',
});
const workspaceJson = getProjects(appTree);
@ -747,6 +824,19 @@ describe('app', () => {
).toEqual('@nrwl/react/plugins/webpack');
});
it('should NOT add custom webpack config if bundler is vite', async () => {
await applicationGenerator(appTree, {
...schema,
bundler: 'vite',
});
const workspaceJson = getProjects(appTree);
expect(
workspaceJson.get('my-app').targets.build.options.webpackConfig
).toBeUndefined();
});
it('should add required polyfills for core-js and regenerator', async () => {
await applicationGenerator(appTree, {
...schema,
@ -839,6 +929,7 @@ describe('app', () => {
await applicationGenerator(appTree, {
...schema,
rootProject: true,
bundler: 'webpack',
});
expect(appTree.read('/src/main.tsx')).toBeDefined();
expect(appTree.read('/e2e/cypress.config.ts')).toBeDefined();
@ -851,5 +942,60 @@ describe('app', () => {
].options['outputPath']
).toEqual('dist/my-app');
});
it('should create files at the root if bundler is vite', async () => {
await applicationGenerator(appTree, {
...schema,
name: 'my-app2',
rootProject: true,
bundler: 'vite',
});
expect(appTree.read('/src/main.tsx')).toBeDefined();
expect(appTree.read('/e2e/cypress.config.ts')).toBeDefined();
expect(readJson(appTree, '/tsconfig.json').extends).toEqual(
'./tsconfig.base.json'
);
expect(
readJson(appTree, '/workspace.json').projects['my-app2'].architect[
'build'
].options['outputPath']
).toEqual('dist/my-app2');
});
});
describe('setup React app with --bundler=vite', () => {
let viteAppTree: Tree;
beforeAll(async () => {
viteAppTree = createTreeWithEmptyV1Workspace();
await applicationGenerator(viteAppTree, { ...schema, bundler: 'vite' });
});
it('should setup targets with vite configuration', () => {
const workspaceJson = getProjects(viteAppTree);
const targetConfig = workspaceJson.get('my-app').targets;
expect(targetConfig.build.executor).toEqual('@nrwl/vite:build');
expect(targetConfig.serve.executor).toEqual('@nrwl/vite:dev-server');
expect(targetConfig.serve.options).toEqual({
buildTarget: 'my-app:build',
});
});
it('should add dependencies in package.json', () => {
const packageJson = readJson(viteAppTree, '/package.json');
expect(packageJson.devDependencies).toMatchObject({
vite: expect.any(String),
'@vitejs/plugin-react': expect.any(String),
});
});
it('should create correct tsconfig compilerOptions', () => {
const tsconfigJson = readJson(viteAppTree, '/apps/my-app/tsconfig.json');
expect(tsconfigJson.compilerOptions.types).toMatchObject(['vite/client']);
});
it('should create index.html and vite.config file at the root of the app', () => {
expect(viteAppTree.exists('/apps/my-app/index.html')).toBe(true);
expect(viteAppTree.exists('/apps/my-app/vite.config.ts')).toBe(true);
});
});
});

View File

@ -26,6 +26,7 @@ import reactInitGenerator from '../init/init';
import { Linter, lintProjectGenerator } from '@nrwl/linter';
import { swcCoreVersion } from '@nrwl/js/src/utils/versions';
import { swcLoaderVersion } from '@nrwl/webpack/src/utils/versions';
import { viteConfigurationGenerator } from '@nrwl/vite';
async function addLinting(host: Tree, options: NormalizedSchema) {
const tasks: GeneratorCallback[] = [];
@ -69,6 +70,8 @@ async function addLinting(host: Tree, options: NormalizedSchema) {
}
export async function applicationGenerator(host: Tree, schema: Schema) {
const tasks = [];
const options = normalizeOptions(host, schema);
const initTask = await reactInitGenerator(host, {
@ -76,28 +79,39 @@ export async function applicationGenerator(host: Tree, schema: Schema) {
skipFormat: true,
});
tasks.push(initTask);
createApplicationFiles(host, options);
addProject(host, options);
if (options.bundler === 'vite') {
const viteTask = await viteConfigurationGenerator(host, {
uiFramework: 'react',
project: options.projectName,
newProject: true,
});
tasks.push(viteTask);
}
const lintTask = await addLinting(host, options);
tasks.push(lintTask);
const cypressTask = await addCypress(host, options);
tasks.push(cypressTask);
const jestTask = await addJest(host, options);
tasks.push(jestTask);
updateJestConfig(host, options);
const styledTask = addStyledModuleDependencies(host, options.styledModule);
tasks.push(styledTask);
const routingTask = addRouting(host, options);
tasks.push(routingTask);
setDefaults(host, options);
if (!options.skipFormat) {
await formatFiles(host);
}
return runTasksInSerial(
initTask,
lintTask,
cypressTask,
jestTask,
styledTask,
routingTask
);
return runTasksInSerial(...tasks);
}
export default applicationGenerator;

View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title><%= className %></title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,26 @@
import { render } from '@testing-library/react';
<% if (routing) { %>
import { BrowserRouter } from 'react-router-dom';
<% } %>
import App from './<%= fileName %>';
describe('App', () => {
it('should render successfully', () => {
<% if (routing) { %>
const { baseElement } = render(<BrowserRouter><App /></BrowserRouter>);
<% } else { %>
const { baseElement } = render(<App />);
<% } %>
expect(baseElement).toBeTruthy();
});
it('should have a greeting as the title', () => {
<% if (routing) { %>
const { getByText } = render(<BrowserRouter><App /></BrowserRouter>);
<% } else { %>
const { getByText } = render(<App />);
<% } %>
expect(getByText(/Welcome <%= projectName %>/gi)).toBeTruthy();
});
});

View File

@ -0,0 +1,820 @@
/*
* * * * * * * * * * * * * * * * * * * * * * * * * * * *
This is a starter component and can be deleted.
* * * * * * * * * * * * * * * * * * * * * * * * * * * *
Delete this file and get started with your project!
* * * * * * * * * * * * * * * * * * * * * * * * * * * *
*/
export function NxWelcome({ title }: { title: string }) {
return (
<>
<style
dangerouslySetInnerHTML={{
__html: `
html {
-webkit-text-size-adjust: 100%;
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif,
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji';
line-height: 1.5;
tab-size: 4;
scroll-behavior: smooth;
}
body {
font-family: inherit;
line-height: inherit;
margin: 0;
}
h1,
h2,
p,
pre {
margin: 0;
}
*,
::before,
::after {
box-sizing: border-box;
border-width: 0;
border-style: solid;
border-color: currentColor;
}
h1,
h2 {
font-size: inherit;
font-weight: inherit;
}
a {
color: inherit;
text-decoration: inherit;
}
pre {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
'Liberation Mono', 'Courier New', monospace;
}
svg {
display: block;
vertical-align: middle;
shape-rendering: auto;
text-rendering: optimizeLegibility;
}
pre {
background-color: rgba(55, 65, 81, 1);
border-radius: 0.25rem;
color: rgba(229, 231, 235, 1);
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
'Liberation Mono', 'Courier New', monospace;
overflow: scroll;
padding: 0.5rem 0.75rem;
}
.shadow {
box-shadow: 0 0 #0000, 0 0 #0000, 0 10px 15px -3px rgba(0, 0, 0, 0.1),
0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
.rounded {
border-radius: 1.5rem;
}
.wrapper {
width: 100%;
}
.container {
margin-left: auto;
margin-right: auto;
max-width: 768px;
padding-bottom: 3rem;
padding-left: 1rem;
padding-right: 1rem;
color: rgba(55, 65, 81, 1);
width: 100%;
}
#welcome {
margin-top: 2.5rem;
}
#welcome h1 {
font-size: 3rem;
font-weight: 500;
letter-spacing: -0.025em;
line-height: 1;
}
#welcome span {
display: block;
font-size: 1.875rem;
font-weight: 300;
line-height: 2.25rem;
margin-bottom: 0.5rem;
}
#hero {
align-items: center;
background-color: hsla(214, 62%, 21%, 1);
border: none;
box-sizing: border-box;
color: rgba(55, 65, 81, 1);
display: grid;
grid-template-columns: 1fr;
margin-top: 3.5rem;
}
#hero .text-container {
color: rgba(255, 255, 255, 1);
padding: 3rem 2rem;
}
#hero .text-container h2 {
font-size: 1.5rem;
line-height: 2rem;
position: relative;
}
#hero .text-container h2 svg {
color: hsla(162, 47%, 50%, 1);
height: 2rem;
left: -0.25rem;
position: absolute;
top: 0;
width: 2rem;
}
#hero .text-container h2 span {
margin-left: 2.5rem;
}
#hero .text-container a {
background-color: rgba(255, 255, 255, 1);
border-radius: 0.75rem;
color: rgba(55, 65, 81, 1);
display: inline-block;
margin-top: 1.5rem;
padding: 1rem 2rem;
text-decoration: inherit;
}
#hero .logo-container {
display: none;
justify-content: center;
padding-left: 2rem;
padding-right: 2rem;
}
#hero .logo-container svg {
color: rgba(255, 255, 255, 1);
width: 66.666667%;
}
#middle-content {
align-items: flex-start;
display: grid;
gap: 4rem;
grid-template-columns: 1fr;
margin-top: 3.5rem;
}
#learning-materials {
padding: 2.5rem 2rem;
}
#learning-materials h2 {
font-weight: 500;
font-size: 1.25rem;
letter-spacing: -0.025em;
line-height: 1.75rem;
padding-left: 1rem;
padding-right: 1rem;
}
.list-item-link {
align-items: center;
border-radius: 0.75rem;
display: flex;
margin-top: 1rem;
padding: 1rem;
transition-property: background-color, border-color, color, fill, stroke,
opacity, box-shadow, transform, filter, backdrop-filter,
-webkit-backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
width: 100%;
}
.list-item-link svg:first-child {
margin-right: 1rem;
height: 1.5rem;
transition-property: background-color, border-color, color, fill, stroke,
opacity, box-shadow, transform, filter, backdrop-filter,
-webkit-backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
width: 1.5rem;
}
.list-item-link > span {
flex-grow: 1;
font-weight: 400;
transition-property: background-color, border-color, color, fill, stroke,
opacity, box-shadow, transform, filter, backdrop-filter,
-webkit-backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.list-item-link > span > span {
color: rgba(107, 114, 128, 1);
display: block;
flex-grow: 1;
font-size: 0.75rem;
font-weight: 300;
line-height: 1rem;
transition-property: background-color, border-color, color, fill, stroke,
opacity, box-shadow, transform, filter, backdrop-filter,
-webkit-backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.list-item-link svg:last-child {
height: 1rem;
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
width: 1rem;
}
.list-item-link:hover {
color: rgba(255, 255, 255, 1);
background-color: hsla(162, 47%, 50%, 1);
}
.list-item-link:hover > span {}
.list-item-link:hover > span > span {
color: rgba(243, 244, 246, 1);
}
.list-item-link:hover svg:last-child {
transform: translateX(0.25rem);
}
#other-links {}
.button-pill {
padding: 1.5rem 2rem;
transition-duration: 300ms;
transition-property: background-color, border-color, color, fill, stroke,
opacity, box-shadow, transform, filter, backdrop-filter,
-webkit-backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
align-items: center;
display: flex;
}
.button-pill svg {
transition-property: background-color, border-color, color, fill, stroke,
opacity, box-shadow, transform, filter, backdrop-filter,
-webkit-backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
flex-shrink: 0;
width: 3rem;
}
.button-pill > span {
letter-spacing: -0.025em;
font-weight: 400;
font-size: 1.125rem;
line-height: 1.75rem;
padding-left: 1rem;
padding-right: 1rem;
}
.button-pill span span {
display: block;
font-size: 0.875rem;
font-weight: 300;
line-height: 1.25rem;
}
.button-pill:hover svg,
.button-pill:hover {
color: rgba(255, 255, 255, 1) !important;
}
#nx-console:hover {
background-color: rgba(0, 122, 204, 1);
}
#nx-console svg {
color: rgba(0, 122, 204, 1);
}
#nx-repo:hover {
background-color: rgba(24, 23, 23, 1);
}
#nx-repo svg {
color: rgba(24, 23, 23, 1);
}
#nx-cloud {
margin-bottom: 2rem;
margin-top: 2rem;
padding: 2.5rem 2rem;
}
#nx-cloud > div {
align-items: center;
display: flex;
}
#nx-cloud > div svg {
border-radius: 0.375rem;
flex-shrink: 0;
width: 3rem;
}
#nx-cloud > div h2 {
font-size: 1.125rem;
font-weight: 400;
letter-spacing: -0.025em;
line-height: 1.75rem;
padding-left: 1rem;
padding-right: 1rem;
}
#nx-cloud > div h2 span {
display: block;
font-size: 0.875rem;
font-weight: 300;
line-height: 1.25rem;
}
#nx-cloud p {
font-size: 1rem;
line-height: 1.5rem;
margin-top: 1rem;
}
#nx-cloud pre {
margin-top: 1rem;
}
#nx-cloud a {
color: rgba(107, 114, 128, 1);
display: block;
font-size: 0.875rem;
line-height: 1.25rem;
margin-top: 1.5rem;
text-align: right;
}
#nx-cloud a:hover {
text-decoration: underline;
}
#commands {
padding: 2.5rem 2rem;
margin-top: 3.5rem;
}
#commands h2 {
font-size: 1.25rem;
font-weight: 400;
letter-spacing: -0.025em;
line-height: 1.75rem;
padding-left: 1rem;
padding-right: 1rem;
}
#commands p {
font-size: 1rem;
font-weight: 300;
line-height: 1.5rem;
margin-top: 1rem;
padding-left: 1rem;
padding-right: 1rem;
}
details {
align-items: center;
display: flex;
margin-top: 1rem;
padding-left: 1rem;
padding-right: 1rem;
width: 100%;
}
details pre > span {
color: rgba(181, 181, 181, 1);
display: block;
}
summary {
border-radius: 0.5rem;
display: flex;
font-weight: 400;
padding: 0.5rem;
cursor: pointer;
transition-property: background-color, border-color, color, fill, stroke,
opacity, box-shadow, transform, filter, backdrop-filter,
-webkit-backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
summary:hover {
background-color: rgba(243, 244, 246, 1);
}
summary svg {
height: 1.5rem;
margin-right: 1rem;
width: 1.5rem;
}
#love {
color: rgba(107, 114, 128, 1);
font-size: 0.875rem;
line-height: 1.25rem;
margin-top: 3.5rem;
opacity: 0.6;
text-align: center;
}
#love svg {
color: rgba(252, 165, 165, 1);
width: 1.25rem;
height: 1.25rem;
display: inline;
margin-top: -0.25rem;
}
@media screen and (min-width: 768px) {
#hero {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
#hero .logo-container {
display: flex;
}
#middle-content {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
`,
}}
/>
<div className="wrapper">
<div className="container">
<div id="welcome">
<h1>
<span> Hello there, </span>
Welcome {title} 👋
</h1>
</div>
<div id="hero" className="rounded">
<div className="text-container">
<h2>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M9 12l2 2 4-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z"
/>
</svg>
<span>You&apos;re up and running</span>
</h2>
<a href="#commands"> What&apos;s next? </a>
</div>
<div className="logo-container">
<svg
fill="currentColor"
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M11.987 14.138l-3.132 4.923-5.193-8.427-.012 8.822H0V4.544h3.691l5.247 8.833.005-3.998 3.044 4.759zm.601-5.761c.024-.048 0-3.784.008-3.833h-3.65c.002.059-.005 3.776-.003 3.833h3.645zm5.634 4.134a2.061 2.061 0 0 0-1.969 1.336 1.963 1.963 0 0 1 2.343-.739c.396.161.917.422 1.33.283a2.1 2.1 0 0 0-1.704-.88zm3.39 1.061c-.375-.13-.8-.277-1.109-.681-.06-.08-.116-.17-.176-.265a2.143 2.143 0 0 0-.533-.642c-.294-.216-.68-.322-1.18-.322a2.482 2.482 0 0 0-2.294 1.536 2.325 2.325 0 0 1 4.002.388.75.75 0 0 0 .836.334c.493-.105.46.36 1.203.518v-.133c-.003-.446-.246-.55-.75-.733zm2.024 1.266a.723.723 0 0 0 .347-.638c-.01-2.957-2.41-5.487-5.37-5.487a5.364 5.364 0 0 0-4.487 2.418c-.01-.026-1.522-2.39-1.538-2.418H8.943l3.463 5.423-3.379 5.32h3.54l1.54-2.366 1.568 2.366h3.541l-3.21-5.052a.7.7 0 0 1-.084-.32 2.69 2.69 0 0 1 2.69-2.691h.001c1.488 0 1.736.89 2.057 1.308.634.826 1.9.464 1.9 1.541a.707.707 0 0 0 1.066.596zm.35.133c-.173.372-.56.338-.755.639-.176.271.114.412.114.412s.337.156.538-.311c.104-.231.14-.488.103-.74z" />
</svg>
</div>
</div>
<div id="middle-content">
<div id="learning-materials" className="rounded shadow">
<h2>Learning materials</h2>
<a
href="https://nx.dev/getting-started/intro?utm_source=nx-project"
target="_blank"
rel="noreferrer"
className="list-item-link"
>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"
/>
</svg>
<span>
Documentation
<span> Everything is in there </span>
</span>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M9 5l7 7-7 7"
/>
</svg>
</a>
<a
href="https://blog.nrwl.io/?utm_source=nx-project"
target="_blank"
rel="noreferrer"
className="list-item-link"
>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9a2 2 0 00-2-2h-2m-4-3H9M7 16h6M7 8h6v4H7V8z"
/>
</svg>
<span>
Blog
<span> Changelog, features & events </span>
</span>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M9 5l7 7-7 7"
/>
</svg>
</a>
<a
href="https://www.youtube.com/c/Nrwl_io/videos?utm_source=nx-project&sub_confirmation=1"
target="_blank"
rel="noreferrer"
className="list-item-link"
>
<svg
role="img"
viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<title>YouTube</title>
<path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z" />
</svg>
<span>
YouTube channel
<span> Nx Show, talks & tutorials </span>
</span>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M9 5l7 7-7 7"
/>
</svg>
</a>
<a
href="https://nx.dev/react-tutorial/1-code-generation?utm_source=nx-project"
target="_blank"
rel="noreferrer"
className="list-item-link"
>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M15 15l-2 5L9 9l11 4-5 2zm0 0l5 5M7.188 2.239l.777 2.897M5.136 7.965l-2.898-.777M13.95 4.05l-2.122 2.122m-5.657 5.656l-2.12 2.122"
/>
</svg>
<span>
Interactive tutorials
<span> Create an app, step-by-step </span>
</span>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M9 5l7 7-7 7"
/>
</svg>
</a>
<a
href="https://nxplaybook.com/?utm_source=nx-project"
target="_blank"
rel="noreferrer"
className="list-item-link"
>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M12 14l9-5-9-5-9 5 9 5z" />
<path d="M12 14l6.16-3.422a12.083 12.083 0 01.665 6.479A11.952 11.952 0 0012 20.055a11.952 11.952 0 00-6.824-2.998 12.078 12.078 0 01.665-6.479L12 14z" />
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M12 14l9-5-9-5-9 5 9 5zm0 0l6.16-3.422a12.083 12.083 0 01.665 6.479A11.952 11.952 0 0012 20.055a11.952 11.952 0 00-6.824-2.998 12.078 12.078 0 01.665-6.479L12 14zm-4 6v-7.5l4-2.222"
/>
</svg>
<span>
Video courses
<span> Nx custom courses </span>
</span>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M9 5l7 7-7 7"
/>
</svg>
</a>
</div>
<div id="other-links">
<a
id="nx-console"
className="button-pill rounded shadow"
href="https://marketplace.visualstudio.com/items?itemName=nrwl.angular-console&utm_source=nx-project"
target="_blank"
rel="noreferrer"
>
<svg
fill="currentColor"
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<title>Visual Studio Code</title>
<path d="M23.15 2.587L18.21.21a1.494 1.494 0 0 0-1.705.29l-9.46 8.63-4.12-3.128a.999.999 0 0 0-1.276.057L.327 7.261A1 1 0 0 0 .326 8.74L3.899 12 .326 15.26a1 1 0 0 0 .001 1.479L1.65 17.94a.999.999 0 0 0 1.276.057l4.12-3.128 9.46 8.63a1.492 1.492 0 0 0 1.704.29l4.942-2.377A1.5 1.5 0 0 0 24 20.06V3.939a1.5 1.5 0 0 0-.85-1.352zm-5.146 14.861L10.826 12l7.178-5.448v10.896z" />
</svg>
<span>
Install Nx Console
<span>Plugin for VSCode</span>
</span>
</a>
<div id="nx-cloud" className="rounded shadow">
<div>
<svg
viewBox="0 0 120 120"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M120 15V30C103.44 30 90 43.44 90 60C90 76.56 76.56 90 60 90C43.44 90 30 103.44 30 120H15C6.72 120 0 113.28 0 105V15C0 6.72 6.72 0 15 0H105C113.28 0 120 6.72 120 15Z"
fill="#0E2039"
/>
<path
d="M120 30V105C120 113.28 113.28 120 105 120H30C30 103.44 43.44 90 60 90C76.56 90 90 76.56 90 60C90 43.44 103.44 30 120 30Z"
fill="white"
/>
</svg>
<h2>
NxCloud
<span>Enable faster CI & better DX</span>
</h2>
</div>
<p>
You can activate distributed tasks executions and caching by
running:
</p>
<pre>nx connect-to-nx-cloud</pre>
<a
href="https://nx.app/?utm_source=nx-project"
target="_blank"
rel="noreferrer"
>
{' '}
What is Nx Cloud?{' '}
</a>
</div>
<a
id="nx-repo"
className="button-pill rounded shadow"
href="https://github.com/nrwl/nx?utm_source=nx-project"
target="_blank"
rel="noreferrer"
>
<svg
fill="currentColor"
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
</svg>
<span>
Nx is open source
<span> Love Nx? Give us a star! </span>
</span>
</a>
</div>
</div>
<div id="commands" className="rounded shadow">
<h2>Next steps</h2>
<p>Here are some things you can do with Nx:</p>
<details>
<summary>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
Add UI library
</summary>
<pre>
<span># Generate UI lib</span>
nx g @nrwl/react:lib ui
<span># Add a component</span>
nx g @nrwl/react:component button --project ui
</pre>
</details>
<details>
<summary>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
View interactive project graph
</summary>
<pre>nx graph</pre>
</details>
<details>
<summary>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
Run affected commands
</summary>
<pre>
<span># see what&apos;s been affected by changes</span>
nx affected:graph
<span># run tests for current changes</span>
nx affected:test
<span># run e2e tests for current changes</span>
nx affected:e2e
</pre>
</details>
</div>
<p id="love">
Carefully crafted with
<svg
fill="currentColor"
stroke="none"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
/>
</svg>
</p>
</div>
</div>
</>
);
}
export default NxWelcome;

View File

@ -0,0 +1,3 @@
export const environment = {
production: true
};

View File

@ -0,0 +1,6 @@
// This file can be replaced during build by using the `fileReplacements` array.
// When building for production, this file is replaced with `environment.prod.ts`.
export const environment = {
production: false
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,10 @@
<% if (strict) { %>import { StrictMode } from 'react';<% } %>
import * as ReactDOM from 'react-dom/client';
<% if (routing) { %>import { BrowserRouter } from 'react-router-dom';<% } %>
import App from './app/<%= fileName %>';
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
root.render(
<% if (strict) { %><StrictMode><% } %><% if (routing) { %><BrowserRouter><% } %><App /><% if (routing) { %></BrowserRouter><% } %><% if (strict) { %></StrictMode><% } %>
);

View File

@ -0,0 +1,7 @@
/**
* Polyfill stable language features. These imports will be optimized by `@babel/preset-env`.
*
* See: https://github.com/zloirock/core-js#babel
*/
import 'core-js/stable';
import 'regenerator-runtime/runtime';

View File

@ -0,0 +1,14 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "<%= offsetFromRoot %>dist/out-tsc",
"types": ["node"]
},
"files": [
<% if (style === 'styled-jsx') { %>"<%= offsetFromRoot %>node_modules/@nrwl/react/typings/styled-jsx.d.ts",<% } %>
"<%= offsetFromRoot %>node_modules/@nrwl/react/typings/cssmodule.d.ts",
"<%= offsetFromRoot %>node_modules/@nrwl/react/typings/image.d.ts"
],
"exclude": ["jest.config.ts","**/*.spec.ts", "**/*.test.ts", "**/*.spec.tsx", "**/*.test.tsx", "**/*.spec.js", "**/*.test.js", "**/*.spec.jsx", "**/*.test.jsx"],
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
}

View File

@ -0,0 +1,29 @@
{
"extends": "<%= rootTsConfigPath %>",
"compilerOptions": {
"jsx": "react-jsx",
<% if (style === '@emotion/styled') { %>"jsxImportSource": "@emotion/react",<% } %>
"allowJs": false,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"module": "ESNext",
"moduleResolution": "Node",
"noEmit": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true,
"target": "ESNext",
"types": ["vite/client"],
"useDefineForClassFields": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.app.json"
}
]
}

View File

@ -11,13 +11,17 @@ export function addProject(host, options: NormalizedSchema) {
root: options.appProjectRoot,
sourceRoot: `${options.appProjectRoot}/src`,
projectType: 'application',
targets: {
build: createBuildTarget(options),
serve: createServeTarget(options),
},
targets: {},
tags: options.parsedTags,
};
if (options.bundler === 'webpack') {
project.targets = {
build: createBuildTarget(options),
serve: createServeTarget(options),
};
}
addProjectConfiguration(
host,
options.projectName,

View File

@ -60,7 +60,10 @@ export function createApplicationFiles(host: Tree, options: NormalizedSchema) {
generateFiles(
host,
join(__dirname, '../files/common'),
join(
__dirname,
options.bundler === 'vite' ? '../files/common-vite' : '../files/common'
),
options.appProjectRoot,
templateVariables
);

View File

@ -29,6 +29,7 @@ export interface Schema {
skipDefaultProject?: boolean;
skipPackageJson?: boolean;
rootProject?: boolean;
bundler?: 'webpack' | 'vite';
}
export interface NormalizedSchema extends Schema {

View File

@ -175,7 +175,14 @@
"type": "boolean",
"default": false,
"hidden": true
},
"bundler": {
"description": "The bundler to use.",
"enum": ["vite", "webpack"],
"x-prompt": "Which bundler do you want to use?",
"default": "webpack"
}
},
"required": []
"required": [],
"examplesFile": "../../../docs/application-examples.md"
}

View File

@ -2,16 +2,11 @@ import { assertMinimumCypressVersion } from '@nrwl/cypress/src/utils/cypress-ver
import {
DependencyType,
ProjectGraph,
readJson,
readProjectConfiguration,
readTargetOptions,
Tree,
updateProjectConfiguration,
} from '@nrwl/devkit';
import {
createTreeWithEmptyV1Workspace,
createTreeWithEmptyWorkspace,
} from '@nrwl/devkit/testing';
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
import { Linter } from '@nrwl/linter';
import { applicationGenerator } from '../application/application';
import { componentGenerator } from '../component/component';
@ -45,6 +40,7 @@ describe('React:CypressComponentTestConfiguration', () => {
style: 'scss',
unitTestRunner: 'none',
name: 'my-app',
bundler: 'vite',
});
await libraryGenerator(tree, {
linter: Linter.EsLint,
@ -79,6 +75,7 @@ describe('React:CypressComponentTestConfiguration', () => {
],
},
};
await cypressComponentConfigGenerator(tree, {
project: 'some-lib',
generateTests: false,
@ -115,6 +112,7 @@ describe('React:CypressComponentTestConfiguration', () => {
style: 'scss',
unitTestRunner: 'none',
name: 'my-app',
bundler: 'vite',
});
await libraryGenerator(tree, {
linter: Linter.EsLint,
@ -185,6 +183,7 @@ describe('React:CypressComponentTestConfiguration', () => {
style: 'scss',
unitTestRunner: 'none',
name: 'my-app',
bundler: 'vite',
});
await libraryGenerator(tree, {
linter: Linter.EsLint,
@ -232,6 +231,7 @@ describe('React:CypressComponentTestConfiguration', () => {
style: 'scss',
unitTestRunner: 'none',
name: 'my-app',
bundler: 'vite',
});
await libraryGenerator(tree, {
linter: Linter.EsLint,
@ -286,6 +286,7 @@ describe('React:CypressComponentTestConfiguration', () => {
style: 'scss',
unitTestRunner: 'none',
name: 'my-app',
bundler: 'vite',
});
await libraryGenerator(tree, {
name: 'some-lib',
@ -327,7 +328,7 @@ describe('React:CypressComponentTestConfiguration', () => {
"Error trying to find build configuration. Try manually specifying the build target with the --build-target flag.
Provided project? some-lib
Provided build target? my-app:build
Provided Executors? @nrwl/webpack:webpack"
Provided Executors? @nrwl/webpack:webpack, @nrwl/vite:build"
`);
});
});

View File

@ -16,7 +16,10 @@ export async function updateProjectConfig(
const found = await findBuildConfig(tree, {
project: options.project,
buildTarget: options.buildTarget,
validExecutorNames: new Set<string>(['@nrwl/webpack:webpack']),
validExecutorNames: new Set<string>([
'@nrwl/webpack:webpack',
'@nrwl/vite:build',
]),
});
assetValidConfig(found?.config);

View File

@ -1,10 +1,7 @@
import { formatFiles, Tree } from '@nrwl/devkit';
import applicationGenerator from '../application/application';
import {
normalizeOptions,
normalizeProjectName,
} from '../application/lib/normalize-options';
import { normalizeOptions } from '../application/lib/normalize-options';
import { updateModuleFederationProject } from '../../rules/update-module-federation-project';
import { addModuleFederationFiles } from './lib/add-module-federation-files';
import { updateModuleFederationE2eProject } from './lib/update-module-federation-e2e-project';
@ -35,6 +32,7 @@ export async function hostGenerator(host: Tree, schema: Schema) {
e2eTestRunner: options.e2eTestRunner,
linter: options.linter,
devServerPort: remotePort,
bundler: options.bundler ?? 'webpack',
});
remotePort++;
}

View File

@ -21,6 +21,7 @@ export interface Schema {
compiler?: 'babel' | 'swc';
devServerPort?: number;
remotes?: string[];
bundler?: 'webpack' | 'vite';
}
export interface NormalizedSchema extends Schema {

View File

@ -148,6 +148,12 @@
"devServerPort": {
"type": "number",
"description": "The port for the dev server of the remote app."
},
"bundler": {
"description": "The bundler to use.",
"enum": ["vite", "webpack"],
"x-prompt": "Which bundler do you want to use?",
"default": "webpack"
}
},
"required": ["name"],

View File

@ -453,6 +453,7 @@ describe('lib', () => {
routing: true,
style: 'css',
standaloneConfig: false,
bundler: 'webpack',
});
await libraryGenerator(appTree, {
@ -479,6 +480,7 @@ describe('lib', () => {
name: 'myApp',
style: 'css',
standaloneConfig: false,
bundler: 'webpack',
});
await libraryGenerator(appTree, {

View File

@ -32,6 +32,7 @@ export async function remoteGenerator(host: Tree, schema: Schema) {
const initApp = await applicationGenerator(host, {
...options,
skipDefaultProject: true,
bundler: schema.bundler ?? 'webpack',
});
if (schema.host) {

View File

@ -23,4 +23,5 @@ export interface Schema {
compiler?: 'babel' | 'swc';
host?: string;
devServerPort?: number;
bundler?: 'webpack' | 'vite';
}

View File

@ -152,6 +152,12 @@
"devServerPort": {
"type": "number",
"description": "The port for the dev server of the remote app."
},
"bundler": {
"description": "The bundler to use.",
"enum": ["vite", "webpack"],
"x-prompt": "Which bundler do you want to use?",
"default": "webpack"
}
},
"required": ["name"],

View File

@ -66,6 +66,7 @@ export async function createTestUIApp(name: string): Promise<Tree> {
unitTestRunner: 'none',
name,
standaloneConfig: false,
bundler: 'vite',
});
const config = readProjectConfiguration(tree, name);

View File

@ -0,0 +1,32 @@
This is a generator for setting up Vite configuration for an existing React or Web application. It will change the build and serve targets to use the `@nrwl/vite` executors for serving and building the application. This generator will modify your code, so make sure to commit your changes before running it.
```bash
nx g @nrwl/vite:configuration
```
When running this generator, you will be prompted to provide the following:
- The `project`, as the name of the project you want to generate the configuration for.
- The `uiFramework` you want to use. Supported values are: `react` and `none`.
You must provide a `project` and a `uiFramework` for the generator to work.
You can read more about how this generator works, in the [Vite package overview page](/packages/vite).
## Examples
### Change a React app to use Vite
```bash
nx g @nrwl/vite:configuration --project=my-app --uiFramework=react
```
This will change the `my-app` project to use Vite instead of the default Webpack configuration. The changes this generator will do are described in the [Vite package overview page](/packages/vite).
### Change a Web app to use Vite
```bash
nx g @nrwl/vite:configuration --project=my-app --uiFramework=none
```
This will change the `my-app` project to use Vite instead of the existing bundler configuration.

View File

@ -0,0 +1,23 @@
This is a generator will initialize Vite.js in your workspace. It will install all the necessary dependencies. You can read more about how this generator works, in the [Vite package overview page](/packages/vite).
You can use it on its own like this:
```bash
nx g @nrwl/vite:configuration
```
However, this generator will be called when you are either converting an existing React or Web app to use Vite, using the [`@nrwl/vite:configuration` generator](/packages/vite/generators/configuration), or when you are creating a new React or Web app using the [`@nrwl/react:app`](/packages/react/generators/application) or [`@nrwl/web:app`](<(/packages/web/generators/application)>) generators, if you choose `vite` as the `bundler`.
## Examples
### Install all the necessary dependencies for Vite and the React plugin
```bash
nx g @nrwl/vite:init --uiFramework=react
```
### Install all the necessary dependencies for Vite
```bash
nx g @nrwl/vite:init --uiFramework=none
```

View File

@ -5,18 +5,32 @@
"init": {
"factory": "./src/generators/init/init#initSchematic",
"schema": "./src/generators/init/schema.json",
"description": "Add Storybook configuration to the workspace.",
"description": "Initialize Vite in the workspace.",
"aliases": ["ng-add"],
"hidden": true
},
"configuration": {
"factory": "./src/generators/configuration/configuration#configurationSchematic",
"schema": "./src/generators/configuration/schema.json",
"description": "Add Vite configuration to an application.",
"aliases": ["ng-add"],
"hidden": false
}
},
"generators": {
"init": {
"factory": "./src/generators/init/init",
"schema": "./src/generators/init/schema.json",
"description": "Add Vite configuration to the workspace.",
"description": "Initialize Vite in the workspace.",
"aliases": ["ng-add"],
"hidden": true
},
"configuration": {
"factory": "./src/generators/configuration/configuration",
"schema": "./src/generators/configuration/schema.json",
"description": "Add Vite configuration to an application.",
"aliases": ["ng-add"],
"hidden": false
}
}
}

View File

@ -1 +1,2 @@
export * from './src/utils/versions';
export { viteConfigurationGenerator } from './src/generators/configuration/configuration';

View File

@ -1,7 +1,7 @@
import { ExecutorContext, logger } from '@nrwl/devkit';
import { build, InlineConfig } from 'vite';
import 'dotenv/config';
import { getBuildConfig } from '../../utils/helper-functions';
import { getBuildConfig } from '../../utils/options-utils';
import { ViteBuildExecutorOptions } from './schema';
import { copyAssets } from '@nrwl/js';

View File

@ -1,13 +1,13 @@
import type { AssetGlob } from '@nrwl/workspace/src/utilities/assets';
import type { FileReplacement } from '../../plugins/rollup-replace-files.plugin';
export interface ViteBuildExecutorOptions {
outputPath?: string;
outputPath: string;
baseHref?: string;
proxyConfig?: string;
tsConfig?: string;
configFile?: string;
assets: AssetGlob[];
fileReplacements: FileReplacement[];
assets?: AssetGlob[];
fileReplacements?: FileReplacement[];
sourcemap?: boolean | 'inline' | 'hidden';
hmr?: boolean;
}

View File

@ -7,7 +7,7 @@ import {
getBuildConfig,
getBuildTargetOptions,
getServerOptions,
} from '../../utils/helper-functions';
} from '../../utils/options-utils';
import { copyAssets, CopyAssetsResult } from '@nrwl/js';

View File

@ -1,9 +1,7 @@
import type { FileReplacement } from '../../plugins/rollup-replace-files.plugin';
export interface ViteDevServerExecutorOptions {
buildTarget: string;
baseHref?: string;
proxyConfig?: string;
fileReplacements: FileReplacement[];
port?: number;
host?: string | boolean;
https?: boolean;

View File

@ -8,7 +8,11 @@
"presets": [
{
"name": "Default minimum setup",
"keys": []
"keys": ["buildTarget"]
},
{
"name": "Using a Different Port",
"keys": ["buildTarget", "port"]
}
],
"properties": {
@ -16,38 +20,11 @@
"type": "string",
"description": "Target which builds the application."
},
"baseHref": {
"type": "string",
"description": "Base url for the application being built."
},
"proxyConfig": {
"type": "string",
"description": "Path to the proxy configuration file.",
"x-completion-type": "file"
},
"fileReplacements": {
"description": "Replace files with other files in the build.",
"type": "array",
"items": {
"type": "object",
"properties": {
"replace": {
"type": "string",
"description": "The file to be replaced.",
"x-completion-type": "file"
},
"with": {
"type": "string",
"description": "The file to replace with.",
"x-completion-type": "file"
}
},
"additionalProperties": false,
"required": ["replace", "with"]
},
"default": []
},
"port": {
"type": "number",
"description": "Port to listen on.",
@ -77,6 +54,6 @@
}
},
"definitions": {},
"required": [],
"required": ["buildTarget"],
"examplesFile": "../../../docs/dev-server-examples.md"
}

View File

@ -1,6 +1,5 @@
import { ExecutorContext } from '@nrwl/devkit';
import { File, Reporter } from 'vitest';
import { normalizeConfigFilePath } from '../../utils/helper-functions';
import { VitestExecutorSchema } from './schema';
class NxReporter implements Reporter {

View File

@ -0,0 +1,94 @@
import { addDependenciesToPackageJson, readJson, Tree } from '@nrwl/devkit';
import { createTreeWithEmptyV1Workspace } from '@nrwl/devkit/testing';
import { nxVersion } from '../../utils/versions';
import { viteConfigurationGenerator } from './configuration';
import {
mockReactAppGenerator,
mockWebAppGenerator,
} from '../../utils/test-utils';
describe('@nrwl/vite:configuration', () => {
let tree: Tree;
describe('transform React app to use Vite', () => {
beforeAll(async () => {
tree = createTreeWithEmptyV1Workspace();
await mockReactAppGenerator(tree);
const existing = 'existing';
const existingVersion = '1.0.0';
addDependenciesToPackageJson(
tree,
{ '@nrwl/vite': nxVersion, [existing]: existingVersion },
{ [existing]: existingVersion }
);
await viteConfigurationGenerator(tree, {
uiFramework: 'react',
project: 'my-test-react-app',
});
});
it('should add vite packages and react-related dependencies for vite', async () => {
const packageJson = readJson(tree, '/package.json');
expect(packageJson.devDependencies).toMatchObject({
vite: expect.any(String),
'@vitejs/plugin-react': expect.any(String),
});
});
it('should move index.html to the root of the project', () => {
expect(tree.exists('apps/my-test-react-app/src/index.html')).toBeFalsy();
expect(tree.exists('apps/my-test-react-app/index.html')).toBeTruthy();
});
it('should create correct tsconfig compilerOptions', () => {
const tsconfigJson = readJson(
tree,
'apps/my-test-react-app/tsconfig.json'
);
expect(tsconfigJson.compilerOptions.types).toMatchObject(['vite/client']);
});
it('should create vite.config file at the root of the app', () => {
expect(tree.exists('apps/my-test-react-app/vite.config.ts')).toBe(true);
});
});
describe('transform Web app to use Vite', () => {
beforeAll(async () => {
tree = createTreeWithEmptyV1Workspace();
await mockWebAppGenerator(tree);
const existing = 'existing';
const existingVersion = '1.0.0';
addDependenciesToPackageJson(
tree,
{ '@nrwl/vite': nxVersion, [existing]: existingVersion },
{ [existing]: existingVersion }
);
await viteConfigurationGenerator(tree, {
uiFramework: 'none',
project: 'my-test-web-app',
});
});
it('should add vite dependencies for vite', async () => {
const packageJson = readJson(tree, '/package.json');
expect(packageJson.devDependencies).toMatchObject({
vite: expect.any(String),
'vite-tsconfig-paths': expect.any(String),
});
});
it('should move index.html to the root of the project', () => {
expect(tree.exists('apps/my-test-web-app/src/index.html')).toBeFalsy();
expect(tree.exists('apps/my-test-web-app/index.html')).toBeTruthy();
});
it('should create correct tsconfig compilerOptions', () => {
const tsconfigJson = readJson(tree, 'apps/my-test-web-app/tsconfig.json');
expect(tsconfigJson.compilerOptions.types).toMatchObject(['vite/client']);
});
it('should create vite.config file at the root of the app', () => {
expect(tree.exists('apps/my-test-web-app/vite.config.ts')).toBe(true);
});
});
});

View File

@ -0,0 +1,52 @@
import {
convertNxGenerator,
formatFiles,
GeneratorCallback,
readProjectConfiguration,
Tree,
} from '@nrwl/devkit';
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';
import {
findServeAndBuildTargets,
addOrChangeBuildTarget,
addOrChangeServeTarget,
editTsConfig,
moveAndEditIndexHtml,
writeViteConfig,
} from '../../utils/generator-utils';
import initGenerator from '../init/init';
import { Schema } from './schema';
export async function viteConfigurationGenerator(tree: Tree, schema: Schema) {
const tasks: GeneratorCallback[] = [];
const { targets } = readProjectConfiguration(tree, schema.project);
let buildTarget = 'build';
let serveTarget = 'serve';
if (!schema.newProject) {
buildTarget = findServeAndBuildTargets(targets).buildTarget;
serveTarget = findServeAndBuildTargets(targets).serveTarget;
moveAndEditIndexHtml(tree, schema, buildTarget);
editTsConfig(tree, schema);
}
const initTask = await initGenerator(tree, {
uiFramework: schema.uiFramework,
});
tasks.push(initTask);
addOrChangeBuildTarget(tree, schema, buildTarget);
addOrChangeServeTarget(tree, schema, serveTarget);
writeViteConfig(tree, schema);
await formatFiles(tree);
return runTasksInSerial(...tasks);
}
export default viteConfigurationGenerator;
export const configurationSchematic = convertNxGenerator(
viteConfigurationGenerator
);

View File

@ -0,0 +1,5 @@
export interface Schema {
uiFramework: 'react' | 'none';
project: string;
newProject?: boolean;
}

View File

@ -0,0 +1,33 @@
{
"cli": "nx",
"title": "Add Vite Configuration to an application.",
"description": "Add Vite Configuration to an application.",
"$id": "configure-vite-app",
"type": "object",
"properties": {
"project": {
"type": "string",
"description": "The name of the project.",
"$default": {
"$source": "argv",
"index": 0
},
"aliases": ["name", "projectName"],
"x-dropdown": "project",
"x-prompt": "What is the name of the project to set up a webpack for?"
},
"uiFramework": {
"type": "string",
"description": "UI Framework to use for Vite.",
"enum": ["react", "none"],
"default": "none",
"x-prompt": "What UI framework plugin should Vite use?"
},
"newProject": {
"type": "boolean",
"description": "Is this a new project?",
"default": false
}
},
"examplesFile": "../../../docs/configuration-examples.md"
}

View File

@ -5,6 +5,7 @@ import {
Tree,
updateJson,
} from '@nrwl/devkit';
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';
import {
nxVersion,
@ -54,9 +55,8 @@ function moveToDevDependencies(tree: Tree) {
}
export function initGenerator(tree: Tree, schema: Schema) {
const installTask = checkDependenciesInstalled(tree, schema);
moveToDevDependencies(tree);
const installTask = checkDependenciesInstalled(tree, schema);
return installTask;
}

View File

@ -1,7 +1,7 @@
{
"cli": "nx",
"title": "Add Vite Configuration to the workspace",
"description": "Add Vite Configuration to the workspace.",
"title": "Initialize Vite in the workspace.",
"description": "Initialize Vite in the workspace.",
"$id": "init-vite-plugin",
"type": "object",
"properties": {
@ -12,5 +12,6 @@
"default": "react",
"x-prompt": "What UI framework plugin should Vite use?"
}
}
},
"examplesFile": "../../../docs/init-examples.md"
}

View File

@ -0,0 +1,354 @@
import {
joinPathFragments,
logger,
offsetFromRoot,
readJson,
readProjectConfiguration,
TargetConfiguration,
Tree,
updateProjectConfiguration,
writeJson,
} from '@nrwl/devkit';
import { ViteBuildExecutorOptions } from '../executors/build/schema';
import { ViteDevServerExecutorOptions } from '../executors/dev-server/schema';
import { Schema } from '../generators/configuration/schema';
/**
* This function is used to find the build and serve targets for
* an application.
*
* The reason this function exists is because we cannot assume
* that the user has not created a custom build target for the application,
* or that they have not changed the name of the build target
* from build to anything else.
*
* So, in order to find the correct name of the target,
* we have to look into all the targets, check the executor
* they are using, and infer from the executor that the target
* is a build target.
*/
export function findServeAndBuildTargets(targets: {
[targetName: string]: TargetConfiguration;
}): {
buildTarget: string;
serveTarget: string;
} {
const returnObject: {
buildTarget: string;
serveTarget: string;
} = {
buildTarget: 'build',
serveTarget: 'serve',
};
Object.entries(targets).forEach(([target, targetConfig]) => {
switch (targetConfig.executor) {
case '@nrwl/webpack:dev-server':
case '@nxext/vite:dev':
returnObject.serveTarget = target;
break;
case '@angular-devkit/build-angular:browser':
/**
* Not looking for '@nrwl/angular:ng-packagr-lite' or any other
* angular executors.
* Only looking for '@angular-devkit/build-angular:browser'
* because the '@nrwl/angular:ng-packagr-lite' executor
* (and maybe the other custom exeucutors) is only used for libs atm.
*/
returnObject.buildTarget = target;
break;
case '@nrwl/webpack:webpack':
case '@nrwl/next:build':
case '@nrwl/web:webpack':
case '@nrwl/web:rollup':
case '@nrwl/js:tsc':
case '@nrwl/angular:ng-packagr-lite':
case '@nrwl/js:babel':
case '@nrwl/js:swc':
case '@nxext/vite:build':
returnObject.buildTarget = target;
break;
default:
returnObject.buildTarget = 'build';
returnObject.serveTarget = 'serve';
break;
}
});
return returnObject;
}
export function addOrChangeBuildTarget(
tree: Tree,
options: Schema,
target: string
) {
const project = readProjectConfiguration(tree, options.project);
const buildOptions: ViteBuildExecutorOptions = {
outputPath: joinPathFragments(
'dist',
project.root != '.' ? project.root : options.project
),
};
const targets = {
...project.targets,
};
if (targets[target]) {
targets[target].options = {
...buildOptions,
};
targets[target].executor = '@nrwl/vite:build';
} else {
targets[`${target}`] = {
executor: '@nrwl/vite:build',
outputs: ['{options.outputPath}'],
defaultConfiguration: 'production',
options: buildOptions,
configurations: {
development: {},
production: {
fileReplacements: [
{
replace: joinPathFragments(
project.sourceRoot,
'environments/environment.ts'
),
with: joinPathFragments(
project.sourceRoot,
'environments/environment.prod.ts'
),
},
],
sourceMap: false,
},
},
};
}
updateProjectConfiguration(tree, options.project, {
...project,
targets: {
...targets,
},
});
}
export function addOrChangeServeTarget(
tree: Tree,
options: Schema,
target: string
) {
const project = readProjectConfiguration(tree, options.project);
const serveOptions: ViteDevServerExecutorOptions = {
buildTarget: `${options.project}:build`,
};
const targets = {
...project.targets,
};
if (targets[target]) {
targets[target].options = {
...serveOptions,
};
targets[target].executor = '@nrwl/vite:dev-server';
} else {
targets[`${target}`] = {
executor: '@nrwl/vite:dev-server',
defaultConfiguration: 'development',
options: {
buildTarget: `${options.project}:build`,
},
configurations: {
development: {
buildTarget: `${options.project}:build:development`,
},
production: {
buildTarget: `${options.project}:build:production`,
hmr: false,
},
},
};
}
updateProjectConfiguration(tree, options.project, {
...project,
targets: {
...targets,
},
});
}
export function editTsConfig(tree: Tree, options: Schema) {
const projectConfig = readProjectConfiguration(tree, options.project);
const config = readJson(tree, `${projectConfig.root}/tsconfig.json`);
switch (options.uiFramework) {
case 'react':
config.compilerOptions = {
target: 'ESNext',
useDefineForClassFields: true,
lib: ['DOM', 'DOM.Iterable', 'ESNext'],
allowJs: false,
skipLibCheck: true,
esModuleInterop: false,
allowSyntheticDefaultImports: true,
strict: true,
forceConsistentCasingInFileNames: true,
module: 'ESNext',
moduleResolution: 'Node',
resolveJsonModule: true,
isolatedModules: true,
noEmit: true,
jsx: 'react-jsx',
types: ['vite/client'],
};
config.include = [...config.include, 'src'];
break;
case 'none':
config.compilerOptions = {
target: 'ESNext',
useDefineForClassFields: true,
module: 'ESNext',
lib: ['ESNext', 'DOM'],
moduleResolution: 'Node',
strict: true,
resolveJsonModule: true,
isolatedModules: true,
esModuleInterop: true,
noEmit: true,
noUnusedLocals: true,
noUnusedParameters: true,
noImplicitReturns: true,
skipLibCheck: true,
types: ['vite/client'],
};
config.include = [...config.include, 'src'];
break;
default:
break;
}
writeJson(tree, `${projectConfig.root}/tsconfig.json`, config);
}
export function moveAndEditIndexHtml(
tree: Tree,
options: Schema,
buildTarget: string
) {
const projectConfig = readProjectConfiguration(tree, options.project);
let indexHtmlPath =
projectConfig.targets[buildTarget].options?.index ??
`${projectConfig.root}/src/index.html`;
const mainPath = (
projectConfig.targets[buildTarget].options?.main ??
`${projectConfig.root}/src/main.ts${
options.uiFramework === 'react' ? 'x' : ''
}`
).replace(projectConfig.root, '');
if (
!tree.exists(indexHtmlPath) &&
tree.exists(`${projectConfig.root}/index.html`)
) {
indexHtmlPath = `${projectConfig.root}/index.html`;
}
if (tree.exists(indexHtmlPath)) {
const indexHtmlContent = tree.read(indexHtmlPath, 'utf8');
if (
!indexHtmlContent.includes(
`<script type="module" src="${mainPath}"></script>`
)
) {
tree.write(
`${projectConfig.root}/index.html`,
indexHtmlContent.replace(
'</body>',
`<script type="module" src="${mainPath}"></script>
</body>`
)
);
if (tree.exists(`${projectConfig.root}/src/index.html`))
tree.delete(`${projectConfig.root}/src/index.html`);
}
} else {
tree.write(
`${projectConfig.root}/index.html`,
`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="${mainPath}"></script>
</body>
</html>`
);
}
}
export function writeViteConfig(tree: Tree, options: Schema) {
const projectConfig = readProjectConfiguration(tree, options.project);
const viteConfigPath = `${projectConfig.root}/vite.config.ts`;
if (tree.exists(viteConfigPath)) {
// TODO (katerina): Ideally we should check if the config is already set up correctly
logger.info(
`vite.config.ts already exists. Skipping creation of vite config for ${options.project}.`
);
return;
}
let viteConfigContent = '';
switch (options.uiFramework) {
case 'react':
viteConfigContent = `
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import ViteTsConfigPathsPlugin from 'vite-tsconfig-paths';
export default defineConfig({
plugins: [
react(),
ViteTsConfigPathsPlugin({
root: '${offsetFromRoot(projectConfig.root)}',
projects: ['tsconfig.base.json'],
}),
],
});`;
break;
case 'none':
viteConfigContent = `
import { defineConfig } from 'vite';
import ViteTsConfigPathsPlugin from 'vite-tsconfig-paths';
export default defineConfig({
plugins: [
ViteTsConfigPathsPlugin({
root: '${offsetFromRoot(projectConfig.root)}',
projects: ['tsconfig.base.json'],
}),
],
});`;
break;
default:
break;
}
tree.write(viteConfigPath, viteConfigContent);
}

View File

@ -0,0 +1,85 @@
{
"name": "my-test-react-app",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"root": "apps/my-test-react-app",
"sourceRoot": "apps/my-test-react-app/src",
"projectType": "application",
"targets": {
"build": {
"executor": "@nrwl/webpack:webpack",
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "production",
"options": {
"compiler": "babel",
"outputPath": "dist/apps/my-test-react-app",
"index": "apps/my-test-react-app/src/index.html",
"baseHref": "/",
"main": "apps/my-test-react-app/src/main.tsx",
"polyfills": "apps/my-test-react-app/src/polyfills.ts",
"tsConfig": "apps/my-test-react-app/tsconfig.app.json",
"assets": [
"apps/my-test-react-app/src/favicon.ico",
"apps/my-test-react-app/src/assets"
],
"styles": ["apps/my-test-react-app/src/styles.css"],
"scripts": [],
"webpackConfig": "@nrwl/react/plugins/webpack"
},
"configurations": {
"development": {
"extractLicenses": false,
"optimization": false,
"sourceMap": true,
"vendorChunk": true
},
"production": {
"fileReplacements": [
{
"replace": "apps/my-test-react-app/src/environments/environment.ts",
"with": "apps/my-test-react-app/src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false
}
}
},
"serve": {
"executor": "@nrwl/webpack:dev-server",
"defaultConfiguration": "development",
"options": {
"buildTarget": "my-test-react-app:build",
"hmr": true
},
"configurations": {
"development": {
"buildTarget": "my-test-react-app:build:development"
},
"production": {
"buildTarget": "my-test-react-app:build:production",
"hmr": false
}
}
},
"lint": {
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["apps/my-test-react-app/**/*.{ts,tsx,js,jsx}"]
}
},
"test": {
"executor": "@nrwl/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "apps/my-test-react-app/jest.config.ts",
"passWithNoTests": true
}
}
},
"tags": []
}

View File

@ -0,0 +1,72 @@
{
"name": "my-test-web-app",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"root": "apps/my-test-web-app",
"sourceRoot": "apps/my-test-web-app/src",
"tags": [],
"targets": {
"build": {
"executor": "@nrwl/webpack:webpack",
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "production",
"options": {
"outputPath": "dist/apps/my-test-web-app",
"compiler": "babel",
"main": "apps/my-test-web-app/src/main.ts",
"tsConfig": "apps/my-test-web-app/tsconfig.app.json",
"assets": [
"apps/my-test-web-app/src/favicon.ico",
"apps/my-test-web-app/src/assets"
],
"index": "apps/my-test-web-app/src/index.html",
"baseHref": "/",
"polyfills": "apps/my-test-web-app/src/polyfills.ts",
"styles": ["apps/my-test-web-app/src/styles.css"],
"scripts": []
},
"configurations": {
"production": {
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"fileReplacements": [
{
"replace": "apps/my-test-web-app/src/environments/environment.ts",
"with": "apps/my-test-web-app/src/environments/environment.prod.ts"
}
]
}
}
},
"serve": {
"executor": "@nrwl/webpack:dev-server",
"options": {
"buildTarget": "my-test-web-app:build"
},
"configurations": {
"production": {
"buildTarget": "my-test-web-app:build:production"
}
}
},
"lint": {
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["apps/my-test-web-app/**/*.ts"]
}
},
"test": {
"executor": "@nrwl/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "apps/my-test-web-app/jest.config.ts",
"passWithNoTests": true
}
}
}
}

View File

@ -0,0 +1,148 @@
import { Tree, writeJson } from '@nrwl/devkit';
import * as reactAppConfig from './test-files/react-project.config.json';
import * as webAppConfig from './test-files/web-project.config.json';
export function mockReactAppGenerator(tree: Tree): Tree {
const appName = 'my-test-react-app';
tree.write(
`apps/${appName}/src/main.tsx`,
`import ReactDOM from 'react-dom';\n`
);
tree.write(
`apps/${appName}/tsconfig.json`,
`{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"jsx": "react-jsx",
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}
`
);
tree.write(
`apps/${appName}/src/index.html`,
`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>My Test React App</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
</head>
<body>
<div id="root"></div>
</body>
</html>`
);
writeJson(
tree,
'workspace.json',
{
projects: {
'my-test-react-app': {
...reactAppConfig,
root: `apps/${appName}`,
projectType: 'application',
},
},
}
);
writeJson(tree, `apps/${appName}/project.json`, {
...reactAppConfig,
root: `apps/${appName}`,
projectType: 'application',
});
return tree;
}
export function mockWebAppGenerator(tree: Tree): Tree {
const appName = 'my-test-web-app';
tree.write(`apps/${appName}/src/main.ts`, `import './app/app.element.ts';`);
tree.write(
`apps/${appName}/tsconfig.json`,
`{
"extends": "../../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}
`
);
tree.write(
`apps/${appName}/src/index.html`,
`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>WebappPure</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
</head>
<body>
<workspace-root></workspace-root>
</body>
</html>
`
);
writeJson(
tree,
'workspace.json',
{
projects: {
'my-test-web-app': {
...webAppConfig,
root: `apps/${appName}`,
projectType: 'application',
},
},
}
);
writeJson(tree, `apps/${appName}/project.json`, {
...webAppConfig,
root: `apps/${appName}`,
projectType: 'application',
});
return tree;
}

View File

@ -0,0 +1,43 @@
## Examples
{% tabs %}
{% tab label="Simple Application" %}
Create an application named `my-app`:
```bash
nx g @nrwl/web:application my-app
```
{% /tab %}
{% tab label="Application using Vite as bundler" %}
Create an application named `my-app`:
```bash
nx g @nrwl/web:app my-app --bundler=vite
```
{% /tab %}
{% tab label="Specify directory" %}
Create an application named `my-app` in the `my-dir` directory:
```bash
nx g @nrwl/web:app my-app --directory=my-dir
```
{% /tab %}
{% tab label="Add tags" %}
Add tags to the application (used for linting).
```bash
nx g @nrwl/web:app my-app --tags=scope:admin,type:ui
```
{% /tab %}
{% /tabs %}

View File

@ -43,6 +43,7 @@
"@nrwl/js": "file:../js",
"@nrwl/linter": "file:../linter",
"@nrwl/rollup": "file:../rollup",
"@nrwl/vite": "file:../vite",
"@nrwl/webpack": "file:../webpack",
"@nrwl/workspace": "file:../workspace",
"babel-plugin-const-enum": "^1.0.1",

View File

@ -140,6 +140,36 @@ describe('app', () => {
`);
});
it('should generate files if bundler is vite', async () => {
await applicationGenerator(tree, {
name: 'myApp',
standaloneConfig: false,
bundler: 'vite',
});
expect(tree.exists('apps/my-app/src/main.ts')).toBeTruthy();
expect(tree.exists('apps/my-app/src/app/app.element.ts')).toBeTruthy();
expect(
tree.exists('apps/my-app/src/app/app.element.spec.ts')
).toBeTruthy();
expect(tree.exists('apps/my-app/src/app/app.element.css')).toBeTruthy();
const tsconfig = readJson(tree, 'apps/my-app/tsconfig.json');
expect(tsconfig.extends).toBe('../../tsconfig.base.json');
expect(tsconfig.references).toEqual([
{
path: './tsconfig.app.json',
},
{
path: './tsconfig.spec.json',
},
]);
expect(tsconfig.compilerOptions.types).toMatchObject(['vite/client']);
expect(tree.exists('apps/my-app-e2e/cypress.config.ts')).toBeTruthy();
expect(tree.exists('apps/my-app/index.html')).toBeTruthy();
expect(tree.exists('apps/my-app/vite.config.ts')).toBeTruthy();
});
it('should extend from root tsconfig.json when no tsconfig.base.json', async () => {
tree.rename('tsconfig.base.json', 'tsconfig.json');
@ -345,6 +375,39 @@ describe('app', () => {
});
});
it('should setup the nrwl vite:build builder if bundler is vite', async () => {
await applicationGenerator(tree, {
name: 'my-App',
standaloneConfig: false,
bundler: 'vite',
});
const workspaceJson = readJson(tree, 'workspace.json');
const architectConfig = workspaceJson.projects['my-app'].architect;
expect(architectConfig.build.builder).toEqual('@nrwl/vite:build');
expect(architectConfig.build.outputs).toEqual(['{options.outputPath}']);
expect(architectConfig.build.options).toEqual({
outputPath: 'dist/apps/my-app',
});
});
it('should setup the nrwl vite:dev-server builder if bundler is vite', async () => {
await applicationGenerator(tree, {
name: 'my-App',
standaloneConfig: false,
bundler: 'vite',
});
const workspaceJson = readJson(tree, 'workspace.json');
const architectConfig = workspaceJson.projects['my-app'].architect;
expect(architectConfig.serve.builder).toEqual('@nrwl/vite:dev-server');
expect(architectConfig.serve.options).toEqual({
buildTarget: 'my-app:build',
});
expect(architectConfig.serve.configurations.production).toEqual({
buildTarget: 'my-app:build:production',
hmr: false,
});
});
it('should setup the eslint builder', async () => {
await applicationGenerator(tree, {
name: 'my-App',
@ -467,4 +530,42 @@ describe('app', () => {
`);
});
});
describe('setup web app with --bundler=vite', () => {
let viteAppTree: Tree;
beforeAll(async () => {
viteAppTree = createTreeWithEmptyV1Workspace();
await applicationGenerator(viteAppTree, {
name: 'myApp',
bundler: 'vite',
});
});
it('should setup targets with vite configuration', () => {
const workspaceJson = getProjects(viteAppTree);
const targetConfig = workspaceJson.get('my-app').targets;
expect(targetConfig.build.executor).toEqual('@nrwl/vite:build');
expect(targetConfig.serve.executor).toEqual('@nrwl/vite:dev-server');
expect(targetConfig.serve.options).toEqual({
buildTarget: 'my-app:build',
});
});
it('should add dependencies in package.json', () => {
const packageJson = readJson(viteAppTree, '/package.json');
expect(packageJson.devDependencies).toMatchObject({
vite: expect.any(String),
});
});
it('should create correct tsconfig compilerOptions', () => {
const tsconfigJson = readJson(viteAppTree, '/apps/my-app/tsconfig.json');
expect(tsconfigJson.compilerOptions.types).toMatchObject(['vite/client']);
});
it('should create index.html and vite.config file at the root of the app', () => {
expect(viteAppTree.exists('/apps/my-app/index.html')).toBe(true);
expect(viteAppTree.exists('/apps/my-app/vite.config.ts')).toBe(true);
});
});
});

View File

@ -24,6 +24,7 @@ import { swcCoreVersion } from '@nrwl/js/src/utils/versions';
import { Linter, lintProjectGenerator } from '@nrwl/linter';
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';
import { getRelativePathToRootTsConfig } from '@nrwl/workspace/src/utilities/typescript';
import { viteConfigurationGenerator } from '@nrwl/vite';
import { swcLoaderVersion } from '../../utils/versions';
import { webInitGenerator } from '../init/init';
@ -38,16 +39,24 @@ interface NormalizedSchema extends Schema {
}
function createApplicationFiles(tree: Tree, options: NormalizedSchema) {
generateFiles(tree, join(__dirname, './files/app'), options.appProjectRoot, {
...options,
...names(options.name),
tmpl: '',
offsetFromRoot: offsetFromRoot(options.appProjectRoot),
rootTsConfigPath: getRelativePathToRootTsConfig(
tree,
options.appProjectRoot
generateFiles(
tree,
join(
__dirname,
options.bundler === 'vite' ? './files/app-vite' : './files/app'
),
});
options.appProjectRoot,
{
...options,
...names(options.name),
tmpl: '',
offsetFromRoot: offsetFromRoot(options.appProjectRoot),
rootTsConfigPath: getRelativePathToRootTsConfig(
tree,
options.appProjectRoot
),
}
);
if (options.unitTestRunner === 'none') {
tree.delete(join(options.appProjectRoot, './src/app/app.element.spec.ts'));
}
@ -143,7 +152,9 @@ async function addProject(tree: Tree, options: NormalizedSchema) {
options.standaloneConfig
);
await setupBundler(tree, options);
if (options.bundler !== 'vite') {
await setupBundler(tree, options);
}
const workspace = readWorkspaceConfiguration(tree);
@ -187,6 +198,15 @@ export async function applicationGenerator(host: Tree, schema: Schema) {
createApplicationFiles(host, options);
await addProject(host, options);
if (options.bundler === 'vite') {
const viteTask = await viteConfigurationGenerator(host, {
uiFramework: 'react',
project: options.projectName,
newProject: true,
});
tasks.push(viteTask);
}
const lintTask = await lintProjectGenerator(host, {
linter: options.linter,
project: options.projectName,

View File

@ -0,0 +1,5 @@
{
"presets": [
"@nrwl/web/babel"
]
}

View File

@ -0,0 +1,13 @@
# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
#
# If you need to support different browsers in production, you may tweak the list below.
last 1 Chrome version
last 1 Firefox version
last 2 Edge major versions
last 2 Safari major version
last 2 iOS major versions
Firefox ESR
not IE 9-11 # For IE 9-11 support, remove 'not'.

View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title><%= className %></title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
</head>
<body>
<<%= prefix %>-root></<%= prefix %>-root>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@ -0,0 +1,416 @@
/*
* Remove template code below
*/
html {
-webkit-text-size-adjust: 100%;
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif,
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji';
line-height: 1.5;
tab-size: 4;
scroll-behavior: smooth;
}
body {
font-family: inherit;
line-height: inherit;
margin: 0;
}
h1,
h2,
p,
pre {
margin: 0;
}
*,
::before,
::after {
box-sizing: border-box;
border-width: 0;
border-style: solid;
border-color: currentColor;
}
h1,
h2 {
font-size: inherit;
font-weight: inherit;
}
a {
color: inherit;
text-decoration: inherit;
}
pre {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
'Liberation Mono', 'Courier New', monospace;
}
svg {
display: block;
vertical-align: middle;
}
svg {
shape-rendering: auto;
text-rendering: optimizeLegibility;
}
pre {
background-color: rgba(55, 65, 81, 1);
border-radius: 0.25rem;
color: rgba(229, 231, 235, 1);
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
'Liberation Mono', 'Courier New', monospace;
overflow: scroll;
padding: 0.5rem 0.75rem;
}
.shadow {
box-shadow: 0 0 #0000, 0 0 #0000, 0 10px 15px -3px rgba(0, 0, 0, 0.1),
0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
.rounded {
border-radius: 1.5rem;
}
.wrapper {
width: 100%;
}
.container {
margin-left: auto;
margin-right: auto;
max-width: 768px;
padding-bottom: 3rem;
padding-left: 1rem;
padding-right: 1rem;
color: rgba(55, 65, 81, 1);
width: 100%;
}
#welcome {
margin-top: 2.5rem;
}
#welcome h1 {
font-size: 3rem;
font-weight: 500;
letter-spacing: -0.025em;
line-height: 1;
}
#welcome span {
display: block;
font-size: 1.875rem;
font-weight: 300;
line-height: 2.25rem;
margin-bottom: 0.5rem;
}
#hero {
align-items: center;
background-color: hsla(214, 62%, 21%, 1);
border: none;
box-sizing: border-box;
color: rgba(55, 65, 81, 1);
display: grid;
grid-template-columns: 1fr;
margin-top: 3.5rem;
}
#hero .text-container {
color: rgba(255, 255, 255, 1);
padding: 3rem 2rem;
}
#hero .text-container h2 {
font-size: 1.5rem;
line-height: 2rem;
position: relative;
}
#hero .text-container h2 svg {
color: hsla(162, 47%, 50%, 1);
height: 2rem;
left: -0.25rem;
position: absolute;
top: 0;
width: 2rem;
}
#hero .text-container h2 span {
margin-left: 2.5rem;
}
#hero .text-container a {
background-color: rgba(255, 255, 255, 1);
border-radius: 0.75rem;
color: rgba(55, 65, 81, 1);
display: inline-block;
margin-top: 1.5rem;
padding: 1rem 2rem;
text-decoration: inherit;
}
#hero .logo-container {
display: none;
justify-content: center;
padding-left: 2rem;
padding-right: 2rem;
}
#hero .logo-container svg {
color: rgba(255, 255, 255, 1);
width: 66.666667%;
}
#middle-content {
align-items: flex-start;
display: grid;
gap: 4rem;
grid-template-columns: 1fr;
margin-top: 3.5rem;
}
#learning-materials {
padding: 2.5rem 2rem;
}
#learning-materials h2 {
font-weight: 500;
font-size: 1.25rem;
letter-spacing: -0.025em;
line-height: 1.75rem;
padding-left: 1rem;
padding-right: 1rem;
}
.list-item-link {
align-items: center;
border-radius: 0.75rem;
display: flex;
margin-top: 1rem;
padding: 1rem;
transition-property: background-color, border-color, color, fill, stroke,
opacity, box-shadow, transform, filter, backdrop-filter,
-webkit-backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
width: 100%;
}
.list-item-link svg:first-child {
margin-right: 1rem;
height: 1.5rem;
transition-property: background-color, border-color, color, fill, stroke,
opacity, box-shadow, transform, filter, backdrop-filter,
-webkit-backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
width: 1.5rem;
}
.list-item-link > span {
flex-grow: 1;
font-weight: 400;
transition-property: background-color, border-color, color, fill, stroke,
opacity, box-shadow, transform, filter, backdrop-filter,
-webkit-backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.list-item-link > span > span {
color: rgba(107, 114, 128, 1);
display: block;
flex-grow: 1;
font-size: 0.75rem;
font-weight: 300;
line-height: 1rem;
transition-property: background-color, border-color, color, fill, stroke,
opacity, box-shadow, transform, filter, backdrop-filter,
-webkit-backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.list-item-link svg:last-child {
height: 1rem;
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
width: 1rem;
}
.list-item-link:hover {
color: rgba(255, 255, 255, 1);
background-color: hsla(162, 47%, 50%, 1);
}
.list-item-link:hover > span {
}
.list-item-link:hover > span > span {
color: rgba(243, 244, 246, 1);
}
.list-item-link:hover svg:last-child {
transform: translateX(0.25rem);
}
#other-links {
}
.button-pill {
padding: 1.5rem 2rem;
transition-duration: 300ms;
transition-property: background-color, border-color, color, fill, stroke,
opacity, box-shadow, transform, filter, backdrop-filter,
-webkit-backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
align-items: center;
display: flex;
}
.button-pill svg {
transition-property: background-color, border-color, color, fill, stroke,
opacity, box-shadow, transform, filter, backdrop-filter,
-webkit-backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
flex-shrink: 0;
width: 3rem;
}
.button-pill > span {
letter-spacing: -0.025em;
font-weight: 400;
font-size: 1.125rem;
line-height: 1.75rem;
padding-left: 1rem;
padding-right: 1rem;
}
.button-pill span span {
display: block;
font-size: 0.875rem;
font-weight: 300;
line-height: 1.25rem;
}
.button-pill:hover svg,
.button-pill:hover {
color: rgba(255, 255, 255, 1) !important;
}
#nx-console:hover {
background-color: rgba(0, 122, 204, 1);
}
#nx-console svg {
color: rgba(0, 122, 204, 1);
}
#nx-repo:hover {
background-color: rgba(24, 23, 23, 1);
}
#nx-repo svg {
color: rgba(24, 23, 23, 1);
}
#nx-cloud {
margin-bottom: 2rem;
margin-top: 2rem;
padding: 2.5rem 2rem;
}
#nx-cloud > div {
align-items: center;
display: flex;
}
#nx-cloud > div svg {
border-radius: 0.375rem;
flex-shrink: 0;
width: 3rem;
}
#nx-cloud > div h2 {
font-size: 1.125rem;
font-weight: 400;
letter-spacing: -0.025em;
line-height: 1.75rem;
padding-left: 1rem;
padding-right: 1rem;
}
#nx-cloud > div h2 span {
display: block;
font-size: 0.875rem;
font-weight: 300;
line-height: 1.25rem;
}
#nx-cloud p {
font-size: 1rem;
line-height: 1.5rem;
margin-top: 1rem;
}
#nx-cloud pre {
margin-top: 1rem;
}
#nx-cloud a {
color: rgba(107, 114, 128, 1);
display: block;
font-size: 0.875rem;
line-height: 1.25rem;
margin-top: 1.5rem;
text-align: right;
}
#nx-cloud a:hover {
text-decoration: underline;
}
#commands {
padding: 2.5rem 2rem;
margin-top: 3.5rem;
}
#commands h2 {
font-size: 1.25rem;
font-weight: 400;
letter-spacing: -0.025em;
line-height: 1.75rem;
padding-left: 1rem;
padding-right: 1rem;
}
#commands p {
font-size: 1rem;
font-weight: 300;
line-height: 1.5rem;
margin-top: 1rem;
padding-left: 1rem;
padding-right: 1rem;
}
details {
align-items: center;
display: flex;
margin-top: 1rem;
padding-left: 1rem;
padding-right: 1rem;
width: 100%;
}
details pre > span {
color: rgba(181, 181, 181, 1);
}
summary {
border-radius: 0.5rem;
display: flex;
font-weight: 400;
padding: 0.5rem;
cursor: pointer;
transition-property: background-color, border-color, color, fill, stroke,
opacity, box-shadow, transform, filter, backdrop-filter,
-webkit-backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
summary:hover {
background-color: rgba(243, 244, 246, 1);
}
summary svg {
height: 1.5rem;
margin-right: 1rem;
width: 1.5rem;
}
#love {
color: rgba(107, 114, 128, 1);
font-size: 0.875rem;
line-height: 1.25rem;
margin-top: 3.5rem;
opacity: 0.6;
text-align: center;
}
#love svg {
color: rgba(252, 165, 165, 1);
width: 1.25rem;
height: 1.25rem;
display: inline;
margin-top: -0.25rem;
}
@media screen and (min-width: 768px) {
#hero {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
#hero .logo-container {
display: flex;
}
#middle-content {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}

View File

@ -0,0 +1,21 @@
import { AppElement } from './app.element';
describe('AppElement', () => {
let app: AppElement;
beforeEach(() => {
app = new AppElement();
});
it('should create successfully', () => {
expect(app).toBeTruthy();
});
it('should have a greeting', () => {
app.connectedCallback();
expect(app.querySelector('h1').innerHTML).toContain(
'Welcome <%= projectName %>'
);
});
});

View File

@ -0,0 +1,385 @@
import './app.element.<%= style %>';
export class AppElement extends HTMLElement {
public static observedAttributes = [
];
connectedCallback() {
const title = '<%= projectName %>';
this.innerHTML = `
<div class="wrapper">
<div class="container">
<!-- WELCOME -->
<div id="welcome">
<h1>
<span> Hello there, </span>
Welcome ${title} 👋
</h1>
</div>
<!-- HERO -->
<div id="hero" class="rounded">
<div class="text-container">
<h2>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 12l2 2 4-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z"
/>
</svg>
<span>You&apos;re up and running</span>
</h2>
<a href="#commands"> What&apos;s next? </a>
</div>
<div class="logo-container">
<svg
fill="currentColor"
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11.987 14.138l-3.132 4.923-5.193-8.427-.012 8.822H0V4.544h3.691l5.247 8.833.005-3.998 3.044 4.759zm.601-5.761c.024-.048 0-3.784.008-3.833h-3.65c.002.059-.005 3.776-.003 3.833h3.645zm5.634 4.134a2.061 2.061 0 0 0-1.969 1.336 1.963 1.963 0 0 1 2.343-.739c.396.161.917.422 1.33.283a2.1 2.1 0 0 0-1.704-.88zm3.39 1.061c-.375-.13-.8-.277-1.109-.681-.06-.08-.116-.17-.176-.265a2.143 2.143 0 0 0-.533-.642c-.294-.216-.68-.322-1.18-.322a2.482 2.482 0 0 0-2.294 1.536 2.325 2.325 0 0 1 4.002.388.75.75 0 0 0 .836.334c.493-.105.46.36 1.203.518v-.133c-.003-.446-.246-.55-.75-.733zm2.024 1.266a.723.723 0 0 0 .347-.638c-.01-2.957-2.41-5.487-5.37-5.487a5.364 5.364 0 0 0-4.487 2.418c-.01-.026-1.522-2.39-1.538-2.418H8.943l3.463 5.423-3.379 5.32h3.54l1.54-2.366 1.568 2.366h3.541l-3.21-5.052a.7.7 0 0 1-.084-.32 2.69 2.69 0 0 1 2.69-2.691h.001c1.488 0 1.736.89 2.057 1.308.634.826 1.9.464 1.9 1.541a.707.707 0 0 0 1.066.596zm.35.133c-.173.372-.56.338-.755.639-.176.271.114.412.114.412s.337.156.538-.311c.104-.231.14-.488.103-.74z"
/>
</svg>
</div>
</div>
<!-- MIDDLE CONTENT -->
<div id="middle-content">
<div id="learning-materials" class="rounded shadow">
<h2>Learning materials</h2>
<a href="https://nx.dev/getting-started/intro?utm_source=nx-project" target="_blank" rel="noreferrer" class="list-item-link">
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"
/>
</svg>
<span>
Documentation
<span> Everything is in there </span>
</span>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 5l7 7-7 7"
/>
</svg>
</a>
<a href="https://blog.nrwl.io/?utm_source=nx-project" target="_blank" rel="noreferrer" class="list-item-link">
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9a2 2 0 00-2-2h-2m-4-3H9M7 16h6M7 8h6v4H7V8z"
/>
</svg>
<span>
Blog
<span> Changelog, features & events </span>
</span>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 5l7 7-7 7"
/>
</svg>
</a>
<a href="https://www.youtube.com/c/Nrwl_io/videos?utm_source=nx-project&sub_confirmation=1" target="_blank" rel="noreferrer" class="list-item-link">
<svg
role="img"
viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<title>YouTube</title>
<path
d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"
/>
</svg>
<span>
YouTube channel
<span> Nx Show, talks & tutorials </span>
</span>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 5l7 7-7 7"
/>
</svg>
</a>
<a href="https://nx.dev/react-tutorial/1-code-generation?utm_source=nx-project" target="_blank" rel="noreferrer" class="list-item-link">
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M15 15l-2 5L9 9l11 4-5 2zm0 0l5 5M7.188 2.239l.777 2.897M5.136 7.965l-2.898-.777M13.95 4.05l-2.122 2.122m-5.657 5.656l-2.12 2.122"
/>
</svg>
<span>
Interactive tutorials
<span> Create an app, step-by-step </span>
</span>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 5l7 7-7 7"
/>
</svg>
</a>
<a href="https://nxplaybook.com/?utm_source=nx-project" target="_blank" rel="noreferrer" class="list-item-link">
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M12 14l9-5-9-5-9 5 9 5z" />
<path
d="M12 14l6.16-3.422a12.083 12.083 0 01.665 6.479A11.952 11.952 0 0012 20.055a11.952 11.952 0 00-6.824-2.998 12.078 12.078 0 01.665-6.479L12 14z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 14l9-5-9-5-9 5 9 5zm0 0l6.16-3.422a12.083 12.083 0 01.665 6.479A11.952 11.952 0 0012 20.055a11.952 11.952 0 00-6.824-2.998 12.078 12.078 0 01.665-6.479L12 14zm-4 6v-7.5l4-2.222"
/>
</svg>
<span>
Video courses
<span> Nx custom courses </span>
</span>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 5l7 7-7 7"
/>
</svg>
</a>
</div>
<div id="other-links">
<a id="nx-console" class="button-pill rounded shadow" href="https://marketplace.visualstudio.com/items?itemName=nrwl.angular-console&utm_source=nx-project" target="_blank" rel="noreferrer">
<svg
fill="currentColor"
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<title>Visual Studio Code</title>
<path
d="M23.15 2.587L18.21.21a1.494 1.494 0 0 0-1.705.29l-9.46 8.63-4.12-3.128a.999.999 0 0 0-1.276.057L.327 7.261A1 1 0 0 0 .326 8.74L3.899 12 .326 15.26a1 1 0 0 0 .001 1.479L1.65 17.94a.999.999 0 0 0 1.276.057l4.12-3.128 9.46 8.63a1.492 1.492 0 0 0 1.704.29l4.942-2.377A1.5 1.5 0 0 0 24 20.06V3.939a1.5 1.5 0 0 0-.85-1.352zm-5.146 14.861L10.826 12l7.178-5.448v10.896z"
/>
</svg>
<span>
Install Nx Console
<span>Plugin for VSCode</span>
</span>
</a>
<div id="nx-cloud" class="rounded shadow">
<div>
<svg
viewBox="0 0 120 120"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M120 15V30C103.44 30 90 43.44 90 60C90 76.56 76.56 90 60 90C43.44 90 30 103.44 30 120H15C6.72 120 0 113.28 0 105V15C0 6.72 6.72 0 15 0H105C113.28 0 120 6.72 120 15Z"
fill="#0E2039"
/>
<path
d="M120 30V105C120 113.28 113.28 120 105 120H30C30 103.44 43.44 90 60 90C76.56 90 90 76.56 90 60C90 43.44 103.44 30 120 30Z"
fill="white"
/>
</svg>
<h2>
NxCloud
<span>
Enable faster CI & better DX
</span>
</h2>
</div>
<p>
You can activate distributed tasks executions and caching by
running:
</p>
<pre>nx connect-to-nx-cloud</pre>
<a href="https://nx.app/?utm_source=nx-project" target="_blank" rel="noreferrer"> What is Nx Cloud? </a>
</div>
<a id="nx-repo" class="button-pill rounded shadow" href="https://github.com/nrwl/nx?utm_source=nx-project" target="_blank" rel="noreferrer">
<svg
fill="currentColor"
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"
/>
</svg>
<span>
Nx is open source
<span> Love Nx? Give us a star! </span>
</span>
</a>
</div>
</div>
<!-- COMMANDS -->
<div id="commands" class="rounded shadow">
<h2>Next steps</h2>
<p>Here are some things you can do with Nx:</p>
<details>
<summary>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
Add UI library
</summary>
<pre><span># Generate UI lib</span>
nx g @nrwl/angular:lib ui
<span># Add a component</span>
nx g @nrwl/angular:component button --project ui</pre>
</details>
<details>
<summary>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
View interactive project graph
</summary>
<pre>nx graph</pre>
</details>
<details>
<summary>
<svg
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
Run affected commands
</summary>
<pre><span># see what&apos;s been affected by changes</span>
nx affected:graph
<span># run tests for current changes</span>
nx affected:test
<span># run e2e tests for current changes</span>
nx affected:e2e</pre>
</details>
</div>
<p id="love">
Carefully crafted with
<svg
fill="currentColor"
stroke="none"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
/>
</svg>
</p>
</div>
</div>
`;
}
}
customElements.define('<%= prefix %>-root', AppElement);

View File

@ -0,0 +1,3 @@
export const environment = {
production: true
};

View File

@ -0,0 +1,6 @@
// This file can be replaced during build by using the `fileReplacements` array.
// When building for production, this file is replaced with `environment.prod.ts`.
export const environment = {
production: false
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1 @@
import './app/app.element.ts';

View File

@ -0,0 +1,7 @@
/**
* Polyfill stable language features. These imports will be optimized by `@babel/preset-env`.
*
* See: https://github.com/zloirock/core-js#babel
*/
import 'core-js/stable';
import 'regenerator-runtime/runtime';

View File

@ -0,0 +1 @@
/* You can add global styles to this file, and also import other style files */

View File

@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "<%= offsetFromRoot %>dist/out-tsc",
"types": ["node"]
},
"exclude": ["jest.config.ts","**/*.spec.ts", "**/*.test.ts"],
"include": ["**/*.ts"]
}

View File

@ -0,0 +1,27 @@
{
"extends": "<%= rootTsConfigPath %>",
"files": [],
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ESNext", "DOM"],
"moduleResolution": "Node",
"strict": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"noEmit": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"skipLibCheck": true,
"types": ["vite/client"]
},
"include": ["src"],
"references": [
{
"path": "./tsconfig.app.json"
}
]
}

View File

@ -4,7 +4,7 @@ export interface Schema {
name: string;
prefix?: string;
style?: string;
bundler?: 'webpack' | 'none';
bundler?: 'webpack' | 'none' | 'vite';
compiler?: 'babel' | 'swc';
skipFormat?: boolean;
directory?: string;

View File

@ -56,8 +56,9 @@
"bundler": {
"type": "string",
"description": "The bundler to use.",
"enum": ["webpack", "none"],
"default": "webpack"
"enum": ["webpack", "none", "vite"],
"default": "webpack",
"x-prompt": "Which bundler do you want to use?"
},
"linter": {
"description": "The tool to use for running lint checks.",
@ -96,5 +97,6 @@
"type": "boolean"
}
},
"required": []
"required": [],
"examplesFile": "../../../docs/application-examples.md"
}

View File

@ -1,5 +1,5 @@
export interface Schema {
bundler?: 'webpack' | 'none';
bundler?: 'webpack' | 'none' | 'vite';
unitTestRunner?: 'jest' | 'none';
e2eTestRunner?: 'cypress' | 'none';
skipFormat?: boolean;

View File

@ -9,7 +9,7 @@
"bundler": {
"type": "string",
"description": "The bundler to use.",
"enum": ["webpack", "none"],
"enum": ["webpack", "none", "vite"],
"default": "webpack"
},
"unitTestRunner": {

View File

@ -94,6 +94,8 @@
"@nrwl/tao": ["packages/tao"],
"@nrwl/tao/*": ["packages/tao/*"],
"@nrwl/typedoc-theme": ["/typedoc-theme/src/index.ts"],
"@nrwl/vite": ["packages/vite"],
"@nrwl/vite/*": ["packages/vite/*"],
"@nrwl/web": ["packages/web"],
"@nrwl/web/*": ["packages/web/*"],
"@nrwl/webpack": ["packages/webpack"],