From 5e224bd7f03d71e84512400329542424acf136b5 Mon Sep 17 00:00:00 2001 From: Sheel Patel Date: Fri, 9 Feb 2018 17:40:10 -0700 Subject: [PATCH] Add no-config support for jsx (#751) * support no-config jsx * Automatically enable JSX in normal .js files if react or preact are dependencies --- .eslintrc.json | 5 +- package.json | 5 +- src/assets/JSAsset.js | 1 - src/transforms/babel.js | 85 +++++++++++++++++++++-- test/integration/jsx-preact/index.js | 1 + test/integration/jsx-preact/package.json | 5 ++ test/integration/jsx-react/index.js | 1 + test/integration/jsx-react/package.json | 5 ++ test/integration/jsx/index.jsx | 1 + test/integration/typescript-jsx/index.tsx | 1 + test/javascript.js | 21 ++++++ test/typescript.js | 7 ++ yarn.lock | 20 ++++++ 13 files changed, 148 insertions(+), 10 deletions(-) create mode 100644 test/integration/jsx-preact/index.js create mode 100644 test/integration/jsx-preact/package.json create mode 100644 test/integration/jsx-react/index.js create mode 100644 test/integration/jsx-react/package.json create mode 100644 test/integration/jsx/index.jsx create mode 100644 test/integration/typescript-jsx/index.tsx diff --git a/.eslintrc.json b/.eslintrc.json index af19e168425..7087a0b984e 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,7 +1,10 @@ { "extends": "eslint:recommended", "parserOptions": { - "ecmaVersion": 8 + "ecmaVersion": 8, + "ecmaFeatures": { + "jsx": true + } }, "env": { "node": true, diff --git a/package.json b/package.json index ccfabb018d7..c9bbc36dad5 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/assets/JSAsset.js b/src/assets/JSAsset.js index c21a055b5e4..abcec0e1261 100644 --- a/src/assets/JSAsset.js +++ b/src/assets/JSAsset.js @@ -75,7 +75,6 @@ class JSAsset extends Asset { async parse(code) { const options = await this.getParserOptions(); - return babylon.parse(code, options); } diff --git a/src/transforms/babel.js b/src/transforms/babel.js index 7623459be39..6490866e342 100644 --- a/src/transforms/babel.js +++ b/src/transforms/babel.js @@ -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) { @@ -77,6 +87,7 @@ 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) { @@ -84,35 +95,65 @@ async function getBabelConfig(asset) { // 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: @@ -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 + }; + } +} diff --git a/test/integration/jsx-preact/index.js b/test/integration/jsx-preact/index.js new file mode 100644 index 00000000000..8d097d55d7f --- /dev/null +++ b/test/integration/jsx-preact/index.js @@ -0,0 +1 @@ +module.exports =
; diff --git a/test/integration/jsx-preact/package.json b/test/integration/jsx-preact/package.json new file mode 100644 index 00000000000..7ca2d696c01 --- /dev/null +++ b/test/integration/jsx-preact/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "preact": "*" + } +} diff --git a/test/integration/jsx-react/index.js b/test/integration/jsx-react/index.js new file mode 100644 index 00000000000..8d097d55d7f --- /dev/null +++ b/test/integration/jsx-react/index.js @@ -0,0 +1 @@ +module.exports =
; diff --git a/test/integration/jsx-react/package.json b/test/integration/jsx-react/package.json new file mode 100644 index 00000000000..e04e63e83ad --- /dev/null +++ b/test/integration/jsx-react/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "react": "*" + } +} diff --git a/test/integration/jsx/index.jsx b/test/integration/jsx/index.jsx new file mode 100644 index 00000000000..8d097d55d7f --- /dev/null +++ b/test/integration/jsx/index.jsx @@ -0,0 +1 @@ +module.exports =
; diff --git a/test/integration/typescript-jsx/index.tsx b/test/integration/typescript-jsx/index.tsx new file mode 100644 index 00000000000..8d097d55d7f --- /dev/null +++ b/test/integration/typescript-jsx/index.tsx @@ -0,0 +1 @@ +module.exports =
; diff --git a/test/javascript.js b/test/javascript.js index 359c5c184a4..41409e08430 100644 --- a/test/javascript.js +++ b/test/javascript.js @@ -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"')); + }); }); diff --git a/test/typescript.js b/test/typescript.js index 5f04556fb7b..6f12634772b 100644 --- a/test/typescript.js +++ b/test/typescript.js @@ -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"')); + }); }); diff --git a/yarn.lock b/yarn.lock index ecc0296e3e2..9fd873ffe0d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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" @@ -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" @@ -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"