feat(web): add swc compiler option for webpack executor (#8114)

This commit is contained in:
Jack Hsu 2021-12-10 16:09:20 -05:00 committed by GitHub
parent 3bedfd8039
commit f8c394af46
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 195 additions and 57 deletions

View File

@ -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

View File

@ -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`

View File

@ -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`

View File

@ -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

View File

@ -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`

View File

@ -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`

View File

@ -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

View File

@ -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`

View File

@ -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`

View File

@ -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`
);

View File

@ -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),
});
});
});
});

View File

@ -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);

View File

@ -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: '/',

View File

@ -20,6 +20,7 @@ export interface Schema {
strict?: boolean;
setParserOptionsProject?: boolean;
standaloneConfig?: boolean;
compiler?: 'babel' | 'swc';
}
export interface NormalizedSchema extends Schema {

View File

@ -152,6 +152,12 @@
"standaloneConfig": {
"description": "Split the project configuration into <projectRoot>/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": []

View File

@ -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."

View File

@ -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(),

View File

@ -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: '/',

View File

@ -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) {

View File

@ -4,6 +4,7 @@ export interface Schema {
name: string;
prefix?: string;
style?: string;
compiler?: 'babel' | 'swc';
skipFormat?: boolean;
directory?: string;
tags?: string;

View File

@ -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",

View File

@ -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: [

View File

@ -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',

View File

@ -9,6 +9,7 @@ export interface OptimizationOptions {
export interface BuildBuilderOptions {
main: string;
outputPath: string;
compiler: 'babel' | 'swc';
tsConfig: string;
watch?: boolean;
sourceMap?: boolean | 'hidden';

View File

@ -1,3 +1,4 @@
export const nxVersion = '*';
export const swcLoaderVersion = '0.1.15';
export const sassVersion = '1.43.2';

View File

@ -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',

View File

@ -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',
],