Skip to content

Commit

Permalink
Add no-config support for jsx (#751)
Browse files Browse the repository at this point in the history
* support no-config jsx

* Automatically enable JSX in normal .js files if react or preact are dependencies
  • Loading branch information
sheeldotme authored and devongovett committed Feb 10, 2018
1 parent 665e6b1 commit 5e224bd
Show file tree
Hide file tree
Showing 13 changed files with 148 additions and 10 deletions.
5 changes: 4 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
{
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 8
"ecmaVersion": 8,
"ecmaFeatures": {
"jsx": true
}
},
"env": {
"node": true,
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@
"dependencies": {
"babel-core": "^6.25.0",
"babel-generator": "^6.25.0",
"babel-template": "^6.25.0",
"babel-types": "^6.25.0",
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",
"babel-plugin-transform-react-jsx": "^6.24.1",
"babel-preset-env": "^1.6.1",
"babel-template": "^6.25.0",
"babel-types": "^6.25.0",
"babylon": "^6.17.4",
"babylon-walk": "^1.0.2",
"browser-resolve": "^1.11.2",
Expand Down
1 change: 0 additions & 1 deletion src/assets/JSAsset.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ class JSAsset extends Asset {

async parse(code) {
const options = await this.getParserOptions();

return babylon.parse(code, options);
}

Expand Down
85 changes: 79 additions & 6 deletions src/transforms/babel.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@ const ENV_PRESETS = {
env: true
};

const JSX_EXTENSIONS = {
'.jsx': true,
'.tsx': true
};

const JSX_PRAGMA = {
react: 'React.createElement',
preact: 'h'
};

async function babelTransform(asset) {
let config = await getConfig(asset);
if (!config) {
Expand Down Expand Up @@ -77,42 +87,73 @@ async function getBabelConfig(asset) {

let babelrc = await getBabelRc(asset);
let envConfig = await getEnvConfig(asset, !!babelrc);
let jsxConfig = getJSXConfig(asset, !!babelrc);

// Merge the babel-preset-env config and the babelrc if needed
if (babelrc) {
if (envConfig) {
// Filter out presets that are already applied by babel-preset-env
if (Array.isArray(babelrc.presets)) {
babelrc.presets = babelrc.presets.filter(preset => {
preset = Array.isArray(preset) ? preset[0] : preset;
return !ENV_PRESETS[preset];
return !ENV_PRESETS[getPluginName(preset)];
});
}

// Filter out plugins that are already applied by babel-preset-env
if (Array.isArray(babelrc.plugins)) {
babelrc.plugins = babelrc.plugins.filter(plugin => {
plugin = Array.isArray(plugin) ? plugin[0] : plugin;
return !ENV_PLUGINS[plugin];
return !ENV_PLUGINS[getPluginName(plugin)];
});
}

// Add plugins generated by babel-preset-env to get to the app's target engines.
babelrc.plugins = (babelrc.plugins || []).concat(envConfig.plugins);
mergeConfigs(babelrc, envConfig);
}

// Add JSX config if it isn't already specified in the babelrc
let hasReact =
hasPlugin(babelrc.presets, 'react') ||
hasPlugin(babelrc.plugins, 'transform-react-jsx');

if (!hasReact) {
mergeConfigs(babelrc, jsxConfig);
}

return babelrc;
}

// If there is a babel-preset-env config, and it isn't empty use that
if (envConfig && envConfig.plugins.length > 0) {
if (envConfig && (envConfig.plugins.length > 0 || jsxConfig)) {
mergeConfigs(envConfig, jsxConfig);
return envConfig;
}

// If there is a JSX config, return that
if (jsxConfig) {
return jsxConfig;
}

// Otherwise, don't run babel at all
return null;
}

function mergeConfigs(a, b) {
if (b) {
a.presets = (a.presets || []).concat(b.presets || []);
a.plugins = (a.plugins || []).concat(b.plugins || []);
}

return a;
}

function hasPlugin(arr, plugin) {
return Array.isArray(arr) && arr.some(p => getPluginName(p) === plugin);
}

function getPluginName(p) {
return Array.isArray(p) ? p[0] : p;
}

/**
* Finds a .babelrc for an asset. By default, .babelrc files inside node_modules are not used.
* However, there are some exceptions:
Expand Down Expand Up @@ -199,3 +240,35 @@ async function getEnvPlugins(targets) {
envCache.set(key, plugins);
return plugins;
}

/**
* Generates a babel config for JSX. Attempts to detect react or react-like libraries
* and changes the pragma accordingly.
*/
function getJSXConfig(asset, isSourceModule) {
// Don't enable JSX in node_modules
if (asset.name.includes(NODE_MODULES) && !isSourceModule) {
return null;
}

// Find a dependency that we can map to a JSX pragma
let pragma = null;
for (let dep in JSX_PRAGMA) {
let pkg = asset.package;
if (
pkg &&
((pkg.dependencies && pkg.dependencies[dep]) ||
(pkg.devDependencies && pkg.devDependencies[dep]))
) {
pragma = JSX_PRAGMA[dep];
break;
}
}

if (pragma || JSX_EXTENSIONS[path.extname(asset.name)]) {
return {
plugins: [[require('babel-plugin-transform-react-jsx'), {pragma}]],
internal: true
};
}
}
1 change: 1 addition & 0 deletions test/integration/jsx-preact/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = <div />;
5 changes: 5 additions & 0 deletions test/integration/jsx-preact/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"dependencies": {
"preact": "*"
}
}
1 change: 1 addition & 0 deletions test/integration/jsx-react/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = <div />;
5 changes: 5 additions & 0 deletions test/integration/jsx-react/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"dependencies": {
"react": "*"
}
}
1 change: 1 addition & 0 deletions test/integration/jsx/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = <div />;
1 change: 1 addition & 0 deletions test/integration/typescript-jsx/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = <div />;
21 changes: 21 additions & 0 deletions test/javascript.js
Original file line number Diff line number Diff line change
Expand Up @@ -655,4 +655,25 @@ describe('javascript', function() {
assert(!file.includes('class Foo {}'));
assert(!file.includes('class Bar {}'));
});

it('should support compiling JSX', async function() {
await bundle(__dirname + '/integration/jsx/index.jsx');

let file = fs.readFileSync(__dirname + '/dist/index.js', 'utf8');
assert(file.includes('React.createElement("div"'));
});

it('should support compiling JSX in JS files with React dependency', async function() {
await bundle(__dirname + '/integration/jsx-react/index.js');

let file = fs.readFileSync(__dirname + '/dist/index.js', 'utf8');
assert(file.includes('React.createElement("div"'));
});

it('should support compiling JSX in JS files with Preact dependency', async function() {
await bundle(__dirname + '/integration/jsx-preact/index.js');

let file = fs.readFileSync(__dirname + '/dist/index.js', 'utf8');
assert(file.includes('h("div"'));
});
});
7 changes: 7 additions & 0 deletions test/typescript.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,11 @@ describe('typescript', function() {
let js = fs.readFileSync(__dirname + '/dist/index.js', 'utf8');
assert(!js.includes('/* test comment */'));
});

it('should support compiling JSX', async function() {
await bundle(__dirname + '/integration/typescript-jsx/index.tsx');

let file = fs.readFileSync(__dirname + '/dist/index.js', 'utf8');
assert(file.includes('React.createElement("div"'));
});
});
20 changes: 20 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,14 @@ babel-helper-builder-binary-assignment-operator-visitor@^6.24.1:
babel-runtime "^6.22.0"
babel-types "^6.24.1"

babel-helper-builder-react-jsx@^6.24.1:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz#39ff8313b75c8b65dceff1f31d383e0ff2a408a0"
dependencies:
babel-runtime "^6.26.0"
babel-types "^6.26.0"
esutils "^2.0.2"

babel-helper-call-delegate@^6.24.1:
version "6.24.1"
resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz#ece6aacddc76e41c3461f88bfc575bd0daa2df8d"
Expand Down Expand Up @@ -440,6 +448,10 @@ babel-plugin-syntax-exponentiation-operator@^6.8.0:
version "6.13.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de"

babel-plugin-syntax-jsx@^6.8.0:
version "6.18.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946"

babel-plugin-syntax-trailing-function-commas@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3"
Expand Down Expand Up @@ -628,6 +640,14 @@ babel-plugin-transform-exponentiation-operator@^6.22.0:
babel-plugin-syntax-exponentiation-operator "^6.8.0"
babel-runtime "^6.22.0"

babel-plugin-transform-react-jsx@^6.24.1:
version "6.24.1"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.24.1.tgz#840a028e7df460dfc3a2d29f0c0d91f6376e66a3"
dependencies:
babel-helper-builder-react-jsx "^6.24.1"
babel-plugin-syntax-jsx "^6.8.0"
babel-runtime "^6.22.0"

babel-plugin-transform-regenerator@^6.22.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz#e0703696fbde27f0a3efcacf8b4dca2f7b3a8f2f"
Expand Down

0 comments on commit 5e224bd

Please sign in to comment.