UNPKG

@nx/web

Version:

The Nx Plugin for Web Components contains generators for managing Web Component applications and libraries within an Nx workspace. It provides: - Integration with libraries such as Jest, Playwright, Cypress, and Storybook. - Scaffolding for creating bu

483 lines (482 loc) • 21.9 kB
"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;