feat(react): add Vite bundler option for buildable libraries (#13382)
This commit is contained in:
parent
c2db462992
commit
a63a25d2e2
@ -48,6 +48,11 @@
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"skipBabelConfig": {
|
||||
"description": "Do not generate a root babel.config.json (if babel is not needed).",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"js": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
@ -244,7 +249,7 @@
|
||||
"description": "The bundler to use.",
|
||||
"type": "string",
|
||||
"enum": ["vite", "webpack"],
|
||||
"x-prompt": "Which bundler do you want to use?",
|
||||
"x-prompt": "Which bundler do you want to use to build the application?",
|
||||
"default": "webpack"
|
||||
}
|
||||
},
|
||||
@ -339,8 +344,7 @@
|
||||
"unitTestRunner": {
|
||||
"type": "string",
|
||||
"enum": ["jest", "vitest", "none"],
|
||||
"description": "Test runner to use for unit tests.",
|
||||
"default": "jest"
|
||||
"description": "Test runner to use for unit tests."
|
||||
},
|
||||
"inSourceTests": {
|
||||
"type": "boolean",
|
||||
@ -384,7 +388,7 @@
|
||||
"buildable": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Generate a buildable library."
|
||||
"description": "Generate a buildable library. If a bundler is set then the library is buildable by default."
|
||||
},
|
||||
"importPath": {
|
||||
"type": "string",
|
||||
@ -419,11 +423,17 @@
|
||||
"description": "Split the project configuration into `<projectRoot>/project.json` rather than including it inside `workspace.json`.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"bundler": {
|
||||
"type": "string",
|
||||
"description": "The bundler to use.",
|
||||
"enum": ["vite", "rollup"],
|
||||
"x-prompt": "Which bundler would you like to use to build the library?"
|
||||
},
|
||||
"compiler": {
|
||||
"type": "string",
|
||||
"enum": ["babel", "swc"],
|
||||
"default": "babel",
|
||||
"description": "Which compiler to use."
|
||||
"description": "Which compiler to use. Does not apply if bundler is set to Vite."
|
||||
},
|
||||
"skipPackageJson": {
|
||||
"description": "Do not add dependencies to `package.json`.",
|
||||
|
||||
@ -31,6 +31,11 @@
|
||||
"enum": ["react", "none"],
|
||||
"default": "react",
|
||||
"x-prompt": "What UI framework plugin should Vite use?"
|
||||
},
|
||||
"includeLib": {
|
||||
"type": "boolean",
|
||||
"description": "Add dependencies needed to build libraries.",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"examplesFile": "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).\n\nYou can use it on its own like this:\n\n```bash\nnx g @nrwl/vite:configuration\n```\n\nHowever, 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`.\n\n## Examples\n\n### Install all the necessary dependencies for Vite and the React plugin\n\n```bash\nnx g @nrwl/vite:init --uiFramework=react\n```\n\n### Install all the necessary dependencies for Vite\n\n```bash\nnx g @nrwl/vite:init --uiFramework=none\n```\n",
|
||||
@ -60,6 +65,12 @@
|
||||
"x-dropdown": "project",
|
||||
"x-prompt": "What is the name of the project to set up a webpack for?"
|
||||
},
|
||||
"includeLib": {
|
||||
"type": "boolean",
|
||||
"description": "Add a library build option.",
|
||||
"default": false,
|
||||
"x-prompt": "Does this project contain a buildable library?"
|
||||
},
|
||||
"uiFramework": {
|
||||
"type": "string",
|
||||
"description": "UI Framework to use for Vite.",
|
||||
|
||||
@ -53,6 +53,11 @@
|
||||
"description": "Do not add dependencies to `package.json`.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"skipBabelConfig": {
|
||||
"description": "Do not generate a root babel.config.json (if babel is not needed).",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"required": [],
|
||||
|
||||
@ -97,7 +97,7 @@ describe('Build React libraries and apps', () => {
|
||||
|
||||
afterEach(() => {
|
||||
killPorts();
|
||||
cleanupProject();
|
||||
// cleanupProject();
|
||||
});
|
||||
|
||||
describe('Buildable libraries', () => {
|
||||
@ -252,4 +252,21 @@ export async function h() { return 'c'; }
|
||||
}).toThrow();
|
||||
}, 250000);
|
||||
});
|
||||
|
||||
it('should support bundling with Vite', async () => {
|
||||
const libName = uniq('lib');
|
||||
|
||||
runCLI(
|
||||
`generate @nrwl/react:lib ${libName} --bundler=vite --no-interactive`
|
||||
);
|
||||
|
||||
await runCLIAsync(`build ${libName}`);
|
||||
|
||||
checkFilesExist(
|
||||
`dist/libs/${libName}/package.json`,
|
||||
`dist/libs/${libName}/index.d.ts`,
|
||||
`dist/libs/${libName}/index.js`,
|
||||
`dist/libs/${libName}/index.mjs`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import {
|
||||
checkFilesExist,
|
||||
cleanupProject,
|
||||
createFile,
|
||||
exists,
|
||||
@ -32,38 +33,23 @@ describe('Vite Plugin', () => {
|
||||
`apps/${myApp}/index.html`,
|
||||
`
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang='en'>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta charset='utf-8' />
|
||||
<title>My App</title>
|
||||
<base href="/" />
|
||||
<base href='/' />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||
<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>
|
||||
<div id='root'></div>
|
||||
<script type='module' src='src/main.tsx'></script>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
);
|
||||
|
||||
createFile(
|
||||
`apps/${myApp}/src/environments/environment.prod.ts`,
|
||||
`export const environment = {
|
||||
production: true,
|
||||
myTestVar: 'MyProductionValue',
|
||||
};`
|
||||
);
|
||||
createFile(
|
||||
`apps/${myApp}/src/environments/environment.ts`,
|
||||
`export const environment = {
|
||||
production: false,
|
||||
myTestVar: 'MyDevelopmentValue',
|
||||
};`
|
||||
);
|
||||
|
||||
updateFile(
|
||||
`apps/${myApp}/src/app/app.tsx`,
|
||||
`
|
||||
@ -168,20 +154,6 @@ describe('Vite Plugin', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should build application and replace files', async () => {
|
||||
runCLI(`build ${myApp}`);
|
||||
expect(readFile(`dist/apps/${myApp}/index.html`)).toBeDefined();
|
||||
const fileArray = listFiles(`dist/apps/${myApp}/assets`);
|
||||
const mainBundle = fileArray.find((file) => file.endsWith('.js'));
|
||||
expect(readFile(`dist/apps/${myApp}/assets/${mainBundle}`)).toContain(
|
||||
'MyProductionValue'
|
||||
);
|
||||
expect(
|
||||
readFile(`dist/apps/${myApp}/assets/${mainBundle}`)
|
||||
).not.toContain('MyDevelopmentValue');
|
||||
rmDist();
|
||||
}, 200000);
|
||||
|
||||
it('should serve application in dev mode', async () => {
|
||||
const port = 4212;
|
||||
const p = await runCommandUntil(
|
||||
@ -206,76 +178,11 @@ describe('Vite Plugin', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('set up new React app with --bundler=vite option', () => {
|
||||
beforeEach(() => {
|
||||
proj = newProject();
|
||||
runCLI(`generate @nrwl/react:app ${myApp} --bundler=vite`);
|
||||
updateFile(
|
||||
`apps/${myApp}/src/environments/environment.prod.ts`,
|
||||
`export const environment = {
|
||||
production: true,
|
||||
myTestVar: 'MyProductionValue',
|
||||
};`
|
||||
);
|
||||
updateFile(
|
||||
`apps/${myApp}/src/environments/environment.ts`,
|
||||
`export const environment = {
|
||||
production: false,
|
||||
myTestVar: 'MyDevelopmentValue',
|
||||
};`
|
||||
);
|
||||
|
||||
updateFile(
|
||||
`apps/${myApp}/src/app/app.tsx`,
|
||||
`
|
||||
import { environment } from './../environments/environment';
|
||||
export function App() {
|
||||
return (
|
||||
<>
|
||||
<h1>{environment.myTestVar}</h1>
|
||||
<p>Welcome ${myApp}!</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
export default App;
|
||||
`
|
||||
);
|
||||
});
|
||||
afterEach(() => cleanupProject());
|
||||
it('should build application and replace files', async () => {
|
||||
runCLI(`build ${myApp}`);
|
||||
expect(readFile(`dist/apps/${myApp}/index.html`)).toBeDefined();
|
||||
const fileArray = listFiles(`dist/apps/${myApp}/assets`);
|
||||
const mainBundle = fileArray.find((file) => file.endsWith('.js'));
|
||||
expect(readFile(`dist/apps/${myApp}/assets/${mainBundle}`)).toContain(
|
||||
'MyProductionValue'
|
||||
);
|
||||
expect(
|
||||
readFile(`dist/apps/${myApp}/assets/${mainBundle}`)
|
||||
).not.toContain('MyDevelopmentValue');
|
||||
rmDist();
|
||||
}, 200000);
|
||||
});
|
||||
|
||||
describe('convert React webpack app to vite using the vite:configuration generator', () => {
|
||||
beforeEach(() => {
|
||||
proj = newProject();
|
||||
runCLI(`generate @nrwl/react:app ${myApp} --bundler=webpack`);
|
||||
runCLI(`generate @nrwl/vite:configuration ${myApp}`);
|
||||
updateFile(
|
||||
`apps/${myApp}/src/environments/environment.prod.ts`,
|
||||
`export const environment = {
|
||||
production: true,
|
||||
myTestVar: 'MyProductionValue',
|
||||
};`
|
||||
);
|
||||
updateFile(
|
||||
`apps/${myApp}/src/environments/environment.ts`,
|
||||
`export const environment = {
|
||||
production: false,
|
||||
myTestVar: 'MyDevelopmentValue',
|
||||
};`
|
||||
);
|
||||
|
||||
updateFile(
|
||||
`apps/${myApp}/src/app/app.tsx`,
|
||||
@ -294,18 +201,6 @@ describe('Vite Plugin', () => {
|
||||
);
|
||||
});
|
||||
afterEach(() => cleanupProject());
|
||||
it('should build application and replace files', async () => {
|
||||
runCLI(`build ${myApp}`);
|
||||
expect(readFile(`dist/apps/${myApp}/index.html`)).toBeDefined();
|
||||
const fileArray = listFiles(`dist/apps/${myApp}/assets`);
|
||||
const mainBundle = fileArray.find((file) => file.endsWith('.js'));
|
||||
expect(readFile(`dist/apps/${myApp}/assets/${mainBundle}`)).toContain(
|
||||
'MyProductionValue'
|
||||
);
|
||||
expect(
|
||||
readFile(`dist/apps/${myApp}/assets/${mainBundle}`)
|
||||
).not.toContain('MyDevelopmentValue');
|
||||
}, 200000);
|
||||
|
||||
it('should serve application in dev mode', async () => {
|
||||
const port = 4212;
|
||||
@ -348,7 +243,7 @@ describe('Vite Plugin', () => {
|
||||
readFile(`dist/apps/${myApp}/assets/${mainBundle}`)
|
||||
).toBeDefined();
|
||||
rmDist();
|
||||
}, 200000);
|
||||
}, 200_000);
|
||||
});
|
||||
|
||||
describe('convert @nrwl/web webpack app to vite using the vite:configuration generator', () => {
|
||||
@ -391,14 +286,16 @@ describe('Vite Plugin', () => {
|
||||
`Successfully ran target test for project ${myApp}`
|
||||
);
|
||||
});
|
||||
});
|
||||
}),
|
||||
100_000;
|
||||
});
|
||||
|
||||
describe('should be able to create libs that use vitest', () => {
|
||||
const lib = uniq('my-lib');
|
||||
beforeEach(() => {
|
||||
proj = newProject();
|
||||
});
|
||||
}),
|
||||
100_000;
|
||||
|
||||
it('should be able to run tests', async () => {
|
||||
runCLI(`generate @nrwl/react:lib ${lib} --unitTestRunner=vitest`);
|
||||
@ -408,7 +305,8 @@ describe('Vite Plugin', () => {
|
||||
expect(result.combinedOutput).toContain(
|
||||
`Successfully ran target test for project ${lib}`
|
||||
);
|
||||
});
|
||||
}),
|
||||
100_000;
|
||||
|
||||
it('should be able to run tests with inSourceTests set to true', async () => {
|
||||
runCLI(
|
||||
@ -432,6 +330,6 @@ describe('Vite Plugin', () => {
|
||||
|
||||
const result = await runCLIAsync(`test ${lib}`);
|
||||
expect(result.combinedOutput).toContain(`1 passed`);
|
||||
});
|
||||
}, 100_000);
|
||||
});
|
||||
});
|
||||
|
||||
@ -19,7 +19,6 @@ describe('app', () => {
|
||||
compiler: 'babel',
|
||||
e2eTestRunner: 'cypress',
|
||||
skipFormat: false,
|
||||
unitTestRunner: 'jest',
|
||||
name: 'myApp',
|
||||
linter: Linter.EsLint,
|
||||
style: 'css',
|
||||
@ -381,6 +380,12 @@ describe('app', () => {
|
||||
expect(targetConfig.build.options).toEqual({
|
||||
outputPath: 'dist/apps/my-app',
|
||||
});
|
||||
expect(
|
||||
appTree.exists(`apps/my-app/environments/environment.ts`)
|
||||
).toBeFalsy();
|
||||
expect(
|
||||
appTree.exists(`apps/my-app/environments/environment.prod.ts`)
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should setup the nrwl web dev server builder', async () => {
|
||||
|
||||
@ -80,6 +80,7 @@ export async function applicationGenerator(host: Tree, schema: Schema) {
|
||||
const initTask = await reactInitGenerator(host, {
|
||||
...options,
|
||||
skipFormat: true,
|
||||
skipBabelConfig: options.bundler === 'vite',
|
||||
});
|
||||
|
||||
tasks.push(initTask);
|
||||
@ -88,6 +89,10 @@ export async function applicationGenerator(host: Tree, schema: Schema) {
|
||||
addProject(host, options);
|
||||
|
||||
if (options.bundler === 'vite') {
|
||||
// We recommend users use `import.meta.env.MODE` and other variables in their code to differentiate between production and development.
|
||||
// See: https://vitejs.dev/guide/env-and-mode.html
|
||||
host.delete(joinPathFragments(options.appProjectRoot, 'src/environments'));
|
||||
|
||||
const viteTask = await viteConfigurationGenerator(host, {
|
||||
uiFramework: 'react',
|
||||
project: options.projectName,
|
||||
@ -111,9 +116,11 @@ export async function applicationGenerator(host: Tree, schema: Schema) {
|
||||
|
||||
const cypressTask = await addCypress(host, options);
|
||||
tasks.push(cypressTask);
|
||||
const jestTask = await addJest(host, options);
|
||||
tasks.push(jestTask);
|
||||
updateSpecConfig(host, options);
|
||||
if (options.unitTestRunner === 'jest') {
|
||||
const jestTask = await addJest(host, options);
|
||||
tasks.push(jestTask);
|
||||
updateSpecConfig(host, options);
|
||||
}
|
||||
const styledTask = addStyledModuleDependencies(host, options.styledModule);
|
||||
tasks.push(styledTask);
|
||||
const routingTask = addRouting(host, options);
|
||||
|
||||
@ -40,20 +40,7 @@ export function normalizeOptions(
|
||||
|
||||
assertValidStyle(options.style);
|
||||
|
||||
if (options.bundler === 'vite') {
|
||||
options.unitTestRunner = 'vitest';
|
||||
}
|
||||
|
||||
options.routing = options.routing ?? false;
|
||||
options.strict = options.strict ?? true;
|
||||
options.classComponent = options.classComponent ?? false;
|
||||
options.unitTestRunner = options.unitTestRunner ?? 'jest';
|
||||
options.e2eTestRunner = options.e2eTestRunner ?? 'cypress';
|
||||
options.compiler = options.compiler ?? 'babel';
|
||||
options.bundler = options.bundler ?? 'webpack';
|
||||
options.devServerPort ??= findFreePort(host);
|
||||
|
||||
return {
|
||||
const normalized = {
|
||||
...options,
|
||||
name: names(options.name).fileName,
|
||||
projectName: appProjectName,
|
||||
@ -63,5 +50,18 @@ export function normalizeOptions(
|
||||
fileName,
|
||||
styledModule,
|
||||
hasStyles: options.style !== 'none',
|
||||
};
|
||||
} as NormalizedSchema;
|
||||
|
||||
normalized.routing = normalized.routing ?? false;
|
||||
normalized.strict = normalized.strict ?? true;
|
||||
normalized.classComponent = normalized.classComponent ?? false;
|
||||
normalized.compiler = normalized.compiler ?? 'babel';
|
||||
normalized.bundler = normalized.bundler ?? 'webpack';
|
||||
normalized.unitTestRunner =
|
||||
normalized.unitTestRunner ??
|
||||
(normalized.bundler === 'vite' ? 'vitest' : 'jest');
|
||||
normalized.e2eTestRunner = normalized.e2eTestRunner ?? 'cypress';
|
||||
normalized.devServerPort ??= findFreePort(host);
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
@ -28,8 +28,8 @@ export function setDefaults(host: Tree, options: NormalizedSchema) {
|
||||
...prev,
|
||||
application: {
|
||||
style: options.style,
|
||||
unitTestRunner: options.unitTestRunner,
|
||||
linter: options.linter,
|
||||
bundler: options.bundler,
|
||||
...prev.application,
|
||||
},
|
||||
component: {
|
||||
@ -38,7 +38,6 @@ export function setDefaults(host: Tree, options: NormalizedSchema) {
|
||||
},
|
||||
library: {
|
||||
style: options.style,
|
||||
unitTestRunner: options.unitTestRunner,
|
||||
linter: options.linter,
|
||||
...prev.library,
|
||||
},
|
||||
|
||||
@ -7,7 +7,7 @@ export interface Schema {
|
||||
skipFormat: boolean;
|
||||
directory?: string;
|
||||
tags?: string;
|
||||
unitTestRunner: 'jest' | 'vitest' | 'none';
|
||||
unitTestRunner?: 'jest' | 'vitest' | 'none';
|
||||
inSourceTests?: boolean;
|
||||
/**
|
||||
* @deprecated
|
||||
@ -41,4 +41,5 @@ export interface NormalizedSchema extends Schema {
|
||||
fileName: string;
|
||||
styledModule: null | SupportedStyles;
|
||||
hasStyles: boolean;
|
||||
unitTestRunner: 'jest' | 'vitest' | 'none';
|
||||
}
|
||||
|
||||
@ -185,7 +185,7 @@
|
||||
"description": "The bundler to use.",
|
||||
"type": "string",
|
||||
"enum": ["vite", "webpack"],
|
||||
"x-prompt": "Which bundler do you want to use?",
|
||||
"x-prompt": "Which bundler do you want to use to build the application?",
|
||||
"default": "webpack"
|
||||
}
|
||||
},
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
export interface InitSchema {
|
||||
unitTestRunner?: 'jest' | 'vitest' | 'none';
|
||||
e2eTestRunner?: 'cypress' | 'none';
|
||||
skipBabelConfig?: boolean;
|
||||
skipFormat?: boolean;
|
||||
skipPackageJson?: boolean;
|
||||
js?: boolean;
|
||||
|
||||
@ -28,6 +28,11 @@
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"skipBabelConfig": {
|
||||
"description": "Do not generate a root babel.config.json (if babel is not needed).",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"js": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "<%= name %>",
|
||||
"version": "0.0.1",
|
||||
"main": "./index.js",
|
||||
"module": "./index.mjs",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./index.mjs",
|
||||
"require": "./index.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
{
|
||||
"name": "<%= name %>",
|
||||
"version": "0.0.1"
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title><%= className %> Demo</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/demo.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "<%= name %>",
|
||||
"version": "0.0.1",
|
||||
"main": "./index.js",
|
||||
"types": "./index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./index.mjs",
|
||||
"require": "./index.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* This a a demo file that can be helpful when developing components by serving and interacting with them in the browser.
|
||||
*/
|
||||
<% if (component) { %>
|
||||
import * as ReactDOM from 'react-dom/client';
|
||||
import { <%= className %> } from './index';
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
|
||||
root.render(
|
||||
<<%= className %> />
|
||||
);
|
||||
<% } else { %>
|
||||
import * as ReactDOM from 'react-dom/client';
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
|
||||
root.render(
|
||||
<h1><%= className %> Demo</h1>
|
||||
);
|
||||
<% } %>
|
||||
@ -0,0 +1,60 @@
|
||||
import type { Tree } from '@nrwl/devkit';
|
||||
import { Linter } from '@nrwl/linter';
|
||||
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
|
||||
import { normalizeOptions } from './normalize-options';
|
||||
|
||||
describe('normalizeOptions', () => {
|
||||
let tree: Tree;
|
||||
|
||||
beforeEach(() => {
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
});
|
||||
|
||||
it('should set unitTestRunner=jest and bundler=rollup by default', async () => {
|
||||
const options = normalizeOptions(tree, {
|
||||
name: 'test',
|
||||
style: 'css',
|
||||
linter: Linter.None,
|
||||
});
|
||||
|
||||
expect(options).toMatchObject({
|
||||
buildable: false,
|
||||
bundler: 'rollup',
|
||||
compiler: 'babel',
|
||||
unitTestRunner: 'jest',
|
||||
});
|
||||
});
|
||||
|
||||
it('should set unitTestRunner=vitest by default when bundler is vite', async () => {
|
||||
const options = normalizeOptions(tree, {
|
||||
name: 'test',
|
||||
style: 'css',
|
||||
linter: Linter.None,
|
||||
bundler: 'vite',
|
||||
});
|
||||
|
||||
expect(options).toMatchObject({
|
||||
buildable: true,
|
||||
bundler: 'vite',
|
||||
compiler: 'babel',
|
||||
unitTestRunner: 'vitest',
|
||||
});
|
||||
});
|
||||
|
||||
it('should set maintain unitTestRunner when bundler is vite', async () => {
|
||||
const options = normalizeOptions(tree, {
|
||||
name: 'test',
|
||||
style: 'css',
|
||||
linter: Linter.None,
|
||||
bundler: 'vite',
|
||||
unitTestRunner: 'jest',
|
||||
});
|
||||
|
||||
expect(options).toMatchObject({
|
||||
buildable: true,
|
||||
bundler: 'vite',
|
||||
compiler: 'babel',
|
||||
unitTestRunner: 'jest',
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,79 @@
|
||||
import {
|
||||
getImportPath,
|
||||
getProjects,
|
||||
getWorkspaceLayout,
|
||||
joinPathFragments,
|
||||
names,
|
||||
normalizePath,
|
||||
Tree,
|
||||
} from '@nrwl/devkit';
|
||||
import { assertValidStyle } from '../../../utils/assertion';
|
||||
import { NormalizedSchema } from '../library';
|
||||
import { Schema } from '../schema';
|
||||
|
||||
export function normalizeOptions(
|
||||
host: Tree,
|
||||
options: Schema
|
||||
): NormalizedSchema {
|
||||
const name = names(options.name).fileName;
|
||||
const projectDirectory = options.directory
|
||||
? `${names(options.directory).fileName}/${name}`
|
||||
: name;
|
||||
|
||||
const projectName = projectDirectory.replace(new RegExp('/', 'g'), '-');
|
||||
const fileName = projectName;
|
||||
const { libsDir, npmScope } = getWorkspaceLayout(host);
|
||||
const projectRoot = joinPathFragments(libsDir, projectDirectory);
|
||||
|
||||
const parsedTags = options.tags
|
||||
? options.tags.split(',').map((s) => s.trim())
|
||||
: [];
|
||||
|
||||
const importPath =
|
||||
options.importPath || getImportPath(npmScope, projectDirectory);
|
||||
|
||||
const normalized = {
|
||||
...options,
|
||||
compiler: options.compiler ?? 'babel',
|
||||
bundler: options.bundler ?? 'rollup',
|
||||
fileName,
|
||||
routePath: `/${name}`,
|
||||
name: projectName,
|
||||
projectRoot,
|
||||
projectDirectory,
|
||||
parsedTags,
|
||||
importPath,
|
||||
} as NormalizedSchema;
|
||||
|
||||
// Libraries with a bundler or is publishable must also be buildable.
|
||||
normalized.buildable = Boolean(
|
||||
options.bundler || options.buildable || options.publishable
|
||||
);
|
||||
|
||||
normalized.unitTestRunner =
|
||||
normalized.unitTestRunner ??
|
||||
(normalized.bundler === 'vite' ? 'vitest' : 'jest');
|
||||
|
||||
if (options.appProject) {
|
||||
const appProjectConfig = getProjects(host).get(options.appProject);
|
||||
|
||||
if (appProjectConfig.projectType !== 'application') {
|
||||
throw new Error(
|
||||
`appProject expected type of "application" but got "${appProjectConfig.projectType}"`
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
normalized.appMain = appProjectConfig.targets.build.options.main;
|
||||
normalized.appSourceRoot = normalizePath(appProjectConfig.sourceRoot);
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`Could not locate project main for ${options.appProject}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
assertValidStyle(normalized.style);
|
||||
|
||||
return normalized;
|
||||
}
|
||||
@ -225,7 +225,11 @@ describe('lib', () => {
|
||||
`);
|
||||
});
|
||||
it('should update jest.config.ts for babel', async () => {
|
||||
await libraryGenerator(appTree, { ...defaultSchema, compiler: 'babel' });
|
||||
await libraryGenerator(appTree, {
|
||||
...defaultSchema,
|
||||
buildable: true,
|
||||
compiler: 'babel',
|
||||
});
|
||||
expect(appTree.read('libs/my-lib/jest.config.ts', 'utf-8')).toContain(
|
||||
"['babel-jest', { presets: ['@nrwl/react/babel'] }]"
|
||||
);
|
||||
@ -280,6 +284,7 @@ describe('lib', () => {
|
||||
await libraryGenerator(appTree, {
|
||||
...defaultSchema,
|
||||
directory: 'myDir',
|
||||
buildable: true,
|
||||
compiler: 'babel',
|
||||
});
|
||||
expect(
|
||||
@ -725,6 +730,7 @@ describe('lib', () => {
|
||||
it('should install swc dependencies if needed', async () => {
|
||||
await libraryGenerator(appTree, {
|
||||
...defaultSchema,
|
||||
buildable: true,
|
||||
compiler: 'swc',
|
||||
});
|
||||
const packageJson = readJson(appTree, 'package.json');
|
||||
@ -764,6 +770,7 @@ describe('lib', () => {
|
||||
await libraryGenerator(appTree, {
|
||||
...defaultSchema,
|
||||
style,
|
||||
compiler: 'babel',
|
||||
name: 'myLib',
|
||||
});
|
||||
|
||||
|
||||
@ -44,10 +44,12 @@ import {
|
||||
typesReactRouterDomVersion,
|
||||
} from '../../utils/versions';
|
||||
import componentGenerator from '../component/component';
|
||||
import init from '../init/init';
|
||||
import initGenerator from '../init/init';
|
||||
import { Schema } from './schema';
|
||||
import { updateJestConfigContent } from '../../utils/jest-utils';
|
||||
import { vitestGenerator } from '@nrwl/vite';
|
||||
import { viteConfigurationGenerator, vitestGenerator } from '@nrwl/vite';
|
||||
import { normalizeOptions } from './lib/normalize-options';
|
||||
|
||||
export interface NormalizedSchema extends Schema {
|
||||
name: string;
|
||||
fileName: string;
|
||||
@ -57,6 +59,7 @@ export interface NormalizedSchema extends Schema {
|
||||
parsedTags: string[];
|
||||
appMain?: string;
|
||||
appSourceRoot?: string;
|
||||
unitTestRunner: 'jest' | 'vitest' | 'none';
|
||||
}
|
||||
|
||||
export async function libraryGenerator(host: Tree, schema: Schema) {
|
||||
@ -72,10 +75,11 @@ export async function libraryGenerator(host: Tree, schema: Schema) {
|
||||
options.style = 'none';
|
||||
}
|
||||
|
||||
const initTask = await init(host, {
|
||||
const initTask = await initGenerator(host, {
|
||||
...options,
|
||||
e2eTestRunner: 'none',
|
||||
skipFormat: true,
|
||||
skipBabelConfig: options.bundler === 'vite',
|
||||
});
|
||||
tasks.push(initTask);
|
||||
|
||||
@ -90,6 +94,18 @@ export async function libraryGenerator(host: Tree, schema: Schema) {
|
||||
updateBaseTsConfig(host, options);
|
||||
}
|
||||
|
||||
if (options.buildable && options.bundler === 'vite') {
|
||||
const viteTask = await viteConfigurationGenerator(host, {
|
||||
uiFramework: 'react',
|
||||
project: options.name,
|
||||
newProject: true,
|
||||
includeLib: true,
|
||||
inSourceTests: options.inSourceTests,
|
||||
includeVitest: true,
|
||||
});
|
||||
tasks.push(viteTask);
|
||||
}
|
||||
|
||||
if (options.unitTestRunner === 'jest') {
|
||||
const jestTask = await jestProjectGenerator(host, {
|
||||
...options,
|
||||
@ -110,7 +126,10 @@ export async function libraryGenerator(host: Tree, schema: Schema) {
|
||||
);
|
||||
host.write(jestConfigPath, updatedContent);
|
||||
}
|
||||
} else if (options.unitTestRunner === 'vitest') {
|
||||
} else if (
|
||||
options.unitTestRunner === 'vitest' &&
|
||||
options.bundler !== 'vite' // tests are already configured if bundler is vite
|
||||
) {
|
||||
const vitestTask = await vitestGenerator(host, {
|
||||
uiFramework: 'react',
|
||||
project: options.name,
|
||||
@ -299,22 +318,34 @@ function updateBaseTsConfig(host: Tree, options: NormalizedSchema) {
|
||||
}
|
||||
|
||||
function createFiles(host: Tree, options: NormalizedSchema) {
|
||||
const substitutions = {
|
||||
...options,
|
||||
...names(options.name),
|
||||
tmpl: '',
|
||||
offsetFromRoot: offsetFromRoot(options.projectRoot),
|
||||
rootTsConfigPath: getRelativePathToRootTsConfig(host, options.projectRoot),
|
||||
};
|
||||
|
||||
generateFiles(
|
||||
host,
|
||||
joinPathFragments(__dirname, './files/lib'),
|
||||
joinPathFragments(__dirname, './files/common'),
|
||||
options.projectRoot,
|
||||
{
|
||||
...options,
|
||||
...names(options.name),
|
||||
tmpl: '',
|
||||
offsetFromRoot: offsetFromRoot(options.projectRoot),
|
||||
rootTsConfigPath: getRelativePathToRootTsConfig(
|
||||
host,
|
||||
options.projectRoot
|
||||
),
|
||||
}
|
||||
substitutions
|
||||
);
|
||||
|
||||
if (options.bundler === 'vite') {
|
||||
generateFiles(
|
||||
host,
|
||||
joinPathFragments(__dirname, './files/vite'),
|
||||
options.projectRoot,
|
||||
substitutions
|
||||
);
|
||||
|
||||
if (host.exists(joinPathFragments(options.projectRoot, '.babelrc'))) {
|
||||
host.delete(joinPathFragments(options.projectRoot, '.babelrc'));
|
||||
}
|
||||
}
|
||||
|
||||
if (!options.publishable && !options.buildable) {
|
||||
host.delete(`${options.projectRoot}/package.json`);
|
||||
}
|
||||
@ -415,59 +446,6 @@ function readComponent(
|
||||
return { content, source };
|
||||
}
|
||||
|
||||
function normalizeOptions(host: Tree, options: Schema): NormalizedSchema {
|
||||
const name = names(options.name).fileName;
|
||||
const projectDirectory = options.directory
|
||||
? `${names(options.directory).fileName}/${name}`
|
||||
: name;
|
||||
|
||||
const projectName = projectDirectory.replace(new RegExp('/', 'g'), '-');
|
||||
const fileName = projectName;
|
||||
const { libsDir, npmScope } = getWorkspaceLayout(host);
|
||||
const projectRoot = joinPathFragments(libsDir, projectDirectory);
|
||||
|
||||
const parsedTags = options.tags
|
||||
? options.tags.split(',').map((s) => s.trim())
|
||||
: [];
|
||||
|
||||
const importPath =
|
||||
options.importPath || getImportPath(npmScope, projectDirectory);
|
||||
|
||||
const normalized: NormalizedSchema = {
|
||||
...options,
|
||||
fileName,
|
||||
routePath: `/${name}`,
|
||||
name: projectName,
|
||||
projectRoot,
|
||||
projectDirectory,
|
||||
parsedTags,
|
||||
importPath,
|
||||
};
|
||||
|
||||
if (options.appProject) {
|
||||
const appProjectConfig = getProjects(host).get(options.appProject);
|
||||
|
||||
if (appProjectConfig.projectType !== 'application') {
|
||||
throw new Error(
|
||||
`appProject expected type of "application" but got "${appProjectConfig.projectType}"`
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
normalized.appMain = appProjectConfig.targets.build.options.main;
|
||||
normalized.appSourceRoot = normalizePath(appProjectConfig.sourceRoot);
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`Could not locate project main for ${options.appProject}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
assertValidStyle(normalized.style);
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function updateLibPackageNpmScope(host: Tree, options: NormalizedSchema) {
|
||||
return updateJson(host, `${options.projectRoot}/package.json`, (json) => {
|
||||
json.name = options.importPath;
|
||||
|
||||
@ -2,27 +2,28 @@ import { Linter } from '@nrwl/linter';
|
||||
import { SupportedStyles } from '../../../typings/style';
|
||||
|
||||
export interface Schema {
|
||||
name: string;
|
||||
directory?: string;
|
||||
style: SupportedStyles;
|
||||
skipTsConfig: boolean;
|
||||
skipFormat: boolean;
|
||||
tags?: string;
|
||||
pascalCaseFiles?: boolean;
|
||||
routing?: boolean;
|
||||
appProject?: string;
|
||||
unitTestRunner: 'jest' | 'vitest' | 'none';
|
||||
inSourceTests?: boolean;
|
||||
linter: Linter;
|
||||
component?: boolean;
|
||||
publishable?: boolean;
|
||||
buildable?: boolean;
|
||||
importPath?: string;
|
||||
js?: boolean;
|
||||
globalCss?: boolean;
|
||||
strict?: boolean;
|
||||
setParserOptionsProject?: boolean;
|
||||
standaloneConfig?: boolean;
|
||||
bundler?: 'rollup' | 'vite';
|
||||
compiler?: 'babel' | 'swc';
|
||||
component?: boolean;
|
||||
directory?: string;
|
||||
globalCss?: boolean;
|
||||
importPath?: string;
|
||||
inSourceTests?: boolean;
|
||||
js?: boolean;
|
||||
linter: Linter;
|
||||
name: string;
|
||||
pascalCaseFiles?: boolean;
|
||||
publishable?: boolean;
|
||||
routing?: boolean;
|
||||
setParserOptionsProject?: boolean;
|
||||
skipFormat?: boolean;
|
||||
skipPackageJson?: boolean;
|
||||
skipTsConfig?: boolean;
|
||||
standaloneConfig?: boolean;
|
||||
strict?: boolean;
|
||||
style: SupportedStyles;
|
||||
tags?: string;
|
||||
unitTestRunner?: 'jest' | 'vitest' | 'none';
|
||||
}
|
||||
|
||||
@ -81,8 +81,7 @@
|
||||
"unitTestRunner": {
|
||||
"type": "string",
|
||||
"enum": ["jest", "vitest", "none"],
|
||||
"description": "Test runner to use for unit tests.",
|
||||
"default": "jest"
|
||||
"description": "Test runner to use for unit tests."
|
||||
},
|
||||
"inSourceTests": {
|
||||
"type": "boolean",
|
||||
@ -126,7 +125,7 @@
|
||||
"buildable": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Generate a buildable library."
|
||||
"description": "Generate a buildable library. If a bundler is set then the library is buildable by default."
|
||||
},
|
||||
"importPath": {
|
||||
"type": "string",
|
||||
@ -161,11 +160,17 @@
|
||||
"description": "Split the project configuration into `<projectRoot>/project.json` rather than including it inside `workspace.json`.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"bundler": {
|
||||
"type": "string",
|
||||
"description": "The bundler to use.",
|
||||
"enum": ["vite", "rollup"],
|
||||
"x-prompt": "Which bundler would you like to use to build the library?"
|
||||
},
|
||||
"compiler": {
|
||||
"type": "string",
|
||||
"enum": ["babel", "swc"],
|
||||
"default": "babel",
|
||||
"description": "Which compiler to use."
|
||||
"description": "Which compiler to use. Does not apply if bundler is set to Vite."
|
||||
},
|
||||
"skipPackageJson": {
|
||||
"description": "Do not add dependencies to `package.json`.",
|
||||
|
||||
@ -4,25 +4,42 @@ import 'dotenv/config';
|
||||
import { getBuildConfig } from '../../utils/options-utils';
|
||||
import { ViteBuildExecutorOptions } from './schema';
|
||||
import { copyAssets } from '@nrwl/js';
|
||||
import { existsSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
export default async function viteBuildExecutor(
|
||||
options: ViteBuildExecutorOptions,
|
||||
context: ExecutorContext
|
||||
) {
|
||||
if (options.assets) {
|
||||
const projectRoot = context.workspace.projects[context.projectName].root;
|
||||
let assets = options.assets;
|
||||
|
||||
// Copy package.json as an asset if it exists
|
||||
if (existsSync(join(projectRoot, 'package.json'))) {
|
||||
assets ??= [];
|
||||
assets.push({
|
||||
input: '.',
|
||||
output: '.',
|
||||
glob: 'package.json',
|
||||
});
|
||||
}
|
||||
|
||||
logger.info(`NX Vite build starting ...`);
|
||||
const buildResult = await runInstance(await getBuildConfig(options, context));
|
||||
logger.info(`NX Vite build finished ...`);
|
||||
logger.info(`NX Vite files available in ${options.outputPath}`);
|
||||
|
||||
// TODO(jack): handle watch once we add that option
|
||||
if (assets) {
|
||||
await copyAssets(
|
||||
{
|
||||
outputPath: options.outputPath,
|
||||
assets: options.assets,
|
||||
assets: assets,
|
||||
},
|
||||
context
|
||||
);
|
||||
}
|
||||
|
||||
logger.info(`NX Vite build starting ...`);
|
||||
await runInstance(await getBuildConfig(options, context));
|
||||
logger.info(`NX Vite build finished ...`);
|
||||
logger.info(`NX Vite files available in ${options.outputPath}`);
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,9 @@
|
||||
import { addDependenciesToPackageJson, readJson, Tree } from '@nrwl/devkit';
|
||||
import {
|
||||
addDependenciesToPackageJson,
|
||||
addProjectConfiguration,
|
||||
readJson,
|
||||
Tree,
|
||||
} from '@nrwl/devkit';
|
||||
import { createTreeWithEmptyV1Workspace } from '@nrwl/devkit/testing';
|
||||
import { nxVersion } from '../../utils/versions';
|
||||
|
||||
@ -116,4 +121,27 @@ describe('@nrwl/vite:configuration', () => {
|
||||
expect(viteConfig).toContain('test');
|
||||
});
|
||||
});
|
||||
|
||||
describe('library mode', () => {
|
||||
beforeEach(async () => {
|
||||
tree = createTreeWithEmptyV1Workspace();
|
||||
addProjectConfiguration(tree, 'my-lib', {
|
||||
root: 'my-lib',
|
||||
});
|
||||
});
|
||||
|
||||
it('should add config for building library', async () => {
|
||||
await viteConfigurationGenerator(tree, {
|
||||
uiFramework: 'react',
|
||||
includeLib: true,
|
||||
project: 'my-lib',
|
||||
newProject: true,
|
||||
});
|
||||
|
||||
const viteConfig = tree.read('my-lib/vite.config.ts').toString();
|
||||
|
||||
expect(viteConfig).toMatch('build: {');
|
||||
expect(viteConfig).toMatch("external: ['react'");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -35,6 +35,7 @@ export async function viteConfigurationGenerator(tree: Tree, schema: Schema) {
|
||||
|
||||
const initTask = await initGenerator(tree, {
|
||||
uiFramework: schema.uiFramework,
|
||||
includeLib: schema.includeLib,
|
||||
});
|
||||
tasks.push(initTask);
|
||||
|
||||
|
||||
@ -4,4 +4,5 @@ export interface Schema {
|
||||
newProject?: boolean;
|
||||
includeVitest?: boolean;
|
||||
inSourceTests?: boolean;
|
||||
includeLib?: boolean;
|
||||
}
|
||||
|
||||
@ -16,6 +16,12 @@
|
||||
"x-dropdown": "project",
|
||||
"x-prompt": "What is the name of the project to set up a webpack for?"
|
||||
},
|
||||
"includeLib": {
|
||||
"type": "boolean",
|
||||
"description": "Add a library build option.",
|
||||
"default": false,
|
||||
"x-prompt": "Does this project contain a buildable library?"
|
||||
},
|
||||
"uiFramework": {
|
||||
"type": "string",
|
||||
"description": "UI Framework to use for Vite.",
|
||||
|
||||
@ -5,17 +5,17 @@ import {
|
||||
Tree,
|
||||
updateJson,
|
||||
} from '@nrwl/devkit';
|
||||
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';
|
||||
|
||||
import {
|
||||
jsdomVersion,
|
||||
nxVersion,
|
||||
vitePluginDtsVersion,
|
||||
vitePluginEslintVersion,
|
||||
vitePluginReactVersion,
|
||||
viteVersion,
|
||||
vitestUiVersion,
|
||||
vitestVersion,
|
||||
viteTsConfigPathsVersion,
|
||||
jsdomVersion,
|
||||
viteVersion,
|
||||
} from '../../utils/versions';
|
||||
import { Schema } from './schema';
|
||||
|
||||
@ -39,6 +39,10 @@ function checkDependenciesInstalled(host: Tree, schema: Schema) {
|
||||
devDependencies['@vitejs/plugin-react'] = vitePluginReactVersion;
|
||||
}
|
||||
|
||||
if (schema.includeLib) {
|
||||
devDependencies['vite-plugin-dts'] = vitePluginDtsVersion;
|
||||
}
|
||||
|
||||
return addDependenciesToPackageJson(host, dependencies, devDependencies);
|
||||
}
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
export interface Schema {
|
||||
uiFramework: 'react' | 'none';
|
||||
includeLib?: boolean;
|
||||
}
|
||||
|
||||
@ -11,6 +11,11 @@
|
||||
"enum": ["react", "none"],
|
||||
"default": "react",
|
||||
"x-prompt": "What UI framework plugin should Vite use?"
|
||||
},
|
||||
"includeLib": {
|
||||
"type": "boolean",
|
||||
"description": "Add dependencies needed to build libraries.",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"examplesFile": "../../../docs/init-examples.md"
|
||||
|
||||
@ -107,8 +107,10 @@ describe('vitest generator', () => {
|
||||
import react from '@vitejs/plugin-react';
|
||||
import ViteTsConfigPathsPlugin from 'vite-tsconfig-paths';
|
||||
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
|
||||
react(),
|
||||
ViteTsConfigPathsPlugin({
|
||||
root: '../../',
|
||||
@ -116,6 +118,7 @@ describe('vitest generator', () => {
|
||||
}),
|
||||
],
|
||||
|
||||
|
||||
test: {
|
||||
globals: true,
|
||||
environment: 'jsdom',
|
||||
@ -140,14 +143,17 @@ describe('vitest generator', () => {
|
||||
import react from '@vitejs/plugin-react';
|
||||
import ViteTsConfigPathsPlugin from 'vite-tsconfig-paths';
|
||||
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
|
||||
react(),
|
||||
ViteTsConfigPathsPlugin({
|
||||
root: '../../',
|
||||
projects: ['tsconfig.base.json'],
|
||||
}),
|
||||
],
|
||||
|
||||
define: {
|
||||
'import.meta.vitest': undefined
|
||||
},
|
||||
|
||||
@ -150,21 +150,23 @@ export function addOrChangeBuildTarget(
|
||||
options: buildOptions,
|
||||
configurations: {
|
||||
development: {},
|
||||
production: {
|
||||
fileReplacements: [
|
||||
{
|
||||
replace: joinPathFragments(
|
||||
project.sourceRoot,
|
||||
'environments/environment.ts'
|
||||
),
|
||||
with: joinPathFragments(
|
||||
project.sourceRoot,
|
||||
'environments/environment.prod.ts'
|
||||
),
|
||||
production: options.includeLib
|
||||
? {}
|
||||
: {
|
||||
fileReplacements: [
|
||||
{
|
||||
replace: joinPathFragments(
|
||||
project.sourceRoot,
|
||||
'environments/environment.ts'
|
||||
),
|
||||
with: joinPathFragments(
|
||||
project.sourceRoot,
|
||||
'environments/environment.prod.ts'
|
||||
),
|
||||
},
|
||||
],
|
||||
sourceMap: false,
|
||||
},
|
||||
],
|
||||
sourceMap: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -356,7 +358,8 @@ export function writeViteConfig(tree: Tree, options: Schema) {
|
||||
|
||||
let viteConfigContent = '';
|
||||
|
||||
const testOption = `test: {
|
||||
const testOption = options.includeVitest
|
||||
? `test: {
|
||||
globals: true,
|
||||
environment: 'jsdom',
|
||||
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
||||
@ -365,11 +368,45 @@ export function writeViteConfig(tree: Tree, options: Schema) {
|
||||
? `includeSource: ['src/**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}']`
|
||||
: ''
|
||||
}
|
||||
},`;
|
||||
},`
|
||||
: '';
|
||||
|
||||
const defineOption = `define: {
|
||||
const defineOption = options.inSourceTests
|
||||
? `define: {
|
||||
'import.meta.vitest': undefined
|
||||
},`;
|
||||
},`
|
||||
: '';
|
||||
|
||||
const dtsPlugin = `dts({
|
||||
tsConfigFilePath: join(__dirname, 'tsconfig.lib.json'),
|
||||
// Faster builds by skipping tests. Set this to false to enable type checking.
|
||||
skipDiagnostics: true,
|
||||
}),`;
|
||||
|
||||
const buildOption = options.includeLib
|
||||
? `
|
||||
// Configuration for building your library.
|
||||
// See: https://vitejs.dev/guide/build.html#library-mode
|
||||
build: {
|
||||
lib: {
|
||||
// Could also be a dictionary or array of multiple entry points.
|
||||
entry: 'src/index.ts',
|
||||
name: '${options.project}',
|
||||
fileName: 'index',
|
||||
// Change this to the formats you want to support.
|
||||
// Don't forgot to update your package.json as well.
|
||||
formats: ['es', 'cjs']
|
||||
},
|
||||
rollupOptions: {
|
||||
// External packages that should not be bundled into your library.
|
||||
external: [${
|
||||
options.uiFramework === 'react'
|
||||
? "'react', 'react-dom', 'react/jsx-runtime'"
|
||||
: ''
|
||||
}]
|
||||
}
|
||||
},`
|
||||
: '';
|
||||
|
||||
switch (options.uiFramework) {
|
||||
case 'react':
|
||||
@ -378,17 +415,20 @@ ${options.includeVitest ? '/// <reference types="vitest" />' : ''}
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import ViteTsConfigPathsPlugin from 'vite-tsconfig-paths';
|
||||
${options.includeLib ? "import dts from 'vite-plugin-dts';" : ''}
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
${options.includeLib ? dtsPlugin : ''}
|
||||
react(),
|
||||
ViteTsConfigPathsPlugin({
|
||||
root: '${offsetFromRoot(projectConfig.root)}',
|
||||
projects: ['tsconfig.base.json'],
|
||||
}),
|
||||
],
|
||||
${options.inSourceTests ? defineOption : ''}
|
||||
${options.includeVitest ? testOption : ''}
|
||||
${buildOption}
|
||||
${defineOption}
|
||||
${testOption}
|
||||
});`;
|
||||
break;
|
||||
case 'none':
|
||||
@ -396,16 +436,19 @@ ${options.includeVitest ? '/// <reference types="vitest" />' : ''}
|
||||
${options.includeVitest ? '/// <reference types="vitest" />' : ''}
|
||||
import { defineConfig } from 'vite';
|
||||
import ViteTsConfigPathsPlugin from 'vite-tsconfig-paths';
|
||||
${options.includeLib ? "import dts from 'vite-plugin-dts';" : ''}
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
${options.includeLib ? dtsPlugin : ''}
|
||||
ViteTsConfigPathsPlugin({
|
||||
root: '${offsetFromRoot(projectConfig.root)}',
|
||||
projects: ['tsconfig.base.json'],
|
||||
}),
|
||||
],
|
||||
${options.inSourceTests ? defineOption : ''}
|
||||
${options.includeVitest ? testOption : ''}
|
||||
${buildOption}
|
||||
${defineOption}
|
||||
${testOption}
|
||||
});`;
|
||||
break;
|
||||
default:
|
||||
|
||||
@ -8,3 +8,4 @@ export const vitePluginVueVersion = '^3.2.0';
|
||||
export const vitePluginVueJsxVersion = '^2.1.1';
|
||||
export const viteTsConfigPathsVersion = '^3.5.2';
|
||||
export const jsdomVersion = '~20.0.3';
|
||||
export const vitePluginDtsVersion = '~1.7.1';
|
||||
|
||||
@ -168,6 +168,12 @@ describe('app', () => {
|
||||
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();
|
||||
expect(
|
||||
tree.exists(`apps/my-app/environments/environment.ts`)
|
||||
).toBeFalsy();
|
||||
expect(
|
||||
tree.exists(`apps/my-app/environments/environment.prod.ts`)
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should extend from root tsconfig.json when no tsconfig.base.json', async () => {
|
||||
|
||||
@ -192,6 +192,8 @@ export async function applicationGenerator(host: Tree, schema: Schema) {
|
||||
const webTask = await webInitGenerator(host, {
|
||||
...options,
|
||||
skipFormat: true,
|
||||
// Vite does not use babel by default
|
||||
skipBabelConfig: options.bundler === 'vite',
|
||||
});
|
||||
tasks.push(webTask);
|
||||
|
||||
@ -199,6 +201,10 @@ export async function applicationGenerator(host: Tree, schema: Schema) {
|
||||
await addProject(host, options);
|
||||
|
||||
if (options.bundler === 'vite') {
|
||||
// We recommend users use `import.meta.env.MODE` and other variables in their code to differentiate between production and development.
|
||||
// See: https://vitejs.dev/guide/env-and-mode.html
|
||||
host.delete(joinPathFragments(options.appProjectRoot, 'src/environments'));
|
||||
|
||||
const viteTask = await viteConfigurationGenerator(host, {
|
||||
uiFramework: 'react',
|
||||
project: options.projectName,
|
||||
|
||||
@ -42,14 +42,16 @@ function updateDependencies(tree: Tree, schema: Schema) {
|
||||
);
|
||||
}
|
||||
|
||||
function initRootBabelConfig(tree: Tree) {
|
||||
function initRootBabelConfig(tree: Tree, schema: Schema) {
|
||||
if (tree.exists('/babel.config.json') || tree.exists('/babel.config.js')) {
|
||||
return;
|
||||
}
|
||||
|
||||
writeJson(tree, '/babel.config.json', {
|
||||
babelrcRoots: ['*'], // Make sure .babelrc files other than root can be loaded in a monorepo
|
||||
});
|
||||
if (!schema.skipBabelConfig) {
|
||||
writeJson(tree, '/babel.config.json', {
|
||||
babelrcRoots: ['*'], // Make sure .babelrc files other than root can be loaded in a monorepo
|
||||
});
|
||||
}
|
||||
|
||||
const workspaceConfiguration = readWorkspaceConfiguration(tree);
|
||||
|
||||
@ -80,7 +82,7 @@ export async function webInitGenerator(tree: Tree, schema: Schema) {
|
||||
const installTask = updateDependencies(tree, schema);
|
||||
tasks.push(installTask);
|
||||
}
|
||||
initRootBabelConfig(tree);
|
||||
initRootBabelConfig(tree, schema);
|
||||
if (!schema.skipFormat) {
|
||||
await formatFiles(tree);
|
||||
}
|
||||
|
||||
1
packages/web/src/generators/init/schema.d.ts
vendored
1
packages/web/src/generators/init/schema.d.ts
vendored
@ -4,4 +4,5 @@ export interface Schema {
|
||||
e2eTestRunner?: 'cypress' | 'none';
|
||||
skipFormat?: boolean;
|
||||
skipPackageJson?: boolean;
|
||||
skipBabelConfig?: boolean;
|
||||
}
|
||||
|
||||
@ -33,6 +33,11 @@
|
||||
"description": "Do not add dependencies to `package.json`.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"skipBabelConfig": {
|
||||
"description": "Do not generate a root babel.config.json (if babel is not needed).",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user