text: import hash from 'string-hash'
  import { join, basename } from 'path'
  import babelLoader from 'babel-loader'
  
  // increment 'e' to invalidate cache
  // eslint-disable-next-line no-useless-concat
  const cacheKey = 'babel-cache-' + 'e' + '-'
  const nextBabelPreset = require('../../babel/preset')
  
  const getModernOptions = (babelOptions = {}) => {
    const presetEnvOptions = Object.assign({}, babelOptions['preset-env'])
    const transformRuntimeOptions = Object.assign(
      {},
      babelOptions['transform-runtime'],
      { regenerator: false }
    )
  
    presetEnvOptions.targets = {
      esmodules: true,
    }
    presetEnvOptions.exclude = [
      ...(presetEnvOptions.exclude || []),
      // Blacklist accidental inclusions
      'transform-regenerator',
      'transform-async-to-generator',
    ]
  
    return {
      ...babelOptions,
      'preset-env': presetEnvOptions,
      'transform-runtime': transformRuntimeOptions,
    }
  }
  
  const nextBabelPresetModern = presetOptions => context =>
    nextBabelPreset(context, getModernOptions(presetOptions))
  
  module.exports = babelLoader.custom(babel => {
    const presetItem = babel.createConfigItem(nextBabelPreset, {
      type: 'preset',
    })
    const applyCommonJs = babel.createConfigItem(
      require('../../babel/plugins/commonjs'),
      { type: 'plugin' }
    )
    const commonJsItem = babel.createConfigItem(
      require('@babel/plugin-transform-modules-commonjs'),
      { type: 'plugin' }
    )
  
    const configs = new Set()
  
    return {
      customOptions(opts) {
        const custom = {
          isServer: opts.isServer,
          isModern: opts.isModern,
          pagesDir: opts.pagesDir,
          hasModern: opts.hasModern,
          babelPresetPlugins: opts.babelPresetPlugins,
        }
        const filename = join(opts.cwd, 'noop.js')
        const loader = Object.assign(
          opts.cache
            ? {
                cacheCompression: false,
                cacheDirectory: join(opts.distDir, 'cache', 'next-babel-loader'),
                cacheIdentifier:
                  cacheKey +
                  (opts.isServer ? '-server' : '') +
                  (opts.isModern ? '-modern' : '') +
                  (opts.hasModern ? '-has-modern' : '') +
                  JSON.stringify(
                    babel.loadPartialConfig({
                      filename,
                      cwd: opts.cwd,
                      sourceFileName: filename,
                    }).options
                  ),
              }
            : {
                cacheDirectory: false,
              },
          opts
        )
  
        delete loader.isServer
        delete loader.cache
        delete loader.distDir
        delete loader.isModern
        delete loader.hasModern
        delete loader.pagesDir
        delete loader.babelPresetPlugins
        return { loader, custom }
      },
      config(
        cfg,
        {
          source,
          customOptions: {
            isServer,
            isModern,
            hasModern,
            pagesDir,
            babelPresetPlugins,
          },
        }
      ) {
        const filename = this.resourcePath
        const options = Object.assign({}, cfg.options)
        const isPageFile = filename.startsWith(pagesDir)
  
        if (cfg.hasFilesystemConfig()) {
          for (const file of [cfg.babelrc, cfg.config]) {
            // We only log for client compilation otherwise there will be double output
            if (file && !isServer && !configs.has(file)) {
              configs.add(file)
              console.log(`> Using external babel configuration`)
              console.log(`> Location: "${file}"`)
            }
          }
        } else {
          // Add our default preset if the no "babelrc" found.
          options.presets = [...options.presets, presetItem]
        }
  
        options.caller.isServer = isServer
        options.caller.isModern = isModern
  
        options.plugins = options.plugins || []
  
        if (!isServer && isPageFile) {
          const pageConfigPlugin = babel.createConfigItem(
            [require('../../babel/plugins/next-page-config')],
            { type: 'plugin' }
          )
          options.plugins.push(pageConfigPlugin)
        }
  
        if (isServer && source.indexOf('next/data') !== -1) {
          const nextDataPlugin = babel.createConfigItem(
            [
              require('../../babel/plugins/next-data'),
              { key: basename(filename) + '-' + hash(filename) },
            ],
            { type: 'plugin' }
          )
          options.plugins.push(nextDataPlugin)
        }
  
        if (isModern) {
          const nextPreset = options.presets.find(
            preset => preset && preset.value === nextBabelPreset
          ) || { options: {} }
  
          const additionalPresets = options.presets.filter(
            preset => preset !== nextPreset
          )
  
          const presetItemModern = babel.createConfigItem(
            nextBabelPresetModern(nextPreset.options),
            {
              type: 'preset',
            }
          )
  
          options.presets = [...additionalPresets, presetItemModern]
        }
  
        // If the file has `module.exports` we have to transpile commonjs because Babel adds `import` statements
        // That break webpack, since webpack doesn't support combining commonjs and esmodules
        if (!hasModern && source.indexOf('module.exports') !== -1) {
          options.plugins.push(applyCommonJs)
        }
  
        options.plugins.push([
          require.resolve('babel-plugin-transform-define'),
          { 'typeof window': isServer ? 'undefined' : 'object' },
          'next-js-transform-define-instance',
        ])
  
        // As next-server/lib has stateful modules we have to transpile commonjs
        options.overrides = [
          ...(options.overrides || []),
          {
            test: [
              /next[\\/]dist[\\/]next-server[\\/]lib/,
              /next[\\/]dist[\\/]client/,
              /next[\\/]dist[\\/]pages/,
            ],
            plugins: [commonJsItem],
          },
        ]
  
        for (const plugin of babelPresetPlugins) {
          require(join(plugin.dir, 'src', 'babel-preset-build.js'))(
            options,
            plugin.config || {}
          )
        }
  
        return options
      },
    }
  })
  