Search code examples
javascriptruby-on-railswebpackzxing

Understanding Rails with webpacker and incidence on JS


A rails 6 application calls from a legacy application being migrated:

<%= javascript_pack_tag 'zxing.js' %>
<script type="text/javascript">
  window.addEventListener('load', function () {
    let selectedDeviceId;
    const codeReader = new ZXing.BrowserMultiFormatReader()
    console.log('ZXing code reader initialized')
    codeReader.listVideoInputDevices()
      .then((videoInputDevices) => {
        const sourceSelect = document.getElementById('sourceSelect')
        selectedDeviceId = videoInputDevices[0].deviceId
        if (videoInputDevices.length >= 1) {
          videoInputDevices.forEach((element) => {
            const sourceOption = document.createElement('option')
            sourceOption.text = element.label
            sourceOption.value = element.deviceId
            sourceSelect.appendChild(sourceOption)
          })

          sourceSelect.onchange = () => {
            selectedDeviceId = sourceSelect.value;
          };

          const sourceSelectPanel = document.getElementById('sourceSelectPanel')
          sourceSelectPanel.style.display = 'block'
        }

        document.getElementById('startButton').addEventListener('click', () => {
          codeReader.decodeFromVideoDevice(selectedDeviceId, 'video', (result, err) => {
            if (result) {
              console.log(result)
              document.getElementById('result').textContent = result.text
let formData = new FormData();
let CodeParams = {
code_data: result.text,
shopkeeper_id:  <%= current_user.id %>
};
formData.append("code_json_data", JSON.stringify(CodeParams));
$.ajax({
url: "new_moviment",
type: "post",
data: formData,
   processData: false,
  contentType: false,
 });
            }
            if (err && !(err instanceof ZXing.NotFoundException)) {
              console.error(err)
              document.getElementById('result').textContent = err
            }
          })
          console.log(`Started continous decode from camera with id ${selectedDeviceId}`)
        })

        document.getElementById('resetButton').addEventListener('click', () => {
          codeReader.reset()
          document.getElementById('result').textContent = '';
          console.log('Reset.')
        })

      })
      .catch((err) => {
        console.error(err)
      })
  })

The javascript is loading, but the script for the page is not Uncaught ReferenceError: ZXing is not defined

THe ZXing documentation does provide different code bases according to whether one is Use on browser with ES6 modules or AMD, UMD, or CommonJS.

I am under the impression that with the move to webpacker, Rails is pre-processing under ES6. But that impression is confused by the fact that there is browser-based code and module-based code.

How should this script be edited (and are there - for JS-knotheads - online tools to allow to identify and change syntax for ES6 ?)

Update

After a yarn add @zxing/library packs/application.js edited to

require("@zxing/library")
import("../src/qr_scan")

src/qr_scan.js is now as follows:

import { BrowserMultiFormatReader, NotFoundException } from '@zxing/library';

window.addEventListener('load', function () {
  let selectedDeviceId;
  const codeReader = new BrowserMultiFormatReader() // note the updated module reference
    console.log('ZXing code reader initialized')
    codeReader.listVideoInputDevices()
      .then((videoInputDevices) => {
        const sourceSelect = document.getElementById('sourceSelect')
        selectedDeviceId = videoInputDevices[0].deviceId
        if (videoInputDevices.length >= 1) {
          videoInputDevices.forEach((element) => {
            const sourceOption = document.createElement('option')
            sourceOption.text = element.label
            sourceOption.value = element.deviceId
            sourceSelect.appendChild(sourceOption)
          })

          sourceSelect.onchange = () => {
            selectedDeviceId = sourceSelect.value;
          };

          const sourceSelectPanel = document.getElementById('sourceSelectPanel')
          sourceSelectPanel.style.display = 'block'
        }

        document.getElementById('startButton').addEventListener('click', () => {
          codeReader.decodeFromVideoDevice(selectedDeviceId, 'video', (result, err) => {
            if (result) {
              console.log(result)
              document.getElementById('result').textContent = result.text

              let formData = new FormData();
              let CodeParams = {
              code_data: result.text
              };
              formData.append("code_json_data", JSON.stringify(CodeParams));
              $.ajax({
              url: 'new_user',
              type: "post",
              data: formData,
                 processData: false,
                contentType: false,
               });
            }
           if (err && !(err instanceof ZXing.NotFoundException)) {
              console.error(err)
              document.getElementById('result').textContent = err
            }
          })
          console.log(`Started continous decode from camera with id ${selectedDeviceId}`)
        })

        document.getElementById('resetButton').addEventListener('click', () => {
          codeReader.reset()
          document.getElementById('result').textContent = '';
          console.log('Reset.')
        })

      })
      .catch((err) => {
        console.error(err)
      })
  })

and the view page has no more javascript references. Loading the page however leads to the following error in the console:

Uncaught TypeError: _zxing_library__WEBPACK_IMPORTED_MODULE_0__.BrowserMultiFormatReader is not a constructor

pointing to codeReader line. The page was processed with webpacker attempting to compile.

Although js/zxing-3544fb3b633f87715d29.js 528 KiB zxing [emitted] [immutable] zxing is successfully built ([./app/javascript/packs/zxing.js] 524 KiB {zxing} [built]), both the import functions failed:

ERROR in ./app/javascript/src/qr_scan.js 4:23-47
"export 'BrowserMultiFormatReader' was not found in '@zxing/library'
 @ ./app/javascript/packs/application.js

ERROR in ./app/javascript/src/qr_scan.js 47:36-53
"export 'NotFoundException' was not found in '@zxing/library'
 @ ./app/javascript/packs/application.js

searches on those strings in the zxing compiled file only reveal two instances:

e.BrowserMultiFormatReader = s;
e.NotFoundException = l["default"]; 

leading naturally to a console error:

window.addEventListener('load', function () {
  var selectedDeviceId;
  var codeReader = new _zxing_library__WEBPACK_IMPORTED_MODULE_0__["BrowserMultiFormatReader"](); // note the updated module reference

Webpacker finally compiled when reverting to the library's documentation referenced command

const codeReader = new BrowserQRCodeReader() // note the updated module reference
//  const codeReader = new BrowserMultiFormatReader() // note the updated module reference

but now, there is a console error:

Uncaught ReferenceError: BrowserQRCodeReader is not defined

pointing to var codeReader = new BrowserQRCodeReader();


Solution

  • JavaScript modules in webpack are not exported to the global scope, i.e., when using Webpacker with Rails, you can't reference something like ZXing from a <script> tag in an ERB template by default.

    Instead, you would move all your JavaScript to the app/javascript directory and update your ZXing (and other library) references to use module imports.

    Rails will treat everything in the "packs" folder as a separate bundle. Start with just an application pack that points to your source code:

    app/
      javascript/
        packs/
          application.js
        src/
          my_barcode.js
    
    // app/javascript/packs/application.js
    
    import '../src/my_barcode'
    
    // app/javascript/src/my_barcode.js
    
    // Based off the module imports described in the ZXing README
    import { BrowserMultiFormatReader, NotFoundException } from '@zxing/library/esm';
    
    window.addEventListener('load', function () {
      let selectedDeviceId;
      const codeReader = new BrowserMultiFormatReader() // note the updated module reference
      // and so on ...
    

    And in your application layout:

    <%= javascript_pack_tag 'application' %>