From aa4cc2fef50e0d26e6cb0252b5a3f758f5984f2c Mon Sep 17 00:00:00 2001 From: Sascha Tandel Date: Wed, 26 Aug 2020 17:16:14 +0200 Subject: [PATCH] feat: support format option for transform This allows to use `transform` to output a different format without going through `build` which would resolve all imports. Import handling: - `cjs`: imports are not resolved and re-written as `require` calls - `iife`: any import will through --- internal/bundler/bundler.go | 2 +- internal/config/config.go | 3 ++- lib/common.ts | 3 ++- lib/types.ts | 3 ++- pkg/api/api.go | 2 ++ pkg/api/api_impl.go | 9 +++++++++ pkg/cli/cli_impl.go | 14 +++++++++++++- scripts/js-api-tests.js | 32 ++++++++++++++++++++++++++++++++ 8 files changed, 63 insertions(+), 5 deletions(-) diff --git a/internal/bundler/bundler.go b/internal/bundler/bundler.go index 38f7d1e27bf..e1e58f788f8 100644 --- a/internal/bundler/bundler.go +++ b/internal/bundler/bundler.go @@ -259,7 +259,7 @@ func parseFile(args parseArgs) { if args.options.IsBundling { result.resolveResults = make([]*resolver.ResolveResult, len(result.file.ast.ImportRecords)) - if len(result.file.ast.ImportRecords) > 0 { + if len(result.file.ast.ImportRecords) > 0 && !(args.options.IsTransforming && args.options.OutputFormat != config.FormatIIFE) { cache := make(map[string]*resolver.ResolveResult) // Resolve relative to the parent directory of the source file with the diff --git a/internal/config/config.go b/internal/config/config.go index 6ca4719d271..6c5e108e558 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -145,7 +145,8 @@ type ExternalModules struct { type Options struct { // true: imports are scanned and bundled along with the file // false: imports are left alone and the file is passed through as-is - IsBundling bool + IsBundling bool + IsTransforming bool RemoveWhitespace bool MinifyIdentifiers bool diff --git a/lib/common.ts b/lib/common.ts index f6a38e44cf9..2c80a770c1f 100644 --- a/lib/common.ts +++ b/lib/common.ts @@ -20,6 +20,8 @@ function pushCommonFlags(flags: string[], options: types.CommonOptions, isTTY: b if (options.minifyWhitespace) flags.push('--minify-whitespace'); if (options.minifyIdentifiers) flags.push('--minify-identifiers'); + if (options.format) flags.push(`--format=${options.format}`); + if (options.jsxFactory) flags.push(`--jsx-factory=${options.jsxFactory}`); if (options.jsxFragment) flags.push(`--jsx-fragment=${options.jsxFragment}`); if (options.define) { @@ -50,7 +52,6 @@ function flagsForBuildOptions(options: types.BuildOptions, isTTY: boolean): [str if (options.outfile) flags.push(`--outfile=${options.outfile}`); if (options.outdir) flags.push(`--outdir=${options.outdir}`); if (options.platform) flags.push(`--platform=${options.platform}`); - if (options.format) flags.push(`--format=${options.format}`); if (options.tsconfig) flags.push(`--tsconfig=${options.tsconfig}`); if (options.resolveExtensions) flags.push(`--resolve-extensions=${options.resolveExtensions.join(',')}`); if (options.external) for (let name of options.external) flags.push(`--external:${name}`); diff --git a/lib/types.ts b/lib/types.ts index beadc810b01..505a13c98a4 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -9,6 +9,8 @@ export interface CommonOptions { target?: string | string[]; strict?: boolean | Strict[]; + format?: Format; + minify?: boolean; minifyWhitespace?: boolean; minifyIdentifiers?: boolean; @@ -32,7 +34,6 @@ export interface BuildOptions extends CommonOptions { metafile?: string; outdir?: string; platform?: Platform; - format?: Format; color?: boolean; external?: string[]; loader?: { [ext: string]: Loader }; diff --git a/pkg/api/api.go b/pkg/api/api.go index 1759826b467..3feafea8b01 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -271,6 +271,8 @@ type TransformOptions struct { Defines map[string]string PureFunctions []string + Format Format + Sourcefile string Loader Loader } diff --git a/pkg/api/api_impl.go b/pkg/api/api_impl.go index 124cb7ab2fd..3404211d48e 100644 --- a/pkg/api/api_impl.go +++ b/pkg/api/api_impl.go @@ -592,6 +592,7 @@ func transformImpl(input string, transformOpts TransformOptions) TransformResult // Convert and validate the transformOpts options := config.Options{ + IsTransforming: true, UnsupportedFeatures: validateFeatures(log, transformOpts.Target, transformOpts.Engines), Strict: validateStrict(transformOpts.Strict), JSX: config.JSXOptions{ @@ -603,6 +604,7 @@ func transformImpl(input string, transformOpts TransformOptions) TransformResult MangleSyntax: transformOpts.MinifySyntax, RemoveWhitespace: transformOpts.MinifyWhitespace, MinifyIdentifiers: transformOpts.MinifyIdentifiers, + OutputFormat: validateFormat(transformOpts.Format), AbsOutputFile: transformOpts.Sourcefile + "-out", Stdin: &config.StdinInfo{ Loader: validateLoader(transformOpts.Loader), @@ -610,6 +612,13 @@ func transformImpl(input string, transformOpts TransformOptions) TransformResult SourceFile: transformOpts.Sourcefile, }, } + + if options.OutputFormat == config.FormatESModule { + options.OutputFormat = config.FormatPreserve + } else if options.OutputFormat != config.FormatPreserve { + options.IsBundling = true + } + if options.SourceMap == config.SourceMapLinkedWithComment { // Linked source maps don't make sense because there's no output file name log.AddError(nil, ast.Loc{}, "Cannot transform with linked source maps") diff --git a/pkg/cli/cli_impl.go b/pkg/cli/cli_impl.go index 0e5db689054..fb01da2ef16 100644 --- a/pkg/cli/cli_impl.go +++ b/pkg/cli/cli_impl.go @@ -238,15 +238,27 @@ func parseOptionsImpl(osArgs []string, buildOpts *api.BuildOptions, transformOpt return fmt.Errorf("Invalid platform: %q (valid: browser, node)", value) } - case strings.HasPrefix(arg, "--format=") && buildOpts != nil: + case strings.HasPrefix(arg, "--format="): value := arg[len("--format="):] switch value { case "iife": + if buildOpts != nil { buildOpts.Format = api.FormatIIFE + } else { + transformOpts.Format = api.FormatIIFE + } case "cjs": + if buildOpts != nil { buildOpts.Format = api.FormatCommonJS + } else { + transformOpts.Format = api.FormatCommonJS + } case "esm": + if buildOpts != nil { buildOpts.Format = api.FormatESModule + } else { + transformOpts.Format = api.FormatESModule + } default: return fmt.Errorf("Invalid format: %q (valid: iife, cjs, esm)", value) } diff --git a/scripts/js-api-tests.js b/scripts/js-api-tests.js index b3ddecaa972..99beaebc6e3 100644 --- a/scripts/js-api-tests.js +++ b/scripts/js-api-tests.js @@ -463,6 +463,38 @@ let transformTests = { assert.strictEqual(js, `export const foo = 123;\n`) }, + async esm_to_esm({ service }) { + const { js } = await service.transform(`import path from 'path';export default path`, { format: 'esm' }) + + assert.strictEqual(js, `import path2 from "path";\nexport default path2;\n`) + }, + + async esm_to_cjs({ service, testDir }) { + const { js } = await service.transform(`import path from 'path';export default path`, { format: 'cjs' }) + + const output = path.join(testDir, 'out.js') + await writeFileAsync(output, js) + const result = require(output) + assert.strictEqual(result.default, path) + assert.strictEqual(result.__esModule, true) + }, + + async esm_to_iife({ service, testDir }) { + const { js } = await service.transform(`const a = document.querySelector('.a')`, { format: 'iife' }) + + assert.strictEqual(js, `(() => {\n // \n const a = document.querySelector(".a");\n})();\n`) + }, + + async esm_to_iife_with_import({ service, testDir }) { + try { + await service.transform(`import path from 'path';export default path`, { format: 'iife' }) + + assert.fail('iife can not import') + } catch (error) { + assert.strictEqual(error.message, `Transform failed with 1 error:\n:1:17: error: Could not resolve "path"`) + } + }, + async jsx({ service }) { const { js } = await service.transform(`console.log(
)`, { loader: 'jsx' }) assert.strictEqual(js, `console.log(/* @__PURE__ */ React.createElement("div", null));\n`)