varan
TypeScript icon, indicating that this package has built-in type declarations

0.17.1 • Public • Published

Varan

A webpack starter-kit for production ready webpack applications. Includes full support for server side rendered React applications out of the box.

Disclaimer: There will be breaking changes and outdated documentation during the pre-v1.0.0 cycles.

npm CircleCI Codecov branch David Known Vulnerabilities license

varan terminal example

Documentation

Installation

Alternative 1: New project

The best way of creating a new varan project is to use the create-varan-app script. This will automatically set up a project using varan with all the bells and whistles. It is very good starting point for modern web apps.

  1. Create a new varan project
$ npx create-varan-app my-project
  1. Profit. Do your magic.

Alternative 2: Existing projects

  1. Install varan in your project
$ npm i --save-dev varan
  1. Add npm scripts in your package.json to support your workflow. This is usually enough for most projects:
  "scripts"{
    "build""varan build",
    "build:analyze""varan build --analyze",
    "start:watch""varan watch -- --inspect"
  }
  1. Optionally customize the webpack config to support your specific project.

At a minimum, make sure your entry files are explicitly defined in your own custom config, or matches the defaults of varan. See customization for more information.

Usage

$ varan
 
  Usage:  <command> [options]
 
  Options:
 
    -V, --version               output the version number
    -h, --help                  output usage information
 
  Commands:
 
    build [options] [files...]
    watch [options]
 

Development (using local installation and npm scripts)

Start the development server with hot reloading by running

npm run start:watch

Production (using local installation and npm scripts)

To create a minified and bundled production build, run

npm run build

To analyze the production build, run

npm run build:analyze

Customization and Setup

Varan provides sane defaults and should be a good starting point for most projects. You can find the default client and server config files under varan/webpack.

The most important option to set correctly is the entry points. Existing projects implementing varan might have to set a custom entry point for the client and/or the server using a custom webpack config. The default entry points in varan are as follows:

Type Entry file relative to project root
Client src/client/index
Server src/server/bin/web

Hot Reloading

You can use react-hot-loader to get hot reloading (of react components) in your project. If you also use hooks you can install @hot-loader/react-dom and follow the customizing webpack chapter to add it to your webpack config. No need to customize your webpack config.

Remember to follow all the steps as described in the link above and create a .babelrc.js file in your project with the react-hot-loader/babel babel plugin.

Example .babelrc.js file

module.exports = {
  presets: ['varan'],
  plugins: ['react-hot-loader/babel'],
};

CSS and SASS

Varan comes with full CSS and SASS support out of the box so you don't need to do anything, even if you want to use CSS/SASS modules. By default class names are only hashed for CSS and SASS modules (e.g. *.module.css and *.module.sass). If you are using modules you need to import them directly in your components like this:

import classes from './Header.module.scss';
 
export const Header = () => (
  <header className={classes.header}>
    <h1>A heading</h1>
  </header>
);

Images

Varan comes with support for automatically resizing images (only for jpeg and png). This enables automatic generation of srcSets that you can spread on your img tags or simple url references to smaller images.

CSS/SASS basic example:

@media (max-width: 499px) {
  background: url('../my-image.png?size=500');
}
@media (min-width: 500px) {
  background: url('../my-image.png?size=1000');
}
@media (min-width: 1000px) {
  background: url('../my-image.png?size=1920');
}

Images handled by the automatic resizer should work without issues for most use cases. If it does not, it is often caused by a webpack loader not expecting the output format of the resizer. Varan has an escape hatch built-in for these scenarios, or if you prefer to have a single resized output image. You can append a query parameter srcOnly to indicate that you want a single resized image reference — a string. This is for instance used for the app manifest as it does not support multiple image references.

App manifest example

{
  (...)
  "icons": [
    {
      "src": "./images/icons/icon-512x512.png?size=72&srcOnly",
      "sizes": "72x72",
      "type": "image/png"
    },
    {
      "src": "./images/icons/icon-512x512.png?size=96&srcOnly",
      "sizes": "96x96",
      "type": "image/png"
    },
    {
      "src": "./images/icons/icon-512x512.png?size=128&srcOnly",
      "sizes": "128x128",
      "type": "image/png"
    },
  ]
}

Performance

There are many things you can do to optimize build and runtime performance. Here are some recommendations.

Fibers

Consider adding fibers to your project as a development dependency. This may significantly improve build performance for sass heavy projects.

Note: you have to select the right version of fibers depending on your node version!

Polyfills and Browser Support

Varan automatically adds basic polyfills for IE11 (to be removed at some point) and injects any other necessary polyfills using @babel/preset-env and @babel/plugin-transform-runtime based on your browserslist. Note that it is expected that you have @babel/runtime and, core-js@3 or core-js-pure@3 package as a production dependency to provide polyfills.

Browserslist

Varan supports browserslist for polyfilling. Refer to browserslist for more information on how to use it. Varan supports both .browserslistrc files and the browserslist property in package.json.

Customizing Webpack

When you create your own webpack configuration it is highly recommended to extend the existing config and only make the necessary changes to fit your use case. The built in configs contain many useful defaults and are kept up to date as best practices and plugins change.

The built in configs are available at varan/webpack/client and varan/webpack/server for the client and server config respectively. The configs exports a function where you can set some options without having to revert to overriding the exported webpack config object.

For available options see default webpack configs.

Build-time Variables

By default, all environment variables starting with APP_BUILD_VAR_ or REACT_APP_ (for compatibility reasons) are exchanged in place during build time. This also includes the process.env.NODE_ENV environment variable. Only use this for non-sensitive variables for use in the front-end.

Note: Be careful with what you put in those environment variables as any (even sensitive) information can be exposed to all users!

If you are using the default webpack configs, then you can also pass in an object to the buildVars property (see API for more information) with variables to replace at build time.

This is useful if you want to have different builds depending on some build parameters, e.g. different backend API urls depending on your environment.

Progressive Web Apps

There are several methods for creating your asset manifest. Varan has app-manifest-loader built in and will automatically create manifests from *.webmanifest and browserconfig.xml files. You can also use other alternatives such as webpack-pwa-manifest if you customize your configuration.

Here is a simple example on how to create your own application manifest, but please, refer to app-manifest-loader for more information

import Helmet from 'react-helmet-async';
 
// Import your handcrafted manifests
import manifest from './manifest.webmanifest';
import browserconfig from './browserconfig.xml';
 
// Exports
export const App = () => (
  <>
    <Helmet>
      <link rel="manifest" href={manifest} />
      <meta name="msapplication-config" content={browserconfig} />
    </Helmet>
    <div className="App">
      <p>My First PWA App!</p>
    </div>
  </>
);

Static client only applications (e.g. create-react-app)

Example project
static-example

Create the following directory structure in the root of your project

my-project
\--- webpack
     +--- client.js - client customizations

Make sure the client.js file exports a function that returns the webpack configuration object. Install html-webpack-plugin and webpack-merge in your project by running npm i --save-dev html-webpack-plugin webpack-merge.

Add html-webpack-plugin to your client.js configuration as seen below:

const HtmlWebpackPlugin = require('html-webpack-plugin');
const merge = require('webpack-merge');
const path = require('path');
const client = require('varan/webpack/client');
 
module.exports = (options) =>
  merge(client(options), {
    plugins: [
      new HtmlWebpackPlugin({
        inject: true,
        favicon: options.favicon,
        // Optionally provide a custom index.html template
        // template: path.resolve(__dirname, '..', 'src', 'client', 'index.html'),
        minify: {
          removeComments: true,
          collapseWhitespace: true,
          removeRedundantAttributes: true,
          useShortDoctype: true,
          removeEmptyAttributes: true,
          removeStyleLinkTypeAttributes: true,
          keepClosingSlash: true,
          minifyJS: true,
          minifyCSS: true,
          minifyURLs: true,
        },
      }),
    ],
  });

Use Your Own Webpack Configuration

Customizations are supported through specifying your own webpack config files for varan build and/or varan watch. It is recommended to create your own webpack directory with your custom client and server files, and specifying these when using varan build and varan watch. Whenever possible, extend the default webpack configs provided in varan/webpack/client and varan/webpack/server respectively instead of creating your own from scratch. See extending webpack config for more information on how to extend the config.

Note that development mode (varan watch) only supports up to two config files, one with target: 'browser' and one with target: 'node', while production mode (varan build) supports any number of configs.

To use your new local configs in development mode you can provide the path to your config files directly. If you only want to override client or server you can use varan/webpack/server or varan/webpack/client respectively to use the default config for the non-overridden config.

To override only the client config for development mode, run:

varan watch ./webpack/client varan/webpack/server

For production build you can specify a list of config files to build like so:

varan build ./webpack/client ./webpack/server

Remember to update your npm scripts to use the new config files as shown above.

Troubleshooting

Sometimes stuff go wrong and with the complexity that Webpack brings it may be difficult to find a root cause and solution. Here are some common issues you might encounter and some suggestions on how to fix them. If you encounter other issues not mentioned here, please leave an issue and a simple reproduction repository (and a fix if you have it) and we can add it here for the benefit of others.

varan watch gives 404

Sometimes it might seem that varan watch is working fine - no issues encountered - but your browser gives you 404. Try running varan build as some errors might be swallowed by the watcher, but will surface when building. This should give you a better indication of the actual issue and allow you to solve it.

Syntax errors in dependencies

Sometimes you may encounter dependencies that require special care or a custom webpack config. This is usually because the dependency is built for a different environment than you have, either it is browser vs Node.js or different version of Node.js.

Typically you will see something similar when running your application:

💬 SERVER: [REDACTED]/node_modules/@aws-amplify/ui/dist/style.css:13
:root {
^
 
SyntaxError: Unexpected token :
    at new Script (vm.js:79:7)
    at createScript (vm.js:251:10)
    at Object.runInThisContext (vm.js:303:10)
    at Module._compile (internal/modules/cjs/loader.js:657:28)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
    at Module.load (internal/modules/cjs/loader.js:599:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
    at Function.Module._load (internal/modules/cjs/loader.js:530:3)
    at Module.require (internal/modules/cjs/loader.js:637:17)
    at require (internal/modules/cjs/helpers.js:20:18)

In this case it is because aws-amplify and aws-amplify-react is installed and depends on @aws-amplify/ui which requires a .css file. This works fine in the front-end because all code, including node_modules, is bundled and run through webpack. The back-end Node.js server does not run node_modules through webpack and hence does not know how to parse .css files. The result is that it crashes immediately when started.

This specific issue can be solved in two ways;

  1. Bundle the specific dependency that causes issues. This is the recommended approach.
  2. Bundle all node_modules - even on the server. Note that this may have severe performance implications.

In both cases you will need to create your own webpack configuration and override the relevant options. See customizing webpack for more information on using your own webpack config.

This is an example of a custom webpack config for solving the specific issue described above according to the recommended option 1):

const server = require('varan/webpack/server');
 
module.exports = (options) => server({ ...options, whitelistExternals: ['aws-amplify-react'] });

API

build([options])

Create a production build using any number of webpack configurations.

import { build } from 'varan';
  • options (object): Object with options
    • appDir (string): The working directory to use. Default: process.cwd()
    • configs ((string|function|object)[]): An array of valid webpack configurations. Either paths to a file, functions or actual webpack config object are supported, or a mix. All functions provided here will also receive these options. Default: ['varan/webpack/client', 'varan/webpack/server']
    • verbose (boolean): Verbose logging? Default: false
    • env (string): Build environment to use. Either development or production. Default: process.env.NODE_ENV
    • warnAssetSize (number): Warn when an asset exceeds this size in bytes. Default: 512 * 1024 (512kb)
    • warnChunkSize (number): Warn when a chunk exceeds this size in bytes. Default: 1024 * 1024 (1mb)

Default webpack configs

Varan provides a default webpack config for client applications and server applications. These are exported as functions that take some options (see below) and returns a valid webpack configuration object.

varan/webpack/client

client([options])

  • options (object): Object with options
    • analyze (boolean): Analyze the bundle? Opens a browser window with a breakdown on bundle sizes. Default: false
    • appDir (string): The working directory to use. Default: process.cwd()
    • buildVars (object): A key => value object with global variables to substitute during build time. Default: {}
    • entry (string): The bundle entry file relative to the sourceDir. Default: index
    • env (string): Current environment as in NODE_ENV. Should be development or production. Default: process.env.NODE_ENV
    • name (string): The name of the application. Default: name of entry file
    • targetDir (string): The directory to output the build relative to the working directory. Default: dist/client
    • sourceDir (string): The directory of the source files relative to the working directory. Default: src/client
    • devServerPort (number): The port number to use for the development server used when running varan watch. Default 3000

varan/webpack/server

server([options])

  • options (object): Object with options
    • appDir (string): The working directory to use. Default: process.cwd()
    • buildVars (object): A key => value object with global variables to substitute during build time. Default: {}
    • entry (string): The bundle entry file relative to the sourceDir. Default: index
    • env (string): Current environment as in NODE_ENV. Should be development or production. Default: process.env.NODE_ENV
    • name (string): The name of the application. Default: name of entry file
    • targetDir (string): The directory to output the build relative to the working directory. Default: dist/server
    • sourceDir (string): The directory of the source files relative to the working directory. Default: src/server
    • clientTargetDir (string): The directory of the client build files relative to the working directory. Default: dist/client
    • whitelistExternals (string[]): List of node_modules to include in the server bundle. Use this if you are using modules that requires to be processed through webpack (e.g. if you get syntax errors because of non-javascript files or because your node version does not support the required featureset) . Default: []

License

MIT

Package Sidebar

Install

npm i varan

Weekly Downloads

6

Version

0.17.1

License

MIT

Unpacked Size

267 kB

Total Files

81

Last publish

Collaborators

  • ersims