Repository: parcel-bundler/parcel (js)
Issue: Parcel ignores distDir/--dist-dir option
<!---
Thanks for filing an issue 😄 ! Before you submit, please read the following:

Search open/closed issues before submitting since someone might have asked the same thing before!
-->

# 🐛 bug report

Parcel ignores `distDir` / `--dist-dir` option with `build` command and output the bundled files in cwd.

## 🎛 Configuration (.babelrc, package.json, cli command)

**.babelrc**
```js
{
  "presets": ["@babel/preset-env", "@babel/preset-typescript"]
}
```

**package.json**
```js
{
  "name": "my-app",
  "version": "1.0.0",
  "default": "index.html",
  "license": "MIT",
  "scripts": {
    "start": " parcel index.html",
    "build": "parcel build index.html",
    "test": "jest",
    "test:watch": "jest --watch"
  },
  "targets": {
    "default": {
      "engines": {
        "browsers": "> 0.25%"
      },
      "distDir": "www",
      "publicUrl": ".",
      "sourceMap": false
    }
  },
  "dependencies": {
    "countup.js": "^2.0.4"
  },
  "devDependencies": {
    "@babel/core": "^7.11.1",
    "@babel/preset-env": "^7.11.0",
    "@babel/preset-typescript": "^7.10.4",
    "@types/jest": "^26.0.8",
    "babel-jest": "^26.2.2",
    "jest": "^26.2.2",
    "parcel": "^2.0.0-nightly.360",
    "rimraf": "3.0.0",
    "sass": "^1.25.1-test.1",
    "typescript": "^3.9.7"
  }
}
```

## 🤔 Expected Behavior

Bundled files should be outputted in `www` folder

## 😯 Current Behavior

Bundled files are outputted in cwd

## 🔦 Context

The option `sourceMap` in `package.json` is not ignored.
I tried with the **v2 alpha** first and ended up trying with the **nightly build** hoping it will solve my issue.

## 🌍 Your Environment

<!--- Include as many relevant details about the environment you experienced the bug in -->

Node: v12.18.3
Yarn: v1.22.4
Operating System : Windows 10

Pull request description: Read explicit distdir for custom targets
# ↪️ Pull Request

Closes #4996

## 💻 Examples

```json
{
  "targets": {
    "default": {
      "engines": {
        "browsers": "> 0.25%"
      },
      "distDir": "www", // <---
      "publicUrl": ".",
      "sourceMap": false
    }
  }
}
```


Patch:
diff --git a/packages/core/core/src/requests/TargetRequest.js b/packages/core/core/src/requests/TargetRequest.js
index fac3d80be..74d2288a2 100644
--- a/packages/core/core/src/requests/TargetRequest.js
+++ b/packages/core/core/src/requests/TargetRequest.js
@@ -483,9 +483,13 @@ export class TargetResolver {
           pkgFilePath,
           pkgContents,
         );
+        let pkgDir = path.dirname(nullthrows(pkgFilePath));
         targets.set(targetName, {
           name: targetName,
-          distDir,
+          distDir:
+            descriptor.distDir != null
+              ? path.resolve(pkgDir, descriptor.distDir)
+              : distDir,
           distEntry,
           publicUrl: descriptor.publicUrl ?? this.options.publicUrl,
           env: createEnvironment({


Test patch:
diff --git a/packages/core/core/test/requests/ParcelConfigRequest.test.js b/packages/core/core/test/ParcelConfigRequest.test.js
similarity index 98%
rename from packages/core/core/test/requests/ParcelConfigRequest.test.js
rename to packages/core/core/test/ParcelConfigRequest.test.js
index 697482ae3..b5532f626 100644
--- a/packages/core/core/test/requests/ParcelConfigRequest.test.js
+++ b/packages/core/core/test/ParcelConfigRequest.test.js
@@ -1,7 +1,7 @@
 // @flow
 import assert from 'assert';
 import path from 'path';
-import ParcelConfig from '../../src/ParcelConfig';
+import ParcelConfig from '../src/ParcelConfig';
 import {
   validateConfigFile,
   mergePipelines,
@@ -11,9 +11,9 @@ import {
   readAndProcessConfigChain,
   resolveParcelConfig,
   processConfig,
-} from '../../src/requests/ParcelConfigRequest';
-import {validatePackageName} from '../../src/ParcelConfig.schema';
-import {DEFAULT_OPTIONS} from '../test-utils';
+} from '../src/requests/ParcelConfigRequest';
+import {validatePackageName} from '../src/ParcelConfig.schema';
+import {DEFAULT_OPTIONS} from './test-utils';
 
 describe('loadParcelConfig', () => {
   describe('validatePackageName', () => {
diff --git a/packages/core/core/test/requests/TargetRequest.test.js b/packages/core/core/test/TargetRequest.test.js
similarity index 95%
rename from packages/core/core/test/requests/TargetRequest.test.js
rename to packages/core/core/test/TargetRequest.test.js
index 40b47893e..02d2fe60b 100644
--- a/packages/core/core/test/requests/TargetRequest.test.js
+++ b/packages/core/core/test/TargetRequest.test.js
@@ -4,8 +4,8 @@ import assert from 'assert';
 import path from 'path';
 import tempy from 'tempy';
 import {inputFS as fs} from '@parcel/test-utils';
-import {TargetResolver} from '../../src/requests/TargetRequest';
-import {DEFAULT_OPTIONS as _DEFAULT_OPTIONS} from '../test-utils';
+import {TargetResolver} from '../src/requests/TargetRequest';
+import {DEFAULT_OPTIONS as _DEFAULT_OPTIONS} from './test-utils';
 
 const DEFAULT_OPTIONS = {..._DEFAULT_OPTIONS, sourceMaps: true};
 
@@ -24,6 +24,11 @@ const CUSTOM_TARGETS_FIXTURE_PATH = path.join(
   'fixtures/custom-targets',
 );
 
+const CUSTOM_TARGETS_DISTDIR_FIXTURE_PATH = path.join(
+  __dirname,
+  'fixtures/custom-targets-distdir',
+);
+
 const INVALID_TARGETS_FIXTURE_PATH = path.join(
   __dirname,
   'fixtures/invalid-targets',
@@ -387,6 +392,47 @@ describe('TargetResolver', () => {
     );
   });
 
+  it('resolves explicit distDir for custom targets from package.json', async () => {
+    let targetResolver = new TargetResolver(DEFAULT_OPTIONS);
+    assert.deepEqual(
+      await targetResolver.resolve(CUSTOM_TARGETS_DISTDIR_FIXTURE_PATH),
+      {
+        files: [
+          {
+            filePath: path.join(
+              CUSTOM_TARGETS_DISTDIR_FIXTURE_PATH,
+              'package.json',
+            ),
+          },
+        ],
+        targets: [
+          {
+            name: 'app',
+            distDir: path.join(
+              __dirname,
+              'fixtures/custom-targets-distdir/www',
+            ),
+            distEntry: undefined,
+            publicUrl: 'www',
+            env: {
+              context: 'browser',
+              engines: {
+                browsers: '> 0.25%',
+              },
+              includeNodeModules: true,
+              outputFormat: 'global',
+              isLibrary: false,
+              minify: false,
+              scopeHoist: false,
+            },
+            sourceMap: {},
+            loc: undefined,
+          },
+        ],
+      },
+    );
+  });
+
   it('resolves main target with context from package.json', async () => {
     let targetResolver = new TargetResolver(DEFAULT_OPTIONS);
     assert.deepEqual(await targetResolver.resolve(CONTEXT_FIXTURE_PATH), {
diff --git a/packages/core/core/test/fixtures/custom-targets-distdir/package.json b/packages/core/core/test/fixtures/custom-targets-distdir/package.json
new file mode 100644
index 000000000..5f27e0eb9
--- /dev/null
+++ b/packages/core/core/test/fixtures/custom-targets-distdir/package.json
@@ -0,0 +1,11 @@
+{
+  "targets": {
+    "app": {
+      "engines": {
+        "browsers": "> 0.25%"
+      },
+      "distDir": "www",
+      "publicUrl": "www"
+    }
+  }
+}