Skip to content

Commit

Permalink
feat(react): add Vite bundler option for buildable libraries
Browse files Browse the repository at this point in the history
  • Loading branch information
jaysoo authored and Jack Hsu committed Nov 25, 2022
1 parent 5aaba51 commit f4dfff2
Show file tree
Hide file tree
Showing 38 changed files with 368 additions and 201 deletions.
20 changes: 15 additions & 5 deletions docs/generated/packages/react.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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"
}
},
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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"],
"default": "rollup"
},
"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`.",
Expand Down
6 changes: 6 additions & 0 deletions docs/generated/packages/vite.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,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.",
Expand Down
5 changes: 5 additions & 0 deletions docs/generated/packages/web.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [],
Expand Down
18 changes: 18 additions & 0 deletions e2e/react/src/react-package.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,4 +252,22 @@ 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} --buildable --bundler=vite --no-interactive`
);

const result = await runCLIAsync(`build ${libName}`);

expect(result).toMatch(/Vite builder finished/);

checkFilesExist(
`dist/libs/${libName}/package.json`,
`dist/libs/${libName}/index.js`,
`dist/libs/${libName}/index.mjs`
);
});
});
123 changes: 9 additions & 114 deletions e2e/vite/src/vite.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
checkFilesExist,
cleanupProject,
createFile,
exists,
Expand Down Expand Up @@ -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`,
`
Expand All @@ -83,7 +69,7 @@ describe('Vite Plugin', () => {
createFile(
`apps/${myApp}/vite.config.ts`,
`
/// <reference types="vitest" />
/// <reference types='vitest' />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import plugin from 'vite-tsconfig-paths';
Expand Down Expand Up @@ -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(
Expand All @@ -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`,
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ describe('app', () => {
compiler: 'babel',
e2eTestRunner: 'cypress',
skipFormat: false,
unitTestRunner: 'jest',
name: 'myApp',
linter: Linter.EsLint,
style: 'css',
Expand Down Expand Up @@ -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 () => {
Expand Down
13 changes: 10 additions & 3 deletions packages/react/src/generators/application/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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,
Expand All @@ -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);
Expand Down
30 changes: 15 additions & 15 deletions packages/react/src/generators/application/lib/normalize-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export function setDefaults(host: Tree, options: NormalizedSchema) {
style: options.style,
unitTestRunner: options.unitTestRunner,
linter: options.linter,
bundler: options.bundler,
...prev.application,
},
component: {
Expand Down
Loading

0 comments on commit f4dfff2

Please sign in to comment.