Skip to content

Commit

Permalink
add main fields and prefer "main" for node (#363)
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Sep 12, 2020
1 parent 7de7ba4 commit 25321ae
Show file tree
Hide file tree
Showing 12 changed files with 268 additions and 90 deletions.
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,15 @@

The workaround for these problems in this release is that esbuild will now exclusively use `"main"` for a package that is loaded using `require()` at least once. Otherwise, if a package is only loaded using `import`, esbuild will exclusively use the `"module"` field. This still takes advantage of tree shaking for ECMAScript modules but gracefully falls back to CommonJS for compatibility.

Keep in mind that the [`"browser"` field](https://github.com/defunctzombie/package-browser-field-spec) still takes precedence over both `"module"` and `"main"`.
Keep in mind that the [`"browser"` field](https://github.com/defunctzombie/package-browser-field-spec) still takes precedence over both `"module"` and `"main"` when building for the browser platform.

* Add the `--main-fields=` flag ([#363](https://github.com/evanw/esbuild/issues/363))

This adopts a configuration option from Webpack that lets you specify the order of "main fields" from `package.json` to use when determining the main module file for a package. Node only uses `main` but bundlers often respect other ones too such as `module` or `browser`. You can read more about this feature in the Webpack documentation [here](https://webpack.js.org/configuration/resolve/#resolvemainfields).

The default order when targeting the browser is essentially `browser,module,main` with the caveat that `main` may be chosen over `module` for CommonJS compatibility as described above. If choosing `module` over `main` at the expense of CommonJS compatibility is important to you, this behavior can be disabled by explicitly specifying `--main-fields=browser,module,main`.

The default order when targeting node is `main,module`. Note that this is different than Webpack, which defaults to `module,main`. This is also for compatibility because some packages incorrectly treat `module` as meaning "code for the browser" instead of what it actually means, which is "code for ES6 environments". Unfortunately this disables most tree shaking that would otherwise be possible because it means CommonJS modules will be chosen over ECMAScript modules. If choosing `module` over `main` is important to you (e.g. to potentially take advantage of improved tree shaking), this behavior can be disabled by explicitly specifying `--main-fields=module,main`.

* Additional validation of arguments to JavaScript API calls ([#381](https://github.com/evanw/esbuild/issues/381))

Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,9 @@ Advanced options:
--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"
--main-fields=... Override the main file order in package.json
(default "browser,module,main" when platform is
browser and "main,module" when platform is node)
--color=... Force use of color terminal escapes (true or false)

Examples:
Expand Down
3 changes: 3 additions & 0 deletions cmd/esbuild/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ Advanced options:
--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"
--main-fields=... Override the main file order in package.json
(default "browser,module,main" when platform is
browser and "main,module" when platform is node)
--color=... Force use of color terminal escapes (true or false)
Examples:
Expand Down
98 changes: 96 additions & 2 deletions internal/bundler/bundler_packagejson_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,7 @@ func TestPackageJsonBrowserOverModuleBrowser(t *testing.T) {
})
}

func TestPackageJsonBrowserOverModuleNode(t *testing.T) {
func TestPackageJsonBrowserOverMainNode(t *testing.T) {
packagejson_suite.expectBundled(t, bundled{
files: map[string]string{
"/Users/user/project/src/entry.js": `
Expand Down Expand Up @@ -562,7 +562,7 @@ func TestPackageJsonBrowserWithModuleBrowser(t *testing.T) {
})
}

func TestPackageJsonBrowserWithModuleNode(t *testing.T) {
func TestPackageJsonBrowserWithMainNode(t *testing.T) {
packagejson_suite.expectBundled(t, bundled{
files: map[string]string{
"/Users/user/project/src/entry.js": `
Expand Down Expand Up @@ -727,6 +727,42 @@ func TestPackageJsonDualPackageHazardImportAndRequireSeparateFiles(t *testing.T)
})
}

func TestPackageJsonDualPackageHazardImportAndRequireForceModuleBeforeMain(t *testing.T) {
packagejson_suite.expectBundled(t, bundled{
files: map[string]string{
"/Users/user/project/src/entry.js": `
import './test-main'
import './test-module'
`,
"/Users/user/project/src/test-main.js": `
console.log(require('demo-pkg'))
`,
"/Users/user/project/src/test-module.js": `
import value from 'demo-pkg'
console.log(value)
`,
"/Users/user/project/node_modules/demo-pkg/package.json": `
{
"main": "./main.js",
"module": "./module.js"
}
`,
"/Users/user/project/node_modules/demo-pkg/main.js": `
module.exports = 'main'
`,
"/Users/user/project/node_modules/demo-pkg/module.js": `
export default 'module'
`,
},
entryPaths: []string{"/Users/user/project/src/entry.js"},
options: config.Options{
Mode: config.ModeBundle,
MainFields: []string{"module", "main"},
AbsOutputFile: "/Users/user/project/out.js",
},
})
}

func TestPackageJsonDualPackageHazardImportAndRequireBrowser(t *testing.T) {
packagejson_suite.expectBundled(t, bundled{
files: map[string]string{
Expand Down Expand Up @@ -771,3 +807,61 @@ func TestPackageJsonDualPackageHazardImportAndRequireBrowser(t *testing.T) {
},
})
}

func TestPackageJsonMainFieldsA(t *testing.T) {
packagejson_suite.expectBundled(t, bundled{
files: map[string]string{
"/Users/user/project/src/entry.js": `
import value from 'demo-pkg'
console.log(value)
`,
"/Users/user/project/node_modules/demo-pkg/package.json": `
{
"a": "./a.js",
"b": "./b.js"
}
`,
"/Users/user/project/node_modules/demo-pkg/a.js": `
module.exports = 'a'
`,
"/Users/user/project/node_modules/demo-pkg/b.js": `
export default 'b'
`,
},
entryPaths: []string{"/Users/user/project/src/entry.js"},
options: config.Options{
Mode: config.ModeBundle,
MainFields: []string{"a", "b"},
AbsOutputFile: "/Users/user/project/out.js",
},
})
}

func TestPackageJsonMainFieldsB(t *testing.T) {
packagejson_suite.expectBundled(t, bundled{
files: map[string]string{
"/Users/user/project/src/entry.js": `
import value from 'demo-pkg'
console.log(value)
`,
"/Users/user/project/node_modules/demo-pkg/package.json": `
{
"a": "./a.js",
"b": "./b.js"
}
`,
"/Users/user/project/node_modules/demo-pkg/a.js": `
module.exports = 'a'
`,
"/Users/user/project/node_modules/demo-pkg/b.js": `
export default 'b'
`,
},
entryPaths: []string{"/Users/user/project/src/entry.js"},
options: config.Options{
Mode: config.ModeBundle,
MainFields: []string{"b", "a"},
AbsOutputFile: "/Users/user/project/out.js",
},
})
}
87 changes: 67 additions & 20 deletions internal/bundler/snapshots/snapshots_packagejson.txt
Original file line number Diff line number Diff line change
Expand Up @@ -158,29 +158,32 @@ const demo_pkg = __toModule(require_main_browser());
console.log(demo_pkg.default());

================================================================================
TestPackageJsonBrowserOverModuleBrowser
TestPackageJsonBrowserOverMainNode
---------- /Users/user/project/out.js ----------
// /Users/user/project/node_modules/demo-pkg/main.browser.js
var require_main_browser = __commonJS((exports, module) => {
// /Users/user/project/node_modules/demo-pkg/main.js
var require_main = __commonJS((exports, module) => {
module.exports = function() {
return 123;
};
});

// /Users/user/project/src/entry.js
const demo_pkg = __toModule(require_main_browser());
const demo_pkg = __toModule(require_main());
console.log(demo_pkg.default());

================================================================================
TestPackageJsonBrowserOverModuleNode
TestPackageJsonBrowserOverModuleBrowser
---------- /Users/user/project/out.js ----------
// /Users/user/project/node_modules/demo-pkg/main.esm.js
function main_esm_default() {
return 123;
}
// /Users/user/project/node_modules/demo-pkg/main.browser.js
var require_main_browser = __commonJS((exports, module) => {
module.exports = function() {
return 123;
};
});

// /Users/user/project/src/entry.js
console.log(main_esm_default());
const demo_pkg = __toModule(require_main_browser());
console.log(demo_pkg.default());

================================================================================
TestPackageJsonBrowserString
Expand All @@ -197,26 +200,29 @@ const demo_pkg = __toModule(require_browser());
console.log(demo_pkg.default());

================================================================================
TestPackageJsonBrowserWithModuleBrowser
TestPackageJsonBrowserWithMainNode
---------- /Users/user/project/out.js ----------
// /Users/user/project/node_modules/demo-pkg/main.browser.esm.js
function main_browser_esm_default() {
return 123;
}
// /Users/user/project/node_modules/demo-pkg/main.js
var require_main = __commonJS((exports, module) => {
module.exports = function() {
return 123;
};
});

// /Users/user/project/src/entry.js
console.log(main_browser_esm_default());
const demo_pkg = __toModule(require_main());
console.log(demo_pkg.default());

================================================================================
TestPackageJsonBrowserWithModuleNode
TestPackageJsonBrowserWithModuleBrowser
---------- /Users/user/project/out.js ----------
// /Users/user/project/node_modules/demo-pkg/main.esm.js
function main_esm_default() {
// /Users/user/project/node_modules/demo-pkg/main.browser.esm.js
function main_browser_esm_default() {
return 123;
}

// /Users/user/project/src/entry.js
console.log(main_esm_default());
console.log(main_browser_esm_default());

================================================================================
TestPackageJsonDualPackageHazardImportAndRequireBrowser
Expand All @@ -235,6 +241,26 @@ console.log(demo_pkg.default);

// /Users/user/project/src/entry.js

================================================================================
TestPackageJsonDualPackageHazardImportAndRequireForceModuleBeforeMain
---------- /Users/user/project/out.js ----------
// /Users/user/project/node_modules/demo-pkg/module.js
var require_module = __commonJS((exports) => {
__export(exports, {
default: () => module_default
});
var module_default = "module";
});

// /Users/user/project/src/test-main.js
console.log(require_module());

// /Users/user/project/src/test-module.js
const demo_pkg = __toModule(require_module());
console.log(demo_pkg.default);

// /Users/user/project/src/entry.js

================================================================================
TestPackageJsonDualPackageHazardImportAndRequireSameFile
---------- /Users/user/project/out.js ----------
Expand Down Expand Up @@ -298,6 +324,27 @@ var require_custom_main = __commonJS((exports, module) => {
const demo_pkg = __toModule(require_custom_main());
console.log(demo_pkg.default());

================================================================================
TestPackageJsonMainFieldsA
---------- /Users/user/project/out.js ----------
// /Users/user/project/node_modules/demo-pkg/a.js
var require_a = __commonJS((exports, module) => {
module.exports = "a";
});

// /Users/user/project/src/entry.js
const demo_pkg = __toModule(require_a());
console.log(demo_pkg.default);

================================================================================
TestPackageJsonMainFieldsB
---------- /Users/user/project/out.js ----------
// /Users/user/project/node_modules/demo-pkg/b.js
var b_default = "b";

// /Users/user/project/src/entry.js
console.log(b_default);

================================================================================
TestPackageJsonModule
---------- /Users/user/project/out.js ----------
Expand Down
1 change: 1 addition & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ type Options struct {
UnsupportedFeatures compat.Feature

ExtensionOrder []string
MainFields []string
ExternalModules ExternalModules

AbsOutputFile string
Expand Down
Loading

0 comments on commit 25321ae

Please sign in to comment.