diff --git a/docs/config.md b/docs/config.md index 71f9a9fe..b6d72078 100644 --- a/docs/config.md +++ b/docs/config.md @@ -48,9 +48,9 @@ father 支持以下配置项。 - 类型:`browser` | `node` - 默认值:`` -指定构建产物的目标平台,其中 `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 @@ -61,6 +61,22 @@ father 支持以下配置项。 > 注:Bundless 模式下 map 对象的 file 字段为空 +### targets + +- 类型: `Record` +- 默认值:`` + +指定源码编译产物的兼容性,不同目标平台和编译模式下的默认值如下: + +| `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 构建模式。 diff --git a/src/builder/bundle/index.ts b/src/builder/bundle/index.ts index 7f37cdcc..cc611f41 100644 --- a/src/builder/bundle/index.ts +++ b/src/builder/bundle/index.ts @@ -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')), @@ -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/], @@ -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: {}, diff --git a/src/builder/bundless/loaders/javascript/babel.ts b/src/builder/bundless/loaders/javascript/babel.ts index f8a62c97..88c1532b 100644 --- a/src/builder/bundless/loaders/javascript/babel.ts +++ b/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'; @@ -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), diff --git a/src/builder/bundless/loaders/javascript/esbuild.ts b/src/builder/bundless/loaders/javascript/esbuild.ts index f3e6b3f4..8702a816 100644 --- a/src/builder/bundless/loaders/javascript/esbuild.ts +++ b/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'; /** @@ -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, diff --git a/src/builder/bundless/loaders/javascript/swc.ts b/src/builder/bundless/loaders/javascript/swc.ts index 145128b2..79d31622 100644 --- a/src/builder/bundless/loaders/javascript/swc.ts +++ b/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'; @@ -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, @@ -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), }, diff --git a/src/builder/config.ts b/src/builder/config.ts index 017882c4..63ee4276 100644 --- a/src/builder/config.ts +++ b/src/builder/config.ts @@ -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))]; } diff --git a/src/builder/utils.ts b/src/builder/utils.ts index 2568333e..cb168a70 100644 --- a/src/builder/utils.ts +++ b/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 ( @@ -62,3 +68,44 @@ export function ensureRelativePath(relativePath: string) { } return relativePath; } + +const defaultCompileTarget: Record< + IFatherPlatformTypes, + Record +> = { + [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; +} diff --git a/src/features/configPlugins/schema.ts b/src/features/configPlugins/schema.ts index 35f11987..93124df2 100644 --- a/src/features/configPlugins/schema.ts +++ b/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 any> { return { @@ -16,6 +16,7 @@ function getCommonSchemas(): Record any> { extraBabelPresets: (Joi) => Joi.array().optional(), extraBabelPlugins: (Joi) => Joi.array().optional(), sourcemap: (Joi) => Joi.boolean().optional(), + targets: (Joi) => Joi.object().optional(), }; } @@ -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()), }); diff --git a/src/types.ts b/src/types.ts index f48c3e3e..f6f9bcbf 100644 --- a/src/types.ts +++ b/src/types.ts @@ -117,6 +117,11 @@ export interface IFatherBaseConfig { * output sourcemap */ sourcemap?: boolean; + + /** + * compile targets + */ + targets?: Record; } export interface IFatherBundlessConfig extends IFatherBaseConfig { diff --git a/tests/fixtures/build/bundle-targets/.fatherrc.ts b/tests/fixtures/build/bundle-targets/.fatherrc.ts new file mode 100644 index 00000000..55edb31b --- /dev/null +++ b/tests/fixtures/build/bundle-targets/.fatherrc.ts @@ -0,0 +1,5 @@ +export default { + umd: { + targets: { chrome: 85 }, + }, +}; diff --git a/tests/fixtures/build/bundle-targets/expect.ts b/tests/fixtures/build/bundle-targets/expect.ts new file mode 100644 index 00000000..74b20758 --- /dev/null +++ b/tests/fixtures/build/bundle-targets/expect.ts @@ -0,0 +1,3 @@ +export default (files: Record) => { + expect(files['umd/index.min.js']).toContain('await'); +}; diff --git a/tests/fixtures/build/bundle-targets/package.json b/tests/fixtures/build/bundle-targets/package.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/tests/fixtures/build/bundle-targets/package.json @@ -0,0 +1 @@ +{} diff --git a/tests/fixtures/build/bundle-targets/src/index.ts b/tests/fixtures/build/bundle-targets/src/index.ts new file mode 100644 index 00000000..02ae7784 --- /dev/null +++ b/tests/fixtures/build/bundle-targets/src/index.ts @@ -0,0 +1,5 @@ +async function hello() { + return await Promise.resolve('ok'); +} + +hello(); diff --git a/tests/fixtures/build/bundle-targets/tsconfig.json b/tests/fixtures/build/bundle-targets/tsconfig.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/tests/fixtures/build/bundle-targets/tsconfig.json @@ -0,0 +1 @@ +{} diff --git a/tests/fixtures/build/bundless-babel-targets/.fatherrc.ts b/tests/fixtures/build/bundless-babel-targets/.fatherrc.ts new file mode 100644 index 00000000..db61fe10 --- /dev/null +++ b/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 }, + }, +}; diff --git a/tests/fixtures/build/bundless-babel-targets/expect.ts b/tests/fixtures/build/bundless-babel-targets/expect.ts new file mode 100644 index 00000000..ab8d54b8 --- /dev/null +++ b/tests/fixtures/build/bundless-babel-targets/expect.ts @@ -0,0 +1,4 @@ +export default (files: Record) => { + expect(files['cjs/index.js']).toContain('await'); + expect(files['esm/index.js']).toContain('_regeneratorRuntime'); +}; diff --git a/tests/fixtures/build/bundless-babel-targets/package.json b/tests/fixtures/build/bundless-babel-targets/package.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/tests/fixtures/build/bundless-babel-targets/package.json @@ -0,0 +1 @@ +{} diff --git a/tests/fixtures/build/bundless-babel-targets/src/index.ts b/tests/fixtures/build/bundless-babel-targets/src/index.ts new file mode 100644 index 00000000..02ae7784 --- /dev/null +++ b/tests/fixtures/build/bundless-babel-targets/src/index.ts @@ -0,0 +1,5 @@ +async function hello() { + return await Promise.resolve('ok'); +} + +hello(); diff --git a/tests/fixtures/build/bundless-babel-targets/tsconfig.json b/tests/fixtures/build/bundless-babel-targets/tsconfig.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/tests/fixtures/build/bundless-babel-targets/tsconfig.json @@ -0,0 +1 @@ +{} diff --git a/tests/fixtures/build/bundless-esbuild-targets/.fatherrc.ts b/tests/fixtures/build/bundless-esbuild-targets/.fatherrc.ts new file mode 100644 index 00000000..d7082512 --- /dev/null +++ b/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 }, + }, +}; diff --git a/tests/fixtures/build/bundless-esbuild-targets/expect.ts b/tests/fixtures/build/bundless-esbuild-targets/expect.ts new file mode 100644 index 00000000..2ca87809 --- /dev/null +++ b/tests/fixtures/build/bundless-esbuild-targets/expect.ts @@ -0,0 +1,4 @@ +export default (files: Record) => { + expect(files['cjs/index.js']).toContain('await'); + expect(files['esm/index.js']).toContain('yield'); +}; diff --git a/tests/fixtures/build/bundless-esbuild-targets/package.json b/tests/fixtures/build/bundless-esbuild-targets/package.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/tests/fixtures/build/bundless-esbuild-targets/package.json @@ -0,0 +1 @@ +{} diff --git a/tests/fixtures/build/bundless-esbuild-targets/src/index.ts b/tests/fixtures/build/bundless-esbuild-targets/src/index.ts new file mode 100644 index 00000000..02ae7784 --- /dev/null +++ b/tests/fixtures/build/bundless-esbuild-targets/src/index.ts @@ -0,0 +1,5 @@ +async function hello() { + return await Promise.resolve('ok'); +} + +hello(); diff --git a/tests/fixtures/build/bundless-esbuild-targets/tsconfig.json b/tests/fixtures/build/bundless-esbuild-targets/tsconfig.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/tests/fixtures/build/bundless-esbuild-targets/tsconfig.json @@ -0,0 +1 @@ +{} diff --git a/tests/fixtures/build/bundless-swc-targets/.fatherrc.ts b/tests/fixtures/build/bundless-swc-targets/.fatherrc.ts new file mode 100644 index 00000000..b747a4e2 --- /dev/null +++ b/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 }, + }, +}; diff --git a/tests/fixtures/build/bundless-swc-targets/expect.ts b/tests/fixtures/build/bundless-swc-targets/expect.ts new file mode 100644 index 00000000..af6ce534 --- /dev/null +++ b/tests/fixtures/build/bundless-swc-targets/expect.ts @@ -0,0 +1,4 @@ +export default (files: Record) => { + expect(files['cjs/index.js']).toContain('await'); + expect(files['esm/index.js']).toContain('_asyncToGenerator'); +}; diff --git a/tests/fixtures/build/bundless-swc-targets/package.json b/tests/fixtures/build/bundless-swc-targets/package.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/tests/fixtures/build/bundless-swc-targets/package.json @@ -0,0 +1 @@ +{} diff --git a/tests/fixtures/build/bundless-swc-targets/src/index.ts b/tests/fixtures/build/bundless-swc-targets/src/index.ts new file mode 100644 index 00000000..02ae7784 --- /dev/null +++ b/tests/fixtures/build/bundless-swc-targets/src/index.ts @@ -0,0 +1,5 @@ +async function hello() { + return await Promise.resolve('ok'); +} + +hello(); diff --git a/tests/fixtures/build/bundless-swc-targets/tsconfig.json b/tests/fixtures/build/bundless-swc-targets/tsconfig.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/tests/fixtures/build/bundless-swc-targets/tsconfig.json @@ -0,0 +1 @@ +{}