Skip to content

Commit

Permalink
Merge pull request #279 from benstepp/bs-babel
Browse files Browse the repository at this point in the history
Support user provided babel configs

Conflicts:
	lib/utils/webpack.config.js
	package.json
  • Loading branch information
KyleAMathews committed May 11, 2016
2 parents 43c26d2 + f41081f commit 9b14d00
Show file tree
Hide file tree
Showing 12 changed files with 228 additions and 34 deletions.
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 @@ -365,10 +365,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 @@ -8,7 +8,6 @@ import buildProductionBundle from './build-javascript'
import postBuild from './post-build'
import globPages from './glob-pages'


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
16 changes: 3 additions & 13 deletions lib/utils/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ const debug = require('debug')('gatsby:webpack-config')
import path from 'path'
import _ from 'lodash'

import babelConfig from './babel-config'

let modifyWebpackConfig
try {
const gatsbyNodeConfig = path.resolve(process.cwd(), './gatsby-node')
Expand Down Expand Up @@ -185,9 +187,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 @@ -285,16 +285,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.1",
"json5": "^0.5.0",
"less-loader": "^2.2.0",
"loader-utils": "^0.2.14",
"lodash": "^4.12.0",
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))
})

0 comments on commit 9b14d00

Please sign in to comment.