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
20 changes: 18 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` 产物默认兼容性为 Chrome65 而不是 IE11。

### sourcemap

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

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

### targets

- 类型: `Record<string, number>`
- 默认值:`<auto>`

指定源码编译产物的兼容性,不同目标平台和编译模式下的默认值如下:

| `platform` | `transformer` | default value |
| ---------- | ------------- | ---------------- |
| `browser` | `babel` | `{ ie: 11 }` |
| `browser` | `esbuild` | `{ chrome: 65 }` |
| `browser` | `swc` | `{ chrome: 65 }` |
| `node` | `babel` | `{ node: 14 }` |
| `node` | `esbuild` | `{ node: 14 }` |
| `node` | `babel` | `{ node: 14 }` |

## 构建配置

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
14 changes: 5 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 @@ -133,6 +130,9 @@ const swcTransformer: IJSTransformer = async function (content) {
)
: undefined,
sourceMaps: this.config.sourcemap,
env: {
targets: getBundlessTargets(this.config),
},

jsc: {
baseUrl: this.paths.cwd,
Expand All @@ -142,10 +142,6 @@ const swcTransformer: IJSTransformer = async function (content) {
...(isTSFile && isJSXFile ? { tsx: true } : {}),
...(!isTSFile && isJSXFile ? { jsx: true } : {}),
},
target:
this.config.platform === IFatherPlatformTypes.BROWSER
? 'es5'
: 'es2019',
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
49 changes: 48 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,44 @@ export function ensureRelativePath(relativePath: string) {
}
return relativePath;
}

const defaultCompileTarget: Record<
IFatherPlatformTypes,
Record<IFatherJSTransformerTypes, any>
> = {
[IFatherPlatformTypes.BROWSER]: {
[IFatherJSTransformerTypes.BABEL]: { ie: 11 },
[IFatherJSTransformerTypes.ESBUILD]: ['chrome65'],
[IFatherJSTransformerTypes.SWC]: { chrome: 65 },
},
[IFatherPlatformTypes.NODE]: {
[IFatherJSTransformerTypes.BABEL]: { node: 14 },
[IFatherJSTransformerTypes.ESBUILD]: ['node14'],
[IFatherJSTransformerTypes.SWC]: { node: 14 },
},
};

export function getBundleTargets({ targets }: IFatherBaseConfig) {
if (!targets || !Object.keys(targets).length) {
return defaultCompileTarget[IFatherPlatformTypes.BROWSER][
IFatherJSTransformerTypes.BABEL
];
}

return targets;
}

export function getBundlessTargets(config: IBundlessConfig) {
const { platform, transformer, targets } = config;

// targets is undefined or empty, fallback to default
if (!targets || !Object.keys(targets).length) {
return defaultCompileTarget[platform!][transformer!];
}
// esbuild accept string or string[]
if (transformer === IFatherJSTransformerTypes.ESBUILD) {
return Object.keys(targets).map((name) => `${name}${targets![name]}`);
}

return targets;
}
9 changes: 7 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,7 @@ 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.object().optional(),
};
}

Expand All @@ -34,7 +35,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?: Record<string, number>;
}

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: { chrome: 85 },
},
};
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: { chrome: 85 },
},
esm: {
transformer: 'babel',
targets: { ie: 11, chrome: 60 },
},
};
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: { chrome: 70 },
cjs: {
transformer: 'esbuild',
targets: { chrome: 85 },
},
esm: {
transformer: 'esbuild',
targets: { chrome: 54 },
},
};
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: { chrome: 85 },
},
esm: {
transformer: 'swc',
targets: { ie: 11 },
},
};
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 @@
{}