Showing posts with label babeljs. Show all posts
Showing posts with label babeljs. Show all posts

Saturday, June 23, 2018

Can I prevent Babel from traversing code inserted by a plugin?

Leave a Comment

I'm building a plugin that inserts enterFunction() in front of every existing function call by calling path.insertBefore. So my code is transformed from:

myFunction(); 

To:

enterFunction(); myFunction(); 

The problem is that when I insert the node Babel once again traverses the inserted node. Here's the logging output:

'CallExpression', 'myFunction'
'CallExpression', 'enterFunction'

How can I prevent Babel from entering the enterFunction call expression and its children?

This is the code I'm currently using for my Babel plugin:

function(babel) {     return {         visitor: {             CallExpression: function(path) {                 console.log("CallExpression", path.node.callee.name)                 if (path.node.ignore) {                     return;                 }                 path.node.ignore = true                  var enterCall = babel.types.callExpression(                     babel.types.identifier("enterFunction"), []                 )                 enterCall.ignore = true;                 path.insertBefore(enterCall)             }         }     } } 

1 Answers

Answers 1

The Babel Handbook mentions the following section:

If your plugin needs to not run in a certain situation, the simpliest thing to do is to write an early return.

BinaryExpression(path) {   if (path.node.operator !== '**') return; } 

If you are doing a sub-traversal in a top level path, you can use 2 provided API methods:

path.skip() skips traversing the children of the current path. path.stop() stops traversal entirely.

outerPath.traverse({   Function(innerPath) {     innerPath.skip(); // if checking the children is irrelevant   },   ReferencedIdentifier(innerPath, state) {     state.iife = true;     innerPath.stop(); // if you want to save some state and then stop traversal, or deopt   } }); 

In short, use path.skip() to skip traversing the children of the current path. One application of this method is illustrated in this snippet using Visitors, CallExpression and skip():

export default function (babel) {   const { types: t } = babel;    return {     name: "ast-transform", // not required     visitor: {       CallExpression(path) {         path.replaceWith(t.blockStatement([           t.expressionStatement(t.yieldExpression(path.node))         ]));         path.skip();       }     }   }; } 
Read More

Sunday, June 10, 2018

Can I prevent Babel from traversing code inserted by a plugin?

Leave a Comment

I'm building a plugin that inserts enterFunction() in front of every existing function call by calling path.insertBefore. So my code is transformed from:

myFunction(); 

To:

enterFunction(); myFunction(); 

The problem is that when I insert the node Babel once again traverses the inserted node. Here's the logging output:

'CallExpression', 'myFunction'
'CallExpression', 'enterFunction'

How can I prevent Babel from entering the enterFunction call expression and its children?

This is the code I'm currently using for my Babel plugin:

function(babel) {     return {         visitor: {             CallExpression: function(path) {                 console.log("CallExpression", path.node.callee.name)                 if (path.node.ignore) {                     return;                 }                 path.node.ignore = true                  var enterCall = babel.types.callExpression(                     babel.types.identifier("enterFunction"), []                 )                 enterCall.ignore = true;                 path.insertBefore(enterCall)             }         }     } } 

0 Answers

Read More

Friday, April 13, 2018

browserify and babelify very slow due to large data js files

Leave a Comment

I have a nodejs project which uses large dictionary lists (millions of entries), stored in js files, that look like this:

module.exports = ["entry1", "entry2", "entry3", "entry4", "entry5", etc.];

and then I use them from the other files like this:

var values = require('./filePath');

This works great and it works in the browser too (using browserify), however bundling takes ages - about 10 minutes.

I use the following command to create the bundle: browserify "./src/myModule.js" --standalone myModule -t [ babelify --presets [ es2015 stage-2 ] --plugins ["transform-es2015-classes", {"loose": true}]

I have tried to avoid parsing of my dictionary js files using --noparse ["path1", "path2", "path3", etc.] but it did not make any difference.

Ideally I would like to just speed up the browserify\babelify process, however if that's not possible I would be very happy to find another way (ie. avoid require) to store and use my lists, so that they don't slow the process down but that crucially work in node and in the browser too.

2 Answers

Answers 1

You can bundle the data files separately, so you'll only need to rebundle them when they change. This is possible using the --require -r and --external -x options.

To create the data bundle, do something like this:

browserify -r ./path/to/data.js -r ./path/to/other/data.js > data-bundle.js 

The resulting data-bundle.js will define the require function globally which can be used to obtain any file you listed in the command above. Just make sure you include this bundle in a script tag before your main bundle.

It would be nice to be able to --require a glob pattern, but unfortunately browserify does not support this. If you try to use the shell to expand a pattern, the -r option will only apply to the first, which sucks. You can probably write a shell script that builds a command from an ls or something, to avoid having to list all of the data files explicilty, but that's beyond the scope of the question, I think.

To create your main bundle without rebuilding the data files, simply add an option like this to your command:

-x './path/to/data/*.js' 

This tells browserify to basically ignore them and let them be pulled in through the global require function created by your other bundle. As you can see, this does support glob patterns, so it's a bit easier.

Update:

To make the two bundles into one, just put something like this at the end of a shell script that starts with the browserify command that builds your main bundle:

cat data-bundle.js main-bundle.js > bundle.js rm main-bundle.js 

Unfortunately this will always have to write a copy of data-bundle.js to disk, which may be the ultimate cause of the slowdown, as I mentioned in the comments below. Worth giving a shot, though.

If even that doesn't work, there are some other, much more hacky approaches you might take. I'll pass on going into those for now though, because I don't think they're worth it unless you absolutely must have it as one file and have no other way of doing it. :\

Answers 2

If you have files with data - just load them in separate way and don't include them into build process

  1. Format your big data files as JSON
  2. On the server use:

    let fs = require('fs'); let yourContent = JSON.parse(fs.readFileSync('path/to/file'));

  3. On client use:

    let request = require("client-request"); // do npm install client-request

    var options = { uri: "http://.com/path/to/file", json: true }

    var req = request(options, function callback(err, response, body) { console.log(response.statusCode) if (body) { let yourContent = body } })

Or use any other library which makes HTTP request which you prefer

Read More

Friday, February 2, 2018

Babel / Typescript aliases not working properly

Leave a Comment

I have a Typescript environment which i compile using Gulp, tsify, browserify and babelify. I have successfully configured aliases to navigate the project better.

I am trying to import a node module, lets say query-string, into component.ts by doing this:

import * as querystring from 'query-string';

The traceResolution option of tsconfig.json shows me this:

Module name 'query-string' was successfully resolved to '/node_modules/query-string/index.js'

But I'm still getting an error in the console saying:

Error: Cannot find module 'query-string' from '/components/example-component/'

Imagine the project structure like this:

/ |-- node_modules/ | |-- ts/ |    |-- app.ts //this is the main file, it imports component.ts | |-- components/ |    |-- example-component/ |        |-- component.html |        |-- component.ts //attempting to import a node module here | |-- gulpfile.js |-- tsconfig.json |-- .babelrc |-- package.json 

My tsconfig.json file looks like this:

{   "compilerOptions": {     "noImplicitAny": false,     "target": "es2015",     "sourceMap": true,     "baseUrl": "./ts",     "traceResolution": true,     "paths": {       "@components/*": ["../components/*"],       "*": ["../node_modules/*"]     }   } } 

My .babelrc looks like this:

{   "presets": ["es2015"],   "plugins": [     ["module-resolver", {       "cwd": "babelrc",       "root": ["./ts"],       "alias": {         "@components": "./components"       }     }]   ] } 

Here is what actually does work:

  1. importing .ts files using relative paths
  2. importing .ts files using aliases (eg. import '@component/example-component/component.ts')
  3. importing a node module into app.ts

2 Answers

Answers 1

Successfully locating a module's entry point, which is what is correctly happening based on the traceResolution message you provided, is a separate problem from understanding that module.

For TypeScript to understand the module it needs to find a typings file (.d.ts) that describes the module's types.

Looking at the query-string repo, it does not ship with a .d.ts file included, so you will need to pull it in from elsewhere.

It does, however, appear that the typings for query-string are available in the Definitely Typed repo, meaning that you should be able to run npm install --save-dev @types/query-string and the typings will be added to your project.

Answers 2

its not hard use this (I copied it from somewhere else):

Successfully locating a module's entry point, which is what is correctly happening based on the traceResolution message you provided, is a separate problem from understanding that module.

For TypeScript to understand the module it needs to find a typings file (.d.ts) that describes the module's types.

Looking at the query-string repo, it does not ship with a .d.ts file included, so you will need to pull it in from elsewhere.

It does, however, appear that the typings for query-string are available in the Definitely Typed repo, meaning that you should be able to run npm install --save-dev @types/query-string and the typings will be added to your project.

Read More

Tuesday, January 23, 2018

Change what directory Babel plugins are resolved against?

1 comment

I'm getting this error:

Unknown plugin "transform-class-properties" specified in "base" at 0, attempted to resolve relative to "/home/me/Projects/myproj/src"

The message is pretty clear, so I know why it's happening, but I want to change where Babel looks for the plugins/presets/packages.

I'm using Babel with rollup via rollup-plugin-babel.

The options I'm giving it are:

{ plugins: [ 'transform-class-properties', 'transform-object-rest-spread' ],   babelrc: false } 

However, I can't find an option to change where Babel looks for the plugins. Is there no way to do this without rewriting my plugins list to use absolute paths?


I also can't find a public API method for extracting the dependencies from .babelrc, so it's pretty hard to manually rewrite the file to use full paths. N.B. Babel configs might also be stored in package.json, and there's been some talk about adding support for .babelrc.js too -- I really don't want to maintain my own project that searches for all the different places a babel config might be hiding, parse the file(s), and scan it for all the plugins, with and without the arbitrary babel-plugin- prefixes.

1 Answers

Answers 1

You can use NODE_PATH to do the same.

$ npx babel test.js Unknown plugin "external-helpers" specified in "/Users/tarun.lalwani/Desktop/babeltest/.babelrc" at 0, attempted to resolve relative to "/Users/tarun.lalwani/Desktop/babeltest" 

After specifying the path for modules in a different location

$ NODE_PATH=/Users/tarun.lalwani/Desktop/babeltest2/node_modules npx babel test.js function test() {    this.abc = function (url) {       return console.log(url);    }; } 

NODE_PATH environment variable allows you to specify additional locations where the modules can be searched for

Read More

Monday, December 4, 2017

Module build failed: ReferenceError: [BABEL] /app/src/index.js: Unknown option: /app/node_modules/react/react.js.Children

Leave a Comment

My project fails with the error message in title on heroku, but it works locally.

This is my webpack.config.js:

module.exports = {   entry: [     './src/index.js'   ],   output: {     path: __dirname,     publicPath: '/',     filename: 'bundle.js'   },   module: {     loaders: [{       test: /\.js$/,       exclude: /node_modules/,       loader: 'babel-loader',       query: {         presets: ['react', 'es2015', 'stage-0']       }     }]   },   resolve: {     extensions: ['.jsx', '.js']   },   devServer: {     historyApiFallback: true,     contentBase: './'   } }; 

And this is package.json:

{   "main": "index.js",   "scripts": {     "start": "node ./node_modules/webpack-dev-server/bin/webpack-dev-server.js",     "test": "mocha --compilers js:babel-core/register --require ./test/test_helper.js --recursive ./test",     "test:watch": "npm run test -- --watch"   },   "author": "",   "license": "ISC",   "devDependencies": {     "babel-core": "^6.26.0",     "babel-loader": "^7.1.2",     "babel-preset-es2015": "^6.1.18",     "babel-preset-react": "^6.24.1",     "babel-preset-stage-1": "^6.24.1",     "chai": "^3.5.0",     "chai-jquery": "^2.0.0",     "jquery": "^2.2.1",     "jsdom": "^8.1.0",     "mocha": "^2.4.5",     "react-addons-test-utils": "^0.14.7"   },   "dependencies": {     "axios": "^0.17.1",     "lodash": "^3.10.1",     "react": "^0.14.3",     "react-dom": "^0.14.3",     "react-redux": "4.3.0",     "react-router": "^2.0.1",     "react-router-dom": "^4.0.0",     "redux": "^3.0.4",     "redux-form": "^6.6.3",     "redux-promise": "^0.5.3",     "validator": "^9.1.2",     "webpack": "^3.8.1",     "webpack-dev-server": "^2.9.4"   } } 

I digged around a bit, trying to find an answer, but there was no case such as working on one machine, and not on another machine.

Update 1

Here is index.js:

import React from 'react'; import ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; import { createStore, applyMiddleware } from 'redux'; import { BrowserRouter, Route, Switch } from 'react-router-dom'; import promise from 'redux-promise';  import reducers from './reducers';  import LoginForm from './components/login_form';  const createStoreWithMiddleware = applyMiddleware(promise)(createStore);  ReactDOM.render(   <Provider store={createStoreWithMiddleware(reducers)}>       <BrowserRouter>         <div>           <Route path="/" component={LoginForm} />         </div>       </BrowserRouter>   </Provider>   , document.querySelector('.container')); 

** Update 2 ** I tried changing the jsx to js for the test property in webpack loader config object, it didn't help. I removed stage-1 and it's still failing.

1 Answers

Answers 1

This problem got fixed:

  1. Deleted module.query and resolve from the webpack config file.
  2. Moved dependencies from devDependencies to dependencies (heroku is a production environment, and therefore it doesn't download devDependencies, which doesn't make sense to me !)
Read More

Thursday, October 19, 2017

Package Control: Install Package missing in Sublime Text 3

Leave a Comment

I'm trying to install the babel plugin for Sublime Text 3. I followed the instruction here: https://packagecontrol.io/installation

I restarted Sublime Text and when I hit ctrl + shift + p and type 'package' I should see 'Package Control: Install Package'. But this is not appearing in the list.

enter image description here

What can I do? I have already removed 'package_control' from the 'ignored_packages' setting...

I also tried downloading the zip file from https://github.com/babel/babel-sublime and placing in sublime-text-3/Installed Packages. Still no joy

3 Answers

Answers 1

This is a common enough bug in sublime. Have you seen this issue in github?:

Package Control not showing in sublime 3

Commonly, removing package control from the ignored list would suffice, but from your case, it seems that package control is not installed properly. In that case, try to reinstall the package control and see the console for any error.

Answers 2

Try follow these steps:

  1. Go to the configuration folder by opening preferences menu and Browse Package.
  2. Rename whole configuration "Sublime Text 3" folder to "Sublime Text 3 - Backup".
  3. Uninstall Sublime text 3
  4. Install Sublime text 3 with this guide and run it without registering.. You may find Package Control now..
  5. Restore your old configuration on Sublime Text 3 - Backup if needed

Answers 3

Please follow the below process. Open Sublime and go to View->Show console

Then past the code in text box and enter.

import urllib.request,os,hashlib; h = '6f4c264a24d933ce70df5dedcf1dcaee' + 'ebe013ee18cced0ef93d5f746d80ef60'; pf = 'Package Control.sublime-package'; ipp = sublime.installed_packages_path(); urllib.request.install_opener( urllib.request.build_opener( urllib.request.ProxyHandler()) ); by = urllib.request.urlopen( 'http://packagecontrol.io/' + pf.replace(' ', '%20')).read(); dh = hashlib.sha256(by).hexdigest(); print('Error validating download (got %s instead of %s), please try manual install' % (dh, h)) if dh != h else open(os.path.join( ipp, pf), 'wb' ).write(by)

Reference Link: https://packagecontrol.io/installation

Read More

Monday, August 7, 2017

Use Babel Generated AMD Modules

Leave a Comment

I have an existing app that is all AMD modules based and use require.js on bundling up things. I need to bring a bunch of es2015 react code into this app as AMD modules and use Babel to turn them into AMD modules. Here is my babel config

 babel:   options:     sourceMap: false     plugins: [ 'transform-class-properties',                 'transform-object-rest-spread',                 'transform-es2015-modules-amd']     presets: [ 'react','es2015']   dist:      files: [{       expand: true       cwd: "src/foo/jsx"       src: "**/*.js"       dest: "build/foo/js/"       ext: ".js"       }]  

The result works and generates the AMD modules as follows for instance for myreact.js module:

define(["exports", "react", "prop-types"], function (exports, _react, _propTypes) {   "use strict";   Object.defineProperty(exports, "__esModule", {     value: true   });   var _react2 = _interopRequireDefault(_react);   var _propTypes2 = _interopRequireDefault(_propTypes);   function _interopRequireDefault(obj) {     return obj && obj.__esModule ? obj : {       default: obj     };   }   var REQUIRED_FIELD_SYMBOL = "*";   function Blah(props) {     ...   }   exports.default = Blah; }); 

Now, when I use this AMD module in my other AMD modules with define, it is undefined

define([..., 'foo/js/myreact'], function(..., RM) {  RM.Blah <---- this is undefied RM.default <---- still undefied 

The generated code does not return the exports.

Also, I noticed in my require.js generated bundle.js, the react model shows up as follows:

define('foo/js/myreact',["exports", "react", "prop-types"],     function (exports, _react, _propTypes){ .. } 

I am not really defining exports anywhere in my require.js config file so how I am supposed to setup exports?

Basically, how should I use my react AMD modules within other AMD modules?

0 Answers

Read More

Monday, July 24, 2017

Babel JS babel-preset-php error

Leave a Comment

I was just trying the new Babel's babel-preset-php (https://gitlab.com/kornelski/babel-preset-php#php7-to-es7-syntax-translator). I did everything in the README file, I installed the preset with npm i -S babel-preset-php. Then I created a .babelrc file with the following contents;

{     "presets": ["php"] } 

Installed the cli with npm i -g babel-cli. Then I created a simple PHP file that only contains a simple function:

<?php  function addCalculator($x, $y) {     return $x + $y; } 

And tried to run the transpiler with babel number.php -o file.js. But I get an error in the execution of the script:

/home/claudio/Documents/Development/babel/node_modules/babel-preset-php/lib/plugins.js:6         Identifier(p) {                   ^ SyntaxError: Unexpected token ( (While processing preset: "/home/claudio/Documents/Development/babel/node_modules/babel-preset-php/index.js") at Module._compile (module.js:439:25) at Object.Module._extensions..js (module.js:474:10) at Module.load (module.js:356:32) at Function.Module._load (module.js:312:12) at Module.require (module.js:364:17) at require (module.js:380:17) at Object.<anonymous> (/home/claudio/Documents/Development/babel/node_modules/babel-preset-php/index.js:1:79) at Module._compile (module.js:456:26) at Object.Module._extensions..js (module.js:474:10) at Module.load (module.js:356:32) at Function.Module._load (module.js:312:12) 

I'm not that experient with nodejs and npm, so any idea on what might be happening?

1 Answers

Answers 1

Edit: Ok, I just realized that you are calling a public function outside of a class. That's not correct PHP. You can't define a function as public outside of a class. Your PHP code is just wrong.

Read More

Tuesday, June 6, 2017

babel preset-env with electron & react

Leave a Comment

To create an electron & react application I was using the react and node7 presents as follows (which works)

  "babel": {     "sourceMaps": "inline",     "presets": [       "react",       "node7"     ]   }, 

But I wanted to go with something more future proof so I tried out the preset-env

  "babel": {     "sourceMaps": "inline",     "presets": [       [         "env",         {           "targets": {             "electron": "1.6.7"           },           "debug": true,           "useBuiltIns": true         }       ]     ]   }, 

However this is not working for me. It seems that es7 object spread is not being found, and there are issues with react JSX. What simple thing did I miss?

babel-preset-env: `DEBUG` option  Using targets: {   "electron": "1.6.7" }  Modules transform: commonjs  Using plugins:   syntax-trailing-function-commas {"electron":"1.6.7"}  Using polyfills:   es7.string.pad-start {"electron":"1.6.7"}   es7.string.pad-end {"electron":"1.6.7"}   web.timers {"electron":"1.6.7"}   web.immediate {"electron":"1.6.7"}   web.dom.iterable {"electron":"1.6.7"} src\dash\actions.js -> dist\dash\actions.js src\dash\actionTypes.js -> dist\dash\actionTypes.js SyntaxError: src/dash/dash.js: Unexpected token (29:18)   27 |     let stats = remote.getGlobal('DASH').stats   28 |     //stats.jobStats = (stats.jobStats == "") ? "0" : stats.jobStats  -- we only needed this if the folder was m issing > 29 |     let state = { ...STORE.getState().ServerStats }      |                   ^   30 |     let original = { ...state }   31 |     state.WatsonInstalled = (state.WatsonInstalled != stats.watsonInstalled) ? stats.watsonInstalled : state.Wat sonInstalled   32 |     state.WatsonRunning = (state.WatsonRunning != stats.watsonRunning) ? stats.watsonRunning : state.WatsonRunni ng SyntaxError: src/dash/reducers.js: Unexpected token (18:25)   16 |     switch (action.type) {   17 |         case A.SetUpdating: > 18 |             newState = { ...state, isUpdating: action.payload }      |                          ^   19 |             return newState   20 |         case A.SetChanger:   21 |             newState = { ...state, fieldName: action.payload } SyntaxError: src/jsx/Configuration.jsx: Unexpected token (19:4)   17 |     resetForm,   18 |     isUpdating, > 19 |     ...props }) => {      |     ^   20 |     COMPONENTS["Configuration"] = props // Register this component in the Dash Components list (so we can get th e change() function)   21 |     return (   22 |         <Form horizontal onSubmit={(e) => { alert('foobar'); handleSubmit(doSubmit) }}> SyntaxError: src/jsx/Controls.jsx: Unexpected token (7:8)    5 | const Control = ({ WatsonInstalled, WatsonRunning, FMERunning, buttonAction }) => {    6 |     return ( >  7 |         <Grid>      |         ^    8 |             <Row className="propRow">    9 |                 <Col sm={3}>   10 |                     <OverlayTrigger placement="bottom" SyntaxError: src/jsx/Dashboard.jsx: Unexpected token (12:4)   10 | const App = ({ GLVersion }) => {   11 |   return ( > 12 |     <Grid> |         ^    8 |             <Row className="propRow">    9 |                 <Col sm={3}>   10 |                     <OverlayTrigger placement="bottom" SyntaxError: src/jsx/Dashboard.jsx: Unexpected token (12:4)   10 | const App = ({ GLVersion }) => {   11 |   return ( > 12 |     <Grid>      |     ^   13 |       <Row className="versionRow">   14 |         <Col smOffset={10} sm={1} className="fieldLabel">Version:</Col>   15 |         <Col sm={1} className="fieldLabel">{GLVersion}</Col> SyntaxError: src/jsx/FieldInputs.jsx: Unexpected token (9:8)    7 | const FieldControl = ({ input, meta, type, min, max, tip, cid }) => {    8 |     return ( >  9 |         <OverlayTrigger placement="left" overlay={(<Tooltip id={cid + "-tip"}>{tip}</Tooltip>)}>      |         ^   10 |             <FormControl   11 |                 type={type}   12 |                 min={min} SyntaxError: src/jsx/Utilities.jsx: Unexpected token (9:8)    7 | const Util = ({ JobsDirSize, killAction }) => {    8 |     return ( >  9 |         <Grid>      |         ^   10 |             <Row className="propRow">   11 |                 <Col sm={3} className="fieldLabel">Job Folder Size:</Col>   12 |                 <Col sm={9} className="fieldLabel">{JobsDirSize} MB</Col> src\main\main.js -> dist\main\main.js src\main\monitor.js -> dist\main\monitor.js src\main\utils.js -> dist\main\utils.js src\main\watson.js -> dist\main\watson.js 

1 Answers

Answers 1

It turns out this is an open issue with this preset: https://github.com/babel/babel-preset-env/issues/326

Read More

Monday, May 15, 2017

getting babel to watch two folders

Leave a Comment

Goal: I have two sub-folders (jsx for jsx files) and (dash7 for ECMA-2017 scripts) I don't want to combine these folders. I want to setup a VSCODE tasks that can watch both folders.

Issue: Right now, I have two separate tasks. One "jsx watch" the other "es7 watch). I can only run one at a time with VS Code.

Question: is there a way to write a task that does both, or is there a way to get babel to watch two separate folders. Or another solution?

{     "version": "0.1.0",     "command": "${workspaceRoot}/node_modules/.bin/babel.cmd",     // "isShellCommand": true,     "tasks": [         {             "args": ["jsx", "--out-dir", "jsxo", "-w", "--source-maps inline"],             "taskName": "jsx watch",             "suppressTaskName": true,             "isBuildCommand": true, // make this the F1 > Task: Run Build Task gesture             "isBackground": true // tell VS Code not wait for this task to finish         },         {             "args": ["dash7", "--out-dir", "dash", "-w", "--source-maps inline"],             "taskName": "es7 watch",             "suppressTaskName": true,             "isBuildCommand": true, // make this the F1 > Task: Run Build Task gesture             "isBackground": true // tell VS Code not wait for this task to finish         }     ] } 

babel presets

  "babel": {     "sourceMaps": "inline",     "presets": [       "react",       "node7"     ] 

1 Answers

Answers 1

The VSCODE folks just updated Visual Code with a new terminal runner:

https://github.com/Microsoft/vscode/issues/981

If you change the version to 2.0.0 then you can run multiple tasks at once!

{     "version": "2.0.0",     "command": "${workspaceRoot}/node_modules/.bin/babel.cmd",     // "isShellCommand": true,     "tasks": [         {             "args": ["jsx", "--out-dir", "jsxo", "-w", "--source-maps inline"],             "taskName": "jsx watch",             "suppressTaskName": true,             "isBuildCommand": true, // make this the F1 > Task: Run Build Task gesture             "isBackground": true // tell VS Code not wait for this task to finish         },         {             "args": ["dash7", "--out-dir", "dash", "-w", "--source-maps inline"],             "taskName": "es7 watch",             "suppressTaskName": true,             "isBuildCommand": true, // make this the F1 > Task: Run Build Task gesture             "isBackground": true // tell VS Code not wait for this task to finish         }     ] } 
Read More

Monday, April 10, 2017

How do I properly mock third party libraries (like jQuery and Semantic UI) using Jest?

Leave a Comment

I have been learning React, Babel, Semantic UI, and Jest over the last couple of weeks. I haven't really run into too many issues with my components not rendering in the browser, but I have run into issues with rendering when writing unit tests with Jest.

The SUT is as follows:

EditUser.jsx

var React = require('react'); var { browserHistory, Link } = require('react-router'); var $ = require('jquery');  import Navigation from '../Common/Navigation';  const apiUrl = process.env.API_URL; const phoneRegex = /^[(]{0,1}[0-9]{3}[)]{0,1}[-\s\.]{0,1}[0-9]{3}[-\s\.]{0,1}[0-9]{4}$/;  var EditUser = React.createClass({   getInitialState: function() {     return {       email: '',       firstName: '',       lastName: '',       phone: '',       role: ''     };   },   handleSubmit: function(e) {     e.preventDefault();      var data = {       "email": this.state.email,       "firstName": this.state.firstName,       "lastName": this.state.lastName,       "phone": this.state.phone,       "role": this.state.role     };      if($('.ui.form').form('is valid')) {       $.ajax({         url: apiUrl + '/api/users/' + this.props.params.userId,         dataType: 'json',         contentType: 'application/json',         type: 'PUT',         data: JSON.stringify(data),         success: function(data) {           this.setState({data: data});           browserHistory.push('/Users');           $('.toast').addClass('happy');           $('.toast').html(data["firstName"] + ' ' + data["lastName"] + ' was updated successfully.');           $('.toast').transition('fade up', '500ms');           setTimeout(function(){               $('.toast').transition('fade up', '500ms').onComplete(function() {                   $('.toast').removeClass('happy');               });           }, 3000);         }.bind(this),         error: function(xhr, status, err) {           console.error(this.props.url, status, err.toString());           $('.toast').addClass('sad');           $('.toast').html("Something bad happened: " + err.toString());           $('.toast').transition('fade up', '500ms');           setTimeout(function(){               $('.toast').transition('fade up', '500ms').onComplete(function() {                   $('.toast').removeClass('sad');               });           }, 3000);         }.bind(this)       });     }   },   handleChange: function(e) {     var nextState = {};     nextState[e.target.name] = e.target.value;     this.setState(nextState);   },   componentDidMount: function() {     $('.dropdown').dropdown();      $('.ui.form').form({       fields: {             firstName: {               identifier: 'firstName',               rules: [                     {                       type: 'empty',                       prompt: 'Please enter a first name.'                     },                     {                       type: 'doesntContain[<script>]',                       prompt: 'Please enter a valid first name.'                     }                 ]             },             lastName: {               identifier: 'lastName',               rules: [                     {                       type: 'empty',                       prompt: 'Please enter a last name.'                     },                     {                       type: 'doesntContain[<script>]',                       prompt: 'Please enter a valid last name.'                     }                 ]             },             email: {               identifier: 'email',               rules: [                     {                       type: 'email',                       prompt: 'Please enter a valid email address.'                     },                     {                       type: 'empty',                       prompt: 'Please enter an email address.'                     },                     {                       type: 'doesntContain[<script>]',                       prompt: 'Please enter a valid email address.'                     }                 ]             },             role: {               identifier: 'role',               rules: [                     {                       type: 'empty',                       prompt: 'Please select a role.'                     }                 ]             },             phone: {               identifier: 'phone',               optional: true,               rules: [                     {                       type: 'minLength[10]',                       prompt: 'Please enter a valid phone number of at least {ruleValue} digits.'                     },                     {                       type: 'regExp',                       value: phoneRegex,                       prompt: 'Please enter a valid phone number.'                     }                 ]             }         }     });      $.ajax({       url: apiUrl + '/api/users/' + this.props.params.userId,       dataType:'json',       cache: false,       success: function(data) {         this.setState({data: data});         this.setState({email: data.email});         this.setState({firstName: data.firstName});         this.setState({lastName: data.lastName});         this.setState({phone: data.phone});         this.setState({role: data.role});       }.bind(this),       error: function(xhr, status, err) {         console.error(this.props.url, status, err.toString());       }.bind(this)     });    },   render: function () {     return (       <div className="container">         <Navigation active="Users"/>         <div className="ui segment">             <h2>Edit User</h2>             <div className="required warning">                 <span className="red text">*</span><span> Required</span>             </div>             <form className="ui form" onSubmit={this.handleSubmit} data={this.state}>                 <h4 className="ui dividing header">User Information</h4>                 <div className="ui three column grid field">                     <div className="row fields">                         <div className="column field required">                             <label>First Name</label>                             <input type="text" name="firstName" value={this.state.firstName}                                 onChange={this.handleChange}/>                         </div>                         <div className="column field required">                             <label>Last Name</label>                             <input type="text" name="lastName" value={this.state.lastName}                                 onChange={this.handleChange}/>                         </div>                         <div className="column field required">                             <label>Email</label>                             <input type="text" name="email" value={this.state.email}                                 onChange={this.handleChange}/>                         </div>                     </div>                 </div>                 <div className="ui three column grid field">                     <div className="row fields">                         <div className="column field required">                             <label>User Role</label>                             <select className="ui dropdown" name="role"                                 onChange={this.handleChange} value={this.state.role}>                                 <option value="SuperAdmin">Super Admin</option>                             </select>                         </div>                         <div className="column field">                             <label>Phone</label>                             <input name="phone" value={this.state.phone}                                 onChange={this.handleChange}/>                         </div>                     </div>                 </div>                 <div className="ui three column grid">                     <div className="row">                         <div className="right floated column">                             <div className="right floated large ui buttons">                                 <Link to="/Users" className="ui button">Cancel</Link>                                 <button className="ui button primary" type="submit">Save</button>                             </div>                         </div>                     </div>                 </div>                 <div className="ui error message"></div>             </form>         </div>       </div>     );   } });  module.exports = EditUser; 

The associated test file is as follows:

EditUser.test.js

var React = require('react'); var Renderer = require('react-test-renderer'); var jQuery = require('jquery'); require('../../../semantic/dist/components/dropdown');  import EditUser from '../../../app/components/Users/EditUser';  it('renders correctly', () => {     const component = Renderer.create(         <EditUser />     ).toJSON();     expect(component).toMatchSnapshot(); }); 

The issue that I am seeing when I run jest:

 FAIL  test/components/Users/EditUser.test.js   ● Test suite failed to run      ReferenceError: jQuery is not defined        at Object.<anonymous> (semantic/dist/components/dropdown.min.js:11:21523)       at Object.<anonymous> (test/components/Users/EditUser.test.js:6:370)       at process._tickCallback (node.js:369:9) 

1 Answers

Answers 1

You are doing it in right way but one simple mistake.

You have to tell jest not to mock jquery

To be clear,

from https://www.phpied.com/jest-jquery-testing-vanilla-app/ under 4th subtitle Testing Vanilla

[It talks about testing a Vanilla app, but it perfectly describe about Jest]

The thing about Jest is that it mocks everything. Which is priceless for unit testing. But it also means you need to declare when you don't want something mocked.

That is

jest.unmock(moduleName) 

From Facebook's documentation
unmock Indicates that the module system should never return a mocked version of the specified module from require() (e.g. that it should always return the real module).

The most common use of this API is for specifying the module a given test intends to be testing (and thus doesn't want automatically mocked).

It returns the jest object for chaining.

Note : Previously it was dontMock.

When using babel-jest, calls to unmock will automatically be hoisted to the top of the code block. Use dontMock if you want to explicitly avoid this behavior.
You can see the full documentation here Facebook's Documentation Page in Github .

Also use const instead of var in require. That is

const $ = require('jquery'); 

So the code looks like

jest.unmock('jquery'); // unmock it. In previous versions, use dontMock instead var React = require('react'); var { browserHistory, Link } = require('react-router'); const $ = require('jquery');  import Navigation from '../Common/Navigation';  const apiUrl = process.env.API_URL; const phoneRegex = /^[(]{0,1}[0-9]{3}[)]{0,1}[-\s\.]{0,1}[0-9]{3}[-\s\.]{0,1}[0-9]{4}$/;  var EditUser = React.createClass({   getInitialState: function() {     return {       email: '',       firstName: '',       lastName: '',       phone: '',       role: ''     };   },   handleSubmit: function(e) {     e.preventDefault();      var data = {       "email": this.state.email,       "firstName": this.state.firstName,       "lastName": this.state.lastName,       "phone": this.state.phone,       "role": this.state.role     };      if($('.ui.form').form('is valid')) {       $.ajax({         url: apiUrl + '/api/users/' + this.props.params.userId,         dataType: 'json',         contentType: 'application/json',         type: 'PUT',         data: JSON.stringify(data),         success: function(data) {           this.setState({data: data});           browserHistory.push('/Users');           $('.toast').addClass('happy');           $('.toast').html(data["firstName"] + ' ' + data["lastName"] + ' was updated successfully.');           $('.toast').transition('fade up', '500ms');           setTimeout(function(){               $('.toast').transition('fade up', '500ms').onComplete(function() {                   $('.toast').removeClass('happy');               });           }, 3000);         }.bind(this),         error: function(xhr, status, err) {           console.error(this.props.url, status, err.toString());           $('.toast').addClass('sad');           $('.toast').html("Something bad happened: " + err.toString());           $('.toast').transition('fade up', '500ms');           setTimeout(function(){               $('.toast').transition('fade up', '500ms').onComplete(function() {                   $('.toast').removeClass('sad');               });           }, 3000);         }.bind(this)       });     }   },   handleChange: function(e) {     var nextState = {};     nextState[e.target.name] = e.target.value;     this.setState(nextState);   },   componentDidMount: function() {     $('.dropdown').dropdown();      $('.ui.form').form({       fields: {             firstName: {               identifier: 'firstName',               rules: [                     {                       type: 'empty',                       prompt: 'Please enter a first name.'                     },                     {                       type: 'doesntContain[<script>]',                       prompt: 'Please enter a valid first name.'                     }                 ]             },             lastName: {               identifier: 'lastName',               rules: [                     {                       type: 'empty',                       prompt: 'Please enter a last name.'                     },                     {                       type: 'doesntContain[<script>]',                       prompt: 'Please enter a valid last name.'                     }                 ]             },             email: {               identifier: 'email',               rules: [                     {                       type: 'email',                       prompt: 'Please enter a valid email address.'                     },                     {                       type: 'empty',                       prompt: 'Please enter an email address.'                     },                     {                       type: 'doesntContain[<script>]',                       prompt: 'Please enter a valid email address.'                     }                 ]             },             role: {               identifier: 'role',               rules: [                     {                       type: 'empty',                       prompt: 'Please select a role.'                     }                 ]             },             phone: {               identifier: 'phone',               optional: true,               rules: [                     {                       type: 'minLength[10]',                       prompt: 'Please enter a valid phone number of at least {ruleValue} digits.'                     },                     {                       type: 'regExp',                       value: phoneRegex,                       prompt: 'Please enter a valid phone number.'                     }                 ]             }         }     });      $.ajax({       url: apiUrl + '/api/users/' + this.props.params.userId,       dataType:'json',       cache: false,       success: function(data) {         this.setState({data: data});         this.setState({email: data.email});         this.setState({firstName: data.firstName});         this.setState({lastName: data.lastName});         this.setState({phone: data.phone});         this.setState({role: data.role});       }.bind(this),       error: function(xhr, status, err) {         console.error(this.props.url, status, err.toString());       }.bind(this)     });    },   render: function () {     return (       <div className="container">         <Navigation active="Users"/>         <div className="ui segment">             <h2>Edit User</h2>             <div className="required warning">                 <span className="red text">*</span><span> Required</span>             </div>             <form className="ui form" onSubmit={this.handleSubmit} data={this.state}>                 <h4 className="ui dividing header">User Information</h4>                 <div className="ui three column grid field">                     <div className="row fields">                         <div className="column field required">                             <label>First Name</label>                             <input type="text" name="firstName" value={this.state.firstName}                                 onChange={this.handleChange}/>                         </div>                         <div className="column field required">                             <label>Last Name</label>                             <input type="text" name="lastName" value={this.state.lastName}                                 onChange={this.handleChange}/>                         </div>                         <div className="column field required">                             <label>Email</label>                             <input type="text" name="email" value={this.state.email}                                 onChange={this.handleChange}/>                         </div>                     </div>                 </div>                 <div className="ui three column grid field">                     <div className="row fields">                         <div className="column field required">                             <label>User Role</label>                             <select className="ui dropdown" name="role"                                 onChange={this.handleChange} value={this.state.role}>                                 <option value="SuperAdmin">Super Admin</option>                             </select>                         </div>                         <div className="column field">                             <label>Phone</label>                             <input name="phone" value={this.state.phone}                                 onChange={this.handleChange}/>                         </div>                     </div>                 </div>                 <div className="ui three column grid">                     <div className="row">                         <div className="right floated column">                             <div className="right floated large ui buttons">                                 <Link to="/Users" className="ui button">Cancel</Link>                                 <button className="ui button primary" type="submit">Save</button>                             </div>                         </div>                     </div>                 </div>                 <div className="ui error message"></div>             </form>         </div>       </div>     );   } });  module.exports = EditUser; 
Read More

Monday, March 6, 2017

How do I properly mock third party libraries (like jQuery and Semantic UI) using Jest?

Leave a Comment

I have been learning React, Babel, Semantic UI, and Jest over the last couple of weeks. I haven't really run into too many issues with my components not rendering in the browser, but I have run into issues with rendering when writing unit tests with Jest.

The SUT is as follows:

EditUser.jsx

var React = require('react'); var { browserHistory, Link } = require('react-router'); var $ = require('jquery');  import Navigation from '../Common/Navigation';  const apiUrl = process.env.API_URL; const phoneRegex = /^[(]{0,1}[0-9]{3}[)]{0,1}[-\s\.]{0,1}[0-9]{3}[-\s\.]{0,1}[0-9]{4}$/;  var EditUser = React.createClass({   getInitialState: function() {     return {       email: '',       firstName: '',       lastName: '',       phone: '',       role: ''     };   },   handleSubmit: function(e) {     e.preventDefault();      var data = {       "email": this.state.email,       "firstName": this.state.firstName,       "lastName": this.state.lastName,       "phone": this.state.phone,       "role": this.state.role     };      if($('.ui.form').form('is valid')) {       $.ajax({         url: apiUrl + '/api/users/' + this.props.params.userId,         dataType: 'json',         contentType: 'application/json',         type: 'PUT',         data: JSON.stringify(data),         success: function(data) {           this.setState({data: data});           browserHistory.push('/Users');           $('.toast').addClass('happy');           $('.toast').html(data["firstName"] + ' ' + data["lastName"] + ' was updated successfully.');           $('.toast').transition('fade up', '500ms');           setTimeout(function(){               $('.toast').transition('fade up', '500ms').onComplete(function() {                   $('.toast').removeClass('happy');               });           }, 3000);         }.bind(this),         error: function(xhr, status, err) {           console.error(this.props.url, status, err.toString());           $('.toast').addClass('sad');           $('.toast').html("Something bad happened: " + err.toString());           $('.toast').transition('fade up', '500ms');           setTimeout(function(){               $('.toast').transition('fade up', '500ms').onComplete(function() {                   $('.toast').removeClass('sad');               });           }, 3000);         }.bind(this)       });     }   },   handleChange: function(e) {     var nextState = {};     nextState[e.target.name] = e.target.value;     this.setState(nextState);   },   componentDidMount: function() {     $('.dropdown').dropdown();      $('.ui.form').form({       fields: {             firstName: {               identifier: 'firstName',               rules: [                     {                       type: 'empty',                       prompt: 'Please enter a first name.'                     },                     {                       type: 'doesntContain[<script>]',                       prompt: 'Please enter a valid first name.'                     }                 ]             },             lastName: {               identifier: 'lastName',               rules: [                     {                       type: 'empty',                       prompt: 'Please enter a last name.'                     },                     {                       type: 'doesntContain[<script>]',                       prompt: 'Please enter a valid last name.'                     }                 ]             },             email: {               identifier: 'email',               rules: [                     {                       type: 'email',                       prompt: 'Please enter a valid email address.'                     },                     {                       type: 'empty',                       prompt: 'Please enter an email address.'                     },                     {                       type: 'doesntContain[<script>]',                       prompt: 'Please enter a valid email address.'                     }                 ]             },             role: {               identifier: 'role',               rules: [                     {                       type: 'empty',                       prompt: 'Please select a role.'                     }                 ]             },             phone: {               identifier: 'phone',               optional: true,               rules: [                     {                       type: 'minLength[10]',                       prompt: 'Please enter a valid phone number of at least {ruleValue} digits.'                     },                     {                       type: 'regExp',                       value: phoneRegex,                       prompt: 'Please enter a valid phone number.'                     }                 ]             }         }     });      $.ajax({       url: apiUrl + '/api/users/' + this.props.params.userId,       dataType:'json',       cache: false,       success: function(data) {         this.setState({data: data});         this.setState({email: data.email});         this.setState({firstName: data.firstName});         this.setState({lastName: data.lastName});         this.setState({phone: data.phone});         this.setState({role: data.role});       }.bind(this),       error: function(xhr, status, err) {         console.error(this.props.url, status, err.toString());       }.bind(this)     });    },   render: function () {     return (       <div className="container">         <Navigation active="Users"/>         <div className="ui segment">             <h2>Edit User</h2>             <div className="required warning">                 <span className="red text">*</span><span> Required</span>             </div>             <form className="ui form" onSubmit={this.handleSubmit} data={this.state}>                 <h4 className="ui dividing header">User Information</h4>                 <div className="ui three column grid field">                     <div className="row fields">                         <div className="column field required">                             <label>First Name</label>                             <input type="text" name="firstName" value={this.state.firstName}                                 onChange={this.handleChange}/>                         </div>                         <div className="column field required">                             <label>Last Name</label>                             <input type="text" name="lastName" value={this.state.lastName}                                 onChange={this.handleChange}/>                         </div>                         <div className="column field required">                             <label>Email</label>                             <input type="text" name="email" value={this.state.email}                                 onChange={this.handleChange}/>                         </div>                     </div>                 </div>                 <div className="ui three column grid field">                     <div className="row fields">                         <div className="column field required">                             <label>User Role</label>                             <select className="ui dropdown" name="role"                                 onChange={this.handleChange} value={this.state.role}>                                 <option value="SuperAdmin">Super Admin</option>                             </select>                         </div>                         <div className="column field">                             <label>Phone</label>                             <input name="phone" value={this.state.phone}                                 onChange={this.handleChange}/>                         </div>                     </div>                 </div>                 <div className="ui three column grid">                     <div className="row">                         <div className="right floated column">                             <div className="right floated large ui buttons">                                 <Link to="/Users" className="ui button">Cancel</Link>                                 <button className="ui button primary" type="submit">Save</button>                             </div>                         </div>                     </div>                 </div>                 <div className="ui error message"></div>             </form>         </div>       </div>     );   } });  module.exports = EditUser; 

The associated test file is as follows:

EditUser.test.js

var React = require('react'); var Renderer = require('react-test-renderer'); var jQuery = require('jquery'); require('../../../semantic/dist/components/dropdown');  import EditUser from '../../../app/components/Users/EditUser';  it('renders correctly', () => {     const component = Renderer.create(         <EditUser />     ).toJSON();     expect(component).toMatchSnapshot(); }); 

The issue that I am seeing when I run jest:

 FAIL  test/components/Users/EditUser.test.js   ● Test suite failed to run      ReferenceError: jQuery is not defined        at Object.<anonymous> (semantic/dist/components/dropdown.min.js:11:21523)       at Object.<anonymous> (test/components/Users/EditUser.test.js:6:370)       at process._tickCallback (node.js:369:9) 

3 Answers

Answers 1

You are doing it in right way but one simple mistake.

You have to tell jest not to mock jquery

To be clear,

from https://www.phpied.com/jest-jquery-testing-vanilla-app/ under 4th subtitle Testing Vanilla

[It talks about testing a Vanilla app, but it perfectly describe about Jest]

The thing about Jest is that it mocks everything. Which is priceless for unit testing. But it also means you need to declare when you don't want something mocked.

That is

jest.unmock(moduleName) 

From Facebook's documentation
unmock Indicates that the module system should never return a mocked version of the specified module from require() (e.g. that it should always return the real module).

The most common use of this API is for specifying the module a given test intends to be testing (and thus doesn't want automatically mocked).

It returns the jest object for chaining.

Note : Previously it was dontMock.

When using babel-jest, calls to unmock will automatically be hoisted to the top of the code block. Use dontMock if you want to explicitly avoid this behavior.
You can see the full documentation here Facebook's Documentation Page in Github .

Also use const instead of var in require. That is

const $ = require('jquery'); 

So the code looks like

jest.unmock('jquery'); // unmock it. In previous versions, use dontMock instead var React = require('react'); var { browserHistory, Link } = require('react-router'); const $ = require('jquery');  import Navigation from '../Common/Navigation';  const apiUrl = process.env.API_URL; const phoneRegex = /^[(]{0,1}[0-9]{3}[)]{0,1}[-\s\.]{0,1}[0-9]{3}[-\s\.]{0,1}[0-9]{4}$/;  var EditUser = React.createClass({   getInitialState: function() {     return {       email: '',       firstName: '',       lastName: '',       phone: '',       role: ''     };   },   handleSubmit: function(e) {     e.preventDefault();      var data = {       "email": this.state.email,       "firstName": this.state.firstName,       "lastName": this.state.lastName,       "phone": this.state.phone,       "role": this.state.role     };      if($('.ui.form').form('is valid')) {       $.ajax({         url: apiUrl + '/api/users/' + this.props.params.userId,         dataType: 'json',         contentType: 'application/json',         type: 'PUT',         data: JSON.stringify(data),         success: function(data) {           this.setState({data: data});           browserHistory.push('/Users');           $('.toast').addClass('happy');           $('.toast').html(data["firstName"] + ' ' + data["lastName"] + ' was updated successfully.');           $('.toast').transition('fade up', '500ms');           setTimeout(function(){               $('.toast').transition('fade up', '500ms').onComplete(function() {                   $('.toast').removeClass('happy');               });           }, 3000);         }.bind(this),         error: function(xhr, status, err) {           console.error(this.props.url, status, err.toString());           $('.toast').addClass('sad');           $('.toast').html("Something bad happened: " + err.toString());           $('.toast').transition('fade up', '500ms');           setTimeout(function(){               $('.toast').transition('fade up', '500ms').onComplete(function() {                   $('.toast').removeClass('sad');               });           }, 3000);         }.bind(this)       });     }   },   handleChange: function(e) {     var nextState = {};     nextState[e.target.name] = e.target.value;     this.setState(nextState);   },   componentDidMount: function() {     $('.dropdown').dropdown();      $('.ui.form').form({       fields: {             firstName: {               identifier: 'firstName',               rules: [                     {                       type: 'empty',                       prompt: 'Please enter a first name.'                     },                     {                       type: 'doesntContain[<script>]',                       prompt: 'Please enter a valid first name.'                     }                 ]             },             lastName: {               identifier: 'lastName',               rules: [                     {                       type: 'empty',                       prompt: 'Please enter a last name.'                     },                     {                       type: 'doesntContain[<script>]',                       prompt: 'Please enter a valid last name.'                     }                 ]             },             email: {               identifier: 'email',               rules: [                     {                       type: 'email',                       prompt: 'Please enter a valid email address.'                     },                     {                       type: 'empty',                       prompt: 'Please enter an email address.'                     },                     {                       type: 'doesntContain[<script>]',                       prompt: 'Please enter a valid email address.'                     }                 ]             },             role: {               identifier: 'role',               rules: [                     {                       type: 'empty',                       prompt: 'Please select a role.'                     }                 ]             },             phone: {               identifier: 'phone',               optional: true,               rules: [                     {                       type: 'minLength[10]',                       prompt: 'Please enter a valid phone number of at least {ruleValue} digits.'                     },                     {                       type: 'regExp',                       value: phoneRegex,                       prompt: 'Please enter a valid phone number.'                     }                 ]             }         }     });      $.ajax({       url: apiUrl + '/api/users/' + this.props.params.userId,       dataType:'json',       cache: false,       success: function(data) {         this.setState({data: data});         this.setState({email: data.email});         this.setState({firstName: data.firstName});         this.setState({lastName: data.lastName});         this.setState({phone: data.phone});         this.setState({role: data.role});       }.bind(this),       error: function(xhr, status, err) {         console.error(this.props.url, status, err.toString());       }.bind(this)     });    },   render: function () {     return (       <div className="container">         <Navigation active="Users"/>         <div className="ui segment">             <h2>Edit User</h2>             <div className="required warning">                 <span className="red text">*</span><span> Required</span>             </div>             <form className="ui form" onSubmit={this.handleSubmit} data={this.state}>                 <h4 className="ui dividing header">User Information</h4>                 <div className="ui three column grid field">                     <div className="row fields">                         <div className="column field required">                             <label>First Name</label>                             <input type="text" name="firstName" value={this.state.firstName}                                 onChange={this.handleChange}/>                         </div>                         <div className="column field required">                             <label>Last Name</label>                             <input type="text" name="lastName" value={this.state.lastName}                                 onChange={this.handleChange}/>                         </div>                         <div className="column field required">                             <label>Email</label>                             <input type="text" name="email" value={this.state.email}                                 onChange={this.handleChange}/>                         </div>                     </div>                 </div>                 <div className="ui three column grid field">                     <div className="row fields">                         <div className="column field required">                             <label>User Role</label>                             <select className="ui dropdown" name="role"                                 onChange={this.handleChange} value={this.state.role}>                                 <option value="SuperAdmin">Super Admin</option>                             </select>                         </div>                         <div className="column field">                             <label>Phone</label>                             <input name="phone" value={this.state.phone}                                 onChange={this.handleChange}/>                         </div>                     </div>                 </div>                 <div className="ui three column grid">                     <div className="row">                         <div className="right floated column">                             <div className="right floated large ui buttons">                                 <Link to="/Users" className="ui button">Cancel</Link>                                 <button className="ui button primary" type="submit">Save</button>                             </div>                         </div>                     </div>                 </div>                 <div className="ui error message"></div>             </form>         </div>       </div>     );   } });  module.exports = EditUser; 

Answers 2

From your error stack, it seems like the semantic dropdown is looking for jQuery, which has not been previously loaded. I think that if you change:

var jQuery = require('jquery'); 

To

require('jquery'); 

That would load it for the tests and not place it in a variable, making it available for the semantic dropdown as well.

Answers 3

You probably don't need to import jquery in your test file, since you are already importing in the EditUser component.

You should also have a look at enzyme. You can do shallow rendering, or full DOM rendering. More info here. Whatever frameworks you are using within your React components, you can easily test your components output with enzyme.

Some simple examples are below:

import React from 'react'; import { mount, shallow, render } from 'enzyme'; import EditUser from './EditUser';  describe('EditUser', function() {    // Ensure component mounts OK   it('EditUser should mount', function() {     const user = shallow(       <EditUser />     );     expect(user).not.toBe.undefined;   });    // Ensure error is thrown when invalid data is passed in to a prop   it('Prop with invalid data throw error', function() {     expect(() => {       shallow(<EditUser prop="invalid data" />);     }).toThrow();   });    // Ensure that a specific prop is of type function   it('Should ensure that some prop is a function', () => {     const propClick = function() {console.log('click')};     const item = mount(       <EditUser someProp={propClick} />     );     expect(typeof(item.props().someProp) === "function");   }); }); 

I use enzyme(with Jest) in all my React projects, and it seems to be the easiest to work with, and supports modern test runners.

Read More

Thursday, March 2, 2017

Tell react-native packager to watch a non-javascript file

Leave a Comment

I'm using a babel plugin to load environment variables from a .env file into a React Native project, but changes to the .env file are not loaded until the javascript file importing them changes. I'd like a way to tell the react-native packager to recompile in the event that this file changes. I would accept an answer that:

  1. Simply re-transpiles the entire project when a specific file (.env) changes.
  2. Re-transpiles only those files containing a specific string, say foo

Is there a simple way to do this by writing a plugin/middleware? Maybe a separate background script that fires events to watchman that the react-native packager is listening for?

[EDIT in reply to a comment]

My current .babelrc is the following, where babel-plugin-react-native-config is a plugin I wrote to do hot variable swapping in conjunction with the react-native-config package.

{   "presets": [     "react-native"   ],   "plugins": [      ["babel-plugin-espower", {        "sourceRoot": "./App"      }],      "transform-flow-strip-types"   ],   "env": {     "production": {       "plugins": [         "babel-plugin-unassert",       ]     },     "development": {       "plugins": [         ["babel-plugin-react-native-config", { envfile: ".env" }]       ]     }   }   } 

The problem is that the react-native packager only watches javascript files. I don't think changing my babel configuration will help, unless babel can somehow speak upwards to react-native or watchman to inform it that some file needs recompiling...

[EDIT 2]

I determined that the react-native packager uses watchman to watch files. E.g., when I do watchman watch-list after starting the packager (and after doing a watchman watch-del-all), I get

{     "version": "4.6.0",     "roots": [         "/path/to/my/project"     ] } 

Moreover, when I delete this watch while the packager is running, nothing happens (from its perspective, the js isn't changing because it doesn't receive any updates), but then when I restart the packager it recreates this watch and transpiles everything.

So it seems that, unless there's a better way, I have to create a watchman trigger to both (1) kill the react-packager (2) kill the watch on my app directory (3) restart the node packager. This seems slow and hacky, but I would like to see if it can even work.

I haven't quite gotten this to work in a generic way, but I'm experimenting with various things.

1 Answers

Answers 1

Since it's been two weeks since I've asked this question, I'm going to post the (kind of terrible) workaround I was able to cobble together. I will leave this question unanswered, and accept any new answer that is better (less hacky) than this one.

The react native packager uses watchman to watch for filesystem changes, and upon getting an event that some JS file has changed, it looks to see if the file has actually changed, and then retranspiles if so. This prevents me from doing something simple like a watchman trigger that touches the relevant JS file, because the react packager thinks it's so smart that it can ignore updates with no diff. Whatever.

So my solution is to create a watchman trigger on .env changing which calls make clear_env_cache, where clear_env_cache is the following (phony) target in a Makefile.

# get the PID of the react packager pid := $(shell lsof -i:8081 | grep node | awk '{print $$2;}' | head -n 1)  # Kill files that the packager uses to determine whether it needs to  # re-transpile a js file, then restart the packager clear_env_cache:     find ${TMPDIR}/react-native-packager-cache-* -name "my_pattern" | xargs rm     kill -9 $(pid) || echo "no packager running"     nohup node node_modules/react-native/local-cli/cli.js start > /dev/null 2>&1 & 

Note that my_pattern will change depending on your project layout. For me there's one file importing all the envvars called Settings.js, so the pattern is "*Settings*". Note this target also basically kills and reboots the packager every time the file changes, and it nohups the node packager so you won't be able to see the process anymore. Not a big deal unless you need to view the output of the packager.

The watchman-cli command to trigger this is watchman-make --root . -p .env -t clear_env_cache and for convenience I set up a make target that nohups this command:

# Run `make hotswap_env` to allow envvar changes to show up in the react-native packager. hotswap_env:     nohup watchman-make --root . -p .env -t clear_env_cache > /dev/null 2>&1 & 

Now I can (once per system boot) run make hotswap_env and it will trigger whenever .env changes and ensure the packager server is continuously running.

Disclaimer: This script is probably not portable, and is definitely brittle. Caveat emptor and YMMV and IANAL and all that. Suggested improvements for portability are welcome.

Read More

Wednesday, January 25, 2017

How to warn when you forget to `await` an async function in Javascript?

Leave a Comment

I'm using Babel and Webpack. If I forget to await an async function, it can often go unnoticed. Once in a while, if I forgot the await, an error occurs in the async function and I get an Unhandled promise rejection. Then, I realize that I forgot the await.

Is there a way to get a warning when I forget to add an await?

1 Answers

Answers 1

Setup better eslint integration with webpack, repo, and code editor.
Here is the applicable rule, require await.

Consider integrating the following:

Read More

Thursday, April 14, 2016

ng-forward bootstrap function and/or babel transpilation not working on stock android browser

Leave a Comment

When I view my app on my stock Android browser all I see is a white screen. I've seen a few issues about the "white screen of death" in Ionic apps, but none of that seems to apply here. It's difficult to debug, since I don't know of any way to see JS errors on a mobile browser (other than Chrome and its remote debugging feature). I guess my hope is that there's something obviously wrong with my bootstrap function, my main application class or my router. I'm using Angular 1.5.0-rc.2 and ng-forward. Here's the relevant part of my index.html:

<!-- no Angular components are loaded in here on stock Android browser --> <myapp>     loading . . . </myapp>  <script src="/path/to/angular.js"></script> <script src="/path/to/angular-ui-router.js"></script> 

bootstrap.js

import 'reflect-metadata'; import { bootstrap } from 'ng-forward'; import { Application } from '../components/app/Application'; import config from './config/app.config';  bootstrap(Application, [     'ui.router',     config.name ]); 

app.config.js

export default angular.module('app.config', []) .config(['$locationProvider', '$urlRouterProvider', '$httpProvider', Config]);  function Config($locationProvider, $urlRouterProvider, $animateProvider, $httpProvider) {     $locationProvider.html5Mode({ enabled: true, requireBase: false });     $urlRouterProvider.otherwise('/'); } 

Routes.js

import { Home } from '../../components/home/Home';  class Routes {     static Config()     {         return [             {                 url: '/',                 name: 'home',                 component: Home,                 template: '<home></home>'             }         ]     } } export default Routes.Config() 

Application.js

import { Component, StateConfig, Inject } from 'ng-forward'; import Routes from '../../app/config/Routes';  @Component({     selector: 'myapp',     template: `         <h1>My App</h1>         <ng-outlet></ng-outlet>     ` }) @StateConfig(Routes) export class Application {     constructor()     {         console.log('Application component instantiated');     } } 

This works on every desktop browser I've tried, and also on mobile using Chrome, Safari and Firefox. On my stock Android browser, the <myapp></myapp> and <home></home> components are never populated.

update

It occurs to me that this might instead/also be a Babel transpilation issue. I've got 4 Angular apps, 1 works on stock android browser, 3 don't. The only unique difference between the one that works and the three that don't is those three are es6. So I thought I'd include my Babel setup here, too, in case that helps with diagnosis:

package.json

"dependencies": {     "babel-core": "^6.4.0",     "babel-polyfill": "^6.3.14",     "babel-runtime": "^6.3.19" }, "devDependencies": {     "babel-plugin-syntax-async-functions": "^6.3.13",     "babel-plugin-transform-async-to-generator": "^6.4.0",     "babel-plugin-transform-decorators-legacy": "^1.3.4",     "babel-plugin-transform-regenerator": "^6.3.26",     "babel-plugin-transform-runtime": "^6.4.0",     "babel-preset-es2015": "^6.3.13",     "babel-preset-stage-0": "^6.3.13",     "babel-preset-stage-3": "^6.3.13",     "babelify": "^7.2.0" }, "babel": {     "presets": [         "es2015",         "stage-0",         "stage-3"     ],     "plugins": [         "transform-runtime",         "transform-regenerator",         "syntax-async-functions",         "transform-async-to-generator",         "transform-decorators-legacy"     ] } 

babel.js (grunt)

module.exports = function(grunt) {     grunt.config.set('babel', {         options: {             sourceMap: true,             presets: ['es2015', 'stage-0', 'stage-3'],             plugins: ['transform-decorators-legacy']         },         client: {             files: [{                 expand: true,                 cwd: '<%= grunt.path.client %>',                 src: ['{app,components,services}/**/*.js'],                 dest: '<%= grunt.path.tmp %>'             }]         }     }); }; 

Update

I found about about Android's about:debug setting for the stock browser. You type that into the address bar and hit enter, and then you suddenly have a Debug setting in the browser settings, among which is a checkbox for "Show JavaScript Console". However, toggling that produces no effect whatsoever (even when I know there are JS errors). HTC was no help there. Still flummoxed.

1 Answers

Answers 1

I wrapped the transpiled code in a try/catch and got the error, ReferenceError: Set is not defined, which led me to the reflect-metatdata function, which was trying to create a polyfill. The solution appears to be simply importing babel-polyfill in my bootstrap file:

import 'reflect-metadata';  // Missing this appears to be the cause of the problem import 'babel-polyfill';  import { bootstrap } from 'ng-forward'; import { Application } from '../components/app/Application'; import config from './config/app.config';  bootstrap(Application, [     'ui.router',     config.name ]); 
Read More

Tuesday, March 15, 2016

babel watch support for renaming or moving files

Leave a Comment

I'm using the babel command with the --watch flag to transpile my code. However, when I move or rename a file, the old version of the file remains in the output directory. Is there any way to tell babel to do a clean when something like this happens or should I just switch to chokidar and do it myself?

1 Answers

Answers 1

Take a look at gulp-babel-wrap package

Seems like first option from Available tasks: section makes what you need - clean - wipes the destination directory (default 'dist'

Read More