Skip to content
This repository was archived by the owner on Oct 1, 2020. It is now read-only.

fix: fix file watching on compilation error #66

Merged
merged 3 commits into from
Feb 28, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .mocharc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"watch-ignore": ["./test/_test-output", "node_modules"]
}
4 changes: 3 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
{
"standard.enable": false,
"eslint.enable": true,
"eslint.validate": ["json"],
"git.ignoreLimitWarning": true
}
}
11 changes: 7 additions & 4 deletions circle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@ jobs:

steps:
- checkout

- restore_cache:
keys:
- cache-{{ arch }}-{{ .Branch }}-{{ checksum "package.json" }}
- cache-{{ arch }}-{{ .Branch }}-{{ checksum "package.json" }}
- run:
name: Yarn install
command: yarn install --frozen-lockfile
- save_cache:
key: cache-{{ arch }}-{{ .Branch }}-{{ checksum "package.json" }}
paths:
- ~/.cache
- ~/.cache
- run:
name: Lint code
command: npm run lint
Expand All @@ -33,7 +33,10 @@ jobs:
- run:
name: Run tests
command: npm test

- run: yarn add -D webpack@latest webpack-cli@latest
- run:
name: Run tests w/ webpack@latest
command: npm test
- run:
name: Semantic release
command: npm run semantic-release || true
19 changes: 14 additions & 5 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,14 +108,17 @@ const preprocessor = (options = {}) => {

const rejectWithErr = (err) => {
err.filePath = filePath
debug(`errored bundling ${outputPath}`, err)
debug(`errored bundling ${outputPath}`, err.message)

latestBundle.reject(err)
}

// this function is called when bundling is finished, once at the start
// and, if watching, each time watching triggers a re-bundle
const handle = (err, stats) => {
if (err) {
debug('handle - had error', err.message)

return rejectWithErr(err)
}

Expand All @@ -126,12 +129,14 @@ const preprocessor = (options = {}) => {

const errorsToAppend = jsonStats.errors
// remove stack trace lines since they're useless for debugging
.map((err) => err.replace(/\n\s*at.*/g, '').replace(/From previous event:\n?/g, ''))
.map(cleanseError)
// multiple errors separated by newline
.join('\n\n')

err.message += `\n${errorsToAppend}`

debug('stats had error(s)')

return rejectWithErr(err)
}

Expand All @@ -157,7 +162,7 @@ const preprocessor = (options = {}) => {
latestBundle = createDeferred()
bundles[filePath] = latestBundle.promise

bundles[filePath].tap(() => {
bundles[filePath].finally(() => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be the only functional change

debug('- compile finished for', filePath)
// when the bundling is finished, emit 'rerun' to let Cypress
// know to rerun the spec
Expand Down Expand Up @@ -187,12 +192,12 @@ const preprocessor = (options = {}) => {

// when the spec or project is closed, we need to clean up the cached
// bundle promise and stop the watcher via `bundler.close()`
file.on('close', () => {
file.on('close', (cb = function () {}) => {
debug('close', filePath)
delete bundles[filePath]

if (file.shouldWatch) {
bundler.close()
bundler.close(cb)
}
})

Expand Down Expand Up @@ -220,4 +225,8 @@ preprocessor.__reset = () => {
bundles = {}
}

function cleanseError (err) {
return err.replace(/\n\s*at.*/g, '').replace(/From previous event:\n?/g, '')
}

module.exports = preprocessor
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,14 @@
"fs-extra": "8.1.0",
"github-post-release": "1.13.1",
"license-checker": "13.0.3",
"mocha": "3.5.0",
"mocha": "^7.1.0",
"mockery": "2.1.0",
"nsp": "2.7.0",
"prettier-eslint-cli": "4.4.0",
"semantic-release": "8.2.0",
"simple-commit-message": "3.3.1",
"sinon": "3.2.1",
"sinon-chai": "2.13.0",
"sinon": "^9.0.0",
"sinon-chai": "^3.5.0",
"snap-shot-it": "7.9.2",
"webpack": "^4.18.1"
},
Expand Down
71 changes: 60 additions & 11 deletions test/e2e/compilation.spec.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
const EventEmitter = require('events').EventEmitter
const expect = require('chai').expect
const chai = require('chai')
const fs = require('fs-extra')
const path = require('path')
const snapshot = require('snap-shot-it')
const sinon = require('sinon')
const Bluebird = require('bluebird')
const sinonChai = require('sinon-chai')

chai.use(sinonChai)
const { expect } = chai

const preprocessor = require('../../index')

Expand All @@ -12,48 +18,91 @@ describe('webpack preprocessor - e2e', () => {
let file
let filePath

beforeEach(() => {
beforeEach(async () => {
const originalFilePath = path.join(__dirname, '..', 'fixtures', 'example_spec.js')

filePath = path.join(__dirname, '..', '_test-output', 'example_spec.js')

preprocessor.__reset()
fs.removeSync(path.join(__dirname, '_test-output'))
fs.outputFileSync(filePath, '')
fs.copyFileSync(originalFilePath, filePath)
await fs.remove(path.join(__dirname, '_test-output'))
await fs.outputFile(filePath, '')
await fs.copyFile(originalFilePath, filePath)

file = Object.assign(new EventEmitter(), {
filePath,
outputPath,
})
})

afterEach(async () => {
if (file.shouldWatch) {
await new Promise((resolve) => {
file.emit('close', resolve)
})
}
})

it('correctly preprocesses the file', () => {
return preprocessor()(file).then(() => {
snapshot(fs.readFileSync(outputPath).toString())
})
})

it('allows attaching catch later on syntax error without triggering unhandled rejection', (done) => {
it('allows attaching catch later on syntax error without triggering unhandled rejection', async () => {
process.on('unhandledRejection', (err) => {
// eslint-disable-next-line no-console
console.error('Unhandled Rejection:', err.stack)
done('Should not have trigger unhandled rejection')
throw new Error('Should not have trigger unhandled rejection')
})

file.shouldWatch = true

preprocessor()(file).then(() => {
fs.outputFileSync(filePath, '{')
await preprocessor()(file)
await fs.outputFile(filePath, '{')

await new Promise((resolve) => {
setTimeout(() => {
preprocessor()(file)
.catch((err) => {
expect(err.stack).to.include('Unexpected token')
file.emit('close')
done()
resolve()
})
}, 1000)
})
})

it('triggers rerun on syntax error', async () => {
const _emit = sinon.spy(file, 'emit')

file.shouldWatch = true

await preprocessor()(file)

_emit.resetHistory()

await fs.outputFile(filePath, '{')

await retry(() => expect(_emit).calledWith('rerun'))
})
})

function retry (fn, timeout = 1000) {
let timedOut = false

setTimeout(() => timedOut = true, timeout)
const tryFn = () => {
return Bluebird.try(() => {
return fn()
})

.catch((err) => {
if (timedOut) {
throw err
}

return Bluebird.delay(100).then(() => tryFn())
})
}

return tryFn()
}
Loading