Skip to content

PutziSan/TS-JS-React-Webpack-babel7-setup

Repository files navigation

TS-JS-React-Project via Webpack4 and babel7

The project is actually only built with the latest Babel-features + Webpack4 + jest for unit-tests.

This documentation is not intended to show how to build a React application with this configuration, but to explain each unit and their meaning of the used configurations. I will try to document every config-line used for this setup.

see the example in the src-folder, in principle each feature is illustrated by a mini example.

get started

git clone https://github.com/PutziSan/TS-JS-React-Webpack-babel7-setup.git [NEW_PROJECT_NAME]
cd [NEW_PROJECT_NAME]
git remote remove origin
yarn

whats included

  • mix TypeScript and JavaScript with ES6-Imports via babel7
  • Tree-Shaking and Code-Splitting via dynamic import()
  • jest-test using quasi-same babel-config
  • Hot-Module-Replacement for your React-Components via react-hot-loader on development
  • (...) => Basically everything you know from Create-React-App (CRA), only significantly slimmer implemented via babel7

table of contents

babel

babel7 is used equally for all build+test+develop.

babel.config.js

Babel is configured via the babel.config.js-file. The process.env.NODE_ENV-variable is used to determine which plugins and presets should be added.

presets and plugins

Presets are a predefined set of plugins, see babel-dependencies for the individual presets/plugins we use.

Plugin/Preset Ordering:

  • Plugins run before Presets
  • Plugin ordering is first to last
  • Preset ordering is reversed (last to first)

production-specific options

development-specific options

test-specific options

Customizations for jest, since jest ES6 cannot import/export and does not understand dynamic imports, see jest-documentation:

babel-helper-functions via babel-runtime for smaller bundle-size

Babel injects small helper-functions like _extend, when needed. With @babel/plugin-transform-runtime the code is not copied in every file, but the transform-runtime-plugin will inject referneces to the @babel/runtime-package, which holds the implementations of the helper-functions ("Helper aliasing"). This will result in a smaller bundle. The "useESModules": true-option will use ES6-modules (import/export) instead of the implementations with commonjs (require/module.exports).

Please note that the @babel/plugin-transform-runtime can also perform other transformations:

In my opinion it is not a good idea to use these options, because the inserted transformations can take up a lot of space and it is very likely that others also use polyfills, so it may be that a feature is polyfilled by several different libraries which bloats your bundle. If you are developing a library, it is best not to use features that require polyfills at all. If really necessary, use ponyfills and document the use.

polyfills

In src/index.tsx the first line loads a polyfill-script (import './bootstrap/polyfills';), so that the app also runs under Internet Explorer 11 (IE11). Following polyfills are included:

Both polyfills together increase the bundle-size by ~ 5kb. If you think you do not need this polyfills you can remove them. If you need other polyfills, because you use new features or have to support very old browsers, you should attach them in src/bootstrap/polyfills.js.

polyfills via babel-polyfill (not recommended)

To stop thinking about polyfills you can automate this process with babel-polyfill. Similar to @babel/plugin-transform-runtime with the corejs-option, polyfills via core-js are added for older browsers. In contrast to runtime, the polyfills are loaded globally into the application (which is not recommended for libraries).

You can then additionaly use the useBuiltIns-option of the babel-preset-env:

  • useBuiltIns: 'usage': Adds specific imports for polyfills when they are used in each file. We take advantage of the fact that a bundler will load the same polyfill only once. Be aware that this will not polyfill usages in node_modules
  • useBuiltIns: 'entry': You need to import @babel/polyfill in your entry-file once, babel will transform this import to only include imports needed by the specified preset-env target-option; At the moment (as of 13.08.2018) this is for browsers: ['>0.25%'] still over 80 kb

I would not recommend the use of babel-polyfill since:

  • either significantly too many polyfills are imported (library standalone or with useBuiltIns: 'entry') or
  • using useBuiltIns: 'usage' the polyfills are incosistent (they are included locally per file but change the global namespace) and only functions used in your code are analyzed (since used node_moduless are not examined), außerdem ist auch mit dieser Methode
    • Also with this method the resulting package is bigger than if you install the polyfills yourself as described above
    • For the example app only the two polyfills mentioned above are needed to run under IE11, the bundle size was still ~ 14 kb bigger and I had to install the required imports manually into index.ts, because preset-env did not recognize that for the dynamic import() Promise must polyfill and for React-Loadable Object.assign

babel-dependencies

package description
@babel/core peer-dependency for everything else
@babel/plugin-proposal-class-properties see proposal, so that ES6 class fields can not only be set in the constructor; the "loose": true-option will assign the properties via assignment expressions instead of Object.defineProperty which results in less code
@babel/plugin-proposal-object-rest-spread The Object-React-Operator (...) is not natively supported by preset-env.
The rest operator works in TypeScript files without this transformation because the TypeScript compiler has been compiling it to ES5 code since TypeScript 2.1.
The useBuiltIns: true-Option transforms it to Object.assign-calls, make sure to include a polyfill for older browsers (IE 11)
@babel/plugin-syntax-dynamic-import only Syntax-Plugin! so babel understands dynamic imports, which webpack uses for code-splitting
@babel/plugin-transform-runtime Babel-helpers and -polyfills will use @babel/runtime, without this babel copies the needed helper into every single file when needed, for more information visit their documentation; the "useESModules": true-option will use ES6-modules with import/export
@babel/runtime Babel will inject this dependency in your code when needed via @babel/plugin-transform-runtime, see upper line
@babel/preset-env ES>5 to ES5, should always run as the last transformation, so it should always remain the first presets-entry
@babel/preset-react JSX to ES6;
useBuiltIns-option will passed through the useBuiltIns-option in @babel/plugin-transform-react-jsx => "When spreading props, use Object.assign directly instead of Babel's extend helper."
development-option will add debug-informations, turned off in production
@babel/preset-typescript TS/TSX to ES6/JSX
babel-plugin-dynamic-import-node the only one not by babel itself but by airbnb, only for jest-tests, see jest-declaration

further babel-dependencies

package description
babel-core@7.0.0-bridge.0 for jest-test, siehe jest-doku
babel-jest so that jest also uses the babel-transformations
babel-loader@8.0.0-beta.4 to transform files via webpack, new babel-load-v8 must be used with new babel7
react-hot-loader/babel babel extension for hot-loading to work with react, see react-hot-loader

webpack

webpack is our bundler and development-server. It is configured via webpack.config.js.

webpack.config.js

config description
entry An object where the key represents the output-name of the generated js-file (this will applied to [name] in output.filename) and the value is the location of your entry-point, see Entry Points - object-syntax (webpack-documentation)
output.filename js/ is used to collect all js-files in the js-sub-dir, see this discussion
see caching of your assets for information about [contenthash:8]
output.path The folder specified here is used as the target where it should output your bundles, assets and anything else you bundle or load with webpack.
If you want to collect your js-files in a single directory use output.filename (see above)
output.chunkFilename chunks are generated when using JavaScripts dynamic imports, see webpack-documentation
output.publicPath It is important that you set publicPath with a trailing slash so that the paths are not set relative to the current route (note that the default value is an empty string ("")). For example, the (html-webpack-plugin) would then insert <script src="main.js"> and your browser would search for main.js relative to the current route, but we want <script src="/main.js"> (note the leading slash) so that the JS file is always searched in the root.
mode sets default-plugins (siehe link) and replaces NODE_ENV to production/development, see webpack#optimization to check what the production-mode does
devtool defines how source-maps are written, eval gives best performance, but incorrect line numbers, for our project eval-source-map is the best compromise between correct line numbers and performance
resolve.symlinks false, cause it can cause to unexpected behavior (e.g. it could apply a module-rule even you excluded it if the symlinked path is outside of the exclusion)
module 1. runs babel for every file (see more babel-dependencies) 2. we can import static files (img/pdf), which are converted to an url and added to the bundle as an external file see webpack-dependencies#file-loader
devServer hot: true see react-hot-loader; contentBase: 'public' so the dev-server recognizes the static assets which are in /public
optimization.minimizer set explicit, cause the default-config via mode does not remove comments
also added collapse_vars: false, as it has caused considerable performance losses (uglifyjs-issue, terser-issue, cra-issue), but only 3KiB savings in a > 5 MiB (output-bundle-size) project (Apoly)
plugins webpack.EnvironmentPlugin: see Environment-variables
webpack.HotModuleReplacementPlugin see react-hot-loader
see webpack-dependencies for other plugins

css-configuration

setup:

make sure to include common file-formats (eg woff, woff2, ...) in your file-loader so that the css-files can load them, if not this will raise errors

caching of your assets

Hash functions (to put it simply) always output the same result on repeated calls with a string of any length (they are deterministic). I use a :8-suffix (like in [name].[contenthash:8].js) so that the names don't get too long and even at this length it is unlikely to get the same result for 2 files. If this is too uncertain, you can adjust or remove the number

Hashes are used to ensure that unmodified bundles keep the same name for successive builds and changed files get a new bundle name. Thus browsers can effectively cache files by file name. Caching is configured via different plugins and config-entries:

  • for your bundled JS-files: output.filename ([name].[contenthash:8].js) and output.chunkFilename ([id].[contenthash:8].js)
  • for your bundled CSS-files: filename ([name].[contenthash:8].js) and chunkFilename ([id].[contenthash:8].js) via mini-css-extract-plugin
  • for other static assets: options.name ([name].[hash:8].[ext]) in file-loader-options

during development you should not enable any hashing of your filenames (This can be neglected for CSS-files via mini-css-extract-plugin as it is only used in production anyway)

Have a look at webpacks caching-guide for more information.

If you have build-performance concerns, you should start with webpacks build-performance-article on github and the build-performance-section on their website to learn what you can optimize.

Environment-Variables

The webpack.EnvironmentPlugin replaces the specified keys, so that process.env.[KEY] is replaced with the actual JSON-representation of the current process.env.[KEY] in your Environment.

It is configured so that every key from process.env in the current node-process (via CLI, .env-file, CI, ...) will be available in your bundle.

Make sure to not use sensitive environment-variables in your frontend-project!

Additional all environment variables that you have defined in a .env-file will be available in process.env using the dotenv-library.

webpack-dependencies

package description
webpack core-bundler with node api
webpack-cli webpack via cli
webpack-dev-server webpack-dev-server for development
file-loader import static assets in js
css-loader with this it is possible to import css-files in js-files, a further plugin is needed to append the imported css to the dom (inline or via external css), see below
style-loader this injects the styles inline to the DOM, it is not recommended for production, but for development cause this plugin supports HMR, in production use mini-css-extract-plugin
mini-css-extract-plugin this will grab the css-files from css-loader (see above) and writes them in an external css-file per bundle, which is bether for production cause of smaller js-bundles and caching-abilities for the css-files
optimize-css-assets-webpack-plugin minifies your bundled CSS-files via nano
html-webpack-plugin This will use your src/index.html-template and injects a <script>-tag with src to your generated entry-bundle
uglifyjs-webpack-plugin enables us to use this plugin with other options then defaults, see webpack.config.js ("optimization.minimizer")
dotenv "Loads environment variables from .env"
babel-loader@8.0.0-beta.4 see further babel-dependencies

jest

Jest is translated via babel-jest via babel to es5 and made usable, for special babel settings see babels test-specific options (NODE_ENV to test is set by jest).

jest.config.js

config description
setupTestFrameworkScriptFile dev/setupTests.js configures jest-enzyme
testRegex all tests have to lay inside tests
moduleFileExtensions test these extensions for import or require, corresponds to resolve.extensions in webpack.config.js
moduleNameMapper mock static assets (img, CSS) see jest-doku - Handling Static Assets
transform pass every file through babel

jest-enzyme

jest-enzyme by AirBnB is integrated via dev/setupTests.js, which is linked to jest in setupTestFrameworkScriptFile (see jest.config.js).

jest-enzyme adds more matcher functions for jests expect. For an overview of these functions, see enzyme-docu for enzyme-matchers.

jest-dependencies

package description
jest test-module for js/react by facebook
jest-enzyme adds enzyme-matchers to jests expect
enzyme Test-utils for rendering and mounting React-Components
enzyme-adapter-react-16 adapter for enzyme that it can mount and shallow-render React-16-Components

react-hot-loader

react-hot-loader is a tool by Gaeron (Dan Abramov) to enable Hot Module Replacement (HMR).

HMR means that the page is not completely reloaded when changes are made, but that the JS modules are "replaced" internally.rden

Since Babel7 the integration with TypeScript is much easier, because Babel understands TypeScript and therefore the babel-extension of react-hot-loader can be used easily.

currently the usage is not documented here yet in react-hot-loader, but the PR that explains this already exists

react-hot-loader-setup

  1. Root-Component (src/components/App.tsx) is wrapped with the hot-HOC.
  2. for development the react-hot-loader/babel-plugin is enabled (see babels development-specific options)
  3. in webpack.config.js, the devServer.hot-prop is set to true and the webpack.HotModuleReplacementPlugin is enabled, see webpacks HMR-guide

TypeScript

TypeScript (TypeScript GitHub-repo) is build via babels @babel/preset-typescript.

tsconfig.json

See TS-doku#compiler-options, below only things worth explaining are mentioned:

config value description
moduleResolution node TLDR: node is nowadays default and bether
module esnext since we also use ES6-import/export js, it makes sense to keep this also for TS (esnext so that TS-compiler does not complain about dynamic imports)
target es6 since TS is going through babel again anyway, the ES6
jsx preserve preserve means that JSX is not converted to React.createElement, this is done by the babel compiler.
lib (search for --lib) ["es6", "dom"] "List of library files to be included in the compilation."
sourceMap false since webpack writes the sourcemaps for us it can be neglected by TS, see webpack.config.js
allowJs true allows import and export of JS without compiler-errors

npm-/yarn-scripts

Scripts defined in package.json and executed via yarn or npm run:

yarn start

cross-env NODE_ENV=development webpack-dev-server --open

Sets NODE_ENV to development (OS-agnostic via cross-env), and starts the webpack-dev-server, which uses your webpack.config.js (more information under #webpack). In addition, your default browser will open with http://localhost:3000

per default we are using devtool: 'eval' for the start-command. according to webpack the performance can be improved considerably, but the line-number is not correct in case of errors. If you need to debug with correct line-numbers you can use yarn start:debug

yarn start:debug

Sets the --devtool eval-source-map-flag, see webpacks documentation, the eval-source-map-flag is the one with the best performance while sending correct line-numbers (the other flags does not work correct cause we use typescript)

yarn build

"rimraf dist && ncp public dist && cross-env NODE_ENV=production webpack"

  1. rimraf dist: clear current dist
  2. ncp public dist copies current content of your public-folder to dist
  3. cross-env NODE_ENV=production webpack sets NODE_ENV to production (platform-agnostic via cross-env) and build your project via #webpack

yarn test

executes your defined tests in /tests, using your config in /jest.config.js, look at the jest-part for more information.

npm-scripts-dependencies

package description
cross-env set environment-variables platform-agnostic
ncp copy files platform-agnostic via node
rimraf remove files platform-agnostic via node

development-utilities

ESLint

ESLint is a linter for your JS-Code. You can use it via CLI or integrate it into your development cycle.

ESLint-settings

TODO

ESLint-dependencies

package description
eslint see ESLint
eslint-config-airbnb most commonly used lint-rule-set by Airbnb, this automatically includes following plugins: eslint-plugin-import, eslint-plugin-jsx-a11y, eslint-plugin-react
eslint-plugin-import "ESLint plugin with rules that help validate proper imports."
eslint-import-resolver-node addition toeslint-plugin-import to resolve file extensions other than .js (e.g. necessary to import .ts/.tsx files)
eslint-plugin-jsx-a11y "Static AST checker for accessibility rules on JSX elements."
eslint-plugin-react "React specific linting rules for ESLint"
eslint-plugin-prettier used via the Recommended Configuration, so it will also extend the rules via eslint-config-prettier
eslint-config-prettier Turns off rules that might conflict with Prettier.

TSLint

TSLint is a linting tool that checks your TypeScript-Code via command-line. However, the integration into your development cycle is more comfortable (for example as integration into your IDE like WebStorm or VS code) so that the code is checked quasi "as-you-type", see TSLints "Third-Party Tools".

TSLint-settings

TODO

TSLint-Dependencies

package description
tslint see TSLint
tslint-config-prettier "Use tslint with prettier without any conflict"
tslint-react "Lint rules related to React & JSX for TSLint."

Prettier

Prettier (prettier github-repo) is a strict code-formatter that supports many different languages. Prettier always tries to guarantee the same code style and does not take initial formatting into account. This guarantees that even if several people work on a project, the code style remains consistent. The options to customize the output are limited.

git pre-commit-hooks

Via husky andlint-staged every changed *.{ts,tsx,js,js,jsx,json,css,md} file is formatted uniformly before a commit using Prettier, also a tslint --fix runs for *.{ts,tsx} and a eslint --fix for *.{js,jsx} before the changed files are re-added.

The setup is according to lint-staged's documentation, but husky is set up via /husky.config.js and lint via dev/.lintstagedrc. (package.json should not be stuffed senselessly).

overwiev dependencies and devDependencies

If you ever have trouble understanding why a dependency is in package.json you can find an overview here, which refers to the corresponding documentation section.

dependencies

package link to documenation
@babel/runtime babel-helper-functions via babel-runtime for smaller bundle-size
object-assign polyfills
promise polyfills
prop-types So you can use prop-types in your *.js-files
react" A React-App without the react-package doesn't make that much sense ;)
react-dom "This package serves as the entry point of the DOM-related rendering paths. It is intended to be paired with the isomorphic React, which will be shipped as react to npm."
react-loadable A nice library for code splitting via webpack and dynamic import() calls.
This library also automatically supports HMR for your dynamic imports (see react-hot-loader).

devDependencies

The @types/...-devDependencies are omitted to explain, have a look at the DefinitelyTyped-project for more information.

package link to documenation
@babel/core babel
@babel/plugin-proposal-class-properties babel-dependencies
@babel/plugin-proposal-object-rest-spread babel-dependencies
@babel/plugin-syntax-dynamic-import babel-dependencies
@babel/plugin-transform-runtime babel-helper-functions via babel-runtime for smaller bundle-size
@babel/preset-env babel-dependencies and polyfills via babel-polyfill (not recommended)
@babel/preset-react babel-dependencies
babel-core@7.0.0-bridge.0 further babel-dependencies
babel-jest further babel-dependencies
babel-loader@8.0.0-beta.4 further babel-dependencies
react-hot-loader further babel-dependencies + react-hot-loader
babel-plugin-dynamic-import-node test-specific options
typescript TypeScript
webpack webpack
webpack-cli webpack-dependencies + yarn build
webpack-dev-server webpack-dependencies + yarn start
file-loader webpack-dependencies + caching of your assets
css-loader css-configuration
style-loader css-configuration
mini-css-extract-plugin css-configuration + caching of your assets
optimize-css-assets-webpack-plugin css-configuration
html-webpack-plugin webpack-dependencies
uglifyjs-webpack-plugin webpack-dependencies
dotenv webpack-dependencies
cross-env npm-/yarn-scripts
ncp yarn start
rimraf yarn start
jest jest
jest-enzyme jest-enzyme
enzyme jest-dependencies + jest-enzyme
enzyme-adapter-react-16 jest-dependencies + jest-enzyme
eslint ESLint
eslint-config-airbnb ESLint-dependencies
eslint-plugin-import ESLint-dependencies
eslint-import-resolver-node ESLint-dependencies
eslint-plugin-jsx-a11y ESLint-dependencies
eslint-plugin-react ESLint-dependencies
eslint-plugin-prettier ESLint-dependencies
eslint-config-prettier ESLint-dependencies
tslint TSLint
tslint-config-prettier TSLint-Dependencies
tslint-react TSLint-Dependencies
husky git-utilities
lint-staged git-utilities
prettier git-utilities

To Observe

  • webpack-serve
    • seems to be the succesor of webpack-dev-server, which is only in maintenance mode
    • => but currently there are no reasonable examples or the like. why webpack-dev-server is the better way up-to-date (08/2018)
  • reduce moment-js-bundle-size via the []webpacks IgnorePlugin](https://webpack.js.org/plugins/ignore-plugin/), see "How to optimize moment.js with webpack"
  • performance-regression with collapse_vars (uglify-js2) checken: uglifyjs-issue, terser-issue, cra-issue
    • currently added collapse_vars: false in dev/webpack/ugilfyJsPluginOptions.js
    • zum beispiel für komplettes apoly-projekt (ca 5MiB bundled) war der Unterschied ~3KiB => VERNACHLÄSSIGBAR
    • CRA nutzt terser + webpack-terser, für apoly testweise code eingebaut: terser ist langsamer (112s vs 81s) und schlechter (20188KiB vs 19846KiB, diff 342KiB) als uglify mit collapse_vars: false
  • Ist babel-preset-env wirklich notwendig, bzw vllt mit einer neueren browserslist-config in preset-env (zb ">5%"), um sicherzugehen dass nicht jeder müll für IE11 kompiliert wird, in dem fall muss auch wider auf terser anstatt uglifyj umgestiegen werden da uglifyjs nur es5 kann

(intern): Idee das Projekt von der Nutzung ähnlich wie CRA zu machen:

  • das auslagern mit all den dependencies und doku in ein projekt was übe cmd-line angesteuert werden kann
  • node-js-api anbieten sodass die webpack/babel-config verfügbar ist und man leicht Anpassungen vornehmen kann mit eigener config
    • webpack-cli und webpack-dev-server muss dann selbst hinzugefügt werden um es entsprechend zu nutzen
  • mono-repo dafür erstellen,
    • single packages für (erster spontan-entwurf):
      • babel (create-script für standard-babel-objekt)
      • webpack (create-script für config)
      • eslint
      • tslint
    • zu überlegen:
      • jest (config auslagern?)
      • TypeScript (tsconfig)
    • die einzelnen projekte exportieren jeweils die configs, wenn man etwas anpassen möchte sollte das einzelne projekt als dependency mit genutzt werden sodass man das script selbst anpassen kann auf grundlage des config-objektes

TODO

check + document:

  • optimization.splitChunks => sollte man machen habe aktuell mit paar werten rumgespielt, zb ist aktuell "all" anscheinend nicht so geil, maxSize sinnvoll mit http/2
  • runtimechunk sollte true sein da sonst bei eine änderung quasi jedes chunk einen neuen hash bekommt und nichts gecached werden kann, mit runtimeChunk werdend ie hash werte hier rein geschrieben sodass nicht geänderte files gleich bleiben
  • opt-in useESModules:"auto" => in bezug auf babel-runtime-plugin, sollte ich checken wenn documentation besser ist
    • supportsStaticESM : ist vielleicht auch nur ne interne option, sollte ich prüfen wenn die documentation bissel stabiler ist

gut info auch der Medium-Artikel webpack 4: Code Splitting, chunk graph and the splitChunks optimization

About

Yet another starter-template because I was not able to understand the configurations of other starters.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published