mirror of
https://github.com/zhigang1992/esbuild.git
synced 2026-01-12 17:13:19 +08:00
fix #281: --out-extension for custom extensions
This commit is contained in:
@@ -16,6 +16,10 @@
|
||||
|
||||
When the `tsconfig.json` settings have been force-overridden using the new `--tsconfig` flag, the path resolution behavior behaved subtly differently than if esbuild naturally discovers the `tsconfig.json` file without the flag. The difference caused package paths present in a `node_modules` folder to incorrectly take precedence over custom path aliases configured in `tsconfig.json`. The ordering has been corrected such that custom path aliases always take place over `node_modules`.
|
||||
|
||||
* Add the `--out-extension` flag for custom output extensions ([#281](https://github.com/evanw/esbuild/issues/281))
|
||||
|
||||
Previously esbuild could only output files ending in `.js`. Now you can override this to another extension by passing something like `--out-extension:.js=.mjs`. This allows generating output files with the node-specific `.cjs` and `.mjs` extensions without having to use a separate command to rename them afterwards.
|
||||
|
||||
## 0.6.5
|
||||
|
||||
* Fix IIFE wrapper for ES5
|
||||
|
||||
@@ -383,6 +383,7 @@ Advanced options:
|
||||
--strict Transforms handle edge cases but have more overhead
|
||||
--pure=N Mark the name N as a pure function for tree shaking
|
||||
--tsconfig=... Use this tsconfig.json file instead of other ones
|
||||
--out-extension:.js=.mjs Use a custom output extension instead of ".js"
|
||||
|
||||
Examples:
|
||||
# Produces dist/entry_point.js and dist/entry_point.js.map
|
||||
|
||||
@@ -54,6 +54,7 @@ Advanced options:
|
||||
--strict Transforms handle edge cases but have more overhead
|
||||
--pure=N Mark the name N as a pure function for tree shaking
|
||||
--tsconfig=... Use this tsconfig.json file instead of other ones
|
||||
--out-extension:.js=.mjs Use a custom output extension instead of ".js"
|
||||
|
||||
Examples:
|
||||
# Produces dist/entry_point.js and dist/entry_point.js.map
|
||||
|
||||
@@ -4659,3 +4659,45 @@ func TestIIFE_ES5(t *testing.T) {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestOutputExtensionRemappingFile(t *testing.T) {
|
||||
expectBundled(t, bundled{
|
||||
files: map[string]string{
|
||||
"/entry.js": `
|
||||
console.log('test');
|
||||
`,
|
||||
},
|
||||
entryPaths: []string{"/entry.js"},
|
||||
options: config.Options{
|
||||
IsBundling: true,
|
||||
OutputExtensions: map[string]string{".js": ".notjs"},
|
||||
AbsOutputFile: "/out.js",
|
||||
},
|
||||
expected: map[string]string{
|
||||
"/out.js": `// /entry.js
|
||||
console.log("test");
|
||||
`,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestOutputExtensionRemappingDir(t *testing.T) {
|
||||
expectBundled(t, bundled{
|
||||
files: map[string]string{
|
||||
"/entry.js": `
|
||||
console.log('test');
|
||||
`,
|
||||
},
|
||||
entryPaths: []string{"/entry.js"},
|
||||
options: config.Options{
|
||||
IsBundling: true,
|
||||
OutputExtensions: map[string]string{".js": ".notjs"},
|
||||
AbsOutputDir: "/out",
|
||||
},
|
||||
expected: map[string]string{
|
||||
"/out/entry.notjs": `// /entry.js
|
||||
console.log("test");
|
||||
`,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2077,9 +2077,7 @@ func (c *linkerContext) computeChunks() []chunkMeta {
|
||||
|
||||
// Swap the extension for ".js"
|
||||
ext := c.fs.Ext(chunkRelPath)
|
||||
if ext != ".js" {
|
||||
chunkRelPath = chunkRelPath[:len(chunkRelPath)-len(ext)] + ".js"
|
||||
}
|
||||
chunkRelPath = chunkRelPath[:len(chunkRelPath)-len(ext)] + c.options.OutputExtensionFor(".js")
|
||||
}
|
||||
|
||||
// Always use cross-platform path separators to avoid problems with Windows
|
||||
@@ -2128,7 +2126,7 @@ func (c *linkerContext) computeChunks() []chunkMeta {
|
||||
bytes := []byte(lowerCaseAbsPathForWindows(chunk.relPath))
|
||||
hashBytes := sha1.Sum(bytes)
|
||||
hash := base64.URLEncoding.EncodeToString(hashBytes[:])[:8]
|
||||
chunk.relPath = "chunk." + hash + ".js"
|
||||
chunk.relPath = "chunk." + hash + c.options.OutputExtensionFor(".js")
|
||||
}
|
||||
|
||||
chunk.entryBits = partMeta.entryBits
|
||||
|
||||
@@ -152,6 +152,7 @@ type Options struct {
|
||||
|
||||
AbsOutputFile string
|
||||
AbsOutputDir string
|
||||
OutputExtensions map[string]string
|
||||
ModuleName string
|
||||
TsConfigOverride string
|
||||
ExtensionToLoader map[string]Loader
|
||||
@@ -163,3 +164,10 @@ type Options struct {
|
||||
SourceMap SourceMap
|
||||
Stdin *StdinInfo
|
||||
}
|
||||
|
||||
func (options *Options) OutputExtensionFor(key string) string {
|
||||
if ext, ok := options.OutputExtensions[key]; ok {
|
||||
return ext
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
@@ -22,7 +22,12 @@ function pushCommonFlags(flags: string[], options: types.CommonOptions, isTTY: b
|
||||
|
||||
if (options.jsxFactory) flags.push(`--jsx-factory=${options.jsxFactory}`);
|
||||
if (options.jsxFragment) flags.push(`--jsx-fragment=${options.jsxFragment}`);
|
||||
if (options.define) for (let key in options.define) flags.push(`--define:${key}=${options.define[key]}`);
|
||||
if (options.define) {
|
||||
for (let key in options.define) {
|
||||
if (key.indexOf('=') >= 0) throw new Error(`Invalid define: ${key}`);
|
||||
flags.push(`--define:${key}=${options.define[key]}`);
|
||||
}
|
||||
}
|
||||
if (options.pure) for (let fn of options.pure) flags.push(`--pure:${fn}`);
|
||||
|
||||
if (options.color) flags.push(`--color=${options.color}`);
|
||||
@@ -49,7 +54,18 @@ function flagsForBuildOptions(options: types.BuildOptions, isTTY: boolean): [str
|
||||
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}`);
|
||||
if (options.loader) for (let ext in options.loader) flags.push(`--loader:${ext}=${options.loader[ext]}`);
|
||||
if (options.loader) {
|
||||
for (let ext in options.loader) {
|
||||
if (ext.indexOf('=') >= 0) throw new Error(`Invalid extension: ${ext}`);
|
||||
flags.push(`--loader:${ext}=${options.loader[ext]}`);
|
||||
}
|
||||
}
|
||||
if (options.outExtension) {
|
||||
for (let ext in options.outExtension) {
|
||||
if (ext.indexOf('=') >= 0) throw new Error(`Invalid extension: ${ext}`);
|
||||
flags.push(`--out-extension:${ext}=${options.outExtension[ext]}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.entryPoints) {
|
||||
for (let entryPoint of options.entryPoints) {
|
||||
|
||||
@@ -39,6 +39,7 @@ export interface BuildOptions extends CommonOptions {
|
||||
resolveExtensions?: string[];
|
||||
write?: boolean;
|
||||
tsconfig?: string;
|
||||
outExtension?: { [ext: string]: string };
|
||||
|
||||
entryPoints?: string[];
|
||||
stdin?: StdinOptions;
|
||||
|
||||
@@ -217,6 +217,7 @@ type BuildOptions struct {
|
||||
Loaders map[string]Loader
|
||||
ResolveExtensions []string
|
||||
Tsconfig string
|
||||
OutExtensions map[string]string
|
||||
|
||||
EntryPoints []string
|
||||
Stdin *StdinOptions
|
||||
|
||||
@@ -216,12 +216,16 @@ func validateExternals(log logging.Log, fs fs.FS, paths []string) config.Externa
|
||||
return result
|
||||
}
|
||||
|
||||
func isValidExtension(ext string) bool {
|
||||
return len(ext) >= 2 && ext[0] == '.' && ext[len(ext)-1] != '.'
|
||||
}
|
||||
|
||||
func validateResolveExtensions(log logging.Log, order []string) []string {
|
||||
if order == nil {
|
||||
return []string{".tsx", ".ts", ".jsx", ".mjs", ".cjs", ".js", ".json"}
|
||||
}
|
||||
for _, ext := range order {
|
||||
if len(ext) < 2 || ext[0] != '.' {
|
||||
if !isValidExtension(ext) {
|
||||
log.AddError(nil, ast.Loc{}, fmt.Sprintf("Invalid file extension: %q", ext))
|
||||
}
|
||||
}
|
||||
@@ -232,7 +236,7 @@ func validateLoaders(log logging.Log, loaders map[string]Loader) map[string]conf
|
||||
result := bundler.DefaultExtensionToLoaderMap()
|
||||
if loaders != nil {
|
||||
for ext, loader := range loaders {
|
||||
if len(ext) < 2 || ext[0] != '.' || ext[len(ext)-1] == '.' {
|
||||
if !isValidExtension(ext) {
|
||||
log.AddError(nil, ast.Loc{}, fmt.Sprintf("Invalid file extension: %q", ext))
|
||||
}
|
||||
result[ext] = validateLoader(loader)
|
||||
@@ -343,6 +347,20 @@ func validatePath(log logging.Log, fs fs.FS, relPath string) string {
|
||||
return absPath
|
||||
}
|
||||
|
||||
func validateOutputExtensions(log logging.Log, outExtensions map[string]string) map[string]string {
|
||||
result := make(map[string]string)
|
||||
for key, value := range outExtensions {
|
||||
if key != ".js" {
|
||||
log.AddError(nil, ast.Loc{}, fmt.Sprintf("Invalid output extension: %q (valid: .js)", key))
|
||||
}
|
||||
if !isValidExtension(value) {
|
||||
log.AddError(nil, ast.Loc{}, fmt.Sprintf("Invalid output extension: %q", value))
|
||||
}
|
||||
result[key] = value
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func messagesOfKind(kind logging.MsgKind, msgs []logging.Msg) []Message {
|
||||
var filtered []Message
|
||||
for _, msg := range msgs {
|
||||
@@ -407,6 +425,7 @@ func buildImpl(buildOpts BuildOptions) BuildResult {
|
||||
AbsOutputFile: validatePath(log, realFS, buildOpts.Outfile),
|
||||
AbsOutputDir: validatePath(log, realFS, buildOpts.Outdir),
|
||||
AbsMetadataFile: validatePath(log, realFS, buildOpts.Metafile),
|
||||
OutputExtensions: validateOutputExtensions(log, buildOpts.OutExtensions),
|
||||
ExtensionToLoader: validateLoaders(log, buildOpts.Loaders),
|
||||
ExtensionOrder: validateResolveExtensions(log, buildOpts.ResolveExtensions),
|
||||
ExternalModules: validateExternals(log, realFS, buildOpts.Externals),
|
||||
|
||||
@@ -185,6 +185,17 @@ func parseOptionsImpl(osArgs []string, buildOpts *api.BuildOptions, transformOpt
|
||||
transformOpts.Engines = engines
|
||||
}
|
||||
|
||||
case strings.HasPrefix(arg, "--out-extension:") && buildOpts != nil:
|
||||
value := arg[len("--out-extension:"):]
|
||||
equals := strings.IndexByte(value, '=')
|
||||
if equals == -1 {
|
||||
return fmt.Errorf("Missing \"=\": %q", value)
|
||||
}
|
||||
if buildOpts.OutExtensions == nil {
|
||||
buildOpts.OutExtensions = make(map[string]string)
|
||||
}
|
||||
buildOpts.OutExtensions[value[:equals]] = value[equals+1:]
|
||||
|
||||
case arg == "--strict":
|
||||
value := api.StrictOptions{
|
||||
NullishCoalescing: true,
|
||||
|
||||
Reference in New Issue
Block a user