@nx/web
Version:
483 lines (482 loc) • 21.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.applicationGenerator = applicationGenerator;
exports.applicationGeneratorInternal = applicationGeneratorInternal;
const devkit_1 = require("@nx/devkit");
const project_name_and_root_utils_1 = require("@nx/devkit/src/generators/project-name-and-root-utils");
const js_1 = require("@nx/js");
const versions_1 = require("@nx/js/src/utils/versions");
const path_1 = require("path");
const versions_2 = require("../../utils/versions");
const init_1 = require("../init/init");
const get_npm_scope_1 = require("@nx/js/src/utils/package-json/get-npm-scope");
const has_webpack_plugin_1 = require("../../utils/has-webpack-plugin");
const target_defaults_utils_1 = require("@nx/devkit/src/generators/target-defaults-utils");
const log_show_project_command_1 = require("@nx/devkit/src/utils/log-show-project-command");
const static_serve_configuration_1 = require("../static-serve/static-serve-configuration");
const ts_solution_setup_1 = require("@nx/js/src/utils/typescript/ts-solution-setup");
function createApplicationFiles(tree, options) {
const rootTsConfigPath = (0, js_1.getRelativePathToRootTsConfig)(tree, options.appProjectRoot);
if (options.bundler === 'vite') {
(0, devkit_1.generateFiles)(tree, (0, path_1.join)(__dirname, './files/app-vite'), options.appProjectRoot, {
...options,
tmpl: '',
offsetFromRoot: (0, devkit_1.offsetFromRoot)(options.appProjectRoot),
rootTsConfigPath,
});
}
else {
const rootOffset = (0, devkit_1.offsetFromRoot)(options.appProjectRoot);
(0, devkit_1.generateFiles)(tree, (0, path_1.join)(__dirname, './files/app-webpack'), options.appProjectRoot, {
...options,
tmpl: '',
offsetFromRoot: rootOffset,
rootTsConfigPath,
webpackPluginOptions: (0, has_webpack_plugin_1.hasWebpackPlugin)(tree)
? {
compiler: options.compiler,
target: 'web',
outputPath: options.isUsingTsSolutionConfig
? 'dist'
: (0, devkit_1.joinPathFragments)(rootOffset, 'dist', options.appProjectRoot !== '.'
? options.appProjectRoot
: options.projectName),
tsConfig: './tsconfig.app.json',
main: './src/main.ts',
assets: ['./src/favicon.ico', './src/assets'],
index: './src/index.html',
baseHref: '/',
styles: [`./src/styles.${options.style}`],
}
: null,
});
if (options.unitTestRunner === 'none') {
tree.delete((0, path_1.join)(options.appProjectRoot, './src/app/app.element.spec.ts'));
}
}
if (options.isUsingTsSolutionConfig) {
(0, devkit_1.updateJson)(tree, (0, devkit_1.joinPathFragments)(options.appProjectRoot, 'tsconfig.json'), () => ({
extends: rootTsConfigPath,
files: [],
include: [],
references: [
{
path: './tsconfig.app.json',
},
],
}));
}
else {
(0, devkit_1.updateJson)(tree, (0, devkit_1.joinPathFragments)(options.appProjectRoot, 'tsconfig.json'), (json) => {
return {
...json,
compilerOptions: {
...(json.compilerOptions || {}),
strict: options.strict,
},
};
});
}
}
async function setupBundler(tree, options) {
const main = (0, devkit_1.joinPathFragments)(options.appProjectRoot, 'src/main.ts');
const tsConfig = (0, devkit_1.joinPathFragments)(options.appProjectRoot, 'tsconfig.app.json');
const assets = [
(0, devkit_1.joinPathFragments)(options.appProjectRoot, 'src/favicon.ico'),
(0, devkit_1.joinPathFragments)(options.appProjectRoot, 'src/assets'),
];
if (options.bundler === 'webpack') {
const { configurationGenerator } = (0, devkit_1.ensurePackage)('@nx/webpack', versions_2.nxVersion);
await configurationGenerator(tree, {
target: 'web',
project: options.projectName,
main,
tsConfig,
compiler: options.compiler ?? 'babel',
devServer: true,
webpackConfig: (0, devkit_1.joinPathFragments)(options.appProjectRoot, 'webpack.config.js'),
skipFormat: true,
addPlugin: options.addPlugin,
});
const project = (0, devkit_1.readProjectConfiguration)(tree, options.projectName);
if (project.targets?.build) {
const prodConfig = project.targets.build.configurations.production;
const buildOptions = project.targets.build.options;
buildOptions.assets = assets;
buildOptions.index = (0, devkit_1.joinPathFragments)(options.appProjectRoot, 'src/index.html');
buildOptions.baseHref = '/';
buildOptions.styles = [
(0, devkit_1.joinPathFragments)(options.appProjectRoot, `src/styles.${options.style}`),
];
// We can delete that, because this projest is an application
// and applications have a .babelrc file in their root dir.
// So Nx will find it and use it
delete buildOptions.babelUpwardRootMode;
buildOptions.scripts = [];
prodConfig.fileReplacements = [
{
replace: (0, devkit_1.joinPathFragments)(options.appProjectRoot, `src/environments/environment.ts`),
with: (0, devkit_1.joinPathFragments)(options.appProjectRoot, `src/environments/environment.prod.ts`),
},
];
prodConfig.optimization = true;
prodConfig.outputHashing = 'all';
prodConfig.sourceMap = false;
prodConfig.namedChunks = false;
prodConfig.extractLicenses = true;
prodConfig.vendorChunk = false;
(0, devkit_1.updateProjectConfiguration)(tree, options.projectName, project);
}
// TODO(jack): Flush this out... no bundler should be possible for web but the experience isn't holistic due to missing features (e.g. writing index.html).
}
else if (options.bundler === 'none') {
const project = (0, devkit_1.readProjectConfiguration)(tree, options.projectName);
(0, target_defaults_utils_1.addBuildTargetDefaults)(tree, `@nx/js:${options.compiler}`);
project.targets ??= {};
project.targets.build = {
executor: `@nx/js:${options.compiler}`,
outputs: ['{options.outputPath}'],
options: {
main,
outputPath: (0, devkit_1.joinPathFragments)('dist', options.appProjectRoot),
tsConfig,
},
};
(0, devkit_1.updateProjectConfiguration)(tree, options.projectName, project);
}
else {
throw new Error('Unsupported bundler type');
}
}
async function addProject(tree, options) {
const packageJson = {
name: options.importPath,
version: '0.0.1',
private: true,
};
if (!options.useProjectJson) {
if (options.projectName !== options.importPath) {
packageJson.nx = { name: options.projectName };
}
if (options.parsedTags?.length) {
packageJson.nx ??= {};
packageJson.nx.tags = options.parsedTags;
}
}
else {
(0, devkit_1.addProjectConfiguration)(tree, options.projectName, {
projectType: 'application',
root: options.appProjectRoot,
sourceRoot: (0, devkit_1.joinPathFragments)(options.appProjectRoot, 'src'),
tags: options.parsedTags,
targets: {},
});
}
if (!options.useProjectJson || options.isUsingTsSolutionConfig) {
(0, devkit_1.writeJson)(tree, (0, devkit_1.joinPathFragments)(options.appProjectRoot, 'package.json'), packageJson);
}
}
function setDefaults(tree, options) {
const nxJson = (0, devkit_1.readNxJson)(tree);
nxJson.generators = nxJson.generators || {};
nxJson.generators['@nx/web:application'] = {
style: options.style,
linter: options.linter,
unitTestRunner: options.unitTestRunner,
e2eTestRunner: options.e2eTestRunner,
...nxJson.generators['@nx/web:application'],
};
(0, devkit_1.updateNxJson)(tree, nxJson);
}
async function applicationGenerator(host, schema) {
return await applicationGeneratorInternal(host, {
addPlugin: false,
...schema,
});
}
async function applicationGeneratorInternal(host, schema) {
const options = await normalizeOptions(host, schema);
if (options.isUsingTsSolutionConfig) {
await (0, ts_solution_setup_1.addProjectToTsSolutionWorkspace)(host, options.appProjectRoot);
}
const tasks = [];
const jsInitTask = await (0, js_1.initGenerator)(host, {
js: false,
skipFormat: true,
platform: 'web',
});
tasks.push(jsInitTask);
const webTask = await (0, init_1.webInitGenerator)(host, {
...options,
skipFormat: true,
});
tasks.push(webTask);
await addProject(host, options);
if (options.bundler !== 'vite') {
await setupBundler(host, options);
}
createApplicationFiles(host, options);
if (options.linter === 'eslint') {
const { lintProjectGenerator } = (0, devkit_1.ensurePackage)('@nx/eslint', versions_2.nxVersion);
const lintTask = await lintProjectGenerator(host, {
linter: options.linter,
project: options.projectName,
tsConfigPaths: [
(0, devkit_1.joinPathFragments)(options.appProjectRoot, 'tsconfig.app.json'),
],
unitTestRunner: options.unitTestRunner,
skipFormat: true,
setParserOptionsProject: options.setParserOptionsProject,
addPlugin: options.addPlugin,
});
tasks.push(lintTask);
// Add out-tsc ignore pattern when using TS solution setup
if (options.isUsingTsSolutionConfig) {
const { addIgnoresToLintConfig } = await Promise.resolve().then(() => require('@nx/eslint/src/generators/utils/eslint-file'));
addIgnoresToLintConfig(host, options.appProjectRoot, ['**/out-tsc']);
}
}
if (options.bundler === 'vite') {
const { viteConfigurationGenerator, createOrEditViteConfig } = (0, devkit_1.ensurePackage)('@nx/vite', versions_2.nxVersion);
// 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
if (host.exists((0, devkit_1.joinPathFragments)(options.appProjectRoot, 'src/environments'))) {
host.delete((0, devkit_1.joinPathFragments)(options.appProjectRoot, 'src/environments'));
}
const viteTask = await viteConfigurationGenerator(host, {
uiFramework: 'none',
project: options.projectName,
newProject: true,
includeVitest: options.unitTestRunner === 'vitest',
inSourceTests: options.inSourceTests,
skipFormat: true,
addPlugin: options.addPlugin,
});
tasks.push(viteTask);
createOrEditViteConfig(host, {
project: options.projectName,
includeLib: false,
includeVitest: options.unitTestRunner === 'vitest',
inSourceTests: options.inSourceTests,
}, false);
}
if (options.bundler !== 'vite' && options.unitTestRunner === 'vitest') {
const { vitestGenerator, createOrEditViteConfig } = (0, devkit_1.ensurePackage)('@nx/vite', versions_2.nxVersion);
const vitestTask = await vitestGenerator(host, {
uiFramework: 'none',
project: options.projectName,
coverageProvider: 'v8',
inSourceTests: options.inSourceTests,
skipFormat: true,
addPlugin: options.addPlugin,
compiler: options.compiler,
});
tasks.push(vitestTask);
createOrEditViteConfig(host, {
project: options.projectName,
includeLib: false,
includeVitest: true,
inSourceTests: options.inSourceTests,
}, true);
}
if ((options.bundler === 'vite' || options.unitTestRunner === 'vitest') &&
options.inSourceTests) {
host.delete((0, devkit_1.joinPathFragments)(options.appProjectRoot, `src/app/app.element.spec.ts`));
}
const nxJson = (0, devkit_1.readNxJson)(host);
let hasPlugin;
let buildPlugin;
let buildConfigFile;
if (options.bundler === 'webpack' || options.bundler === 'vite') {
buildPlugin = `@nx/${options.bundler}/plugin`;
buildConfigFile =
options.bundler === 'webpack' ? 'webpack.config.js' : `vite.config.ts`;
hasPlugin = nxJson.plugins?.find((p) => typeof p === 'string' ? p === buildPlugin : p.plugin === buildPlugin);
}
if (!hasPlugin) {
await (0, static_serve_configuration_1.default)(host, {
buildTarget: `${options.projectName}:build`,
spa: true,
});
}
let e2eWebServerInfo = {
e2eWebServerAddress: `http://localhost:4200`,
e2eWebServerCommand: `${(0, devkit_1.getPackageManagerCommand)().exec} nx run ${options.projectName}:serve`,
e2eCiWebServerCommand: `${(0, devkit_1.getPackageManagerCommand)().exec} nx run ${options.projectName}:serve-static`,
e2eCiBaseUrl: `http://localhost:4200`,
e2eDevServerTarget: `${options.projectName}:serve`,
};
if (options.bundler === 'webpack') {
const { getWebpackE2EWebServerInfo } = (0, devkit_1.ensurePackage)('@nx/webpack', versions_2.nxVersion);
e2eWebServerInfo = await getWebpackE2EWebServerInfo(host, options.projectName, (0, devkit_1.joinPathFragments)(options.appProjectRoot, `webpack.config.js`), options.addPlugin, 4200);
}
else if (options.bundler === 'vite') {
const { getViteE2EWebServerInfo } = (0, devkit_1.ensurePackage)('@nx/vite', versions_2.nxVersion);
e2eWebServerInfo = await getViteE2EWebServerInfo(host, options.projectName, (0, devkit_1.joinPathFragments)(options.appProjectRoot, `vite.config.ts`), options.addPlugin, 4200);
}
if (options.e2eTestRunner === 'cypress') {
const { configurationGenerator } = (0, devkit_1.ensurePackage)('@nx/cypress', versions_2.nxVersion);
const packageJson = {
name: options.e2eProjectName,
version: '0.0.1',
private: true,
};
if (!options.useProjectJson) {
packageJson.nx = {
implicitDependencies: [options.projectName],
};
}
else {
(0, devkit_1.addProjectConfiguration)(host, options.e2eProjectName, {
root: options.e2eProjectRoot,
sourceRoot: (0, devkit_1.joinPathFragments)(options.e2eProjectRoot, 'src'),
projectType: 'application',
targets: {},
tags: [],
implicitDependencies: [options.projectName],
});
}
if (!options.useProjectJson || options.isUsingTsSolutionConfig) {
(0, devkit_1.writeJson)(host, (0, devkit_1.joinPathFragments)(options.e2eProjectRoot, 'package.json'), packageJson);
}
const cypressTask = await configurationGenerator(host, {
...options,
project: options.e2eProjectName,
devServerTarget: e2eWebServerInfo.e2eDevServerTarget,
baseUrl: e2eWebServerInfo.e2eWebServerAddress,
directory: 'src',
skipFormat: true,
webServerCommands: {
default: e2eWebServerInfo.e2eWebServerCommand,
production: e2eWebServerInfo.e2eCiWebServerCommand,
},
ciWebServerCommand: e2eWebServerInfo.e2eCiWebServerCommand,
ciBaseUrl: e2eWebServerInfo.e2eCiBaseUrl,
});
tasks.push(cypressTask);
}
else if (options.e2eTestRunner === 'playwright') {
const { configurationGenerator: playwrightConfigGenerator } = (0, devkit_1.ensurePackage)('@nx/playwright', versions_2.nxVersion);
const packageJson = {
name: options.e2eProjectName,
version: '0.0.1',
private: true,
};
if (!options.useProjectJson) {
packageJson.nx = {
implicitDependencies: [options.projectName],
};
}
else {
(0, devkit_1.addProjectConfiguration)(host, options.e2eProjectName, {
root: options.e2eProjectRoot,
sourceRoot: (0, devkit_1.joinPathFragments)(options.e2eProjectRoot, 'src'),
projectType: 'application',
targets: {},
tags: [],
implicitDependencies: [options.projectName],
});
}
if (!options.useProjectJson || options.isUsingTsSolutionConfig) {
(0, devkit_1.writeJson)(host, (0, devkit_1.joinPathFragments)(options.e2eProjectRoot, 'package.json'), packageJson);
}
const playwrightTask = await playwrightConfigGenerator(host, {
project: options.e2eProjectName,
skipFormat: true,
skipPackageJson: false,
directory: 'src',
js: false,
linter: options.linter,
setParserOptionsProject: options.setParserOptionsProject,
webServerCommand: e2eWebServerInfo.e2eCiWebServerCommand,
webServerAddress: e2eWebServerInfo.e2eCiBaseUrl,
addPlugin: options.addPlugin,
});
tasks.push(playwrightTask);
}
if (options.unitTestRunner === 'jest') {
const { configurationGenerator } = (0, devkit_1.ensurePackage)('@nx/jest', versions_2.nxVersion);
const jestTask = await configurationGenerator(host, {
project: options.projectName,
skipSerializers: true,
setupFile: 'web-components',
compiler: options.compiler,
skipFormat: true,
addPlugin: options.addPlugin,
});
tasks.push(jestTask);
}
if (options.compiler === 'swc') {
(0, devkit_1.writeJson)(host, (0, devkit_1.joinPathFragments)(options.appProjectRoot, '.swcrc'), {
jsc: {
parser: {
syntax: 'typescript',
},
target: 'es2016',
},
});
const installTask = (0, devkit_1.addDependenciesToPackageJson)(host, {}, { '@swc/core': versions_1.swcCoreVersion, 'swc-loader': versions_2.swcLoaderVersion });
tasks.push(installTask);
}
else {
(0, devkit_1.writeJson)(host, (0, devkit_1.joinPathFragments)(options.appProjectRoot, '.babelrc'), {
presets: ['@nx/js/babel'],
});
}
setDefaults(host, options);
tasks.push((0, devkit_1.addDependenciesToPackageJson)(host, { tslib: versions_2.tsLibVersion }, { '@types/node': versions_2.typesNodeVersion }));
(0, ts_solution_setup_1.updateTsconfigFiles)(host, options.appProjectRoot, 'tsconfig.app.json', {
module: 'esnext',
moduleResolution: 'bundler',
}, options.linter === 'eslint'
? ['eslint.config.js', 'eslint.config.cjs', 'eslint.config.mjs']
: undefined);
if (!options.skipFormat) {
await (0, devkit_1.formatFiles)(host);
}
tasks.push(() => {
(0, log_show_project_command_1.logShowProjectCommand)(options.projectName);
});
return (0, devkit_1.runTasksInSerial)(...tasks);
}
async function normalizeOptions(host, options) {
await (0, project_name_and_root_utils_1.ensureRootProjectName)(options, 'application');
const { projectName, projectRoot: appProjectRoot, importPath, } = await (0, project_name_and_root_utils_1.determineProjectNameAndRootOptions)(host, {
name: options.name,
projectType: 'application',
directory: options.directory,
});
const nxJson = (0, devkit_1.readNxJson)(host);
const addPluginDefault = process.env.NX_ADD_PLUGINS !== 'false' &&
nxJson.useInferencePlugins !== false;
options.addPlugin ??= addPluginDefault;
const isUsingTsSolutionConfig = (0, ts_solution_setup_1.isUsingTsSolutionSetup)(host);
const appProjectName = !isUsingTsSolutionConfig || options.name ? projectName : importPath;
const e2eProjectName = `${appProjectName}-e2e`;
const e2eProjectRoot = `${appProjectRoot}-e2e`;
const npmScope = (0, get_npm_scope_1.getNpmScope)(host);
const parsedTags = options.tags
? options.tags.split(',').map((s) => s.trim())
: [];
options.style = options.style || 'css';
options.linter = options.linter || 'eslint';
options.unitTestRunner = options.unitTestRunner || 'jest';
options.e2eTestRunner = options.e2eTestRunner || 'playwright';
return {
...options,
prefix: options.prefix ?? npmScope ?? 'app',
compiler: options.compiler ?? 'babel',
bundler: options.bundler ?? 'webpack',
projectName: appProjectName,
importPath,
strict: options.strict ?? true,
appProjectRoot,
e2eProjectRoot,
e2eProjectName,
parsedTags,
names: (0, devkit_1.names)(projectName),
isUsingTsSolutionConfig,
useProjectJson: options.useProjectJson ?? !isUsingTsSolutionConfig,
};
}
exports.default = applicationGenerator;