Sunday, November 24, 2019

Banana in a box for Angular 1.x code monkeys :)

Two-way databinding, used to be this simple in AngularJS 1.x:

<input ng-model="todo.completed" type="checkbox" />


Angular 2 made the two-way databinding explicit, which can be accomplished by either of the following:

<input [checked]="todo.completed" (input)="todo.completed = $event.target.checked" type="checkbox" />

<input [ngModel]="todo.completed" (ngModelChange)="todo.completed = $event" type="checkbox"/>


Still pining for the old days? You can do Angular 1's terser approach in Angular 2:
<input [(ngModel)]="todo.completed" type="checkbox" />

That's called banana in a box. Just put parenthesis (a.k.a. banana) around ngModel, otherwise it won't work.


It's true, we are code monkeys :)


Using the ngModel approach produces an error if FormsModule is not imported:

Can't bind to 'ngModel' since it isn't a known property of 'input'


To solve that, import FormsModule from "@angular/forms", a good place to import it is from app.module.ts, on entry point of the app.

Monday, October 14, 2019

Absolute import with Next.js and TypeScript

Make Next.js project TypeScript-capable by following this:

https://nextjs.org/blog/next-9#built-in-zero-config-typescript-support


Create shared directory in project’s root directory

Under shared directory, create randomizer.ts:

export function randomizer() {
    return 4;
}


Create deeply/nested directories under components directory

Under components/deeply/nested directory, create SampleComponent.tsx:

import React from "react";

// import { randomizer } from "../../../shared/randomizer";

import { randomizer } from "love/randomizer";

export function SampleComponent() {
    const a = randomizer();

    return (
        <div>
            <strong>I am strong {a}</strong>
        </div>
    );
}

Edit pages/index.tsx, then add SampleComponent, e.g.,

<p className="description">
    To get started, edit <code>pages/index.js</code> and save to
    reload.
</p>

<SampleComponent />


Add this import statement to pages/index.tsx:

import { SampleComponent } from "../components/deeply/nested/SampleComponent";

Edit tsconfig.json and add the following under compilerOptions:

"baseUrl": ".",
"paths": {
    "love/*": ["shared/*"]
}

Webpack must be informed of TypeScript’s paths, this can be done by modifying webpack’s resolve.alias object. To modify webpack’s configuration, create next.config.js in project’s root directory, then put the following:

const path = require("path");

module.exports = {
    webpack(config, { dev }) {
        config.resolve.alias = {
            ...config.resolve.alias,
            love: path.resolve(__dirname, "shared")
        };

        return config;
    }
};

Run yarn dev, everything shall work.


To avoid manual synchronization of tsconfig’s paths and webpack’s resolve.alias, use the following helper: (sourced from: https://gist.github.com/nerdyman/2f97b24ab826623bff9202750013f99e)

Create resolve-tsconfig-path-to-webpack-alias.js in project's root directory:

const { resolve } = require('path');

/**
 * Resolve tsconfig.json paths to Webpack aliases
 * @param  {string} tsconfigPath           - Path to tsconfig
 * @param  {string} webpackConfigBasePath  - Path from tsconfig to Webpack config to create absolute aliases
 * @return {object}                        - Webpack alias config
 */
function resolveTsconfigPathsToAlias({
    tsconfigPath = './tsconfig.json',
    webpackConfigBasePath = __dirname,
} = {}) {
    const { paths } = require(tsconfigPath).compilerOptions;

    const aliases = {};

    Object.keys(paths).forEach((item) => {
        const key = item.replace('/*', '');
        const value = resolve(webpackConfigBasePath, paths[item][0].replace('/*', '').replace('*', ''));

        aliases[key] = value;
    });

    return aliases;
}

module.exports = resolveTsconfigPathsToAlias;

Change next.config.js to following:

const path = require("path");

const resolveTsconfigPathsToAlias = require("./resolve-tsconfig-path-to-webpack-alias");

module.exports = {
    webpack(config, { dev }) {
        config.resolve.alias = {
            ...config.resolve.alias,
            ...resolveTsconfigPathsToAlias()
        };

        return config;
    }
};

Run yarn dev, everything shall work.


Sample working code: https://github.com/ienablemuch/experiment-react-nextjs-typescript-absolute-import

Saturday, September 28, 2019

React projects' initial size with 4K cluster size

npx create-razzle-app a1-create-razzle-app
npx create-react-app a2-create-react-app
npx create-next-app a3-create-next-app
npx create-react-app a4-create-react-app-pnp --use-pnp


Developers-iMac:_experiment_ dev$ du -sh a1-create-razzle-app/
1.3G a1-create-razzle-app/
Developers-iMac:_experiment_ dev$ du -sh a2-create-react-app/
2.2G a2-create-react-app/
Developers-iMac:_experiment_ dev$ du -sh a3-create-next-app/
707M a3-create-next-app/
Developers-iMac:_experiment_ dev$ du -sh a4-create-react-app-pnp/
231M a4-create-react-app-pnp/

Friday, September 27, 2019

Customize webpack from create-react-app

$ npx create-react-app experiment-react-customized-webpack --scripts-version react-scripts-rewired --use-pnp

Create a directory named shared under src directory

Create randomizer.js under shared directory
export function randomizer() {
    return 4;
}

Create directories components/deeply/nested under src directory

Create SampleComponent.js under src/components/deeply/nested directory
import React from "react";
import { randomizer } from "../../../shared/randomizer";

export function SampleComponent() {
    const a = randomizer();
    return (
        <>
            <strong>I am strong {a}</strong>
        </>
    );
}


Run

It should show on the page:

I am strong 4


Change SampleComponent.js import statement:
import { randomizer } from "../../../shared/randomizer";

to
import { randomizer } from "love/randomizer";

Run

It will fail compiling


Change webpack.config.extend.js code to:

const path = require("path");
/**
 * Project's customized Webpack Configuration Extension
 * ----------------------------------------------------
 *
 * this file is heavily inspired by `react-app-rewired` mechanism.
 *
 * it simply gives you the chance to hook into the default Webpack
 * configuration as it is provided by `create-react-app`, and to
 * change it so to match your project's needs.
 *
 * If you want to check out the default values look into:
 * `./node_modules/marcopeg-react-scripts/config/webpack.config.${env}.js`
 *
 */
module.exports = (webpackConfig, env, { paths }) => {
    // here you can extend your webpackConfig at will
    webpackConfig.resolve.alias = {
        ...webpackConfig.alias,
        love: path.resolve(__dirname, "src", "shared")
    };
    return webpackConfig;
};

Run

It should show on the page:

I am strong 4




Thursday, August 29, 2019

sh: react-scripts: command not found

sh: react-scripts: command not found
npm ERR! file sh
npm ERR! code ELIFECYCLE
npm ERR! errno ENOENT
npm ERR! syscall spawn




It could be that you are running a script from Visual Studio Code's NPM-Scripts sidebar. To fix that, add the following configuration in settings.json file under .vscode directory. If .vscode directory nor settings.json is not existing, just create those two.


{
    "npm.packageManager": "yarn"
}

Restart VS Code, then click the start script from NPM Scripts sidebar.

If you don't want to create .vscode directory and settings.json file, there's a global settings where npm.packageManager setting could be applied. This can be set by going to the menu under File > Preferences > Settings. (Code > Preferences > Settings on macOS). On User tab, type package manager. Change the Npm: Package Manager to yarn.



Restart VS Code.

Alternatively, it can be set directly on global settings.json file by opening the Command Palette. Command Palette can be accessed in the menu under View > Command Palette (keyboard shortcut: Control+Shift+P on Windows, Command+Shift+P on Mac). Then type settings



Choose Preferences: Open Settings (JSON).

Then add "npm.packageManager", then choose "yarn":



Restart VS Code

Thursday, August 15, 2019

Error: Invariant failed: You should not use Link outside a Router

As of the time of this writing, the latest version for connected-react-router is 6.5.2, and the latest version for react-router and react-router-dom is 5.0.1. However, I got the errors that says Link should not be used outside a Router when using those versions.



Cause of the error: https://github.com/ReactTraining/react-router/issues/6634#issuecomment-500084539


The example of connected-react-router (even if the latest version is 6.5.2) uses connected-react-router 6.0.0 though: https://github.com/supasate/connected-react-router/blob/d822fb9afd12e9d32e8353a04c1c6b4b5ba95f72/examples/basic/package.json#L26

That example uses react-router and react-router-dom 4.3.1.


To fix the error, just use the same version from the example. Pin the version of connected-react-router to 6.0.0, and both react-router, react-router-dom to ^4.3.1.

"dependencies": {
    "connected-react-router": "6.0.0",
    "history": "^4.9.0",
    "react": "^16.9.0",
    "react-dom": "^16.9.0",
    "react-redux": "^7.1.0",
    "react-router": "^4.3.1",
    "react-router-dom": "^4.3.1",
    "react-scripts": "3.1.1",
    "redux": "^4.0.4"
},


Then delete yarn.lock and node_modules:
$ rm yarn.lock
$ rm -rf node_modules

Then reinstall:
$ yarn

Then add prop-types:
$ yarn add prop-types


As of the time of this writing, I got ^15.7.2 for prop-types.


That's all.




Wednesday, August 14, 2019

npm install -g without sudo

On my machine, ~/.npmrc file is not existing. So I created one, and then add the following:


prefix=~/.npm


And in ~/.bash_profile (which is also not existing on my machine, so I created one), I added this line:

export PATH="$HOME/.npm/bin:$PATH"


Restarted terminal, npm install -g now works even without the sudo


Followed these steps:

https://medium.com/@ExplosionPills/dont-use-sudo-with-npm-still-66e609f5f92

https://www.reddit.com/r/applehelp/comments/9tf7hx/bashrc_on_mojave/