Vue.js CLI and Papaparse for CSVs

I am working on a side project for work and decided to move the proof-of-concept to Vue.js.  After several weeks of spinning up on the Vue framework and Webpack, I've started implementing.

Issue #1: Can't use <script> tag to load Papa Parse in Webpack

In the original project I used Papa Parse to parse CSVs by including it with a <script> tag.  In Vue CLI, it's a bit more involved.  You need a file loader (Webpack's csv-loader in this case), which runs Papa Parse for you (after you configure vue.config.js to use the Webpack loader via chainWebpack).  So you can do something like this in your main.js file:

import Papa from "@/assets/data/mycsv.csv";

Which loaded the file as JSON as desired; however, I needed to dynamically load CSVs depending on a user selection.

Issue #2: Conditionally loading CSVs

After several frustrating hours, I discovered the dynamic import() feature of JavaScript which allows you to load modules conditionally at runtime. Jackpot.  This post helped me quite a bit.

Great.  So this worked for me:

import("@/assets/data/myFile.csv")
.then( ({default: data}) => { 
    console.log("data: ", data); 
});

Issue #3: Using a variable to conditionally load files or modules in webpack

However, what if I wanted "myFile" to be "myFile2" etc.?  Well, webpack makes this a bit more difficult than simply using a variable.  The following does NOT work in webpack:

  let file = "@/assets/data/myFile.csv";
  import(file)
  .then( ({default: data}) => { 
      console.log("data: ", data); 
  })
  .catch( error => console.log(error) );

The REASON is this: webpack needs to have a good idea what to bundle up when running your program.  When your path is in a variable, it has no idea what file or subdirectory to bundle, and thus throws an error.

From the webpack docs:

It is not possible to use a fully dynamic import statement, such as import(foo). Because foo could potentially be any path to any file in your system or project.

The import() must contain at least some information about where the module is located. Bundling can be limited to a specific directory or set of files so that when you are using a dynamic expression - every module that could potentially be requested on an import() call is included. For example, import(`./locale/${language}.json`) will cause every .json file in the ./locale directory to be bundled into the new chunk. At run time, when the variable language has been computed, any file like english.json or german.json will be available for consumption.


What we need to do is to give it a *hint* by using string interpolation, as shown above (more on this below).

Solution

This is my main.js file:

import Vue from "vue";
import BootstrapVue from "bootstrap-vue";
import App from "./App.vue";
import router from "./router";
import "bootstrap/dist/css/bootstrap.css";
import "bootstrap-vue/dist/bootstrap-vue.css";

Vue.use(BootstrapVue);
Vue.config.productionTip = false;

new Vue({
  router,
  mounted() {
  	let file = "myFile";
  	import(`@/assets/data/${file}.csv`)
  	.then( ({default: data}) => { 
  		console.log("data: ", data); 
  	})
  	.catch( error => console.log(error) );
  },
  render: h => h(App)
}).$mount("#app");

Let's look at what's going on here.

mounted()
  • We are using Vue's mounted() lifecycle hook to test this code out in our main.js file (entry point of the program).  
  • function() is ES6 shortcut syntax for mounted: function() {}
import()
  • If you're new to Vue CLI, the @ symbol is a shortcut to the 'src' folder
  • We are using string interpolation to drop ONLY the filename into the import statement, which allows webpack to know what folder to bundle
.then()
  • import() returns a JavaScript promise object which you can chain .then's onto in order to process.  We need .then() because the import is asynchronous so we have no idea when the data will return, hence the 'promise'.  Within the .then(), we are simply logging the file contents to the console
Arrow function ( i.e., => )
  • Arrow functions are ES6 shortcut syntax for anonymous functions: function() {...} can be rewritten as: () => {...}
Object destructuring
  • Within the arrow function, we are destructuring the Promise object.  The feature we are using is the ability to rename 'default' to whatever we want, in this case we are renaming it to 'data'.  If you are not familiar with javascript modules, you can define a 'default' export from your module if no named import is used - we are simply renaming default here. 
.catch()
  • Simple error handling logged to the browser's console

The next part is simply implementing the dynamic import in the Vue Single File Components (.vue files), and use the data.

Hope this helps someone out there.

Comments

Popular posts from this blog

Could NOT find ZMusic (missing: ZMUSIC_LIBRARIES ZMUSIC_INCLUDE_DIR)

Vue Js & Papa Parse (Part II): Parsing server side CSVs without webpack / vue-cli bundling / code splitting

Mechanical Keyboard Review: Varmilo VA108M