Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ✨ 新增targets配置,用于修改构建产物的兼容性 #549

Merged
merged 11 commits into from Dec 7, 2022
23 changes: 21 additions & 2 deletions docs/config.md
Expand Up @@ -48,9 +48,9 @@ father 支持以下配置项。
- 类型:`browser` | `node`
- 默认值:`<auto>`

指定构建产物的目标平台,其中 `esm` 与 `umd` 产物的默认 `platform` 为 `browser`,`cjs` 产物的默认 `platform` 为 `node`;指定为 `browser` 时产物默认兼容至 IE11,指定为 `node` 时产物默认兼容至 Node.js v14,兼容性不支持配置
指定构建产物的目标平台,其中 `esm` 与 `umd` 产物的默认 `platform` 为 `browser`,`cjs` 产物的默认 `platform` 为 `node`;指定为 `browser` 时产物默认兼容至 IE11,指定为 `node` 时产物默认兼容至 Node.js v14。

> 注:Bundless 模式下,如果手动将 `transformer` 指定为 `esbuild`,那么 `browser` 产物兼容性为 ES6 而不是 IE11。
> 注:Bundless 模式下,如果手动将 `transformer` 指定为 `esbuild`,那么 `browser` 产物默认兼容性为 ES6 而不是 IE11。

### sourcemap

Expand All @@ -61,6 +61,25 @@ father 支持以下配置项。

> 注:Bundless 模式下 map 对象的 file 字段为空

### targets

- 类型: `string` | `string[]`
huarse marked this conversation as resolved.
Show resolved Hide resolved
- 默认值:`<auto>`

指定源码编译产物的兼容性,不同编译模式和目标平台的传值略有区别:

| `transformer` | example |
| ------------- | ------------------------------------ |
| `babel` | `chrome85`, `['chrome85', 'node14']` |
| `esbuild` | `es2017`, `['es2017', 'chrome85']` |
| `swc` | `es2017`, `es5` |

> 注:
>
> 1. `babel` 编译模式下仅支持传容器版本,例如 `chrome85`, `ie11`, `node14` 等,不支持 `es2015` 等语言版本;
> 2. `swc` 编译模式下仅支持语言版本,如 `es2017`, `es5` 等,不支持容器版本,且仅支持单个值,如果传入数组,则只取第一个;
huarse marked this conversation as resolved.
Show resolved Hide resolved
> 3. `umd` 构建产物仅支持 `babel` 编译模式,即只能传容器版本

## 构建配置

father 以构建产物类型划分构建配置,其中 `esm`、`cjs` 产物为 Bundless 构建模式,`umd` 产物为 Bundle 构建模式,另外依赖预打包 `prebundle` 产物也为 Bundle 构建模式。
Expand Down
8 changes: 5 additions & 3 deletions src/builder/bundle/index.ts
Expand Up @@ -2,7 +2,7 @@ import { chalk, importLazy, logger } from '@umijs/utils';
import path from 'path';
import { CACHE_PATH } from '../../constants';
import type { BundleConfigProvider } from '../config';
import { getBabelPresetReactOpts } from '../utils';
import { getBabelPresetReactOpts, getBundleTargets } from '../utils';

const bundler: typeof import('@umijs/bundler-webpack') = importLazy(
path.dirname(require.resolve('@umijs/bundler-webpack/package.json')),
Expand Down Expand Up @@ -52,7 +52,7 @@ export default async (opts: {
theme: config.theme,

// compatible with IE11 by default
targets: { ie: 11 },
targets: getBundleTargets(config),
jsMinifier: JSMinifier.terser,
cssMinifier: CSSMinifier.cssnano,
extraBabelIncludes: [/node_modules/],
Expand All @@ -66,7 +66,9 @@ export default async (opts: {
babelPreset: [
require.resolve('@umijs/babel-preset-umi'),
{
presetEnv: {},
presetEnv: {
targets: getBundleTargets(config),
},
presetReact: getBabelPresetReactOpts(opts.configProvider.pkg),
presetTypeScript: {},
pluginTransformRuntime: {},
Expand Down
8 changes: 3 additions & 5 deletions src/builder/bundless/loaders/javascript/babel.ts
@@ -1,11 +1,12 @@
import { transform } from '@umijs/bundler-utils/compiled/babel/core';
import { winPath } from '@umijs/utils';
import path from 'path';
import { IFatherBundlessTypes, IFatherPlatformTypes } from '../../../../types';
import { IFatherBundlessTypes } from '../../../../types';
import {
addSourceMappingUrl,
ensureRelativePath,
getBabelPresetReactOpts,
getBundlessTargets,
} from '../../../utils';
import type { IJSTransformer } from '../types';

Expand Down Expand Up @@ -35,10 +36,7 @@ const babelTransformer: IJSTransformer = function (content) {
// TODO: correct optional in umi types and replace any here
const presetOpts: any = {
presetEnv: {
targets:
this.config.platform === IFatherPlatformTypes.BROWSER
? { ie: 11 }
: { node: 14 },
targets: getBundlessTargets(this.config),
modules: this.config.format === IFatherBundlessTypes.ESM ? false : 'auto',
},
presetReact: getBabelPresetReactOpts(this.pkg),
Expand Down
6 changes: 3 additions & 3 deletions src/builder/bundless/loaders/javascript/esbuild.ts
@@ -1,7 +1,8 @@
import { build } from '@umijs/bundler-utils/compiled/esbuild';
import { winPath } from '@umijs/utils';
import path from 'path';
import { IFatherBundlessConfig, IFatherPlatformTypes } from '../../../../types';
import { IFatherBundlessConfig } from '../../../../types';
import { getBundlessTargets } from '../../../utils';
import type { IJSTransformer } from '../types';

/**
Expand Down Expand Up @@ -59,8 +60,7 @@ const esbuildTransformer: IJSTransformer = async function () {
format: this.config.format,
define: this.config.define,
platform: this.config.platform,
target:
this.config.platform === IFatherPlatformTypes.NODE ? 'node14' : 'es6',
target: getBundlessTargets(this.config),
// esbuild need relative entry path
entryPoints: [path.relative(this.paths.cwd, this.paths.fileAbsPath)],
absWorkingDir: this.paths.cwd,
Expand Down
12 changes: 3 additions & 9 deletions src/builder/bundless/loaders/javascript/swc.ts
@@ -1,14 +1,11 @@
import { winPath } from '@umijs/utils';
import { lstatSync } from 'fs';
import path from 'path';
import {
IFatherBundlessConfig,
IFatherBundlessTypes,
IFatherPlatformTypes,
} from '../../../../types';
import { IFatherBundlessConfig, IFatherBundlessTypes } from '../../../../types';
import {
addSourceMappingUrl,
ensureRelativePath,
getBundlessTargets,
getSWCTransformReactOpts,
} from '../../../utils';
import { IJSTransformer } from '../types';
Expand Down Expand Up @@ -142,10 +139,7 @@ const swcTransformer: IJSTransformer = async function (content) {
...(isTSFile && isJSXFile ? { tsx: true } : {}),
...(!isTSFile && isJSXFile ? { jsx: true } : {}),
},
target:
this.config.platform === IFatherPlatformTypes.BROWSER
? 'es5'
: 'es2019',
target: getBundlessTargets(this.config),
transform: {
react: getSWCTransformReactOpts(this.pkg),
},
Expand Down
1 change: 1 addition & 0 deletions src/builder/config.ts
Expand Up @@ -334,6 +334,7 @@ export class BundlessConfigProvider extends ConfigProvider {
});
}

// TODO 这里匹配有问题,会先匹配到全局的,例如 src/async 会被 src/** 匹配到
getConfigForFile(filePath: string) {
return this.configs[this.matchers.findIndex((m) => m.match(filePath))];
}
Expand Down
71 changes: 70 additions & 1 deletion src/builder/utils.ts
@@ -1,6 +1,12 @@
import { semver } from '@umijs/utils';
import path from 'path';
import { IApi } from '../types';
import {
IApi,
IFatherBaseConfig,
IFatherJSTransformerTypes,
IFatherPlatformTypes,
} from '../types';
import type { IBundlessConfig } from './config';

export function addSourceMappingUrl(code: string, loc: string) {
return (
Expand Down Expand Up @@ -62,3 +68,66 @@ export function ensureRelativePath(relativePath: string) {
}
return relativePath;
}

// transfer string to object, for babel preset targets
const normalizeBabelTargets = (targets?: IFatherBaseConfig['targets']) => {
if (!targets) return undefined;

const targetList = typeof targets === 'string' ? [targets] : targets;
return targetList.reduce((prev, curr) => {
const [, key, value] = /^([a-z_-]+)(\d+)$/i.exec(curr) || ([] as string[]);
return { ...prev, [key]: +value || 0 };
}, {} as Record<string, number>);
};

export function getBundleTargets(
config: IFatherBaseConfig,
): Record<string, number> {
return normalizeBabelTargets(config.targets) || { ie: 11 };
}

const defaultBundlessTargets: Record<
IFatherPlatformTypes,
Record<IFatherJSTransformerTypes, any>
> = {
[IFatherPlatformTypes.BROWSER]: {
babel: { ie: 11 },
esbuild: 'es6',
swc: 'es5',
},
[IFatherPlatformTypes.NODE]: {
babel: { node: 14 },
esbuild: 'node14',
swc: 'es2019',
},
};

export function getBundlessTargets(config: IBundlessConfig) {
let {
platform = IFatherPlatformTypes.BROWSER,
transformer,
targets,
} = config;
if (!transformer) {
transformer =
platform === IFatherPlatformTypes.BROWSER
? IFatherJSTransformerTypes.BABEL
: IFatherJSTransformerTypes.ESBUILD;
}

// targets is undefined or empty
if (!targets || !targets.length) {
return defaultBundlessTargets[platform][transformer];
}

// swc accept only one target
if (transformer === 'swc') {
return typeof targets === 'string' ? targets : targets[0];
}
// esbuild accept string or string[]
if (transformer === 'esbuild') {
return targets;
}
// babel accept object
return normalizeBabelTargets(targets);
}
10 changes: 8 additions & 2 deletions src/features/configPlugins/schema.ts
@@ -1,5 +1,5 @@
import type { Root, SchemaLike } from '@umijs/utils/compiled/@hapi/joi';
import { IFatherPlatformTypes } from '../../types';
import { IFatherJSTransformerTypes, IFatherPlatformTypes } from '../../types';

function getCommonSchemas(): Record<string, (Joi: Root) => any> {
return {
Expand All @@ -16,6 +16,8 @@ function getCommonSchemas(): Record<string, (Joi: Root) => any> {
extraBabelPresets: (Joi) => Joi.array().optional(),
extraBabelPlugins: (Joi) => Joi.array().optional(),
sourcemap: (Joi) => Joi.boolean().optional(),
targets: (Joi) =>
Joi.alternatives().try(Joi.string(), Joi.array().items(Joi.string())),
};
}

Expand All @@ -34,7 +36,11 @@ function getBundlessSchemas(Joi: Root) {
...getCommonSchemasJoi(Joi),
input: Joi.string(),
output: Joi.string(),
transformer: Joi.string(),
transformer: Joi.equal(
IFatherJSTransformerTypes.BABEL,
IFatherJSTransformerTypes.ESBUILD,
IFatherJSTransformerTypes.SWC,
).optional(),
overrides: Joi.object(),
ignores: Joi.array().items(Joi.string()),
});
Expand Down
5 changes: 5 additions & 0 deletions src/types.ts
Expand Up @@ -117,6 +117,11 @@ export interface IFatherBaseConfig {
* output sourcemap
*/
sourcemap?: boolean;

/**
* compile targets
*/
targets?: string | string[];
}

export interface IFatherBundlessConfig extends IFatherBaseConfig {
Expand Down
5 changes: 5 additions & 0 deletions tests/fixtures/build/bundle-targets/.fatherrc.ts
@@ -0,0 +1,5 @@
export default {
umd: {
targets: 'chrome85',
},
};
3 changes: 3 additions & 0 deletions tests/fixtures/build/bundle-targets/expect.ts
@@ -0,0 +1,3 @@
export default (files: Record<string, string>) => {
expect(files['umd/index.min.js']).toContain('await');
};
1 change: 1 addition & 0 deletions tests/fixtures/build/bundle-targets/package.json
@@ -0,0 +1 @@
{}
5 changes: 5 additions & 0 deletions tests/fixtures/build/bundle-targets/src/index.ts
@@ -0,0 +1,5 @@
async function hello() {
return await Promise.resolve('ok');
}

hello();
1 change: 1 addition & 0 deletions tests/fixtures/build/bundle-targets/tsconfig.json
@@ -0,0 +1 @@
{}
10 changes: 10 additions & 0 deletions tests/fixtures/build/bundless-babel-targets/.fatherrc.ts
@@ -0,0 +1,10 @@
export default {
cjs: {
transformer: 'babel',
targets: 'chrome85',
},
esm: {
transformer: 'babel',
targets: ['ie11', 'chrome60'],
},
};
4 changes: 4 additions & 0 deletions tests/fixtures/build/bundless-babel-targets/expect.ts
@@ -0,0 +1,4 @@
export default (files: Record<string, string>) => {
expect(files['cjs/index.js']).toContain('await');
expect(files['esm/index.js']).toContain('_regeneratorRuntime');
};
1 change: 1 addition & 0 deletions tests/fixtures/build/bundless-babel-targets/package.json
@@ -0,0 +1 @@
{}
5 changes: 5 additions & 0 deletions tests/fixtures/build/bundless-babel-targets/src/index.ts
@@ -0,0 +1,5 @@
async function hello() {
return await Promise.resolve('ok');
}

hello();
1 change: 1 addition & 0 deletions tests/fixtures/build/bundless-babel-targets/tsconfig.json
@@ -0,0 +1 @@
{}
11 changes: 11 additions & 0 deletions tests/fixtures/build/bundless-esbuild-targets/.fatherrc.ts
@@ -0,0 +1,11 @@
export default {
targets: 'es6',
huarse marked this conversation as resolved.
Show resolved Hide resolved
cjs: {
transformer: 'esbuild',
targets: ['es2017', 'chrome85'],
},
esm: {
transformer: 'esbuild',
// targets: 'es6',
},
};
4 changes: 4 additions & 0 deletions tests/fixtures/build/bundless-esbuild-targets/expect.ts
@@ -0,0 +1,4 @@
export default (files: Record<string, string>) => {
expect(files['cjs/index.js']).toContain('await');
expect(files['esm/index.js']).toContain('yield');
};
1 change: 1 addition & 0 deletions tests/fixtures/build/bundless-esbuild-targets/package.json
@@ -0,0 +1 @@
{}
5 changes: 5 additions & 0 deletions tests/fixtures/build/bundless-esbuild-targets/src/index.ts
@@ -0,0 +1,5 @@
async function hello() {
return await Promise.resolve('ok');
}

hello();
@@ -0,0 +1 @@
{}
10 changes: 10 additions & 0 deletions tests/fixtures/build/bundless-swc-targets/.fatherrc.ts
@@ -0,0 +1,10 @@
export default {
cjs: {
transformer: 'swc',
targets: 'es2017',
},
esm: {
transformer: 'swc',
targets: 'es5',
},
};
4 changes: 4 additions & 0 deletions tests/fixtures/build/bundless-swc-targets/expect.ts
@@ -0,0 +1,4 @@
export default (files: Record<string, string>) => {
expect(files['cjs/index.js']).toContain('await');
expect(files['esm/index.js']).toContain('_asyncToGenerator');
};
1 change: 1 addition & 0 deletions tests/fixtures/build/bundless-swc-targets/package.json
@@ -0,0 +1 @@
{}
5 changes: 5 additions & 0 deletions tests/fixtures/build/bundless-swc-targets/src/index.ts
@@ -0,0 +1,5 @@
async function hello() {
return await Promise.resolve('ok');
}

hello();
1 change: 1 addition & 0 deletions tests/fixtures/build/bundless-swc-targets/tsconfig.json
@@ -0,0 +1 @@
{}