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

Commit

Permalink
patch: improve lazy-loading of babel deps and add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisbreiding committed May 29, 2019
1 parent fe6f5b8 commit 49b4f48
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 45 deletions.
72 changes: 35 additions & 37 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,39 +1,35 @@
const cloneDeep = require('lodash.clonedeep')
const path = require('path')
const webpack = require('webpack')
const debug = require('debug')('cypress:webpack')

const createDeferred = require('./deferred')
const stubbableRequire = require('./stubbable-require')

const bundles = {}

// by default, we transform JavaScript supported by @babel/preset-env
const defaultBabelLoaderRules = () => {
return [
{
test: /\.js?$/,
exclude: [/node_modules/],
use: [
{
loader: require.resolve('babel-loader'),
options: {
presets: [require.resolve('@babel/preset-env')],
},
},
],
},
]
}

// we don't automatically load the rules, so that the babel dependencies are
// not required if a user passes in their own configuration
const defaultOptions = {
webpackOptions: {
const getDefaultWebpackOptions = () => {
debug('load default options')

return {
module: {
rules: [],
rules: [
{
test: /\.jsx?$/,
exclude: [/node_modules/],
use: [
{
loader: stubbableRequire.resolve('babel-loader'),
options: {
presets: [stubbableRequire.resolve('@babel/preset-env')],
},
},
],
},
],
},
},
watchOptions: {},
}
}

// export a function that returns another function, making it easy for users
Expand All @@ -57,24 +53,24 @@ const preprocessor = (options = {}) => {
// the supported file and spec file to be requested again
return (file) => {
const filePath = file.filePath

debug('get', filePath)

// since this function can get called multiple times with the same
// filePath, we return the cached bundle promise if we already have one
// since we don't want or need to re-initiate webpack for it
if (bundles[filePath]) {
debug(`already have bundle for ${filePath}`)

return bundles[filePath]
}

// user can override the default options
let webpackOptions = Object.assign({}, defaultOptions.webpackOptions, options.webpackOptions)
// here is where we load the default rules if the user has not passed
// in their own configuration
if (webpackOptions.module.rules === defaultOptions.webpackOptions) {
webpackOptions.module.rules = defaultBabelLoaderRules()
}
let watchOptions = Object.assign({}, defaultOptions.watchOptions, options.watchOptions)
let webpackOptions = options.webpackOptions || getDefaultWebpackOptions()
const watchOptions = options.watchOptions || {}

debug('webpackOptions: %o', webpackOptions)
debug('watchOptions: %o', watchOptions)

// we're provided a default output path that lives alongside Cypress's
// app data files so we don't have to worry about where to put the bundled
Expand Down Expand Up @@ -186,14 +182,16 @@ const preprocessor = (options = {}) => {
}
}

// provide a clone of the default options, making sure to lazy-load
// babel dependencies so that they aren't required unless the user
// utilizes them
// provide a clone of the default options, lazy-loading them
// so they aren't required unless the user utilizes them
Object.defineProperty(preprocessor, 'defaultOptions', {
get () {
const clonedDefaults = cloneDeep(defaultOptions)
clonedDefaults.webpackOptions.module.rules = defaultBabelLoaderRules()
return clonedDefaults
debug('get default options')

return {
webpackOptions: getDefaultWebpackOptions(),
watchOptions: {},
}
},
})

Expand Down
5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"secure": "nsp check",
"size": "t=\"$(npm pack .)\"; wc -c \"${t}\"; tar tvf \"${t}\"; rm \"${t}\";",
"test": "mocha",
"test-watch": "chokidar '*.js' 'test/*.js' -c 'npm test'",
"test-watch": "chokidar '*.js' 'test/*.js' -c 'mocha'",
"semantic-release": "semantic-release pre && npm publish --access public && semantic-release post"
},
"devDependencies": {
Expand Down Expand Up @@ -68,8 +68,7 @@
},
"dependencies": {
"bluebird": "3.5.0",
"debug": "3.1.0",
"lodash.clonedeep": "4.5.0"
"debug": "3.1.0"
},
"release": {
"verifyConditions": "condition-circle",
Expand Down
5 changes: 5 additions & 0 deletions stubbable-require.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
resolve (dependency) {
return require.resolve(dependency)
},
}
52 changes: 47 additions & 5 deletions test/index_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ mockery.enable({
mockery.registerMock('webpack', webpack)

const preprocessor = require('../index')
const stubbableRequire = require('../stubbable-require')

describe('webpack preprocessor', function () {
beforeEach(function () {
Expand Down Expand Up @@ -43,15 +44,14 @@ describe('webpack preprocessor', function () {
on: sandbox.stub(),
emit: sandbox.spy(),
}
this.options = {}
this.util = {
getOutputPath: sandbox.stub().returns(this.outputPath),
fileUpdated: sandbox.spy(),
onClose: sandbox.stub(),
}

this.run = () => {
return preprocessor(this.options)(this.file)
this.run = (options, file = this.file) => {
return preprocessor(options)(file)
}
})

Expand Down Expand Up @@ -128,8 +128,9 @@ describe('webpack preprocessor', function () {
it('includes watchOptions if provided', function () {
this.file.shouldWatch = true
this.compilerApi.watch.yields(null, this.statsApi)
this.options.watchOptions = { poll: true }
return this.run().then(() => {
const options = { watchOptions: { poll: true } }

return this.run(options).then(() => {
expect(this.compilerApi.watch.lastCall.args[0]).to.eql({
poll: true,
})
Expand Down Expand Up @@ -174,6 +175,47 @@ describe('webpack preprocessor', function () {
expect(this.watchApi.close).not.to.be.called
})
})

it('uses default webpack options when no user options', function () {
return this.run().then(() => {
expect(webpack.lastCall.args[0].module.rules[0].use).to.have.length(1)
expect(webpack.lastCall.args[0].module.rules[0].use[0].loader).to.be.a('string')
})
})

it('uses default options when no user webpack options', function () {
return this.run({}).then(() => {
expect(webpack.lastCall.args[0].module.rules[0].use).to.have.length(1)
expect(webpack.lastCall.args[0].module.rules[0].use[0].loader).to.be.a('string')
})
})

it('does not use default options when user options are non-default', function () {
const options = { webpackOptions: { module: { rules: [] } } }

return this.run(options).then(() => {
expect(webpack.lastCall.args[0].module).to.equal(options.webpackOptions.module)
})
})

it('requires babel dependencies when default options are used', function () {
sandbox.spy(stubbableRequire, 'resolve')

return this.run().then(() => {
expect(stubbableRequire.resolve).to.be.calledWith('babel-loader')
expect(stubbableRequire.resolve).to.be.calledWith('@babel/preset-env')
})
})

it('does not requires babel dependencies when user options are non-default', function () {
sandbox.spy(stubbableRequire, 'resolve')
const options = { webpackOptions: { module: { rules: [] } } }

return this.run(options).then(() => {
expect(stubbableRequire.resolve).not.to.be.calledWith('babel-loader')
expect(stubbableRequire.resolve).not.to.be.calledWith('@babel/preset-env')
})
})
})

describe('when it errors', function () {
Expand Down

0 comments on commit 49b4f48

Please sign in to comment.