Skip to content

Commit

Permalink
Defer throwing asset errors until after dependencies are handled. (#2475
Browse files Browse the repository at this point in the history
)

* Defer throwing asset errors until after dependencies are handled.

This allows compiled langauges like Elm that handle their own dependency
compilation to set up watchers for dependencies so that hot-reload
continues to work due to errors in new depdenencies. Fixes #2147.

* Fix Elm error generation.

I'm not entirely sure why this fix is necessary, but without it a
compile error during a rebuild results in Parcel printing "Unknown
error" instead of anything useful. Since we are intecepting the error
though, we can also remove redundant stack information.

* Add test for tracking dependencies on error

* revert hmr-runtime changes

* Transform all errors in Pipeline.process.

This prevents error data from being lost when Pipeline is run via
`WorkerFarm`.

* Separate error-depenency test input from basic Elm tests.

* Update ElmAsset.js
  • Loading branch information
Matthew Cheely authored and wbinnssmith committed Apr 11, 2019
1 parent fb4422a commit ec51040
Show file tree
Hide file tree
Showing 8 changed files with 128 additions and 9 deletions.
52 changes: 52 additions & 0 deletions packages/core/integration-tests/test/hmr.js
Expand Up @@ -529,4 +529,56 @@ describe.skip('hmr', function() {

await buildEnd;
});

it('should watch new dependencies that cause errors', async function() {
await ncp(
path.join(__dirname, '/integration/elm-dep-error'),
path.join(__dirname, '/input')
);

b = bundler(path.join(__dirname, '/input/index.js'), {
watch: true,
hmr: true
});
await b.bundle();

ws = new WebSocket('ws://localhost:' + b.options.hmrPort);

const buildEnd = nextEvent(b, 'buildEnd');

await sleep(100);
fs.writeFile(
path.join(__dirname, '/input/src/Main.elm'),
`
module Main exposing (main)
import BrokenDep
import Html
main =
Html.text "Hello, world!"
`
);

let msg = JSON.parse(await nextEvent(ws, 'message'));
assert.equal(msg.type, 'error');

await sleep(100);
fs.writeFile(
path.join(__dirname, '/input/src/BrokenDep.elm'),
`
module BrokenDep exposing (anError)
anError : String
anError =
"fixed"
`
);

msg = JSON.parse(await nextEvent(ws, 'message'));
assert.equal(msg.type, 'error-resolved');

await buildEnd;
});
});
@@ -0,0 +1,24 @@
{
"type": "application",
"source-directories": [
"src"
],
"elm-version": "0.19.0",
"dependencies": {
"direct": {
"elm/browser": "1.0.0",
"elm/core": "1.0.0",
"elm/html": "1.0.0"
},
"indirect": {
"elm/json": "1.0.0",
"elm/time": "1.0.0",
"elm/url": "1.0.0",
"elm/virtual-dom": "1.0.0"
}
},
"test-dependencies": {
"direct": {},
"indirect": {}
}
}
@@ -0,0 +1,5 @@
var local = require('./src/Main.elm');

module.exports = function () {
return local;
};
@@ -0,0 +1,8 @@
module BrokenDep exposing (anError)

{- This module causes a compiler error -}


anError : String
anError =
2
@@ -0,0 +1,7 @@
module Main exposing (main)

import Html


main =
Html.text "Hello, world!"
7 changes: 7 additions & 0 deletions packages/core/parcel-bundler/src/Bundler.js
Expand Up @@ -594,6 +594,13 @@ class Bundler extends EventEmitter {
})
);

// If there was a processing error, re-throw now that we've set up
// depdenency watchers. This keeps reloading working if there is an
// error in a dependency not directly handled by Parcel.
if (processed.error !== null) {
throw processed.error;
}

// Store resolved assets in their original order
dependencies.forEach((dep, i) => {
asset.dependencies.set(dep.name, dep);
Expand Down
13 changes: 10 additions & 3 deletions packages/core/parcel-bundler/src/Pipeline.js
@@ -1,5 +1,6 @@
const Parser = require('./Parser');
const path = require('path');
const {errorUtils} = require('@parcel/utils');

/**
* A Pipeline composes multiple Asset types together.
Expand All @@ -17,10 +18,16 @@ class Pipeline {
}

let asset = this.parser.getAsset(path, options);
let generated = await this.processAsset(asset);
let error = null;
let generatedMap = {};
for (let rendition of generated) {
generatedMap[rendition.type] = rendition.value;
try {
let generated = await this.processAsset(asset);
for (let rendition of generated) {
generatedMap[rendition.type] = rendition.value;
}
} catch (err) {
error = errorUtils.errorToJson(err);
error.fileName = path;
}

return {
Expand Down
21 changes: 15 additions & 6 deletions packages/core/parcel-bundler/src/assets/ElmAsset.js
Expand Up @@ -44,12 +44,7 @@ class ElmAsset extends Asset {
options.optimize = true;
}

let compiled = await this.elm.compileToString(this.name, options);
this.contents = compiled.toString();
if (this.options.hmr) {
let {inject} = await localRequire('elm-hot', this.name);
this.contents = inject(this.contents);
}
this.elmOpts = options;
}

async collectDependencies() {
Expand All @@ -76,6 +71,13 @@ class ElmAsset extends Asset {
}

async generate() {
let compiled = await this.elm.compileToString(this.name, this.elmOpts);
this.contents = compiled.toString();
if (this.options.hmr) {
let {inject} = await localRequire('elm-hot', this.name);
this.contents = inject(this.contents);
}

let output = this.contents;

if (this.options.minify) {
Expand Down Expand Up @@ -129,6 +131,13 @@ class ElmAsset extends Asset {
return result.code;
}
}

generateErrorMessage(err) {
// The generated stack is not useful, but other code may
// expect it and try to print it, so make it an empty string.
err.stack = '';
return err;
}
}

module.exports = ElmAsset;

0 comments on commit ec51040

Please sign in to comment.