title: WordPress customizer and webpack - exposing wp global
body: <p>I'm using webpack to bundle my files. And I want to add a script that will enable me to change settings in the customizer on the fly.</p>
  
  <p>I created a class which will hold all my customizer functionality</p>
  
  <pre><code>&lt;?php
  /**
   * The Customizer specific functionality.
   *
   * @since   1.0.0
   * @package mytheme
   */
  
  namespace MyTheme\Customizer;
  
  use MyTheme\Helpers as Helpers;
  
  /**
   * Class Customizer
   */
  class Customizer {
  
    /**
     * Global theme name
     *
     * @var string
     *
     * @since 1.0.0
     */
    protected $theme_name;
  
    /**
     * Global theme version
     *
     * @var string
     *
     * @since 1.0.0
     */
    protected $theme_version;
  
    /**
     * Initialize class
     *
     * @param array                  $theme_info Load global theme info.
     * @param Helpers\General_Helper $helpers Instance of the General Helpers object.
     *
     * @since 1.0.0
     */
    public function __construct( $theme_info = null, Helpers\General_Helper $helpers ) {
      $this-&gt;theme_name    = $theme_info['theme_name'];
      $this-&gt;theme_version = $theme_info['theme_version'];
  
      $this-&gt;helpers = $helpers;
  
    }
  
    /**
     * Register customizer settings
     *
     * @see add_action('customize_register',$func)
     * @param  \WP_Customize_Manager $wp_customize WP Customize object.
     * @since 1.0.0
     */
    public function register_customizer_settings( \WP_Customize_Manager $wp_customize ) {
      // Abort if selective refresh is not available.
      if ( ! isset( $wp_customize-&gt;selective_refresh ) ) {
        return;
      }
  
      /**
       * Footer section
       */
      $wp_customize-&gt;add_section( 'footer_section', array(
          'title'    =&gt; esc_html__( 'Footer', 'mytheme' ),
          'priority' =&gt; 100,
      ) );
  
      /**
       * Copyright notice
       */
      $wp_customize-&gt;add_setting( 'footer_copyright', array(
          'default'           =&gt; '',
          'type'              =&gt; 'theme_mod',
          'transport'         =&gt; 'postMessage',
          'sanitize_callback' =&gt; 'wp_kses_post',
      ) );
  
      $wp_customize-&gt;add_control( 'footer_copyright', array(
          'label'       =&gt; esc_html__( 'Footer Copyright Text', 'mytheme' ),
          'section'     =&gt; 'footer_section',
          'type'        =&gt; 'text',
      ) );
    }
  
    /**
     * Enqueue live preview script
     *
     * @since 1.0.0
     */
    public function live_preview_enqueue() {
      $customizer_script = '/skin/public/scripts/customizer.js';
  
      wp_register_script( $this-&gt;theme_name . '-customizer', get_template_directory_uri() . $customizer_script, array(), $this-&gt;helpers-&gt;get_assets_version( $customizer_script ) );
      wp_enqueue_script( $this-&gt;theme_name . '-customizer', array( 'jquery', 'customize-preview' ), false );
    }
  }
  </code></pre>
  
  <p>In another class where I load my actions I have</p>
  
  <pre><code>private function define_customizer_hooks() {
    $customizer = new Customizer\Customizer( $this-&gt;get_theme_info(), new Helpers\General_Helper() );
  
    $this-&gt;loader-&gt;add_action( 'customize_register', $customizer, 'register_customizer_settings', 11 );
    $this-&gt;loader-&gt;add_action( 'customize_preview_init', $customizer, 'live_preview_enqueue' );
  }
  </code></pre>
  
  <p>Now, I can see my settings just fine. The <code>Footer section</code> is showing. But I get an error in my console</p>
  
  <blockquote>
    <p>jQuery.Deferred exception: wp.customize is not a function TypeError: wp.customize is not a function</p>
  </blockquote>
  
  <p>The <strong>customizer.js</strong> looks like this</p>
  
  <pre><code>/* global wp */
  $(function() {
    wp.customize('footer_copyright', (value) =&gt; {
      value.bind((newval) =&gt; {
        if ($('.footer__copyright').length) {
          if (newval !== '') {
            if ($('.footer__copyright').hasClass('hide')) {
              $('.footer__copyright').removeClass('hide').text(newval);
            } else {
              $('.footer__copyright').text(newval);
            }
          } else {
            $('.footer__copyright').addClass('hide');
          }
        } else {
          $('.footer__container').append(`&lt;div class="footer__copyright"&gt;${newval}&lt;/div&gt;`);
        }
      });
    });
  });
  </code></pre>
  
  <p>And my <strong>webpack.config.js</strong> looks like this:</p>
  
  <pre><code>/* global process __dirname */
  
  const DEV = process.env.NODE_ENV !== 'production';
  
  const path = require('path');
  const webpack = require('webpack');
  
  const CleanWebpackPlugin = require('clean-webpack-plugin');
  const MiniCssExtractPlugin = require('mini-css-extract-plugin');
  const BrowserSyncPlugin = require('browser-sync-webpack-plugin');
  const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
  
  const appPath = `${path.resolve(__dirname)}`;
  
  // Dev Server
  const proxyUrl = 'mytheme.test';
  
  // Theme
  const themeName = 'mytheme';
  const themePath = `/wp-content/themes/${themeName}/skin`;
  const themeFullPath = `${appPath}${themePath}`;
  const themePublicPath = `${themePath}/public/`;
  const themeEntry = `${themeFullPath}/assets/application.js`;
  const themeAdminEntry = `${themeFullPath}/assets/application-admin.js`;
  const themeGutenbergEntry = `${themeFullPath}/assets/application-gutenberg.js`;
  const themeCustomizerEntry = `${themeFullPath}/assets/customizer.js`;
  const themeOutput = `${themeFullPath}/public`;
  
  // Outputs
  const outputJs = 'scripts/[name].js';
  const outputCss = 'styles/[name].css';
  const outputFile = '[name].[ext]';
  const outputImages = `images/${outputFile}`;
  const outputFonts = `fonts/${outputFile}`;
  
  const allModules = {
    rules: [
      {
        test: /\.(js|jsx)$/,
        use: 'babel-loader',
        exclude: /node_modules/,
      },
      {
        test: /\.json$/,
        exclude: /node_modules/,
        use: 'file-loader',
      },
      {
        test: /\.(png|svg|jpg|jpeg|gif|ico)$/,
        exclude: [/fonts/, /node_modules/],
        use: `file-loader?name=${outputImages}`,
      },
      {
        test: /\.(eot|otf|ttf|woff|woff2|svg)$/,
        exclude: [/images/, /node_modules/],
        use: `file-loader?name=${outputFonts}`,
      },
      {
        test: /\.scss$/,
        exclude: /node_modules/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader', 'postcss-loader', 'sass-loader',
        ],
      },
    ],
  };
  
  const allPlugins = [
    new MiniCssExtractPlugin({
      filename: outputCss,
    }),
  
    new webpack.ProvidePlugin({
      $: 'jquery',
      jQuery: 'jquery',
    }),
  
    // Use BrowserSync For assets
    new BrowserSyncPlugin({
      host: 'localhost',
      port: 3000,
      proxy: proxyUrl,
      files: [
        {
          match: ['wp-content/themes/**/*.php', 'wp-content/plugins/**/*.php'],
        },
      ],
    }),
  
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: JSON.stringify(process.env.NODE_ENV || 'development'),
      },
    }),
  
    new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
  ];
  
  const allOptimizations = {
    runtimeChunk: false,
    splitChunks: {
      cacheGroups: {
        commons: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
        },
      },
    },
  };
  
  // Use only for production build
  if (!DEV) {
    allOptimizations.minimizer = [
      new UglifyJsPlugin({
        cache: true,
        parallel: true,
        sourceMap: true,
        uglifyOptions: {
          output: {
            comments: false,
          },
          compress: {
            warnings: false,
            drop_console: true, // eslint-disable-line camelcase
          },
        },
      }),
    ];
    allPlugins.push(new CleanWebpackPlugin([themeOutput]));
  }
  
  module.exports = [
  
    // Theme Skin
    {
      context: path.join(__dirname),
      entry: {
        application: [themeEntry],
        applicationAdmin: [themeAdminEntry],
        themeGutenberg: [themeGutenbergEntry],
        customizer: [themeCustomizerEntry],
      },
      output: {
        path: themeOutput,
        publicPath: themePublicPath,
        filename: outputJs,
        library: ['wp', '[name]'],
        libraryTarget: 'var',
      },
  
      externals: {
        wp: 'wp',
      },
  
      optimization: allOptimizations,
  
      mode: 'production',
  
      module: allModules,
  
      plugins: allPlugins,
  
      devtool: DEV ? '#inline-source-map' : '',
    },
  ];
  </code></pre>
  
  <p>and <strong>package.json</strong> is</p>
  
  <pre><code>{
    "name": "mytheme",
    "version": "1.0.0",
    "author": "dingo_d",
    "private": true,
    "main": "",
    "scripts": {
      "__sassUnusedTheme": "sass-unused \"wp-content/themes/init_theme_name/**/*.scss\"",
      "__eslintTheme": "eslint wp-content/themes/init_theme_name/skin/assets/",
      "__stylelintTheme": "stylelint \"wp-content/themes/init_theme_name/**/*.scss\" --syntax scss",
      "precommitCss": "npm run __stylelintTheme",
      "precommitCssUnused": "npm run __sassUnusedTheme",
      "precommitJs": "npm run __eslintTheme",
      "precommit": "npm run precommitJs &amp;&amp; npm run precommitCss &amp;&amp; npm run precommitCssUnused",
      "start": "webpack --progress --watch --display-error-details --display-reasons",
      "build": "NODE_ENV=production webpack --progress"
    },
    "devDependencies": {
      "@infinumjs/eslint-config": "^1.1.0",
      "@infinumjs/stylelint-config": "^1.0.0",
      "babel-core": "^6.26.3",
      "babel-eslint": "^8.2.3",
      "babel-loader": "^7.1.4",
      "babel-plugin-syntax-dynamic-import": "^6.18.0",
      "babel-preset-env": "^1.6.1",
      "browser-sync": "^2.24.1",
      "browser-sync-webpack-plugin": "^2.2.2",
      "clean-webpack-plugin": "^0.1.19",
      "copy-webpack-plugin": "^4.5.1",
      "css-loader": "^0.28.11",
      "eslint": "^4.19.1",
      "eslint-plugin-import": "^2.11.0",
      "expose-loader": "^0.7.5",
      "extract-text-webpack-plugin": "^3.0.2",
      "file-loader": "^1.1.11",
      "imports-loader": "^0.8.0",
      "mini-css-extract-plugin": "^0.4.0",
      "node-sass": "^4.9.0",
      "postcss-cssnext": "^3.1.0",
      "postcss-loader": "^2.1.4",
      "precss": "^3.1.2",
      "sass-loader": "^7.0.1",
      "style-loader": "^0.21.0",
      "stylelint": "^9.2.0",
      "uglifyjs-webpack-plugin": "^1.2.5",
      "webpack": "^4.6.0",
      "webpack-cli": "^2.1.2"
    },
    "dependencies": {
      "autoprefixer": "^8.4.1",
      "babel-polyfill": "^6.26.0",
      "bugsnag-js": "^4.6.0",
      "css-mqpacker": "^6.0.2",
      "cssnano": "^3.10.0",
      "jquery": "^3.3.1",
      "jquery-match-height": "^0.7.2",
      "layzr.js": "^2.2.2",
      "media-blender": "^2.1.0",
      "normalize.css": "^8.0.0",
      "npm": "^6.0.0",
      "postcss-font-magician": "^2.1.1",
      "slick-carousel": "^1.8.1",
      "whatwg-fetch": "^2.0.4"
    },
    "browserslist": [
      "android &gt;= 4.2",
      "not ie &lt; 11",
      "last 2 versions",
      "Safari &gt;= 8"
    ]
  }
  </code></pre>
  
  <p>The thing that bothers me is that I basically used (and modified) my code that works in the theme that is built without all the fancy build tools (<a href="https://themes.svn.wordpress.org/expire/1.0.9/inc/customizer/js/customizer.js" rel="nofollow noreferrer">https://themes.svn.wordpress.org/expire/1.0.9/inc/customizer/js/customizer.js</a>)</p>
  
  <p>I tried with </p>
  
  <pre><code>/* global wp */
  (function(api) {
    api('footer_copyright', (value) =&gt; {
      value.bind((newval) =&gt; {
        if ($('.footer__copyright').length) {
          if (newval !== '') {
            if ($('.footer__copyright').hasClass('hide')) {
              $('.footer__copyright').removeClass('hide').text(newval);
            } else {
              $('.footer__copyright').text(newval);
            }
          } else {
            $('.footer__copyright').addClass('hide');
          }
        } else {
          $('.footer__container').append(`&lt;div class="footer__copyright"&gt;${newval}&lt;/div&gt;`);
        }
      });
    });
  })(wp.customize);
  </code></pre>
  
  <p>But again, I got </p>
  
  <blockquote>
    <p>Uncaught ReferenceError: wp is not defined</p>
  </blockquote>
  
  <p>And in the inspector, probably after everything is loaded the <code>wp.customize</code> is available (<code>ƒ (){return f.instance.apply(f,arguments)}</code>).</p>
  
  <p>I followed advice given by the <a href="https://github.com/WordPress/gutenberg/blob/9870e81b6298ba79272002acf92e9ee326e1458c/webpack.config.js#L133-L134" rel="nofollow noreferrer">Gutenberg</a> team, who are using webpack, but I'm not sure I'm doing it right.</p>