diff --git a/docs/angular/api-react/generators/application.md b/docs/angular/api-react/generators/application.md index c912289b6b..0e922fe3af 100644 --- a/docs/angular/api-react/generators/application.md +++ b/docs/angular/api-react/generators/application.md @@ -63,6 +63,16 @@ Type: `boolean` Use class components instead of functional component. +### compiler + +Default: `babel` + +Type: `string` + +Possible values: `babel`, `swc` + +The compiler to use + ### directory Alias(es): dir diff --git a/docs/angular/api-web/executors/webpack.md b/docs/angular/api-web/executors/webpack.md index cdbb792757..71d6c7d54e 100644 --- a/docs/angular/api-web/executors/webpack.md +++ b/docs/angular/api-web/executors/webpack.md @@ -63,6 +63,16 @@ Type: `boolean` Use a separate bundle containing code used across multiple bundles. +### compiler + +Default: `babel` + +Type: `string` + +Possible values: `babel`, `swc` + +The compiler to use + ### crossOrigin Type: `string` diff --git a/docs/angular/api-web/generators/application.md b/docs/angular/api-web/generators/application.md index 917d827e28..01daaaddd2 100644 --- a/docs/angular/api-web/generators/application.md +++ b/docs/angular/api-web/generators/application.md @@ -41,6 +41,16 @@ Type: `boolean` Use babel instead ts-jest +### compiler + +Default: `babel` + +Type: `string` + +Possible values: `babel`, `swc` + +The compiler to use + ### directory Type: `string` diff --git a/docs/node/api-react/generators/application.md b/docs/node/api-react/generators/application.md index 51764bd9a3..ab20c580e4 100644 --- a/docs/node/api-react/generators/application.md +++ b/docs/node/api-react/generators/application.md @@ -63,6 +63,16 @@ Type: `boolean` Use class components instead of functional component. +### compiler + +Default: `babel` + +Type: `string` + +Possible values: `babel`, `swc` + +The compiler to use + ### directory Alias(es): dir diff --git a/docs/node/api-web/executors/webpack.md b/docs/node/api-web/executors/webpack.md index e102bda6eb..8df6225be6 100644 --- a/docs/node/api-web/executors/webpack.md +++ b/docs/node/api-web/executors/webpack.md @@ -63,6 +63,16 @@ Type: `boolean` Use a separate bundle containing code used across multiple bundles. +### compiler + +Default: `babel` + +Type: `string` + +Possible values: `babel`, `swc` + +The compiler to use + ### crossOrigin Type: `string` diff --git a/docs/node/api-web/generators/application.md b/docs/node/api-web/generators/application.md index dc333565f0..6755992172 100644 --- a/docs/node/api-web/generators/application.md +++ b/docs/node/api-web/generators/application.md @@ -41,6 +41,16 @@ Type: `boolean` Use babel instead ts-jest +### compiler + +Default: `babel` + +Type: `string` + +Possible values: `babel`, `swc` + +The compiler to use + ### directory Type: `string` diff --git a/docs/react/api-react/generators/application.md b/docs/react/api-react/generators/application.md index 51764bd9a3..ab20c580e4 100644 --- a/docs/react/api-react/generators/application.md +++ b/docs/react/api-react/generators/application.md @@ -63,6 +63,16 @@ Type: `boolean` Use class components instead of functional component. +### compiler + +Default: `babel` + +Type: `string` + +Possible values: `babel`, `swc` + +The compiler to use + ### directory Alias(es): dir diff --git a/docs/react/api-web/executors/webpack.md b/docs/react/api-web/executors/webpack.md index e102bda6eb..8df6225be6 100644 --- a/docs/react/api-web/executors/webpack.md +++ b/docs/react/api-web/executors/webpack.md @@ -63,6 +63,16 @@ Type: `boolean` Use a separate bundle containing code used across multiple bundles. +### compiler + +Default: `babel` + +Type: `string` + +Possible values: `babel`, `swc` + +The compiler to use + ### crossOrigin Type: `string` diff --git a/docs/react/api-web/generators/application.md b/docs/react/api-web/generators/application.md index dc333565f0..6755992172 100644 --- a/docs/react/api-web/generators/application.md +++ b/docs/react/api-web/generators/application.md @@ -41,6 +41,16 @@ Type: `boolean` Use babel instead ts-jest +### compiler + +Default: `babel` + +Type: `string` + +Possible values: `babel`, `swc` + +The compiler to use + ### directory Type: `string` diff --git a/e2e/web/src/web.test.ts b/e2e/web/src/web.test.ts index 237ba026a0..2a8d801273 100644 --- a/e2e/web/src/web.test.ts +++ b/e2e/web/src/web.test.ts @@ -73,7 +73,7 @@ describe('Web Components Applications', () => { const appName = uniq('app'); const libName = uniq('lib'); - runCLI(`generate @nrwl/web:app ${appName} --no-interactive`); + runCLI(`generate @nrwl/web:app ${appName} --no-interactive --compiler swc`); runCLI( `generate @nrwl/react:lib ${libName} --buildable --no-interactive --compiler swc` ); diff --git a/packages/react/src/generators/application/application.spec.ts b/packages/react/src/generators/application/application.spec.ts index 6ac29f83a1..b9adccfed3 100644 --- a/packages/react/src/generators/application/application.spec.ts +++ b/packages/react/src/generators/application/application.spec.ts @@ -277,6 +277,7 @@ Object { expect(targetConfig.build.executor).toEqual('@nrwl/web:webpack'); expect(targetConfig.build.outputs).toEqual(['{options.outputPath}']); expect(targetConfig.build.options).toEqual({ + compiler: 'babel', assets: ['apps/my-app/src/favicon.ico', 'apps/my-app/src/assets'], index: 'apps/my-app/src/index.html', main: 'apps/my-app/src/main.tsx', @@ -735,4 +736,19 @@ Object { ).toBeTruthy(); }); }); + + describe('--compiler', () => { + it('should install swc packages if --compiler=swc', async () => { + await applicationGenerator(appTree, { + ...schema, + compiler: 'swc', + }); + const packageJson = readJson(appTree, '/package.json'); + + expect(packageJson.devDependencies).toMatchObject({ + '@swc/core': expect.any(String), + 'swc-loader': expect.any(String), + }); + }); + }); }); diff --git a/packages/react/src/generators/application/application.ts b/packages/react/src/generators/application/application.ts index fd7d308ad2..08631f3a0e 100644 --- a/packages/react/src/generators/application/application.ts +++ b/packages/react/src/generators/application/application.ts @@ -1,6 +1,6 @@ import { - extraEslintDependencies, createReactEslintJson, + extraEslintDependencies, } from '../../utils/lint'; import { NormalizedSchema, Schema } from './schema'; import { createApplicationFiles } from './lib/create-application-files'; @@ -24,6 +24,8 @@ import { import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial'; import reactInitGenerator from '../init/init'; import { lintProjectGenerator } from '@nrwl/linter'; +import { swcCoreVersion } from '@nrwl/js/src/utils/versions'; +import { swcLoaderVersion } from '@nrwl/web/src/utils/versions'; async function addLinting(host: Tree, options: NormalizedSchema) { const tasks: GeneratorCallback[] = []; @@ -52,7 +54,12 @@ async function addLinting(host: Tree, options: NormalizedSchema) { const installTask = await addDependenciesToPackageJson( host, extraEslintDependencies.dependencies, - extraEslintDependencies.devDependencies + { + ...extraEslintDependencies.devDependencies, + ...(options.compiler === 'swc' + ? { '@swc/core': swcCoreVersion, 'swc-loader': swcLoaderVersion } + : {}), + } ); tasks.push(installTask); diff --git a/packages/react/src/generators/application/lib/add-project.ts b/packages/react/src/generators/application/lib/add-project.ts index c70b0aeb92..cb0e8a6da2 100644 --- a/packages/react/src/generators/application/lib/add-project.ts +++ b/packages/react/src/generators/application/lib/add-project.ts @@ -40,6 +40,7 @@ function createBuildTarget(options: NormalizedSchema): TargetConfiguration { outputs: ['{options.outputPath}'], defaultConfiguration: 'production', options: { + compiler: options.compiler ?? 'babel', outputPath: joinPathFragments('dist', options.appProjectRoot), index: joinPathFragments(options.appProjectRoot, 'src/index.html'), baseHref: '/', diff --git a/packages/react/src/generators/application/schema.d.ts b/packages/react/src/generators/application/schema.d.ts index fd18998712..f920cac2c1 100644 --- a/packages/react/src/generators/application/schema.d.ts +++ b/packages/react/src/generators/application/schema.d.ts @@ -20,6 +20,7 @@ export interface Schema { strict?: boolean; setParserOptionsProject?: boolean; standaloneConfig?: boolean; + compiler?: 'babel' | 'swc'; } export interface NormalizedSchema extends Schema { diff --git a/packages/react/src/generators/application/schema.json b/packages/react/src/generators/application/schema.json index 36f311cbc9..a3821601bd 100644 --- a/packages/react/src/generators/application/schema.json +++ b/packages/react/src/generators/application/schema.json @@ -152,6 +152,12 @@ "standaloneConfig": { "description": "Split the project configuration into /project.json rather than including it inside workspace.json", "type": "boolean" + }, + "compiler": { + "type": "string", + "description": "The compiler to use", + "enum": ["babel", "swc"], + "default": "babel" } }, "required": [] diff --git a/packages/web/src/executors/webpack/schema.json b/packages/web/src/executors/webpack/schema.json index 0e3c00ec81..c19791db22 100644 --- a/packages/web/src/executors/webpack/schema.json +++ b/packages/web/src/executors/webpack/schema.json @@ -16,6 +16,12 @@ "type": "string", "description": "The name of the Typescript configuration file." }, + "compiler": { + "type": "string", + "description": "The compiler to use", + "enum": ["babel", "swc"], + "default": "babel" + }, "outputPath": { "type": "string", "description": "The output path of the generated files." diff --git a/packages/web/src/executors/webpack/webpack.impl.ts b/packages/web/src/executors/webpack/webpack.impl.ts index 06276e426e..3fc64de7fa 100644 --- a/packages/web/src/executors/webpack/webpack.impl.ts +++ b/packages/web/src/executors/webpack/webpack.impl.ts @@ -1,4 +1,4 @@ -import type { ExecutorContext } from '@nrwl/devkit'; +import { ExecutorContext, logger } from '@nrwl/devkit'; import type { Configuration, Stats } from 'webpack'; import { from, of } from 'rxjs'; import { bufferCount, mergeScan, switchMap, tap } from 'rxjs/operators'; @@ -132,6 +132,18 @@ export async function* run( const metadata = context.workspace.projects[context.projectName]; + if (options.compiler === 'swc') { + try { + require.resolve('swc-loader'); + require.resolve('@swc/core'); + } catch { + logger.error( + `Missing SWC dependencies: @swc/core, swc-loader. Make sure you install them first.` + ); + return { success: false }; + } + } + if (!options.buildLibsFromSource && context.targetName) { const { dependencies } = calculateProjectDependencies( readCachedProjectGraph(), diff --git a/packages/web/src/generators/application/application.spec.ts b/packages/web/src/generators/application/application.spec.ts index 3a1e2afc7a..ec4c779ee0 100644 --- a/packages/web/src/generators/application/application.spec.ts +++ b/packages/web/src/generators/application/application.spec.ts @@ -257,6 +257,7 @@ describe('app', () => { expect(architectConfig.build.builder).toEqual('@nrwl/web:webpack'); expect(architectConfig.build.outputs).toEqual(['{options.outputPath}']); expect(architectConfig.build.options).toEqual({ + compiler: 'babel', assets: ['apps/my-app/src/favicon.ico', 'apps/my-app/src/assets'], index: 'apps/my-app/src/index.html', baseHref: '/', diff --git a/packages/web/src/generators/application/application.ts b/packages/web/src/generators/application/application.ts index 5d8f67c075..6153067fd1 100644 --- a/packages/web/src/generators/application/application.ts +++ b/packages/web/src/generators/application/application.ts @@ -1,4 +1,5 @@ import { + addDependenciesToPackageJson, addProjectConfiguration, convertNxGenerator, formatFiles, @@ -25,6 +26,8 @@ import { jestProjectGenerator } from '@nrwl/jest'; import { WebWebpackExecutorOptions } from '../../executors/webpack/webpack.impl'; import { Schema } from './schema'; +import { swcCoreVersion } from '@nrwl/js/src/utils/versions'; +import { swcLoaderVersion } from '../../utils/versions'; interface NormalizedSchema extends Schema { projectName: string; @@ -52,6 +55,7 @@ function addBuildTarget( ): ProjectConfiguration { const buildOptions: WebWebpackExecutorOptions = { outputPath: joinPathFragments('dist', options.appProjectRoot), + compiler: options.compiler ?? 'babel', index: joinPathFragments(options.appProjectRoot, 'src/index.html'), baseHref: '/', main: joinPathFragments(options.appProjectRoot, 'src/main.ts'), @@ -222,6 +226,15 @@ export async function applicationGenerator(host: Tree, schema: Schema) { tasks.push(jestTask); } + if (options.compiler === 'swc') { + const installTask = await addDependenciesToPackageJson( + host, + {}, + { '@swc/core': swcCoreVersion, 'swc-loader': swcLoaderVersion } + ); + tasks.push(installTask); + } + setDefaults(host, options); if (!schema.skipFormat) { diff --git a/packages/web/src/generators/application/schema.d.ts b/packages/web/src/generators/application/schema.d.ts index ab4427cc58..ecf35d02e7 100644 --- a/packages/web/src/generators/application/schema.d.ts +++ b/packages/web/src/generators/application/schema.d.ts @@ -4,6 +4,7 @@ export interface Schema { name: string; prefix?: string; style?: string; + compiler?: 'babel' | 'swc'; skipFormat?: boolean; directory?: string; tags?: string; diff --git a/packages/web/src/generators/application/schema.json b/packages/web/src/generators/application/schema.json index fb23d31931..595d786a50 100644 --- a/packages/web/src/generators/application/schema.json +++ b/packages/web/src/generators/application/schema.json @@ -43,6 +43,12 @@ ] } }, + "compiler": { + "type": "string", + "description": "The compiler to use", + "enum": ["babel", "swc"], + "default": "babel" + }, "linter": { "description": "The tool to use for running lint checks.", "type": "string", diff --git a/packages/web/src/utils/config.ts b/packages/web/src/utils/config.ts index aad815ad32..40c7b980a1 100644 --- a/packages/web/src/utils/config.ts +++ b/packages/web/src/utils/config.ts @@ -72,7 +72,7 @@ export function getBaseWebpackPartial( fullySpecified: false, }, }, - { + options.compiler === 'babel' && { test: /\.([jt])sx?$/, loader: join(__dirname, 'web-babel-loader'), exclude: /node_modules/, @@ -87,7 +87,27 @@ export function getBaseWebpackPartial( cacheCompression: false, }, }, - ], + options.compiler === 'swc' && { + test: /\.([jt])sx?$/, + loader: require.resolve('swc-loader'), + exclude: /node_modules/, + options: { + jsc: { + parser: { + syntax: 'typescript', + decorators: true, + tsx: true, + }, + transform: { + react: { + runtime: 'automatic', + }, + }, + loose: true, + }, + }, + }, + ].filter(Boolean), }, resolve: { extensions, @@ -123,7 +143,7 @@ export function getBaseWebpackPartial( }, }; - if (isScriptOptimizeOn) { + if (options.compiler !== 'swc' && isScriptOptimizeOn) { webpackConfig.optimization = { sideEffects: false, minimizer: [ diff --git a/packages/web/src/utils/normalize.spec.ts b/packages/web/src/utils/normalize.spec.ts index 57b4d8b57f..851b839362 100644 --- a/packages/web/src/utils/normalize.spec.ts +++ b/packages/web/src/utils/normalize.spec.ts @@ -10,6 +10,7 @@ describe('normalizeBuildOptions', () => { beforeEach(() => { testOptions = { + compiler: 'babel', main: 'apps/nodeapp/src/main.ts', tsConfig: 'apps/nodeapp/tsconfig.app.json', outputPath: 'dist/apps/nodeapp', diff --git a/packages/web/src/utils/shared-models.ts b/packages/web/src/utils/shared-models.ts index 43b181d247..335e685bad 100644 --- a/packages/web/src/utils/shared-models.ts +++ b/packages/web/src/utils/shared-models.ts @@ -9,6 +9,7 @@ export interface OptimizationOptions { export interface BuildBuilderOptions { main: string; outputPath: string; + compiler: 'babel' | 'swc'; tsConfig: string; watch?: boolean; sourceMap?: boolean | 'hidden'; diff --git a/packages/web/src/utils/versions.ts b/packages/web/src/utils/versions.ts index b2dfdf7bdf..3b2e2c3809 100644 --- a/packages/web/src/utils/versions.ts +++ b/packages/web/src/utils/versions.ts @@ -1,3 +1,4 @@ export const nxVersion = '*'; +export const swcLoaderVersion = '0.1.15'; export const sassVersion = '1.43.2'; diff --git a/packages/web/src/utils/webpack/partials/common.ts b/packages/web/src/utils/webpack/partials/common.ts index 04f428106c..f24ff48e82 100644 --- a/packages/web/src/utils/webpack/partials/common.ts +++ b/packages/web/src/utils/webpack/partials/common.ts @@ -8,13 +8,11 @@ import { ExtraEntryPoint, WebpackConfigOptions } from '../../shared-models'; import { BuildBrowserFeatures } from '../build-browser-features'; import { getOutputHashFormat } from '../../hash-format'; import { normalizeExtraEntryPoints } from '../../normalize'; -import CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); import { findAllNodeModules, findUp } from '../../fs'; +import CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); const ProgressPlugin = require('webpack/lib/ProgressPlugin'); -const TerserPlugin = require('terser-webpack-plugin'); -// tslint:disable-next-line:no-big-function export function getCommonConfig(wco: WebpackConfigOptions): Configuration { const { ContextReplacementPlugin } = webpack; @@ -208,52 +206,6 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration { ); } - if (scriptsOptimization) { - // TODO: Investigate why this fails for some packages: wco.supportES2015 ? 6 : 5; - const terserEcma = 5; - - const terserOptions = { - warnings: !!buildOptions.verbose, - safari10: true, - output: { - ecma: terserEcma, - comments: false, - webkit: true, - }, - // On server, we don't want to compress anything. We still set the ngDevMode = false for it - // to remove dev code, and ngI18nClosureMode to remove Closure compiler i18n code - compress: { - ecma: terserEcma, - // TODO(jack): Investigate options to enable further optimizations - // pure_getters: true, - // PURE comments work best with 3 passes. - // See https://github.com/webpack/webpack/issues/2899#issuecomment-317425926. - // passes: 3, - }, - mangle: true, - }; - - const es5TerserOptions = { - ...terserOptions, - compress: { - ...terserOptions.compress, - ecma: 5, - }, - output: { - ...terserOptions.output, - ecma: 5, - }, - }; - - extraMinimizers.push( - new TerserPlugin({ terserOptions }), - - // Script bundles are fully optimized here in one step since they are never downleveled. - // They are shared between ES2015 & ES5 outputs so must support ES5. - new TerserPlugin({ terserOptions: es5TerserOptions }) - ); - } - return { mode: scriptsOptimization || stylesOptimization ? 'production' : 'development', diff --git a/scripts/depcheck/missing.ts b/scripts/depcheck/missing.ts index cf13f4b4e0..9e9868a5ca 100644 --- a/scripts/depcheck/missing.ts +++ b/scripts/depcheck/missing.ts @@ -90,7 +90,10 @@ const IGNORE_MATCHES = { '@angular-devkit/architect', ], web: [ - '@swc/core', // we don't want to bloat the install of @nrwl/web by including @swc/core as a dependency. + // we don't want to bloat the install of @nrwl/web by including @swc/core and swc-loader as a dependency. + '@swc/core', + 'swc-loader', + 'fibers', 'node-sass', ],