Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support user provided babel configs #279

Merged
merged 6 commits into from
May 11, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 1 addition & 3 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
{
"presets": ['react', 'es2015', 'stage-0'],
"plugins": [
'transform-object-rest-spread'
]
"plugins": []
}
26 changes: 22 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -366,10 +366,28 @@ export.postBuild = function(pages, callback) {

### Configuring Babel

For **Webpack loaded code** you can't modify Babel's behavior as normal
by modifying the .babelrc in your site's root directory.

Instead you'll need to modify the Webpack babel loader as [described
You can modify Babel's behavior as normal by either providing a `.babelrc` in
your site's root directory or by adding a "babel" section in your site's
`package.json`. You can find out more about how to configure babel
[here](https://babeljs.io/docs/usage/babelrc/).

Gatsby by default will use your Babel configuration over the default if it can
find it. Gatsby will automatically add react-hmre to your Babel config during
development.

Note that if you want to use babel-plugin that is not provided by Gatsby, you
will have to also add it to your package.json. You can use any babel-plugin
that Gatsby packs as a dependency without having to add it to your own
package.json:

* babel-plugin-add-module-exports
* babel-plugin-transform-object-assign
* babel-preset-es2015
* babel-preset-react
* babel-preset-stage-0

If you need to change the loader to be something completely custom. You will
have to define your own webpack loader by following the steps [described
above](https://github.com/gatsbyjs/gatsby#how-to-use-your-own-webpack-loaders).

### Deploying to Github Pages (and other hosts where your site's links need prefixes)
Expand Down
122 changes: 122 additions & 0 deletions lib/utils/babel-config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import resolve from 'babel-core/lib/helpers/resolve'
import fs from 'fs'
import path from 'path'
import json5 from 'json5'
import startsWith from 'lodash/startsWith'
import invariant from 'invariant'

const DEFAULT_BABEL_CONFIG = {
presets: ['react', 'es2015', 'stage-0'],
plugins: ['add-module-exports', 'transform-object-assign'],
}

/**
* Uses babel-core helpers to resolve the plugin given it's name. It
* resolves plugins in the following order:
*
* 1. Adding babel-type prefix and checking user's local modules
* 2. Adding babel-type prefix and checking Gatsby's modules
* 3. Checking users's modules without prefix
* 4. Checking Gatsby's modules without prefix
*
*/
function resolvePlugin (pluginName, directory, type) {
const gatsbyPath = path.resolve('..', '..')
const plugin = resolve(`babel-${type}-${pluginName}`, directory) ||
resolve(`babel-${type}-${pluginName}`, gatsbyPath) ||
resolve(pluginName, directory) ||
resolve(pluginName, gatsbyPath)

const name = startsWith(pluginName, 'babel') ? pluginName : `babel-${type}-${pluginName}`
const pluginInvariantMessage = `
You are trying to use a Babel plugin which Gatsby cannot find. You
can install it using "npm install --save ${name}".

You can use any of the Gatsby provided plugins without installing them:
- babel-plugin-add-module-exports
- babel-plugin-transform-object-assign
- babel-preset-es2015
- babel-preset-react
- babel-preset-stage-0
`

invariant(plugin !== null, pluginInvariantMessage)
return plugin
}

/**
* Normalizes a Babel config object to include only absolute paths.
* This way babel-loader will correctly resolve Babel plugins
* regardless of where they are located.
*/
function normalizeConfig (config, directory) {
const normalizedConfig = {
presets: [],
plugins: [],
}

const presets = config.presets || []
presets.forEach(preset => {
normalizedConfig.presets.push(resolvePlugin(preset, directory, 'preset'))
})

const plugins = config.plugins || []
plugins.forEach(plugin => {
normalizedConfig.plugins.push(resolvePlugin(plugin, directory, 'plugin'))
})

return normalizedConfig
}

/**
* Locates a .babelrc in the Gatsby site root directory. Parses it using
* json5 (what Babel uses). It throws an error if the users's .babelrc is
* not parseable.
*/
function findBabelrc (directory) {
try {
const babelrc = fs.readFileSync(path.join(directory, '.babelrc'), 'utf-8')
return json5.parse(babelrc)
} catch (error) {
if (error.code === 'ENOENT') {
return null
} else {
throw error
}
}
}

/**
* Reads the user's package.json and returns the "babel" section. It will
* return undefined when the "babel" section does not exist.
*/
function findBabelPackage (directory) {
try {
const packageJson = require(path.join(directory, 'package.json'))
return packageJson.babel
} catch (error) {
if (error.code === 'MODULE_NOT_FOUND') {
return null
} else {
throw error
}
}
}

/**
* Returns a normalized Babel config to use with babel-loader. All of
* the paths will be absolute so that Babel behaves as expected.
*/
export default function babelConfig (program, stage) {
const { directory } = program

const babelrc = findBabelrc(directory) ||
findBabelPackage(directory) ||
DEFAULT_BABEL_CONFIG

if (stage === 'develop') {
babelrc.presets.unshift('react-hmre')
}

return normalizeConfig(babelrc, directory)
}
1 change: 0 additions & 1 deletion lib/utils/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import globPages from './glob-pages'
import toml from 'toml'
import fs from 'fs'


function customPost (program, callback) {
const directory = program.directory
let customPostBuild
Expand Down
12 changes: 0 additions & 12 deletions lib/utils/develop.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,6 @@ module.exports = (program) => {
}

const htmlCompilerConfig = webpackConfig(program, directory, 'develop', program.port)
// Remove react-transform option from Babel as redbox-react doesn't work
// on the server.
htmlCompilerConfig.removeLoader('js')
htmlCompilerConfig.loader('js', {
test: /\.jsx?$/, // Accept either .js or .jsx files.
exclude: /(node_modules|bower_components)/,
loader: 'babel',
query: {
presets: ['react', 'es2015', 'stage-1'],
plugins: ['add-module-exports'],
},
})

webpackRequire(htmlCompilerConfig.resolve(), require.resolve(HTMLPath), (error, factory) => {
if (error) {
Expand Down
15 changes: 2 additions & 13 deletions lib/utils/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import ExtractTextPlugin from 'extract-text-webpack-plugin'
import Config from 'webpack-configurator'
const debug = require('debug')('gatsby:webpack-config')
import path from 'path'
import babelConfig from './babel-config'
let modifyWebpackConfig
try {
const gatsbyNodeConfig = path.resolve(process.cwd(), './gatsby-node.js')
Expand Down Expand Up @@ -182,9 +183,7 @@ module.exports = (program, directory, stage, webpackPort = 1500, routes = []) =>
test: /\.jsx?$/, // Accept either .js or .jsx files.
exclude: /(node_modules|bower_components)/,
loader: 'babel',
query: {
plugins: ['add-module-exports'],
},
query: babelConfig(program, stage),
})
config.loader('coffee', {
test: /\.coffee$/,
Expand Down Expand Up @@ -282,16 +281,6 @@ module.exports = (program, directory, stage, webpackPort = 1500, routes = []) =>
require('postcss-reporter'),
],
})
config.removeLoader('js')
config.loader('js', {
test: /\.jsx?$/, // Accept either .js or .jsx files.
exclude: /(node_modules|bower_components)/,
loader: 'babel',
query: {
presets: ['react-hmre', 'react', 'es2015', 'stage-1'],
plugins: ['add-module-exports'],
},
})
return config

case 'build-css':
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"babel-core": "^6.8.0",
"babel-loader": "^6.2.4",
"babel-plugin-add-module-exports": "^0.2.0",
"babel-plugin-transform-object-rest-spread": "^6.8.0",
"babel-plugin-transform-object-assign": "^6.8.0",
"babel-preset-es2015": "^6.6.0",
"babel-preset-react": "^6.5.0",
"babel-preset-react-hmre": "^1.1.1",
Expand All @@ -40,6 +40,7 @@
"invariant": "^2.2.1",
"json-loader": "^0.5.2",
"less": "^2.7.0",
"json5": "^0.5.0",
"less-loader": "^2.2.0",
"loader-utils": "^0.2.14",
"lodash": "^4.11.2",
Expand Down
5 changes: 5 additions & 0 deletions test/fixtures/site-with-babelpackage/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"babel": {
"presets": ["react", "es2015", "stage-0"]
}
}
2 changes: 2 additions & 0 deletions test/fixtures/site-with-invalid-babelrc/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{
presets: ['react']
8 changes: 8 additions & 0 deletions test/fixtures/site-with-unresolvable-babelrc/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
plugins: [
"transform-decorators-legacy",
"transform-async-to-generator",
"transform-es2015-modules-commonjs",
"transform-export-extensions",
]
}
3 changes: 3 additions & 0 deletions test/fixtures/site-with-valid-babelrc/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"presets": ['react', 'es2015', 'stage-0']
}
60 changes: 60 additions & 0 deletions test/utils/babel-config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import test from 'ava'
import path from 'path'
import includes from 'lodash/includes'
import babelConfig from '../../lib/utils/babel-config'

function programStub (fixture) {
const directory = path.resolve('..', 'fixtures', fixture)
return { directory }
}

test('it returns a default babel config for babel-loader query', t => {
const program = programStub('site-without-babelrc')
const config = babelConfig(program)

t.true(typeof config === 'object')
t.truthy(config.presets.length)
t.truthy(config.plugins.length)
})

test('all plugins are absolute paths to avoid babel lookups', t => {
const program = programStub('site-without-babelrc')
const config = babelConfig(program)

config.presets.forEach(preset => t.true(path.resolve(preset) === preset))
config.plugins.forEach(plugin => t.true(path.resolve(plugin) === plugin))
})

test('fixture can resolve plugins in gatsby directory (crawling up)', t => {
const program = programStub('site-with-valid-babelrc')

const config = babelConfig(program)
t.truthy(config.presets.length)
})

test('throws error when babelrc is not parseable', t => {
const program = programStub('site-with-invalid-babelrc')

t.throws(() => babelConfig(program))
})

test('can read babel from packagejson', t => {
const program = programStub('site-with-valid-babelpackage')

const config = babelConfig(program)
t.truthy(config.presets.length)
})

test('when in development has hmre', t => {
const program = programStub('site-without-babelrc')
const config = babelConfig(program, 'develop')

// regex matches: babel followed by any amount of hyphen or word characters
const presetNames = config.presets.map(p => p.match(/babel[-|\w]+/)[0])
t.true(includes(presetNames, 'babel-preset-react-hmre'))
})

test('throws when a plugin is not available', t => {
const program = programStub('site-with-unresolvable-babelrc')
t.throws(() => babelConfig(program))
})