<file_sep>import { join } from 'path'

import TsconfigPathsPlugin from 'tsconfig-paths-webpack-plugin'

import { PresetFn } from './types'

export type TsConfigPathsPreset = 'tsconfigpaths'

/**
 * Configures webpack to be able to import files with import
 * aliases from the tsconfig
 */
const config: PresetFn = () => {
  return {
    resolve: {
      plugins: [
        new TsconfigPathsPlugin({
          configFile: join(process.cwd(), './tsconfig.json'),
        }),
      ],
    },
  }
}

export default config
<file_sep>import { Configuration } from 'webpack'
import compact from 'lodash/compact'
import concat from 'lodash/concat'
import map from 'lodash/map'
import split from 'lodash/split'
import trim from 'lodash/trim'

import { Presets } from './webpack/build-utils/presets/types'
import clientConfig from './webpack/webpack.client'
import serverConfig from './webpack/webpack.server'

delete process.env['TS_NODE_PROJECT'] // needed to make sure other loaders don't use the tsconfig version from the environment

/**
 * The environment
 *
 * This object is also passed down to all presets
 */
export type Env = {
  mode: 'production' | 'development'
  presets: Presets[]
  type?: 'client' | 'server'
  target?: 'client' | 'server'
  watch: boolean
}

/**
 * The default environment
 *
 * Presets that are both used on the server and client
 * can be added here
 */
const defaultEnv: Env = {
  mode: 'production',
  presets: ['base', 'babel', 'tsconfigpaths'],
  watch: false,
}

/**
 * Args already parsed by webpack and passed to the config function
 */
type Args = {
  color?: boolean
  watch?: boolean
}

/**
 * Entry point for webpack
 */
function config(
  { presets, ...rest }: Record<string, string> = {},
  args: Args = {}
): Configuration[] {
  const env: Env = Object.assign({}, defaultEnv, rest, {
    presets: compact(
      concat<Presets>(
        map(split(presets, ','), trim) as Presets[],
        defaultEnv.presets
      )
    ),
    watch: !!args.watch,
  })
  const sConfig = serverConfig(env)
  const cConfig = clientConfig(env)
  // console.log(cConfig)
  return [sConfig, cConfig]
}

export default config
<file_sep>/* eslint-disable @typescript-eslint/no-var-requires */
import { resolve } from 'path'

import webpackMerge from 'webpack-merge'
import map from 'lodash/map'
import uniqBy from 'lodash/uniqBy'
import { Configuration } from 'webpack'

import { Env } from '../../../webpack.config'

/**
 * Loads the presets from the file system
 *
 * Presets must have the following file naming convention:
 * `webpack.<preset-name>.ts`
 *
 * @param env - the environment containing the wanted presets
 * @returns - the merged config from all presets
 */
function loadPresets(env: Env): Configuration {
  const presets = uniqBy(env.presets, (preset) => {
    if (typeof preset === 'string') {
      return preset
    }
    return preset[0]
  })
  return webpackMerge(
    map(presets, (preset) => {
      if (typeof preset === 'string') {
        return require(resolve(__dirname, `./webpack.${preset}`)).default(env)
      }
      return require(resolve(__dirname, `./webpack.${preset[0]}`)).default(
        env,
        preset[1]
      )
    })
  )
}

export default loadPresets
<file_sep>import { PresetFn } from './types'

export type BasePreset = 'base'

/**
 * Genereric base config
 */
const config: PresetFn = (e) => {
  return e.mode === 'production'
    ? {
        mode: 'production',
        devtool: 'nosources-source-map',
      }
    : {
        mode: 'development',
        devtool: 'source-map',
      }
}

export default config
<file_sep>import NodemonPlugin from 'nodemon-webpack-plugin'

import { PresetFn } from './types'

export type NodemonPreset = 'nodemon' | ['nodemon', Options]

type Options = ConstructorParameters<typeof NodemonPlugin>[0]

/**
 * Adds the nodemon plugin, the settings for nodemon should be passed
 * into the preset if needed
 */
const config: PresetFn<Options> = (_, settings) => {
  return {
    plugins: [
      /**
       * if adding `watch`, ensure only generated files are watched:
       *
       * ```
       * watch: resolve('./build'),
       * ```
       * @see https://medium.com/@binyamin/get-nodemon-to-restart-after-webpack-re-build-8746db80548e
       */
      new NodemonPlugin(settings),
    ],
  }
}

export default config
<file_sep>import {
  ApolloClient,
  ApolloClientOptions,
  InMemoryCache,
} from '@apollo/client'

export function createApolloClient(
  options?: Partial<ApolloClientOptions<any>>
) {
  return new ApolloClient({
    uri: 'https://48p1r2roz4.sse.codesandbox.io',
    cache:
      typeof window !== 'undefined'
        ? // eslint-disable-next-line @typescript-eslint/ban-ts-comment
          // @ts-expect-error
          new InMemoryCache().restore(window.__APOLLO_STATE__)
        : new InMemoryCache(),
    ...options,
  })
}
<file_sep>import { RootState } from '@client/store'

declare global {
  interface Window {
    __PRELOADED_STATE__: Partial<RootState>
  }
}
<file_sep>import merge from 'lodash/merge'
import concat from 'lodash/concat'

import { Env } from '../../webpack.config'

import { Presets } from './presets/types'

/**
 * Adds preset to the env object
 *
 * @param env - the environment to append the presets to
 * @param presets - the presets to append
 * @returns - the new environment with all presets
 */
export function addPresets(env: Readonly<Env>, presets: Presets[]): Env {
  return merge({}, env, {
    presets: concat(env.presets, presets),
  })
}
<file_sep>import { join } from 'path'

import HtmlWebpackPlugin from 'html-webpack-plugin'
import { Configuration } from 'webpack'
import merge from 'webpack-merge'
import concat from 'lodash/concat'

import { Env } from '../webpack.config'

import { addPresets } from './build-utils'
import loadPresets from './build-utils/presets'
import { Presets } from './build-utils/presets/types'

const PRESETS: Presets[] = []
const DEV_PRESETS: Presets[] = concat([], PRESETS, ['serve'])
const PRODUCTION_PRESETS: Presets[] = PRESETS

function config(env: Env): Configuration {
  return merge(
    {
      entry: {
        main: ['./src/client/index.tsx'],
      },
      plugins: [new HtmlWebpackPlugin({ template: './src/client/index.html' })],
      target: 'web',
      output: {
        filename: '[name].[contenthash].js',
        path: join(process.cwd(), './dist/client/static'),
        publicPath: 'static/',
      },
    },
    loadPresets(
      addPresets(
        env,
        env.mode === 'production' ? PRODUCTION_PRESETS : DEV_PRESETS
      )
    )
  )
}

export default config
<file_sep>// caller.target will be the same as the target option from webpack
// https://webpack.js.org/loaders/babel-loader/#customize-config-based-on-webpack-target
function isWebTarget(caller) {
  return Boolean(caller && caller.target === 'web')
}

function isTestBuild(caller) {
  return Boolean(caller && caller.name === 'babel-jest')
}

module.exports = (api) => {
  const isWeb = api.caller(isWebTarget)
  const isTest = api.caller(isTestBuild)

  const targets =
    isWeb && !isTest
      ? '> 0.25%, not dead'
      : {
          node: 'current',
        }

  /**
   * optimize the build process performance by caching config function execution
   * result
   * @see https://babeljs.io/docs/en/config-files#apicache
   */
  api.cache(false) // can not be true, otherwise we cache the server config and use it for the client

  return {
    presets: [
      [
        '@babel/preset-env',
        {
          targets,
        },
      ],
      '@babel/preset-react',
      '@babel/preset-typescript',
    ],
  }
}
<file_sep>import { PresetFn } from './types'

export type BabelPreset = 'babel'

const config: PresetFn = () => {
  return {
    resolve: {
      extensions: ['.js', '.jsx', '.ts', '.tsx'],
    },
    module: {
      rules: [
        {
          test: /\.(ts|tsx)$/,
          use: [
            {
              // https://webpack.js.org/loaders/babel-loader
              loader: 'babel-loader',
              options: {
                cacheDirectory: true,
              },
            },
          ],
        },
      ],
    },
  }
}

export default config
<file_sep>import { Configuration } from 'webpack'

import { Env } from '../../../src/webpack.config'

import { BabelPreset } from './webpack.babel'
import { BasePreset } from './webpack.base'
import { NodemonPreset } from './webpack.nodemon'
import { ServePreset } from './webpack.serve'
import { TsConfigPathsPreset } from './webpack.tsconfigpaths'

/**
 * A preset function always gets the environment and an optional options object
 * if the user specifies it
 */
export type PresetFn<S = void> = (env: Env, options?: S) => Configuration

/**
 * Allows Typescript to check both the preset and its options
 */
export type Presets =
  | BasePreset
  | ServePreset
  | BabelPreset
  | NodemonPreset
  | TsConfigPathsPreset
<file_sep>import { join } from 'path'

import {
  WebpackPluginServe,
  WebpackPluginServeOptions,
} from 'webpack-plugin-serve'
import merge from 'lodash/merge'

import { PresetFn } from './types'

export type ServePreset = 'serve' | ['serve', WebpackPluginServeOptions]

const defaultOptions: WebpackPluginServeOptions = {
  static: [
    join(process.cwd(), './dist/client'),
    join(process.cwd(), './dist/client/static'),
  ],
  waitForBuild: true,
}

/**
 * Genereric base config
 */
const config: PresetFn<WebpackPluginServeOptions> = (_, o = {}) => {
  return {
    // an example entry definition
    entry: {
      main: ['webpack-plugin-serve/client'], // ← important: this is required, where the magic happens in the browser
    },
    plugins: [new WebpackPluginServe(merge({}, defaultOptions, o))],
  }
}

export default config
<file_sep>import { join } from 'path'

import 'cross-fetch/polyfill'
import express from 'express'

import { renderer } from './renderer'

export const staticPath =
  process.env['NODE_ENV'] === 'production'
    ? join(__dirname, '../client/static/')
    : join(process.cwd(), './dist/client/static/')

const app = express()

app.get('/', renderer)

app.use('/static/', express.static(staticPath))

app.listen(3000, () => {
  console.log('Server listening on port 3000')
})
<file_sep>module.exports = {
  env: {
    browser: true,
    es2021: true,
    node: true,
  },
  extends: [
    'eslint:recommended',
    'plugin:react/recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:cypress/recommended',
    'plugin:import/recommended', // https://github.com/benmosher/eslint-plugin-import
    'plugin:lodash/recommended', // https://github.com/wix/eslint-plugin-lodash
    'plugin:jest/recommended', // https://github.com/jest-community/eslint-plugin-jest
    'plugin:jsx-a11y/recommended', // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y
    'plugin:prettier/recommended', // needs to be last
  ],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaFeatures: {
      jsx: true,
    },
    ecmaVersion: 12,
    sourceType: 'module',
  },
  plugins: ['react', '@typescript-eslint'],
  rules: {
    'import/order': ['error', { 'newlines-between': 'always' }],
    'lodash/prefer-lodash-method': 0,
    'lodash/prefer-lodash-typecheck': 0,
    '@typescript-eslint/explicit-module-boundary-types': 0,
  },
  overrides: [
    {
      files: ['**/*.spec.ts', '**/*.spec.js'],
      rules: {
        'jest/expect-expect': 0, // do not expect expects in cypress tests
      },
    },
  ],
  settings: {
    react: {
      version: 'detect',
    },
    'import/resolver': {
      webpack: {
        config: './webpack.config.ts',
        'config-index': 1,
      },
    },
  },
}
<file_sep>### apollo ssr example

Example of Apollos getDataFromTree ssr functionality apollo offers.

To start the appplication please run:
```
yarn
yarn start
```
<file_sep>import { join } from 'path'

import { Configuration } from 'webpack'
import merge from 'webpack-merge'
import nodeExternals from 'webpack-node-externals'

import { Env } from '../webpack.config'

import { addPresets } from './build-utils'
import loadPresets from './build-utils/presets'
import { Presets } from './build-utils/presets/types'

const PRESETS: Presets[] = [
  [
    'nodemon',
    {
      watch: ['src'],
      ext: 'json,ts,tsx',
      ignore: [
        '__tests__/*',
        '__stories__/*',
        '*.js',
        '*.js.map',
        '*.d.ts',
        '*.md',
      ],
      delay: 1000,
    },
  ],
]

function config(env: Env): Configuration {
  return merge(
    {
      entry: {
        main: './src/server/index.ts',
      },
      target: 'node',
      output: {
        filename: 'server.js',
        path: join(process.cwd(), './dist/server'),
      },
      externalsPresets: { node: true },
      externals: [nodeExternals()] as Configuration['externals'], // needed because the types from the @types repo still target webpack 4 types, should be fixed with the next release of webpack-node-externals because they now generate a types file themselves
    },
    loadPresets(addPresets(env, PRESETS))
  )
}

export default config
